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