Line data Source code
1 : part of flutter_data;
2 :
3 : /// A `Set` that models a relationship between one or more [DataModel] objects
4 : /// and their a [DataModel] owner. Backed by a [GraphNotifier].
5 : abstract class Relationship<E extends DataModel<E>, N> with EquatableMixin {
6 11 : @protected
7 : Relationship(Set<E>? models)
8 29 : : this._(models?.map((m) {
9 9 : if (m._key == null) {
10 0 : throw AssertionError(
11 0 : 'Model $m must be initialized to be included in this relationship');
12 : }
13 9 : return m._key!;
14 9 : }).toSet());
15 :
16 11 : Relationship._(this._uninitializedKeys);
17 :
18 3 : Relationship._remove() : _uninitializedKeys = {};
19 :
20 : String? _ownerKey;
21 : String? _name;
22 : String? _inverseName;
23 :
24 11 : RemoteAdapter<E> get _adapter =>
25 44 : internalRepositories[_internalType]!.remoteAdapter as RemoteAdapter<E>;
26 44 : GraphNotifier get _graph => _adapter.localAdapter.graph;
27 :
28 : final Set<String>? _uninitializedKeys;
29 22 : String get _internalType => DataHelpers.getType<E>();
30 :
31 22 : bool get isInitialized => _ownerKey != null;
32 :
33 : /// Initializes this relationship (typically when initializing the owner
34 : /// in [DataModel]) by supplying the owner, and related metadata.
35 11 : Relationship<E, N> initialize(
36 : {required final DataModel owner,
37 : required final String name,
38 : final String? inverseName}) {
39 11 : if (isInitialized) return this;
40 :
41 22 : _ownerKey = owner._key;
42 11 : _name = name;
43 11 : _inverseName = inverseName;
44 :
45 : // means it was omitted (remote-omitted, or loaded locally), so skip
46 11 : if (_uninitializedKeys == null) return this;
47 :
48 : // setting up from scratch, remove all and add keys
49 :
50 30 : _graph._removeEdges(_ownerKey!,
51 20 : metadata: _name!, inverseMetadata: _inverseName, notify: false);
52 :
53 : // in case node was removed during removeEdges
54 30 : _graph._addNode(_ownerKey!);
55 :
56 20 : _graph._addEdges(
57 10 : _ownerKey!,
58 10 : tos: _uninitializedKeys!,
59 10 : metadata: _name!,
60 10 : inverseMetadata: _inverseName,
61 : notify: false,
62 : );
63 20 : _uninitializedKeys!.clear();
64 :
65 : return this;
66 : }
67 :
68 : // implement collection-like methods
69 :
70 5 : bool _add(E value, {bool notify = true}) {
71 5 : if (_contains(value)) {
72 : return false;
73 : }
74 :
75 20 : _graph._addEdge(_ownerKey!, value._key!,
76 10 : metadata: _name!, inverseMetadata: _inverseName, notify: false);
77 : if (notify) {
78 8 : _graph._notify(
79 12 : [_ownerKey!, value._key!],
80 4 : metadata: _name,
81 : type: DataGraphEventType.addEdge,
82 : );
83 : }
84 :
85 : return true;
86 : }
87 :
88 5 : bool _contains(Object? element) {
89 10 : return _iterable.contains(element);
90 : }
91 :
92 4 : bool _remove(Object? value, {bool notify = true}) {
93 8 : assert(value is E);
94 : final model = value as E;
95 :
96 8 : _graph._removeEdge(
97 4 : _ownerKey!,
98 4 : model._key!,
99 4 : metadata: _name!,
100 4 : inverseMetadata: _inverseName,
101 : notify: false,
102 : );
103 : if (notify) {
104 6 : _graph._notify(
105 9 : [_ownerKey!, value._key!],
106 3 : metadata: _name,
107 : type: DataGraphEventType.removeEdge,
108 : );
109 : }
110 : return true;
111 : }
112 :
113 : // support methods
114 :
115 9 : Iterable<E> get _iterable {
116 63 : return _keys.map((key) => _adapter.localAdapter.findOne(key)).filterNulls;
117 : }
118 :
119 11 : Set<String> get _keys {
120 11 : if (!isInitialized) return {};
121 55 : return _graph._getEdge(_ownerKey!, metadata: _name!).toSet();
122 : }
123 :
124 4 : Set<Object> get _ids {
125 25 : return _keys.map((key) => _graph.getIdForKey(key)).filterNulls.toSet();
126 : }
127 :
128 2 : DelayedStateNotifier<DataGraphEvent> get _relationshipEventNotifier {
129 8 : return _adapter.graph.where((event) {
130 4 : return event.type.isEdge &&
131 6 : event.metadata == _name &&
132 6 : event.keys.containsFirst(_ownerKey!);
133 : });
134 : }
135 :
136 : DelayedStateNotifier<N> watch();
137 :
138 : /// This is used to make `json_serializable`'s `explicitToJson` transparent.
139 : ///
140 : /// For internal use. Does not return valid JSON.
141 8 : dynamic toJson() => this;
142 :
143 : /// Whether the relationship has a value.
144 6 : bool get isPresent => _iterable.isNotEmpty;
145 :
146 5 : @override
147 20 : List<Object?> get props => [_ownerKey, _name, _inverseName];
148 :
149 4 : @override
150 : String toString() {
151 : final keysWithoutId =
152 23 : _keys.where((k) => _graph.getIdForKey(k) == null).map((k) => '[$k]');
153 12 : return {..._ids, ...keysWithoutId}.join(', ');
154 : }
155 : }
156 :
157 : // annotation
158 :
159 : class DataRelationship {
160 : final String? inverse;
161 : final bool serialize;
162 34 : const DataRelationship({this.inverse, this.serialize = true});
163 : }
|