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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.rspec +0 -1
  4. data/Gemfile +6 -5
  5. data/Rakefile +18 -6
  6. data/hyper-model.gemspec +12 -20
  7. data/lib/active_record_base.rb +95 -28
  8. data/lib/enumerable/pluck.rb +3 -2
  9. data/lib/hyper-model.rb +4 -1
  10. data/lib/hyper_model/version.rb +1 -1
  11. data/lib/hyper_react/input_tags.rb +2 -1
  12. data/lib/reactive_record/active_record/associations.rb +125 -35
  13. data/lib/reactive_record/active_record/base.rb +32 -0
  14. data/lib/reactive_record/active_record/class_methods.rb +125 -53
  15. data/lib/reactive_record/active_record/error.rb +2 -0
  16. data/lib/reactive_record/active_record/errors.rb +8 -4
  17. data/lib/reactive_record/active_record/instance_methods.rb +73 -5
  18. data/lib/reactive_record/active_record/public_columns_hash.rb +25 -26
  19. data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +22 -5
  20. data/lib/reactive_record/active_record/reactive_record/base.rb +50 -24
  21. data/lib/reactive_record/active_record/reactive_record/collection.rb +196 -63
  22. data/lib/reactive_record/active_record/reactive_record/dummy_polymorph.rb +22 -0
  23. data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +27 -15
  24. data/lib/reactive_record/active_record/reactive_record/getters.rb +33 -10
  25. data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +71 -44
  26. data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +5 -5
  27. data/lib/reactive_record/active_record/reactive_record/operations.rb +7 -1
  28. data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +3 -6
  29. data/lib/reactive_record/active_record/reactive_record/setters.rb +105 -68
  30. data/lib/reactive_record/active_record/reactive_record/while_loading.rb +22 -1
  31. data/lib/reactive_record/broadcast.rb +59 -25
  32. data/lib/reactive_record/interval.rb +3 -3
  33. data/lib/reactive_record/permissions.rb +1 -1
  34. data/lib/reactive_record/scope_description.rb +3 -2
  35. data/lib/reactive_record/server_data_cache.rb +78 -48
  36. data/polymorph-notes.md +143 -0
  37. metadata +52 -157
  38. 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(*args) # model and id
37
- `#{@records_by_id}[#{args}]` || nil
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
- puts "#{self}.update_collector_scope calling replace(filter_records(#{current}))"
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
- if assoc.inverse.collection?
47
- update_has_many_through_associations assoc, value
48
- update_inverse_collections assoc, value
49
- else
50
- update_inverse_attribute assoc, value
51
- end
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
- def update_inverse_attribute(association, value)
129
- # when updating the inverse attribute of a belongs_to that is itself a belongs_to
130
- # (i.e. 1-1 relationship) we clear the existing inverse value and then
131
- # write the current record to the new value
132
- current_value = @attributes[association.attribute]
133
- inverse_attr = association.inverse.attribute
134
- current_value.attributes[inverse_attr] = nil unless current_value.nil?
135
- return if value.nil?
136
- value.attributes[inverse_attr] = @ar_instance
137
- return if data_loading?
138
- Hyperstack::Internal::State::Variable.set(value.backing_record, inverse_attr, @ar_instance)
139
- end
140
-
141
- def update_inverse_collections(association, value)
142
- # when updating an inverse attribute of a belongs_to that is a has_many (i.e. a collection)
143
- # we need to first remove the current associated value (if non-nil), then add the new
144
- # value to the collection. If the inverse collection is not yet initialized we do it here.
145
- current_value = @attributes[association.attribute]
146
- inverse_attr = association.inverse.attribute
147
- if value.nil?
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
- value.backing_record.push_onto_collection(@model, association.inverse, @ar_instance)
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] << ar_instance
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 update_has_many_through_associations(association, value)
160
- association.through_associations.each { |ta| update_through_association(ta, value) }
161
- association.source_associations.each { |sa| update_source_association(sa, value) }
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
- SendPacket.remote(
24
- Hyperstack::Connection.root_path,
25
- data,
26
- operation: operation,
27
- salt: salt,
28
- authorization: authorization
29
- ).tap { |p| raise p.error if p.rejected? }
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.new?
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[:id]).backing_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[:id] = record.id if record.id
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[:id])
165
- @is_new = params.operation == :create && !@backing_record
166
- yield complete! if @channels == @received
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 == :id ? record[:id] : values.first
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