dynamoid 0.6.1 → 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.
Files changed (92) hide show
  1. data/.travis.yml +4 -0
  2. data/Gemfile +3 -2
  3. data/Gemfile.lock +40 -45
  4. data/README.markdown +55 -25
  5. data/Rakefile +31 -0
  6. data/VERSION +1 -1
  7. data/doc/Dynamoid.html +58 -42
  8. data/doc/Dynamoid/Adapter.html +666 -179
  9. data/doc/Dynamoid/Adapter/AwsSdk.html +752 -236
  10. data/doc/Dynamoid/Associations.html +28 -21
  11. data/doc/Dynamoid/Associations/Association.html +102 -49
  12. data/doc/Dynamoid/Associations/BelongsTo.html +28 -25
  13. data/doc/Dynamoid/Associations/ClassMethods.html +95 -52
  14. data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +28 -25
  15. data/doc/Dynamoid/Associations/HasMany.html +28 -25
  16. data/doc/Dynamoid/Associations/HasOne.html +28 -25
  17. data/doc/Dynamoid/Associations/ManyAssociation.html +138 -94
  18. data/doc/Dynamoid/Associations/SingleAssociation.html +67 -38
  19. data/doc/Dynamoid/Components.html +60 -22
  20. data/doc/Dynamoid/Config.html +61 -44
  21. data/doc/Dynamoid/Config/Options.html +90 -61
  22. data/doc/Dynamoid/Criteria.html +28 -21
  23. data/doc/Dynamoid/Criteria/Chain.html +508 -100
  24. data/doc/Dynamoid/Criteria/ClassMethods.html +26 -19
  25. data/doc/Dynamoid/Dirty.html +424 -0
  26. data/doc/Dynamoid/Dirty/ClassMethods.html +174 -0
  27. data/doc/Dynamoid/Document.html +451 -84
  28. data/doc/Dynamoid/Document/ClassMethods.html +281 -102
  29. data/doc/Dynamoid/Errors.html +29 -22
  30. data/doc/Dynamoid/Errors/ConditionalCheckFailedException.html +141 -0
  31. data/doc/Dynamoid/Errors/DocumentNotValid.html +36 -25
  32. data/doc/Dynamoid/Errors/Error.html +27 -20
  33. data/doc/Dynamoid/Errors/InvalidField.html +27 -19
  34. data/doc/Dynamoid/Errors/InvalidQuery.html +131 -0
  35. data/doc/Dynamoid/Errors/MissingRangeKey.html +27 -19
  36. data/doc/Dynamoid/Fields.html +94 -77
  37. data/doc/Dynamoid/Fields/ClassMethods.html +166 -37
  38. data/doc/Dynamoid/Finders.html +28 -21
  39. data/doc/Dynamoid/Finders/ClassMethods.html +505 -78
  40. data/doc/Dynamoid/IdentityMap.html +492 -0
  41. data/doc/Dynamoid/IdentityMap/ClassMethods.html +534 -0
  42. data/doc/Dynamoid/Indexes.html +41 -28
  43. data/doc/Dynamoid/Indexes/ClassMethods.html +45 -29
  44. data/doc/Dynamoid/Indexes/Index.html +100 -62
  45. data/doc/Dynamoid/Middleware.html +115 -0
  46. data/doc/Dynamoid/Middleware/IdentityMap.html +264 -0
  47. data/doc/Dynamoid/Persistence.html +326 -85
  48. data/doc/Dynamoid/Persistence/ClassMethods.html +275 -109
  49. data/doc/Dynamoid/Validations.html +47 -31
  50. data/doc/_index.html +116 -71
  51. data/doc/class_list.html +13 -7
  52. data/doc/css/full_list.css +4 -2
  53. data/doc/css/style.css +60 -44
  54. data/doc/file.LICENSE.html +26 -19
  55. data/doc/file.README.html +152 -48
  56. data/doc/file_list.html +14 -8
  57. data/doc/frames.html +20 -5
  58. data/doc/index.html +152 -48
  59. data/doc/js/app.js +52 -43
  60. data/doc/js/full_list.js +14 -9
  61. data/doc/js/jquery.js +4 -16
  62. data/doc/method_list.html +446 -540
  63. data/doc/top-level-namespace.html +27 -20
  64. data/{Dynamoid.gemspec → dynamoid.gemspec} +21 -8
  65. data/lib/dynamoid/adapter.rb +11 -10
  66. data/lib/dynamoid/adapter/aws_sdk.rb +40 -19
  67. data/lib/dynamoid/components.rb +2 -1
  68. data/lib/dynamoid/criteria/chain.rb +29 -11
  69. data/lib/dynamoid/dirty.rb +6 -0
  70. data/lib/dynamoid/document.rb +34 -19
  71. data/lib/dynamoid/fields.rb +36 -30
  72. data/lib/dynamoid/finders.rb +7 -5
  73. data/lib/dynamoid/persistence.rb +37 -10
  74. data/spec/app/models/address.rb +2 -0
  75. data/spec/app/models/camel_case.rb +10 -0
  76. data/spec/app/models/car.rb +6 -0
  77. data/spec/app/models/nuclear_submarine.rb +5 -0
  78. data/spec/app/models/subscription.rb +2 -2
  79. data/spec/app/models/vehicle.rb +7 -0
  80. data/spec/dynamoid/adapter/aws_sdk_spec.rb +20 -11
  81. data/spec/dynamoid/adapter_spec.rb +67 -82
  82. data/spec/dynamoid/associations/association_spec.rb +30 -30
  83. data/spec/dynamoid/criteria/chain_spec.rb +56 -9
  84. data/spec/dynamoid/criteria_spec.rb +3 -0
  85. data/spec/dynamoid/dirty_spec.rb +8 -0
  86. data/spec/dynamoid/document_spec.rb +109 -47
  87. data/spec/dynamoid/fields_spec.rb +32 -3
  88. data/spec/dynamoid/finders_spec.rb +12 -0
  89. data/spec/dynamoid/persistence_spec.rb +73 -8
  90. data/spec/spec_helper.rb +1 -0
  91. data/spec/support/with_partitioning.rb +15 -0
  92. metadata +22 -9
