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 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