reactive-record 0.7.1 → 0.7.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 843b69d8493a801694376fe0fe8d2757bf277059
4
- data.tar.gz: 1db4525dedaefe2c5a8a7a1459ad98f9416743d6
3
+ metadata.gz: 7340611c4d6c163c466a2a69ae056e7f03ec5cc8
4
+ data.tar.gz: 12a5dd997cec60c8b9226b3877156ec07a4a0ffa
5
5
  SHA512:
6
- metadata.gz: dc2ef1e11a2687ae53571e8c72c96c89c7164213d6d8477a4fb1413ebde429c83c95ac31d8df71f8bf0668e53029fde1e5209ac28b7a27e6f7f36795942e7f4f
7
- data.tar.gz: 4c66f3c3cc35bdee46bce7f2bb7b1b89829f5ae548546cb642a1759d8d5156e01b73f6fb174c7897e3ce877770f9d6575e63b47f40ae29f7676a64330961d45a
6
+ metadata.gz: 007ed1a1b5f3ed796537f52e2bde0e28869b29887c77e6881e34978bdd1d7e72375b696e0d761b28a8f5825df5ad0ac941ce09a0e04afc5e0609b6fff46b90a9
7
+ data.tar.gz: fdf1e5c4faf0eba8f5b85a44209bedac7802e0ce17f9b71a3ecf21b7192177dd95295f254c0038a3e304b5c1c868de68ccfb39a378d2bc234baebf5649b8fe7d
@@ -66,7 +66,7 @@ module ActiveRecord
66
66
  end
67
67
 
68
68
  def all
69
- ReactiveRecord::Collection.new(self)
69
+ ReactiveRecord::Collection.new(self, nil, nil, self, "all")
70
70
  end
71
71
 
72
72
  [:belongs_to, :has_many, :has_one].each do |macro|
@@ -49,15 +49,16 @@ module ReactiveRecord
49
49
  @data_loading = false
50
50
  end
51
51
 
52
- def self.find(model, attribute, value)
53
-
52
+ def self.find(model, attribute, value)
54
53
  # will return the unique record with this attribute-value pair
55
54
  # value cannot be an association or aggregation
56
55
 
57
56
  model = model.base_class
58
57
  # already have a record with this attribute-value pair?
59
58
  record = @records[model].detect { |record| record.attributes[attribute] == value}
60
-
59
+ if !record and attribute == 'id' and !@disabled_debugging
60
+ # `debugger`
61
+ end
61
62
  unless record
62
63
  # if not, and then the record may be loaded, but not have this attribute set yet,
63
64
  # so find the id of of record with the attribute-value pair, and see if that is loaded.
@@ -95,14 +96,14 @@ module ReactiveRecord
95
96
 
96
97
  def initialize(model, hash = {}, ar_instance = nil)
97
98
  @model = model
98
- @attributes = hash
99
99
  @synced_attributes = {}
100
+ @attributes = hash
100
101
  @ar_instance = ar_instance
101
102
  records[model] << self
102
103
  end
103
104
 
104
105
  def find(*args)
105
- self.find(*args)
106
+ self.class.find(*args)
106
107
  end
107
108
 
108
109
  def new_from_vector(*args)
@@ -142,7 +143,7 @@ module ReactiveRecord
142
143
  end
143
144
 
144
145
  def reactive_set!(attribute, value)
145
- unless @destroyed or attributes[attribute] == value
146
+ unless @destroyed or (!(attributes[attribute].is_a? DummyValue) and attributes.has_key?(attribute) and attributes[attribute] == value)
146
147
  if association = @model.reflect_on_association(attribute)
147
148
  if association.collection?
148
149
  collection = Collection.new(association.klass, @ar_instance, association)
@@ -153,7 +154,7 @@ module ReactiveRecord
153
154
  inverse_association = association.klass.reflect_on_association(inverse_of)
154
155
  if inverse_association.collection?