@@ -1,6 +1,9 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  describe "Dynamoid::Criteria" do
4
+ before(:all) do
5
+ Magazine.create_table
6
+ end
4
7
 
5
8
  before do
6
9
  @user1 = User.create(:name => 'Josh', :email => 'josh@joshsymonds.com')
@@ -22,6 +22,14 @@ describe 'Dynamoid::Dirty' do
22
22
  tweet.reload
23
23
  tweet.changed?.should be_false
24
24
  end
25
+
26
+ it 'should be empty after an update' do
27
+ tweet = Tweet.create!(:tweet_id => "1", :group => 'abc')
28
+ tweet.update! do |t|
29
+ t.set(msg: "foo")
30
+ end
31
+ tweet.changed?.should be_false
32
+ end
25
33
 
26
34
  it 'track changes after saves' do
27
35
  tweet = Tweet.new(:tweet_id => "1", :group => 'abc')
@@ -4,9 +4,9 @@ describe "Dynamoid::Document" do
4
4
 
5
5
  it 'initializes a new document' do
6
6
  @address = Address.new
7
-
7
+
8
8
  @address.new_record.should be_true
9
- @address.attributes.should == {:id=>nil, :created_at=>nil, :updated_at=>nil, :city=>nil, :options=>nil, :deliverable => nil}
9
+ @address.attributes.should == {:id=>nil, :created_at=>nil, :updated_at=>nil, :city=>nil, :options=>nil, :deliverable => nil, :lock_version => nil}
10
10
  end
11
11
 
12
12
  it 'responds to will_change! methods for all fields' do
@@ -16,21 +16,21 @@ describe "Dynamoid::Document" do
16
16
  @address.should respond_to(:created_at_will_change!)
17
17
  @address.should respond_to(:updated_at_will_change!)
18
18
  end
19
-
19
+
20
20
  it 'initializes a new document with attributes' do
21
21
  @address = Address.new(:city => 'Chicago')
22
-
22
+
23
23
  @address.new_record.should be_true
24
-
25
- @address.attributes.should == {:id=>nil, :created_at=>nil, :updated_at=>nil, :city=>"Chicago", :options=>nil, :deliverable => nil}
24
+
25
+ @address.attributes.should == {:id=>nil, :created_at=>nil, :updated_at=>nil, :city=>"Chicago", :options=>nil, :deliverable => nil, :lock_version => nil}
26
26
  end
27
27
 
28
28
  it 'initializes a new document with a virtual attribute' do
