reactive-record 0.7.1 → 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
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