155
156
  if !value
156
- attributes[attribute].attributes[inverse_of].delete(@ar_instance)
157
+ attributes[attribute].attributes[inverse_of].delete(@ar_instance) if attributes[attribute]
157
158
  elsif value.attributes[inverse_of]
158
159
  value.attributes[inverse_of] << @ar_instance
159
160
  else
@@ -176,14 +177,23 @@ module ReactiveRecord
176
177
  end
177
178
 
178
179
  def changed?(*args)
179
- if args.count == 0
180
+ attrs = if args.count == 0
180
181
  React::State.get_state(self, self)
181
- @attributes != @synced_attributes
182
+ @attributes.dup
182
183
  else
183
- key = args[0]
184
- React::State.get_state(@attributes, key)
185
- @attributes.has_key?(key) != @synced_attributes.has_key?(key) or @attributes[key] != @synced_attributes[key]
184
+ React::State.get_state(@attributes, args[0])
185
+ {args[0] => @attributes[args[0]]}
186
+ end
187
+ attrs.each do |attribute, value|
188
+ if association = @model.reflect_on_association(attribute) and association.collection? and value
189
+ return true unless value == @synced_attributes[attribute]
190
+ elsif !@synced_attributes.has_key?(attribute)
191
+ return true
192
+ elsif @synced_attributes[attribute] != value
193
+ return true
194
+ end
186
195
  end
196
+ false
187
197
  end
188
198
 
189
199
  def sync!(hash = {})
@@ -219,18 +229,14 @@ module ReactiveRecord
219
229
  end
220
230
 
221
231
  def find_association(association, id)
222
-
223
232
  inverse_of = association.inverse_of
224
-
225
233
  instance = if id
226
234
  find(association.klass, association.klass.primary_key, id)
227
235
  else
228
236
  new_from_vector(association.klass, nil, *vector, association.attribute)
229
237
  end
230
-
231
238
  instance_backing_record_attributes = instance.instance_variable_get(:@backing_record).attributes
232
239
  inverse_association = association.klass.reflect_on_association(inverse_of)
233
-
234
240
  if inverse_association.collection?
235
241
  instance_backing_record_attributes[inverse_of] = if id and id != ""
236
242
  Collection.new(@model, instance, inverse_association, association.klass, ["find", id], inverse_of)
@@ -240,7 +246,7 @@ module ReactiveRecord
240
246
  instance_backing_record_attributes[inverse_of].replace [@ar_instance]
241
247
  else
242
248
  instance_backing_record_attributes[inverse_of] = @ar_instance
243
- end if inverse_of
249
+ end if inverse_of and !instance_backing_record_attributes.has_key?(inverse_of)
244
250
  instance
245
251
  end
246
252
 
@@ -258,7 +264,7 @@ module ReactiveRecord
258
264
  elsif aggregation = @model.reflect_on_aggregation(method)
259
265
  new_from_vector(aggregation.klass, self, *vector, method)
260
266
  elsif id and id != ""
261
- self.class.fetch_from_db([@model, [:find, id], method]) || self.class.load_from_db(*vector, method)
267
+ value = self.class.fetch_from_db([@model, [:find, id], method]) || self.class.load_from_db(*vector, method)
262
268
  else # its a attribute in an aggregate or we are on the client and don't know the id
263
269
  self.class.fetch_from_db([*vector, method]) || self.class.load_from_db(*vector, method)
264
270
  end
@@ -276,7 +282,7 @@ module ReactiveRecord
276
282
  type = hash[klass.inheritance_column]
277
283
  begin
278
284
  return Object.const_get(type)
279
- rescue Exeception => e
285
+ rescue Exception => e
280
286
  message = "Could not subclass #{@model_klass.model_name} as #{type}. Perhaps #{type} class has not been required. Exception: #{e}"
281
287
  `console.error(#{message})`
282
288
  end if type
@@ -1,7 +1,7 @@
1
1
  module ReactiveRecord