29
29
  @address = Address.new(:zip_code => '12345')
30
-
30
+
31
31
  @address.new_record.should be_true
32
-
33
- @address.attributes.should == {:id=>nil, :created_at=>nil, :updated_at=>nil, :city=>"Chicago", :options=>nil, :deliverable => nil}
32
+
33
+ @address.attributes.should == {:id=>nil, :created_at=>nil, :updated_at=>nil, :city=>"Chicago", :options=>nil, :deliverable => nil, :lock_version => nil}
34
34
  end
35
35
 
36
36
  it 'allows interception of write_attribute on load' do
@@ -41,10 +41,10 @@ describe "Dynamoid::Document" do
41
41
  end
42
42
  Model.new(:city => "Chicago").city.should == "chicago"
43
43
  end
44
-
44
+
45
45
  it 'creates a new document' do
46
46
  @address = Address.create(:city => 'Chicago')
47
-
47
+
48
48
  @address.new_record.should be_false
49
49
  @address.id.should_not be_nil
50
50
  end
@@ -53,61 +53,54 @@ describe "Dynamoid::Document" do
53
53
  @address = Address.create(:city => 'Chicago')
54
54
  Address.exists?(@address.id).should be_true
55
55
  Address.exists?("does-not-exist").should be_false
56
- end
57
-
58
- it 'tests equivalency with itself' do
59
- @address = Address.create(:city => 'Chicago')
60
-
61
- @address.should == @address
56
+ Address.exists?(:city => @address.city).should be_true
57
+ Address.exists?(:city => "does-not-exist").should be_false
62
58
  end
63
59
 
64
- it 'is not equivalent to another document' do
65
- @address.should_not == Address.create
66
- end
67
-
68
- it 'is not equivalent to another object' do
69
- @address = Address.create(:city => 'Chicago')
70
- @address.should_not == "test"
71
- end
72
-
73
- it "isn't equal to nil" do
74
- @address = Address.create(:city => 'Chicago')
75
- @address.should_not == nil
76
- end
77
-
78
60
  it 'gets errors courtesy of ActiveModel' do
79
61
  @address = Address.create(:city => 'Chicago')
80
-
62
+
81
63
  @address.errors.should be_empty
82
64
  @address.errors.full_messages.should be_empty
83
65
  end
84
-
85
- it 'reloads itself and sees persisted changes' do
86
- @address = Address.create
87
-
88
- Address.first.update_attributes(:city => 'Chicago')
89
-
90
- @address.reload.city.should == 'Chicago'
91
- end
92
66
 
93
- it 'reloads document with range key' do
94
- tweet = Tweet.create(:tweet_id => 'x', :group => 'abc')
95
- tweet.reload.group.should == 'abc'
67
+ context '.reload' do
68
+ let(:address){ Address.create }
69
+ let(:message){ Message.create({:text => 'Nice, supporting datetime range!', :time => Time.now.to_datetime}) }
70
+ let(:tweet){ tweet = Tweet.create(:tweet_id => 'x', :group => 'abc') }
71
+
72
+ it 'reflects persisted changes' do
73
+ address.update_attributes(:city => 'Chicago')
74
+ address.reload.city.should == 'Chicago'
75
+ end
76
+
77
+ it 'uses a :consistent_read' do
78
+ Tweet.expects(:find).with(tweet.hash_key, {:range_key => tweet.range_value, :consistent_read => true}).returns(tweet)
79
+ tweet.reload
80
+ end
81
+
82
+ it 'works with range key' do
83
+ tweet.reload.group.should == 'abc'
84
+ end
85
+
86
+ it 'works with a :datetime range key' do
87
+ expect { message.reload }.to_not raise_error(ArgumentError)
88
+ end
96
89
  end
97
-
90
+
98
91
  it 'has default table options' do
99
92
  @address = Address.create
100
-
93
+
101
94
  @address.id.should_not be_nil
102
95
  Address.table_name.should == 'dynamoid_tests_addresses'
103
96
  Address.hash_key.should == :id
104
97
  Address.read_capacity.should == 100
105
98
  Address.write_capacity.should == 20
106
99
  end
107
-
100
+
108
101
  it 'follows any table options provided to it' do
109
102
  @tweet = Tweet.create(:group => 12345)
110
-
103
+
111
104
  lambda {@tweet.id}.should raise_error(NoMethodError)
