reactive-record 0.7.14 → 0.7.15
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/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
|