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>
6 : with SetMixin<E>, _Lifecycle {
7 1 : @protected
8 : Relationship([Set<E>? models])
9 : : _uninitializedKeys = {},
10 : _uninitializedModels = models ?? {},
11 : _wasOmitted = models == null,
12 : _shouldRemove = false;
13 :
14 1 : Relationship._(Iterable<String> keys, this._wasOmitted)
15 1 : : _uninitializedKeys = keys.toSet(),
16 : _uninitializedModels = {},
17 : _shouldRemove = false;
18 :
19 1 : Relationship._remove()
20 : : _uninitializedKeys = {},
21 : _uninitializedModels = {},
22 : _wasOmitted = false,
23 : _shouldRemove = true;
24 :
25 : late final String _ownerKey;
26 : late final String _name;
27 : late final String? _inverseName;
28 : late final Map<String, RemoteAdapter> _adapters;
29 : late final RemoteAdapter<E> _adapter;
30 4 : GraphNotifier get _graph => _adapter.localAdapter.graph;
31 :
32 : final Set<String> _uninitializedKeys;
33 : final Set<E> _uninitializedModels;
34 : final bool _wasOmitted;
35 : final bool _shouldRemove;
36 :
37 1 : @protected
38 1 : String get internalType => DataHelpers.getType<E>();
39 :
40 : /// Initializes this relationship (typically when initializing the owner
41 : /// in [DataModel]) by supplying the owner, and related [adapters] and metadata.
42 1 : Future<Relationship<E, N>> initialize(
43 : {required final Map<String, RemoteAdapter> adapters,
44 : required final DataModel owner,
45 : required final String name,
46 : final String? inverseName}) async {
47 1 : if (isInitialized) return this;
48 :
49 1 : _adapters = adapters;
50 3 : _adapter = adapters[internalType] as RemoteAdapter<E>;
51 :
52 2 : _ownerKey = owner._key!;
53 1 : _name = name;
54 1 : _inverseName = inverseName;
55 :
56 1 : if (_shouldRemove) {
57 3 : _graph._removeEdges(_ownerKey,
58 2 : metadata: _name, inverseMetadata: _inverseName);
59 : } else {
60 : // initialize uninitialized models and get keys
61 3 : final newKeys = _uninitializedModels.map((model) {
62 3 : return model._initialize(_adapters, save: true)._key!;
63 : });
64 2 : _uninitializedKeys.addAll(newKeys);
65 : }
66 :
67 2 : _uninitializedModels.clear();
68 :
69 : // initialize keys
70 1 : if (!_wasOmitted) {
71 : // if it wasn't omitted, we overwrite
72 3 : _graph._removeEdges(_ownerKey,
73 2 : metadata: _name, inverseMetadata: _inverseName);
74 2 : _graph._addEdges(
75 1 : _ownerKey,
76 1 : tos: _uninitializedKeys,
77 1 : metadata: _name,
78 1 : inverseMetadata: _inverseName,
79 : );
80 2 : _uninitializedKeys.clear();
81 : }
82 :
83 1 : isInitialized = true;
84 : return this;
85 : }
86 :
87 : @override
88 : bool isInitialized = false;
89 :
90 : // implement collection-like methods
91 :
92 : /// Add a [value] to this [Relationship]
93 : ///
94 : /// Attempting to add an existing [value] has no effect as this is a [Set]
95 1 : @override
96 : bool add(E value, {bool notify = true}) {
97 1 : if (contains(value)) {
98 : return false;
99 : }
100 :
101 : // try to ensure value is initialized
102 1 : _ensureModelIsInitialized(value);
103 :
104 2 : if (value.isInitialized && isInitialized) {
105 4 : _graph._addEdge(_ownerKey, value._key!,
106 2 : metadata: _name, inverseMetadata: _inverseName);
107 : } else {
108 : // if it can't be initialized, add to the models queue
109 2 : _uninitializedModels.add(value);
110 : }
111 : return true;
112 : }
113 :
114 1 : @override
115 : bool contains(Object? element) {
116 2 : return _iterable.contains(element);
117 : }
118 :
119 : /// Removes a [value] from this [Relationship]
120 1 : @override
121 : bool remove(Object? value, {bool notify = true}) {
122 : assert(value is E);
123 : final model = value as E;
124 1 : if (isInitialized) {
125 1 : _ensureModelIsInitialized(model);
126 2 : _graph._removeEdge(
127 1 : _ownerKey,
128 1 : model._key!,
129 1 : metadata: _name,
130 1 : inverseMetadata: _inverseName,
131 : notify: notify,
132 : );
133 : return true;
134 : }
135 2 : return _uninitializedModels.remove(model);
136 : }
137 :
138 1 : @override
139 2 : Iterator<E> get iterator => _iterable.iterator;
140 :
141 0 : @override
142 0 : E? lookup(Object? element) => lookup(element);
143 :
144 1 : @override
145 2 : Set<E> toSet() => _iterable.toSet();
146 :
147 1 : @override
148 2 : int get length => _iterable.length;
149 :
150 : // support methods
151 :
152 1 : Iterable<E> get _iterable {
153 1 : if (isInitialized) {
154 1 : return keys
155 4 : .map((key) => _adapter.localAdapter
156 1 : .findOne(key)
157 2 : ?._initialize(_adapters, key: key))
158 1 : .filterNulls;
159 : }
160 1 : return _uninitializedModels;
161 : }
162 :
163 : /// Returns keys as [Set] in relationship if initialized, otherwise an empty set
164 1 : @protected
165 : @visibleForTesting
166 : Set<String> get keys {
167 1 : if (isInitialized) {
168 5 : return _graph._getEdge(_ownerKey, metadata: _name).toSet();
169 : }
170 1 : return _uninitializedKeys;
171 : }
172 :
173 1 : Set<String> get ids {
174 6 : return keys.map(_graph.getIdForKey).filterNulls.toSet();
175 : }
176 :
177 1 : E _ensureModelIsInitialized(E model) {
178 2 : if (!model.isInitialized && isInitialized) {
179 2 : model._initialize(_adapters, save: true);
180 : }
181 : return model;
182 : }
183 :
184 1 : DelayedStateNotifier<List<DataGraphEvent>> get _graphEvents {
185 4 : return _adapter.throttledGraph.map((events) {
186 2 : return events.where((event) {
187 2 : return event.type.isEdge &&
188 3 : event.metadata == _name &&
189 3 : event.keys.containsFirst(_ownerKey);
190 1 : }).toImmutableList();
191 : });
192 : }
193 :
194 : DelayedStateNotifier<N> watch();
195 :
196 : /// This is used to make `json_serializable`'s `explicitToJson` transparent.
197 : ///
198 : /// For internal use. Does not return valid JSON.
199 1 : dynamic toJson() => this;
200 :
201 : // equality
202 :
203 1 : @override
204 : bool operator ==(dynamic other) =>
205 : identical(this, other) ||
206 1 : other is Relationship &&
207 1 : isInitialized &&
208 1 : other.isInitialized &&
209 3 : _ownerKey == other._ownerKey &&
210 3 : _name == other._name;
211 :
212 1 : @override
213 : int get hashCode {
214 1 : if (isInitialized) {
215 4 : return Object.hash(runtimeType, _ownerKey, _name);
216 : } else {
217 0 : return runtimeType.hashCode;
218 : }
219 : }
220 :
221 6 : String get _prop => _iterable.map((e) => e.id).join(', ');
222 :
223 0 : @override
224 : void dispose() {
225 : // relationships are not disposed
226 : }
227 : }
228 :
229 : // annotation
230 :
231 : class DataRelationship {
232 : final String inverse;
233 3 : const DataRelationship({required this.inverse});
234 : }
|