LCOV - code coverage report
Current view: top level - model - data_model.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 40 40 100.0 %
Date: 2021-06-18 12:41:16 Functions: 0 0 -

          Line data    Source code
       1             : part of flutter_data;
       2             : 
       3             : /// A mixin to "tag" and ensure the implementation of an [id] getter
       4             : /// in data classes managed through Flutter Data.
       5             : ///
       6             : /// It contains private state and methods to track the model's identity.
       7             : abstract class DataModel<T extends DataModel<T>> {
       8             :   Object? get id;
       9             : 
      10             :   // "late" finals
      11             :   String? _key;
      12             :   Map<String, RemoteAdapter>? _adapters;
      13             : 
      14             :   // computed
      15          14 :   String get _internalType => DataHelpers.getType<T>();
      16           7 :   RemoteAdapter<T> get remoteAdapter =>
      17          21 :       _adapters?[_internalType]! as RemoteAdapter<T>;
      18             : 
      19          21 :   bool get isInitialized => _key != null && _adapters != null;
      20             : 
      21             :   // initializers
      22             : 
      23           7 :   T _initialize(final Map<String, RemoteAdapter> adapters,
      24             :       {String? key, bool save = false}) {
      25           7 :     if (isInitialized) return this as T;
      26             : 
      27           7 :     _adapters = adapters;
      28             : 
      29          49 :     _key = remoteAdapter.graph.getKeyForId(remoteAdapter.internalType, id,
      30           7 :         keyIfAbsent: key ?? DataHelpers.generateKey<T>());
      31             : 
      32             :     if (save) {
      33          28 :       remoteAdapter.localAdapter.save(_key!, this as T);
      34             :     }
      35             : 
      36             :     // initialize relationships
      37             :     for (final metadata
      38          34 :         in remoteAdapter.localAdapter.relationshipsFor(this as T).entries) {
      39          12 :       final relationship = metadata.value['instance'] as Relationship?;
      40             : 
      41           6 :       relationship?.initialize(
      42             :         adapters: adapters,
      43             :         owner: this,
      44          12 :         name: metadata.value['name'] as String,
      45          12 :         inverseName: metadata.value['inverse'] as String?,
      46             :       );
      47             :     }
      48             : 
      49             :     return this as T;
      50             :   }
      51             : }
      52             : 
      53             : /// Extension that adds syntax-sugar to data classes,
      54             : /// linking them to common [Repository] methods such as
      55             : /// [save] and [delete].
      56             : extension DataModelExtension<T extends DataModel<T>> on DataModel<T> {
      57             :   /// Initializes a model copying the identity of supplied [model].
      58             :   ///
      59             :   /// Usage:
      60             :   /// ```
      61             :   /// final post = await repository.findOne('1'); // returns initialized post
      62             :   /// final newPost = Post(title: 'test'); // uninitialized post
      63             :   /// newPost.was(post); // new is now initialized with same key as post
      64             :   /// ```
      65           1 :   T was(T model) {
      66             :     assert(model.isInitialized,
      67             :         'Please initialize model before passing it to `was`');
      68           3 :     return _initialize(model._adapters!, key: model._key, save: true);
      69             :   }
      70             : 
      71             :   /// Saves this model through a call equivalent to [Repository.save].
      72             :   ///
      73             :   /// Usage: `await post.save()`, `author.save(remote: false, params: {'a': 'x'})`.
      74             :   ///
      75             :   /// **Requires this model to be initialized.**
      76           2 :   Future<T> save({
      77             :     bool? remote,
      78             :     Map<String, dynamic>? params,
      79             :     Map<String, String>? headers,
      80             :     OnData<T>? onSuccess,
      81             :     OnDataError<T>? onError,
      82             :   }) async {
      83           1 :     _assertInit('save');
      84           3 :     return await remoteAdapter.save(
      85             :       this as T,
      86             :       remote: remote,
      87             :       params: params,
      88             :       headers: headers,
      89             :       onSuccess: onSuccess,
      90             :       onError: onError,
      91             :     );
      92             :   }
      93             : 
      94             :   /// Deletes this model through a call equivalent to [Repository.delete].
      95             :   ///
      96             :   /// Usage: `await post.delete()`
      97             :   ///
      98             :   /// **Requires this model to be initialized.**
      99           2 :   Future<void> delete({
     100             :     bool? remote,
     101             :     Map<String, dynamic>? params,
     102             :     Map<String, String>? headers,
     103             :     OnData<void>? onSuccess,
     104             :     OnDataError<void>? onError,
     105             :   }) async {
     106           1 :     _assertInit('delete');
     107           3 :     await remoteAdapter.delete(
     108             :       this,
     109             :       remote: remote,
     110             :       params: params,
     111             :       headers: headers,
     112             :       onSuccess: onSuccess,
     113             :       onError: onError,
     114             :     );
     115             :   }
     116             : 
     117             :   /// Re-fetch this model through a call equivalent to [Repository.findOne].
     118             :   /// with the current object/[id]
     119             :   ///
     120             :   /// **Requires this model to be initialized.**
     121           2 :   Future<T?> reload({
     122             :     bool? remote,
     123             :     Map<String, dynamic>? params,
     124             :     Map<String, String>? headers,
     125             :   }) async {
     126           1 :     _assertInit('reload');
     127           3 :     return await remoteAdapter.findOne(
     128             :       this,
     129             :       remote: remote,
     130             :       params: params,
     131             :       headers: headers,
     132             :     );
     133             :   }
     134             : 
     135             :   /// Watch this model through a call equivalent to [Repository.watchOne].
     136             :   /// with the current object/[id].
     137             :   ///
     138             :   /// **Requires this model to be initialized.**
     139           2 :   DataStateNotifier<T?> watch({
     140             :     bool? remote,
     141             :     Map<String, dynamic>? params,
     142             :     Map<String, String>? headers,
     143             :     AlsoWatch<T>? alsoWatch,
     144             :   }) {
     145           1 :     _assertInit('watch');
     146           2 :     return remoteAdapter.watchOne(
     147             :       this,
     148             :       remote: remote,
     149             :       params: params,
     150             :       headers: headers,
     151             :       alsoWatch: alsoWatch,
     152             :     );
     153             :   }
     154             : 
     155           1 :   void _assertInit(String method) {
     156           1 :     if (isInitialized) {
     157             :       return;
     158             :     }
     159           1 :     throw AssertionError('''\n
     160             : This model MUST be initialized in order to call `$method`.
     161             : 
     162             : DON'T DO THIS:
     163             : 
     164           2 :   final ${_internalType.singularize()} = $T(...);
     165           2 :   ${_internalType.singularize()}.$method(...);
     166             : 
     167             : DO THIS:
     168             : 
     169           2 :   final ${_internalType.singularize()} = $T(...).init(context.read);
     170           2 :   ${_internalType.singularize()}.$method(...);
     171             : 
     172             : Call `init(context.read)` on the model first.
     173             : 
     174             : This ONLY happens when a model is manually instantiated
     175             : and had no contact with Flutter Data.
     176             : 
     177             : Initializing models is not necessary in any other case.
     178             : 
     179             : When assigning new models to a relationship, only initialize
     180             : the actual model:
     181             : 
     182             : Family(surname: 'Carlson', dogs: {Dog(name: 'Jerry'), Dog(name: 'Zoe')}.asHasMany)
     183             :   .init(context.read);
     184           1 : ''');
     185             :   }
     186             : }
     187             : 
     188             : /// Returns a model's `_key` private attribute.
     189             : ///
     190             : /// Useful for testing, debugging or usage in [RemoteAdapter] subclasses.
     191           2 : String? keyFor<T extends DataModel<T>>(T model) => model._key;
     192             : 
     193           1 : @visibleForTesting
     194             : @protected
     195             : RemoteAdapter? adapterFor<T extends DataModel<T>>(T model) =>
     196           1 :     model.remoteAdapter;

Generated by: LCOV version 1.15