hyper-mesh 1.0.0.lap27 → 1.0.0.lap28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +6 -2
  4. data/Gemfile +0 -1
  5. data/Rakefile +2 -2
  6. data/hyper-mesh.gemspec +1 -1
  7. data/lib/active_record_base.rb +39 -27
  8. data/lib/hyper-mesh.rb +6 -1
  9. data/lib/hypermesh/version.rb +1 -1
  10. data/lib/object/tap.rb +7 -0
  11. data/lib/reactive_record/active_record/associations.rb +14 -3
  12. data/lib/reactive_record/active_record/base.rb +1 -2
  13. data/lib/reactive_record/active_record/class_methods.rb +120 -67
  14. data/lib/reactive_record/active_record/error.rb +17 -12
  15. data/lib/reactive_record/active_record/errors.rb +374 -0
  16. data/lib/reactive_record/active_record/instance_methods.rb +58 -67
  17. data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +1 -4
  18. data/lib/reactive_record/active_record/reactive_record/base.rb +129 -234
  19. data/lib/reactive_record/active_record/reactive_record/collection.rb +51 -18
  20. data/lib/reactive_record/active_record/reactive_record/column_types.rb +5 -3
  21. data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +6 -4
  22. data/lib/reactive_record/active_record/reactive_record/getters.rb +133 -0
  23. data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +99 -87
  24. data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +54 -0
  25. data/lib/reactive_record/active_record/reactive_record/operations.rb +2 -1
  26. data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +1 -1
  27. data/lib/reactive_record/active_record/reactive_record/setters.rb +194 -0
  28. data/lib/reactive_record/active_record/reactive_record/while_loading.rb +4 -5
  29. data/lib/reactive_record/active_record_error.rb +4 -0
  30. data/lib/reactive_record/broadcast.rb +55 -18
  31. data/lib/reactive_record/permissions.rb +5 -4
  32. data/lib/reactive_record/scope_description.rb +14 -6
  33. data/lib/reactive_record/server_data_cache.rb +119 -70
  34. metadata +16 -13
  35. data/lib/reactive_record/active_record/reactive_record/reactive_set_relationship_helpers.rb +0 -189
@@ -16,7 +16,6 @@ module ReactiveRecord
16
16
  @unsaved_children ||= Set.new
17
17
  unless @uc_already_being_called
18
18
  @uc_already_being_called = true
19
- #@owner.backing_record.update_attribute(@association.attribute)
20
19
  end
21
20
  else
22
21
  @unsaved_children ||= DummySet.new
@@ -72,7 +71,7 @@ module ReactiveRecord
72
71
  if (@collection || all).length <= index and @dummy_collection
73
72
  (@collection.length..index).each do |i|
74
73
  new_dummy_record = ReactiveRecord::Base.new_from_vector(@target_klass, nil, *@vector, "*#{i}")
75
- new_dummy_record.backing_record.attributes[@association.inverse_of] = @owner if @association && !@association.through_association?
74
+ new_dummy_record.attributes[@association.inverse_of] = @owner if @association && !@association.through_association?
76
75
  @collection << new_dummy_record
77
76
  end
78
77
  end
@@ -99,10 +98,35 @@ module ReactiveRecord
99
98
  attr_reader :pre_sync_related_records
100
99
 
101
100
  def to_s
102
- "<Coll-#{object_id} - #{vector}>"
101
+ "<Coll-#{object_id} owner: #{@owner}, parent: #{@parent} - #{vector}>"
103
102
  end
104
103
 
105
104
  class << self
105
+
106
+ =begin
107
+ sync_scopes takes a newly broadcasted record change and updates all relevant currently active scopes
108
+ This is particularly hard when the client proc is specified. For example consider this scope:
109
+
110
+ class TestModel < ApplicationRecord
111
+ scope :quicker, -> { where(completed: true) }, client: -> { completed }
112
+ end
113
+
114
+ and this slice of reactive code:
115
+
116
+ DIV { "quicker.count = #{TestModel.quicker.count}" }
117
+
118
+ then on the server this code is executed:
119
+
120
+ TestModel.last.update(completed: false)
121
+
122
+ This will result in the changes being broadcast to the client, which may cauase the value of
123
+ TestModel.quicker.count to increase or decrease. Of course we may not actually have the all the records,
124
+ perhaps we just have the aggregate count.
125
+
126
+ To determine this sync_scopes first asks if the record being changed is in the scope given its value
127
+
128
+
129
+ =end
106
130
  def sync_scopes(broadcast)
