hyper-mesh 1.0.0.lap27 → 1.0.0.lap28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +6 -2
- data/Gemfile +0 -1
- data/Rakefile +2 -2
- data/hyper-mesh.gemspec +1 -1
- data/lib/active_record_base.rb +39 -27
- data/lib/hyper-mesh.rb +6 -1
- data/lib/hypermesh/version.rb +1 -1
- data/lib/object/tap.rb +7 -0
- data/lib/reactive_record/active_record/associations.rb +14 -3
- data/lib/reactive_record/active_record/base.rb +1 -2
- data/lib/reactive_record/active_record/class_methods.rb +120 -67
- data/lib/reactive_record/active_record/error.rb +17 -12
- data/lib/reactive_record/active_record/errors.rb +374 -0
- data/lib/reactive_record/active_record/instance_methods.rb +58 -67
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +1 -4
- data/lib/reactive_record/active_record/reactive_record/base.rb +129 -234
- data/lib/reactive_record/active_record/reactive_record/collection.rb +51 -18
- data/lib/reactive_record/active_record/reactive_record/column_types.rb +5 -3
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +6 -4
- data/lib/reactive_record/active_record/reactive_record/getters.rb +133 -0
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +99 -87
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +54 -0
- data/lib/reactive_record/active_record/reactive_record/operations.rb +2 -1
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +1 -1
- data/lib/reactive_record/active_record/reactive_record/setters.rb +194 -0
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +4 -5
- data/lib/reactive_record/active_record_error.rb +4 -0
- data/lib/reactive_record/broadcast.rb +55 -18
- data/lib/reactive_record/permissions.rb +5 -4
- data/lib/reactive_record/scope_description.rb +14 -6
- data/lib/reactive_record/server_data_cache.rb +119 -70
- metadata +16 -13
- data/lib/reactive_record/active_record/reactive_record/reactive_set_relationship_helpers.rb +0 -189
@@ -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.
|
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
|
-
|
183
|
+
klass = record.class
|
184
|
+
if @association&.through_association
|
160
185
|
@association.through_association.klass == record.class
|
161
|
-
|
162
|
-
|
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.
|
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
|
-
|
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.
|
365
|
-
|
366
|
-
|
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.
|
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) &&
|
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.
|
493
|
+
backing_record.update_belongs_to(inverse_of, nil)
|
460
494
|
end
|
461
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
60
|
-
f.send_to_server klass.name,
|
61
|
-
f.when_on_server { @server_data_cache[klass, [
|
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 :
|
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)
|
137
|
-
@fetch_scheduled
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
172
|
-
|
173
|
-
|
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
|
248
|
+
def save_or_validate(save, validate, force, &block)
|
245
249
|
if data_loading?
|
246
250
|
sync!
|
247
|
-
elsif force
|
251
|
+
elsif force || changed?
|
248
252
|
HyperMesh.load do
|
249
253
|
ReactiveRecord.loads_pending! unless self.class.pending_fetches.empty?
|
250
|
-
end.then {
|
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
|
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
|
-
|
267
|
+
begin
|
268
|
+
backing_records.each { |id, record| record.saving! } if save
|
264
269
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
-
|
274
|
-
|
275
|
-
|
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
|
-
|
278
|
-
log("Reactive Record Save Failed: #{response[:message]}", :error)
|
290
|
+
|
279
291
|
response[:saved_models].each do | item |
|
280
|
-
|
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
|
-
|
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
|
-
|
295
|
-
|
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
|
-
|
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.
|
459
|
-
elsif
|
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.
|
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
|
|