2
-
2
+
3
3
  class Collection
4
-
4
+
5
5
  def initialize(target_klass, owner = nil, association = nil, *vector)
6
6
  if association and (association.macro != :has_many or association.klass != target_klass)
7
7
  message = "unimplemented association #{owner} :#{association.macro} #{association.attribute}"
@@ -10,8 +10,8 @@ module ReactiveRecord
10
10
  @owner = owner # can be nil if this is an outer most scope
11
11
  @association = association
12
12
  @target_klass = target_klass
13
- if owner and !owner.id and !owner.vector
14
- @synced_collection = @collection = []
13
+ if owner and !owner.id and vector.length <= 1
14
+ @collection = []
15
15
  else
16
16
  @vector = vector.count == 0 ? [target_klass] : vector
17
17
  end
@@ -19,82 +19,117 @@ module ReactiveRecord
19
19
  end
20
20
 
21
21
  def all
22
+ @dummy_collection.notify if @dummy_collection
22
23
  unless @collection
23
24
  @collection = []
24
- if ids = ReactiveRecord::Base.fetch_from_db([*@vector, "*all"])
25
- ids.each do |id|
25
+ if ids = ReactiveRecord::Base.fetch_from_db([*@vector, "*all"])
26
+ ids.each do |id|
26
27
  @collection << @target_klass.find_by(@target_klass.primary_key => id)
27
28
  end
28
29
  else
29
- ReactiveRecord::Base.load_from_db(*@vector, "*all")
30
- @collection << ReactiveRecord::Base.new_from_vector(@target_klass, nil, *@vector, "*")
30
+ @dummy_collection = ReactiveRecord::Base.load_from_db(*@vector, "*all")
31
+ @dummy_record = ReactiveRecord::Base.new_from_vector(@target_klass, nil, *@vector, "*")
32
+ @dummy_record.instance_variable_get(:@backing_record).attributes[@association.inverse_of] = @owner if @association and @association.inverse_of
33
+ @collection << @dummy_record
31
34
  end
32
35
  end
33
36
  @collection
34
37
  end
35
-
36
-
38
+
37
39
  def ==(other_collection)
38
- if @collection
39
- @collection == other_collection.all
40
- else
41
- !other_collection.instance_variable_get(:@collection)
42
- end
40
+ my_collection = (@collection || []).select { |target| target != @dummy_record }
41
+ other_collection = (other_collection ? (other_collection.collection || []) : []).select { |target| target != other_collection.dummy_record }
42
+ my_collection == other_collection
43
43
  end
44
-
45
- def apply_scope(scope)
44
+
45
+ def apply_scope(scope, *args)
46
46
  # The value returned is another ReactiveRecordCollection with the scope added to the vector
47
47
  # no additional action is taken
48
- @scopes[scope] ||= new(@target_klass, @owner, @association, *vector, scope)
48
+ scope = [scope, *args] if args.count > 0
49
+ @scopes[scope] ||= Collection.new(@target_klass, @owner, @association, *@vector, [scope])
49
50
  end
50
51
 
51
52
  def proxy_association
52
- @association
53
+ @association || self # returning self allows this to work with things like Model.all
53
54
  end
54
55
 
56
+ def klass
57
+ @target_klass
58
+ end
59
+
55
60
 
56
61
  def <<(item)
57
- inverse_of = @association.inverse_of
58
- if @owner and inverse_of = @association.inverse_of
62
+ if @owner and @association and inverse_of = @association.inverse_of
59
63
  item.attributes[inverse_of].attributes[@association.attribute].delete(item) if item.attributes[inverse_of] and item.attributes[inverse_of].attributes[@association.attribute]
60
- item.attributes[inverse_of] = @owner
64
+ item.attributes[inverse_of] = @owner
61
65
  backing_record = item.instance_variable_get(:@backing_record)
