dorm_generator

pub package pub popularity pub likes pub points

Provides code adapted to work with the dORM framework.

Getting started

Run the following commands inside your project:

dart pub add dev:dorm_generator
dart pub add dev:build_runner
dart pub add dev:json_serializable
dart pub get

Usage

Note: This document assumes that you have already seen the dorm_annotations documentation.

Generating

Create a file inside the lib folder of your Dart project. In this example, it will be lib/models.dart.

Write the classes, their getters and their annotations to this file. Add the following directives to top of this file:

import 'package:dorm_annotations/dorm_annotations.dart';
import 'package:dorm_framework/dorm_framework.dart';

part 'models.g.dart';

part 'models.dorm.dart';

Run the following line in your command prompt:

dart run build_runner build

This will generate all the files based on the annotated classes.

Models

Any class _Class annotated with Model will create four new classes: ClassData, Class, ClassDependency and ClassEntity.

@Model(name: 'class', as: #classes)
abstract class _Class {
  @Field(name: 'name')
  String? get name;

  @Field(name: 'timestamp')
  DateTime get timestamp;

  @ForeignField(name: 'school-id', referTo: _School)
  String get schoolId;
}

Data

A ClassData will contain only the getters annotated with Field, PolymorphicField and ModelField. In the above example is defined as:

@JsonSerializable(anyMap: true, explicitToJson: true)
class ClassData {
  @JsonKey(name: 'name')
  final String? name;

  @JsonKey(name: 'timestamp', required: true, disallowNullValue: true)
  final DateTime timestamp;

  factory ClassData.fromJson(Map json) => _$ClassDataFromJson(json);

  const ClassData({
    required this.name,
    required this.timestamp,
  });

  Map<String, Object?> toJson() => _$ClassDataToJson(this);
}

Model

A Class extends ClassData, implements _Class, has an additional id field and will contain only the getters annotated with ForeignField and QueryField. In the above example is defined as:

@JsonSerializable(anyMap: true, explicitToJson: true)
class Class extends ClassData implements _Class {
  @JsonKey(name: '_id', required: true, disallowNullValue: true)
  final String id;

  @JsonKey(name: 'school-id', required: true, disallowNullValue: true)
  final String schoolId;

  factory Class.fromJson(String id, Map json) =>
      _$ClassFromJson({...json, '_id': id});

  const Class({
    required this.id,
    required this.schoolId,
    required super.name,
    required super.timestamp,
  });

  Map<String, Object?> toJson() =>
      _$ClassToJson(this)
        ..remove('_id');
}

dORM components

A ClassDependency and a ClassEntity extends respectively Dependency<ClassData> and Entity<ClassData, Class>, exported by the dorm_framework package.

Accessors

The code generation will also create a new class named Dorm, which will contain the repository accessors. In the above example, it is defined as:

class Dorm {
  final BaseEngine _engine;

  const Dorm(this._engine);

  DatabaseEntity<ClassData, Class> get classes =>
      DatabaseEntity(const ClassEntity(), engine: _engine);
}

Refer to the dorm_*_database packages to read more about how to obtain a Engine. With a Dorm instance, you can operate on classes using a Repository, also exported by dorm_framework:

void main() async {
  final Dorm dorm /* =  ... */;

  // Create
  final Class c = await dorm.classes.repository.put(
    ClassDependency(schoolId: 'school-0'),
    ClassData(name: 'A class.', timestamp: DateTime.now()),
  );

  // Read
  final Future<Class> fc = await dorm.classes.repository.peek('class-1');
  final Future<List<Class>> fcs = await dorm.classes.repository.peekAll();
  final Stream<Class> sc = dorm.classes.repository.pull('class-1');
  final Stream<List<Class>> scs = dorm.classes.repository.pullAll();

  // Update
  final Class uc = await dorm.classes.repository.push(Class(
    id: 'class-1',
    schoolId: 'school-1',
    name: 'A new class.',
    timestamp: DateTime.now(),
  ));

  // Delete
  await dorm.classes.repository.pop('class-1');
}

Polymorphism

Consider the following annotated code:

abstract class _Action {}

@PolymorphicData(name: 'attack')
abstract class _Attack implements _Action {
  @Field(name: 'strength')
  int get strength;
}

@PolymorphicData(name: 'defence')
abstract class _Defense implements _Action {
  @Field(name: 'resistence')
  int get resistence;
}

@PolymorphicData(name: 'healing', as: #heal)
abstract class _Healing implements _Action {
  @Field(name: 'health')
  int get health;
}

@Model(name: 'operation', as: #operations)
abstract class _Operation {
  @Field(name: 'name')
  String get name;

  @PolymorphicField(name: 'action', pivotName: 'type', pivotAs: #type)
  _Action get action;
}

The generated code will contain an abstract class named Action with three subclasses: Attack, Defense and Healing. It'll also contain an enum named ActionType with three values: attack, defense and heal (not healing; see its PolymorphicData's as argument).

The _Operation model will be generated as described previously, except that will contain an additional field named type of type ActionType, which will allow the user to check the runtime type of the action field.

The following code explains how to manipulate generated code for a Model with a field annotated with PolymorphicField:

void main() async {
  final Operation o1 = await dorm.operations.repository.put(
    const OperationDependency(),
    OperationData(name: 'AoT', action: Attack(strength: 42), type: ActionType.attack),
  );

  final Operation o2 = await dorm.operations.repository.peek('543f2f8da023');

  final int value;
  switch (operation.type) {
    case ActionType.attack:
      final Attack attack = operation.action as Attack;
      value = attack.strength;
      break;
    case ActionType.defense:
      final Defense defense = operation.action as Defense;
      value = defense.resistence;
      break;
    case ActionType.heal:
      final Healing healing = operation.action as Healing;
      value = healing.health;
      break;
  }
}

Unique identification

Simple

If

@Model(name: 'country', as: #countries, uidType: UidType.simple())
abstract class _Country {
  @Field(name: 'name')
  String get name;
}

then

void main() async {
  final Country country = await dorm.countries.repository.put(
    CountryDependency(),
    CountryData(name: 'Brazil'),
  );
  // uuid
  assert(country.id == '27f04af67a1f');
}

Composite

If

@Model(name: 'state', as: #states, uidType: UidType.composite())
abstract class _State {
  @Field(name: 'name')
  String get name;

  @ForeignField(name: 'country-id', referTo: _Country)
  String get countryId;
}

then

void main() async {
  final State state = await dorm.states.repository.put(
    StateDependency(countryId: '27f04af67a1f'),
    StateData(name: 'Rio de Janeiro'),
  );
  // ${countryId}_uuid
  assert(country.id == '27f04af67a1f_367f1672f637');
}

Same-as

If

@Model(name: 'capital', as: #capitals, uidType: UidType.sameAs(_Country))
abstract class _Capital {
  @Field(name: 'name')
  String get name;

  @ForeignField(name: 'country-id', referTo: _Country)
  String get countryId;
}

then

void main() async {
  final Capital capital = await dorm.capitals.repository.put(
    CapitalDependency(countryId: '27f04af67a1f'),
    CapitalData(name: 'Brasilia'),
  );
  // countryId
  assert(capital.id == '27f04af67a1f');
}

Custom

If

CustomUidValue _identifyCitizen(Object data) {
  data as _Citizen;
  if (data.isForeigner) {
    return CustomUidValue.value(data.visaCode!);
  }
  if (data.socialSecurity != null) {
    return CustomUidValue.value(data.socialSecurity);
  }
  return const CustomUidValue.simple();
}

@Model(name: 'citizen', as: #citizens, uidType: UidType.custom(_identifyCitizen))
abstract class _Citizen {
  @Field(name: 'name')
  String get name;

  @Field(name: 'is-foreigner', defaultValue: false)
  bool get isForeigner;

  @Field(name: 'visa-code')
  String? get visaCode;

  @Field(name: 'ssn')
  String? get socialSecurity;

  @ForeignField(name: 'country-id', referTo: _Country)
  String get countryId;
}

then

void main() async {
  final Citizen c1 = await dorm.citizens.repository.put(
    CitizenDependency(countryId: '27f04af67a1f'),
    CitizenData(
      name: 'Rodrigo Maia',
      isForeigner: true,
      visaCode: '4bb6',
      socialSecurity: '11111111111',
    ),
  );
  // visaCode
  assert(c1.id == '4bb6');

  final Citizen c2 = await dorm.citizens.repository.put(
    CitizenDependency(countryId: '27f04af67a1f'),
    CitizenData(
      name: 'Arthur Lira',
      isForeigner: false,
      visaCode: null,
      socialSecurity: '22222222222',
    ),
  );
  // socialSecurity
  assert(c2.id == '22222222222');

  final Citizen c3 = await dorm.citizens.repository.put(
    CitizenDependency(countryId: '27f04af67a1f'),
    CitizenData(name: 'Capivara Filó', isForeigner: false, visaCode: null, socialSecurity: null),
  );
  // uuid
  assert(c3.id == 'b2a6304807a0');
}

Libraries

dorm_generator