Models topic

Models are the key entities for your database mapping. All tables in your database will be deducted from your model classes, and the columns of a table will be deducted from the fields of a model.

You define a model by using the @Model() annotation on an abstract class. This class should only contain getters and have no constructor.

@Model()
abstract class Book {
  @PrimaryKey()
  String get id;

  String get title;
}

Note: The model class acts mostly as a blueprint for your database tables. It is recommended to not use this class directly in you application, but rather one of the generated entity classes. More on this later.

This model uses an additional @PrimaryKey() annotation on its id field. It will be translated into the following sql table:

TABLE "books" (
  "id" text NOT NULL,
  "title" text NOT NULL,
  PRIMARY KEY ("id")
);

You can also make any field auto-increment by using the @AutoIncrement() annotation. Auto-increment fields must be of type int.

@Model()
abstract class Book {
  @PrimaryKey()
  @AutoIncrement()
  int get id;

  @AutoIncrement()
  int get someOtherValue;
}

Relations

When using relational database systems, you model your data using relations, namely one-to-one, one-to-many, or many-to-many relations.

When you want to specify a relation to another model, you simply use that model as the type of any field. stormberry analyzes your models and automatically determines the correct relation types.


@Model()
abstract class Author {
  @PrimaryKey()
  String get id;

  String get name;
}

@Model()
abstract class Book {
  @PrimaryKey()
  String get id;

  String get title;
  Author get author;
}

The above code specifies a many-to-one relation between Book and Author. It is ok to specify a relation only in one of the two models, but you could also specify the List<Book> get books; in the Author model.

When only one side is specified, stormberry will default to a many-to-one or one-to-many relation. To instead specify a one-to-one relation, simply specify Book get book; on the author side.

Generally, the correct relation type is determined by whether you use List<...> on one or both sides of the relation. Depending on the relation type, it is also mandatory to specify a primary key field.

Notice how when you specify both sides of a relation, querying one of the models would lead to a cyclic dependency. You can solve this by using Views.

Bindings

When you have multiple relations between the same types, it may be ambiguous fields refer to each other. In those cases, you can use the @BindTo(#otherField) annotation like this:

@Model()
abstract class User {
  @PrimaryKey()
  String get id;

  @BindTo(#author)
  List<Post> get posts;
  @BindTo(#likes)
  List<Post> get liked;
}

@Model()
abstract class Post {
  @PrimaryKey()
  String get id;

  @BindTo(#posts)
  User get author;
  @BindTo(#liked)
  List<User> get likes;
}

Indexes

As an advanced configuration you can specify indexes on your table using the TableIndex class. You can add indexes to your @Model() annotation like this:

@Model(
  views: [...],
  indexes: [
    TableIndex(name: 'my_index', columns: ['my_column'], unique: true)
  ],
)
abstract class MyModel {
  ...
}

Checkout the api documentation here for a description of the available parameters you can specify on an index.

Converters

When using a custom type for a model field, you need to create a custom TypeConverter for this type. Implement a custom type converter like this:

class LatLngConverter extends TypeConverter<LatLng> {
  const LatLngConverter() : super('point');
  
  @override
  dynamic encode(LatLng value) => PgPoint(value.latitude, value.longitude);

  @override
  LatLng decode(dynamic value) {
    if (value is PgPoint) {
      return LatLng(value.latitude, value.longitude);
    } else {
      var m = RegExp(r'\((.+),(.+)\)').firstMatch(value.toString());
      var lat = double.parse(m!.group(1)!.trim());
      var lng = double.parse(m.group(2)!.trim());
      return LatLng(lat, lng);
    }
  }
}

This transforms a value of type LatLng to the postgres data type point. Note the usage of PgPoint as the encoded object, which comes from the postgres package. For decoding, we also cover the case that the value is returned as a point string literal instead of a point object.

To use this converter specify it for a field of your model with:

@Model(...)
abstract class MyModel {
  ...

  // Custom Type
  @UseConverter(LatLngConverter())
  LatLng get location;
}

Next: Views

Classes

AutoIncrement Models
Used to annotate a field as an auto increment value. Can only be applied to an integer field.
BindTo Models
Used to annotate a relational field and specify a binding target.
ClassMeta Models
Metadata for a generated class.
FilterByField Models
HiddenIn Models Views
Hides the annotated field in the given view.
Model Models
Used to annotate a class as a database model
ModelMeta Models
Metadata for the generated classes in order to use serialization for these classes.
PrimaryKey Models
Used to annotate a field as the primary key of the table.
TableIndex Models
Used to define indexes on a table
TransformedIn Models Views
Applies the transformer on the annotated field in the given view.
Transformer Models
TypeConverter<T> Models
Extend this to define a custom type converter.
UseConverter Models
Specify a converter to be used for the annotated field.
ViewedIn Models Views
Modified the annotated field in the given view.

Enums

IndexAlgorithm Models
The algorithm for an index.