reactive-record 0.7.20 → 0.7.22
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/lib/reactive-record.rb +10 -10
- data/lib/reactive_record/active_record/class_methods.rb +1 -1
- data/lib/reactive_record/active_record/instance_methods.rb +4 -0
- data/lib/reactive_record/active_record/reactive_record/base.rb +7 -2
- data/lib/reactive_record/active_record/reactive_record/collection.rb +52 -16
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +82 -50
- data/lib/reactive_record/server_data_cache.rb +20 -12
- data/lib/reactive_record/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ac17cb9431cfb70da2b36ad364d6f6daddabfad
|
4
|
+
data.tar.gz: 1ce72b88c991bcd5bd7b2b643a64c10b0da1173b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f030a78f2bfb6be443e26f00a3782f33730abbfea6fcdf9897fde842436e14f113cd7c2d152ed2280536ef245e67ec91629c97080f1fbb4e6a882b8a00baabc
|
7
|
+
data.tar.gz: 8ced444a4e20aeabba7536f3acddfee6aad92c195f9ffbee484955e53ba0714b08b8f6b53c35d71f28694cc32a65d49d6076581b283fc9fd2fcd68f385d754f6
|
data/lib/reactive-record.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
if RUBY_ENGINE == 'opal'
|
2
|
-
|
2
|
+
|
3
3
|
require "reactive-ruby"
|
4
4
|
require "reactive_record/active_record/error"
|
5
5
|
require "reactive_record/server_data_cache"
|
@@ -13,30 +13,30 @@ if RUBY_ENGINE == 'opal'
|
|
13
13
|
require "reactive_record/active_record/instance_methods"
|
14
14
|
require "reactive_record/active_record/base"
|
15
15
|
require "reactive_record/interval"
|
16
|
-
|
16
|
+
|
17
17
|
else
|
18
|
-
|
18
|
+
|
19
19
|
module ::ActiveRecord
|
20
20
|
module Core
|
21
21
|
module ClassMethods
|
22
|
-
def inherited(child_class)
|
22
|
+
def inherited(child_class)
|
23
23
|
begin
|
24
24
|
file = Rails.root.join('app','models',"#{child_class.name.underscore}.rb").to_s rescue nil
|
25
|
-
begin
|
26
|
-
require file
|
25
|
+
begin
|
26
|
+
require file
|
27
27
|
rescue LoadError
|
28
28
|
end
|
29
29
|
# from active record:
|
30
30
|
child_class.initialize_find_by_cache
|
31
|
-
rescue
|
31
|
+
rescue
|
32
32
|
end
|
33
33
|
super
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
39
|
-
|
38
|
+
|
39
|
+
|
40
40
|
require "opal"
|
41
41
|
require "reactive_record/version"
|
42
42
|
require "reactive_record/permissions"
|
@@ -47,5 +47,5 @@ else
|
|
47
47
|
|
48
48
|
Opal.append_path File.expand_path('../', __FILE__).untaint
|
49
49
|
Opal.append_path File.expand_path('../../vendor', __FILE__).untaint
|
50
|
-
|
50
|
+
|
51
51
|
end
|
@@ -63,7 +63,7 @@ module ActiveRecord
|
|
63
63
|
|
64
64
|
def scope(name, body)
|
65
65
|
singleton_class.send(:define_method, name) do | *args |
|
66
|
-
args = args.count == 0 ? name : [name, *args]
|
66
|
+
args = (args.count == 0) ? name : [name, *args]
|
67
67
|
ReactiveRecord::Base.class_scopes(self)[name] ||= ReactiveRecord::Collection.new(self, nil, nil, self, args)
|
68
68
|
end
|
69
69
|
singleton_class.send(:define_method, "#{name}=") do |collection|
|
@@ -32,6 +32,7 @@ module ReactiveRecord
|
|
32
32
|
attr_accessor :changed_attributes
|
33
33
|
attr_accessor :aggregate_owner
|
34
34
|
attr_accessor :aggregate_attribute
|
35
|
+
attr_accessor :destroyed
|
35
36
|
|
36
37
|
# While data is being loaded from the server certain internal behaviors need to change
|
37
38
|
# for example records all record changes are synced as they happen.
|
@@ -156,7 +157,11 @@ module ReactiveRecord
|
|
156
157
|
|
157
158
|
def reactive_get!(attribute)
|
158
159
|
unless @destroyed
|
159
|
-
|
160
|
+
if @attributes.has_key? attribute
|
161
|
+
attributes[attribute].notify if @attributes[attribute].is_a? DummyValue
|
162
|
+
else
|
163
|
+
apply_method(attribute)
|
164
|
+
end
|
160
165
|
React::State.get_state(self, attribute) unless data_loading?
|
161
166
|
attributes[attribute]
|
162
167
|
end
|
@@ -261,7 +266,7 @@ module ReactiveRecord
|
|
261
266
|
@synced_attributes[key] = value.dup_for_sync
|
262
267
|
elsif aggregation = model.reflect_on_aggregation(key)
|
263
268
|
value.backing_record.sync!
|
264
|
-
|
269
|
+
elsif !model.reflect_on_association(key)
|
265
270
|
@synced_attributes[key] = JSON.parse(value.to_json)
|
266
271
|
end
|
267
272
|
end
|
@@ -36,14 +36,24 @@ module ReactiveRecord
|
|
36
36
|
end
|
37
37
|
else
|
38
38
|
@dummy_collection = ReactiveRecord::Base.load_from_db(*@vector, "*all")
|
39
|
-
@dummy_record =
|
40
|
-
@dummy_record.backing_record.attributes[@association.inverse_of] = @owner if @association and @association.inverse_of
|
41
|
-
@collection << @dummy_record
|
39
|
+
@dummy_record = self[0]
|
42
40
|
end
|
43
41
|
end
|
44
42
|
@collection
|
45
43
|
end
|
46
44
|
|
45
|
+
def [](index)
|
46
|
+
|
47
|
+
if (@collection || all).length <= index and @dummy_collection
|
48
|
+
(@collection.length..index).each do |i|
|
49
|
+
new_dummy_record = ReactiveRecord::Base.new_from_vector(@target_klass, nil, *@vector, "*#{i}")
|
50
|
+
new_dummy_record.backing_record.attributes[@association.inverse_of] = @owner if @association and @association.inverse_of
|
51
|
+
@collection << new_dummy_record
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@collection[index]
|
55
|
+
end
|
56
|
+
|
47
57
|
def ==(other_collection)
|
48
58
|
my_collection = (@collection || []).select { |target| target != @dummy_record }
|
49
59
|
other_collection = (other_collection ? (other_collection.collection || []) : []).select { |target| target != other_collection.dummy_record }
|
@@ -57,6 +67,19 @@ module ReactiveRecord
|
|
57
67
|
@scopes[scope] ||= Collection.new(@target_klass, @owner, @association, *@vector, [scope])
|
58
68
|
end
|
59
69
|
|
70
|
+
def count
|
71
|
+
if @collection
|
72
|
+
@collection.count
|
73
|
+
elsif @count ||= ReactiveRecord::Base.fetch_from_db([*@vector, "*count"])
|
74
|
+
@count
|
75
|
+
else
|
76
|
+
ReactiveRecord::Base.load_from_db(*@vector, "*count")
|
77
|
+
@count = 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
alias_method :length, :count
|
82
|
+
|
60
83
|
def proxy_association
|
61
84
|
@association || self # returning self allows this to work with things like Model.all
|
62
85
|
end
|
@@ -65,24 +88,21 @@ module ReactiveRecord
|
|
65
88
|
@target_klass
|
66
89
|
end
|
67
90
|
|
68
|
-
|
69
91
|
def <<(item)
|
70
92
|
backing_record = item.backing_record
|
71
|
-
#
|
72
|
-
# item.attributes[inverse_of].attributes[@association.attribute].delete(item) if item.attributes[inverse_of] and item.attributes[inverse_of].attributes[@association.attribute]
|
73
|
-
# item.attributes[inverse_of] = @owner
|
74
|
-
# React::State.set_state(backing_record, inverse_of, @owner) unless backing_record.data_loading?
|
75
|
-
# end
|
76
|
-
#all << item unless all.include? item
|
77
|
-
all << item unless all.include? item
|
93
|
+
all << item unless all.include? item # does this use == if so we are okay...
|
78
94
|
if backing_record and @owner and @association and inverse_of = @association.inverse_of and item.attributes[inverse_of] != @owner
|
79
95
|
current_association = item.attributes[inverse_of]
|
80
96
|
backing_record.update_attribute(inverse_of, @owner)
|
81
97
|
current_association.attributes[@association.attribute].delete(item) if current_association and current_association.attributes[@association.attribute]
|
82
98
|
@owner.backing_record.update_attribute(@association.attribute) # forces a check if association contents have changed from synced values
|
83
99
|
end
|
84
|
-
|
85
|
-
|
100
|
+
if item.id and @dummy_record
|
101
|
+
@dummy_record.id = item.id
|
102
|
+
@collection.delete(@dummy_record)
|
103
|
+
@dummy_record = @collection.detect { |r| r.backing_record.vector.last =~ /^\*[0-9]+$/ }
|
104
|
+
@dummy_collection = nil
|
105
|
+
end
|
86
106
|
self
|
87
107
|
end
|
88
108
|
|
@@ -97,9 +117,20 @@ module ReactiveRecord
|
|
97
117
|
end
|
98
118
|
|
99
119
|
def replace(new_array)
|
100
|
-
|
101
|
-
|
102
|
-
@
|
120
|
+
|
121
|
+
# not tested if you do all[n] where n > 0... this will create additional dummy items, that this will not sync up.
|
122
|
+
# probably just moving things around so the @dummy_collection and @dummy_record are updated AFTER the new items are pushed
|
123
|
+
# should work.
|
124
|
+
|
125
|
+
if @dummy_collection
|
126
|
+
@dummy_collection.notify
|
127
|
+
array = new_array.is_a?(Collection) ? new_array.collection : new_array
|
128
|
+
@collection.each_with_index do |r, i|
|
129
|
+
r.id = new_array[i].id if array[i] and array[i].id and r.backing_record.vector.last =~ /^\*[0-9]+$/
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
@collection.dup.each { |item| delete(item) } if @collection # this line is a big nop I think
|
103
134
|
@collection = []
|
104
135
|
if new_array.is_a? Collection
|
105
136
|
@dummy_collection = new_array.dummy_collection
|
@@ -124,6 +155,11 @@ module ReactiveRecord
|
|
124
155
|
end
|
125
156
|
end
|
126
157
|
|
158
|
+
def loading?
|
159
|
+
all # need to force initialization at this point
|
160
|
+
@dummy_collection.loading?
|
161
|
+
end
|
162
|
+
|
127
163
|
def method_missing(method, *args, &block)
|
128
164
|
if [].respond_to? method
|
129
165
|
all.send(method, *args, &block)
|
@@ -86,70 +86,95 @@ module ReactiveRecord
|
|
86
86
|
DummyValue.new
|
87
87
|
end
|
88
88
|
|
89
|
-
|
89
|
+
if RUBY_ENGINE == 'opal'
|
90
|
+
class ::Object
|
90
91
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
92
|
+
def loaded?
|
93
|
+
!loading?
|
94
|
+
end
|
95
|
+
|
96
|
+
def loading?
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def present?
|
101
|
+
!!self
|
95
102
|
end
|
96
|
-
end
|
97
103
|
|
98
|
-
def initialize()
|
99
|
-
notify
|
100
104
|
end
|
101
105
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
106
|
+
class DummyValue < NilClass
|
107
|
+
|
108
|
+
def notify
|
109
|
+
unless ReactiveRecord::Base.data_loading?
|
110
|
+
ReactiveRecord.loads_pending! #loads
|
111
|
+
ReactiveRecord::WhileLoading.loading! #loads
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def initialize()
|
107
116
|
notify
|
108
|
-
"".send(method, *args, &block)
|
109
|
-
else
|
110
|
-
super
|
111
117
|
end
|
112
|
-
end
|
113
118
|
|
114
|
-
|
115
|
-
|
116
|
-
|
119
|
+
def method_missing(method, *args, &block)
|
120
|
+
if 0.respond_to? method
|
121
|
+
notify
|
122
|
+
0.send(method, *args, &block)
|
123
|
+
elsif "".respond_to? method
|
124
|
+
notify
|
125
|
+
"".send(method, *args, &block)
|
126
|
+
else
|
127
|
+
super
|
128
|
+
end
|
129
|
+
end
|
117
130
|
|
118
|
-
|
119
|
-
|
120
|
-
|
131
|
+
def loading?
|
132
|
+
true
|
133
|
+
end
|
121
134
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
end
|
135
|
+
def present?
|
136
|
+
false
|
137
|
+
end
|
126
138
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
end
|
139
|
+
def coerce(s)
|
140
|
+
[self.send("to_#{s.class.name.downcase}"), s]
|
141
|
+
end
|
131
142
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
end
|
143
|
+
def ==(other_value)
|
144
|
+
other_value.object_id == self.object_id
|
145
|
+
end
|
136
146
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
147
|
+
def to_s
|
148
|
+
notify
|
149
|
+
""
|
150
|
+
end
|
141
151
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
152
|
+
def to_f
|
153
|
+
notify
|
154
|
+
0.0
|
155
|
+
end
|
146
156
|
|
147
|
-
|
148
|
-
|
149
|
-
|
157
|
+
def to_i
|
158
|
+
notify
|
159
|
+
0
|
160
|
+
end
|
150
161
|
|
151
|
-
|
162
|
+
def to_numeric
|
163
|
+
notify
|
164
|
+
0
|
165
|
+
end
|
152
166
|
|
167
|
+
def to_date
|
168
|
+
notify
|
169
|
+
"2001-01-01T00:00:00.000-00:00".to_date
|
170
|
+
end
|
171
|
+
|
172
|
+
def acts_as_string?
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
153
178
|
|
154
179
|
def self.schedule_fetch
|
155
180
|
#ReactiveRecord.loads_pending!
|
@@ -166,7 +191,7 @@ module ReactiveRecord
|
|
166
191
|
begin
|
167
192
|
ReactiveRecord::Base.load_from_json(response.json)
|
168
193
|
rescue Exception => e
|
169
|
-
log("
|
194
|
+
log("Unexpected exception raised while loading json from server: #{e}", :error)
|
170
195
|
end
|
171
196
|
log(" Processed in: #{(Time.now-fetch_time).to_i}s")
|
172
197
|
log([" Returned: %o", response.json.to_n])
|
@@ -339,6 +364,8 @@ module ReactiveRecord
|
|
339
364
|
puts "aggregate attributes after merge = #{aggregate.attributes}"
|
340
365
|
parent.send("#{association[:attribute]}=", aggregate)
|
341
366
|
puts "updated is frozen? #{aggregate.frozen?}, parent attributes = #{parent.send(association[:attribute]).attributes}"
|
367
|
+
elsif parent.class.reflect_on_association(association[:attribute].to_sym).nil?
|
368
|
+
raise "Missing association :#{association[:attribute]} for #{parent.class.name}. Was association defined on opal side only?"
|
342
369
|
elsif parent.class.reflect_on_association(association[:attribute].to_sym).collection?
|
343
370
|
puts ">>>>>>>>>> #{parent.class.name}.send('#{association[:attribute]}') << #{reactive_records[association[:child_id]]})"
|
344
371
|
#parent.send("#{association[:attribute]}") << reactive_records[association[:child_id]]
|
@@ -417,8 +444,13 @@ module ReactiveRecord
|
|
417
444
|
promise.resolve({success: true})
|
418
445
|
end
|
419
446
|
|
420
|
-
|
421
|
-
|
447
|
+
# DO NOT CLEAR ATTRIBUTES. Records that are not found, are destroyed, and if they are searched for again, we want to make
|
448
|
+
# sure to find them. We may want to change this, and provide a separate flag called not_found. In this case you
|
449
|
+
# would put these lines here:
|
450
|
+
# @attributes = {}
|
451
|
+
# sync!
|
452
|
+
# and modify server_data_cache so that it does NOT call destroy
|
453
|
+
|
422
454
|
@destroyed = true
|
423
455
|
|
424
456
|
promise
|
@@ -188,7 +188,11 @@ module ReactiveRecord
|
|
188
188
|
end
|
189
189
|
|
190
190
|
def apply_method(method)
|
191
|
-
|
191
|
+
if method.is_a? Array and method.first == "find_by_id"
|
192
|
+
method[0] = "find"
|
193
|
+
elsif method.is_a? String and method =~ /^\*[0-9]+$/
|
194
|
+
method = "*"
|
195
|
+
end
|
192
196
|
new_vector = vector + [method]
|
193
197
|
@db_cache.detect { |cached_item| cached_item.vector == new_vector} || build_new_instances(method)
|
194
198
|
end
|
@@ -196,6 +200,8 @@ module ReactiveRecord
|
|
196
200
|
def build_new_instances(method)
|
197
201
|
if method == "*all"
|
198
202
|
apply_method_to_cache("*all") { |cache_item| cache_item.value.collect { |record| record.id } }
|
203
|
+
elsif method == "*count"
|
204
|
+
apply_method_to_cache("*count") { |cache_item| cache_item.value.count }
|
199
205
|
elsif method == "*"
|
200
206
|
if @ar_object and @ar_object.length > 0
|
201
207
|
@ar_object.inject(nil) do | value, record | # just using inject so we will return the last value
|
@@ -251,36 +257,38 @@ module ReactiveRecord
|
|
251
257
|
ignore_all = nil
|
252
258
|
|
253
259
|
# have to process *all before any other items
|
254
|
-
|
260
|
+
# we leave the "*all" key in just for debugging purposes, and then skip it below
|
261
|
+
|
262
|
+
if sorted_collection = tree["*all"]
|
255
263
|
target.replace sorted_collection.collect { |id| target.proxy_association.klass.find(id) }
|
256
264
|
end
|
257
265
|
|
258
266
|
tree.each do |method, value|
|
259
267
|
method = JSON.parse(method) rescue method
|
260
268
|
new_target = nil
|
261
|
-
if
|
269
|
+
if method == "*all"
|
270
|
+
next # its already been processed above
|
271
|
+
elsif !target
|
262
272
|
load_from_json(value, Object.const_get(method))
|
263
|
-
elsif method == "*
|
264
|
-
target.
|
273
|
+
elsif method == "*count"
|
274
|
+
target.instance_variable_set(:@count, value.first)
|
265
275
|
elsif method.is_a? Integer or method =~ /^[0-9]+$/
|
266
|
-
ignore_all = true
|
267
276
|
target << (new_target = target.proxy_association.klass.find(method))
|
268
277
|
elsif method.is_a? Array
|
269
278
|
if !(target.class < ActiveRecord::Base)
|
270
|
-
new_target = target.send *method
|
279
|
+
new_target = target.send *method
|
280
|
+
# value is an array if scope returns nil, so we destroy the bogus record
|
281
|
+
new_target.destroy and new_target = nil if value.is_a? Array
|
271
282
|
else
|
272
283
|
target.attributes[[method]] = value.first
|
273
284
|
end
|
274
285
|
elsif value.is_a? Array
|
275
|
-
|
276
|
-
target.backing_record.reactive_set!(method, value.first)
|
286
|
+
target.send "#{method}=", value.first
|
277
287
|
elsif value.is_a? Hash and value[:id] and value[:id].first #and
|
278
288
|
association = target.class.reflect_on_association(method)
|
279
289
|
new_target = association.klass.find(value[:id].first)
|
280
|
-
|
281
|
-
target.backing_record.reactive_set!(method, new_target)
|
290
|
+
target.send "#{method}=", new_target
|
282
291
|
else
|
283
|
-
# target might be able to respond to method directly so we can't optimize out the target send
|
284
292
|
new_target = target.send("#{method}=", target.send(method))
|
285
293
|
end
|
286
294
|
load_from_json(value, new_target) if new_target
|
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.
|
4
|
+
version: 0.7.22
|
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-
|
11
|
+
date: 2015-10-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|