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
@@ -2,28 +2,39 @@ module ReactiveRecord
|
|
2
2
|
class Broadcast
|
3
3
|
|
4
4
|
def self.after_commit(operation, model)
|
5
|
+
# Calling public_columns_hash once insures all policies are loaded
|
6
|
+
# before the first broadcast.
|
7
|
+
@public_columns_hash ||= ActiveRecord::Base.public_columns_hash
|
5
8
|
Hyperstack::InternalPolicy.regulate_broadcast(model) do |data|
|
9
|
+
puts "Broadcast aftercommit hook: #{data}" if Hyperstack::Connection.show_diagnostics
|
10
|
+
|
6
11
|
if !Hyperstack.on_server? && Hyperstack::Connection.root_path
|
7
|
-
send_to_server(operation, data) rescue nil # server no longer running so ignore
|
12
|
+
send_to_server(operation, data, model.__synchromesh_update_time) rescue nil # fails if server no longer running so ignore
|
8
13
|
else
|
9
|
-
SendPacket.run(data, operation: operation)
|
14
|
+
SendPacket.run(data, operation: operation, updated_at: model.__synchromesh_update_time)
|
10
15
|
end
|
11
16
|
end
|
12
17
|
rescue ActiveRecord::StatementInvalid => e
|
13
18
|
raise e unless e.message == "Could not find table 'hyperstack_connections'"
|
14
19
|
end unless RUBY_ENGINE == 'opal'
|
15
20
|
|
16
|
-
def self.send_to_server(operation, data)
|
21
|
+
def self.send_to_server(operation, data, updated_at)
|
17
22
|
salt = SecureRandom.hex
|
18
23
|
authorization = Hyperstack.authorization(salt, data[:channel], data[:broadcast_id])
|
19
24
|
raise 'no server running' unless Hyperstack::Connection.root_path
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
Timeout::timeout(Hyperstack.send_to_server_timeout) do
|
26
|
+
SendPacket.remote(
|
27
|
+
Hyperstack::Connection.root_path,
|
28
|
+
data,
|
29
|
+
operation: operation,
|
30
|
+
updated_at: updated_at,
|
31
|
+
salt: salt,
|
32
|
+
authorization: authorization
|
33
|
+
).tap { |p| raise p.error if p.rejected? }
|
34
|
+
end
|
35
|
+
rescue Timeout::Error
|
36
|
+
puts "\n********* FAILED TO RECEIVE RESPONSE FROM SERVER WITHIN #{Hyperstack.send_to_server_timeout} SECONDS. CHANGES WILL NOT BE SYNCED ************\n"
|
37
|
+
raise 'no server running'
|
27
38
|
end unless RUBY_ENGINE == 'opal'
|
28
39
|
|
29
40
|
class SendPacket < Hyperstack::ServerOp
|
@@ -37,6 +48,7 @@ module ReactiveRecord
|
|
37
48
|
param :record
|
38
49
|
param :operation
|
39
50
|
param :previous_changes
|
51
|
+
param :updated_at
|
40
52
|
|
41
53
|
unless RUBY_ENGINE == 'opal'
|
42
54
|
validate do
|
@@ -60,11 +72,11 @@ module ReactiveRecord
|
|
60
72
|
ReactiveRecord::Collection.sync_scopes broadcast.process_previous_changes
|
61
73
|
end
|
62
74
|
end
|
63
|
-
end
|
75
|
+
end if RUBY_ENGINE == 'opal'
|
64
76
|
|
65
77
|
def self.to_self(record, data = {})
|
66
78
|
# simulate incoming packet after a local save
|
67
|
-
operation = if record.
|
79
|
+
operation = if record.new_record?
|
68
80
|
:create
|
69
81
|
elsif record.destroyed?
|
70
82
|
:destroy
|
@@ -78,7 +90,7 @@ module ReactiveRecord
|
|
78
90
|
|
79
91
|
def record_with_current_values
|
80
92
|
ReactiveRecord::Base.load_data do
|
81
|
-
backing_record = @backing_record || klass.find(record[
|
93
|
+
backing_record = @backing_record || klass.find(record[klass.primary_key]).backing_record
|
82
94
|
if destroyed?
|
83
95
|
backing_record.ar_instance
|
84
96
|
else
|
@@ -105,6 +117,10 @@ module ReactiveRecord
|
|
105
117
|
@destroyed
|
106
118
|
end
|
107
119
|
|
120
|
+
def local?
|
121
|
+
@is_local
|
122
|
+
end
|
123
|
+
|
108
124
|
def klass
|
109
125
|
Object.const_get(@klass)
|
110
126
|
end
|
@@ -116,6 +132,7 @@ module ReactiveRecord
|
|
116
132
|
# private
|
117
133
|
|
118
134
|
attr_reader :record
|
135
|
+
attr_reader :updated_at
|
119
136
|
|
120
137
|
def self.open_channels
|
121
138
|
@open_channels ||= Set.new
|
@@ -125,7 +142,7 @@ module ReactiveRecord
|
|
125
142
|
@in_transit ||= Hash.new { |h, k| h[k] = new(k) }
|
126
143
|
end
|
127
144
|
|
128
|
-
def initialize(id)
|
145
|
+
def initialize(id = nil)
|
129
146
|
@id = id
|
130
147
|
@received = Set.new
|
131
148
|
@record = {}
|
@@ -134,19 +151,15 @@ module ReactiveRecord
|
|
134
151
|
|
135
152
|
def local(operation, record, data)
|
136
153
|
@destroyed = operation == :destroy
|
154
|
+
@is_local = true
|
137
155
|
@is_new = operation == :create
|
138
156
|
@klass = record.class.name
|
139
157
|
@record = data
|
140
158
|
record.backing_record.destroyed = false
|
141
|
-
@record[
|
159
|
+
@record[record.primary_key] = record.id if record.id
|
142
160
|
record.backing_record.destroyed = @destroyed
|
143
161
|
@backing_record = record.backing_record
|
144
162
|
@previous_changes = record.changes
|
145
|
-
# attributes = record.attributes
|
146
|
-
# data.each do |k, v|
|
147
|
-
# next if klass.reflect_on_association(k) || attributes[k] == v
|
148
|
-
# @previous_changes[k] = [attributes[k], v]
|
149
|
-
# end
|
150
163
|
self
|
151
164
|
end
|
152
165
|
|
@@ -157,10 +170,35 @@ module ReactiveRecord
|
|
157
170
|
@klass ||= params.klass
|
158
171
|
@record.merge! params.record
|
159
172
|
@previous_changes.merge! params.previous_changes
|
173
|
+
@updated_at = params.updated_at
|
160
174
|
ReactiveRecord::Base.when_not_saving(klass) do
|
161
|
-
@backing_record = ReactiveRecord::Base.exists?(klass, params.record[
|
162
|
-
|
163
|
-
|
175
|
+
@backing_record = ReactiveRecord::Base.exists?(klass, params.record[klass.primary_key])
|
176
|
+
|
177
|
+
# first check to see if we already destroyed it and if so exit the block
|
178
|
+
break if @backing_record&.destroyed
|
179
|
+
|
180
|
+
# We ignore whether the record is being created or not, and just check and see if in our
|
181
|
+
# local copy we have ever loaded this id before. If we have then its not new to us.
|
182
|
+
# BUT if we are destroying a record then it can't be treated as new regardless.
|
183
|
+
# This is because we might be just doing a count on a scope and so no actual records will
|
184
|
+
# exist. Treating a destroyed record as "new" would cause us to first increment the
|
185
|
+
# scope counter and then decrement for the destroy, resulting in a nop instead of a -1 on
|
186
|
+
# the scope count.
|
187
|
+
@is_new = !@backing_record&.id_loaded? && !@destroyed
|
188
|
+
|
189
|
+
# it is possible that we are recieving data on a record for which we are also waiting
|
190
|
+
# on an an inital data load in which case we have not yet set the loaded id, so we
|
191
|
+
# set if now.
|
192
|
+
@backing_record&.loaded_id = params.record[klass.primary_key]
|
193
|
+
|
194
|
+
# once we have received all the data from all the channels (applies to create and update only)
|
195
|
+
# we yield and process the record
|
196
|
+
|
197
|
+
# pusher fake can send duplicate records which will result in a nil broadcast
|
198
|
+
# so we also check that before yielding
|
199
|
+
if @channels == @received && (broadcast = complete!)
|
200
|
+
yield broadcast
|
201
|
+
end
|
164
202
|
end
|
165
203
|
end
|
166
204
|
|
@@ -205,7 +243,7 @@ module ReactiveRecord
|
|
205
243
|
|
206
244
|
def merge_current_values(br)
|
207
245
|
current_values = Hash[*@previous_changes.collect do |attr, values|
|
208
|
-
value = attr ==
|
246
|
+
value = attr == klass.primary_key ? record[klass.primary_key] : values.first
|
209
247
|
if br.attributes.key?(attr) &&
|
210
248
|
br.attributes[attr] != br.convert(attr, value) &&
|
211
249
|
br.attributes[attr] != br.convert(attr, values.last)
|
@@ -216,7 +254,6 @@ module ReactiveRecord
|
|
216
254
|
end
|
217
255
|
[attr, value]
|
218
256
|
end.compact.flatten(1)]
|
219
|
-
# TODO: verify - it used to be current_values.merge(br.attributes)
|
220
257
|
klass._react_param_conversion(br.attributes.merge(current_values))
|
221
258
|
end
|
222
259
|
end
|
@@ -168,12 +168,12 @@ end
|
|
168
168
|
module Kernel
|
169
169
|
# (see Browser::Window#after)
|
170
170
|
def after(time, &block)
|
171
|
-
|
171
|
+
$window.after(time, &block)
|
172
172
|
end
|
173
173
|
|
174
174
|
# (see Browser::Window#after!)
|
175
175
|
def after!(time, &block)
|
176
|
-
|
176
|
+
$window.after!(time, &block)
|
177
177
|
end
|
178
178
|
end
|
179
179
|
|
@@ -187,4 +187,4 @@ class Proc
|
|
187
187
|
def after!(time)
|
188
188
|
$window.after!(time, &self)
|
189
189
|
end
|
190
|
-
end
|
190
|
+
end
|
@@ -90,7 +90,7 @@ class ActiveRecord::Base
|
|
90
90
|
def belongs_to(attr_name, *args)
|
91
91
|
belongs_to_without_reactive_record_add_is_method(attr_name, *args).tap do
|
92
92
|
define_method "#{attr_name}_is?".to_sym do |model|
|
93
|
-
self.class.reflections[attr_name].foreign_key == model.id
|
93
|
+
attributes[self.class.reflections[attr_name.to_s].foreign_key] == model.id
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|
@@ -103,7 +103,19 @@ class ActiveRecord::Base
|
|
103
103
|
self.acting_user = old
|
104
104
|
self
|
105
105
|
else
|
106
|
-
|
106
|
+
acting_user_string =
|
107
|
+
if acting_user
|
108
|
+
id = user.respond_to?(:id) ? user.id : user
|
109
|
+
"not allowed for acting_user: <##{user.class} id: #{user}>"
|
110
|
+
else
|
111
|
+
"not allowed without acting_user (acting_user = nil)"
|
112
|
+
end
|
113
|
+
|
114
|
+
message = "CRUD access violation: <##{self.class} id: #{self.id}> - #{permission}(#{args}) #{acting_user_string}"
|
115
|
+
if permission == :view_permitted?
|
116
|
+
details = Hyperstack::PolicyDiagnostics.policy_dump_for(self, user)
|
117
|
+
end
|
118
|
+
Hyperstack::InternalPolicy.raise_operation_access_violation(message, details || '')
|
107
119
|
end
|
108
120
|
end
|
109
121
|
|
@@ -93,8 +93,9 @@ module ReactiveRecord
|
|
93
93
|
vector = []
|
94
94
|
path.split('.').inject(@model) do |model, attribute|
|
95
95
|
association = model.reflect_on_association(attribute)
|
96
|
-
|
97
|
-
|
96
|
+
inverse_of = association.inverse_of if association
|
97
|
+
raise build_error(path, model, attribute) unless inverse_of
|
98
|
+
vector = [inverse_of, *vector]
|
98
99
|
@joins[association.klass] << vector
|
99
100
|
association.klass
|
100
101
|
end
|
@@ -229,6 +229,26 @@ module ReactiveRecord
|
|
229
229
|
@db_cache.add_item_to_cache self
|
230
230
|
end
|
231
231
|
|
232
|
+
def to_s
|
233
|
+
acting_user_string =
|
234
|
+
if acting_user
|
235
|
+
" - with acting user: <#{acting_user.class.name} id: #{acting_user.id}>"
|
236
|
+
else
|
237
|
+
' - with no acting user'
|
238
|
+
end
|
239
|
+
vector.collect do |e|
|
240
|
+
if e.is_a? String
|
241
|
+
e
|
242
|
+
elsif e.is_a? Array
|
243
|
+
e.length > 1 ? "#{e.first}(#{e[1..-1].join(', ')})" : e.first
|
244
|
+
else
|
245
|
+
e.name
|
246
|
+
end
|
247
|
+
end.join('.') + acting_user_string
|
248
|
+
rescue
|
249
|
+
vector.to_s + acting_user_string
|
250
|
+
end
|
251
|
+
|
232
252
|
def start_timing(&block)
|
233
253
|
ServerDataCache.class.start_timing(&block)
|
234
254
|
end
|
@@ -240,45 +260,54 @@ module ReactiveRecord
|
|
240
260
|
def apply_method_to_cache(method)
|
241
261
|
@db_cache.cache.inject(nil) do |representative, cache_item|
|
242
262
|
if cache_item.vector == vector
|
243
|
-
|
244
|
-
#
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
+
begin
|
264
|
+
# error_recovery_method holds the current method that we are attempting to apply
|
265
|
+
# in case we throw an exception, and need to give the developer a meaningful message.
|
266
|
+
if method == "*"
|
267
|
+
# apply_star does the security check if value is present
|
268
|
+
cache_item.apply_star || representative
|
269
|
+
elsif method == "*all"
|
270
|
+
# if we secure the collection then we assume its okay to read the ids
|
271
|
+
error_recovery_method = [:all]
|
272
|
+
secured_value = cache_item.value.__secure_collection_check(cache_item)
|
273
|
+
cache_item.build_new_cache_item(timing(:active_record) { secured_value.collect { |record| record.id } }, method, method)
|
274
|
+
elsif method == "*count"
|
275
|
+
error_recovery_method = [:count]
|
276
|
+
secured_value = cache_item.value.__secure_collection_check(cache_item)
|
277
|
+
cache_item.build_new_cache_item(timing(:active_record) { cache_item.value.__secure_collection_check(cache_item).count }, method, method)
|
278
|
+
elsif preloaded_value = @preloaded_records[cache_item.absolute_vector + [method]]
|
279
|
+
# no security check needed since we already evaluated this
|
280
|
+
cache_item.build_new_cache_item(preloaded_value, method, method)
|
281
|
+
elsif aggregation = cache_item.aggregation?(method)
|
282
|
+
# aggregations are not protected
|
283
|
+
error_recovery_method = [method, :mapping, :all]
|
284
|
+
cache_item.build_new_cache_item(aggregation.mapping.collect { |attribute, accessor| cache_item.value[attribute] }, method, method)
|
263
285
|
else
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
286
|
+
if !cache_item.value || cache_item.value.is_a?(Array)
|
287
|
+
# seeing as we just returning representative, no check is needed (its already checked)
|
288
|
+
representative
|
289
|
+
elsif method == 'model_name'
|
290
|
+
error_recovery_method = [:model_name]
|
291
|
+
cache_item.build_new_cache_item(timing(:active_record) { cache_item.value.model_name }, method, method)
|
292
|
+
else
|
293
|
+
begin
|
294
|
+
secured_method = "__secure_remote_access_to_#{[*method].first}"
|
295
|
+
error_recovery_method = [*method]
|
296
|
+
# order is important. This check must be first since scopes can have same name as attributes!
|
297
|
+
if cache_item.value.respond_to? secured_method
|
298
|
+
cache_item.build_new_cache_item(timing(:active_record) { cache_item.value.send(secured_method, cache_item.value, @acting_user, *([*method][1..-1])) }, method, method)
|
299
|
+
elsif (cache_item.value.class < ActiveRecord::Base) && cache_item.value.attributes.has_key?(method) # TODO: second check is not needed, its built into check_permmissions, check should be does class respond to check_permissions...
|
300
|
+
cache_item.value.check_permission_with_acting_user(@acting_user, :view_permitted?, method)
|
301
|
+
cache_item.build_new_cache_item(timing(:active_record) { cache_item.value.send(*method) }, method, method)
|
302
|
+
else
|
303
|
+
raise "Method missing while fetching data: \`#{[*method].first}\` "\
|
304
|
+
'was expected to be an attribute or a method defined using the server_method of finder_method macros.'
|
305
|
+
end
|
275
306
|
end
|
276
|
-
rescue Exception => e # this check may no longer be needed as we are quite explicit now on which methods we apply
|
277
|
-
# ReactiveRecord::Pry::rescued(e)
|
278
|
-
::Rails.logger.debug "\033[0;31;1mERROR: HyperModel exception caught when applying #{method} to db object #{cache_item.value}: #{e}\033[0;30;21m"
|
279
|
-
raise e, "HyperModel fetching records failed, exception caught when applying #{method} to db object #{cache_item.value}: #{e}", e.backtrace
|
280
307
|
end
|
281
308
|
end
|
309
|
+
rescue StandardError => e
|
310
|
+
raise e.class, form_error_message(e, cache_item.vector + error_recovery_method), e.backtrace
|
282
311
|
end
|
283
312
|
else
|
284
313
|
representative
|
@@ -286,6 +315,15 @@ module ReactiveRecord
|
|
286
315
|
end
|
287
316
|
end
|
288
317
|
|
318
|
+
def form_error_message(original_error, vector)
|
319
|
+
expression = vector.collect do |exp|
|
320
|
+
next exp unless exp.is_a? Array
|
321
|
+
next exp.first if exp.length == 1
|
322
|
+
"#{exp.first}(#{exp[1..-1].join(', ')})"
|
323
|
+
end.join('.')
|
324
|
+
"raised when evaluating #{expression}\n#{original_error}"
|
325
|
+
end
|
326
|
+
|
289
327
|
def aggregation?(method)
|
290
328
|
if method.is_a?(String) && @value.class.respond_to?(:reflect_on_aggregation)
|
291
329
|
aggregation = @value.class.reflect_on_aggregation(method.to_sym)
|
@@ -296,7 +334,7 @@ module ReactiveRecord
|
|
296
334
|
end
|
297
335
|
|
298
336
|
def apply_star
|
299
|
-
if @value && @value.__secure_collection_check(
|
337
|
+
if @value && @value.__secure_collection_check(self) && @value.length > 0
|
300
338
|
i = -1
|
301
339
|
@value.inject(nil) do |representative, current_value|
|
302
340
|
i += 1
|
@@ -358,6 +396,8 @@ module ReactiveRecord
|
|
358
396
|
if method == "*"
|
359
397
|
if @value.is_a? Array # this happens when a scope is empty there is test case, but
|
360
398
|
@parent.as_hash({}) # does it work for all edge cases?
|
399
|
+
elsif (@value.class < ActiveRecord::Base) && children.is_a?(Hash)
|
400
|
+
@parent.as_hash({@value.id => merge_inheritance_column(children)})
|
361
401
|
else
|
362
402
|
@parent.as_hash({@value.id => children})
|
363
403
|
end
|
@@ -411,17 +451,30 @@ keys:
|
|
411
451
|
if value is a hash
|
412
452
|
=end
|
413
453
|
|
414
|
-
|
415
454
|
def self.load_from_json(tree, target = nil)
|
416
|
-
|
417
455
|
# have to process *all before any other items
|
418
456
|
# we leave the "*all" key in just for debugging purposes, and then skip it below
|
419
457
|
|
420
458
|
if sorted_collection = tree["*all"]
|
421
|
-
|
459
|
+
loaded_collection = sorted_collection.collect do |id|
|
460
|
+
ReactiveRecord::Base.find_by_id(target.proxy_association.klass, id)
|
461
|
+
end
|
462
|
+
if loaded_collection[0] && target.scope_description&.name == '___hyperstack_internal_scoped_find_by'
|
463
|
+
primary_key = target.proxy_association.klass.primary_key
|
464
|
+
attrs = target.vector[-1][1].reject { |key, _| key == primary_key }
|
465
|
+
loaded_collection[0].backing_record.sync_attributes(attrs)
|
466
|
+
end
|
467
|
+
target.replace loaded_collection
|
468
|
+
# we need to notify any observers of the collection. collection#replace
|
469
|
+
# will not notify if we are data_loading (which we are) so we will do it
|
470
|
+
# here. BUT we want the notification to occur after the current event
|
471
|
+
# completes so we wrap it a bulk_update
|
472
|
+
Hyperstack::Internal::State::Mapper.bulk_update do
|
473
|
+
Hyperstack::Internal::State::Variable.set(target, :collection, target.collection)
|
474
|
+
end
|
422
475
|
end
|
423
476
|
|
424
|
-
if id_value = tree[
|
477
|
+
if (id_value = tree[target.class.try(:primary_key)] || tree[:id]) && id_value.is_a?(Array)
|
425
478
|
target.id = id_value.first
|
426
479
|
end
|
427
480
|
tree.each do |method, value|
|
@@ -433,17 +486,16 @@ keys:
|
|
433
486
|
elsif !target
|
434
487
|
load_from_json(value, Object.const_get(method))
|
435
488
|
elsif method == "*count"
|
436
|
-
target.
|
489
|
+
target.count_state = value.first
|
437
490
|
elsif method.is_a? Integer or method =~ /^[0-9]+$/
|
438
491
|
new_target = target.push_and_update_belongs_to(method)
|
439
|
-
#target << (new_target = target.proxy_association.klass.find(method))
|
440
492
|
elsif method.is_a? Array
|
441
493
|
if method[0] == "new"
|
442
494
|
new_target = ReactiveRecord::Base.lookup_by_object_id(method[1])
|
443
495
|
elsif !(target.class < ActiveRecord::Base)
|
444
496
|
new_target = target.send(*method)
|
445
497
|
# value is an array if scope returns nil, so we destroy the bogus record
|
446
|
-
new_target.destroy
|
498
|
+
new_target.destroy && (new_target = nil) if value.is_a? Array
|
447
499
|
else
|
448
500
|
target.backing_record.update_simple_attribute([method], target.backing_record.convert(method, value.first))
|
449
501
|
end
|
@@ -454,12 +506,11 @@ keys:
|
|
454
506
|
|
455
507
|
target.send "#{method}=", value.first
|
456
508
|
elsif value.is_a? Array
|
457
|
-
|
458
|
-
|
459
|
-
target.backing_record.set_attr_value(method, value.first) unless method == :id
|
460
|
-
elsif value.is_a? Hash and value[:id] and value[:id].first and association = target.class.reflect_on_association(method)
|
509
|
+
target.send("_hyperstack_internal_setter_#{method}", value.first) unless method == target.class.primary_key
|
510
|
+
elsif value.is_a?(Hash) && value[:id] && value[:id].first && (association = target.class.reflect_on_association(method))
|
461
511
|
# not sure if its necessary to check the id above... is it possible to for the method to be an association but not have an id?
|
462
|
-
|
512
|
+
klass = value[:model_name] ? Object.const_get(value[:model_name].first) : association.klass
|
513
|
+
new_target = ReactiveRecord::Base.find_by_id(klass, value[:id].first)
|
463
514
|
target.send "#{method}=", new_target
|
464
515
|
elsif !(target.class < ActiveRecord::Base)
|
465
516
|
new_target = target.send(*method)
|
@@ -471,7 +522,6 @@ keys:
|
|
471
522
|
load_from_json(value, new_target) if new_target
|
472
523
|
end
|
473
524
|
rescue Exception => e
|
474
|
-
# debugger
|
475
525
|
raise e
|
476
526
|
end
|
477
527
|
end
|