reactive-record 0.7.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +29 -0
- data/app/controllers/reactive_record/application_controller.rb +4 -0
- data/app/controllers/reactive_record/reactive_record_controller.rb +22 -0
- data/config/routes.rb +5 -0
- data/lib/Gemfile +17 -0
- data/lib/reactive-record.rb +28 -0
- data/lib/reactive_record/active_record/aggregations.rb +38 -0
- data/lib/reactive_record/active_record/associations.rb +54 -0
- data/lib/reactive_record/active_record/base.rb +9 -0
- data/lib/reactive_record/active_record/class_methods.rb +113 -0
- data/lib/reactive_record/active_record/instance_methods.rb +76 -0
- data/lib/reactive_record/active_record/reactive_record/base.rb +287 -0
- data/lib/reactive_record/active_record/reactive_record/collection.rb +100 -0
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +274 -0
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +262 -0
- data/lib/reactive_record/engine.rb +13 -0
- data/lib/reactive_record/interval.rb +190 -0
- data/lib/reactive_record/serializers.rb +7 -0
- data/lib/reactive_record/server_data_cache.rb +250 -0
- data/lib/reactive_record/version.rb +3 -0
- metadata +191 -0
@@ -0,0 +1,250 @@
|
|
1
|
+
module ReactiveRecord
|
2
|
+
|
3
|
+
# the point is to collect up a all records needed, with whatever attributes were required + primary key, and inheritance column
|
4
|
+
# or get all scope arrays, with the record ids
|
5
|
+
|
6
|
+
# the incoming vector includes the terminal method
|
7
|
+
|
8
|
+
# output is a hash tree of the form
|
9
|
+
# tree ::= {method => tree | [value]} | method's value is either a nested tree or a single value which is wrapped in array
|
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
|
+
# {integer => tree} for collections, each item retrieved will be represented by its id
|
12
|
+
#
|
13
|
+
# example
|
14
|
+
# {
|
15
|
+
# "User" => {
|
16
|
+
# ["find", 12] => {
|
17
|
+
# :id => 12
|
18
|
+
# "email" => ["mitch@catprint.com"]
|
19
|
+
# "todos" => {
|
20
|
+
# "active" => {
|
21
|
+
# 123 =>
|
22
|
+
# {
|
23
|
+
# id: 123,
|
24
|
+
# title: ["get fetch_records_from_db done"]
|
25
|
+
# },
|
26
|
+
# 119 =>
|
27
|
+
# {
|
28
|
+
# id: 119
|
29
|
+
# title: ["go for a swim"]
|
30
|
+
# }
|
31
|
+
# ]
|
32
|
+
# }
|
33
|
+
# }
|
34
|
+
# }
|
35
|
+
# }
|
36
|
+
# }
|
37
|
+
# }
|
38
|
+
|
39
|
+
# To build this tree we first fill values for each individual vector, saving all the intermediate data
|
40
|
+
# when all these are built we build the above hash structure
|
41
|
+
|
42
|
+
# basic
|
43
|
+
# [Todo, [find, 123], title]
|
44
|
+
# -> [[Todo, [find, 123], title], "get fetch_records_from_db done", 123]
|
45
|
+
|
46
|
+
# [User, [find_by_email, "mitch@catprint.com"], first_name]
|
47
|
+
# -> [[User, [find_by_email, "mitch@catprint.com"], first_name], "Mitch", 12]
|
48
|
+
|
49
|
+
# misses
|
50
|
+
# [User, [find_by_email, "foobar@catprint.com"], first_name]
|
51
|
+
# nothing is found so nothing is downloaded
|
52
|
+
# prerendering may do this
|
53
|
+
# [User, [find_by_email, "foobar@catprint.com"]]
|
54
|
+
# which will return a cache object whose id is nil, and value is nil
|
55
|
+
|
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]
|
60
|
+
|
61
|
+
# collection with nested belongs_to
|
62
|
+
# [User, [find, 12], todos, *, team]
|
63
|
+
# -> [[User, [find, 12], todos, *, team, name], "developers", 12, 123, 252]
|
64
|
+
# [[User, [find, 12], todos, *, team, name], nil, 12, 119] <- no team defined for todo<119> so list ends early
|
65
|
+
|
66
|
+
# collections that are empty will deliver nothing
|
67
|
+
# [User, [find, 13], todos, *, team, name] # no todos for user 13
|
68
|
+
# evaluation will get this far: [[User, [find, 13], todos], nil, 13]
|
69
|
+
# nothing will match [User, [find, 13], todos, team, name] so nothing will be downloaded
|
70
|
+
|
71
|
+
|
72
|
+
# aggregate
|
73
|
+
# [User, [find, 12], address, zip_code]
|
74
|
+
# -> [[User, [find, 12], address, zip_code]], "14622", 12] <- note parent id is returned
|
75
|
+
|
76
|
+
# aggregate with a belongs_to
|
77
|
+
# [User, [find, 12], address, country, country_code]
|
78
|
+
# -> [[User, [find, 12], address, country, country_code], "US", 12, 342]
|
79
|
+
|
80
|
+
# collection * (for iterators etc)
|
81
|
+
# [User, [find, 12], todos, overdue, *all]
|
82
|
+
# -> [[User, [find, 12], todos, active, *all], [119, 123], 12]
|
83
|
+
|
84
|
+
# [Todo, [find, 119], owner, todos, active, *all]
|
85
|
+
# -> [[Todo, [find, 119], owner, todos, active, *all], [119, 123], 119, 12]
|
86
|
+
|
87
|
+
|
88
|
+
class ServerDataCache
|
89
|
+
|
90
|
+
def initialize
|
91
|
+
@cache = []
|
92
|
+
@requested_cache_items = []
|
93
|
+
end
|
94
|
+
|
95
|
+
if RUBY_ENGINE != 'opal'
|
96
|
+
|
97
|
+
def [](*vector)
|
98
|
+
vector.inject(CacheItem.new(@cache, vector[0])) { |cache_item, method| cache_item.apply_method method }
|
99
|
+
vector[0] = vector[0].constantize
|
100
|
+
new_items = @cache.select { | cache_item | cache_item.vector == vector}
|
101
|
+
@requested_cache_items += new_items
|
102
|
+
new_items.last.value if new_items.last
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.[](vectors)
|
106
|
+
cache = new
|
107
|
+
vectors.each { |vector| cache[*vector] }
|
108
|
+
cache.as_json
|
109
|
+
end
|
110
|
+
|
111
|
+
def clear_requests
|
112
|
+
@requested_cache_items = []
|
113
|
+
end
|
114
|
+
|
115
|
+
def as_json
|
116
|
+
@requested_cache_items.inject({}) do | hash, cache_item|
|
117
|
+
hash.deep_merge! cache_item.as_hash
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def select(&block); @cache.select █ end
|
122
|
+
|
123
|
+
def detect(&block); @cache.detect █ end
|
124
|
+
|
125
|
+
def inject(initial, &block); @cache.inject(initial) █ end
|
126
|
+
|
127
|
+
class CacheItem
|
128
|
+
|
129
|
+
attr_reader :vector
|
130
|
+
attr_reader :record_chain
|
131
|
+
|
132
|
+
def value
|
133
|
+
@ar_object
|
134
|
+
end
|
135
|
+
|
136
|
+
def method
|
137
|
+
vector.last
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.new(db_cache, klass)
|
141
|
+
return existing if existing = db_cache.detect { |cached_item| cached_item.vector == [klass] }
|
142
|
+
super
|
143
|
+
end
|
144
|
+
|
145
|
+
def initialize(db_cache, klass)
|
146
|
+
klass = klass.constantize
|
147
|
+
@db_cache = db_cache
|
148
|
+
@vector = [klass]
|
149
|
+
@ar_object = klass
|
150
|
+
@record_chain = []
|
151
|
+
@parent = nil
|
152
|
+
db_cache << self
|
153
|
+
end
|
154
|
+
|
155
|
+
def apply_method_to_cache(method, &block)
|
156
|
+
@db_cache.inject(nil) do | representative, cache_item |
|
157
|
+
if cache_item.vector == vector
|
158
|
+
cache_item.clone.instance_eval do
|
159
|
+
@vector = @vector + [method] # don't push it on since you need a new vector!
|
160
|
+
@ar_object = yield cache_item
|
161
|
+
@db_cache << self
|
162
|
+
@parent = cache_item
|
163
|
+
self
|
164
|
+
end
|
165
|
+
else
|
166
|
+
representative
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def apply_method(method)
|
172
|
+
new_vector = vector + [method]
|
173
|
+
@db_cache.detect { |cached_item| cached_item.vector == new_vector} || build_new_instances(method)
|
174
|
+
end
|
175
|
+
|
176
|
+
def build_new_instances(method)
|
177
|
+
if method == "*all"
|
178
|
+
apply_method_to_cache(method) { |cache_item| cache_item.value.collect { |record| record.id }}
|
179
|
+
elsif method == "*" and @ar_object and @ar_object.length > 0
|
180
|
+
@ar_object.inject(nil) do | value, record | # just using inject so we will return the last value
|
181
|
+
apply_method_to_cache(method) { record }
|
182
|
+
end
|
183
|
+
elsif @ar_object.respond_to? [*method].first
|
184
|
+
apply_method_to_cache(method) { |cache_item|
|
185
|
+
cache_item.value.send(*method)}
|
186
|
+
else
|
187
|
+
self
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def as_hash(children = [@ar_object])
|
192
|
+
if @parent
|
193
|
+
if method == "*"
|
194
|
+
@parent.as_hash({@ar_object.id => children})
|
195
|
+
elsif @ar_object.class < ActiveRecord::Base
|
196
|
+
@parent.as_hash({method => children.merge({
|
197
|
+
:id => [@ar_object.id],
|
198
|
+
@ar_object.class.inheritance_column => [@ar_object[@ar_object.class.inheritance_column]],
|
199
|
+
})})
|
200
|
+
elsif method == "*all"
|
201
|
+
@parent.as_hash({method => children.first})
|
202
|
+
else
|
203
|
+
@parent.as_hash({method => children})
|
204
|
+
end
|
205
|
+
else
|
206
|
+
{method.name => children}
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.load_from_json(tree, target = nil)
|
215
|
+
tree.each do |method, value|
|
216
|
+
method = JSON.parse(method) rescue method
|
217
|
+
new_target = nil
|
218
|
+
if !target
|
219
|
+
load_from_json(value, Object.const_get(method))
|
220
|
+
elsif method == "*all"
|
221
|
+
target.replace value.collect { |id| target.proxy_association.klass.find(id) }
|
222
|
+
elsif method.is_a? Integer or method =~ /^[0-9]+$/
|
223
|
+
new_target = target.proxy_association.klass.find(method)
|
224
|
+
target << new_target
|
225
|
+
elsif method.is_a? Array
|
226
|
+
new_target = target.send *method
|
227
|
+
elsif value.is_a? Array
|
228
|
+
target.send "#{method}=", value.first
|
229
|
+
elsif value.is_a? Hash and value[:id] and value[:id].first
|
230
|
+
new_target = target.class.reflect_on_association(method).klass.find(value[:id].first)
|
231
|
+
target.send "#{method}=", new_target
|
232
|
+
else
|
233
|
+
new_target = target.send *method
|
234
|
+
begin
|
235
|
+
new_target = target.send "#{method}=", new_target
|
236
|
+
rescue Exception => e
|
237
|
+
message = "FAILED #{target}.#{method} not set to #{new_target}"
|
238
|
+
`console.error(message)`
|
239
|
+
end
|
240
|
+
end
|
241
|
+
load_from_json(value, new_target) if new_target
|
242
|
+
end
|
243
|
+
target.save if target.respond_to? :save
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
end
|
metadata
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reactive-record
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mitch VanDuyn
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.2.13
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.2.13
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sqlite3
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: opal-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: opal-browser
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: react-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: therubyracer
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: opal-react
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Description of ReactiveRecord.
|
140
|
+
email:
|
141
|
+
- mitch@catprint.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- MIT-LICENSE
|
147
|
+
- README.rdoc
|
148
|
+
- Rakefile
|
149
|
+
- app/controllers/reactive_record/application_controller.rb
|
150
|
+
- app/controllers/reactive_record/reactive_record_controller.rb
|
151
|
+
- config/routes.rb
|
152
|
+
- lib/Gemfile
|
153
|
+
- lib/reactive-record.rb
|
154
|
+
- lib/reactive_record/active_record/aggregations.rb
|
155
|
+
- lib/reactive_record/active_record/associations.rb
|
156
|
+
- lib/reactive_record/active_record/base.rb
|
157
|
+
- lib/reactive_record/active_record/class_methods.rb
|
158
|
+
- lib/reactive_record/active_record/instance_methods.rb
|
159
|
+
- lib/reactive_record/active_record/reactive_record/base.rb
|
160
|
+
- lib/reactive_record/active_record/reactive_record/collection.rb
|
161
|
+
- lib/reactive_record/active_record/reactive_record/isomorphic_base.rb
|
162
|
+
- lib/reactive_record/active_record/reactive_record/while_loading.rb
|
163
|
+
- lib/reactive_record/engine.rb
|
164
|
+
- lib/reactive_record/interval.rb
|
165
|
+
- lib/reactive_record/serializers.rb
|
166
|
+
- lib/reactive_record/server_data_cache.rb
|
167
|
+
- lib/reactive_record/version.rb
|
168
|
+
homepage:
|
169
|
+
licenses: []
|
170
|
+
metadata: {}
|
171
|
+
post_install_message:
|
172
|
+
rdoc_options: []
|
173
|
+
require_paths:
|
174
|
+
- lib
|
175
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '0'
|
185
|
+
requirements: []
|
186
|
+
rubyforge_project:
|
187
|
+
rubygems_version: 2.4.8
|
188
|
+
signing_key:
|
189
|
+
specification_version: 4
|
190
|
+
summary: Summary of ReactiveRecord.
|
191
|
+
test_files: []
|