hyper-model 1.0.alpha1.2 → 1.0.alpha1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.rspec +0 -1
- data/Gemfile +6 -5
- data/Rakefile +27 -3
- data/hyper-model.gemspec +11 -19
- data/lib/active_record_base.rb +105 -33
- data/lib/enumerable/pluck.rb +3 -2
- data/lib/hyper-model.rb +4 -1
- 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 +32 -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 +8 -4
- data/lib/reactive_record/active_record/instance_methods.rb +73 -5
- data/lib/reactive_record/active_record/public_columns_hash.rb +25 -26
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +22 -5
- data/lib/reactive_record/active_record/reactive_record/base.rb +50 -24
- data/lib/reactive_record/active_record/reactive_record/collection.rb +226 -68
- 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 +81 -51
- 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 +105 -68
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +249 -32
- 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 +54 -153
- data/Gemfile.lock +0 -421
@@ -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 [target.class.primary_key, :id].include? method
|
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
|