107
131
  # record_with_current_values will return nil if data between
108
132
  # the broadcast record and the value on the client is out of sync
@@ -156,16 +180,23 @@ module ReactiveRecord
156
180
  # is it necessary to check @association in the next 2 methods???
157
181
 
158
182
  def joins_with?(record)
159
- if @association && @association.through_association
183
+ klass = record.class
184
+ if @association&.through_association
160
185
  @association.through_association.klass == record.class
161
- else
162
- @target_klass == record.class
186
+ elsif @target_klass == klass
187
+ true
188
+ elsif !klass.inheritance_column
189
+ false
190
+ elsif klass.base_class == @target_class
191
+ klass < @target_klass
192
+ elsif klass.base_class == klass
193
+ @target_klass < klass
163
194
  end
164
195
  end
165
196
 
166
197
  def related_records_for(record)
167
198
  return [] unless @association
168
- attrs = record.backing_record.attributes
199
+ attrs = record.attributes
169
200
  return [] unless attrs[@association.inverse_of] == @owner
170
201
  if !@association.through_association
171
202
  [record]
@@ -196,6 +227,7 @@ module ReactiveRecord
196
227
  live_scopes.each { |scope| scope.set_pre_sync_related_records(@pre_sync_related_records) }
197
228
  end
198
229
 
230
+ # NOTE sync_scopes is overridden in scope_description.rb
199
231
  def sync_scopes(related_records, record, filtering = true)
200
232
  #related_records = related_records.intersection([*@collection])
201
233
  #related_records = in_this_collection related_records
@@ -341,7 +373,8 @@ module ReactiveRecord
341
373
 
342
374
  def set_belongs_to(child)
343
375
  if @owner
344
- child.send("#{@association.inverse_of}=", @owner) if @association
376
+ # TODO this is major broken...current
377
+ child.send("#{@association.inverse_of}=", @owner) if @association && !@association.through_association
345
378
  elsif @parent
346
379
  @parent.set_belongs_to(child)
347
380
  end
@@ -354,16 +387,17 @@ module ReactiveRecord
354
387
  # means appointment.doctor_value.patients << appointment.patient
355
388
  # and we have to appointment.doctor(current value).patients.delete(appointment.patient)
356
389
 
357
-
358
390
  def update_child(item)
359
391
  backing_record = item.backing_record
360
392
  if backing_record && @owner && @association && !@association.through_association? && item.attributes[@association.inverse_of] != @owner
361
393
  inverse_of = @association.inverse_of
362
394
  current_association = item.attributes[inverse_of]
363
395
  backing_record.virgin = false unless backing_record.data_loading?
364
- backing_record.update_attribute(inverse_of, @owner)
365
- current_association.attributes[@association.attribute].delete(item) if current_association and current_association.attributes[@association.attribute]
366
- @owner.backing_record.update_attribute(@association.attribute) # forces a check if association contents have changed from synced values
396
+ backing_record.update_belongs_to(inverse_of, @owner)
397
+ if current_association && current_association.attributes[@association.attribute]
398
+ current_association.attributes[@association.attribute].delete(item)
399
+ end
400
+ @owner.backing_record.sync_has_many(@association.attribute)
367
401
  end
368
402
  end
369
403
 
@@ -374,7 +408,7 @@ module ReactiveRecord
374
408
  else
375
409
  unsaved_children << item
376
410
  update_child(item)
377
- @owner.backing_record.update_attribute(@association.attribute) if @owner && @association
411
+ @owner.backing_record.sync_has_many(@association.attribute) if @owner && @association
378
412
  if !@count.nil?
379
413
  @count += item.destroyed? ? -1 : 1
380
414
  notify_of_change self
@@ -454,12 +488,11 @@ module ReactiveRecord
454
488
  notify_of_change(
455
489
  if @owner && @association && !@association.through_association?
456
490
  inverse_of = @association.inverse_of
457
- if (backing_record = item.backing_record) && backing_record.attributes[inverse_of] == @owner
491
+ if (backing_record = item.backing_record) && item.attributes[inverse_of] == @owner
458
492
  # the if prevents double update if delete is being called from << (see << above)
459
- backing_record.update_attribute(inverse_of, nil)
493
+ backing_record.update_belongs_to(inverse_of, nil)
460
494
  end
461
- # forces a check if association contents have changed from synced values
462
- delete_internal(item) { @owner.backing_record.update_attribute(@association.attribute) }
495
+ delete_internal(item) { @owner.backing_record.sync_has_many(@association.attribute) }
463
496
  else
464
497
  delete_internal(item)
465
498
  end
@@ -472,7 +505,7 @@ module ReactiveRecord
472
505
  elsif !@count.nil?
473
506
  @count -= 1
474
507
  end
475
- yield item if block_given?
508
+ yield if block_given? # was yield item, but item is not used
476
509
  item
477
510
  end
478
511
 
@@ -5,10 +5,12 @@ module ReactiveRecord
5
5
  model.columns_hash
6
6
  end
7
7
 
8
+ def self.column_type(column_hash)
9
+ column_hash && column_hash[:sql_type_metadata] && column_hash[:sql_type_metadata][:type]
10
+ end
11
+
8
12
  def column_type(attr)
9
- column_hash = columns_hash[attr]
10
- return nil unless column_hash
11
- column_hash[:sql_type_metadata] && column_hash[:sql_type_metadata][:type]
13
+ Base.column_type(columns_hash[attr])
12
14
  end
13
15
 
14
16
  def convert_datetime(val)
@@ -26,10 +26,7 @@ module ReactiveRecord
26
26
  column_hash ||= {}
27
27
  notify
28
28
  @column_hash = column_hash
29
- column_type = (
30
- @column_hash[:sql_type_metadata] &&
31
- @column_hash[:sql_type_metadata][:type]
32
- ) || 'nil'
29
+ column_type = Base.column_type(@column_hash) || 'nil'
33
30
  default_value_method = "build_default_value_for_#{column_type}"
34
31
  @object = __send__ default_value_method
35
32
  rescue ::Exception
@@ -153,6 +150,11 @@ module ReactiveRecord
153
150
  ''
154
151
  end
155
152
 
153
+ def tap
154
+ yield self
155
+ self
156
+ end
157
+
156
158
  alias inspect to_s
157
159
 
158
160
  `#{self}.$$proto.toString = Opal.Object.$$proto.toString`
@@ -0,0 +1,133 @@
1
+ module ReactiveRecord
2
+ # creates getters for various method types
3
+ # TODO replace sync_attribute calls with direct logic
4
+ module Getters
5
+ def get_belongs_to(assoc, reload = nil)
6
+ getter_common(assoc.attribute, reload) do |has_key, attr|
7
+ return if new?
8
+ value = Base.fetch_from_db([@model, [:find, id], attr, @model.primary_key]) if id.present?
9
+ value = find_association(assoc, value)
10
+ sync_ignore_dummy attr, value, has_key
11
+ end&.cast_to_current_sti_type
12
+ end
13
+
14
+ def get_has_many(assoc, reload = nil)
15
+ getter_common(assoc.attribute, reload) do |_has_key, attr|
16
+ if new?
17
+ @attributes[attr] = Collection.new(assoc.klass, @ar_instance, assoc)
18
+ else
19
+ sync_attribute attr, Collection.new(assoc.klass, @ar_instance, assoc, *vector, attr)
20
+ end
21
+ end
22
+ end
23
+
24
+ def get_attr_value(attr, reload = nil)
25
+ non_relationship_getter_common(attr, reload) do
26
+ sync_attribute attr, convert(attr, model.columns_hash[attr][:default])
27
+ end
28
+ end
29
+
30
+ def get_primary_key_value
31
+ non_relationship_getter_common(model.primary_key, false)
32
+ end
33
+
34
+ def get_server_method(attr, reload = nil)
35
+ non_relationship_getter_common(attr, reload) do |has_key|
36
+ sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key
37
+ end
38
+ end
39
+
40
+ def get_ar_aggregate(aggr, reload = nil)
41
+ getter_common(aggr.attribute, reload) do |has_key, attr|
42
+ if new?
43
+ @attributes[attr] = aggr.klass.new.backing_record.link_aggregate(attr, self)
44
+ else
45
+ sync_ignore_dummy attr, new_from_vector(aggr.klass, self, *vector, attr), has_key
46
+ end
47
+ end
48
+ end
49
+
50
+ def get_non_ar_aggregate(attr, reload = nil)
51
+ non_relationship_getter_common(attr, reload)
52
+ end
53
+
54
+ private
55
+
56
+ def virtual_fetch_on_server_warning(attr)
57
+ log(
58
+ "Warning fetching virtual attributes (#{model.name}.#{attr}) during prerendering "\
59
+ 'on a changed or new model is not implemented.',
60
+ :warning
61
+ )
62
+ end
63
+
64
+ def sync_ignore_dummy(attr, value, has_key)
65
+ # ignore the value if its a Dummy value and there is already a value present
66
+ # this is used to implement reloading. During the reload while we are waiting we
67
+ # want the current attribute (if its present) to not change. Once the fetch
68
+ # is complete the fetch process will reload the attribute
69
+ value = @attributes[attr] if has_key && value.is_a?(Base::DummyValue)
70
+ sync_attribute(attr, value)
71
+ end
72
+
73
+ def non_relationship_getter_common(attr, reload, &block)
74
+ getter_common(attr, reload) do |has_key|
75
+ if new?
76
+ yield has_key if block
77
+ elsif on_opal_client?
78
+ sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key
79
+ elsif id.present?
80
+ sync_attribute attr, Base.fetch_from_db([@model, [:find, id], attr])
81
+ else
82
+ sync_attribute attr, Base.fetch_from_db([*vector, attr])
83
+ end
84
+ end
85
+ end
86
+
87
+ def getter_common(attribute, reload)
88
+ @virgin = false unless data_loading?
89
+ return if @destroyed
90
+ if @attributes.key? attribute
91
+ current_value = @attributes[attribute]
92
+ current_value.notify if current_value.is_a? Base::DummyValue
93
+ if reload
94
+ virtual_fetch_on_server_warning(attribute) if on_opal_server? && changed?
95
+ yield true, attribute
96
+ else
97
+ current_value
98
+ end
99
+ else
100
+ virtual_fetch_on_server_warning(attribute) if on_opal_server? && changed?
101
+ yield false, attribute
102
+ end.tap { |value| React::State.get_state(self, attribute) unless data_loading? }
103
+ end
104
+
105
+ def find_association(association, id)
106
+ inverse_of = association.inverse_of
107
+ instance = if id
108
+ find(association.klass, association.klass.primary_key => id)
109
+ else
110
+ new_from_vector(association.klass, nil, *vector, association.attribute)
111
+ end
112
+ instance_backing_record_attributes = instance.attributes
113
+ inverse_association = association.klass.reflect_on_association(inverse_of)
114
+ if inverse_association.collection?
115
+ instance_backing_record_attributes[inverse_of] = if id and id != ""
116
+ Collection.new(@model, instance, inverse_association, association.klass, ["find", id], inverse_of)
117
+ else
118
+ Collection.new(@model, instance, inverse_association, *vector, association.attribute, inverse_of)
119
+ end unless instance_backing_record_attributes[inverse_of]
120
+ instance_backing_record_attributes[inverse_of].replace [@ar_instance]
121
+ else
122
+ instance_backing_record_attributes[inverse_of] = @ar_instance
123
+ end unless association.through_association? || instance_backing_record_attributes.key?(inverse_of)
124
+ instance
125
+ end
126
+
127
+ def link_aggregate(attr, parent)
128
+ self.aggregate_owner = parent
129
+ self.aggregate_attribute = attr
130
+ @ar_instance
131
+ end
132
+ end
133
+ end
@@ -14,12 +14,11 @@ module ReactiveRecord
14
14
  define_attribute_methods
15
15
  @outer_scopes = Set.new
16
16
  @fetch_scheduled = nil
17
- @records = Hash.new { |hash, key| hash[key] = [] }
18
- @class_scopes = Hash.new { |hash, key| hash[key] = {} }
17
+ initialize_lookup_tables
19
18
  if on_opal_client?
20
19
  @pending_fetches = []
21
20
  @pending_records = []
22
- @last_fetch_at = Time.now
21
+ #@current_fetch_id = nil
23
22
  unless `typeof window.ReactiveRecordInitialData === 'undefined'`
24
23
  log(["Reactive record prerendered data being loaded: %o", `window.ReactiveRecordInitialData`])
25
24
  JSON.from_object(`window.ReactiveRecordInitialData`).each do |hash|
@@ -56,9 +55,9 @@ module ReactiveRecord
56
55
  f.when_on_server { @server_data_cache[*vector] }
57
56
  end
58
57
 
59
- isomorphic_method(:find_in_db) do |f, klass, attribute, value|
60
- f.send_to_server klass.name, attribute, value if RUBY_ENGINE == 'opal'
61
- f.when_on_server { @server_data_cache[klass, ["find_by", { attribute => value }], 'id'] }
58
+ isomorphic_method(:find_in_db) do |f, klass, attrs|
59
+ f.send_to_server klass.name, attrs if RUBY_ENGINE == 'opal'
60
+ f.when_on_server { @server_data_cache[klass, ['find_by', attrs], 'id'] }
62
61
  end
63
62
 
64
63
  class << self
@@ -128,50 +127,55 @@ module ReactiveRecord
128
127
  class << self
129
128
 
130
129
  attr_reader :pending_fetches
131
- attr_reader :last_fetch_at
130
+ attr_reader :current_fetch_id
132
131
 
133
132
  end
134
133
 
135
134
  def self.schedule_fetch
136
- React::State.set_state(WhileLoading, :quiet, false) # moved from while loading module see loading! method
137
- @fetch_scheduled ||= after(0) do
138
- if @pending_fetches.count > 0 # during testing we might reset the context while there are pending fetches otherwise this would never normally happen
139
- last_fetch_at = @last_fetch_at
140
- @last_fetch_at = Time.now
141
- pending_fetches = @pending_fetches.uniq
142
- models, associations = gather_records(@pending_records, false, nil)
143
- log(["Server Fetching: %o", pending_fetches.to_n])
144
- start_time = `Date.now()`
145
- Operations::Fetch.run(models: models, associations: associations, pending_fetches: pending_fetches)
146
- .then do |response|
147
- begin
148
- fetch_time = `Date.now()`
149
- log(" Fetched in: #{`(fetch_time - start_time)/ 1000`}s")
150
- begin
151
- ReactiveRecord::Base.load_from_json(response)
152
- rescue Exception => e
153
- log("Unexpected exception raised while loading json from server: #{e}", :error)
154
- end
155
- log(" Processed in: #{`(Date.now() - fetch_time) / 1000`}s")
156
- log([" Returned: %o", response.to_n])
157
- ReactiveRecord.run_blocks_to_load last_fetch_at
158
- ensure
159
- ReactiveRecord::WhileLoading.loaded_at last_fetch_at
160
- ReactiveRecord::WhileLoading.quiet! if @pending_fetches.empty?
161
- end
135
+ React::State.set_state(WhileLoading, :quiet, false) # moved from while loading module see loading! method
136
+ return if @fetch_scheduled
137
+ @current_fetch_id = Time.now
138
+ @fetch_scheduled = after(0) do
139
+ # Skip the fetch if there are no pending_fetches. This would never normally happen
140
+ # but during testing we might reset the context while there are pending fetches
141
+ next unless @pending_fetches.count > 0
142
+ saved_current_fetch_id = @current_fetch_id
143
+ saved_pending_fetches = @pending_fetches.uniq
144
+ models, associations = gather_records(@pending_records, false, nil)
145
+ log(["Server Fetching: %o", saved_pending_fetches.to_n])
146
+ start_time = `Date.now()`
147
+ Operations::Fetch.run(models: models, associations: associations, pending_fetches: saved_pending_fetches)
148
+ .then do |response|
149
+ begin
150
+ fetch_time = `Date.now()`
151
+ log(" Fetched in: #{`(fetch_time - start_time)/ 1000`}s")
152
+ timer = after(0) do
153
+ log(" Processed in: #{`(Date.now() - fetch_time) / 1000`}s")
154
+ log([' Returned: %o', response.to_n])
162
155
  end
163
- .fail do |response|
164
- log("Fetch failed", :error)
165
- begin
166
- ReactiveRecord.run_blocks_to_load(last_fetch_at, response)
167
- ensure
168
- ReactiveRecord::WhileLoading.quiet! if @pending_fetches.empty?
169
- end
156
+ begin
157
+ ReactiveRecord::Base.load_from_json(response)
158
+ rescue Exception => e
159
+ `clearTimeout(#{timer})`
160
+ log("Unexpected exception raised while loading json from server: #{e}", :error)
170
161
  end
171
- @pending_fetches = []
172
- @pending_records = []
173
- @fetch_scheduled = nil
162
+ ReactiveRecord.run_blocks_to_load saved_current_fetch_id
163
+ ensure
164
+ ReactiveRecord::WhileLoading.loaded_at saved_current_fetch_id
165
+ ReactiveRecord::WhileLoading.quiet! if @pending_fetches.empty?
166
+ end
174
167
  end
168
+ .fail do |response|
169
+ log("Fetch failed", :error)
170
+ begin
171
+ ReactiveRecord.run_blocks_to_load(saved_current_fetch_id, response)
172
+ ensure
173
+ ReactiveRecord::WhileLoading.quiet! if @pending_fetches.empty?
174
+ end
175
+ end
176
+ @pending_fetches = []
177
+ @pending_records = []
178
+ @fetch_scheduled = nil
175
179
  end
176
180
  end
177
181
 
@@ -241,13 +245,13 @@ module ReactiveRecord
241
245
  [models, associations, backing_records]
242
246
  end
243
247
 
244
- def save(validate, force, &block)
248
+ def save_or_validate(save, validate, force, &block)
245
249
  if data_loading?
246
250
  sync!
247
- elsif force or changed?
251
+ elsif force || changed?
248
252
  HyperMesh.load do
249
253
  ReactiveRecord.loads_pending! unless self.class.pending_fetches.empty?
250
- end.then { save_to_server(validate, force, &block) }
254
+ end.then { send_save_to_server(save, validate, force, &block) }
251
255
  #save_to_server(validate, force, &block)
252
256
  else
253
257
  promise = Promise.new
@@ -257,46 +261,54 @@ module ReactiveRecord
257
261
  end
258
262
  end
259
263
 
260
- def save_to_server(validate, force, &block)
264
+ def send_save_to_server(save, validate, force, &block)
261
265
  models, associations, backing_records = self.class.gather_records([self], force, self)
262
266
 
263
- backing_records.each { |id, record| record.saving! }
267
+ begin
268
+ backing_records.each { |id, record| record.saving! } if save
264
269
 
265
- promise = Promise.new
266
- Operations::Save.run(models: models, associations: associations, validate: validate)
267
- .then do |response|
268
- begin
269
- response[:models] = response[:saved_models].collect do |item|
270
- backing_records[item[0]].ar_instance
271
- end
270
+ promise = Promise.new
271
+ Operations::Save.run(models: models, associations: associations, save: save, validate: validate)
272
+ .then do |response|
273
+ begin
274
+ response[:models] = response[:saved_models].collect do |item|
275
+ backing_records[item[0]].ar_instance
276
+ end
272
277
 
273
- if response[:success]
274
- response[:saved_models].each do | item |
275
- Broadcast.to_self backing_records[item[0]].ar_instance, item[2]
278
+ if save
279
+ if response[:success]
280
+ response[:saved_models].each do |item|
281
+ Broadcast.to_self backing_records[item[0]].ar_instance, item[2]
282
+ end
283
+ else
284
+ log("Reactive Record Save Failed: #{response[:message]}", :error)
285
+ response[:saved_models].each do | item |
286
+ log(" Model: #{item[1]}[#{item[0]}] Attributes: #{item[2]} Errors: #{item[3]}", :error) if item[3]
287
+ end
288
+ end
276
289
  end
277
- else
278
- log("Reactive Record Save Failed: #{response[:message]}", :error)
290
+
279
291
  response[:saved_models].each do | item |
280
- log(" Model: #{item[1]}[#{item[0]}] Attributes: #{item[2]} Errors: #{item[3]}", :error) if item[3]
292
+ backing_records[item[0]].sync_unscoped_collection! if save
293
+ backing_records[item[0]].errors! item[3]
281
294
  end
282
- end
283
-
284
- response[:saved_models].each do | item |
285
- backing_records[item[0]].sync_unscoped_collection!
286
- backing_records[item[0]].errors! item[3]
287
- end
288
-
289
- yield response[:success], response[:message], response[:models] if block
290
- promise.resolve response # TODO this could be problematic... there was no .json here, so .... what's to do?
291
295
 
292
- backing_records.each { |id, record| record.saved! }
296
+ yield response[:success], response[:message], response[:models] if block
297
+ promise.resolve response # TODO this could be problematic... there was no .json here, so .... what's to do?
293
298
 
294
- rescue Exception => e
295
- log("Exception raised while saving - #{e}", :error)
299
+ rescue Exception => e
300
+ # debugger
301
+ log("Exception raised while saving - #{e}", :error)
302
+ ensure
303
+ backing_records.each { |_id, record| record.saved! rescue nil } if save
304
+ end
296
305
  end
306
+ promise
307
+ rescue Exception => e
308
+ backing_records.each { |_id, record| record.saved!(true) rescue nil } if save
297
309
  end
298
- promise
299
310
  rescue Exception => e
311
+ # debugger
300
312
  log("Exception raised while saving - #{e}", :error)
301
313
  yield false, e.message, [] if block
302
314
  promise.resolve({success: false, message: e.message, models: []})
@@ -357,7 +369,7 @@ module ReactiveRecord
357
369
  method
358
370
  end
359
371
  end
360
- reactive_records[model_to_save[:id]] = vectors[vector] = record = find_record(model, id, vector, save)
372
+ reactive_records[model_to_save[:id]] = vectors[vector] = record = find_record(model, id, vector, save) # ??? || validate ???
361
373
  if record and record.respond_to?(:id) and record.id
362
374
  # we have an already exising activerecord model
363
375
  keys = record.attributes.keys
@@ -447,27 +459,28 @@ module ReactiveRecord
447
459
 
448
460
  saved_models = reactive_records.collect do |reactive_record_id, model|
449
461
  #puts "saving rr_id: #{reactive_record_id} model.object_id: #{model.object_id} frozen? <#{model.frozen?}>"
450
- if model and (model.frozen? or dont_save_list.include?(model) or model.changed.include?(model.class.primary_key))
462
+ next unless model
463
+ if !save|| model.frozen? || dont_save_list.include?(model) || model.changed.include?(model.class.primary_key)
451
464
  # the above check for changed including the private key happens if you have an aggregate that includes its own id
452
- #puts "validating frozen model #{model.class.name} #{model} (reactive_record_id = #{reactive_record_id})"
465
+ # puts "validating frozen model #{model.class.name} #{model} (reactive_record_id = #{reactive_record_id})"
453
466
  valid = model.valid?
454
- #puts "has_errors before = #{has_errors}, validate= #{validate}, !valid= #{!valid} (validate and !valid) #{validate and !valid}"
467
+ # puts "has_errors before = #{has_errors}, validate= #{validate}, !valid= #{!valid} (validate and !valid) #{validate and !valid}"
455
468
  has_errors ||= (validate and !valid)
456
- #puts "validation complete errors = <#{!valid}>, #{model.errors.messages} has_errors #{has_errors}"
469
+ # puts "validation complete errors = <#{!valid}>, #{model.errors.messages} has_errors #{has_errors}"
457
470
  error_messages << [model, model.errors.messages] unless valid
458
- [reactive_record_id, model.class.name, model.attributes, (valid ? nil : model.errors.messages)]
459
- elsif model and (!model.id or model.changed?)
460
- #puts "saving #{model.class.name} #{model} (reactive_record_id = #{reactive_record_id})"
471
+ [reactive_record_id, model.class.name, model.__hyperloop_secure_attributes(acting_user), (valid ? nil : model.errors.messages)]
472
+ elsif !model.id || model.changed?
473
+ # puts "saving #{model.class.name} #{model} (reactive_record_id = #{reactive_record_id})"
461
474
  saved = model.check_permission_with_acting_user(acting_user, new_models.include?(model) ? :create_permitted? : :update_permitted?).save(validate: validate)
462
475
  has_errors ||= !saved
463
476
  messages = model.errors.messages if (validate and !saved) or (!validate and !model.valid?)
464
477
  error_messages << [model, messages] if messages
465
- #puts "saved complete errors = <#{!saved}>, #{messages} has_errors #{has_errors}"
466
- [reactive_record_id, model.class.name, model.attributes, messages]
478
+ # puts "saved complete errors = <#{!saved}>, #{messages} has_errors #{has_errors}"
479
+ [reactive_record_id, model.class.name, model.__hyperloop_secure_attributes(acting_user), messages]
467
480
  end
468
481
  end.compact
469
482
 
470
- if has_errors
483
+ if has_errors && save
471
484
  ::Rails.logger.debug "\033[0;31;1mERROR: HyperModel saving records failed:\033[0;30;21m"
472
485
  error_messages.each do |model, messages|
473
486
  messages.each do |message|
@@ -477,12 +490,11 @@ module ReactiveRecord
477
490
  raise "HyperModel saving records failed!"
478
491
  end
479
492
 
480
- if save
493
+ if save || validate
481
494
 
482
495
  {success: true, saved_models: saved_models }
483
496
 
484
497
  else
485
-
486
498
  # vectors.each { |vector, model| model.reload unless model.nil? or model.new_record? or model.frozen? }
487
499
  vectors
488
500