reactive-record 0.7.14 → 0.7.15
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/active_record/associations.rb +26 -19
- data/lib/reactive_record/active_record/class_methods.rb +21 -21
- data/lib/reactive_record/active_record/reactive_record/base.rb +76 -76
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +70 -70
- data/lib/reactive_record/server_data_cache.rb +45 -46
- 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: 7855dd3a22d52663865ebec642e73a24b63edd47
|
4
|
+
data.tar.gz: 3a383477635ae0dc2cab7375e761acbb9a568323
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7628e05b00f071724498ba1e9cc562e1786f26818385a021003250428f0b767d49d7afd9a9722beef43c70678856ce7eb9256fc0ce90b7888a111d2ea8d64bae
|
7
|
+
data.tar.gz: 7fed6602d7e6556c50ccd9cd18ce25fa09c1803fcfe55ebe979147b690f0d5460bb0a151f82e103da4ea779773acf6d7d9ee187b369d0dea5d28253fb6da45ce
|
@@ -1,25 +1,32 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
|
2
|
+
|
3
3
|
class Base
|
4
|
-
|
4
|
+
|
5
5
|
def self.reflect_on_all_associations
|
6
6
|
base_class.instance_eval { @associations ||= superclass.instance_eval { (@associations && @associations.dup) || [] } }
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def self.reflect_on_association(attribute)
|
10
|
-
reflect_on_all_associations.detect { |association| association.attribute == attribute }
|
10
|
+
if found = reflect_on_all_associations.detect { |association| association.attribute == attribute and association.owner_class == self }
|
11
|
+
found
|
12
|
+
elsif superclass == Base
|
13
|
+
nil
|
14
|
+
else
|
15
|
+
superclass.reflect_on_association(attribute)
|
16
|
+
end
|
11
17
|
end
|
12
|
-
|
18
|
+
|
13
19
|
end
|
14
|
-
|
20
|
+
|
15
21
|
module Associations
|
16
|
-
|
22
|
+
|
17
23
|
class AssociationReflection
|
18
|
-
|
24
|
+
|
19
25
|
attr_reader :association_foreign_key
|
20
26
|
attr_reader :attribute
|
21
27
|
attr_reader :macro
|
22
|
-
|
28
|
+
attr_reader :owner_class
|
29
|
+
|
23
30
|
def initialize(owner_class, macro, name, options = {})
|
24
31
|
owner_class.reflect_on_all_associations << self
|
25
32
|
@owner_class = owner_class
|
@@ -33,29 +40,29 @@ module ActiveRecord
|
|
33
40
|
@association_foreign_key = options[:foreign_key] || (macro == :belongs_to && "#{name}_id") || "#{@owner_class.name.underscore}_id"
|
34
41
|
@attribute = name
|
35
42
|
end
|
36
|
-
|
43
|
+
|
37
44
|
def inverse_of
|
38
45
|
unless @options[:through] or @inverse_of
|
39
|
-
inverse_association = klass.reflect_on_all_associations.detect do | association |
|
40
|
-
association.association_foreign_key == @association_foreign_key and association.klass
|
46
|
+
inverse_association = klass.reflect_on_all_associations.detect do | association |
|
47
|
+
association.association_foreign_key == @association_foreign_key and association.klass == @owner_class and association.attribute != attribute and klass == association.owner_class
|
41
48
|
end
|
42
49
|
raise "Association #{@owner_class}.#{attribute} (foreign_key: #{@association_foreign_key}) has no inverse in #{@klass_name}" unless inverse_association
|
43
50
|
@inverse_of = inverse_association.attribute
|
44
51
|
end
|
45
52
|
@inverse_of
|
46
53
|
end
|
47
|
-
|
54
|
+
|
48
55
|
def klass
|
49
56
|
@klass ||= Object.const_get(@klass_name)
|
50
57
|
end
|
51
|
-
|
58
|
+
|
52
59
|
def collection?
|
53
60
|
[:has_many].include? @macro
|
54
61
|
end
|
55
|
-
|
62
|
+
|
56
63
|
end
|
57
|
-
|
64
|
+
|
58
65
|
end
|
59
|
-
|
60
|
-
|
61
|
-
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
|
2
|
+
|
3
3
|
module ClassMethods
|
4
|
-
|
4
|
+
|
5
5
|
def base_class
|
6
6
|
|
7
7
|
unless self < Base
|
@@ -27,7 +27,7 @@ module ActiveRecord
|
|
27
27
|
def primary_key=(val)
|
28
28
|
base_class.instance_eval { @primary_key_value = val }
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def inheritance_column
|
32
32
|
base_class.instance_eval {@inheritance_column_value || "type"}
|
33
33
|
end
|
@@ -44,11 +44,11 @@ module ActiveRecord
|
|
44
44
|
def find(id)
|
45
45
|
base_class.instance_eval {ReactiveRecord::Base.find(self, primary_key, id)}
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
def find_by(opts = {})
|
49
49
|
base_class.instance_eval {ReactiveRecord::Base.find(self, opts.first.first, opts.first.last)}
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
def method_missing(name, *args, &block)
|
53
53
|
if args.count == 1 && name =~ /^find_by_/ && !block
|
54
54
|
find_by(name.gsub(/^find_by_/, "") => args[0])
|
@@ -56,49 +56,49 @@ module ActiveRecord
|
|
56
56
|
raise "#{self.name}.#{name}(#{args}) (called class method missing)"
|
57
57
|
end
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
60
|
def abstract_class=(val)
|
61
61
|
@abstract_class = val
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
def scope(name, body)
|
65
65
|
singleton_class.send(:define_method, name) do
|
66
|
-
ReactiveRecord::Base.class_scopes(self)[name] ||= ReactiveRecord::Collection.new(self, nil, nil, self, name)
|
66
|
+
ReactiveRecord::Base.class_scopes(self)[name] ||= ReactiveRecord::Collection.new(self, nil, nil, self, name)
|
67
67
|
end
|
68
|
-
singleton_class.send(:define_method, "#{name}=") do |collection|
|
68
|
+
singleton_class.send(:define_method, "#{name}=") do |collection|
|
69
69
|
ReactiveRecord::Base.class_scopes(self)[name] = collection
|
70
70
|
end
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
def all
|
74
74
|
ReactiveRecord::Base.class_scopes(self)[:all] ||= ReactiveRecord::Collection.new(self, nil, nil, self, "all")
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
def all=(collection)
|
78
78
|
ReactiveRecord::Base.class_scopes(self)[:all] = collection
|
79
79
|
end
|
80
|
-
|
81
|
-
[:belongs_to, :has_many, :has_one].each do |macro|
|
80
|
+
|
81
|
+
[:belongs_to, :has_many, :has_one].each do |macro|
|
82
82
|
define_method(macro) do |*args| # is this a bug in opal? saying name, scope=nil, opts={} does not work!
|
83
83
|
name = args.first
|
84
84
|
opts = (args.count > 1 and args.last.is_a? Hash) ? args.last : {}
|
85
|
-
Associations::AssociationReflection.new(
|
85
|
+
Associations::AssociationReflection.new(self, macro, name, opts)
|
86
86
|
end
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
def composed_of(name, opts = {})
|
90
90
|
Aggregations::AggregationReflection.new(base_class, :composed_of, name, opts)
|
91
91
|
end
|
92
92
|
|
93
93
|
[
|
94
|
-
"table_name=", "before_validation", "with_options", "validates_presence_of", "validates_format_of",
|
95
|
-
"accepts_nested_attributes_for", "before_create", "after_create", "before_save", "after_save", "before_destroy", "where", "validate",
|
94
|
+
"table_name=", "before_validation", "with_options", "validates_presence_of", "validates_format_of",
|
95
|
+
"accepts_nested_attributes_for", "before_create", "after_create", "before_save", "after_save", "before_destroy", "where", "validate",
|
96
96
|
"attr_protected", "validates_numericality_of", "default_scope", "has_attached_file", "attr_accessible",
|
97
97
|
"serialize"
|
98
98
|
].each do |method|
|
99
99
|
define_method(method.to_s) { |*args, &block| }
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
def _react_param_conversion(param, opt = nil)
|
103
103
|
# defines how react will convert incoming json to this ActiveRecord model
|
104
104
|
param_is_native = !param.respond_to?(:is_a?) rescue true
|
@@ -122,7 +122,7 @@ module ActiveRecord
|
|
122
122
|
nil
|
123
123
|
end
|
124
124
|
end
|
125
|
-
|
125
|
+
|
126
126
|
end
|
127
|
-
|
128
|
-
end
|
127
|
+
|
128
|
+
end
|
@@ -1,66 +1,66 @@
|
|
1
1
|
module ReactiveRecord
|
2
2
|
class Base
|
3
|
-
|
3
|
+
|
4
4
|
# Its all about lazy loading. This prevents us from grabbing enormous association collections, or large attributes
|
5
5
|
# unless they are explicitly requested.
|
6
|
-
|
6
|
+
|
7
7
|
# During prerendering we get each attribute as its requested and fill it in both on the javascript side, as well as
|
8
|
-
# remember that the attribute needs to be part of the download to client.
|
9
|
-
|
8
|
+
# remember that the attribute needs to be part of the download to client.
|
9
|
+
|
10
10
|
# On the client we fill in the record data with empty values (nil, or one element collections) but only as the attribute
|
11
11
|
# is requested. Each request queues up a request to get the real data from the server.
|
12
|
-
|
13
|
-
# The ReactiveRecord class serves two purposes. First it is the unique data corresponding to the last known state of a
|
14
|
-
# database record. This means All records matching a specific database record are unique. This is unlike AR but is
|
12
|
+
|
13
|
+
# The ReactiveRecord class serves two purposes. First it is the unique data corresponding to the last known state of a
|
14
|
+
# database record. This means All records matching a specific database record are unique. This is unlike AR but is
|
15
15
|
# important both for the lazy loading and also so that when values change react can be informed of the change.
|
16
|
-
|
16
|
+
|
17
17
|
# Secondly it serves as name space for all the ReactiveRecord specific methods, so every AR Instance has a ReactiveRecord
|
18
|
-
|
18
|
+
|
19
19
|
# Because there is no point in generating a new ar_instance everytime a search is made we cache the first ar_instance created.
|
20
20
|
# Its possible however during loading to create a new ar_instances that will in the end point to the same record.
|
21
|
-
|
21
|
+
|
22
22
|
# VECTORS... are an important concept. They are the substitute for a primary key before a record is loaded.
|
23
23
|
# Vectors have the form [ModelClass, method_call, method_call, method_call...]
|
24
|
-
|
24
|
+
|
25
25
|
# Each method call is either a simple method name or an array in the form [method_name, param, param ...]
|
26
26
|
# Example [User, [find, 123], todos, active, [due, "1/1/2016"], title]
|
27
27
|
# Roughly corresponds to this query: User.find(123).todos.active.due("1/1/2016").select(:title)
|
28
|
-
|
28
|
+
|
29
29
|
attr_accessor :ar_instance
|
30
30
|
attr_accessor :vector
|
31
31
|
attr_accessor :model
|
32
|
-
|
32
|
+
|
33
33
|
# While data is being loaded from the server certain internal behaviors need to change
|
34
34
|
# for example records all record changes are synced as they happen.
|
35
35
|
# This is implemented this way so that the ServerDataCache class can use pure active
|
36
36
|
# record methods in its implementation
|
37
|
-
|
37
|
+
|
38
38
|
def self.data_loading?
|
39
39
|
@data_loading
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def data_loading?
|
43
43
|
self.class.data_loading?
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
def self.load_data(&block)
|
47
47
|
current_data_loading, @data_loading = [@data_loading, true]
|
48
48
|
yield
|
49
49
|
@data_loading = current_data_loading
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
def self.load_from_json(json, target = nil)
|
53
53
|
load_data { ServerDataCache.load_from_json(json, target) }
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
def self.class_scopes(model)
|
57
57
|
@class_scopes[model.base_class]
|
58
58
|
end
|
59
|
-
|
60
|
-
def self.find(model, attribute, value)
|
59
|
+
|
60
|
+
def self.find(model, attribute, value)
|
61
61
|
# will return the unique record with this attribute-value pair
|
62
62
|
# value cannot be an association or aggregation
|
63
|
-
|
63
|
+
|
64
64
|
model = model.base_class
|
65
65
|
# already have a record with this attribute-value pair?
|
66
66
|
record = @records[model].detect { |record| record.attributes[attribute] == value}
|
@@ -70,7 +70,7 @@ module ReactiveRecord
|
|
70
70
|
# find_in_db returns nil if we are not prerendering which will force us to create a new record
|
71
71
|
# because there is no way of knowing the id.
|
72
72
|
if attribute != model.primary_key and id = find_in_db(model, attribute, value)
|
73
|
-
record = @records[model].detect { |record| record.id == id}
|
73
|
+
record = @records[model].detect { |record| record.id == id}
|
74
74
|
end
|
75
75
|
# if we don't have a record then create one
|
76
76
|
(record = new(model)).vector = [model, ["find_by_#{attribute}", value]] unless record
|
@@ -79,26 +79,27 @@ module ReactiveRecord
|
|
79
79
|
# and set the primary if we have one
|
80
80
|
record.sync_attribute(model.primary_key, id) if id
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
# finally initialize and return the ar_instance
|
84
84
|
record.ar_instance ||= infer_type_from_hash(model, record.attributes).new(record)
|
85
85
|
end
|
86
|
-
|
87
|
-
def self.new_from_vector(model,
|
86
|
+
|
87
|
+
def self.new_from_vector(model, aggregate_method, *vector)
|
88
88
|
# this is the equivilent of find but for associations and aggregations
|
89
|
-
# because we are not fetching a specific attribute yet, there is NO communication with the
|
89
|
+
# because we are not fetching a specific attribute yet, there is NO communication with the
|
90
90
|
# server. That only happens during find.
|
91
|
-
|
92
91
|
model = model.base_class
|
93
|
-
|
92
|
+
|
94
93
|
# do we already have a record with this vector? If so return it, otherwise make a new one.
|
95
|
-
|
96
|
-
record = @records[model].detect { |record| record.vector == vector}
|
97
|
-
|
94
|
+
|
95
|
+
record = @records[model].detect { |record| record.vector == vector }
|
96
|
+
unless record
|
97
|
+
record = new model
|
98
|
+
record.vector = vector
|
99
|
+
end
|
98
100
|
record.ar_instance ||= infer_type_from_hash(model, record.attributes).new(record)
|
99
|
-
|
100
101
|
end
|
101
|
-
|
102
|
+
|
102
103
|
def initialize(model, hash = {}, ar_instance = nil)
|
103
104
|
@model = model
|
104
105
|
@ar_instance = ar_instance
|
@@ -106,51 +107,50 @@ module ReactiveRecord
|
|
106
107
|
@attributes = {}
|
107
108
|
records[model] << self
|
108
109
|
end
|
109
|
-
|
110
|
+
|
110
111
|
def find(*args)
|
111
112
|
self.class.find(*args)
|
112
113
|
end
|
113
|
-
|
114
|
+
|
114
115
|
def new_from_vector(*args)
|
115
116
|
self.class.new_from_vector(*args)
|
116
117
|
end
|
117
|
-
|
118
|
+
|
118
119
|
def primary_key
|
119
120
|
@model.primary_key
|
120
121
|
end
|
121
|
-
|
122
|
+
|
122
123
|
def id
|
123
124
|
attributes[primary_key]
|
124
125
|
end
|
125
|
-
|
126
|
+
|
126
127
|
def id=(value)
|
127
|
-
# we
|
128
|
-
existing_record = records[@model].detect { |record| record.attributes[primary_key] == value}
|
129
|
-
|
128
|
+
# value can be nil if we are loading an aggregate otherwise check if it already exists
|
129
|
+
if !(value and existing_record = records[@model].detect { |record| record.attributes[primary_key] == value})
|
130
|
+
attributes[primary_key] = value
|
131
|
+
else
|
130
132
|
@ar_instance.instance_eval { @backing_record = existing_record }
|
131
133
|
existing_record.attributes.merge!(attributes) { |key, v1, v2| v1 }
|
132
|
-
else
|
133
|
-
attributes[primary_key] = value
|
134
134
|
end
|
135
135
|
end
|
136
|
-
|
136
|
+
|
137
137
|
def attributes
|
138
138
|
@last_access_at = Time.now
|
139
139
|
@attributes
|
140
140
|
end
|
141
|
-
|
141
|
+
|
142
142
|
def reactive_get!(attribute)
|
143
143
|
unless @destroyed
|
144
|
-
apply_method(attribute) unless @attributes.has_key? attribute
|
144
|
+
apply_method(attribute) unless @attributes.has_key? attribute
|
145
145
|
React::State.get_state(self, attribute) unless data_loading?
|
146
146
|
attributes[attribute]
|
147
147
|
end
|
148
148
|
end
|
149
|
-
|
149
|
+
|
150
150
|
def reactive_set!(attribute, value)
|
151
151
|
unless @destroyed or (!(attributes[attribute].is_a? DummyValue) and attributes.has_key?(attribute) and attributes[attribute] == value)
|
152
|
-
if association = @model.reflect_on_association(attribute)
|
153
|
-
if association.collection?
|
152
|
+
if association = @model.reflect_on_association(attribute)
|
153
|
+
if association.collection?
|
154
154
|
collection = Collection.new(association.klass, @ar_instance, association)
|
155
155
|
collection.replace(value || [])
|
156
156
|
value = collection
|
@@ -168,7 +168,7 @@ module ReactiveRecord
|
|
168
168
|
end
|
169
169
|
elsif value
|
170
170
|
attributes[attribute].attributes[inverse_of] = nil if attributes[attribute]
|
171
|
-
value.attributes[inverse_of] = @ar_instance
|
171
|
+
value.attributes[inverse_of] = @ar_instance
|
172
172
|
React::State.set_state(value.instance_variable_get(:@backing_record), inverse_of, @ar_instance) unless data_loading?
|
173
173
|
elsif attributes[attribute]
|
174
174
|
attributes[attribute].attributes[inverse_of] = nil
|
@@ -182,7 +182,7 @@ module ReactiveRecord
|
|
182
182
|
end
|
183
183
|
value
|
184
184
|
end
|
185
|
-
|
185
|
+
|
186
186
|
def changed?(*args)
|
187
187
|
changed2(if args.count == 0
|
188
188
|
React::State.get_state(self, self)
|
@@ -192,7 +192,7 @@ module ReactiveRecord
|
|
192
192
|
{args[0] => @attributes[args[0]]}
|
193
193
|
end)
|
194
194
|
end
|
195
|
-
|
195
|
+
|
196
196
|
def changed2(attrs)
|
197
197
|
attrs.each do |attribute, value|
|
198
198
|
if association = @model.reflect_on_association(attribute) and association.collection? and value
|
@@ -205,12 +205,12 @@ module ReactiveRecord
|
|
205
205
|
end
|
206
206
|
false
|
207
207
|
end
|
208
|
-
|
209
|
-
|
208
|
+
|
209
|
+
|
210
210
|
def errors
|
211
211
|
@errors ||= ActiveModel::Error.new
|
212
212
|
end
|
213
|
-
|
213
|
+
|
214
214
|
def sync!(hash = {}) # does NOT notify (see saved! for notification)
|
215
215
|
@attributes.merge! hash
|
216
216
|
@synced_attributes = @attributes.dup
|
@@ -219,13 +219,13 @@ module ReactiveRecord
|
|
219
219
|
@errors = nil
|
220
220
|
self
|
221
221
|
end
|
222
|
-
|
222
|
+
|
223
223
|
def sync_attribute(attribute, value)
|
224
224
|
@synced_attributes[attribute] = attributes[attribute] = value
|
225
225
|
@synced_attributes[attribute] = value.dup if value.is_a? ReactiveRecord::Collection
|
226
226
|
value
|
227
227
|
end
|
228
|
-
|
228
|
+
|
229
229
|
def revert
|
230
230
|
@attributes.each do |attribute, value|
|
231
231
|
@ar_instance.send("#{attribute}=", @synced_attributes[attribute])
|
@@ -233,33 +233,33 @@ module ReactiveRecord
|
|
233
233
|
@attributes.delete_if { |attribute, value| !@synced_attributes.has_key?(attribute) }
|
234
234
|
@errors = nil
|
235
235
|
end
|
236
|
-
|
237
|
-
def saving!
|
236
|
+
|
237
|
+
def saving!
|
238
238
|
React::State.set_state(self, self, :saving) unless data_loading?
|
239
239
|
@saving = true
|
240
240
|
end
|
241
|
-
|
241
|
+
|
242
242
|
def saved!(errors = nil) # sets saving to false AND notifies
|
243
243
|
@saving = false
|
244
244
|
@errors = ActiveModel::Error.new(errors)
|
245
245
|
if errors
|
246
246
|
#errors.each { |attribute, error| React::State.set_state(self, attribute, attributes[attribute]) }
|
247
247
|
React::State.set_state(self, self, :errors)
|
248
|
-
elsif !data_loading?
|
248
|
+
elsif !data_loading?
|
249
249
|
React::State.set_state(self, self, :saved)
|
250
250
|
end
|
251
251
|
self
|
252
252
|
end
|
253
|
-
|
253
|
+
|
254
254
|
def saving?
|
255
255
|
React::State.get_state(self, self)
|
256
256
|
@saving
|
257
257
|
end
|
258
|
-
|
258
|
+
|
259
259
|
def new?
|
260
260
|
!id and !vector
|
261
261
|
end
|
262
|
-
|
262
|
+
|
263
263
|
def find_association(association, id)
|
264
264
|
inverse_of = association.inverse_of
|
265
265
|
instance = if id
|
@@ -271,32 +271,32 @@ module ReactiveRecord
|
|
271
271
|
inverse_association = association.klass.reflect_on_association(inverse_of)
|
272
272
|
if inverse_association.collection?
|
273
273
|
instance_backing_record_attributes[inverse_of] = if id and id != ""
|
274
|
-
Collection.new(@model, instance, inverse_association, association.klass, ["find", id], inverse_of)
|
274
|
+
Collection.new(@model, instance, inverse_association, association.klass, ["find", id], inverse_of)
|
275
275
|
else
|
276
|
-
Collection.new(@model, instance, inverse_association, *vector, association.attribute, inverse_of)
|
276
|
+
Collection.new(@model, instance, inverse_association, *vector, association.attribute, inverse_of)
|
277
277
|
end unless instance_backing_record_attributes[inverse_of]
|
278
278
|
instance_backing_record_attributes[inverse_of].replace [@ar_instance]
|
279
279
|
else
|
280
|
-
instance_backing_record_attributes[inverse_of] = @ar_instance
|
280
|
+
instance_backing_record_attributes[inverse_of] = @ar_instance
|
281
281
|
end if inverse_of and !instance_backing_record_attributes.has_key?(inverse_of)
|
282
282
|
instance
|
283
283
|
end
|
284
|
-
|
284
|
+
|
285
285
|
def apply_method(method)
|
286
286
|
# Fills in the value returned by sending "method" to the corresponding server side db instance
|
287
287
|
if !new?
|
288
288
|
sync_attribute(
|
289
|
-
method,
|
289
|
+
method,
|
290
290
|
if association = @model.reflect_on_association(method)
|
291
|
-
if association.collection?
|
291
|
+
if association.collection?
|
292
292
|
Collection.new(association.klass, @ar_instance, association, *vector, method)
|
293
293
|
else
|
294
294
|
find_association(association, (id and id != "" and self.class.fetch_from_db([@model, [:find, id], method, @model.primary_key])))
|
295
295
|
end
|
296
296
|
elsif aggregation = @model.reflect_on_aggregation(method)
|
297
297
|
new_from_vector(aggregation.klass, self, *vector, method)
|
298
|
-
elsif id and id != ""
|
299
|
-
|
298
|
+
elsif id and id != ""
|
299
|
+
self.class.fetch_from_db([@model, [:find, id], method]) || self.class.load_from_db(*vector, method)
|
300
300
|
else # its a attribute in an aggregate or we are on the client and don't know the id
|
301
301
|
self.class.fetch_from_db([*vector, method]) || self.class.load_from_db(*vector, method)
|
302
302
|
end
|
@@ -307,9 +307,9 @@ module ReactiveRecord
|
|
307
307
|
@attributes[method] = aggregation.klass.new
|
308
308
|
end
|
309
309
|
end
|
310
|
-
|
310
|
+
|
311
311
|
def self.infer_type_from_hash(klass, hash)
|
312
|
-
klass = klass.base_class
|
312
|
+
klass = klass.base_class
|
313
313
|
return klass unless hash
|
314
314
|
type = hash[klass.inheritance_column]
|
315
315
|
begin
|
@@ -320,6 +320,6 @@ module ReactiveRecord
|
|
320
320
|
end if type
|
321
321
|
klass
|
322
322
|
end
|
323
|
-
|
323
|
+
|
324
324
|
end
|
325
|
-
end
|
325
|
+
end
|
@@ -3,9 +3,9 @@ require 'json'
|
|
3
3
|
module ReactiveRecord
|
4
4
|
|
5
5
|
class Base
|
6
|
-
|
6
|
+
|
7
7
|
include React::IsomorphicHelpers
|
8
|
-
|
8
|
+
|
9
9
|
before_first_mount do |context|
|
10
10
|
if RUBY_ENGINE != 'opal'
|
11
11
|
@server_data_cache = ReactiveRecord::ServerDataCache.new(context.controller.acting_user)
|
@@ -13,7 +13,7 @@ module ReactiveRecord
|
|
13
13
|
@fetch_scheduled = nil
|
14
14
|
@records = Hash.new { |hash, key| hash[key] = [] }
|
15
15
|
@class_scopes = Hash.new { |hash, key| hash[key] = {} }
|
16
|
-
if on_opal_client?
|
16
|
+
if on_opal_client?
|
17
17
|
@pending_fetches = []
|
18
18
|
@last_fetch_at = Time.now
|
19
19
|
unless `typeof window.ReactiveRecordInitialData === 'undefined'`
|
@@ -25,25 +25,25 @@ module ReactiveRecord
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
def records
|
30
30
|
self.class.instance_variable_get(:@records)
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
# Prerendering db access (returns nil if on client):
|
34
34
|
# at end of prerendering dumps all accessed records in the footer
|
35
|
-
|
35
|
+
|
36
36
|
isomorphic_method(:fetch_from_db) do |f, vector|
|
37
37
|
# vector must end with either "*all", or be a simple attribute
|
38
38
|
f.send_to_server [vector.shift.name, *vector] if RUBY_ENGINE == 'opal'
|
39
39
|
f.when_on_server { @server_data_cache[*vector] }
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
isomorphic_method(:find_in_db) do |f, klass, attribute, value|
|
43
43
|
f.send_to_server klass.name, attribute, value if RUBY_ENGINE == 'opal'
|
44
44
|
f.when_on_server { @server_data_cache[klass, ["find_by_#{attribute}", value], :id] }
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
prerender_footer do
|
48
48
|
if @server_data_cache
|
49
49
|
json = @server_data_cache.as_json.to_json # can this just be to_json?
|
@@ -51,7 +51,7 @@ module ReactiveRecord
|
|
51
51
|
else
|
52
52
|
json = {}.to_json
|
53
53
|
end
|
54
|
-
path = ::Rails.application.routes.routes.detect do |route|
|
54
|
+
path = ::Rails.application.routes.routes.detect do |route|
|
55
55
|
# not sure why the second check is needed. It happens in the test app
|
56
56
|
route.app == ReactiveRecord::Engine or (route.app.respond_to?(:app) and route.app.app == ReactiveRecord::Engine)
|
57
57
|
end.path.spec
|
@@ -61,13 +61,13 @@ module ReactiveRecord
|
|
61
61
|
"window.ReactiveRecordInitialData.push(#{json})\n"+
|
62
62
|
"</script>\n"
|
63
63
|
end if RUBY_ENGINE != 'opal'
|
64
|
-
|
64
|
+
|
65
65
|
# Client side db access (never called during prerendering):
|
66
|
-
|
66
|
+
|
67
67
|
# Always returns an object of class DummyValue which will act like most standard AR field types
|
68
68
|
# Whenever a dummy value is accessed it notify React that there are loads pending so appropriate rerenders
|
69
69
|
# will occur when the value is eventually loaded.
|
70
|
-
|
70
|
+
|
71
71
|
# queue up fetches, and at the end of each rendering cycle fetch the records
|
72
72
|
# notify that loads are pending
|
73
73
|
|
@@ -75,7 +75,7 @@ module ReactiveRecord
|
|
75
75
|
return nil unless on_opal_client? # this can happen when we are on the server and a nil value is returned for an attribute
|
76
76
|
# only called from the client side
|
77
77
|
# pushes the value of vector onto the a list of vectors that will be loaded from the server when the next
|
78
|
-
# rendering cycle completes.
|
78
|
+
# rendering cycle completes.
|
79
79
|
# takes care of informing react that there are things to load, and schedules the loader to run
|
80
80
|
# Note there is no equivilent to find_in_db, because each vector implicitly does a find.
|
81
81
|
raise "attempt to do a find_by_id of nil. This will return all records, and is not allowed" if vector[1] == ["find_by_id", nil]
|
@@ -85,20 +85,20 @@ module ReactiveRecord
|
|
85
85
|
end
|
86
86
|
DummyValue.new
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
class DummyValue < NilClass
|
90
|
-
|
90
|
+
|
91
91
|
def notify
|
92
92
|
unless ReactiveRecord::Base.data_loading?
|
93
93
|
ReactiveRecord.loads_pending!
|
94
94
|
ReactiveRecord::WhileLoading.loading!
|
95
95
|
end
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
def initialize()
|
99
99
|
notify
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
def method_missing(method, *args, &block)
|
103
103
|
if 0.respond_to? method
|
104
104
|
notify
|
@@ -110,45 +110,45 @@ module ReactiveRecord
|
|
110
110
|
super
|
111
111
|
end
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
def coerce(s)
|
115
115
|
[self.send("to_#{s.class.name.downcase}"), s]
|
116
116
|
end
|
117
|
-
|
117
|
+
|
118
118
|
def ==(other_value)
|
119
119
|
other_value.is_a? DummyValue
|
120
120
|
end
|
121
|
-
|
122
|
-
def to_s
|
121
|
+
|
122
|
+
def to_s
|
123
123
|
notify
|
124
124
|
""
|
125
125
|
end
|
126
|
-
|
126
|
+
|
127
127
|
def to_f
|
128
128
|
notify
|
129
129
|
0.0
|
130
130
|
end
|
131
|
-
|
131
|
+
|
132
132
|
def to_i
|
133
133
|
notify
|
134
134
|
0
|
135
135
|
end
|
136
|
-
|
136
|
+
|
137
137
|
def to_numeric
|
138
138
|
notify
|
139
139
|
0
|
140
140
|
end
|
141
|
-
|
141
|
+
|
142
142
|
def to_date
|
143
143
|
"2001-01-01T00:00:00.000-00:00".to_date
|
144
144
|
end
|
145
|
-
|
145
|
+
|
146
146
|
def acts_as_string?
|
147
147
|
true
|
148
148
|
end
|
149
|
-
|
149
|
+
|
150
150
|
end
|
151
|
-
|
151
|
+
|
152
152
|
|
153
153
|
def self.schedule_fetch
|
154
154
|
@fetch_scheduled ||= after(0.001) do
|
@@ -179,23 +179,23 @@ module ReactiveRecord
|
|
179
179
|
end
|
180
180
|
end
|
181
181
|
end
|
182
|
-
|
182
|
+
|
183
183
|
def self.get_type_hash(record)
|
184
184
|
{record.class.inheritance_column => record[record.class.inheritance_column]}
|
185
185
|
end
|
186
|
-
|
186
|
+
|
187
187
|
# save records
|
188
|
-
|
188
|
+
|
189
189
|
if RUBY_ENGINE == 'opal'
|
190
|
-
|
191
|
-
def save(&block)
|
192
|
-
|
190
|
+
|
191
|
+
def save(&block)
|
192
|
+
|
193
193
|
if data_loading?
|
194
194
|
|
195
195
|
sync!
|
196
196
|
|
197
197
|
elsif changed?
|
198
|
-
|
198
|
+
|
199
199
|
# we want to pass not just the model data to save, but also enough information so that on return from the server
|
200
200
|
# we can update the models on the client
|
201
201
|
|
@@ -203,9 +203,9 @@ module ReactiveRecord
|
|
203
203
|
records_to_process = [self] # list of records to process, will grow as we chase associations
|
204
204
|
# outputs
|
205
205
|
models = [] # the actual data to save {id: record.object_id, model: record.model.model_name, attributes: changed_attributes}
|
206
|
-
associations = [] # {parent_id: record.object_id, attribute: attribute, child_id: assoc_record.object_id}
|
207
|
-
# used to keep track of records that have been processed for effeciency
|
208
|
-
backing_records = {self.object_id => self} # for quick lookup of records that have been or will be processed [record.object_id] => record
|
206
|
+
associations = [] # {parent_id: record.object_id, attribute: attribute, child_id: assoc_record.object_id}
|
207
|
+
# used to keep track of records that have been processed for effeciency
|
208
|
+
backing_records = {self.object_id => self} # for quick lookup of records that have been or will be processed [record.object_id] => record
|
209
209
|
|
210
210
|
add_new_association = lambda do |record, attribute, assoc_record|
|
211
211
|
if assoc_record.changed?
|
@@ -224,7 +224,7 @@ module ReactiveRecord
|
|
224
224
|
models << {id: record.object_id, model: record.model.model_name, attributes: output_attributes}
|
225
225
|
record.attributes.each do |attribute, value|
|
226
226
|
if association = record.model.reflect_on_association(attribute)
|
227
|
-
if association.collection?
|
227
|
+
if association.collection?
|
228
228
|
value.each { |assoc| add_new_association.call record, attribute, assoc.instance_variable_get(:@backing_record) }
|
229
229
|
elsif value
|
230
230
|
add_new_association.call record, attribute, value.instance_variable_get(:@backing_record)
|
@@ -239,9 +239,9 @@ module ReactiveRecord
|
|
239
239
|
end
|
240
240
|
record_index += 1
|
241
241
|
end
|
242
|
-
|
242
|
+
|
243
243
|
backing_records.each { |id, record| record.saving! }
|
244
|
-
|
244
|
+
|
245
245
|
promise = Promise.new
|
246
246
|
|
247
247
|
HTTP.post(`window.ReactiveRecordEnginePath`+"/save", payload: {models: models, associations: associations}).then do |response|
|
@@ -254,7 +254,7 @@ module ReactiveRecord
|
|
254
254
|
response.json[:saved_models].each { | item | backing_records[item[0]].sync!(item[2]) }
|
255
255
|
else
|
256
256
|
response.json[:saved_models].each { | item | backing_records[item[0]].saved! item[3] }
|
257
|
-
log("Reactive Record Save Failed: #{response.json[:message]}", :error)
|
257
|
+
log("Reactive Record Save Failed: #{response.json[:message]}", :error)
|
258
258
|
response.json[:saved_models].each do | item |
|
259
259
|
log(" Model: #{item[1]}[#{item[0]}] Attributes: #{item[2]} Errors: #{item[3]}", :error) if item[3]
|
260
260
|
end
|
@@ -262,7 +262,7 @@ module ReactiveRecord
|
|
262
262
|
|
263
263
|
yield response.json[:success], response.json[:message], response.json[:models] if block
|
264
264
|
promise.resolve response.json
|
265
|
-
|
265
|
+
|
266
266
|
backing_records.each { |id, record| record.saved! } if response.json[:success]
|
267
267
|
rescue Exception => e
|
268
268
|
puts "Save Failed: #{e}"
|
@@ -276,11 +276,11 @@ module ReactiveRecord
|
|
276
276
|
promise
|
277
277
|
end
|
278
278
|
end
|
279
|
-
|
279
|
+
|
280
280
|
else
|
281
|
-
|
281
|
+
|
282
282
|
def self.save_records(models, associations, acting_user)
|
283
|
-
|
283
|
+
|
284
284
|
reactive_records = {}
|
285
285
|
new_models = []
|
286
286
|
saved_models = []
|
@@ -293,7 +293,7 @@ module ReactiveRecord
|
|
293
293
|
record = model.find(id)
|
294
294
|
keys = record.attributes.keys
|
295
295
|
attributes.each do |key, value|
|
296
|
-
if keys.include? key
|
296
|
+
if keys.include? key
|
297
297
|
record[key] = value
|
298
298
|
else
|
299
299
|
record.send("#{key}=",value)
|
@@ -304,7 +304,7 @@ module ReactiveRecord
|
|
304
304
|
record = model.new
|
305
305
|
keys = record.attributes.keys
|
306
306
|
attributes.each do |key, value|
|
307
|
-
if keys.include? key
|
307
|
+
if keys.include? key
|
308
308
|
record[key] = value
|
309
309
|
else
|
310
310
|
record.send("#{key}=",value)
|
@@ -314,9 +314,9 @@ module ReactiveRecord
|
|
314
314
|
record
|
315
315
|
end
|
316
316
|
end
|
317
|
-
|
317
|
+
|
318
318
|
ActiveRecord::Base.transaction do
|
319
|
-
|
319
|
+
|
320
320
|
associations.each do |association|
|
321
321
|
parent = reactive_records[association[:parent_id]]
|
322
322
|
parent.instance_variable_set("@reactive_record_#{association[:attribute]}_changed", true)
|
@@ -327,36 +327,36 @@ module ReactiveRecord
|
|
327
327
|
else
|
328
328
|
parent.send("#{association[:attribute]}=", reactive_records[association[:child_id]])
|
329
329
|
end
|
330
|
-
end if associations
|
331
|
-
|
330
|
+
end if associations
|
331
|
+
|
332
332
|
has_errors = false
|
333
333
|
|
334
334
|
saved_models = reactive_records.collect do |reactive_record_id, model|
|
335
|
-
unless model.frozen?
|
335
|
+
unless model.frozen?
|
336
336
|
saved = model.check_permission_with_acting_user(acting_user, new_models.include?(model) ? :create_permitted? : :update_permitted?).save
|
337
337
|
has_errors ||= !saved
|
338
338
|
[reactive_record_id, model.class.name, model.attributes, (saved ? nil : model.errors.messages)]
|
339
339
|
end
|
340
340
|
end.compact
|
341
|
-
|
341
|
+
|
342
342
|
raise "Could not save all models" if has_errors
|
343
|
-
|
343
|
+
|
344
344
|
end
|
345
|
-
|
345
|
+
|
346
346
|
{success: true, saved_models: saved_models }
|
347
|
-
|
347
|
+
|
348
348
|
rescue Exception => e
|
349
|
-
|
349
|
+
|
350
350
|
{success: false, saved_models: saved_models, message: e.message}
|
351
|
-
|
351
|
+
|
352
352
|
end
|
353
|
-
|
353
|
+
|
354
354
|
end
|
355
|
-
|
355
|
+
|
356
356
|
# destroy records
|
357
|
-
|
357
|
+
|
358
358
|
if RUBY_ENGINE == 'opal'
|
359
|
-
|
359
|
+
|
360
360
|
def destroy(&block)
|
361
361
|
|
362
362
|
return if @destroyed
|
@@ -380,19 +380,19 @@ module ReactiveRecord
|
|
380
380
|
yield true, nil if block
|
381
381
|
promise.resolve({success: true})
|
382
382
|
end
|
383
|
-
|
383
|
+
|
384
384
|
@attributes = {}
|
385
385
|
sync!
|
386
386
|
@destroyed = true
|
387
|
-
|
387
|
+
|
388
388
|
promise
|
389
389
|
end
|
390
|
-
|
390
|
+
|
391
391
|
else
|
392
|
-
|
392
|
+
|
393
393
|
def self.destroy_record(model, id, vector, acting_user)
|
394
394
|
model = Object.const_get(model)
|
395
|
-
record = if id
|
395
|
+
record = if id
|
396
396
|
model.find(id)
|
397
397
|
else
|
398
398
|
ServerDataCache.new(acting_user)[*vector]
|
@@ -403,8 +403,8 @@ module ReactiveRecord
|
|
403
403
|
rescue Exception => e
|
404
404
|
{success: false, record: record, message: e.message}
|
405
405
|
end
|
406
|
-
|
406
|
+
|
407
407
|
end
|
408
408
|
end
|
409
409
|
|
410
|
-
end
|
410
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module ReactiveRecord
|
2
|
-
|
2
|
+
|
3
3
|
# the point is to collect up a all records needed, with whatever attributes were required + primary key, and inheritance column
|
4
4
|
# or get all scope arrays, with the record ids
|
5
5
|
|
@@ -9,7 +9,7 @@ module ReactiveRecord
|
|
9
9
|
# tree ::= {method => tree | [value]} | method's value is either a nested tree or a single value which is wrapped in array
|
10
10
|
# {:id => primary_key_id_value} | if its the id method we leave the array off because we know it must be an int
|
11
11
|
# {integer => tree} for collections, each item retrieved will be represented by its id
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# example
|
14
14
|
# {
|
15
15
|
# "User" => {
|
@@ -18,12 +18,12 @@ module ReactiveRecord
|
|
18
18
|
# "email" => ["mitch@catprint.com"]
|
19
19
|
# "todos" => {
|
20
20
|
# "active" => {
|
21
|
-
# 123 =>
|
21
|
+
# 123 =>
|
22
22
|
# {
|
23
23
|
# id: 123,
|
24
24
|
# title: ["get fetch_records_from_db done"]
|
25
25
|
# },
|
26
|
-
# 119 =>
|
26
|
+
# 119 =>
|
27
27
|
# {
|
28
28
|
# id: 119
|
29
29
|
# title: ["go for a swim"]
|
@@ -38,14 +38,14 @@ module ReactiveRecord
|
|
38
38
|
|
39
39
|
# To build this tree we first fill values for each individual vector, saving all the intermediate data
|
40
40
|
# when all these are built we build the above hash structure
|
41
|
-
|
41
|
+
|
42
42
|
# basic
|
43
|
-
# [Todo, [find, 123], title]
|
43
|
+
# [Todo, [find, 123], title]
|
44
44
|
# -> [[Todo, [find, 123], title], "get fetch_records_from_db done", 123]
|
45
45
|
|
46
|
-
# [User, [find_by_email, "mitch@catprint.com"], first_name]
|
46
|
+
# [User, [find_by_email, "mitch@catprint.com"], first_name]
|
47
47
|
# -> [[User, [find_by_email, "mitch@catprint.com"], first_name], "Mitch", 12]
|
48
|
-
|
48
|
+
|
49
49
|
# misses
|
50
50
|
# [User, [find_by_email, "foobar@catprint.com"], first_name]
|
51
51
|
# nothing is found so nothing is downloaded
|
@@ -54,20 +54,20 @@ module ReactiveRecord
|
|
54
54
|
# which will return a cache object whose id is nil, and value is nil
|
55
55
|
|
56
56
|
# scoped collection
|
57
|
-
# [User, [find, 12], todos, active, *, title]
|
58
|
-
# -> [[User, [find, 12], todos, active, *, title], "get fetch_records_from_db done", 12, 123]
|
59
|
-
# -> [[User, [find, 12], todos, active, *, title], "go for a swim", 12, 119]
|
57
|
+
# [User, [find, 12], todos, active, *, title]
|
58
|
+
# -> [[User, [find, 12], todos, active, *, title], "get fetch_records_from_db done", 12, 123]
|
59
|
+
# -> [[User, [find, 12], todos, active, *, title], "go for a swim", 12, 119]
|
60
60
|
|
61
61
|
# collection with nested belongs_to
|
62
62
|
# [User, [find, 12], todos, *, team]
|
63
63
|
# -> [[User, [find, 12], todos, *, team, name], "developers", 12, 123, 252]
|
64
64
|
# [[User, [find, 12], todos, *, team, name], nil, 12, 119] <- no team defined for todo<119> so list ends early
|
65
|
-
|
65
|
+
|
66
66
|
# collections that are empty will deliver nothing
|
67
67
|
# [User, [find, 13], todos, *, team, name] # no todos for user 13
|
68
68
|
# evaluation will get this far: [[User, [find, 13], todos], nil, 13]
|
69
69
|
# nothing will match [User, [find, 13], todos, team, name] so nothing will be downloaded
|
70
|
-
|
70
|
+
|
71
71
|
|
72
72
|
# aggregate
|
73
73
|
# [User, [find, 12], address, zip_code]
|
@@ -79,22 +79,22 @@ module ReactiveRecord
|
|
79
79
|
|
80
80
|
# collection * (for iterators etc)
|
81
81
|
# [User, [find, 12], todos, overdue, *all]
|
82
|
-
# -> [[User, [find, 12], todos, active, *all], [119, 123], 12]
|
82
|
+
# -> [[User, [find, 12], todos, active, *all], [119, 123], 12]
|
83
83
|
|
84
84
|
# [Todo, [find, 119], owner, todos, active, *all]
|
85
85
|
# -> [[Todo, [find, 119], owner, todos, active, *all], [119, 123], 119, 12]
|
86
|
-
|
86
|
+
|
87
87
|
|
88
88
|
class ServerDataCache
|
89
|
-
|
89
|
+
|
90
90
|
def initialize(acting_user)
|
91
91
|
@acting_user = acting_user
|
92
92
|
@cache = []
|
93
93
|
@requested_cache_items = []
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
if RUBY_ENGINE != 'opal'
|
97
|
-
|
97
|
+
|
98
98
|
def [](*vector)
|
99
99
|
root = CacheItem.new(@cache, @acting_user, vector[0])
|
100
100
|
vector[1..-1].inject(root) { |cache_item, method| cache_item.apply_method method if cache_item }
|
@@ -103,29 +103,29 @@ module ReactiveRecord
|
|
103
103
|
@requested_cache_items += new_items
|
104
104
|
new_items.last.value if new_items.last
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
def self.[](vectors, acting_user)
|
108
108
|
cache = new(acting_user)
|
109
109
|
vectors.each { |vector| cache[*vector] }
|
110
110
|
cache.as_json
|
111
111
|
end
|
112
|
-
|
112
|
+
|
113
113
|
def clear_requests
|
114
114
|
@requested_cache_items = []
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
def as_json
|
118
118
|
@requested_cache_items.inject({}) do | hash, cache_item|
|
119
119
|
hash.deep_merge! cache_item.as_hash
|
120
120
|
end
|
121
121
|
end
|
122
|
-
|
122
|
+
|
123
123
|
def select(&block); @cache.select █ end
|
124
|
-
|
124
|
+
|
125
125
|
def detect(&block); @cache.detect █ end
|
126
|
-
|
126
|
+
|
127
127
|
def inject(initial, &block); @cache.inject(initial) █ end
|
128
|
-
|
128
|
+
|
129
129
|
class CacheItem
|
130
130
|
|
131
131
|
attr_reader :vector
|
@@ -133,14 +133,14 @@ module ReactiveRecord
|
|
133
133
|
attr_reader :root
|
134
134
|
attr_reader :acting_user
|
135
135
|
|
136
|
-
def value
|
136
|
+
def value
|
137
137
|
@ar_object
|
138
138
|
end
|
139
|
-
|
139
|
+
|
140
140
|
def method
|
141
141
|
vector.last
|
142
142
|
end
|
143
|
-
|
143
|
+
|
144
144
|
def self.new(db_cache, acting_user, klass)
|
145
145
|
klass_constant = klass.constantize
|
146
146
|
if existing = db_cache.detect { |cached_item| cached_item.vector == [klass_constant] }
|
@@ -160,7 +160,7 @@ module ReactiveRecord
|
|
160
160
|
@root = self
|
161
161
|
db_cache << self
|
162
162
|
end
|
163
|
-
|
163
|
+
|
164
164
|
def apply_method_to_cache(method, &block)
|
165
165
|
@db_cache.inject(nil) do | representative, cache_item |
|
166
166
|
if cache_item.vector == vector
|
@@ -191,11 +191,11 @@ module ReactiveRecord
|
|
191
191
|
new_vector = vector + [method]
|
192
192
|
@db_cache.detect { |cached_item| cached_item.vector == new_vector} || build_new_instances(method)
|
193
193
|
end
|
194
|
-
|
194
|
+
|
195
195
|
def build_new_instances(method)
|
196
196
|
if method == "*all"
|
197
197
|
apply_method_to_cache("*all") { |cache_item| cache_item.value.collect { |record| record.id } }
|
198
|
-
elsif method == "*"
|
198
|
+
elsif method == "*"
|
199
199
|
if @ar_object and @ar_object.length > 0
|
200
200
|
@ar_object.inject(nil) do | value, record | # just using inject so we will return the last value
|
201
201
|
apply_method_to_cache(method) { record }
|
@@ -203,22 +203,22 @@ module ReactiveRecord
|
|
203
203
|
else
|
204
204
|
apply_method_to_cache(method) {[]}
|
205
205
|
end
|
206
|
-
else
|
206
|
+
else
|
207
207
|
apply_method_to_cache(method) { |cache_item| cache_item.value.send(*method) }
|
208
208
|
end
|
209
209
|
end
|
210
|
-
|
210
|
+
|
211
211
|
def as_hash(children = [@ar_object])
|
212
212
|
if @parent
|
213
213
|
if method == "*"
|
214
214
|
if @ar_object.is_a? Array # this happens when a scope is empty there is test case, but
|
215
|
-
@parent.as_hash({}) # does it work for all edge cases?
|
215
|
+
@parent.as_hash({}) # does it work for all edge cases?
|
216
216
|
else
|
217
217
|
@parent.as_hash({@ar_object.id => children})
|
218
218
|
end
|
219
219
|
elsif @ar_object.class < ActiveRecord::Base and children.is_a? Hash
|
220
220
|
@parent.as_hash({method => children.merge({
|
221
|
-
:id => [@ar_object.id],
|
221
|
+
:id => [@ar_object.id],
|
222
222
|
@ar_object.class.inheritance_column => [@ar_object[@ar_object.class.inheritance_column]],
|
223
223
|
})})
|
224
224
|
elsif method == "*all"
|
@@ -232,9 +232,9 @@ module ReactiveRecord
|
|
232
232
|
end
|
233
233
|
|
234
234
|
end
|
235
|
-
|
235
|
+
|
236
236
|
end
|
237
|
-
|
237
|
+
|
238
238
|
def self.load_from_json(tree, target = nil)
|
239
239
|
ignore_all = nil
|
240
240
|
tree.each do |method, value|
|
@@ -251,21 +251,20 @@ module ReactiveRecord
|
|
251
251
|
new_target = target.send *method unless value.is_a? Array # value is an array if scope returns nil
|
252
252
|
elsif value.is_a? Array
|
253
253
|
target.send "#{method}=", value.first
|
254
|
-
elsif value.is_a? Hash and value[:id] and value[:id].first #and
|
254
|
+
elsif value.is_a? Hash and value[:id] and value[:id].first #and
|
255
255
|
association = target.class.reflect_on_association(method)
|
256
256
|
new_target = association.klass.find(value[:id].first)
|
257
257
|
target.send "#{method}=", new_target
|
258
258
|
else
|
259
|
-
new_target = target.send("#{method}=", target.send(method))
|
259
|
+
new_target = target.send("#{method}=", target.send(method))
|
260
260
|
end
|
261
261
|
load_from_json(value, new_target) if new_target
|
262
262
|
end
|
263
|
-
target.save if target.respond_to? :save
|
264
|
-
|
263
|
+
target.save if target.respond_to? :save
|
265
264
|
end
|
266
|
-
|
267
|
-
|
265
|
+
|
266
|
+
|
268
267
|
end
|
269
|
-
|
270
|
-
|
271
|
-
end
|
268
|
+
|
269
|
+
|
270
|
+
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.
|
4
|
+
version: 0.7.15
|
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-09-
|
11
|
+
date: 2015-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|