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 1 : @protected
7 5 : Relationship(Set<E>? models) : this._(models?.map((m) => m._key).toSet());
8 :
9 1 : Relationship._(this._uninitializedKeys);
10 :
11 1 : Relationship._remove() : _uninitializedKeys = {};
12 :
13 : String? _ownerKey;
14 : String? _name;
15 : String? _inverseName;
16 :
17 1 : RemoteAdapter<E> get _adapter =>
18 4 : internalRepositories[_internalType]!.remoteAdapter as RemoteAdapter<E>;
19 4 : GraphNotifier get _graph => _adapter.localAdapter.graph;
20 :
21 : final Set<String>? _uninitializedKeys;
22 2 : String get _internalType => DataHelpers.getType<E>();
23 :
24 : /// Initializes this relationship (typically when initializing the owner
25 : /// in [DataModel]) by supplying the owner, and related metadata.
26 1 : Relationship<E, N> initialize(
27 : {required final DataModel owner,
28 : required final String name,
29 : final String? inverseName}) {
30 1 : if (_ownerKey != null) {
31 : return this; // return if already initialized
32 : }
33 :
34 2 : _ownerKey = owner._key;
35 1 : _name = name;
36 1 : _inverseName = inverseName;
37 :
38 1 : if (_uninitializedKeys == null) {
39 : // means it was omitted (remote-omitted, or loaded locally), so skip
40 : return this;
41 : }
42 :
43 : // setting up from scratch, remove all and add keys
44 :
45 3 : _graph._removeEdges(_ownerKey!,
46 2 : metadata: _name!, inverseMetadata: _inverseName, notify: false);
47 :
48 2 : _graph._addEdges(
49 1 : _ownerKey!,
50 1 : tos: _uninitializedKeys!,
51 1 : metadata: _name!,
52 1 : inverseMetadata: _inverseName,
53 : notify: false,
54 : );
55 2 : _uninitializedKeys!.clear();
56 :
57 : return this;
58 : }
59 :
60 : // implement collection-like methods
61 :
62 : /// Add a [value] to this [Relationship]
63 : ///
64 : /// Attempting to add an existing [value] has no effect as this is a [Set]
65 1 : bool add(E value, {bool notify = true}) {
66 1 : if (contains(value)) {
67 : return false;
68 : }
69 :
70 4 : _graph._addEdge(_ownerKey!, value._key,
71 2 : metadata: _name!, inverseMetadata: _inverseName, notify: false);
72 : if (notify) {
73 2 : _graph._notify(
74 3 : [_ownerKey!, value._key],
75 1 : metadata: _name,
76 : type: DataGraphEventType.addEdge,
77 : );
78 : }
79 :
80 : return true;
81 : }
82 :
83 1 : bool contains(Object? element) {
84 2 : return _iterable.contains(element);
85 : }
86 :
87 : /// Removes a [value] from this [Relationship]
88 1 : bool remove(Object? value, {bool notify = true}) {
89 : assert(value is E);
90 : final model = value as E;
91 :
92 2 : _graph._removeEdge(
93 1 : _ownerKey!,
94 1 : model._key,
95 1 : metadata: _name!,
96 1 : inverseMetadata: _inverseName,
97 : notify: false,
98 : );
99 : if (notify) {
100 2 : _graph._notify(
101 3 : [_ownerKey!, value._key],
102 1 : metadata: _name,
103 : type: DataGraphEventType.removeEdge,
104 : );
105 : }
106 : return true;
107 : }
108 :
109 3 : Set<E> toSet() => _iterable.toSet();
110 :
111 3 : List<E> toList() => _iterable.toList();
112 :
113 3 : int get length => _iterable.length;
114 :
115 : //
116 :
117 3 : E get first => _iterable.first;
118 :
119 3 : bool get isEmpty => _iterable.isEmpty;
120 :
121 3 : bool get isNotEmpty => _iterable.isNotEmpty;
122 :
123 3 : Iterable<E> where(bool Function(E) test) => _iterable.where(test);
124 :
125 3 : Iterable<T> map<T>(T Function(E) f) => _iterable.map(f);
126 :
127 : // support methods
128 :
129 1 : Iterable<E> get _iterable {
130 7 : return keys.map((key) => _adapter.localAdapter.findOne(key)).filterNulls;
131 : }
132 :
133 : /// Returns keys as [Set] in relationship
134 1 : @protected
135 : @visibleForTesting
136 : Set<String> get keys {
137 5 : return _graph._getEdge(_ownerKey!, metadata: _name!).toSet();
138 : }
139 :
140 1 : Set<String> get ids {
141 7 : return keys.map((key) => _graph.getIdForKey(key)).filterNulls.toSet();
142 : }
143 :
144 1 : Set<Relationship?> andEach(AlsoWatch<E>? alsoWatch) {
145 : return {
146 1 : this,
147 3 : for (final value in _iterable) ...?alsoWatch?.call(value),
148 : };
149 : }
150 :
151 1 : DelayedStateNotifier<DataGraphEvent> get _relationshipEventNotifier {
152 4 : return _adapter.graph.where((event) {
153 2 : return event.type.isEdge &&
154 3 : event.metadata == _name &&
155 3 : event.keys.containsFirst(_ownerKey!);
156 : });
157 : }
158 :
159 : DelayedStateNotifier<N> watch();
160 :
161 : /// This is used to make `json_serializable`'s `explicitToJson` transparent.
162 : ///
163 : /// For internal use. Does not return valid JSON.
164 1 : dynamic toJson() => this;
165 :
166 1 : @override
167 4 : List<Object?> get props => [_ownerKey, _name, _inverseName];
168 :
169 1 : @override
170 : String toString() {
171 : final keysWithoutId =
172 8 : keys.where((k) => _graph.getIdForKey(k) == null).map((k) => '[$k]');
173 4 : return '${{...ids, ...keysWithoutId}.join(', ')}';
174 : }
175 : }
176 :
177 : // annotation
178 :
179 : class DataRelationship {
180 : final String? inverse;
181 : final bool serialize;
182 3 : const DataRelationship({this.inverse, this.serialize = true});
183 : }
|