trestle 0.11.1

  • README.md
  • CHANGELOG.md
  • Installing
  • Versions
  • 70

Trestle

Database gateway and ORM for Dart

Build Status



Abstract

Trestle is the database package used in Bridge. It was created with extensibility and clean API in mind. Providing a unified interface to work with different databases across multiple setups for maximum reusability and agility.

The package is divided into two parts – the Gateway and the ORM. The Gateway is the common abstraction that the different database drivers implement, and the ORM uses the Gateway to talk to the database.

The Gateway has both a Schema Builder and a Query Builder, accessible from the common Gateway class.

One of the more controversial features of Trestle are the so called Predicate Expressions. They are callback-style lambda functions that are translated into SQL constraints. So we can say where((user) => user.age > 20), which then gets parsed into something like WHERE "age" > 20. An it works with pretty complex functions! As soon as you create a predicate that's too complex, the runtime will tell you in time, so that you can straighten things out.

Just know that Trestle doesn't get all rows and then run the constraint, even though that's what it looks like.


Getting started

To get started, choose what database implementation you want to use (you can easily change your mind later). In this example, we use the InMemoryDriver. It doesn't need schema and it doesn't need any configuration.

import 'package:trestle/gateway.dart';

main() async {
  // The database implementation
  Driver driver = new InMemoryDriver();

  // The gateway takes the driver as a constructor argument
  Gateway gateway = new Gateway(driver);

  // Next, connect!
  await gateway.connect();

  // ... Do some work

  // Disconnect when you're done
  await gateway.disconnect();
}

Later, if we want, we can just swap out the driver and call it a day.

// Driver driver = new InMemoryDriver();
// Driver driver = new SqliteDriver('storage/production.db');
// Driver driver = new MySqlDriver(username: 'myuser', password: '123', database: 'mydatabase');
Driver driver = new PostgresqlDriver(username: 'myuser', password: '123', database: 'mydatabase');

Gateway

Think of the gateway as the actual database in SQL. It contains the tables, which can be accessed and modified using a few simple methods.

Creating a table

To create a new table we use the create method on the Gateway class. This method takes two parameters: the name of the table to be created, and a callback containing the Schema Builder. It looks like this:

await gateway.create('users', (Schema schema) {
  schema.id(); // shortcut for an auto incrementing integer primary key
  schema.string('email').unique().nullable(false);
  schema.string('username').unique().nullable(false);
  schema.string('password', 60);
  schema.timestamps(); // adds created_at and updated_at timestamps (used by the ORM)
});

This method returns a Future (much like everything else in Trestle), and should probably be await-ed.

Altering a table

Altering a table is almost identical to creating one, except we use the alter method instead:

await gateway.alter('users', (Schema schema) {
  schema.drop('username');
  schema.string('first_name');
  schema.string('last_name');
});

Deleting a table

Deleting (or dropping) a table could not be simpler:

await gateway.drop('users');

Accessing a table

When we're satisfied with the columns of our table, we can start a query by calling the table method. This starts up the Query Builder, providing a fluent API to construct queries. The builder is stateless, so we can save intermediate queries in variables and fork them later:

// Full query
Stream allUsersOfDrinkingAge = gateway.table('users')
  .where((user) => user.age > 18).get(); // At least in Sweden...

// Intermediate query
Query uniqueAddresses = gateway.table('addresses').distinct();

// Continued query
Stream allUniqueAddressesInSweden = uniqueAddresses
  .where((address) => address.country == 'SWE').get();

// A function extending an intermediate query
Query allUniqueAddressesIn(String country) {
  return uniqueAddresses
    .where((address) => address.country == country);
}

// An aggregate query
int count = await allUniqueAddressesIn('USA').count();

There's a bunch of stuff you can do. Experiment with the query builder and report any bugs! 🐛


Migrations

You can think of migrations as version control for your database. It's an automated way to ensure that everyone on your team is using the same table schema. Each migration extends the Migration abstract class, enforcing the implementation of a run method, as well as a rollback method.

The run method makes a change to the database schema (using the familiar syntax). The rollback method reverses that change. For example, creating a table in run, and dropping it in rollback.

By storing a Set<Type> (where the types are subtypes of Migration), we can ensure that each migration is run in order. And if we need to change something, we can roll back and re-migrate.

class CreateUsersTable extends Migration {
  Future run(Gateway gateway) {
    gateway.create('users', (Schema schema) {
      schema.id();
      schema.string('email');
      // ...
    });
  }

  Future rollback(Gateway gateway) {
    gateway.drop('users');
  }
}

