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