112
105
  @tweet.tweet_id.should_not be_nil
113
106
  Tweet.table_name.should == 'dynamoid_tests_twitters'
@@ -115,4 +108,73 @@ describe "Dynamoid::Document" do
115
108
  Tweet.read_capacity.should == 200
116
109
  Tweet.write_capacity.should == 200
117
110
  end
111
+
112
+ shared_examples 'it has equality testing and hashing' do
113
+ it 'is equal to itself' do
114
+ document.should == document
115
+ end
116
+
117
+ it 'is equal to another document with the same key(s)' do
118
+ document.should == same
119
+ end
120
+
121
+ it 'is not equal to another document with different key(s)' do
122
+ document.should_not == different
123
+ end
124
+
125
+ it 'is not equal to an object that is not a document' do
126
+ document.should_not == 'test'
127
+ end
128
+
129
+ it 'is not equal to nil' do
130
+ document.should_not == nil
131
+ end
132
+
133
+ it 'hashes documents with the keys to the same value' do
134
+ {document => 1}.should have_key(same)
135
+ end
136
+ end
137
+
138
+ context 'without a range key' do
139
+ it_behaves_like 'it has equality testing and hashing' do
140
+ let(:document) { Address.create(id: 123, city: 'Seattle') }
141
+ let(:different) { Address.create(id: 456, city: 'Seattle') }
142
+ let(:same) { Address.new(id: 123, city: 'Boston') }
143
+ end
144
+ end
145
+
146
+ context 'with a range key' do
147
+ it_behaves_like 'it has equality testing and hashing' do
148
+ let(:document){ Tweet.create(:tweet_id => 'x', :group => 'abc', :msg => 'foo') }
149
+ let(:different) { Tweet.create(:tweet_id => 'y', :group => 'abc', :msg => 'foo') }
150
+ let(:same) { Tweet.new(:tweet_id => 'x', :group => 'abc', :msg => 'bar') }
151
+ end
152
+
153
+ it 'is not equal to another document with the same hash key but a different range value' do
154
+ document = Tweet.create(:tweet_id => 'x', :group => 'abc')
155
+ different = Tweet.create(:tweet_id => 'x', :group => 'xyz')
156
+
157
+ document.should_not == different
158
+ end
159
+ end
160
+
161
+ context 'single table inheritance' do
162
+ it "should have a type" do
163
+ Vehicle.new.type.should == "Vehicle"
164
+ end
165
+
166
+ it "reports the same table name for both base and derived classes" do
167
+ Vehicle.table_name.should == Car.table_name
168
+ Vehicle.table_name.should == NuclearSubmarine.table_name
169
+ end
170
+ end
171
+
172
+ context '#count' do
173
+ it 'returns the number of documents in the table' do
174
+ document = Tweet.create(:tweet_id => 'x', :group => 'abc')
175
+ different = Tweet.create(:tweet_id => 'x', :group => 'xyz')
176
+
177
+ Tweet.count.should == 2
178
+ end
179
+ end
118
180
  end
@@ -36,13 +36,13 @@ describe "Dynamoid::Fields" do
36
36
  @address.updated_at.should_not be_nil
37
37
  @address.updated_at.class.should == DateTime
38
38
  end
39
-
39
+
40
40
  context 'with a saved address' do
41
41
  before do
42
42
  @address = Address.create(:deliverable => true)
43
43
  @original_id = @address.id
44
44
  end
45
-
45
+
46
46
  it 'should write an attribute correctly' do
47
47
  @address.write_attribute(:city, 'Chicago')
48
48
  end
@@ -75,6 +75,12 @@ describe "Dynamoid::Fields" do
75
75
  @address.id.should == @original_id
76
76
  end
77
77
 
78
+ it 'should update only created_at when no params are passed' do
79
+ @initial_updated_at = @address.updated_at
80
+ @address.update_attributes([])
81
+ @address.updated_at.should_not == @initial_updated_at
82
+ end
83
+
78
84
  it 'adds in dirty methods for attributes' do
79
85
  @address.city = 'Chicago'
80
86
  @address.save
@@ -85,7 +91,7 @@ describe "Dynamoid::Fields" do
85
91
  end
86
92
 
87
93
  it 'returns all attributes' do