62
66
  React::State.set_state(backing_record, inverse_of, @owner) unless backing_record.data_loading?
63
67
  end
64
68
  all << item unless all.include? item
69
+ @collection.delete(@dummy_record)
70
+ @dummy_record = @dummy_collection = nil
65
71
  self
66
72
  end
67
73
 
74
+ [:first, :last].each do |method|
75
+ define_method method do |*args|
76
+ if args.count == 0
77
+ all.send(method)
78
+ else
79
+ apply_scope(method, *args)
80
+ end
81
+ end
82
+ end
83
+
68
84
  def replace(new_array)
69
- return new_array if @collection == new_array
70
- if @collection
71
- @collection.dup.each { |item| delete(item) }
85
+ #return new_array if @collection == new_array #not sure we need this anymore
86
+ @dummy_collection.notify if @dummy_collection
87
+ @collection.dup.each { |item| delete(item) } if @collection
88
+ @collection = []
89
+ if new_array.is_a? Collection
90
+ @dummy_collection = new_array.dummy_collection
91
+ @dummy_record = new_array.dummy_record
92
+ new_array.collection.each { |item| self << item } if new_array.collection
72
93
  else
73
- @collection = []
94
+ @dummy_collection = @dummy_record = nil
95
+ new_array.each { |item| self << item }
74
96
  end
75
- new_array.each { |item| self << item }
76
97
  new_array
77
98
  end
78
-
99
+
79
100
  def delete(item)
80
- if @owner and inverse_of = @association.inverse_of
101
+ if @owner and @association and inverse_of = @association.inverse_of
81
102
  item.attributes[inverse_of] = nil
82
103
  backing_record = item.instance_variable_get(:@backing_record)
83
104
  React::State.set_state(backing_record, inverse_of, nil) unless backing_record.data_loading?
84
105
  end
85
106
  all.delete(item)
86
107
  end
87
-
108
+
88
109
  def method_missing(method, *args, &block)
89
110
  if [].respond_to? method
90
111
  all.send(method, *args, &block)
91
112
  elsif @target_klass.respond_to? method
92
- apply_scope(method)
113
+ apply_scope(method, *args)
93
114
  else
94
115
  super
95
116
  end
96
117
  end
97
118
 
119
+ protected
120
+
121
+ def dummy_record
122
+ @dummy_record
123
+ end
124
+
125
+ def collection
126
+ @collection
127
+ end
128
+
129
+ def dummy_collection
130
+ @dummy_collection
131
+ end
132
+
98
133
  end
99
-
100
- end
134
+
135
+ end
@@ -14,9 +14,12 @@ module ReactiveRecord
14
14
  if on_opal_client?
15
15
  @pending_fetches = []
16
16
  @last_fetch_at = Time.now
17
- JSON.from_object(`window.ReactiveRecordInitialData`).each do |hash|
18
- load_from_json hash
19
- end unless `typeof window.ReactiveRecordInitialData === 'undefined'`
17
+ unless `typeof window.ReactiveRecordInitialData === 'undefined'`
18
+ log(["Reactive record prerendered data being loaded: %o", `window.ReactiveRecordInitialData`])
19
+ JSON.from_object(`window.ReactiveRecordInitialData`).each do |hash|
20
+ load_from_json hash
21
+ end
22
+ end
20
23
  end
21
24
  end
22
25
  end
@@ -40,8 +43,12 @@ module ReactiveRecord
40
43
  end
41
44
 
42
45
  prerender_footer do
43
- json = @server_data_cache.as_json.to_json # can this just be to_json?
44
- @server_data_cache.clear_requests
46
+ if @server_data_cache
47
+ json = @server_data_cache.as_json.to_json # can this just be to_json?
48
+ @server_data_cache.clear_requests
49
+ else
50
+ json = {}.to_json
51
+ end
45
52
  path = ::Rails.application.routes.routes.detect do |route|
46
53
  # not sure why the second check is needed. It happens in the test app
