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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2018a59b979681b71ceb6af018e1acac2a79f92b
4
- data.tar.gz: 22f1155c538b61a0ea36a1fb594c9570ddbb7882
3
+ metadata.gz: 7855dd3a22d52663865ebec642e73a24b63edd47
4
+ data.tar.gz: 3a383477635ae0dc2cab7375e761acbb9a568323
5
5
  SHA512:
6
- metadata.gz: 3da93c95969beaee6501c52670b339b47b221049c8e85a505136ee1a2f9cb2649d3b15da4b8f42ad5968a09538921af24b74a25d5ec243d959736761ef45adc4
7
- data.tar.gz: 6fb6c6d9d1393317ec6f21ea04d904e4f590f19edceab92fa220202670d198179780ce7510f3d7acd7ab2da1653d90dd963b1839af9d8d08e1c771da8a2da33a
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.base_class == @owner_class.base_class and association.attribute != attribute
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(base_class, macro, name, opts)
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, aggregate_parent, *vector)
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
- (record = new(model)).vector = vector unless record
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 need to keep the id unique
128
- existing_record = records[@model].detect { |record| record.attributes[primary_key] == value}
129
- if existing_record
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
- value = self.class.fetch_from_db([@model, [:find, id], method]) || self.class.load_from_db(*vector, method)
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 &block; end
124
-
124
+
125
125
  def detect(&block); @cache.detect &block; end
126
-
126
+
127
127
  def inject(initial, &block); @cache.inject(initial) &block; 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
@@ -1,3 +1,3 @@
1
1
  module ReactiveRecord
2
- VERSION = "0.7.14"
2
+ VERSION = "0.7.15"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reactive-record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.14
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-15 00:00:00.000000000 Z
11
+ date: 2015-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails