opal-activerecord 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 04e6c4caafb2f1e23a844f1d9719cd2a1f47af68
4
+ data.tar.gz: 306d390550152f3ade7714f97844dc0679e74267
5
+ SHA512:
6
+ metadata.gz: d2dd02a27d5982fc90bd3ba984c7184b02f9b9d064f5b2aa3cd6b6c3803857979a01a8e8cc59156aa12a3ac612f6e1c39b72b2be1577220bcc6765be1b07d571
7
+ data.tar.gz: 44b465aed34c997a4657e7252b9f9c9b201e8e56532fcbb3f946dc813f49c33eee23ca5d5c14ad92d60cf808f71ad4f9f3522b76beb2629b8ea552ee975662c2
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ # VIM
2
+ .backups
3
+ **/.backups
4
+ *.swp
5
+
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'opal', :github => 'opal/opal'
6
+ gem 'opal-rspec', '0.3.0.beta2'
7
+
8
+ gem 'rspec' # for testing in MRI
9
+
10
+ group :test do
11
+ # for testing compatibility with non-opal activerecord
12
+ gem 'activerecord'
13
+ gem 'sqlite3'
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,77 @@
1
+ GIT
2
+ remote: git://github.com/opal/opal.git
3
+ revision: 139b534a8e14719f1406318d432b305ba76db4b1
4
+ specs:
5
+ opal (0.6.0)
6
+ source_map
7
+ sprockets
8
+
9
+ PATH
10
+ remote: .
11
+ specs:
12
+ opal-activerecord (0.0.1)
13
+ opal (>= 0.5.0, < 1.0.0)
14
+
15
+ GEM
16
+ remote: https://rubygems.org/
17
+ specs:
18
+ activemodel (4.0.0)
19
+ activesupport (= 4.0.0)
20
+ builder (~> 3.1.0)
21
+ activerecord (4.0.0)
22
+ activemodel (= 4.0.0)
23
+ activerecord-deprecated_finders (~> 1.0.2)
24
+ activesupport (= 4.0.0)
25
+ arel (~> 4.0.0)
26
+ activerecord-deprecated_finders (1.0.3)
27
+ activesupport (4.0.0)
28
+ i18n (~> 0.6, >= 0.6.4)
29
+ minitest (~> 4.2)
30
+ multi_json (~> 1.3)
31
+ thread_safe (~> 0.1)
32
+ tzinfo (~> 0.3.37)
33
+ arel (4.0.1)
34
+ atomic (1.1.14)
35
+ builder (3.1.4)
36
+ diff-lcs (1.2.4)
37
+ hike (1.2.3)
38
+ i18n (0.6.5)
39
+ json (1.8.1)
40
+ minitest (4.7.5)
41
+ multi_json (1.8.4)
42
+ opal-rspec (0.3.0.beta2)
43
+ opal (>= 0.6.0, < 1.0.0)
44
+ rack (1.5.2)
45
+ rake (10.1.0)
46
+ rspec (2.14.1)
47
+ rspec-core (~> 2.14.0)
48
+ rspec-expectations (~> 2.14.0)
49
+ rspec-mocks (~> 2.14.0)
50
+ rspec-core (2.14.7)
51
+ rspec-expectations (2.14.3)
52
+ diff-lcs (>= 1.1.3, < 2.0)
53
+ rspec-mocks (2.14.4)
54
+ source_map (3.0.1)
55
+ json
56
+ sprockets (2.10.1)
57
+ hike (~> 1.2)
58
+ multi_json (~> 1.0)
59
+ rack (~> 1.0)
60
+ tilt (~> 1.1, != 1.3.0)
61
+ sqlite3 (1.3.8)
62
+ thread_safe (0.1.3)
63
+ atomic
64
+ tilt (1.4.1)
65
+ tzinfo (0.3.38)
66
+
67
+ PLATFORMS
68
+ ruby
69
+
70
+ DEPENDENCIES
71
+ activerecord
72
+ opal!
73
+ opal-activerecord!
74
+ opal-rspec (= 0.3.0.beta2)
75
+ rake
76
+ rspec
77
+ sqlite3
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) <year> <copyright holders>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Elia Schito
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Opal: ActiveRecord
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ gem 'opal-activerecord'
8
+
9
+ And then execute:
10
+
11
+ $ bundle
12
+
13
+ Or install it yourself as:
14
+
15
+ $ gem install opal-activerecord
16
+
17
+
18
+ ## Usage
19
+
20
+ Inside your `application.js.rb`:
21
+
22
+ ```ruby
23
+ require 'active_record' # to require the whole active record lib
24
+ ```
25
+
26
+ ## Testing
27
+
28
+ There are two ways to run tests. You can run them inside of MRI
29
+ for ease of testing and better debuggability or you can run them
30
+ using Opal (as this is how it will actually be used).
31
+
32
+ * To run in Opal do - rake
33
+ * To run in MRI do - rspec spec
34
+
35
+ In addition to this, you can run the spec against the real active
36
+ record to make sure the tests duplicate the functionality there. To
37
+ run that:
38
+
39
+ * run_with_real_active_record=true rspec spec
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create new Pull Request
48
+
49
+ ## License
50
+
51
+ opal-activerecord is Copyright © 2014 Steve Tuckner. It is free software, and may be redistributed under the terms specified in the LICENSE file (an MIT License).
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require 'opal/rspec/rake_task'
5
+ Opal::RSpec::RakeTask.new(:default)
6
+
7
+ require 'bundler/gem_tasks'
@@ -0,0 +1 @@
1
+ require 'opal/activerecord'
@@ -0,0 +1,5 @@
1
+ require 'opal'
2
+ require 'opal/activerecord/version'
3
+
4
+ # Just register our opal code path with opal build tools
5
+ Opal.append_path File.expand_path('../../../opal', __FILE__)
@@ -0,0 +1,5 @@
1
+ module Opal
2
+ module Activerecord
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'opal/activerecord/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'opal-activerecord'
8
+ gem.version = Opal::Activerecord::VERSION
9
+ gem.authors = ['Steve Tuckner']
10
+ gem.email = ['stevetuckner@stewdle.com']
11
+ gem.summary = %q{A small port of the glorious ActiveRecord for Opal}
12
+ gem.description = %q{
13
+ This implements a subset of the rails/activerecord.
14
+ It currently handles has_many and belongs_to
15
+ associations, saving, finding and simple where
16
+ queries.
17
+ }
18
+ gem.licenses = ['MIT']
19
+ gem.homepage = 'https://github.com/boberetezeke/opal-activerecord'
20
+ gem.rdoc_options << '--main' << 'README' <<
21
+ '--line-numbers' <<
22
+ '--include' << 'opal'
23
+
24
+ gem.files = `git ls-files`.split($/)
25
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
26
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
27
+ gem.require_paths = ['lib']
28
+
29
+ gem.add_dependency 'opal', ['>= 0.5.0', '< 1.0.0']
30
+ gem.add_development_dependency 'rake'
31
+ gem.add_development_dependency 'opal-rspec'
32
+ end
@@ -0,0 +1 @@
1
+ require 'active_record/core'
@@ -0,0 +1,545 @@
1
+ class String
2
+ def singularize
3
+ /^(.*)s$/.match(self)[1]
4
+ end
5
+ end
6
+
7
+ def debug(str)
8
+ #puts(str) #if $debug_on
9
+ end
10
+
11
+ module Arel
12
+ class SelectManager
13
+ attr_accessor :ordering, :limit, :offset
14
+ attr_accessor :table_name, :node
15
+
16
+ def initialize(connection, table_name)
17
+ @connection = connection
18
+ @table_name = table_name
19
+ end
20
+
21
+ def where(node)
22
+ @node = node
23
+ end
24
+
25
+ def execute
26
+ @connection.execute(self)
27
+ end
28
+ end
29
+
30
+ module Nodes
31
+ class BinaryOp
32
+ attr_reader :left_node, :right_node
33
+ def initialize(left_node, right_node)
34
+ @left_node = left_node
35
+ @right_node = right_node
36
+ end
37
+
38
+ def to_s
39
+ "BinaryNode: #{self.class}: left:#{@left_node}, right:#{@right_node}"
40
+ end
41
+ end
42
+
43
+ class And < BinaryOp
44
+ def value(record)
45
+ @left_node.value(record) && @right_node.value(record)
46
+ end
47
+ end
48
+
49
+ class Or < BinaryOp
50
+ def value(record)
51
+ @left_node.value(record) || @right_node.value(record)
52
+ end
53
+ end
54
+
55
+ class Equality < BinaryOp
56
+ def value(record)
57
+ @left_node.value(record) == @right_node.value(record)
58
+ end
59
+ end
60
+
61
+ class NotEqual < BinaryOp
62
+ def value(record)
63
+ @left_node.value(record) != @right_node.value(record)
64
+ end
65
+ end
66
+
67
+ class Literal
68
+ def initialize(value)
69
+ @value = value
70
+ end
71
+
72
+ def value(record)
73
+ @value
74
+ end
75
+
76
+ def to_s
77
+ "Literal: #{@value}"
78
+ end
79
+ end
80
+
81
+ class Symbol
82
+ def initialize(symbol)
83
+ @symbol = symbol
84
+ end
85
+
86
+ def value(record)
87
+ record.send(@symbol)
88
+ end
89
+
90
+ def to_s
91
+ "Symbol: #{@symbol}"
92
+ end
93
+ end
94
+
95
+ class Ordering
96
+ attr_reader :order_str
97
+ def initialize(order_str)
98
+ @order_str = order_str
99
+ end
100
+ end
101
+
102
+ class Limit
103
+ attr_reader :limit
104
+ def initialize(limit)
105
+ @limit = limit
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ module ActiveRecord
112
+ class Association
113
+ attr_reader :foreign_key, :association_type
114
+
115
+ def initialize(klass, association_type, name, options, connection)
116
+ @association_type = association_type
117
+ @klass = klass
118
+ @name = name
119
+ @options = options
120
+ @connection = connection
121
+ if @association_type == :belongs_to
122
+ @foreign_key = "#{name}_id"
123
+ elsif @association_type == :has_many
124
+ @foreign_key = "#{@klass.table_name.singularize}_id"
125
+ end
126
+ end
127
+
128
+ def table_name
129
+ (@association_type == :belongs_to) ? @name.to_s + "s" : @name.to_s
130
+ end
131
+
132
+ def all
133
+ where(1 => 1)
134
+ end
135
+ alias load all
136
+
137
+ def where(query={})
138
+ Relation.new(query, @connection)
139
+ end
140
+
141
+ def to_s
142
+ "#Association: #{@name}: #{@association_type}"
143
+ end
144
+ end
145
+
146
+ class CollectionProxy
147
+ def initialize(connection, owner, association)
148
+ @connection = connection
149
+ @owner = owner
150
+ @association = association
151
+ end
152
+
153
+ def <<(collection)
154
+ debug "CollectionProxy(owner: #{@owner})#<<(#{collection})"
155
+ collection = [collection] unless collection.is_a?(Array)
156
+ collection.each do |obj|
157
+ obj.write_attribute(@association.foreign_key, @owner.id)
158
+ obj.save
159
+ end
160
+ end
161
+
162
+ def method_missing(sym, *args, &block)
163
+ if [:first, :last, :all, :load, :reverse].include?(sym)
164
+ where_clause = "#{@owner.table_name.singularize}_id"
165
+ debug "#{sym}: for table: #{@association.table_name}, where: #{where_clause} == #{@owner.id}"
166
+ Relation.new(@connection, @association.table_name).where(where_clause => @owner.id).send(sym)
167
+ else
168
+ super
169
+ end
170
+ end
171
+ end
172
+
173
+ class Relation
174
+ def initialize(connection, table_name)
175
+ @select_manager = Arel::SelectManager.new(connection, table_name)
176
+ end
177
+
178
+ def execute
179
+ @records = @select_manager.execute
180
+ end
181
+
182
+ def where(query)
183
+ key, value = query.first
184
+ node = eq_node(key, value)
185
+ if query.keys.size > 1
186
+ query.to_a[1..-1].each do |key, value|
187
+ node = Arel::Nodes::And.new(node, eq_node(key, value))
188
+ end
189
+ end
190
+
191
+ @select_manager.where(node)
192
+ self
193
+ end
194
+
195
+ def order(order_str)
196
+ @select_manager.ordering = Arel::Nodes::Ordering.new(order_str)
197
+ self
198
+ end
199
+
200
+ def limit(num)
201
+ @select_manager.limit = Arel::Nodes::Limit.new(num)
202
+ self
203
+ end
204
+
205
+ def offset(index)
206
+ @select_manager.offset = Arel::Nodes::Offset.new(index)
207
+ self
208
+ end
209
+
210
+ def first
211
+ execute.first
212
+ end
213
+
214
+ def last
215
+ execute.last
216
+ end
217
+
218
+ def reverse
219
+ execute.reverse
220
+ end
221
+
222
+ def [](index)
223
+ execute[index]
224
+ end
225
+
226
+ def all
227
+ execute
228
+ end
229
+ alias load all
230
+
231
+ def each
232
+ execute.each { |record| yield record }
233
+ end
234
+
235
+ def eq_node(key, value)
236
+ Arel::Nodes::Equality.new(Arel::Nodes::Symbol.new(key), Arel::Nodes::Literal.new(value))
237
+ end
238
+ end
239
+
240
+ class MemoryStore
241
+ attr_reader :tables
242
+
243
+ def initialize
244
+ @tables = {}
245
+ @next_ids = {}
246
+ end
247
+
248
+ def execute(select_manager)
249
+ debug "MemoryStore#execute: table name = #{select_manager.table_name}"
250
+ debug "MemoryStore#execute: tables = #{@tables.keys}"
251
+ debug "MemoryStore#node = #{select_manager.node}"
252
+ records = @tables[select_manager.table_name.to_s].values.select do |record|
253
+ if select_manager.node
254
+ debug "MemoryStore#execute: checking record: #{record}"
255
+ select_manager.node.value(record)
256
+ else
257
+ true
258
+ end
259
+ end
260
+ debug "MemoryStore#execute: result = #{records.inspect}"
261
+ records
262
+ end
263
+
264
+ def push(table_name, record)
265
+ init_new_table(table_name)
266
+ @tables[table_name][record.id] = record
267
+ end
268
+
269
+ def find(table_name, id)
270
+ if @tables[table_name]
271
+ record = @tables[table_name][id]
272
+ else
273
+ record = nil
274
+ end
275
+
276
+ raise "record not found" unless record
277
+ record
278
+ end
279
+
280
+ def on_change(&call_back)
281
+ @change_callback = call_back
282
+ end
283
+
284
+ def create(table_name, record)
285
+ debug "MemoryStore#Create(#{record})"
286
+ init_new_table(table_name)
287
+ next_id = gen_next_id(table_name)
288
+ @tables[table_name][next_id] = record
289
+ @change_callback.call(:insert, record) if @change_callback
290
+ return next_id
291
+ end
292
+
293
+ def update(table_name, record)
294
+ init_new_table(table_name)
295
+ table = @tables[table_name]
296
+ @change_callback.call(:update, record) if @change_callback && record.attributes != table[record.id]
297
+ table[record.id] = record
298
+ end
299
+
300
+ def destroy(table_name, record)
301
+ @change_callback.call(:delete, record) if @change_callback
302
+ @tables[table_name].delete(record.id)
303
+ end
304
+
305
+ def gen_next_id(table_name)
306
+ next_id = @next_ids[table_name]
307
+ @next_ids[table_name] += 1
308
+ return "T-#{next_id}"
309
+ end
310
+
311
+ def init_new_table(table_name)
312
+ @tables[table_name] ||= {}
313
+ @next_ids[table_name] ||= 1
314
+ end
315
+
316
+ def to_s
317
+ "tables: #{@tables.inspect}, next_ids: #{@next_ids.inspect}"
318
+ end
319
+ end
320
+
321
+ class Base
322
+ attr_accessor :attributes, :observers
323
+
324
+ def self.new_from_json(json)
325
+ object = self.new
326
+ object.attributes = json
327
+ object
328
+ end
329
+
330
+ def self.accepts_nested_attributes_for(*args)
331
+ end
332
+
333
+ def self.default_scope(*args)
334
+ end
335
+
336
+ def self.scope(*args)
337
+ end
338
+
339
+ def self.has_many(name, options={})
340
+ @associations ||= {}
341
+ @associations[name.to_s] = Association.new(self, :has_many, name, options, @connection)
342
+ end
343
+
344
+ def self.belongs_to(name, options={})
345
+ @associations ||= {}
346
+ @associations[name.to_s] = Association.new(self, :belongs_to, name, options, @connection)
347
+ end
348
+
349
+ def self.table_name
350
+ self.to_s.downcase + "s"
351
+ end
352
+
353
+ def self.find(id)
354
+ connection.find(table_name, id)
355
+ end
356
+
357
+ def self.associations
358
+ @associations || {}
359
+ end
360
+
361
+ def self.after_initialize(sym)
362
+ @after_initialize_callback = sym
363
+ end
364
+
365
+ def self.connection
366
+ # FIXME: Base.connection seems very hacky, ideally I would like
367
+ # to do something like super.respond_to?(:connection)
368
+ #
369
+ @connection || Base.connection
370
+ end
371
+
372
+ def self.connection=(connection)
373
+ @connection = connection
374
+ end
375
+
376
+ def self.create(*args)
377
+ obj = self.new(*args)
378
+ #obj.save
379
+ obj
380
+ end
381
+
382
+ def self.method_missing(sym, *args)
383
+ if [:first, :last, :all, :where].include?(sym)
384
+ Relation.new(connection, table_name).send(sym, *args)
385
+ else
386
+ super
387
+ end
388
+ end
389
+
390
+ def self.new(*args)
391
+ super(*args)
392
+ end
393
+
394
+ def initialize(initializers={})
395
+ @attributes = {}
396
+ @associations = {}
397
+ @observers = {}
398
+ initializers.each do |initializer, value|
399
+ @attributes[initializer.to_s] = value
400
+ end
401
+ end
402
+
403
+ def method_missing(sym, *args)
404
+ method_name = sym.to_s
405
+ debug "Base#method_missing: #{method_name}, #{attributes}"
406
+ if m = /(.*)=$/.match(method_name)
407
+ val = write_value(m[1], args.shift)
408
+ else
409
+ val = read_value(method_name)
410
+ end
411
+ debug "Base#method_missing (at end), val = #{val}"
412
+ val
413
+ end
414
+
415
+ # stolen from ActiveRecord:core.rb
416
+ def ==(comparison_object)
417
+ super ||
418
+ comparison_object.instance_of?(self.class) &&
419
+ !id.nil? &&
420
+ comparison_object.id == id
421
+ end
422
+ alias :eql? :==
423
+
424
+ def on_change(sym, &block)
425
+ str = sym.to_s
426
+ self.observers ||= {}
427
+ self.observers[str] ||= []
428
+ self.observers[str].push(block)
429
+ debug "Base#on_change: self.observers = #{self.observers.inspect}"
430
+ end
431
+
432
+ def write_attribute(attribute_name, new_value)
433
+ attribute_name = attribute_name.to_s
434
+ old_value = self.attributes[attribute_name]
435
+ self.attributes[attribute_name] = new_value
436
+
437
+ if self.observers[attribute_name] then
438
+ self.observers[attribute_name].each do |observer|
439
+ observer.call(old_value, new_value)
440
+ end
441
+ end
442
+ end
443
+
444
+ def read_attribute(attribute_name)
445
+ self.attributes[attribute_name.to_s]
446
+ end
447
+
448
+ def write_value(name, new_value)
449
+ assoc = self.class.associations[name]
450
+ if assoc
451
+ if self.id
452
+ if assoc.association_type == :has_many
453
+ new_value.each do |value|
454
+ value.write_attribute("#{table_name.singularize}_id", self.id)
455
+ value.save
456
+ end
457
+ elsif assoc.association_type == :belongs_to
458
+ if new_value.id
459
+ write_attribute("#{new_value.table_name}_id", new_value.id)
460
+
461
+ else
462
+ write_attribute(name, new_value)
463
+ end
464
+ end
465
+ else
466
+ write_attribute(name, new_value)
467
+ end
468
+ else
469
+ write_attribute(name, new_value)
470
+ end
471
+ end
472
+
473
+ def read_value(name)
474
+ debug "Base#read_value, name = #{name}, self.class.associations = #{self.class.associations.inspect}"
475
+ if assoc = self.class.associations[name]
476
+ val = (assoc.association_type == :has_many)
477
+ if assoc.association_type == :has_many
478
+ if self.id
479
+ CollectionProxy.new(connection, self, assoc)
480
+ else
481
+ read_attribute(name) || []
482
+ end
483
+ elsif assoc.association_type == :belongs_to
484
+ if self.id
485
+ Relation.new(connection, assoc.table_name).where("id" => read_attribute("#{assoc.table_name}_id")).first
486
+ else
487
+ read_attribute(name)
488
+ end
489
+ end
490
+ else
491
+ read_attribute(name)
492
+ end
493
+ end
494
+
495
+ def save
496
+ debug "save: memory(before) = #{connection}"
497
+ debug "save: self(before): #{self}"
498
+ self.class.associations.to_a.select do |name_and_assoc|
499
+ name = name_and_assoc[0]
500
+ assoc = name_and_assoc[1]
501
+ assoc.association_type == :belongs_to
502
+ end.each do |name_and_assoc|
503
+ name = name_and_assoc[0]
504
+ assoc = name_and_assoc[1]
505
+ debug "name = #{name}, #{assoc}"
506
+ debug "value = #{read_attribute(name).inspect}"
507
+ belongs_to_value = read_attribute(name)
508
+ if belongs_to_value
509
+ debug "save: has belongs_to_value: id(#{belongs_to_value.id}), #{belongs_to_value.attributes}"
510
+ belongs_to_value.save unless belongs_to_value.id
511
+ @attributes["#{name}_id"] = belongs_to_value.id
512
+ end
513
+ end
514
+
515
+ debug "save: self(after): #{self}"
516
+ if self.id
517
+ connection.update(table_name, self)
518
+ else
519
+ @attributes['id'] = connection.create(table_name, self)
520
+ end
521
+
522
+ debug "save: memory(after) = #{connection}"
523
+ end
524
+
525
+ def destroy
526
+ @connection.destroy(table_name, self)
527
+ end
528
+
529
+ def id
530
+ @attributes['id']
531
+ end
532
+
533
+ def table_name
534
+ self.class.table_name
535
+ end
536
+
537
+ def connection
538
+ self.class.connection
539
+ end
540
+
541
+ def to_s
542
+ "#{self.class}:#{self.attributes}"
543
+ end
544
+ end
545
+ end
@@ -0,0 +1 @@
1
+ require 'active_record'
@@ -0,0 +1,297 @@
1
+ require 'spec_helper'
2
+
3
+ class A < ActiveRecord::Base
4
+ end
5
+
6
+ class B < ActiveRecord::Base
7
+ has_many :cs
8
+ end
9
+
10
+ class C < ActiveRecord::Base
11
+ belongs_to :b
12
+ has_many :ds
13
+ end
14
+
15
+ class D < ActiveRecord::Base
16
+ belongs_to :c
17
+ belongs_to :e
18
+ end
19
+
20
+ class E < ActiveRecord::Base
21
+ has_many :ds
22
+ has_many :cs, :through => :ds
23
+ end
24
+
25
+ describe "ActiveRecord::Base" do
26
+ if running_with_real_active_record
27
+ before do
28
+ ActiveRecord::Base.establish_connection(
29
+ adapter: 'sqlite3',
30
+ database: 'test.sqlite3'
31
+ )
32
+ ActiveRecord::Base.connection.create_table("as") {|t| t.integer :x; t.integer :y}
33
+ ActiveRecord::Base.connection.create_table("bs") {|t| t.integer :x; t.integer :y}
34
+ ActiveRecord::Base.connection.create_table("cs") {|t| t.integer :x; t.integer :y; t.integer :b_id}
35
+ ActiveRecord::Base.connection.create_table("ds") {|t| t.integer :x; t.integer :y; t.integer :c_id; t.integer :e_id}
36
+ ActiveRecord::Base.connection.create_table("es") {|t| t.integer :x; t.integer :y}
37
+ end
38
+
39
+ after do
40
+ ActiveRecord::Base.connection.drop_table("as")
41
+ ActiveRecord::Base.connection.drop_table("bs")
42
+ ActiveRecord::Base.connection.drop_table("cs")
43
+ ActiveRecord::Base.connection.drop_table("ds")
44
+ ActiveRecord::Base.connection.drop_table("es")
45
+ end
46
+ else
47
+ # only set memory_store for opal
48
+ let(:memory_store) { ActiveRecord::MemoryStore.new }
49
+ before do
50
+ ActiveRecord::Base.connection = memory_store
51
+ end
52
+ end
53
+
54
+ if !running_with_real_active_record
55
+ describe ".new_from_json" do
56
+ context "when constructing just one object" do
57
+ it "should set attributes on a class with no relationships" do
58
+ a = A.new_from_json({"x" => 1, "y" => 2})
59
+ expect(a.x).to eq(1)
60
+ expect(a.y).to eq(2)
61
+ end
62
+
63
+ it "should set attributes on a class with a has_many relationship" do
64
+ b = B.new_from_json({"x" => 1, "y" => 2})
65
+ expect(b.x).to eq(1)
66
+ expect(b.y).to eq(2)
67
+ end
68
+
69
+ it "should set attributes on a class with a belongs_to relationship" do
70
+ d = D.new_from_json({"x" => 1, "y" => 2})
71
+ expect(d.x).to eq(1)
72
+ expect(d.y).to eq(2)
73
+ end
74
+ end
75
+
76
+ =begin
77
+ context "when contructing an object with a has_many that contains embedded has_many objects" do
78
+ it "should create the first object and the has_many objects" do
79
+ b = B.new_from_json({x: 1, y: 2, cs: [{s: 3, t: 4}]})
80
+ expect(b.x).to eq(1)
81
+ expect(b.y).to eq(2)
82
+ expect(b.c.size).to eq(1)
83
+ #expect(b.c.first.s).to eq(3)
84
+ #expect(b.c.first.t).to eq(4)
85
+ end
86
+ end
87
+ =end
88
+ end
89
+ end
90
+
91
+ context "when using an active record model with no associations" do
92
+ it "should be true" do
93
+ expect(nil).to eq(nil)
94
+ end
95
+ end
96
+
97
+ context "when using an active record model with no associations" do
98
+
99
+ context "when starting with a new model" do
100
+ let(:a) { A.new(x:1) }
101
+
102
+ context "when testing for equality" do
103
+ let(:a1) { A.new(x:1, y:1) }
104
+ let(:a2) { A.new(x:1, y:1) }
105
+
106
+ it "is equal if the objects are the same object" do
107
+ expect(a1 == a1).to eq(true)
108
+ end
109
+
110
+ it "is not equal if the objects are different objects" do
111
+ expect(a1 == a2).to eq(false)
112
+ end
113
+ end
114
+
115
+ it "can create one" do
116
+ expect(a.x).to eq(1)
117
+ end
118
+
119
+ it "has no id" do
120
+ expect(a.id).to be_nil
121
+ end
122
+
123
+ it "has an id after save" do
124
+ a.save
125
+ expect(a.id).to_not be_nil
126
+ end
127
+
128
+ if !running_with_real_active_record
129
+ it "is in the store after the save" do
130
+ a.save
131
+ expect(memory_store.tables["as"].size).to eq(1)
132
+ end
133
+ end
134
+
135
+ end
136
+
137
+ context "when searching for objects" do
138
+ let(:a1) { A.new(x:1, y:1) }
139
+ let(:a2) { A.new(x:1, y:2) }
140
+ let(:a3) { A.new(x:2, y:2) }
141
+
142
+ before do
143
+ a1.save
144
+ a2.save
145
+ a3.save
146
+ end
147
+
148
+ context "when testing for equality" do
149
+ it "is equal if the objects have the same id" do
150
+ expect(a1 == A.first).to eq(true)
151
+ end
152
+
153
+ it "is not equal if the objects are different objects" do
154
+ expect(a2 == A.first).to eq(false)
155
+ end
156
+ end
157
+
158
+ it "finds the first object saved when using first" do
159
+ expect(A.first.x).to eq(1)
160
+ end
161
+
162
+ it "finds the last object saved when using last" do
163
+ expect(A.last.x).to eq(2)
164
+ end
165
+
166
+ it "returns all records when using load" do
167
+ expect(A.all.map{|a| a.x}.sort).to eq([1,1,2])
168
+ end
169
+
170
+ it "returns the record with 1 when using where" do
171
+ expect(A.where(x:1,y:1).load.map{|a| [a.x,a.y]}).to eq([[1,1]])
172
+ end
173
+
174
+ it "returns the record with x:1 and y:3 when using where" do
175
+ expect(A.where(x:1).load.map{|a| a.y}).to eq([1,2])
176
+ end
177
+ end
178
+ end
179
+
180
+ context "when using an active record models with has_many/belongs_to associations" do
181
+ let(:b) { B.new(x:1) }
182
+ let(:c) { C.new(y:1) }
183
+ let(:d) { D.new(x:1, y:1) }
184
+
185
+ context "when the objects are not yet saved" do
186
+
187
+ it "has a B object with [] for the C association" do
188
+ expect(b.cs).to eq([])
189
+ end
190
+
191
+ it "has a C object with nil for the B association" do
192
+ expect(c.b).to be_nil
193
+ end
194
+
195
+ it "allows setting of C object on B" do
196
+ b.cs = [c]
197
+ expect(b.cs).to eq([c])
198
+ end
199
+
200
+ it "allows setting of B object on C" do
201
+ c.b = b
202
+ expect(c.b).to eq(b)
203
+ end
204
+
205
+ it "saves b when saving c" do
206
+ c.b = b
207
+ c.save
208
+ expect(b.id).to_not be_nil
209
+ end
210
+
211
+ it "saves b and c when saving d" do
212
+ c.b = b
213
+ d.c = c
214
+ d.save
215
+ expect(c.id).to_not be_nil
216
+ expect(b.id).to_not be_nil
217
+ end
218
+ end
219
+
220
+ context "when the has many side is saved only" do
221
+ before do
222
+ b.save
223
+ end
224
+
225
+ context "when setting cs" do
226
+ it "allows setting of C object on B" do
227
+ b.cs = [c]
228
+ expect(b.cs.load).to eq([c])
229
+ end
230
+
231
+ it "saves C object when associating it with B" do
232
+ b.cs = [c]
233
+ expect(c.id).to_not be_nil
234
+ end
235
+ end
236
+
237
+ context "when appending to cs" do
238
+ it "allows setting of C object on B" do
239
+ b.cs << c
240
+ expect(b.cs.load).to eq([c])
241
+ end
242
+
243
+ it "saves C object when associating it with B" do
244
+ b.cs << c
245
+ expect(c.id).to_not be_nil
246
+ end
247
+ end
248
+ end
249
+
250
+ context "when the belongs_to side is saved only" do
251
+ before do
252
+ c.save
253
+ end
254
+
255
+ context "when setting b" do
256
+
257
+ it "doesn't save b on assignment to c" do
258
+ c.b = b
259
+ expect(b.id).to be_nil
260
+ end
261
+
262
+ it "saves b when saving c" do
263
+ c.b = b
264
+ c.save
265
+ expect(b.id).to_not be_nil
266
+ end
267
+ end
268
+ end
269
+
270
+ context "when objects are saved" do
271
+ before do
272
+ b.save
273
+ c.save
274
+ end
275
+
276
+ it "has a B object with [] for the C association" do
277
+ expect(b.cs.load).to eq([])
278
+ end
279
+
280
+ it "has a C object with nil for the B association" do
281
+ expect(c.b).to be_nil
282
+ end
283
+
284
+ it "allows setting of C object on B" do
285
+ b.cs = [c]
286
+ expect(b.cs.load).to eq([c])
287
+ end
288
+
289
+ it "allows setting of B object on C" do
290
+ c.b = b
291
+ c.save
292
+ expect(c.b).to eq(b)
293
+ end
294
+ end
295
+ end
296
+ end
297
+
@@ -0,0 +1,28 @@
1
+
2
+ if ENV['run_with_real_active_record']
3
+ Bundler.require(:test)
4
+ require "active_record"
5
+ else
6
+ if RUBY_ENGINE == "opal"
7
+ require 'opal-rspec'
8
+ require 'opal-activerecord'
9
+ else
10
+ require_relative '../opal/active_record/core'
11
+ end
12
+ end
13
+
14
+
15
+ module TestUnitHelpers
16
+ def assert_equal actual, expected
17
+ actual.should == expected
18
+ end
19
+ end
20
+
21
+ RSpec.configure do |config|
22
+ config.include TestUnitHelpers
23
+ end
24
+
25
+ def running_with_real_active_record
26
+ ENV['run_with_real_active_record']
27
+ end
28
+
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opal-activerecord
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Steve Tuckner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: opal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.0
20
+ - - <
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.5.0
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: opal-rspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ description: "\n This implements a subset of the rails/activerecord.\n
62
+ \ It currently handles has_many and belongs_to\n associations,
63
+ saving, finding and simple where\n queries. \n "
64
+ email:
65
+ - stevetuckner@stewdle.com
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - Gemfile.lock
73
+ - LICENSE
74
+ - LICENSE.txt
75
+ - README.md
76
+ - Rakefile
77
+ - lib/opal-activerecord.rb
78
+ - lib/opal/activerecord.rb
79
+ - lib/opal/activerecord/version.rb
80
+ - opal-activerecord.gemspec
81
+ - opal/active_record.rb
82
+ - opal/active_record/core.rb
83
+ - opal/opal-activerecord.rb
84
+ - spec/activerecord_spec.rb
85
+ - spec/spec_helper.rb
86
+ homepage: https://github.com/boberetezeke/opal-activerecord
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options:
92
+ - --main
93
+ - README
94
+ - --line-numbers
95
+ - --include
96
+ - opal
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.0.3
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: A small port of the glorious ActiveRecord for Opal
115
+ test_files:
116
+ - spec/activerecord_spec.rb
117
+ - spec/spec_helper.rb