hyper-model 1.0.alpha1.3 → 1.0.alpha1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.rspec +0 -1
- data/Gemfile +6 -5
- data/Rakefile +18 -6
- data/hyper-model.gemspec +12 -20
- data/lib/active_record_base.rb +95 -28
- 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 +125 -35
- data/lib/reactive_record/active_record/base.rb +32 -0
- data/lib/reactive_record/active_record/class_methods.rb +125 -53
- 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 +196 -63
- 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 +71 -44
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +5 -5
- data/lib/reactive_record/active_record/reactive_record/operations.rb +7 -1
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +3 -6
- data/lib/reactive_record/active_record/reactive_record/setters.rb +105 -68
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +22 -1
- data/lib/reactive_record/broadcast.rb +59 -25
- data/lib/reactive_record/interval.rb +3 -3
- data/lib/reactive_record/permissions.rb +1 -1
- data/lib/reactive_record/scope_description.rb +3 -2
- data/lib/reactive_record/server_data_cache.rb +78 -48
- data/polymorph-notes.md +143 -0
- metadata +52 -157
- data/Gemfile.lock +0 -440
@@ -14,15 +14,15 @@ module ReactiveRecord
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def waiting_for_save(model)
|
17
|
-
@waiting_for_save[model]
|
17
|
+
@waiting_for_save[model.base_class]
|
18
18
|
end
|
19
19
|
|
20
20
|
def wait_for_save(model, &block)
|
21
|
-
@waiting_for_save[model] << block
|
21
|
+
@waiting_for_save[model.base_class] << block
|
22
22
|
end
|
23
23
|
|
24
24
|
def clear_waiting_for_save(model)
|
25
|
-
@waiting_for_save[model] = []
|
25
|
+
@waiting_for_save[model.base_class] = []
|
26
26
|
end
|
27
27
|
|
28
28
|
def lookup_by_object_id(object_id)
|
@@ -33,8 +33,8 @@ module ReactiveRecord
|
|
33
33
|
`#{@records_by_object_id}[#{record.object_id}] = #{record}`
|
34
34
|
end
|
35
35
|
|
36
|
-
def lookup_by_id(
|
37
|
-
`#{@records_by_id}[#{
|
36
|
+
def lookup_by_id(model, id) # model and id
|
37
|
+
`#{@records_by_id}[#{[model.base_class, id]}]` || nil
|
38
38
|
end
|
39
39
|
|
40
40
|
def set_id_lookup(record)
|
@@ -12,6 +12,10 @@ module ReactiveRecord
|
|
12
12
|
|
13
13
|
FORMAT = '0x%x'
|
14
14
|
|
15
|
+
class << self
|
16
|
+
attr_accessor :last_response_sent_at
|
17
|
+
end
|
18
|
+
|
15
19
|
def self.serialize_params(hash)
|
16
20
|
hash['associations'].each do |assoc|
|
17
21
|
assoc['parent_id'] = FORMAT % assoc['parent_id']
|
@@ -38,6 +42,7 @@ module ReactiveRecord
|
|
38
42
|
response[:saved_models].each do |saved_model|
|
39
43
|
saved_model[0] = FORMAT % saved_model[0]
|
40
44
|
end if response.is_a?(Hash) && response[:saved_models]
|
45
|
+
response[:sent_at] = Time.now.to_f
|
41
46
|
response
|
42
47
|
end
|
43
48
|
|
@@ -45,6 +50,7 @@ module ReactiveRecord
|
|
45
50
|
response[:saved_models].each do |saved_model|
|
46
51
|
saved_model[0] = saved_model[0].to_i(16)
|
47
52
|
end if response.is_a?(Hash) && response[:saved_models]
|
53
|
+
Base.last_response_sent_at = response.delete(:sent_at)
|
48
54
|
response
|
49
55
|
end
|
50
56
|
end
|
@@ -93,7 +99,7 @@ module ReactiveRecord
|
|
93
99
|
class Destroy < Base
|
94
100
|
param :acting_user, nils: true
|
95
101
|
param :model
|
96
|
-
param :id
|
102
|
+
param :id, nils: true
|
97
103
|
param :vector
|
98
104
|
step do
|
99
105
|
ReactiveRecord::Base.destroy_record(
|
@@ -9,10 +9,10 @@ module ReactiveRecord
|
|
9
9
|
def set_pre_sync_related_records(related_records, _record = nil)
|
10
10
|
@pre_sync_related_records = nil
|
11
11
|
ReactiveRecord::Base.catch_db_requests do
|
12
|
-
puts "#{self}.set_pre_sync_related_records filter_records(#{related_records})"
|
12
|
+
# puts "#{self}.set_pre_sync_related_records filter_records(#{related_records})"
|
13
13
|
|
14
14
|
@pre_sync_related_records = filter_records(related_records)
|
15
|
-
puts "returns #{@pre_sync_related_records}"
|
15
|
+
# puts "returns #{@pre_sync_related_records}"
|
16
16
|
live_scopes.each do |scope|
|
17
17
|
scope.set_pre_sync_related_records(@pre_sync_related_records)
|
18
18
|
end
|
@@ -35,9 +35,7 @@ module ReactiveRecord
|
|
35
35
|
if collector?
|
36
36
|
update_collector_scope(related_records)
|
37
37
|
else
|
38
|
-
puts "#{self}.update_collection calling filter_records(#{related_records})"
|
39
38
|
related_records = filter_records(related_records)
|
40
|
-
puts "returns #{related_records}"
|
41
39
|
update_filter_scope(@pre_sync_related_records, related_records)
|
42
40
|
end
|
43
41
|
end
|
@@ -46,8 +44,7 @@ module ReactiveRecord
|
|
46
44
|
current = Set.new([*@collection])
|
47
45
|
(related_records - @pre_sync_related_records).each { |r| current << r }
|
48
46
|
(@pre_sync_related_records - related_records).each { |r| current.delete(r) }
|
49
|
-
|
50
|
-
replace(filter_records(current).tap { |rr| puts "returns #{rr}" })
|
47
|
+
replace(filter_records(current))
|
51
48
|
Set.new([*@collection])
|
52
49
|
end
|
53
50
|
|
@@ -43,17 +43,26 @@ module ReactiveRecord
|
|
43
43
|
|
44
44
|
def set_belongs_to(assoc, raw_value)
|
45
45
|
set_common(assoc.attribute, raw_value) do |value, attr|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# itself will just reactively read the value (a model instance) by doing a .id
|
53
|
-
update_belongs_to attr, value.itself
|
46
|
+
current_value = @attributes[assoc.attribute]
|
47
|
+
update_has_many_through_associations assoc, nil, current_value, :remove_member
|
48
|
+
update_has_many_through_associations assoc, nil, value, :add_member
|
49
|
+
remove_current_inverse_attribute assoc, nil, current_value
|
50
|
+
add_new_inverse_attribute assoc, nil, value
|
51
|
+
update_belongs_to attr, value.itself
|
54
52
|
end
|
55
53
|
end
|
56
54
|
|
55
|
+
def set_belongs_to_via_has_many(orig, value)
|
56
|
+
assoc = orig.inverse
|
57
|
+
attr = assoc.attribute
|
58
|
+
current_value = @attributes[attr]
|
59
|
+
update_has_many_through_associations assoc, orig, current_value, :remove_member
|
60
|
+
update_has_many_through_associations assoc, orig, value, :add_member
|
61
|
+
remove_current_inverse_attribute assoc, orig, current_value
|
62
|
+
add_new_inverse_attribute assoc, orig, value
|
63
|
+
update_belongs_to attr, value.itself
|
64
|
+
end
|
65
|
+
|
57
66
|
def sync_has_many(attr)
|
58
67
|
set_change_status_and_notify_only attr, @attributes[attr] != @synced_attributes[attr]
|
59
68
|
end
|
@@ -85,7 +94,7 @@ module ReactiveRecord
|
|
85
94
|
end
|
86
95
|
|
87
96
|
def set_attribute_change_status_and_notify(attr, changed, new_value)
|
88
|
-
if @virgin
|
97
|
+
if @virgin || @being_destroyed
|
89
98
|
@attributes[attr] = new_value
|
90
99
|
else
|
91
100
|
change_status_and_notify_helper(attr, changed) do |had_key, current_value|
|
@@ -99,7 +108,7 @@ module ReactiveRecord
|
|
99
108
|
end
|
100
109
|
|
101
110
|
def set_change_status_and_notify_only(attr, changed)
|
102
|
-
return if @virgin
|
111
|
+
return if @virgin || @being_destroyed
|
103
112
|
change_status_and_notify_helper(attr, changed) do
|
104
113
|
Hyperstack::Internal::State::Variable.set(self, attr, nil) unless data_loading?
|
105
114
|
end
|
@@ -107,8 +116,6 @@ module ReactiveRecord
|
|
107
116
|
|
108
117
|
def change_status_and_notify_helper(attr, changed)
|
109
118
|
empty_before = changed_attributes.empty?
|
110
|
-
# TODO: confirm this works:
|
111
|
-
# || data_loading? added so that model.new can be wrapped in a ReactiveRecord.load_data
|
112
119
|
if !changed || data_loading?
|
113
120
|
changed_attributes.delete(attr)
|
114
121
|
elsif !changed_attributes.include?(attr)
|
@@ -125,70 +132,100 @@ module ReactiveRecord
|
|
125
132
|
)
|
126
133
|
end
|
127
134
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
current_value.attributes[inverse_attr].delete(@ar_instance) unless current_value.nil?
|
135
|
+
# when updating the inverse attribute of a belongs_to that is itself a belongs_to
|
136
|
+
# (i.e. 1-1 relationship) we clear the existing inverse value and then
|
137
|
+
# write the current record to the new value
|
138
|
+
|
139
|
+
# when updating an inverse attribute of a belongs_to that is a has_many (i.e. a collection)
|
140
|
+
# we need to first remove the current associated value (if non-nil), then add the new
|
141
|
+
# value to the collection. If the inverse collection is not yet initialized we do it here.
|
142
|
+
|
143
|
+
# the above is split into three methods, because the inverse of apolymorphic belongs to may
|
144
|
+
# change from has_one to has_many. So we first deal with the current value, then
|
145
|
+
# update the new value which uses the push_onto_collection helper
|
146
|
+
|
147
|
+
def remove_current_inverse_attribute(association, orig, model)
|
148
|
+
return if model.nil?
|
149
|
+
inverse_association = association.inverse(model)
|
150
|
+
return if inverse_association == orig
|
151
|
+
if inverse_association.collection?
|
152
|
+
# note we don't have to check if the collection exists, since it must
|
153
|
+
# exist as at this ar_instance is already part of it.
|
154
|
+
model.attributes[inverse_association.attribute].delete(@ar_instance)
|
149
155
|
else
|
150
|
-
|
156
|
+
model.attributes[inverse_association.attribute] = nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def add_new_inverse_attribute(association, orig, model)
|
161
|
+
return if model.nil?
|
162
|
+
inverse_association = association.inverse(model)
|
163
|
+
return if inverse_association == orig
|
164
|
+
if inverse_association.collection?
|
165
|
+
model.backing_record.push_onto_collection(@model, inverse_association, @ar_instance)
|
166
|
+
else
|
167
|
+
inverse_attr = inverse_association.attribute
|
168
|
+
model.attributes[inverse_attr] = @ar_instance
|
169
|
+
return if data_loading?
|
170
|
+
Hyperstack::Internal::State::Variable.set(model.backing_record, inverse_attr, @ar_instance)
|
151
171
|
end
|
152
172
|
end
|
153
173
|
|
154
174
|
def push_onto_collection(model, association, ar_instance)
|
155
175
|
@attributes[association.attribute] ||= Collection.new(model, @ar_instance, association)
|
156
|
-
@attributes[association.attribute]
|
176
|
+
@attributes[association.attribute]._internal_push ar_instance
|
177
|
+
end
|
178
|
+
|
179
|
+
# class Membership < ActiveRecord::Base
|
180
|
+
# belongs_to :uzer
|
181
|
+
# belongs_to :memerable, polymorphic: true
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# class Project < ActiveRecord::Base
|
185
|
+
# has_many :memberships, as: :memerable, dependent: :destroy
|
186
|
+
# has_many :uzers, through: :memberships
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# class Group < ActiveRecord::Base
|
190
|
+
# has_many :memberships, as: :memerable, dependent: :destroy
|
191
|
+
# has_many :uzers, through: :memberships
|
192
|
+
# end
|
193
|
+
#
|
194
|
+
# class Uzer < ActiveRecord::Base
|
195
|
+
# has_many :memberships
|
196
|
+
# has_many :groups, through: :memberships, source: :memerable, source_type: 'Group'
|
197
|
+
# has_many :projects, through: :memberships, source: :memerable, source_type: 'Project'
|
198
|
+
# end
|
199
|
+
|
200
|
+
# membership.uzer = some_new_uzer (i.e. through association is changing)
|
201
|
+
# means membership.some_new_uzer.(groups OR projects) << uzer.memberable (depending on type of memberable)
|
202
|
+
# and we have to remove the current value of the source association (memerable) from the current uzer group or project
|
203
|
+
# and we have to then find any inverse has_many_through association (i.e. group or projects.uzers) and delete the
|
204
|
+
# current value from those collections and push the new value on
|
205
|
+
|
206
|
+
def update_has_many_through_associations(assoc, orig, value, method)
|
207
|
+
return if value.nil?
|
208
|
+
assoc.through_associations(value).each do |ta|
|
209
|
+
next if orig == ta
|
210
|
+
source_value = @attributes[ta.source]
|
211
|
+
# skip if source value is nil or if type of the association does not match type of source
|
212
|
+
next unless source_value.class.to_s == ta.source_type
|
213
|
+
ta.send method, source_value, value
|
214
|
+
ta.source_associations(source_value).each do |sa|
|
215
|
+
sa.send method, value, source_value
|
216
|
+
end
|
217
|
+
end
|
157
218
|
end
|
158
219
|
|
159
|
-
def
|
160
|
-
|
161
|
-
|
162
|
-
end
|
220
|
+
# def remove_src_assoc(sa, source_value, current_value)
|
221
|
+
# source_inverse_collection = source_value.attributes[sa.attribute]
|
222
|
+
# source_inverse_collection.delete(current_value) if source_inverse_collection
|
223
|
+
# end
|
224
|
+
#
|
225
|
+
# def add_src_assoc(sa, source_value, new_value)
|
226
|
+
# source_value.attributes[sa.attribute] ||= Collection.new(sa.owner_class, source_value, sa)
|
227
|
+
# source_value.attributes[sa.attribute] << new_value
|
228
|
+
# end
|
163
229
|
|
164
|
-
def update_through_association(ta, new_belongs_to_value)
|
165
|
-
# appointment.doctor = doctor_new_value (i.e. through association is changing)
|
166
|
-
# means appointment.doctor_new_value.patients << appointment.patient
|
167
|
-
# and we have to appointment.doctor_current_value.patients.delete(appointment.patient)
|
168
|
-
source_value = @attributes[ta.source]
|
169
|
-
current_belongs_to_value = @attributes[ta.inverse.attribute]
|
170
|
-
return unless source_value
|
171
|
-
unless current_belongs_to_value.nil? || current_belongs_to_value.attributes[ta.attribute].nil?
|
172
|
-
current_belongs_to_value.attributes[ta.attribute].delete(source_value)
|
173
|
-
end
|
174
|
-
return unless new_belongs_to_value
|
175
|
-
new_belongs_to_value.attributes[ta.attribute] ||= Collection.new(ta.klass, new_belongs_to_value, ta)
|
176
|
-
new_belongs_to_value.attributes[ta.attribute] << source_value
|
177
|
-
end
|
178
|
-
|
179
|
-
def update_source_association(sa, new_source_value)
|
180
|
-
# appointment.patient = patient_value (i.e. source is changing)
|
181
|
-
# means appointment.doctor.patients.delete(appointment.patient)
|
182
|
-
# means appointment.doctor.patients << patient_value
|
183
|
-
belongs_to_value = @attributes[sa.inverse.attribute]
|
184
|
-
current_source_value = @attributes[sa.source]
|
185
|
-
return unless belongs_to_value
|
186
|
-
unless belongs_to_value.attributes[sa.attribute].nil? || current_source_value.nil?
|
187
|
-
belongs_to_value.attributes[sa.attribute].delete(current_source_value)
|
188
|
-
end
|
189
|
-
return unless new_source_value
|
190
|
-
belongs_to_value.attributes[sa.attribute] ||= Collection.new(sa.klass, belongs_to_value, sa)
|
191
|
-
belongs_to_value.attributes[sa.attribute] << new_source_value
|
192
|
-
end
|
193
230
|
end
|
194
231
|
end
|
@@ -353,7 +353,7 @@ module ReactiveRecord
|
|
353
353
|
|
354
354
|
end
|
355
355
|
|
356
|
-
def after_mount_and_update
|
356
|
+
def after_mount_and_update(*)
|
357
357
|
@waiting_on_resources = @Loading
|
358
358
|
node = dom_node
|
359
359
|
%x{
|
@@ -478,6 +478,27 @@ if RUBY_ENGINE == 'opal'
|
|
478
478
|
reactive_record_link_set_while_loading_container_class
|
479
479
|
end
|
480
480
|
|
481
|
+
# This is required to support legacy browsers (Internet Explorer 9+)
|
482
|
+
# https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
|
483
|
+
`
|
484
|
+
if (typeof(Element) != 'undefined' && !Element.prototype.matches) {
|
485
|
+
Element.prototype.matches = Element.prototype.msMatchesSelector ||
|
486
|
+
Element.prototype.webkitMatchesSelector;
|
487
|
+
}
|
488
|
+
|
489
|
+
if (typeof(Element) != 'undefined' && !Element.prototype.closest) {
|
490
|
+
Element.prototype.closest = function(s) {
|
491
|
+
var el = this;
|
492
|
+
|
493
|
+
do {
|
494
|
+
if (el.matches(s)) return el;
|
495
|
+
el = el.parentElement || el.parentNode;
|
496
|
+
} while (el !== null && el.nodeType === 1);
|
497
|
+
return null;
|
498
|
+
};
|
499
|
+
}
|
500
|
+
`
|
501
|
+
|
481
502
|
def reactive_record_link_to_enclosing_while_loading_container
|
482
503
|
# Call after any component mounts - attaches the containers loading id to this component
|
483
504
|
# Fyi, the while_loading container is responsible for setting its own link to itself
|
@@ -6,27 +6,35 @@ module ReactiveRecord
|
|
6
6
|
# before the first broadcast.
|
7
7
|
@public_columns_hash ||= ActiveRecord::Base.public_columns_hash
|
8
8
|
Hyperstack::InternalPolicy.regulate_broadcast(model) do |data|
|
9
|
+
puts "Broadcast aftercommit hook: #{data}" if Hyperstack::Connection.show_diagnostics
|
10
|
+
|
9
11
|
if !Hyperstack.on_server? && Hyperstack::Connection.root_path
|
10
|
-
send_to_server(operation, data) rescue nil # fails if 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
|
11
13
|
else
|
12
|
-
SendPacket.run(data, operation: operation)
|
14
|
+
SendPacket.run(data, operation: operation, updated_at: model.__synchromesh_update_time)
|
13
15
|
end
|
14
16
|
end
|
15
17
|
rescue ActiveRecord::StatementInvalid => e
|
16
18
|
raise e unless e.message == "Could not find table 'hyperstack_connections'"
|
17
19
|
end unless RUBY_ENGINE == 'opal'
|
18
20
|
|
19
|
-
def self.send_to_server(operation, data)
|
21
|
+
def self.send_to_server(operation, data, updated_at)
|
20
22
|
salt = SecureRandom.hex
|
21
23
|
authorization = Hyperstack.authorization(salt, data[:channel], data[:broadcast_id])
|
22
24
|
raise 'no server running' unless Hyperstack::Connection.root_path
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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'
|
30
38
|
end unless RUBY_ENGINE == 'opal'
|
31
39
|
|
32
40
|
class SendPacket < Hyperstack::ServerOp
|
@@ -40,6 +48,7 @@ module ReactiveRecord
|
|
40
48
|
param :record
|
41
49
|
param :operation
|
42
50
|
param :previous_changes
|
51
|
+
param :updated_at
|
43
52
|
|
44
53
|
unless RUBY_ENGINE == 'opal'
|
45
54
|
validate do
|
@@ -63,11 +72,11 @@ module ReactiveRecord
|
|
63
72
|
ReactiveRecord::Collection.sync_scopes broadcast.process_previous_changes
|
64
73
|
end
|
65
74
|
end
|
66
|
-
end
|
75
|
+
end if RUBY_ENGINE == 'opal'
|
67
76
|
|
68
77
|
def self.to_self(record, data = {})
|
69
78
|
# simulate incoming packet after a local save
|
70
|
-
operation = if record.
|
79
|
+
operation = if record.new_record?
|
71
80
|
:create
|
72
81
|
elsif record.destroyed?
|
73
82
|
:destroy
|
@@ -81,7 +90,7 @@ module ReactiveRecord
|
|
81
90
|
|
82
91
|
def record_with_current_values
|
83
92
|
ReactiveRecord::Base.load_data do
|
84
|
-
backing_record = @backing_record || klass.find(record[
|
93
|
+
backing_record = @backing_record || klass.find(record[klass.primary_key]).backing_record
|
85
94
|
if destroyed?
|
86
95
|
backing_record.ar_instance
|
87
96
|
else
|
@@ -108,6 +117,10 @@ module ReactiveRecord
|
|
108
117
|
@destroyed
|
109
118
|
end
|
110
119
|
|
120
|
+
def local?
|
121
|
+
@is_local
|
122
|
+
end
|
123
|
+
|
111
124
|
def klass
|
112
125
|
Object.const_get(@klass)
|
113
126
|
end
|
@@ -119,6 +132,7 @@ module ReactiveRecord
|
|
119
132
|
# private
|
120
133
|
|
121
134
|
attr_reader :record
|
135
|
+
attr_reader :updated_at
|
122
136
|
|
123
137
|
def self.open_channels
|
124
138
|
@open_channels ||= Set.new
|
@@ -128,7 +142,7 @@ module ReactiveRecord
|
|
128
142
|
@in_transit ||= Hash.new { |h, k| h[k] = new(k) }
|
129
143
|
end
|
130
144
|
|
131
|
-
def initialize(id)
|
145
|
+
def initialize(id = nil)
|
132
146
|
@id = id
|
133
147
|
@received = Set.new
|
134
148
|
@record = {}
|
@@ -137,19 +151,15 @@ module ReactiveRecord
|
|
137
151
|
|
138
152
|
def local(operation, record, data)
|
139
153
|
@destroyed = operation == :destroy
|
154
|
+
@is_local = true
|
140
155
|
@is_new = operation == :create
|
141
156
|
@klass = record.class.name
|
142
157
|
@record = data
|
143
158
|
record.backing_record.destroyed = false
|
144
|
-
@record[
|
159
|
+
@record[record.primary_key] = record.id if record.id
|
145
160
|
record.backing_record.destroyed = @destroyed
|
146
161
|
@backing_record = record.backing_record
|
147
162
|
@previous_changes = record.changes
|
148
|
-
# attributes = record.attributes
|
149
|
-
# data.each do |k, v|
|
150
|
-
# next if klass.reflect_on_association(k) || attributes[k] == v
|
151
|
-
# @previous_changes[k] = [attributes[k], v]
|
152
|
-
# end
|
153
163
|
self
|
154
164
|
end
|
155
165
|
|
@@ -160,10 +170,35 @@ module ReactiveRecord
|
|
160
170
|
@klass ||= params.klass
|
161
171
|
@record.merge! params.record
|
162
172
|
@previous_changes.merge! params.previous_changes
|
173
|
+
@updated_at = params.updated_at
|
163
174
|
ReactiveRecord::Base.when_not_saving(klass) do
|
164
|
-
@backing_record = ReactiveRecord::Base.exists?(klass, params.record[
|
165
|
-
|
166
|
-
|
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
|
167
202
|
end
|
168
203
|
end
|
169
204
|
|
@@ -208,7 +243,7 @@ module ReactiveRecord
|
|
208
243
|
|
209
244
|
def merge_current_values(br)
|
210
245
|
current_values = Hash[*@previous_changes.collect do |attr, values|
|
211
|
-
value = attr ==
|
246
|
+
value = attr == klass.primary_key ? record[klass.primary_key] : values.first
|
212
247
|
if br.attributes.key?(attr) &&
|
213
248
|
br.attributes[attr] != br.convert(attr, value) &&
|
214
249
|
br.attributes[attr] != br.convert(attr, values.last)
|
@@ -219,7 +254,6 @@ module ReactiveRecord
|
|
219
254
|
end
|
220
255
|
[attr, value]
|
221
256
|
end.compact.flatten(1)]
|
222
|
-
# TODO: verify - it used to be current_values.merge(br.attributes)
|
223
257
|
klass._react_param_conversion(br.attributes.merge(current_values))
|
224
258
|
end
|
225
259
|
end
|