hyper-model 1.0.alpha1.3 → 1.0.alpha1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.rspec +0 -1
- data/Gemfile +6 -5
- data/Rakefile +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
|