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