final migrations = [
  CreateUsersTable,
  // more migrations
  CreateAddressesTable,
  DropUsernameColumnInUsersTable,
].toSet();

// Somewhere in a command line utility or something
gateway.migrate(migrations);

// Somewhere else – remember to import the same migrations set
gateway.rollback(migrations);

ORM

Trestle's primary feature is to provide an ORM for the Bridge Framework. One of the key features of Bridge is the WebSocket transport system Tether. So it was important that Trestle would be able to map rows to plain Dart objects, that could be shared with the client.

So instead of embracing the full Active Record style, we had to move the database interaction from the data structures to a Repository class. However, using a plain object without any intrusive annotations is kind of brittle. So we can optionally extend a Model class and use annotations if we don't care that we're coupling ourselves to Trestle. It works like this:

// Create a data structure
class Parent {
  int id;
  String email;
  String firstName;
  String lastName;
  String password;
  int age;
}

// Or a value object
class Parent {
  // Override the table name with a constant "table" on
  // any of these types of models
  static const String table = 'my_own_table_name';

  final int id;
  final String email;
  final String firstName;
  final String lastName;
  final String password;
  final int age;

  const Parent(this.id, this.email, this.firstName,
             this.lastName, this.password, this.age);
}

// Or create a full model
class Parent extends Model {
  @field String email;
  @field String firstName;
  @field String lastName;
  @field String password;
  @field int age;

  // Relationships are very expressive. Here, all Child models
  // whose table rows has a key "parent_id" matching this model's
  // "id" field, are eager loaded to this List.
  @hasMany List<Child> children;

  // You can also lazy load the children by setting the property
  // type to Stream<Child>, or (if you want to perform queries on
  // the children) to RepositoryQuery<Child>.
}

class Child extends Model {
  // Single relationships can be annotated as either `Child` (eager)
  // or `Future<Child>` (lazy).
  @belongsTo Parent parent;
  @belongsTo Future<Parent> parent;
}

// Instantiate the repository with a gateway as an argument and the model as a type argument.
final parents = new Repository<Parent>(gateway);

// You're done! The repository works like `gateway.table('parents')` would,
// but it returns `Parent` objects instead of maps.
Parent parent = await parents.find(1);

// The relationships are mapped automatically.
Child child = parent.children.first;

print(child.parent == parent); // true
print(parent.child == child); // true

Extending the repository

We can use this class to implement some query scopes or filters:

class UsersRepository extends Repository<User> {
  RepositoryQuery<User> get ofDrinkingAge => where((user) => user.age > 20);
}

// And use it like so:
users.ofDrinkingAge.count();

In Bridge

As (soon to be) mentioned in the Bridge docs, Trestle is automatically set up for you, so we can use dependency injection to get immediate access to a repository:

// An example in the context of the HTTP router – not a part of Trestle
router.get('/users/count', (Repository<User> users) async {
  return 'There are ${await users.count()} users registered';
});

0.11.0

Bub fixes

  • Fix bug #7 where models could not have void methods.

0.10.0

Additions

  • Added back SQLite support via the newly updated sqlite package.
final gateway = new Gateway(
  // driver: new SqliteDriver.inMemory()
  driver: new SqliteDriver('path/to/database.db')
)

0.9.0

Bug fixes

  • The .unique() constraint on a schema column now correctly outputs UNIQUE in SQLDriver.

0.7.0

Breaking!

  • Removing SQLite implementation because it is simply not working well. Apps depending on the SQLite driver must stay <0.7.0.

