reactive-record 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ActiveRecord::Base.send(:define_method, :react_serializer) do
2
+ serializable_hash.merge(ReactiveRecord::Base.get_type_hash(self))
3
+ end
4
+
5
+ ActiveRecord::Relation.send(:define_method, :react_serializer) do
6
+ all.react_serializer
7
+ end
@@ -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 &block; end
122
+
123
+ def detect(&block); @cache.detect &block; end
124
+
125
+ def inject(initial, &block); @cache.inject(initial) &block; 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
@@ -0,0 +1,3 @@
1
+ module ReactiveRecord
2
+ VERSION = "0.7.0"
3
+ 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: []