88
- Address.attributes.should == {:id=>{:type=>:string}, :created_at=>{:type=>:datetime}, :updated_at=>{:type=>:datetime}, :city=>{:type=>:string}, :options=>{:type=>:serialized}, :deliverable => {:type => :boolean}}
94
+ Address.attributes.should == {:id=>{:type=>:string}, :created_at=>{:type=>:datetime}, :updated_at=>{:type=>:datetime}, :city=>{:type=>:string}, :options=>{:type=>:serialized}, :deliverable => {:type => :boolean}, :lock_version => {:type => :integer}}
89
95
  end
90
96
  end
91
97
 
@@ -94,6 +100,18 @@ describe "Dynamoid::Fields" do
94
100
  Address.new city: ("Ten chars " * 6_600)
95
101
  end
96
102
 
103
+ context '.remove_attribute' do
104
+ subject { @address }
105
+ before(:each) do
106
+ Address.field :foobar
107
+ Address.remove_field :foobar
108
+ end
109
+
110
+ it('should not be in the attributes hash') { Address.attributes.should_not have_key(:foobar) }
111
+ it('removes the accessor') { should_not respond_to(:foobar) }
112
+ it('removes the writer') { should_not respond_to(:foobar=) }
113
+ it('removes the interrogative') { should_not respond_to(:foobar?) }
114
+ end
97
115
 
98
116
  context 'default values for fields' do
99
117
  before do
@@ -124,4 +142,15 @@ describe "Dynamoid::Fields" do
124
142
  end
125
143
  end
126
144
 
145
+ context 'single table inheritance' do
146
+ it "has only base class fields on the base class" do
147
+ Vehicle.attributes.keys.to_set.should == Set.new([:type, :description, :created_at, :updated_at, :id])
148
+ end
149
+
150
+ it "has only the base and derived fields on a sub-class" do
151
+ #Only NuclearSubmarines have torpedoes
152
+ Car.attributes.should_not have_key(:torpedoes)
153
+ end
154
+ end
155
+
127
156
  end
@@ -118,6 +118,7 @@ describe "Dynamoid::Finders" do
118
118
  end
119
119
 
120
120
  context 'find_all' do
121
+
121
122
  it 'should return a array of users' do
122
123
  users = (1..10).map { User.create }
123
124
  User.find_all(users.map(&:id)).should eq(users)
@@ -131,5 +132,16 @@ describe "Dynamoid::Finders" do
131
132
  it 'should return an empty array' do
132
133
  User.find_all([]).should eq([])
133
134
  end
135
+
136
+ it 'returns empty array when there are no results' do
137
+ Address.find_all('bad' + @address.id.to_s).should eq []
138
+ end
139
+
140
+ it 'passes options to the adapter' do
141
+ user_ids = [%w(1 red), %w(1 green)]
142
+ Dynamoid::Adapter.expects(:read).with(anything, user_ids, :consistent_read => true)
143
+ User.find_all(user_ids, :consistent_read => true)
144
+ end
145
+
134
146
  end
135
147
  end
@@ -33,7 +33,25 @@ describe "Dynamoid::Persistence" do
33
33
 
34
34
  Dynamoid::Adapter.read("dynamoid_tests_addresses", @address.id)[:id].should == @address.id
35
35
  end
36
-
36
+
37
+ it 'prevents concurrent writes to tables with a lock_version' do
38
+ @address.save!
39
+ a1 = @address
40
+ a2 = Address.find(@address.id)
41
+
42
+ a1.city = 'Seattle'
43
+ a2.city = 'San Francisco'
44
+
45
+ a1.save!
46
+ expect { a2.save! }.to raise_exception(Dynamoid::Errors::ConditionalCheckFailedException)
47
+ end
48
+
49
+ configured_with 'partitioning' do
50
+ it 'raises an error when attempting to use optimistic locking' do
51
+ expect { address.save! }.to raise_exception
52
+ end
53
+ end
54
+
37
55
  it 'assigns itself an id on save only if it does not have one' do
38
56
  @address.id = 'test123'
39
57
  @address.save
@@ -89,14 +107,14 @@ describe "Dynamoid::Persistence" do
89
107
  @address.options = (hash = {:x => [1, 2], "foobar" => 3.14})
90
108
  Address.undump(@address.send(:dump))[:options].should == hash
91
109
  end
92
-
110
+
93
111
  [true, false].each do |bool|
