hyper-model 1.0.alpha1.1 → 1.0.alpha1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.rspec +0 -1
- data/Gemfile +6 -6
- data/Rakefile +27 -3
- data/hyper-model.gemspec +10 -19
- data/lib/active_record_base.rb +101 -33
- data/lib/hyper-model.rb +4 -2
- data/lib/hyper_model/version.rb +1 -1
- data/lib/hyper_react/input_tags.rb +2 -1
- data/lib/reactive_record/active_record/associations.rb +130 -34
- data/lib/reactive_record/active_record/base.rb +17 -0
- data/lib/reactive_record/active_record/class_methods.rb +124 -52
- data/lib/reactive_record/active_record/error.rb +2 -0
- data/lib/reactive_record/active_record/errors.rb +10 -6
- data/lib/reactive_record/active_record/instance_methods.rb +74 -6
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +22 -5
- data/lib/reactive_record/active_record/reactive_record/base.rb +56 -30
- data/lib/reactive_record/active_record/reactive_record/collection.rb +219 -70
- data/lib/reactive_record/active_record/reactive_record/dummy_polymorph.rb +22 -0
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +27 -15
- data/lib/reactive_record/active_record/reactive_record/getters.rb +33 -10
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +73 -46
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +5 -5
- data/lib/reactive_record/active_record/reactive_record/operations.rb +10 -3
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +3 -0
- data/lib/reactive_record/active_record/reactive_record/setters.rb +108 -71
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +258 -41
- data/lib/reactive_record/broadcast.rb +62 -25
- data/lib/reactive_record/interval.rb +3 -3
- data/lib/reactive_record/permissions.rb +14 -2
- data/lib/reactive_record/scope_description.rb +3 -2
- data/lib/reactive_record/server_data_cache.rb +99 -49
- data/polymorph-notes.md +143 -0
- data/spec_fails.txt +3 -0
- metadata +49 -162
- data/Gemfile.lock +0 -431
@@ -4,11 +4,11 @@ module ReactiveRecord
|
|
4
4
|
# the appropriate string. The order of execution is important!
|
5
5
|
module BackingRecordInspector
|
6
6
|
def inspection_details
|
7
|
-
return error_details
|
8
|
-
return new_details
|
7
|
+
return error_details unless errors.empty?
|
8
|
+
return new_details if new?
|
9
9
|
return destroyed_details if destroyed
|
10
|
-
return loading_details
|
11
|
-
return dirty_details
|
10
|
+
return loading_details unless @attributes.key? primary_key
|
11
|
+
return dirty_details unless changed_attributes.empty?
|
12
12
|
"[loaded id: #{id}]"
|
13
13
|
end
|
14
14
|
|
@@ -26,11 +26,28 @@ module ReactiveRecord
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def loading_details
|
29
|
-
"[loading #{
|
29
|
+
"[loading #{pretty_vector}]"
|
30
30
|
end
|
31
31
|
|
32
32
|
def dirty_details
|
33
33
|
"[changed id: #{id} #{changes}]"
|
34
34
|
end
|
35
|
+
|
36
|
+
def pretty_vector
|
37
|
+
v = []
|
38
|
+
i = 0
|
39
|
+
while i < vector.length
|
40
|
+
if vector[i] == 'all' && vector[i + 1].is_a?(Array) &&
|
41
|
+
vector[i + 1][0] == '___hyperstack_internal_scoped_find_by' &&
|
42
|
+
vector[i + 2] == '*0'
|
43
|
+
v << ['find_by', vector[i + 1][1]]
|
44
|
+
i += 3
|
45
|
+
else
|
46
|
+
v << vector[i]
|
47
|
+
i += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
v
|
51
|
+
end
|
35
52
|
end
|
36
53
|
end
|
@@ -38,6 +38,7 @@ module ReactiveRecord
|
|
38
38
|
attr_accessor :aggregate_owner
|
39
39
|
attr_accessor :aggregate_attribute
|
40
40
|
attr_accessor :destroyed
|
41
|
+
attr_accessor :being_destroyed
|
41
42
|
attr_accessor :updated_during
|
42
43
|
attr_accessor :synced_attributes
|
43
44
|
attr_accessor :virgin
|
@@ -67,30 +68,35 @@ module ReactiveRecord
|
|
67
68
|
load_data { ServerDataCache.load_from_json(json, target) }
|
68
69
|
end
|
69
70
|
|
71
|
+
def self.find_locally(model, attrs, new_only: nil)
|
72
|
+
if (id_to_find = attrs[model.primary_key])
|
73
|
+
!new_only && lookup_by_id(model, id_to_find)
|
74
|
+
else
|
75
|
+
@records[model.base_class].detect do |r|
|
76
|
+
(r.new? || !new_only) &&
|
77
|
+
!attrs.detect { |attr, value| r.synced_attributes[attr] != value }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.find_by_id(model, id)
|
83
|
+
find(model, model.primary_key => id)
|
84
|
+
end
|
85
|
+
|
70
86
|
def self.find(model, attrs)
|
71
87
|
# will return the unique record with this attribute-value pair
|
72
88
|
# value cannot be an association or aggregation
|
73
89
|
|
74
90
|
# add the inheritance column if this is an STI subclass
|
75
91
|
|
76
|
-
|
77
|
-
if inher_col && model < model.base_class && !attrs.key?(inher_col)
|
78
|
-
attrs = attrs.merge(inher_col => model.model_name.to_s)
|
79
|
-
end
|
92
|
+
attrs = model.__hyperstack_preprocess_attrs(attrs)
|
80
93
|
|
81
94
|
model = model.base_class
|
82
95
|
primary_key = model.primary_key
|
83
96
|
|
84
97
|
# already have a record with these attribute-value pairs?
|
85
98
|
|
86
|
-
record =
|
87
|
-
if (id_to_find = attrs[primary_key])
|
88
|
-
lookup_by_id(model, id_to_find)
|
89
|
-
else
|
90
|
-
@records[model].detect do |r|
|
91
|
-
!attrs.detect { |attr, value| r.synced_attributes[attr] != value }
|
92
|
-
end
|
93
|
-
end
|
99
|
+
record = find_locally(model, attrs)
|
94
100
|
|
95
101
|
unless record
|
96
102
|
# if not, and then the record may be loaded, but not have this attribute set yet,
|
@@ -102,10 +108,8 @@ module ReactiveRecord
|
|
102
108
|
attrs = attrs.merge primary_key => id
|
103
109
|
end
|
104
110
|
# if we don't have a record then create one
|
105
|
-
|
106
|
-
record
|
107
|
-
# and set the values
|
108
|
-
attrs.each { |attr, value| record.sync_attribute(attr, value) }
|
111
|
+
record ||= set_vector_lookup(new(model), [model, *find_by_vector(attrs)])
|
112
|
+
record.sync_attributes(attrs)
|
109
113
|
end
|
110
114
|
# finally initialize and return the ar_instance
|
111
115
|
record.set_ar_instance!
|
@@ -115,6 +119,9 @@ module ReactiveRecord
|
|
115
119
|
# this is the equivilent of find but for associations and aggregations
|
116
120
|
# because we are not fetching a specific attribute yet, there is NO communication with the
|
117
121
|
# server. That only happens during find.
|
122
|
+
|
123
|
+
return DummyPolymorph.new(vector) unless model
|
124
|
+
|
118
125
|
model = model.base_class
|
119
126
|
|
120
127
|
# do we already have a record with this vector? If so return it, otherwise make a new one.
|
@@ -122,7 +129,6 @@ module ReactiveRecord
|
|
122
129
|
# record = @records[model].detect { |record| record.vector == vector }
|
123
130
|
record = lookup_by_vector(vector)
|
124
131
|
unless record
|
125
|
-
|
126
132
|
record = new model
|
127
133
|
set_vector_lookup(record, vector)
|
128
134
|
end
|
@@ -145,7 +151,7 @@ module ReactiveRecord
|
|
145
151
|
@attributes = {}
|
146
152
|
@changed_attributes = []
|
147
153
|
@virgin = true
|
148
|
-
records[model] << self
|
154
|
+
records[model.base_class] << self
|
149
155
|
Base.set_object_id_lookup(self)
|
150
156
|
end
|
151
157
|
|
@@ -175,15 +181,16 @@ module ReactiveRecord
|
|
175
181
|
@ar_instance.instance_variable_set(:@backing_record, existing_record)
|
176
182
|
existing_record.attributes.merge!(attributes) { |key, v1, v2| v1 }
|
177
183
|
end
|
184
|
+
@id = value
|
178
185
|
value
|
179
186
|
end
|
180
187
|
|
181
188
|
def changed?(*args)
|
182
189
|
if args.count == 0
|
183
|
-
Hyperstack::Internal::
|
190
|
+
Hyperstack::Internal::State::Variable.get(self, "!CHANGED!")
|
184
191
|
!changed_attributes.empty?
|
185
192
|
else
|
186
|
-
Hyperstack::Internal::
|
193
|
+
Hyperstack::Internal::State::Variable.get(self, args[0])
|
187
194
|
changed_attributes.include? args[0]
|
188
195
|
end
|
189
196
|
end
|
@@ -201,7 +208,7 @@ module ReactiveRecord
|
|
201
208
|
end
|
202
209
|
|
203
210
|
def errors
|
204
|
-
@errors ||= ActiveModel::Errors.new(
|
211
|
+
@errors ||= ActiveModel::Errors.new(ar_instance)
|
205
212
|
end
|
206
213
|
|
207
214
|
# called when we have a newly created record, to initialize
|
@@ -211,7 +218,7 @@ module ReactiveRecord
|
|
211
218
|
|
212
219
|
def initialize_collections
|
213
220
|
if (!vector || vector.empty?) && id && id != ''
|
214
|
-
Base.set_vector_lookup(self, [@model,
|
221
|
+
Base.set_vector_lookup(self, [@model, *find_by_vector(@model.primary_key => id)])
|
215
222
|
end
|
216
223
|
Base.load_data do
|
217
224
|
@model.reflect_on_all_associations.each do |assoc|
|
@@ -247,12 +254,16 @@ module ReactiveRecord
|
|
247
254
|
return if @create_sync
|
248
255
|
@create_sync = true
|
249
256
|
end
|
250
|
-
model.unscoped
|
257
|
+
model.unscoped._internal_push ar_instance
|
251
258
|
@synced_with_unscoped = !@synced_with_unscoped
|
252
259
|
end
|
253
260
|
|
254
|
-
def
|
261
|
+
def sync_attributes(attrs)
|
262
|
+
attrs.each { |attr, value| sync_attribute(attr, value) }
|
263
|
+
self
|
264
|
+
end
|
255
265
|
|
266
|
+
def sync_attribute(attribute, value)
|
256
267
|
@synced_attributes[attribute] = @attributes[attribute] = value
|
257
268
|
Base.set_id_lookup(self) if attribute == primary_key
|
258
269
|
|
@@ -260,7 +271,7 @@ module ReactiveRecord
|
|
260
271
|
|
261
272
|
if value.is_a? Collection
|
262
273
|
@synced_attributes[attribute] = value.dup_for_sync
|
263
|
-
elsif aggregation = model.reflect_on_aggregation(attribute)
|
274
|
+
elsif (aggregation = model.reflect_on_aggregation(attribute)) && (aggregation.klass < ActiveRecord::Base)
|
264
275
|
value.backing_record.sync!
|
265
276
|
elsif aggregation
|
266
277
|
@synced_attributes[attribute] = aggregation.deserialize(aggregation.serialize(value))
|
@@ -278,6 +289,14 @@ module ReactiveRecord
|
|
278
289
|
Base.lookup_by_id(model, id)
|
279
290
|
end
|
280
291
|
|
292
|
+
def id_loaded?
|
293
|
+
@id
|
294
|
+
end
|
295
|
+
|
296
|
+
def loaded_id=(id)
|
297
|
+
@id = id
|
298
|
+
end
|
299
|
+
|
281
300
|
def revert
|
282
301
|
@changed_attributes.dup.each do |attribute|
|
283
302
|
@ar_instance.send("#{attribute}=", @synced_attributes[attribute])
|
@@ -288,13 +307,15 @@ module ReactiveRecord
|
|
288
307
|
end
|
289
308
|
|
290
309
|
def saving!
|
291
|
-
Hyperstack::Internal::
|
310
|
+
Hyperstack::Internal::State::Variable.set(self, self, :saving) unless data_loading?
|
292
311
|
@saving = true
|
293
312
|
end
|
294
313
|
|
295
|
-
def errors!(hash)
|
314
|
+
def errors!(hash, saving = false)
|
315
|
+
@errors_at_last_sync = hash if saving
|
296
316
|
notify_waiting_for_save
|
297
317
|
errors.clear && return unless hash
|
318
|
+
errors.non_reactive_clear
|
298
319
|
hash.each do |attribute, messages|
|
299
320
|
messages.each do |message|
|
300
321
|
errors.add(attribute, message)
|
@@ -302,13 +323,17 @@ module ReactiveRecord
|
|
302
323
|
end
|
303
324
|
end
|
304
325
|
|
326
|
+
def revert_errors!
|
327
|
+
errors!(@errors_at_last_sync)
|
328
|
+
end
|
329
|
+
|
305
330
|
def saved!(save_only = nil) # sets saving to false AND notifies
|
306
331
|
notify_waiting_for_save
|
307
332
|
return self if save_only
|
308
333
|
if errors.empty?
|
309
|
-
Hyperstack::Internal::
|
334
|
+
Hyperstack::Internal::State::Variable.set(self, self, :saved)
|
310
335
|
elsif !data_loading?
|
311
|
-
Hyperstack::Internal::
|
336
|
+
Hyperstack::Internal::State::Variable.set(self, self, :error)
|
312
337
|
end
|
313
338
|
self
|
314
339
|
end
|
@@ -334,7 +359,7 @@ module ReactiveRecord
|
|
334
359
|
end
|
335
360
|
|
336
361
|
def saving?
|
337
|
-
Hyperstack::Internal::
|
362
|
+
Hyperstack::Internal::State::Variable.get(self, self)
|
338
363
|
@saving
|
339
364
|
end
|
340
365
|
|
@@ -403,6 +428,7 @@ module ReactiveRecord
|
|
403
428
|
|
404
429
|
def destroy_associations
|
405
430
|
@destroyed = false
|
431
|
+
@being_destroyed = true
|
406
432
|
model.reflect_on_all_associations.each do |association|
|
407
433
|
if association.collection?
|
408
434
|
@attributes[association.attribute].replace([]) if @attributes[association.attribute]
|
@@ -29,7 +29,7 @@ module ReactiveRecord
|
|
29
29
|
@owner = owner # can be nil if this is an outer most scope
|
30
30
|
@association = association
|
31
31
|
@target_klass = target_klass
|
32
|
-
if owner
|
32
|
+
if owner && !owner.id && vector.length <= 1
|
33
33
|
@collection = []
|
34
34
|
elsif vector.length > 0
|
35
35
|
@vector = vector
|
@@ -52,14 +52,19 @@ module ReactiveRecord
|
|
52
52
|
def all
|
53
53
|
observed
|
54
54
|
@dummy_collection.notify if @dummy_collection
|
55
|
+
# unless false && @collection # this fixes https://github.com/hyperstack-org/hyperstack/issues/82 in very limited cases, and breaks otherthings
|
56
|
+
# sync_collection_with_parent
|
57
|
+
# end
|
55
58
|
unless @collection
|
56
59
|
@collection = []
|
57
60
|
if ids = ReactiveRecord::Base.fetch_from_db([*@vector, "*all"])
|
58
61
|
ids.each do |id|
|
59
|
-
@collection <<
|
62
|
+
@collection << ReactiveRecord::Base.find_by_id(@target_klass, id)
|
60
63
|
end
|
61
64
|
else
|
62
65
|
@dummy_collection = ReactiveRecord::Base.load_from_db(nil, *@vector, "*all")
|
66
|
+
# this calls back to all now that the collection is initialized,
|
67
|
+
# so it has the side effect of creating a dummy value in collection[0]
|
63
68
|
@dummy_record = self[0]
|
64
69
|
end
|
65
70
|
end
|
@@ -72,6 +77,8 @@ module ReactiveRecord
|
|
72
77
|
(@collection.length..index).each do |i|
|
73
78
|
new_dummy_record = ReactiveRecord::Base.new_from_vector(@target_klass, nil, *@vector, "*#{i}")
|
74
79
|
new_dummy_record.attributes[@association.inverse_of] = @owner if @association && !@association.through_association?
|
80
|
+
# HMT-TODO: the above needs to be looked into... if we are a hmt then don't we need to create a dummy on the joins collection as well?
|
81
|
+
# or maybe this just does not work for HMT?
|
75
82
|
@collection << new_dummy_record
|
76
83
|
end
|
77
84
|
end
|
@@ -80,8 +87,15 @@ module ReactiveRecord
|
|
80
87
|
|
81
88
|
def ==(other_collection)
|
82
89
|
observed
|
83
|
-
|
90
|
+
# handle special case of other_collection NOT being a collection (typically nil)
|
91
|
+
return (@collection || []) == other_collection unless other_collection.is_a? Collection
|
84
92
|
other_collection.observed
|
93
|
+
# if either collection has not been created then compare the vectors
|
94
|
+
# https://github.com/hyperstack-org/hyperstack/issues/81
|
95
|
+
# TODO: if this works then remove the || [] below (2 of them)
|
96
|
+
if !@collection || !other_collection.collection
|
97
|
+
return @vector == other_collection.vector && unsaved_children == other_collection.unsaved_children
|
98
|
+
end
|
85
99
|
my_children = (@collection || []).select { |target| target != @dummy_record }
|
86
100
|
if other_collection
|
87
101
|
other_children = (other_collection.collection || []).select { |target| target != other_collection.dummy_record }
|
@@ -93,7 +107,7 @@ module ReactiveRecord
|
|
93
107
|
end
|
94
108
|
# todo move following to a separate module related to scope updates ******************
|
95
109
|
attr_reader :vector
|
96
|
-
|
110
|
+
attr_accessor :scope_description
|
97
111
|
attr_writer :parent
|
98
112
|
attr_reader :pre_sync_related_records
|
99
113
|
|
@@ -127,7 +141,10 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
127
141
|
|
128
142
|
|
129
143
|
=end
|
144
|
+
attr_accessor :broadcast_updated_at
|
145
|
+
|
130
146
|
def sync_scopes(broadcast)
|
147
|
+
self.broadcast_updated_at = broadcast.updated_at
|
131
148
|
# record_with_current_values will return nil if data between
|
132
149
|
# the broadcast record and the value on the client is out of sync
|
133
150
|
# not running set_pre_sync_related_records will cause sync scopes
|
@@ -145,6 +162,8 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
145
162
|
)
|
146
163
|
record.backing_record.sync_unscoped_collection! if record.destroyed? || broadcast.new?
|
147
164
|
end
|
165
|
+
ensure
|
166
|
+
self.broadcast_updated_at = nil
|
148
167
|
end
|
149
168
|
|
150
169
|
def apply_to_all_collections(method, record, dont_gather)
|
@@ -200,7 +219,7 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
200
219
|
return [] unless attrs[@association.inverse_of] == @owner
|
201
220
|
if !@association.through_association
|
202
221
|
[record]
|
203
|
-
elsif (source = attrs[@association.source])
|
222
|
+
elsif (source = attrs[@association.source]) && source.is_a?(@target_klass)
|
204
223
|
[source]
|
205
224
|
else
|
206
225
|
[]
|
@@ -212,7 +231,6 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
212
231
|
end
|
213
232
|
|
214
233
|
def filter_records(related_records)
|
215
|
-
# possibly we should never get here???
|
216
234
|
scope_args = @vector.last.is_a?(Array) ? @vector.last[1..-1] : []
|
217
235
|
@scope_description.filter_records(related_records, scope_args)
|
218
236
|
end
|
@@ -221,16 +239,23 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
221
239
|
@live_scopes ||= Set.new
|
222
240
|
end
|
223
241
|
|
242
|
+
def in_this_collection(related_records)
|
243
|
+
# HMT-TODO: I don't think we can get a set of related records here with a through association unless they are part of the collection
|
244
|
+
return related_records if !@association || @association.through_association?
|
245
|
+
related_records.select do |r|
|
246
|
+
r.backing_record.attributes[@association.inverse_of] == @owner
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
224
250
|
def set_pre_sync_related_records(related_records, _record = nil)
|
225
|
-
|
226
|
-
@pre_sync_related_records = related_records #in_this_collection related_records <- not sure if this works
|
251
|
+
@pre_sync_related_records = in_this_collection(related_records)
|
227
252
|
live_scopes.each { |scope| scope.set_pre_sync_related_records(@pre_sync_related_records) }
|
228
253
|
end
|
229
254
|
|
230
255
|
# NOTE sync_scopes is overridden in scope_description.rb
|
231
256
|
def sync_scopes(related_records, record, filtering = true)
|
232
257
|
#related_records = related_records.intersection([*@collection])
|
233
|
-
|
258
|
+
related_records = in_this_collection(related_records) if filtering
|
234
259
|
live_scopes.each { |scope| scope.sync_scopes(related_records, record, filtering) }
|
235
260
|
notify_of_change unless related_records.empty?
|
236
261
|
ensure
|
@@ -261,11 +286,15 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
261
286
|
end
|
262
287
|
|
263
288
|
def link_to_parent
|
264
|
-
|
289
|
+
# puts "#{self}.link_to_parent @linked = #{!!@linked}, collection? #{!!@collection}"
|
290
|
+
# always check that parent is synced - fixes issue https://github.com/hyperstack-org/hyperstack/issues/82
|
291
|
+
# note that sync_collection_with_parent checks to make sure that is NOT a collection and that there IS a parent
|
292
|
+
|
293
|
+
return sync_collection_with_parent if @linked
|
265
294
|
@linked = true
|
266
295
|
if @parent
|
267
296
|
@parent.link_child self
|
268
|
-
sync_collection_with_parent
|
297
|
+
sync_collection_with_parent
|
269
298
|
else
|
270
299
|
ReactiveRecord::Base.add_to_outer_scopes self
|
271
300
|
end
|
@@ -278,21 +307,30 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
278
307
|
end
|
279
308
|
|
280
309
|
def sync_collection_with_parent
|
310
|
+
# puts "#{self}.sync_collection_with_parent"
|
311
|
+
return if @collection || !@parent || @parent.dummy_collection # fixes issue https://github.com/hyperstack-org/hyperstack/issues/78 and supports /82
|
281
312
|
if @parent.collection
|
313
|
+
# puts ">>> @parent.collection present"
|
282
314
|
if @parent.collection.empty?
|
315
|
+
# puts ">>>>> @parent.collection is empty!"
|
283
316
|
@collection = []
|
284
317
|
elsif filter?
|
285
|
-
@
|
318
|
+
# puts "#{self}.sync_collection_with_parent (@parent = #{@parent}) calling filter records on (#{@parent.collection})"
|
319
|
+
@collection = filter_records(@parent.collection).to_a
|
286
320
|
end
|
287
|
-
elsif @parent._count_internal(false).zero?
|
321
|
+
elsif !@linked && @parent._count_internal(false).zero?
|
322
|
+
# don't check _count_internal if already linked as this cause an unnecessary rendering cycle
|
323
|
+
# puts ">>> @parent._count_internal(false).zero? is true!"
|
288
324
|
@count = 0
|
325
|
+
else
|
326
|
+
# puts ">>> NOP"
|
289
327
|
end
|
290
328
|
end
|
291
329
|
|
292
330
|
# end of stuff to move
|
293
331
|
|
294
332
|
def reload_from_db(force = nil)
|
295
|
-
if force || Hyperstack::Internal::
|
333
|
+
if force || Hyperstack::Internal::State::Variable.observed?(self, :collection)
|
296
334
|
@out_of_date = false
|
297
335
|
ReactiveRecord::Base.load_from_db(nil, *@vector, '*all') if @collection
|
298
336
|
ReactiveRecord::Base.load_from_db(nil, *@vector, '*count')
|
@@ -303,31 +341,33 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
303
341
|
end
|
304
342
|
|
305
343
|
def observed
|
306
|
-
return if @observing || ReactiveRecord::Base.data_loading?
|
344
|
+
return self if @observing || ReactiveRecord::Base.data_loading?
|
307
345
|
begin
|
308
346
|
@observing = true
|
309
347
|
link_to_parent
|
310
348
|
reload_from_db(true) if @out_of_date
|
311
|
-
Hyperstack::Internal::
|
349
|
+
Hyperstack::Internal::State::Variable.get(self, :collection)
|
350
|
+
self
|
312
351
|
ensure
|
313
352
|
@observing = false
|
314
353
|
end
|
315
354
|
end
|
316
355
|
|
317
|
-
def
|
356
|
+
def count_state=(val)
|
318
357
|
unless ReactiveRecord::WhileLoading.observed?
|
319
|
-
Hyperstack::Internal::
|
358
|
+
Hyperstack::Internal::State::Variable.set(self, :collection, collection, true)
|
320
359
|
end
|
360
|
+
@count_updated_at = ReactiveRecord::Operations::Base.last_response_sent_at
|
321
361
|
@count = val
|
322
362
|
end
|
323
363
|
|
324
|
-
|
325
|
-
|
326
364
|
def _count_internal(load_from_client)
|
327
365
|
# when count is called on a leaf, count_internal is called for each
|
328
366
|
# ancestor. Only the outermost count has load_from_client == true
|
329
367
|
observed
|
330
|
-
if @
|
368
|
+
if @count && @dummy_collection
|
369
|
+
@count # fixes https://github.com/hyperstack-org/hyperstack/issues/79
|
370
|
+
elsif @collection
|
331
371
|
@collection.count
|
332
372
|
elsif @count ||= ReactiveRecord::Base.fetch_from_db([*@vector, "*count"])
|
333
373
|
@count
|
@@ -366,7 +406,7 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
366
406
|
# child.test_model = 1
|
367
407
|
# so... we go back starting at this collection and look for the first
|
368
408
|
# collection with an owner... that is our guy
|
369
|
-
child = proxy_association.klass
|
409
|
+
child = ReactiveRecord::Base.find_by_id(proxy_association.klass, id)
|
370
410
|
push child
|
371
411
|
set_belongs_to child
|
372
412
|
end
|
@@ -374,7 +414,11 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
374
414
|
def set_belongs_to(child)
|
375
415
|
if @owner
|
376
416
|
# TODO this is major broken...current
|
377
|
-
|
417
|
+
if (through_association = @association.through_association)
|
418
|
+
# HMT-TODO: create a new record with owner and child
|
419
|
+
else
|
420
|
+
child.send("#{@association.inverse_of}=", @owner) if @association && !@association.through_association
|
421
|
+
end
|
378
422
|
elsif @parent
|
379
423
|
@parent.set_belongs_to(child)
|
380
424
|
end
|
@@ -389,36 +433,57 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
389
433
|
|
390
434
|
def update_child(item)
|
391
435
|
backing_record = item.backing_record
|
392
|
-
|
436
|
+
# HMT TODO: The following && !association.through_association was commented out, causing wrong class items to be added to
|
437
|
+
# associations
|
438
|
+
# Why was it commented out.
|
439
|
+
if backing_record && @owner && @association && item.attributes[@association.inverse_of] != @owner && !@association.through_association?
|
393
440
|
inverse_of = @association.inverse_of
|
394
|
-
|
441
|
+
current_association_value = item.attributes[inverse_of]
|
395
442
|
backing_record.virgin = false unless backing_record.data_loading?
|
443
|
+
# next line was commented out and following line was active.
|
396
444
|
backing_record.update_belongs_to(inverse_of, @owner)
|
397
|
-
|
398
|
-
|
399
|
-
|
445
|
+
#backing_record.set_belongs_to_via_has_many(@association, @owner)
|
446
|
+
# following is handled by update_belongs_to and is redundant
|
447
|
+
# unless current_association_value.nil? # might be a dummy value which responds to nil
|
448
|
+
# current_association = @association.inverse.inverse(current_association_value)
|
449
|
+
# current_association_attribute = current_association.attribute
|
450
|
+
# if current_association.collection? && current_association_value.attributes[current_association_attribute]
|
451
|
+
# current_association.attributes[current_association_attribute].delete(item)
|
452
|
+
# end
|
453
|
+
# end
|
400
454
|
@owner.backing_record.sync_has_many(@association.attribute)
|
401
455
|
end
|
402
456
|
end
|
403
457
|
|
404
458
|
def push(item)
|
405
|
-
|
406
|
-
|
407
|
-
self
|
459
|
+
if (through_association = @association&.through_association)
|
460
|
+
through_association.klass.create(@association.inverse_of => @owner, @association.source => item)
|
461
|
+
self
|
408
462
|
else
|
409
|
-
|
410
|
-
update_child(item)
|
411
|
-
@owner.backing_record.sync_has_many(@association.attribute) if @owner && @association
|
412
|
-
if !@count.nil?
|
413
|
-
@count += item.destroyed? ? -1 : 1
|
414
|
-
notify_of_change self
|
415
|
-
end
|
463
|
+
_internal_push(item)
|
416
464
|
end
|
417
|
-
self
|
418
465
|
end
|
419
466
|
|
420
467
|
alias << push
|
421
468
|
|
469
|
+
def _internal_push(item)
|
470
|
+
insure_sync do
|
471
|
+
item.itself # force get of at least the id
|
472
|
+
if collection
|
473
|
+
self.force_push item
|
474
|
+
else
|
475
|
+
unsaved_children << item
|
476
|
+
update_child(item)
|
477
|
+
@owner.backing_record.sync_has_many(@association.attribute) if @owner && @association
|
478
|
+
if !@count.nil?
|
479
|
+
@count += (item.destroyed? ? -1 : 1)
|
480
|
+
notify_of_change self
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
self
|
485
|
+
end
|
486
|
+
|
422
487
|
def sort!(*args, &block)
|
423
488
|
replace(sort(*args, &block))
|
424
489
|
end
|
@@ -438,13 +503,29 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
438
503
|
notify_of_change self
|
439
504
|
end
|
440
505
|
|
441
|
-
[:first, :last].each do |method|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
506
|
+
# [:first, :last].each do |method|
|
507
|
+
# define_method method do |*args|
|
508
|
+
# if args.count == 0
|
509
|
+
# all.send(method)
|
510
|
+
# else
|
511
|
+
# apply_scope(method, *args)
|
512
|
+
# end
|
513
|
+
# end
|
514
|
+
# end
|
515
|
+
|
516
|
+
def first(n = nil)
|
517
|
+
if n
|
518
|
+
apply_scope(:first, n)
|
519
|
+
else
|
520
|
+
self[0]
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def last(n = nil)
|
525
|
+
if n
|
526
|
+
apply_scope(:__hyperstack_internal_scoped_last_n, n)
|
527
|
+
else
|
528
|
+
__hyperstack_internal_scoped_last
|
448
529
|
end
|
449
530
|
end
|
450
531
|
|
@@ -466,46 +547,73 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
466
547
|
@dummy_collection.notify
|
467
548
|
array = new_array.is_a?(Collection) ? new_array.collection : new_array
|
468
549
|
@collection.each_with_index do |r, i|
|
469
|
-
r.id = new_array[i].id if array[i] and array[i].id and !r.
|
550
|
+
r.id = new_array[i].id if array[i] and array[i].id and !r.new_record? and r.backing_record.vector.last =~ /^\*[0-9]+$/
|
470
551
|
end
|
471
552
|
end
|
472
|
-
|
473
|
-
@collection.dup.each { |item| delete(item) } if @collection
|
553
|
+
# the following makes sure that the existing elements are properly removed from the collection
|
554
|
+
@collection.dup.each { |item| delete(item) } if @collection
|
474
555
|
@collection = []
|
475
556
|
if new_array.is_a? Collection
|
476
557
|
@dummy_collection = new_array.dummy_collection
|
477
558
|
@dummy_record = new_array.dummy_record
|
478
|
-
new_array.collection.each { |item|
|
559
|
+
new_array.collection.each { |item| _internal_push item } if new_array.collection
|
479
560
|
else
|
480
561
|
@dummy_collection = @dummy_record = nil
|
481
|
-
new_array.each { |item|
|
562
|
+
new_array.each { |item| _internal_push item }
|
482
563
|
end
|
483
564
|
notify_of_change new_array
|
484
565
|
end
|
485
566
|
|
486
|
-
def
|
487
|
-
|
488
|
-
|
489
|
-
if @owner && @association
|
567
|
+
def destroy_non_habtm(item)
|
568
|
+
Hyperstack::Internal::State::Mapper.bulk_update do
|
569
|
+
unsaved_children.delete(item)
|
570
|
+
if @owner && @association
|
490
571
|
inverse_of = @association.inverse_of
|
491
|
-
if (backing_record = item.backing_record) && item.attributes[inverse_of] == @owner
|
572
|
+
if (backing_record = item.backing_record) && item.attributes[inverse_of] == @owner && !@association.through_association?
|
492
573
|
# the if prevents double update if delete is being called from << (see << above)
|
493
574
|
backing_record.update_belongs_to(inverse_of, nil)
|
494
575
|
end
|
495
576
|
delete_internal(item) { @owner.backing_record.sync_has_many(@association.attribute) }
|
496
577
|
else
|
497
578
|
delete_internal(item)
|
498
|
-
end
|
579
|
+
end.tap { Hyperstack::Internal::State::Variable.set(self, :collection, collection) }
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
def destroy(item)
|
584
|
+
return destroy_non_habtm(item) unless @association&.habtm?
|
585
|
+
|
586
|
+
ta = @association.through_association
|
587
|
+
item_foreign_key = @association.source_belongs_to_association.association_foreign_key
|
588
|
+
join_record = ta.klass.find_by(
|
589
|
+
ta.association_foreign_key => @owner.id,
|
590
|
+
item_foreign_key => item.id
|
499
591
|
)
|
592
|
+
return destroy_non_habtm(item) if join_record.nil? ||
|
593
|
+
join_record.backing_record.being_destroyed
|
594
|
+
|
595
|
+
join_record&.destroy
|
596
|
+
end
|
597
|
+
|
598
|
+
def insure_sync
|
599
|
+
if Collection.broadcast_updated_at && @count_updated_at && Collection.broadcast_updated_at < @count_updated_at
|
600
|
+
reload_from_db
|
601
|
+
else
|
602
|
+
yield
|
603
|
+
end
|
500
604
|
end
|
501
605
|
|
606
|
+
alias delete destroy
|
607
|
+
|
502
608
|
def delete_internal(item)
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
609
|
+
insure_sync do
|
610
|
+
if collection
|
611
|
+
all.delete(item)
|
612
|
+
elsif !@count.nil?
|
613
|
+
@count -= 1
|
614
|
+
end
|
615
|
+
yield if block_given? # was yield item, but item is not used
|
507
616
|
end
|
508
|
-
yield if block_given? # was yield item, but item is not used
|
509
617
|
item
|
510
618
|
end
|
511
619
|
|
@@ -514,19 +622,61 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
514
622
|
@dummy_collection.loading?
|
515
623
|
end
|
516
624
|
|
625
|
+
def find_by(attrs)
|
626
|
+
attrs = @target_klass.__hyperstack_preprocess_attrs(attrs)
|
627
|
+
(r = __hyperstack_internal_scoped_find_by(attrs)) || return
|
628
|
+
r.backing_record.sync_attributes(attrs).set_ar_instance!
|
629
|
+
end
|
630
|
+
|
631
|
+
def find(*args)
|
632
|
+
args = args[0] if args[0].is_a? Array
|
633
|
+
return args.collect { |id| find(id) } if args.count > 1
|
634
|
+
find_by(@target_klass.primary_key => args[0])
|
635
|
+
end
|
636
|
+
|
637
|
+
def _find_by_initializer(scope, attrs)
|
638
|
+
found =
|
639
|
+
if scope.is_a? Collection
|
640
|
+
scope.parent.collection&.detect { |lr| !attrs.detect { |k, v| lr.attributes[k] != v } }
|
641
|
+
else
|
642
|
+
ReactiveRecord::Base.find_locally(@target_klass, attrs)&.ar_instance
|
643
|
+
end
|
644
|
+
return first unless found
|
645
|
+
@collection = [found]
|
646
|
+
found
|
647
|
+
end
|
648
|
+
|
649
|
+
# to avoid fetching the entire collection array we check empty and any against the count
|
650
|
+
|
517
651
|
def empty?
|
518
|
-
|
519
|
-
|
520
|
-
|
652
|
+
count.zero?
|
653
|
+
end
|
654
|
+
|
655
|
+
def any?(*args, &block)
|
656
|
+
# If there are any args passed in, then the collection is being used in the condition
|
657
|
+
# and we must load it all into memory.
|
658
|
+
return all.any?(*args, &block) if args&.length&.positive? || block.present?
|
659
|
+
|
660
|
+
# Otherwise we can just check the count for efficiency
|
661
|
+
!empty?
|
662
|
+
end
|
663
|
+
|
664
|
+
def none?(*args, &block)
|
665
|
+
# If there are any args passed in, then the collection is being used in the condition
|
666
|
+
# and we must load it all into memory.
|
667
|
+
return all.none?(*args, &block) if args&.length&.positive? || block.present?
|
668
|
+
|
669
|
+
# Otherwise we can just check the count for efficiency
|
670
|
+
empty?
|
521
671
|
end
|
522
672
|
|
523
673
|
def method_missing(method, *args, &block)
|
524
|
-
if
|
674
|
+
if args.count == 1 && method.start_with?('find_by_')
|
675
|
+
find_by(method.sub(/^find_by_/, '') => args[0])
|
676
|
+
elsif [].respond_to? method
|
525
677
|
all.send(method, *args, &block)
|
526
678
|
elsif ScopeDescription.find(@target_klass, method)
|
527
679
|
apply_scope(method, *args)
|
528
|
-
elsif args.count == 1 && method.start_with?('find_by_')
|
529
|
-
apply_scope(:find_by, method.sub(/^find_by_/, '') => args.first)
|
530
680
|
elsif @target_klass.respond_to?(method) && ScopeDescription.find(@target_klass, "_#{method}")
|
531
681
|
apply_scope("_#{method}", *args).first
|
532
682
|
else
|
@@ -549,10 +699,9 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
549
699
|
end
|
550
700
|
|
551
701
|
def notify_of_change(value = nil)
|
552
|
-
Hyperstack::Internal::
|
702
|
+
Hyperstack::Internal::State::Variable.set(self, "collection", collection) unless ReactiveRecord::Base.data_loading?
|
553
703
|
value
|
554
704
|
end
|
555
|
-
|
556
705
|
end
|
557
706
|
|
558
707
|
end
|