47
54
  route.app == ReactiveRecord::Engine or (route.app.respond_to?(:app) and route.app.app == ReactiveRecord::Engine)
@@ -54,33 +61,104 @@ module ReactiveRecord
54
61
  end if RUBY_ENGINE != 'opal'
55
62
 
56
63
  # Client side db access (never called during prerendering):
64
+
65
+ # Always returns an object of class DummyValue which will act like most standard AR field types
66
+ # Whenever a dummy value is accessed it notify React that there are loads pending so appropriate rerenders
67
+ # will occur when the value is eventually loaded.
68
+
57
69
  # queue up fetches, and at the end of each rendering cycle fetch the records
58
70
  # notify that loads are pending
59
71
 
60
72
  def self.load_from_db(*vector)
73
+ return nil unless on_opal_client? # this can happen when we are on the server and a nil value is returned for an attribute
61
74
  # only called from the client side
62
75
  # pushes the value of vector onto the a list of vectors that will be loaded from the server when the next
63
76
  # rendering cycle completes.
64
77
  # takes care of informing react that there are things to load, and schedules the loader to run
65
78
  # Note there is no equivilent to find_in_db, because each vector implicitly does a find.
66
- return "" if data_loading?
67
- ReactiveRecord.loads_pending!
68
- ReactiveRecord::WhileLoading.loading! # inform react that the current value is bogus
69
- @pending_fetches << vector
70
- schedule_fetch
71
- ""
79
+ unless data_loading?
80
+ @pending_fetches << vector
81
+ schedule_fetch
82
+ end
83
+ DummyValue.new
84
+ end
85
+
86
+ class DummyValue < NilClass
87
+
88
+ def notify
89
+ unless ReactiveRecord::Base.data_loading?
90
+ ReactiveRecord.loads_pending!
91
+ ReactiveRecord::WhileLoading.loading!
92
+ end
93
+ end
94
+
95
+ def initialize()
96
+ notify
97
+ end
98
+
99
+ def method_missing(method, *args, &block)
100
+ if 0.respond_to? method
101
+ notify
102
+ 0.send(method, *args, &block)
103
+ elsif "".respond_to? method
104
+ notify
105
+ "".send(method, *args, &block)
106
+ else
107
+ super
108
+ end
109
+ end
110
+
111
+ def coerce(s)
112
+ [self.send("to_#{s.class.name.downcase}"), s]
113
+ end
114
+
115
+ def ==(other_value)
116
+ other_value.is_a? DummyValue
117
+ end
118
+
119
+ def to_s
120
+ notify
121
+ ""
122
+ end
123
+
124
+ def to_f
125
+ notify
126
+ 0.0
127
+ end
128
+
129
+ def to_i
130
+ notify
131
+ 0
132
+ end
133
+
134
+ def to_numeric
135
+ notify
136
+ 0
137
+ end
138
+
139
+ def to_date
140
+ "2001-01-01T00:00:00.000-00:00".to_date
141
+ end
142
+
143
+ def acts_as_string?
144
+ true
145
+ end
146
+
72
147
  end
148
+
73
149
 
74
150
  def self.schedule_fetch
75
151
  @fetch_scheduled ||= after(0.001) do
76
152
  last_fetch_at = @last_fetch_at
77
- HTTP.post(`window.ReactiveRecordEnginePath`, payload: {pending_fetches: @pending_fetches.uniq}).then do |response|
153
+ pending_fetches = @pending_fetches.uniq
154
+ log(["Server Fetching: %o", pending_fetches.to_n])
155
+ HTTP.post(`window.ReactiveRecordEnginePath`, payload: {pending_fetches: pending_fetches}).then do |response|
78
156
  begin
79
157
  ReactiveRecord::Base.load_from_json(response.json)
80
158
  rescue Exception => e
81
- message = "Exception raised while loading json from server: #{e}"
82
- `console.error(#{message})`
159
+ log("Exception raised while loading json from server: #{e}", :error)
83
160
  end
