hyper-mesh 1.0.0.lap27 → 1.0.0.lap28
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 +1 -0
- data/.rubocop.yml +6 -2
- data/Gemfile +0 -1
- data/Rakefile +2 -2
- data/hyper-mesh.gemspec +1 -1
- data/lib/active_record_base.rb +39 -27
- data/lib/hyper-mesh.rb +6 -1
- data/lib/hypermesh/version.rb +1 -1
- data/lib/object/tap.rb +7 -0
- data/lib/reactive_record/active_record/associations.rb +14 -3
- data/lib/reactive_record/active_record/base.rb +1 -2
- data/lib/reactive_record/active_record/class_methods.rb +120 -67
- data/lib/reactive_record/active_record/error.rb +17 -12
- data/lib/reactive_record/active_record/errors.rb +374 -0
- data/lib/reactive_record/active_record/instance_methods.rb +58 -67
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +1 -4
- data/lib/reactive_record/active_record/reactive_record/base.rb +129 -234
- data/lib/reactive_record/active_record/reactive_record/collection.rb +51 -18
- data/lib/reactive_record/active_record/reactive_record/column_types.rb +5 -3
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +6 -4
- data/lib/reactive_record/active_record/reactive_record/getters.rb +133 -0
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +99 -87
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +54 -0
- data/lib/reactive_record/active_record/reactive_record/operations.rb +2 -1
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +1 -1
- data/lib/reactive_record/active_record/reactive_record/setters.rb +194 -0
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +4 -5
- data/lib/reactive_record/active_record_error.rb +4 -0
- data/lib/reactive_record/broadcast.rb +55 -18
- data/lib/reactive_record/permissions.rb +5 -4
- data/lib/reactive_record/scope_description.rb +14 -6
- data/lib/reactive_record/server_data_cache.rb +119 -70
- metadata +16 -13
- data/lib/reactive_record/active_record/reactive_record/reactive_set_relationship_helpers.rb +0 -189
@@ -7,7 +7,7 @@ module ReactiveRecord
|
|
7
7
|
return error_details unless errors.empty?
|
8
8
|
return new_details if new?
|
9
9
|
return destroyed_details if destroyed
|
10
|
-
return loading_details unless attributes.key? primary_key
|
10
|
+
return loading_details unless @attributes.key? primary_key
|
11
11
|
return dirty_details unless changed_attributes.empty?
|
12
12
|
"[loaded id: #{id}]"
|
13
13
|
end
|
@@ -30,9 +30,6 @@ module ReactiveRecord
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def dirty_details
|
33
|
-
changes = Hash[changed_attributes.collect do |attr|
|
34
|
-
[attr, [@synced_attributes[attr], attributes[attr]]] if column_type(attr)
|
35
|
-
end.compact]
|
36
33
|
"[changed id: #{id} #{changes}]"
|
37
34
|
end
|
38
35
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module ReactiveRecord
|
2
2
|
class Base
|
3
3
|
include BackingRecordInspector
|
4
|
+
include Setters
|
5
|
+
include Getters
|
6
|
+
extend LookupTables
|
4
7
|
|
5
8
|
# Its all about lazy loading. This prevents us from grabbing enormous association collections, or large attributes
|
6
9
|
# unless they are explicitly requested.
|
@@ -8,7 +11,8 @@ module ReactiveRecord
|
|
8
11
|
# During prerendering we get each attribute as its requested and fill it in both on the javascript side, as well as
|
9
12
|
# remember that the attribute needs to be part of the download to client.
|
10
13
|
|
11
|
-
# On the client we fill in the record data with empty values (
|
14
|
+
# On the client we fill in the record data with empty values (the default value for the attribute,
|
15
|
+
# or one element collections) but only as the attribute
|
12
16
|
# is requested. Each request queues up a request to get the real data from the server.
|
13
17
|
|
14
18
|
# The ReactiveRecord class serves two purposes. First it is the unique data corresponding to the last known state of a
|
@@ -37,9 +41,10 @@ module ReactiveRecord
|
|
37
41
|
attr_accessor :updated_during
|
38
42
|
attr_accessor :synced_attributes
|
39
43
|
attr_accessor :virgin
|
44
|
+
attr_reader :attributes
|
40
45
|
|
41
46
|
# While data is being loaded from the server certain internal behaviors need to change
|
42
|
-
# for example
|
47
|
+
# for example all record changes are synced as they happen.
|
43
48
|
# This is implemented this way so that the ServerDataCache class can use pure active
|
44
49
|
# record methods in its implementation
|
45
50
|
|
@@ -62,44 +67,48 @@ module ReactiveRecord
|
|
62
67
|
load_data { ServerDataCache.load_from_json(json, target) }
|
63
68
|
end
|
64
69
|
|
65
|
-
def self.
|
66
|
-
@class_scopes[model.base_class]
|
67
|
-
end
|
68
|
-
|
69
|
-
# def self.sync_blocks
|
70
|
-
# # @sync_blocks[watch_model][sync_model][scope_name][...array of blocks...]
|
71
|
-
# @sync_blocks ||= Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = Hash.new { |hash, key| hash[key] = [] } } }
|
72
|
-
# end
|
73
|
-
|
74
|
-
|
75
|
-
def self.find(model, attribute, value)
|
70
|
+
def self.find(model, attrs)
|
76
71
|
# will return the unique record with this attribute-value pair
|
77
72
|
# value cannot be an association or aggregation
|
78
73
|
|
74
|
+
# add the inheritance column if this is an STI subclass
|
75
|
+
|
76
|
+
inher_col = model.inheritance_column
|
77
|
+
if inher_col && model < model.base_class && !attrs.key?(inher_col)
|
78
|
+
attrs = attrs.merge(inher_col => model.model_name)
|
79
|
+
end
|
80
|
+
|
79
81
|
model = model.base_class
|
80
|
-
|
81
|
-
|
82
|
+
primary_key = model.primary_key
|
83
|
+
|
84
|
+
# already have a record with these attribute-value pairs?
|
85
|
+
|
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
|
94
|
+
|
82
95
|
unless record
|
83
96
|
# if not, and then the record may be loaded, but not have this attribute set yet,
|
84
97
|
# so find the id of of record with the attribute-value pair, and see if that is loaded.
|
85
98
|
# find_in_db returns nil if we are not prerendering which will force us to create a new record
|
86
99
|
# because there is no way of knowing the id.
|
87
|
-
if
|
88
|
-
record = @records[model].detect { |record| record.id == id}
|
100
|
+
if !attrs.key?(primary_key) && (id = find_in_db(model, attrs))
|
101
|
+
record = lookup_by_id(model, id) # @records[model].detect { |record| record.id == id}
|
102
|
+
attrs = attrs.merge primary_key => id
|
89
103
|
end
|
90
104
|
# if we don't have a record then create one
|
91
|
-
(record = new(model)).vector = [model, [:find_by, attribute => value]] unless record
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
record.sync_attribute(model.primary_key, id) if id
|
105
|
+
# (record = new(model)).vector = [model, [:find_by, attribute => value]] unless record
|
106
|
+
record ||= set_vector_lookup(new(model), [model, [:find_by, attrs]])
|
107
|
+
# and set the values
|
108
|
+
attrs.each { |attr, value| record.sync_attribute(attr, value) }
|
96
109
|
end
|
97
110
|
# finally initialize and return the ar_instance
|
98
|
-
record.
|
99
|
-
end
|
100
|
-
|
101
|
-
def self.find_by_object_id(model, object_id)
|
102
|
-
@records[model].detect { |record| record.object_id == object_id }.ar_instance
|
111
|
+
record.set_ar_instance!
|
103
112
|
end
|
104
113
|
|
105
114
|
def self.new_from_vector(model, aggregate_owner, *vector)
|
@@ -110,13 +119,15 @@ module ReactiveRecord
|
|
110
119
|
|
111
120
|
# do we already have a record with this vector? If so return it, otherwise make a new one.
|
112
121
|
|
113
|
-
record = @records[model].detect { |record| record.vector == vector }
|
122
|
+
# record = @records[model].detect { |record| record.vector == vector }
|
123
|
+
record = lookup_by_vector(vector)
|
114
124
|
unless record
|
125
|
+
|
115
126
|
record = new model
|
116
|
-
record
|
127
|
+
set_vector_lookup(record, vector)
|
117
128
|
end
|
118
129
|
|
119
|
-
record.
|
130
|
+
record.set_ar_instance!
|
120
131
|
|
121
132
|
if aggregate_owner
|
122
133
|
record.aggregate_owner = aggregate_owner
|
@@ -125,7 +136,6 @@ module ReactiveRecord
|
|
125
136
|
end
|
126
137
|
|
127
138
|
record.ar_instance
|
128
|
-
|
129
139
|
end
|
130
140
|
|
131
141
|
def initialize(model, hash = {}, ar_instance = nil)
|
@@ -136,6 +146,7 @@ module ReactiveRecord
|
|
136
146
|
@changed_attributes = []
|
137
147
|
@virgin = true
|
138
148
|
records[model] << self
|
149
|
+
Base.set_object_id_lookup(self)
|
139
150
|
end
|
140
151
|
|
141
152
|
def find(*args)
|
@@ -151,13 +162,15 @@ module ReactiveRecord
|
|
151
162
|
end
|
152
163
|
|
153
164
|
def id
|
154
|
-
attributes[primary_key]
|
165
|
+
@attributes[primary_key]
|
155
166
|
end
|
156
167
|
|
157
168
|
def id=(value)
|
158
169
|
# value can be nil if we are loading an aggregate otherwise check if it already exists
|
159
|
-
if !(value
|
160
|
-
|
170
|
+
# if !(value && (existing_record = records[@model].detect { |record| record.attributes[primary_key] == value}))
|
171
|
+
if !(value && (existing_record = Base.lookup_by_id(model, value)))
|
172
|
+
@attributes[primary_key] = value
|
173
|
+
Base.set_id_lookup(self)
|
161
174
|
else
|
162
175
|
@ar_instance.instance_variable_set(:@backing_record, existing_record)
|
163
176
|
existing_record.attributes.merge!(attributes) { |key, v1, v2| v1 }
|
@@ -165,85 +178,6 @@ module ReactiveRecord
|
|
165
178
|
value
|
166
179
|
end
|
167
180
|
|
168
|
-
def attributes
|
169
|
-
@last_access_at = Time.now
|
170
|
-
@attributes
|
171
|
-
end
|
172
|
-
|
173
|
-
def reactive_get!(attribute, reload = nil)
|
174
|
-
@virgin = false unless data_loading?
|
175
|
-
unless @destroyed
|
176
|
-
if @attributes.has_key? attribute
|
177
|
-
attributes[attribute].notify if @attributes[attribute].is_a? DummyValue
|
178
|
-
apply_method(attribute) if reload
|
179
|
-
else
|
180
|
-
apply_method(attribute)
|
181
|
-
end
|
182
|
-
React::State.get_state(self, attribute) unless data_loading?
|
183
|
-
attributes[attribute]
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
def reactive_set!(attribute, value)
|
188
|
-
@virgin = false unless data_loading?
|
189
|
-
return value if @destroyed || dont_update_attribute?(attribute, value)
|
190
|
-
return attributes[attribute] if update_aggregate(attribute, value)
|
191
|
-
value = update_relationships(attribute, value)
|
192
|
-
update_attribute(attribute, value)
|
193
|
-
value
|
194
|
-
end
|
195
|
-
|
196
|
-
def dont_update_attribute?(attribute, value)
|
197
|
-
return false if attributes[attribute].is_a?(DummyValue)
|
198
|
-
return false unless attributes.key?(attribute)
|
199
|
-
return false if attributes[attribute] != value
|
200
|
-
true
|
201
|
-
end
|
202
|
-
|
203
|
-
def update_attribute(attribute, *args)
|
204
|
-
value = args[0]
|
205
|
-
if args.count != 0 and data_loading?
|
206
|
-
if (aggregation = model.reflect_on_aggregation(attribute)) and !(aggregation.klass < ActiveRecord::Base)
|
207
|
-
@synced_attributes[attribute] = aggregation.deserialize(aggregation.serialize(value))
|
208
|
-
else
|
209
|
-
@synced_attributes[attribute] = value
|
210
|
-
end
|
211
|
-
end
|
212
|
-
if @virgin
|
213
|
-
attributes[attribute] = value if args.count != 0
|
214
|
-
return
|
215
|
-
end
|
216
|
-
changed = if args.count == 0
|
217
|
-
if (association = @model.reflect_on_association(attribute)) and association.collection?
|
218
|
-
attributes[attribute] != @synced_attributes[attribute]
|
219
|
-
else
|
220
|
-
!attributes[attribute].backing_record.changed_attributes.empty?
|
221
|
-
end
|
222
|
-
elsif (association = @model.reflect_on_association(attribute)) and association.collection?
|
223
|
-
value != @synced_attributes[attribute]
|
224
|
-
else
|
225
|
-
!@synced_attributes.has_key?(attribute) or @synced_attributes[attribute] != value
|
226
|
-
end
|
227
|
-
empty_before = changed_attributes.empty?
|
228
|
-
if !changed
|
229
|
-
changed_attributes.delete(attribute)
|
230
|
-
elsif !changed_attributes.include?(attribute)
|
231
|
-
changed_attributes << attribute
|
232
|
-
end
|
233
|
-
had_key = attributes.has_key? attribute
|
234
|
-
current_value = attributes[attribute]
|
235
|
-
attributes[attribute] = value if args.count != 0
|
236
|
-
if !data_loading?
|
237
|
-
React::State.set_state(self, attribute, value)
|
238
|
-
elsif on_opal_client? and had_key and current_value.loaded? and current_value != value and args.count > 0 # this is to handle changes in already loaded server side methods
|
239
|
-
React::State.set_state(self, attribute, value, true)
|
240
|
-
end
|
241
|
-
if empty_before != changed_attributes.empty?
|
242
|
-
React::State.set_state(self, "!CHANGED!", !changed_attributes.empty?, true) unless on_opal_server? or data_loading?
|
243
|
-
aggregate_owner.update_attribute(aggregate_attribute) if aggregate_owner
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
181
|
def changed?(*args)
|
248
182
|
if args.count == 0
|
249
183
|
React::State.get_state(self, "!CHANGED!")
|
@@ -254,20 +188,34 @@ module ReactiveRecord
|
|
254
188
|
end
|
255
189
|
end
|
256
190
|
|
191
|
+
def changed_attributes_and_values
|
192
|
+
Hash[changed_attributes.collect do |attr|
|
193
|
+
[attr, @attributes[attr]] if column_type(attr)
|
194
|
+
end.compact]
|
195
|
+
end
|
196
|
+
|
197
|
+
def changes
|
198
|
+
Hash[changed_attributes.collect do |attr|
|
199
|
+
[attr, [@synced_attributes[attr], @attributes[attr]]] if column_type(attr)
|
200
|
+
end.compact]
|
201
|
+
end
|
202
|
+
|
257
203
|
def errors
|
258
|
-
@errors ||= ActiveModel::
|
204
|
+
@errors ||= ActiveModel::Errors.new(self)
|
259
205
|
end
|
260
206
|
|
261
207
|
# called when we have a newly created record, to initialize
|
262
208
|
# any nil collections to empty arrays. We can do this because
|
263
209
|
# if its a brand new record, then any collections that are still
|
264
210
|
# nil must not have any children.
|
211
|
+
|
265
212
|
def initialize_collections
|
266
213
|
if (!vector || vector.empty?) && id && id != ''
|
267
|
-
|
214
|
+
Base.set_vector_lookup(self, [@model, [:find_by, @model.primary_key => id]])
|
268
215
|
end
|
269
|
-
|
270
|
-
|
216
|
+
Base.load_data do
|
217
|
+
@model.reflect_on_all_associations.each do |assoc|
|
218
|
+
next if !assoc.collection? || @attributes[assoc.attribute]
|
271
219
|
ar_instance.send("#{assoc.attribute}=", [])
|
272
220
|
end
|
273
221
|
end
|
@@ -275,14 +223,14 @@ module ReactiveRecord
|
|
275
223
|
|
276
224
|
# sync! now will also initialize any nil collections
|
277
225
|
def sync!(hash = {}) # does NOT notify (see saved! for notification)
|
278
|
-
hash.each do |attr, value|
|
279
|
-
|
280
|
-
end
|
226
|
+
# hash.each do |attr, value|
|
227
|
+
# @attributes[attr] = convert(attr, value)
|
228
|
+
# end
|
281
229
|
@synced_attributes = {}
|
282
|
-
|
230
|
+
hash.each { |attr, value| sync_attribute(attr, convert(attr, value)) }
|
283
231
|
@changed_attributes = []
|
284
232
|
@saving = false
|
285
|
-
|
233
|
+
errors.clear
|
286
234
|
# set the vector and clear collections - this only happens when a new record is saved
|
287
235
|
initialize_collections if (!vector || vector.empty?) && id && id != ''
|
288
236
|
self
|
@@ -305,7 +253,8 @@ module ReactiveRecord
|
|
305
253
|
|
306
254
|
def sync_attribute(attribute, value)
|
307
255
|
|
308
|
-
@synced_attributes[attribute] = attributes[attribute] = value
|
256
|
+
@synced_attributes[attribute] = @attributes[attribute] = value
|
257
|
+
Base.set_id_lookup(self) if attribute == primary_key
|
309
258
|
|
310
259
|
#@synced_attributes[attribute] = value.dup if value.is_a? ReactiveRecord::Collection
|
311
260
|
|
@@ -326,7 +275,7 @@ module ReactiveRecord
|
|
326
275
|
# helper so we can tell if model exists. We need this so we can detect
|
327
276
|
# if a record has local changes that are out of sync.
|
328
277
|
def self.exists?(model, id)
|
329
|
-
|
278
|
+
Base.lookup_by_id(model, id)
|
330
279
|
end
|
331
280
|
|
332
281
|
def revert
|
@@ -335,7 +284,7 @@ module ReactiveRecord
|
|
335
284
|
@attributes.delete(attribute) unless @synced_attributes.key?(attribute)
|
336
285
|
end
|
337
286
|
@changed_attributes = []
|
338
|
-
|
287
|
+
errors.clear
|
339
288
|
end
|
340
289
|
|
341
290
|
def saving!
|
@@ -343,14 +292,20 @@ module ReactiveRecord
|
|
343
292
|
@saving = true
|
344
293
|
end
|
345
294
|
|
346
|
-
def errors!(
|
347
|
-
|
348
|
-
|
295
|
+
def errors!(hash)
|
296
|
+
notify_waiting_for_save
|
297
|
+
errors.clear && return unless hash
|
298
|
+
hash.each do |attribute, messages|
|
299
|
+
messages.each do |message|
|
300
|
+
errors.add(attribute, message: message)
|
301
|
+
end
|
302
|
+
end
|
349
303
|
end
|
350
304
|
|
351
|
-
def saved!
|
352
|
-
|
353
|
-
|
305
|
+
def saved!(save_only = nil) # sets saving to false AND notifies
|
306
|
+
notify_waiting_for_save
|
307
|
+
return self if save_only
|
308
|
+
if errors.empty?
|
354
309
|
React::State.set_state(self, self, :saved)
|
355
310
|
elsif !data_loading?
|
356
311
|
React::State.set_state(self, self, :error)
|
@@ -358,6 +313,26 @@ module ReactiveRecord
|
|
358
313
|
self
|
359
314
|
end
|
360
315
|
|
316
|
+
def self.when_not_saving(model, &block)
|
317
|
+
if @records[model].detect(&:saving?)
|
318
|
+
wait_for_save(model, &block)
|
319
|
+
else
|
320
|
+
yield model
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def notify_waiting_for_save
|
325
|
+
@saving = false
|
326
|
+
self.class.notify_waiting_for_save(model)
|
327
|
+
end
|
328
|
+
|
329
|
+
def self.notify_waiting_for_save(model)
|
330
|
+
waiters = waiting_for_save(model)
|
331
|
+
return if waiters.empty? || @records[model].detect(&:saving?)
|
332
|
+
waiters.each { |waiter| waiter.call model }
|
333
|
+
clear_waiting_for_save(model)
|
334
|
+
end
|
335
|
+
|
361
336
|
def saving?
|
362
337
|
React::State.get_state(self, self)
|
363
338
|
@saving
|
@@ -367,88 +342,26 @@ module ReactiveRecord
|
|
367
342
|
!id && !vector
|
368
343
|
end
|
369
344
|
|
370
|
-
def
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
else
|
375
|
-
new_from_vector(association.klass, nil, *vector, association.attribute)
|
376
|
-
end
|
377
|
-
instance_backing_record_attributes = instance.backing_record.attributes
|
378
|
-
inverse_association = association.klass.reflect_on_association(inverse_of)
|
379
|
-
if inverse_association.collection?
|
380
|
-
instance_backing_record_attributes[inverse_of] = if id and id != ""
|
381
|
-
Collection.new(@model, instance, inverse_association, association.klass, ["find", id], inverse_of)
|
382
|
-
else
|
383
|
-
Collection.new(@model, instance, inverse_association, *vector, association.attribute, inverse_of)
|
384
|
-
end unless instance_backing_record_attributes[inverse_of]
|
385
|
-
instance_backing_record_attributes[inverse_of].replace [@ar_instance]
|
386
|
-
else
|
387
|
-
instance_backing_record_attributes[inverse_of] = @ar_instance
|
388
|
-
end unless association.through_association? || instance_backing_record_attributes.key?(inverse_of)
|
389
|
-
instance
|
390
|
-
end
|
391
|
-
|
392
|
-
def apply_method(method)
|
393
|
-
# Fills in the value returned by sending "method" to the corresponding server side db instance
|
394
|
-
if on_opal_server? and changed?
|
395
|
-
log("Warning fetching virtual attributes (#{model.name}.#{method}) during prerendering on a changed or new model is not implemented.", :warning)
|
396
|
-
# to implement this we would have to sync up any changes during prererendering with a set the cached models (see server_data_cache)
|
397
|
-
# right now server_data cache is read only, BUT we could change this. However it seems like a tails case. Why would we create or update
|
398
|
-
# a model during prerendering???
|
399
|
-
end
|
400
|
-
if !new?
|
401
|
-
new_value = if association = @model.reflect_on_association(method)
|
402
|
-
if association.collection?
|
403
|
-
Collection.new(association.klass, @ar_instance, association, *vector, method)
|
404
|
-
else
|
405
|
-
find_association(association, (id and id != "" and self.class.fetch_from_db([@model, [:find, id], method, @model.primary_key])))
|
406
|
-
end
|
407
|
-
elsif aggregation = @model.reflect_on_aggregation(method) and (aggregation.klass < ActiveRecord::Base)
|
408
|
-
new_from_vector(aggregation.klass, self, *vector, method)
|
409
|
-
elsif id and id != ''
|
410
|
-
self.class.fetch_from_db([@model, [:find, id], *method]) || self.class.load_from_db(self, *(vector ? vector : [nil]), method)
|
411
|
-
else # its a attribute in an aggregate or we are on the client and don't know the id
|
412
|
-
self.class.fetch_from_db([*vector, *method]) || self.class.load_from_db(self, *(vector ? vector : [nil]), method)
|
413
|
-
end
|
414
|
-
new_value = @attributes[method] if new_value.is_a? DummyValue and @attributes.has_key?(method)
|
415
|
-
sync_attribute(method, new_value)
|
416
|
-
elsif association = @model.reflect_on_association(method) and association.collection?
|
417
|
-
@attributes[method] = Collection.new(association.klass, @ar_instance, association)
|
418
|
-
elsif aggregation = @model.reflect_on_aggregation(method) and (aggregation.klass < ActiveRecord::Base)
|
419
|
-
@attributes[method] = aggregation.klass.new.tap do |aggregate|
|
420
|
-
backing_record = aggregate.backing_record
|
421
|
-
backing_record.aggregate_owner = self
|
422
|
-
backing_record.aggregate_attribute = method
|
423
|
-
end
|
424
|
-
elsif !aggregation and method != model.primary_key
|
425
|
-
if model.columns_hash[method]
|
426
|
-
new_value = convert(method, model.columns_hash[method][:default])
|
427
|
-
else
|
428
|
-
unless @attributes.key?(method)
|
429
|
-
log("Warning: reading from new #{model.name}.#{method} before assignment. Will fetch value from server. This may not be what you expected!!", :warning)
|
430
|
-
end
|
431
|
-
new_value = self.class.load_from_db(self, *(vector ? vector : [nil]), method)
|
432
|
-
new_value = @attributes[method] if new_value.is_a?(DummyValue) && @attributes.key?(method)
|
433
|
-
end
|
434
|
-
sync_attribute(method, new_value)
|
435
|
-
end
|
436
|
-
end
|
437
|
-
|
438
|
-
def self.infer_type_from_hash(klass, hash)
|
439
|
-
klass = klass.base_class
|
440
|
-
return klass unless hash
|
441
|
-
type = hash[klass.inheritance_column]
|
442
|
-
begin
|
443
|
-
return Object.const_get(type)
|
444
|
-
rescue Exception => e
|
445
|
-
message = "Could not subclass #{@model_klass.model_name} as #{type}. Perhaps #{type} class has not been required. Exception: #{e}"
|
446
|
-
`console.error(#{message})`
|
447
|
-
end if type
|
448
|
-
klass
|
345
|
+
def set_ar_instance!
|
346
|
+
klass = self.class.infer_type_from_hash(model, @attributes)
|
347
|
+
@ar_instance = klass._new_without_sti_type_cast(self) unless @ar_instance.class == klass
|
348
|
+
@ar_instance
|
449
349
|
end
|
450
350
|
|
451
351
|
class << self
|
352
|
+
def infer_type_from_hash(klass, hash)
|
353
|
+
klass = klass.base_class
|
354
|
+
return klass unless hash
|
355
|
+
type = hash[klass.inheritance_column]
|
356
|
+
begin
|
357
|
+
return Object.const_get(type)
|
358
|
+
rescue Exception => e
|
359
|
+
message = "Could not subclass #{klass} as #{type}. Perhaps #{type} class has not been required. Exception: #{e}"
|
360
|
+
`console.error(#{message})`
|
361
|
+
end unless !type || type == ''
|
362
|
+
klass
|
363
|
+
end
|
364
|
+
|
452
365
|
attr_reader :outer_scopes
|
453
366
|
|
454
367
|
def default_scope
|
@@ -462,36 +375,19 @@ module ReactiveRecord
|
|
462
375
|
def add_to_outer_scopes(item)
|
463
376
|
@outer_scopes << item
|
464
377
|
end
|
465
|
-
end
|
466
378
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
poller = every(0.1) do
|
472
|
-
unless @records[model].detect(&:saving?)
|
473
|
-
poller.stop
|
474
|
-
yield model
|
475
|
-
end
|
476
|
-
end
|
477
|
-
else
|
478
|
-
yield model
|
479
|
-
end
|
480
|
-
end
|
481
|
-
|
482
|
-
# While evaluating scopes we want to catch any requests
|
483
|
-
# to the server. Once we catch any requests to the server
|
484
|
-
# then all the further scopes in that chain will be made
|
485
|
-
# at the server.
|
379
|
+
# While evaluating scopes we want to catch any requests
|
380
|
+
# to the server. Once we catch any requests to the server
|
381
|
+
# then all the further scopes in that chain will be made
|
382
|
+
# at the server.
|
486
383
|
|
487
|
-
class << self
|
488
384
|
class DbRequestMade < RuntimeError; end
|
489
385
|
|
490
386
|
def catch_db_requests(return_val = nil)
|
491
387
|
@catch_db_requests = true
|
492
388
|
yield
|
493
389
|
rescue DbRequestMade => e
|
494
|
-
|
390
|
+
React::IsomorphicHelpers.log "Warning: request for server side data during scope evaluation: #{e.message}", :warning
|
495
391
|
return_val
|
496
392
|
ensure
|
497
393
|
@catch_db_requests = false
|
@@ -509,13 +405,12 @@ module ReactiveRecord
|
|
509
405
|
@destroyed = false
|
510
406
|
model.reflect_on_all_associations.each do |association|
|
511
407
|
if association.collection?
|
512
|
-
attributes[association.attribute].replace([]) if attributes[association.attribute]
|
408
|
+
@attributes[association.attribute].replace([]) if @attributes[association.attribute]
|
513
409
|
else
|
514
410
|
@ar_instance.send("#{association.attribute}=", nil)
|
515
411
|
end
|
516
412
|
end
|
517
413
|
@destroyed = true
|
518
414
|
end
|
519
|
-
|
520
415
|
end
|
521
416
|
end
|