Moor: Store custom objects

Created on 5 Jul 2019  Â·  14Comments  Â·  Source: simolus3/moor

Hey,

I have a question about how I can store my own custom objects (here called Category) as a column in my database as with the DateTime class.

Here is my data class:

@DataClassName("Entry")
class Entries extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text().nullable()();
  DateTimeColumn get time => dateTime().nullable()();
  RealColumn get amount => real().withDefault(const Constant(0.0))();
  // Add a column here for my Category class.
}

And the Category class

class Category {
  String name;
  IconData icon;
  HexColor accent;
  Category({this.name, this.icon, this.accent});
}

In Room I'd create a TypeConverter for this. Can I achieve something similar in Moor?

enhancement

Most helpful comment

Just published moor version 1.7 which includes support for type converters. They work as I've described above, but this documentation page might also be helpful.

All 14 comments

Moor doesn't have an equivalent of a TypeConverter at the moment. In your example, you could store the categories in another table and then use joins to load the category for each entry. Another way to do this would be to store that category as a text column. You'd then have to write your own domain model classes and then map to/from moor by encoding the information to json.

That said, I do think that moor would benefit from having a simpler way to achieve this, probably with an API that is very similar to Rooms TypeConverters. I can't make any promises to when I get around to implement that though.

Yea, I created a wrapper around wrapper around the data class that can encode/decode to sql compatible data types. I then only expose the wrapper and convert from when retrieving and vice versa when writing. A bit of boiler plate but it works.

Anyway, thanks for your response and this great library!

I have a version of this working on develop. The api looks like this (using your Category class and assuming it had from / to json methods):

class CustomConverter extends TypeConverter<Category, String> {
  const CustomConverter();
  @override
  Category mapToDart(String fromDb) {
    return fromDb == null ? null : Category.fromJson(json.decode(fromDb));
  }

  @override
  String mapToSql(Category value) {
    return json.encode(value.asMap());
  }
}

Then, you could declare a column like this:

  // ...
  TextColumn get category => text().map(const CustomConverter())();

The generated column class will then have a Category field which is serialized and deserialized using the CustomConverter.

The generated code still needs some performance optimizations, and I need to write proper documentation for this. This also doesn't work with custom queries yet.

Yea, this is exactly what I was looking for. Thanks for your great work!

On Thu, Jul 18, 2019, 13:02 Simon Binder notifications@github.com wrote:

I have a version of this working on develop. The api looks like this
(using your Category class and assuming it had from / to json methods):

class CustomConverter extends TypeConverter {
const CustomConverter();
@override
Category mapToDart(String fromDb) {
return fromDb == null ? null : Category.fromJson(json.decode(fromDb));
}

@override
String mapToSql(Category value) {
return json.encode(value.asMap());
}
}

Then, you could declare a column like this:

// ...
TextColumn get category => text().map(const CustomConverter())();

The generated column class will then have a Category field which is
serialized and deserialized using the CustomConverter.

The generated code still needs some performance optimizations, and I need
to write proper documentation for this. This also doesn't work with custom
queries yet.

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/simolus3/moor/issues/64?email_source=notifications&email_token=AKMU2MHEJSBIXINXBEXOLNTQABEUHA5CNFSM4H6LVKGKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2ID7LI#issuecomment-512769965,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AKMU2MC5HZFSGONQBPVSEJLQABEUHANCNFSM4H6LVKGA
.

@simolus3
Is there someway for data classes to map to tables as well? it would be better for collections to map to individual tables instead of columns. Ideally I would like the ShoppingCart data class to have a List inside of it. However, I want the data to be saved in an individual table. Does this make sense?

EDIT: Right now I just have custom insertion methods and a wrapper object around the generated dataclass. However, I would like to use the actual class name instead of there being wrapper objects throughout my code.

class ShoppingCarts extends Table {
  IntColumn get id => integer().autoIncrement()();
  IntColumn get shoppingCartItems => integer().autoIncrement()();
}

class ShoppingCartItems extends Table {
    IntColumn get id => integer().autoIncrement()();

}

I understand your problem but I don't think it can be solved easily. We'd essentially have to support any type of foreign key relationship in the generated data classes and issue secondary queries to load the data. For n:m relationships like your example above, that even means introducing another table. The only way to do that properly is expanding moor into a fully fledged ORM, which is not something I want to do. I'd prefer moor to stay a "typesafe sql in Dart" library without much magic happening behind the scenes.

This is probably similar to what you're already doing, but what's so bad about:

class ShoppingCarts extends Table {
  IntColumn get id => integer().autoIncrement()();
}
class BuyableItems extends Table {
  IntColumn get id => integer().autoIncrement()();
  // price, description, idk
}
class ShoppingCardEntries extends Table {
  IntColumn get shoppingCard => integer()();
  IntColumn get item => integer()();
}

// the only wrapper class you need
class CartWithItems {
  ShoppingCart cart;
  List<BuyableItem> items;
}

In continuation of your above comment @simolus3. How to write a the insert logic for CartWithItems directly where cart will go to ShoppingCarts table and items BuyableItem table with a foreign key. I will be sending CartWithItems in my insert function.

Similarly how to write the select query to get the CartWithItems combined?

Yeah managing lists in with sql is a bit annoying, but you could use two streams and Observable.combineLatest2 from rxdart:
https://gist.github.com/simolus3/b881d1a5cbb308d549dd484204e25776#file-shopping_cart-dart-L60-L103

You'll get each item twice because the result stream is backed by two different streams, overriding operator == in CartWithItems and using a distinct() stream should fix that.

Thank you for your effort. Could you please guide me a bit more by providing a mechanism to get List of CartWithItems stream.

Sorry if I'm bothering you too much.

Sure, I've added a watchAllCarts method here: https://gist.github.com/simolus3/b881d1a5cbb308d549dd484204e25776#file-shopping_cart-dart-L85-L128
It's not exactly simple, but I don't think it's possible to do that with just one query so we have to use switchMap

If you have any questions on this I'd be glad to help, but let's create a new issue in that case to keep this one on topic :)

This is a great feature! I like being able to have table columns automatically mapped to enums. Any thoughts about adding support for this in .moor files? (#85)

This is what I was looking for. Thank you for this awesome example code watchAllCarts.

Just published moor version 1.7 which includes support for type converters. They work as I've described above, but this documentation page might also be helpful.

My solution
My entity have json_annotation so
In TextColumn I add by:
json.encode(request.toJson())
Then I get the model back by:
BoardingRequest.fromJson(jsonify(textData));
dynamic jsonify(dynamic jsonValue) { final Map<String, dynamic> map = <String, dynamic>{}; try { if (jsonValue is String && json.decode(jsonValue) is Map<String, dynamic>) { final Map<String, dynamic> tempMap = json.decode(jsonValue); tempMap.forEach((String key, dynamic value) { tempMap.update(key, (dynamic existingValue) => jsonify(value)); }); map.addAll(tempMap); } else if (jsonValue is List<dynamic>) { for (int i = 0; i < jsonValue.length; i++) { jsonValue[i] = jsonify(jsonValue[i]); } return jsonValue; } else { return jsonValue; } } on FormatException { return jsonValue; } return map; }

Was this page helpful?
0 / 5 - 0 ratings

Related issues

johrpan picture johrpan  Â·  4Comments

felixjunghans picture felixjunghans  Â·  4Comments

jerryzhoujw picture jerryzhoujw  Â·  4Comments

stx picture stx  Â·  3Comments

Beloin picture Beloin  Â·  4Comments