opal-activerecord 0.0.1

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