161
+ log([" Returned: %o", response.json.to_n])
84
162
  ReactiveRecord.run_blocks_to_load
85
163
  ReactiveRecord::WhileLoading.loaded_at last_fetch_at
86
164
  end if @pending_fetches.count > 0
@@ -236,7 +314,7 @@ module ReactiveRecord
236
314
  @ar_instance.send("#{association.attribute}=", nil)
237
315
  end
238
316
  end
239
-
317
+
240
318
  promise = Promise.new
241
319
 
242
320
  if id or vector
@@ -169,20 +169,24 @@ module ReactiveRecord
169
169
  end
170
170
 
171
171
  def apply_method(method)
172
+ method[0] = "find" if method.is_a? Array and method.first == "find_by_id"
172
173
  new_vector = vector + [method]
173
174
  @db_cache.detect { |cached_item| cached_item.vector == new_vector} || build_new_instances(method)
174
175
  end
175
176
 
176
177
  def build_new_instances(method)
177
- if method == "*all"
178
- apply_method_to_cache(method) { |cache_item| cache_item.value.collect { |record| record.id }}
179
- elsif method == "*" and @ar_object and @ar_object.length > 0
180
- @ar_object.inject(nil) do | value, record | # just using inject so we will return the last value
181
- apply_method_to_cache(method) { record }
178
+ if method == "*all"
179
+ apply_method_to_cache("*all") { |cache_item| cache_item.value.collect { |record| record.id } }
180
+ elsif method == "*"
181
+ if @ar_object and @ar_object.length > 0
182
+ @ar_object.inject(nil) do | value, record | # just using inject so we will return the last value
183
+ apply_method_to_cache(method) { record }
184
+ end
185
+ else
186
+ apply_method_to_cache(method) {[]}
182
187
  end
183
188
  elsif @ar_object.respond_to? [*method].first
184
- apply_method_to_cache(method) { |cache_item|
185
- cache_item.value.send(*method)}
189
+ apply_method_to_cache(method) { |cache_item| cache_item.value.send(*method) rescue nil } # rescue in case we are on a nil association
186
190
  else
187
191
  self
188
192
  end
@@ -212,6 +216,7 @@ module ReactiveRecord
212
216
  end
213
217
 
214
218
  def self.load_from_json(tree, target = nil)
219
+ tree.delete("*all") if tree["*"]
215
220
  tree.each do |method, value|
216
221
  method = JSON.parse(method) rescue method
217
222
  new_target = nil
@@ -220,7 +225,7 @@ module ReactiveRecord
220
225
  elsif method == "*all"
221
226
  target.replace value.collect { |id| target.proxy_association.klass.find(id) }
222
227
  elsif method.is_a? Integer or method =~ /^[0-9]+$/
223
- new_target = target.proxy_association.klass.find(method)
228
+ new_target = target.proxy_association.klass.find(method)
224
229
  target << new_target
225
230
  elsif method.is_a? Array
226
231
  new_target = target.send *method
@@ -231,12 +236,7 @@ module ReactiveRecord
231
236
  target.send "#{method}=", new_target
232
237
  else
233
238
  new_target = target.send *method
234
- begin
235
- new_target = target.send "#{method}=", new_target
236
- rescue Exception => e
237
- message = "FAILED #{target}.#{method} not set to #{new_target}"
238
- `console.error(message)`
239
- end
239
+ (new_target = target.send "#{method}=", new_target) rescue nil # this can happen for example if you say TodoItems.all
240
240
  end
241
241
  load_from_json(value, new_target) if new_target
242
242
  end
@@ -1,3 +1,3 @@
1
1
  module ReactiveRecord
2
- VERSION = "0.7.1"
2
+ VERSION = "0.7.4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reactive-record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mitch VanDuyn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-12 00:00:00.000000000 Z
11
+ date: 2015-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails