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.
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