Additions

  • Added a JSON data type (#5)

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  trestle: "^0.11.1"

2. Install it

You can install packages from the command line:

with pub:


$ pub get

Alternatively, your editor might support pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:trestle/trestle.dart';
        
Version Uploaded Documentation Archive
0.11.1 Oct 30, 2016 Go to the documentation of trestle 0.11.1 Download trestle 0.11.1 archive
0.10.0 Sep 16, 2016 Go to the documentation of trestle 0.10.0 Download trestle 0.10.0 archive
0.9.0 Sep 12, 2016 Go to the documentation of trestle 0.9.0 Download trestle 0.9.0 archive
0.8.0 Jun 16, 2016 Go to the documentation of trestle 0.8.0 Download trestle 0.8.0 archive
0.7.0 Mar 10, 2016 Go to the documentation of trestle 0.7.0 Download trestle 0.7.0 archive
0.6.0 Nov 5, 2015 Go to the documentation of trestle 0.6.0 Download trestle 0.6.0 archive
0.5.8 Oct 16, 2015 Go to the documentation of trestle 0.5.8 Download trestle 0.5.8 archive
0.5.6 Oct 16, 2015 Go to the documentation of trestle 0.5.6 Download trestle 0.5.6 archive
0.5.5+1 Oct 9, 2015 Go to the documentation of trestle 0.5.5+1 Download trestle 0.5.5+1 archive
0.5.5 Oct 9, 2015 Go to the documentation of trestle 0.5.5 Download trestle 0.5.5 archive

All 18 versions...

Analysis

We analyzed this package on Apr 23, 2018, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.0.0-dev.49.0
  • pana: 0.10.6

Scores

Popularity:
Describes how popular the package is relative to other packages. [more]
69 / 100
Health:
Code health derived from static analysis. [more]
90 / 100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
46 / 100
Overall score:
Weighted score of the above. [more]
70
Learn more about scoring.

Platforms

Detected platforms: web, other

Primary library: package:trestle/trestle.dart with components: mirrors.

Suggestions

  • Fix analysis and formatting issues.

    Analysis or formatting checks reported 11 errors 33 hints.

    Strong-mode analysis of lib/src/drivers/in_memory_driver.dart failed with the following error:

    line: 152 col: 27
    The argument type '(Map<dynamic, dynamic>) → Map<dynamic, dynamic>' can't be assigned to the parameter type '(dynamic) → dynamic'.

    Strong-mode analysis of lib/src/gateway/create_actions.dart failed with the following error:

    line: 22 col: 3
    Invalid override. The type of '_CreateActions.add' ('(Map<String, dynamic>) → Future<dynamic>') isn't a subtype of 'CreateActions.add' ('(Map<String, dynamic>) → Future<int>').

    Similar analysis of the following files failed:

    • lib/src/gateway/migrator.dart (error)
    • lib/src/gateway/query.dart (error)
    • lib/src/orm/lazy_future.dart (error)
    • lib/src/orm/model.dart (error)
    • lib/src/drivers/my_sql_driver.dart (hint)
    • lib/src/drivers/postgresql_driver.dart (hint)
    • lib/src/drivers/sql_driver.dart (hint)
    • lib/src/drivers/sql_standards.dart (hint)
    • lib/src/gateway/aggregates.dart (hint)
    • lib/src/gateway/constraints.dart (hint)
    • lib/src/gateway/constraints/constraints.dart (hint)
    • lib/src/gateway/driver.dart (hint)
    • lib/src/gateway/gateway.dart (hint)
    • lib/src/gateway/predicate_parser.dart (hint)
    • lib/src/gateway/read_actions.dart (hint)
    • lib/src/gateway/update_actions.dart (hint)
    • lib/src/orm/maps_fields_to_object/maps_fields_to_data_structure.dart (hint)
    • lib/src/orm/maps_fields_to_object/maps_fields_to_model.dart (hint)
    • lib/src/orm/maps_fields_to_object/maps_fields_to_object.dart (hint)
    • lib/src/orm/relationships/many_to_many_relationship.dart (hint)
    • lib/src/orm/relationships/many_to_one_relationship.dart (hint)
    • lib/src/orm/relationships/one_to_many_relationship.dart (hint)
    • lib/src/orm/relationships/one_to_one_relationship.dart (hint)
    • lib/src/orm/relationships/relationship_declaration.dart (hint)
    • lib/src/orm/repository.dart (hint)
    • lib/src/orm/repository_query.dart (hint)
  • The description is too short.

    Add more detail about the package, what it does and what is its target use case. Try to write at least 60 characters.

  • Package is pre-v1 release.

    While there is nothing inherently wrong with versions of 0.*.*, it usually means that the author is still experimenting with the general direction API.

  • Maintain an example.

    None of the files in your example/ directory matches a known example patterns. Common file name patterns include: main.dart, example.dart or you could also use trestle.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=1.12.0 <2.0.0
postgresql ^0.3.0 0.3.4+1
sqlite ^0.4.0 0.4.2
sqljocky ^0.14.0 0.14.1
Transitive dependencies
args 0.13.7 1.4.2
async 2.0.6
charcode 1.1.1
collection 1.14.9
convert 1.1.1 2.0.1
crypto 0.9.2+1 2.0.2+1
http 0.11.3+16
http_parser 3.1.1
logging 0.11.3+1
options_file 0.11.0
path 1.5.1
source_span 1.4.0
string_scanner 1.0.2
typed_data 1.1.5
yaml 2.1.13
Dev dependencies
test any