Moor: How to modify data object before insert/update/delete with companions?

Created on 26 Nov 2019  路  5Comments  路  Source: simolus3/moor

Is there a way to modify an Insertable<> before insert/update in a DAO?

I need to do that for 3 things:

  1. Generate the primary key before an insertion
  2. Set updatedAt when a row is modified.
  3. Mark a row as deleted for a soft delete.

I currently have these methods inside my DAO, but I would like to change them to use Insertable<Task>:

  Future insertTask(TasksCompanion task) {
    var gid = Uuid().v4();
    var taskCopy = task.copyWith(gid: Value(gid));
    return into(tasks).insert(taskCopy);
  }

  Future updateTask(Task task) {
    var taskCopy = task.copyWith(updatedAt: DateTime.now());
    update(tasks).replace(taskCopy);
  }

  Future deleteTask(Task task) {
    var taskCopy = task.copyWith(deletedAt: DateTime.now());
    return update(tasks).replace(taskCopy);
  }

I tried using Iterable<Task> but it doesn't seem to be any methods I can use to modify the data about to be inserted.
Would you happen to know how to use Insertable<Task> instead?

Also, would you consider this to be a correct way to set up the code to auto-generate the primary key?
I do have to use uuid, but is updateTask the right place to set that up?

Thanks.

Most helpful comment

I think that approach should work, yes. You can use a RawValuesInsertable for step 3.

  Future insertTask(Insertable<Task> task) {
    var gid = Uuid().v4();
    var columns = task.toColumns(true);
    columns['gid'] = Variable(gid.toString());

    return into(tasks).insert(RawValuesInsertable(columns));
  }

The map returned by toColumns already has the type converters applied. This means that you'd now have to apply type converters manually when changing values (like with gid.toString()).

If you need it, I can look into ways moor_generator could make this easier. For instance, we could generate a method on generated data classes to turn them into a generated companion. With the introduction of RawValuesInsertable that was needed for upserts, we can't transform a arbitrary Insertable<D> to a generated companion anymore. You could then change your methods to take a TasksCompanion instead of an Insertable<Task>. The places that called your methods with a data class would then have to call the generated method.

All 5 comments

You can call Insertable.createCompanion to turn an Insertable into an appropriate companion, on which you can then call the copy methods.

The method has a boolean parameter, which controls whether null values from the Task class should be mapped to Value.absent or Value(null). We typically set it it to true on inserts (map null to absent) and false on updates (map null to null).

I haven't tried it, but the code could look similar to

  Future insertTask(Insertable<Task> task) {
    var gid = Uuid().v4();
    var companion = task.createCompanion(true) as TasksCompanion;
    var taskCopy = companion.copyWith(gid: Value(gid));
    return into(tasks).insert(taskCopy);
  }

  Future updateTask(Insertable<Task> task) {
    var companion = task.createCompanion(false) as TasksCompanion;
    var taskCopy = companion.copyWith(updatedAt: DateTime.now());
    update(tasks).replace(taskCopy);
  }

  Future deleteTask(Insertable<Task> task) {
    var companion = task.createCompanion(false) as TasksCompanion;
    var taskCopy = companion.copyWith(deletedAt: DateTime.now());
    return update(tasks).replace(taskCopy);
  }

Also, would you consider this to be a correct way to set up the code to auto-generate the primary key?

If you need to generate keys in Dart, doing that in an insert method seems appropriate, yet.

Hello @simolus3,
Given that createCompanion() isn't available anymore, are there any alternatives you recommend to solve the issue?

Would you consider this a good approach?:

  1. Fetch values from myInsertable.toColumns()
  2. Update the values
  3. Build a new insertable with updated values

btw. Thanks for the hard work on this library, it's really good.

I think that approach should work, yes. You can use a RawValuesInsertable for step 3.

  Future insertTask(Insertable<Task> task) {
    var gid = Uuid().v4();
    var columns = task.toColumns(true);
    columns['gid'] = Variable(gid.toString());

    return into(tasks).insert(RawValuesInsertable(columns));
  }

The map returned by toColumns already has the type converters applied. This means that you'd now have to apply type converters manually when changing values (like with gid.toString()).

If you need it, I can look into ways moor_generator could make this easier. For instance, we could generate a method on generated data classes to turn them into a generated companion. With the introduction of RawValuesInsertable that was needed for upserts, we can't transform a arbitrary Insertable<D> to a generated companion anymore. You could then change your methods to take a TasksCompanion instead of an Insertable<Task>. The places that called your methods with a data class would then have to call the generated method.

we could generate a method on generated data classes to turn them into a generated companion

I think that would be really helpful. Our sync code has a bunch of mappers that convert remote models to and from local data classes. All our local insert/update methods take companions, so currently we use something like:

Foo localFoo = remoteFoo.toLocalFoo();
FooCompanion companion = localFoo.createCompanion(false);
await insertFoo(companion, mode: InsertMode.insertOrReplace);

Thanks for the feedback! The createCompanion method will be back in moor 3.1, but only for generated data classes.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

simolus3 picture simolus3  路  4Comments

tony123S picture tony123S  路  4Comments

kira1752 picture kira1752  路  3Comments

apoleo88 picture apoleo88  路  3Comments

jerryzhoujw picture jerryzhoujw  路  4Comments