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.
@@ -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: []