Skip to content
This repository has been archived by the owner on Feb 17, 2020. It is now read-only.

angel-dart-archive/orm

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 

orm

Pub build status

Source-generated PostgreSQL ORM for use with the Angel framework. Now you can combine the power and flexibility of Angel with a strongly-typed ORM.

Documentation for migrations can be found here: https://angel-dart.gitbook.io/angel/v/2.x/orm/migrations

Usage

You'll need these dependencies in your pubspec.yaml:

dependencies:
  angel_orm: ^2.0.0-dev
dev_dependencies:
  angel_orm_generator: ^2.0.0-dev
  build_runner: ^1.0.0

package:angel_orm_generator exports a class that you can include in a package:build flow:

  • PostgresOrmGenerator - Fueled by package:source_gen; include this within a SharedPartBuilder.

However, it also includes a build.yaml that builds ORM files automatically, so you shouldn't have to do any configuration at all.

Models

The ORM works best when used with package:angel_serialize:

library angel_orm.test.models.car;

import 'package:angel_migration/angel_migration.dart';
import 'package:angel_model/angel_model.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize/angel_serialize.dart';
part 'car.g.dart';

@serializable
@orm
abstract class _Car extends Model {
  String get make;

  String get description;

  bool get familyFriendly;

  DateTime get recalledAt;
}

// You can disable migration generation.
@Orm(generateMigrations: false)
abstract class _NoMigrations extends Model {}

Models can use the @SerializableField() annotation; package:angel_orm obeys it.

After building, you'll have access to a Query class with strongly-typed methods that allow to run asynchronous queries without a headache.

Remember that if you don't need automatic id-and-date fields, you can simply just not extend Model:

@Serializable(autoIdAndDateFields: false)
abstract class _ThisIsNotAnAngelModel {
  @primaryKey
  String get username;
}

Example

MVC just got a whole lot easier:

import 'package:angel_framework/angel_framework.dart';
import 'package:angel_orm/angel_orm.dart';
import 'car.dart';
import 'car.orm.g.dart';

/// Returns an Angel plug-in that connects to a database, and sets up a controller connected to it...
AngelConfigurer connectToCarsTable(QueryExecutor executor) {
  return (Angel app) async {
    // Register the connection with Angel's dependency injection system.
    // 
    // This means that we can use it as a parameter in routes and controllers.
    app.container.registerSingleton(executor);
    
    // Attach the controller we create below
    await app.mountController<CarController>();
  };
}

@Expose('/cars')
class CarController extends Controller {
  // The `executor` will be injected.
  @Expose('/recalled_since_2008')
  carsRecalledSince2008(QueryExecutor executor) {
    // Instantiate a Car query, which is auto-generated. This class helps us build fluent queries easily.
    var query = new CarQuery();
    query.where
      ..familyFriendly.equals(false)
      ..recalledAt.year.greaterThanOrEqualTo(2008);
    
    // Shorter syntax we could use instead...
    query.where.recalledAt.year <= 2008;
    
    // `get()` returns a Future<List<Car>>.
    var cars = await query.get(executor);
    
    return cars;
  }
  
  @Expose('/create', method: 'POST')
  createCar(QueryExecutor executor) async {
    // `package:angel_orm` generates a strongly-typed `insert` function on the query class.
    // Say goodbye to typos!!!
    var query = new CarQuery();
    query.values
      ..familyFriendly = true
      ..make 'Honda';
    var car = query.insert(executor);
    
    // Auto-serialized using code generated by `package:angel_serialize`
    return car;
  }
}

Relations

angel_orm supports the following relationships:

  • @HasOne() (one-to-one)
  • @HasMany() (one-to-many)
  • @BelongsTo() (one-to-one)
  • @ManyToMany() (many-to-many, using a "pivot" table)

The annotations can be abbreviated with the default options (ex. @hasOne), or supplied with custom parameters (ex. @HasOne(foreignKey: 'foreign_id')).

@serializable
@orm
abstract class _Author extends Model {
  @HasMany // Use the defaults, and auto-compute `foreignKey`
  List<_Book> books;
  
  // Also supports parameters...
  @HasMany(localKey: 'id', foreignKey: 'author_id', cascadeOnDelete: true)
  List<_Book> books;
  
  @SerializableField(alias: 'writing_utensil')
  @hasOne
  _Pen pen;
}

The relationships will "just work" out-of-the-box, following any operation. For example, after fetching an Author from the database in the above example, the books field would be populated with a set of deserialized Book objects, also fetched from the database.

Relationships use joins when possible, but in the case of @HasMany(), two queries are used:

  • One to fetch the object itself
  • One to fetch a list of related objects

Many to Many Relations

A many-to-many relationship can now be modeled like so. RoleUser in this case is a pivot table joining User and Role.

Note that in this case, the models must reference the private classes (_User, etc.), because the canonical versions (User, etc.) are not-yet-generated:

@serializable
@orm
abstract class _User extends Model {
  String get username;
  String get password;
  String get email;

  @ManyToMany(_RoleUser)
  List<_Role> get roles;
}

@serializable
@orm
abstract class _RoleUser {
  @belongsTo
  _Role get role;

  @belongsTo
  _User get user;
}

@serializable
@orm
abstract class _Role extends Model {
  String name;

  @ManyToMany(_RoleUser)
  List<_User> get users;
}

TLDR:

  1. Make a pivot table, C, between two tables, table A and B
  2. C should @belongsTo both A and B. C should not extend Model.
  3. A should have a field: @ManyToMany(_C) List<_B> get b;
  4. B should have a field: @ManyToMany(_C) List<_A> get a;

Test: https://raw.githubusercontent.com/angel-dart/orm/master/angel_orm_generator/test/many_to_many_test.dart

Columns

Use a @Column() annotation to change how a given field is handled within the ORM.

Column Types

Using the @Column() annotation, it is possible to explicitly declare the data type of any given field:

@serializable
@orm
abstract class _Foo extends Model {
  @Column(type: ColumnType.bigInt)
  int bar;
}

Indices

Columns can also have an index:

@serializable
@orm
abstract class _Foo extends Model {
  @Column(index: IndexType.primaryKey)
  String bar;
}

Default Values

It is also possible to specify the default value of a field. Note that this only works with primitive objects.

If a default value is supplied, the SqlMigrationBuilder will include it in the generated schema. The PostgresOrmGenerator ignores default values; it does not need them to function properly.

@serializable
@orm
abstract class _Foo extends Model {
  @Column(defaultValue: 'baz')
  String bar;
}