94
112
  it "dumps a #{bool} boolean field" do
95
113
  @address.deliverable = bool
96
114
  Address.undump(@address.send(:dump))[:deliverable].should == bool
97
115
  end
98
116
  end
99
-
117
+
100
118
  it 'raises on an invalid boolean value' do
101
119
  expect do
102
120
  @address.deliverable = true
@@ -142,7 +160,7 @@ describe "Dynamoid::Persistence" do
142
160
 
143
161
  lambda {Address.create(hash)}.should_not raise_error
144
162
  end
145
-
163
+
146
164
  context 'create' do
147
165
  {
148
166
  Tweet => ['with range', { :tweet_id => 1, :group => 'abc' }],
@@ -151,11 +169,11 @@ describe "Dynamoid::Persistence" do
151
169
  it "checks for existence of an existing object #{fields[0]}" do
152
170
  t1 = clazz.new(fields[1])
153
171
  t2 = clazz.new(fields[1])
154
-
172
+
155
173
  t1.save
156
174
  expect do
157
175
  t2.save!
158
- end.to raise_exception AWS::DynamoDB::Errors::ConditionalCheckFailedException
176
+ end.to raise_exception Dynamoid::Errors::ConditionalCheckFailedException
159
177
  end
160
178
  end
161
179
  end
@@ -177,7 +195,7 @@ describe "Dynamoid::Persistence" do
177
195
  clazz.new(:deliverable => true) #undump is called here
178
196
  end.to raise_error(ArgumentError)
179
197
  end
180
-
198
+
181
199
  it 'raises when dumping a column with an unknown field type' do
182
200
  doc = clazz.new
183
201
  doc.deliverable = true
@@ -186,13 +204,29 @@ describe "Dynamoid::Persistence" do
186
204
  end.to raise_error(ArgumentError)
187
205
  end
188
206
  end
189
-
207
+
190
208
  context 'update' do
191
209
 
192
210
  before :each do
193
211
  @tweet = Tweet.create(:tweet_id => 1, :group => 'abc', :count => 5, :tags => ['db', 'sql'], :user_name => 'john')
194
212
  end
195
213
 
214
+ it 'runs before_update callbacks when doing #update' do
215
+ CamelCase.any_instance.expects(:doing_before_update).once.returns(true)
216
+
217
+ CamelCase.create(:color => 'blue').update do |t|
218
+ t.set(:color => 'red')
219
+ end
220
+ end
221
+
222
+ it 'runs after_update callbacks when doing #update' do
223
+ CamelCase.any_instance.expects(:doing_after_update).once.returns(true)
224
+
225
+ CamelCase.create(:color => 'blue').update do |t|
226
+ t.set(:color => 'red')
227
+ end
228
+ end
229
+
196
230
  it 'support add/delete operation on a field' do
197
231
  @tweet.update do |t|
198
232
  t.add(:count => 3)
@@ -222,6 +256,17 @@ describe "Dynamoid::Persistence" do
222
256
  end
223
257
  }.to raise_error(Dynamoid::Errors::ConditionalCheckFailedException)
224
258
  end
259
+
260
+ it 'prevents concurrent saves to tables with a lock_version' do
261
+ @address.save!
262
+ a2 = Address.find(@address.id)
263
+ a2.update! { |a| a.set(:city => "Chicago") }
264
+
265
+ expect do
266
+ @address.city = "Seattle"
267
+ @address.save!
268
+ end.to raise_error(Dynamoid::Errors::ConditionalCheckFailedException)
269
+ end
225
270
 
226
271
  end
227
272
 
@@ -233,4 +278,24 @@ describe "Dynamoid::Persistence" do
233
278
  }.should_not raise_error
234
279
  end
235
280
  end
281
+
282
+ context 'single table inheritance' do
283
+ let(:car) { Car.create(power_locks: false) }
284
+ let(:sub) { NuclearSubmarine.create(torpedoes: 5) }
285
+
286
+ it 'saves subclass objects in the parent table' do
287
+ c = car
288
+ Vehicle.find(c.id).should == c
289
+ end
290
+
291
+ it 'loads subclass item when querying the parent table' do
292
+ c = car
293
+ s = sub
294
+
295
+ Vehicle.all.to_a.tap { |v|
296
+ v.should include(c)
297
+ v.should include(s)
298
+ }
299
+ end
300
+ end
236
301
  end