activerecord 2.2.3 → 2.3.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +438 -396
- data/Rakefile +4 -2
- data/lib/active_record.rb +46 -43
- data/lib/active_record/association_preload.rb +34 -19
- data/lib/active_record/associations.rb +193 -251
- data/lib/active_record/associations/association_collection.rb +38 -21
- data/lib/active_record/associations/association_proxy.rb +11 -4
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -2
- data/lib/active_record/associations/has_many_association.rb +2 -2
- data/lib/active_record/associations/has_many_through_association.rb +8 -8
- data/lib/active_record/associations/has_one_association.rb +11 -2
- data/lib/active_record/attribute_methods.rb +1 -0
- data/lib/active_record/autosave_association.rb +349 -0
- data/lib/active_record/base.rb +292 -106
- data/lib/active_record/batches.rb +73 -0
- data/lib/active_record/calculations.rb +34 -16
- data/lib/active_record/callbacks.rb +37 -8
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +16 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +103 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +28 -25
- data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +50 -21
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -41
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +41 -21
- data/lib/active_record/dirty.rb +1 -1
- data/lib/active_record/dynamic_scope_match.rb +25 -0
- data/lib/active_record/fixtures.rb +193 -198
- data/lib/active_record/locale/en.yml +1 -1
- data/lib/active_record/locking/optimistic.rb +33 -0
- data/lib/active_record/migration.rb +8 -2
- data/lib/active_record/named_scope.rb +13 -6
- data/lib/active_record/nested_attributes.rb +329 -0
- data/lib/active_record/query_cache.rb +25 -13
- data/lib/active_record/reflection.rb +6 -1
- data/lib/active_record/schema_dumper.rb +2 -0
- data/lib/active_record/serialization.rb +3 -1
- data/lib/active_record/serializers/json_serializer.rb +19 -0
- data/lib/active_record/serializers/xml_serializer.rb +28 -13
- data/lib/active_record/session_store.rb +318 -0
- data/lib/active_record/test_case.rb +15 -9
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/transactions.rb +58 -8
- data/lib/active_record/validations.rb +29 -24
- data/lib/active_record/version.rb +2 -2
- data/test/cases/ar_schema_test.rb +0 -1
- data/test/cases/associations/belongs_to_associations_test.rb +35 -131
- data/test/cases/associations/cascaded_eager_loading_test.rb +8 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +29 -0
- data/test/cases/associations/eager_test.rb +137 -7
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +45 -7
- data/test/cases/associations/has_many_associations_test.rb +110 -149
- data/test/cases/associations/has_many_through_associations_test.rb +39 -7
- data/test/cases/associations/has_one_associations_test.rb +39 -92
- data/test/cases/associations/has_one_through_associations_test.rb +34 -3
- data/test/cases/associations/inner_join_association_test.rb +0 -5
- data/test/cases/associations/join_model_test.rb +5 -7
- data/test/cases/attribute_methods_test.rb +13 -1
- data/test/cases/autosave_association_test.rb +901 -0
- data/test/cases/base_test.rb +41 -21
- data/test/cases/batches_test.rb +61 -0
- data/test/cases/calculations_test.rb +37 -17
- data/test/cases/callbacks_test.rb +43 -5
- data/test/cases/connection_pool_test.rb +25 -0
- data/test/cases/copy_table_test_sqlite.rb +11 -0
- data/test/cases/datatype_test_postgresql.rb +1 -0
- data/test/cases/defaults_test.rb +37 -26
- data/test/cases/dirty_test.rb +26 -2
- data/test/cases/finder_test.rb +79 -44
- data/test/cases/fixtures_test.rb +15 -19
- data/test/cases/helper.rb +26 -19
- data/test/cases/inheritance_test.rb +2 -2
- data/test/cases/json_serialization_test.rb +1 -1
- data/test/cases/locking_test.rb +23 -5
- data/test/cases/method_scoping_test.rb +126 -3
- data/test/cases/migration_test.rb +253 -237
- data/test/cases/named_scope_test.rb +73 -3
- data/test/cases/nested_attributes_test.rb +509 -0
- data/test/cases/query_cache_test.rb +0 -4
- data/test/cases/reflection_test.rb +13 -3
- data/test/cases/reload_models_test.rb +3 -1
- data/test/cases/repair_helper.rb +50 -0
- data/test/cases/schema_dumper_test.rb +0 -1
- data/test/cases/transactions_test.rb +177 -12
- data/test/cases/validations_i18n_test.rb +288 -294
- data/test/cases/validations_test.rb +230 -180
- data/test/cases/xml_serialization_test.rb +19 -1
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/member_types.yml +6 -0
- data/test/fixtures/members.yml +3 -1
- data/test/fixtures/people.yml +10 -1
- data/test/fixtures/toys.yml +4 -0
- data/test/models/author.rb +1 -2
- data/test/models/bird.rb +3 -0
- data/test/models/category.rb +1 -0
- data/test/models/company.rb +3 -0
- data/test/models/developer.rb +12 -0
- data/test/models/event.rb +3 -0
- data/test/models/member.rb +1 -0
- data/test/models/member_detail.rb +1 -0
- data/test/models/member_type.rb +3 -0
- data/test/models/owner.rb +2 -1
- data/test/models/parrot.rb +2 -0
- data/test/models/person.rb +6 -0
- data/test/models/pet.rb +2 -1
- data/test/models/pirate.rb +55 -1
- data/test/models/post.rb +6 -0
- data/test/models/project.rb +1 -0
- data/test/models/reply.rb +6 -0
- data/test/models/ship.rb +8 -1
- data/test/models/ship_part.rb +5 -0
- data/test/models/topic.rb +13 -1
- data/test/models/toy.rb +4 -0
- data/test/schema/schema.rb +35 -2
- metadata +70 -9
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -1,11 +1,19 @@
|
|
1
1
|
require "cases/helper"
|
2
2
|
require 'models/post'
|
3
3
|
require 'models/person'
|
4
|
+
require 'models/reference'
|
5
|
+
require 'models/job'
|
4
6
|
require 'models/reader'
|
5
7
|
require 'models/comment'
|
8
|
+
require 'models/tag'
|
9
|
+
require 'models/tagging'
|
10
|
+
require 'models/author'
|
11
|
+
require 'models/owner'
|
12
|
+
require 'models/pet'
|
13
|
+
require 'models/toy'
|
6
14
|
|
7
15
|
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
|
8
|
-
fixtures :posts, :readers, :people, :comments, :authors
|
16
|
+
fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys
|
9
17
|
|
10
18
|
def test_associate_existing
|
11
19
|
assert_queries(2) { posts(:thinking);people(:david) }
|
@@ -84,6 +92,24 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
|
|
84
92
|
assert posts(:welcome).reload.people(true).empty?
|
85
93
|
end
|
86
94
|
|
95
|
+
def test_destroy_association
|
96
|
+
assert_difference "Person.count", -1 do
|
97
|
+
posts(:welcome).people.destroy(people(:michael))
|
98
|
+
end
|
99
|
+
|
100
|
+
assert posts(:welcome).reload.people.empty?
|
101
|
+
assert posts(:welcome).people(true).empty?
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_destroy_all
|
105
|
+
assert_difference "Person.count", -1 do
|
106
|
+
posts(:welcome).people.destroy_all
|
107
|
+
end
|
108
|
+
|
109
|
+
assert posts(:welcome).reload.people.empty?
|
110
|
+
assert posts(:welcome).people(true).empty?
|
111
|
+
end
|
112
|
+
|
87
113
|
def test_replace_association
|
88
114
|
assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)}
|
89
115
|
|
@@ -201,6 +227,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
|
|
201
227
|
assert_equal 2, people(:michael).posts.count(:include => :readers)
|
202
228
|
end
|
203
229
|
|
230
|
+
def test_inner_join_with_quoted_table_name
|
231
|
+
assert_equal 2, people(:michael).jobs.size
|
232
|
+
end
|
233
|
+
|
204
234
|
def test_get_ids
|
205
235
|
assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort
|
206
236
|
end
|
@@ -221,12 +251,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
|
|
221
251
|
assert !person.posts.loaded?
|
222
252
|
end
|
223
253
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
# nothing
|
229
|
-
end
|
254
|
+
def test_association_proxy_transaction_method_starts_transaction_in_association_class
|
255
|
+
Tag.expects(:transaction)
|
256
|
+
Post.find(:first).tags.transaction do
|
257
|
+
# nothing
|
230
258
|
end
|
231
259
|
end
|
232
260
|
|
@@ -244,4 +272,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
|
|
244
272
|
author.author_favorites.create(:favorite_author_id => 3)
|
245
273
|
assert_equal post.author.author_favorites, post.author_favorites
|
246
274
|
end
|
275
|
+
|
276
|
+
def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys
|
277
|
+
assert_equal 1, owners(:blackbeard).toys.count
|
278
|
+
end
|
247
279
|
end
|
@@ -59,8 +59,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def test_type_mismatch
|
62
|
-
|
63
|
-
|
62
|
+
assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 }
|
63
|
+
assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) }
|
64
64
|
end
|
65
65
|
|
66
66
|
def test_natural_assignment
|
@@ -76,7 +76,25 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
76
76
|
companies(:first_firm).save
|
77
77
|
assert_nil companies(:first_firm).account
|
78
78
|
# account is dependent, therefore is destroyed when reference to owner is lost
|
79
|
-
|
79
|
+
assert_raise(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_nullification_on_association_change
|
83
|
+
firm = companies(:rails_core)
|
84
|
+
old_account_id = firm.account.id
|
85
|
+
firm.account = Account.new
|
86
|
+
# account is dependent with nullify, therefore its firm_id should be nil
|
87
|
+
assert_nil Account.find(old_account_id).firm_id
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_association_changecalls_delete
|
91
|
+
companies(:first_firm).deletable_account = Account.new
|
92
|
+
assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id]
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_association_change_calls_destroy
|
96
|
+
companies(:first_firm).account = Account.new
|
97
|
+
assert_equal [companies(:first_firm).id], Account.destroyed_account_ids[companies(:first_firm).id]
|
80
98
|
end
|
81
99
|
|
82
100
|
def test_natural_assignment_to_already_associated_record
|
@@ -193,28 +211,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
193
211
|
assert_equal account, firm.account
|
194
212
|
end
|
195
213
|
|
196
|
-
def test_build_before_child_saved
|
197
|
-
firm = Firm.find(1)
|
198
|
-
|
199
|
-
account = firm.account.build("credit_limit" => 1000)
|
200
|
-
assert_equal account, firm.account
|
201
|
-
assert account.new_record?
|
202
|
-
assert firm.save
|
203
|
-
assert_equal account, firm.account
|
204
|
-
assert !account.new_record?
|
205
|
-
end
|
206
|
-
|
207
|
-
def test_build_before_either_saved
|
208
|
-
firm = Firm.new("name" => "GlobalMegaCorp")
|
209
|
-
|
210
|
-
firm.account = account = Account.new("credit_limit" => 1000)
|
211
|
-
assert_equal account, firm.account
|
212
|
-
assert account.new_record?
|
213
|
-
assert firm.save
|
214
|
-
assert_equal account, firm.account
|
215
|
-
assert !account.new_record?
|
216
|
-
end
|
217
|
-
|
218
214
|
def test_failing_build_association
|
219
215
|
firm = Firm.new("name" => "GlobalMegaCorp")
|
220
216
|
firm.save
|
@@ -253,16 +249,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
253
249
|
firm.destroy
|
254
250
|
end
|
255
251
|
|
256
|
-
def test_assignment_before_parent_saved
|
257
|
-
firm = Firm.new("name" => "GlobalMegaCorp")
|
258
|
-
firm.account = a = Account.find(1)
|
259
|
-
assert firm.new_record?
|
260
|
-
assert_equal a, firm.account
|
261
|
-
assert firm.save
|
262
|
-
assert_equal a, firm.account
|
263
|
-
assert_equal a, firm.account(true)
|
264
|
-
end
|
265
|
-
|
266
252
|
def test_finding_with_interpolated_condition
|
267
253
|
firm = Firm.find(:first)
|
268
254
|
superior = firm.clients.create(:name => 'SuperiorCo')
|
@@ -279,61 +265,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
279
265
|
assert_equal a, firm.account
|
280
266
|
assert_equal a, firm.account(true)
|
281
267
|
end
|
282
|
-
|
283
|
-
def test_save_fails_for_invalid_has_one
|
284
|
-
firm = Firm.find(:first)
|
285
|
-
assert firm.valid?
|
286
|
-
|
287
|
-
firm.account = Account.new
|
288
|
-
|
289
|
-
assert !firm.account.valid?
|
290
|
-
assert !firm.valid?
|
291
|
-
assert !firm.save
|
292
|
-
assert_equal "is invalid", firm.errors.on("account")
|
293
|
-
end
|
294
|
-
|
295
|
-
|
296
|
-
def test_save_succeeds_for_invalid_has_one_with_validate_false
|
297
|
-
firm = Firm.find(:first)
|
298
|
-
assert firm.valid?
|
299
|
-
|
300
|
-
firm.unvalidated_account = Account.new
|
301
|
-
|
302
|
-
assert !firm.unvalidated_account.valid?
|
303
|
-
assert firm.valid?
|
304
|
-
assert firm.save
|
305
|
-
end
|
306
|
-
|
307
|
-
def test_assignment_before_either_saved
|
308
|
-
firm = Firm.new("name" => "GlobalMegaCorp")
|
309
|
-
firm.account = a = Account.new("credit_limit" => 1000)
|
310
|
-
assert firm.new_record?
|
311
|
-
assert a.new_record?
|
312
|
-
assert_equal a, firm.account
|
313
|
-
assert firm.save
|
314
|
-
assert !firm.new_record?
|
315
|
-
assert !a.new_record?
|
316
|
-
assert_equal a, firm.account
|
317
|
-
assert_equal a, firm.account(true)
|
318
|
-
end
|
319
|
-
|
320
|
-
def test_not_resaved_when_unchanged
|
321
|
-
firm = Firm.find(:first, :include => :account)
|
322
|
-
firm.name += '-changed'
|
323
|
-
assert_queries(1) { firm.save! }
|
324
|
-
|
325
|
-
firm = Firm.find(:first)
|
326
|
-
firm.account = Account.find(:first)
|
327
|
-
assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
|
328
|
-
|
329
|
-
firm = Firm.find(:first).clone
|
330
|
-
firm.account = Account.find(:first)
|
331
|
-
assert_queries(2) { firm.save! }
|
332
|
-
|
333
|
-
firm = Firm.find(:first).clone
|
334
|
-
firm.account = Account.find(:first).clone
|
335
|
-
assert_queries(2) { firm.save! }
|
336
|
-
end
|
337
268
|
|
338
269
|
def test_save_still_works_after_accessing_nil_has_one
|
339
270
|
jp = Company.new :name => 'Jaded Pixel'
|
@@ -350,8 +281,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
350
281
|
end
|
351
282
|
|
352
283
|
def test_has_one_proxy_should_not_respond_to_private_methods
|
353
|
-
|
354
|
-
|
284
|
+
assert_raise(NoMethodError) { accounts(:signals37).private_method }
|
285
|
+
assert_raise(NoMethodError) { companies(:first_firm).account.private_method }
|
355
286
|
end
|
356
287
|
|
357
288
|
def test_has_one_proxy_should_respond_to_private_methods_via_send
|
@@ -359,4 +290,20 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
|
|
359
290
|
companies(:first_firm).account.send(:private_method)
|
360
291
|
end
|
361
292
|
|
293
|
+
def test_save_of_record_with_loaded_has_one
|
294
|
+
@firm = companies(:first_firm)
|
295
|
+
assert_not_nil @firm.account
|
296
|
+
|
297
|
+
assert_nothing_raised do
|
298
|
+
Firm.find(@firm.id).save!
|
299
|
+
Firm.find(@firm.id, :include => :account).save!
|
300
|
+
end
|
301
|
+
|
302
|
+
@firm.account.destroy
|
303
|
+
|
304
|
+
assert_nothing_raised do
|
305
|
+
Firm.find(@firm.id).save!
|
306
|
+
Firm.find(@firm.id, :include => :account).save!
|
307
|
+
end
|
308
|
+
end
|
362
309
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require "cases/helper"
|
2
2
|
require 'models/club'
|
3
|
+
require 'models/member_type'
|
3
4
|
require 'models/member'
|
4
5
|
require 'models/membership'
|
5
6
|
require 'models/sponsor'
|
@@ -7,7 +8,7 @@ require 'models/organization'
|
|
7
8
|
require 'models/member_detail'
|
8
9
|
|
9
10
|
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
|
10
|
-
fixtures :members, :clubs, :memberships, :sponsors, :organizations
|
11
|
+
fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations
|
11
12
|
|
12
13
|
def setup
|
13
14
|
@member = members(:groucho)
|
@@ -114,8 +115,8 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
|
|
114
115
|
end
|
115
116
|
|
116
117
|
def test_has_one_through_proxy_should_not_respond_to_private_methods
|
117
|
-
|
118
|
-
|
118
|
+
assert_raise(NoMethodError) { clubs(:moustache_club).private_method }
|
119
|
+
assert_raise(NoMethodError) { @member.club.private_method }
|
119
120
|
end
|
120
121
|
|
121
122
|
def test_has_one_through_proxy_should_respond_to_private_methods_via_send
|
@@ -158,4 +159,34 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
|
|
158
159
|
assert @new_organization.members.include?(@member)
|
159
160
|
end
|
160
161
|
|
162
|
+
def test_preloading_has_one_through_on_belongs_to
|
163
|
+
assert_not_nil @member.member_type
|
164
|
+
@organization = organizations(:nsa)
|
165
|
+
@member_detail = MemberDetail.new
|
166
|
+
@member.member_detail = @member_detail
|
167
|
+
@member.organization = @organization
|
168
|
+
@member_details = assert_queries(3) do
|
169
|
+
MemberDetail.find(:all, :include => :member_type)
|
170
|
+
end
|
171
|
+
@new_detail = @member_details[0]
|
172
|
+
assert @new_detail.loaded_member_type?
|
173
|
+
assert_not_nil assert_no_queries { @new_detail.member_type }
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_save_of_record_with_loaded_has_one_through
|
177
|
+
@club = @member.club
|
178
|
+
assert_not_nil @club.sponsored_member
|
179
|
+
|
180
|
+
assert_nothing_raised do
|
181
|
+
Club.find(@club.id).save!
|
182
|
+
Club.find(@club.id, :include => :sponsored_member).save!
|
183
|
+
end
|
184
|
+
|
185
|
+
@club.sponsor.destroy
|
186
|
+
|
187
|
+
assert_nothing_raised do
|
188
|
+
Club.find(@club.id).save!
|
189
|
+
Club.find(@club.id, :include => :sponsored_member).save!
|
190
|
+
end
|
191
|
+
end
|
161
192
|
end
|
@@ -29,11 +29,6 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
|
|
29
29
|
assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?.*TERMINATING_MARKER/, sql
|
30
30
|
end
|
31
31
|
|
32
|
-
def test_construct_finder_sql_applies_aliases_tables_on_association_conditions
|
33
|
-
result = Author.find(:all, :joins => [:thinking_posts, :welcome_posts])
|
34
|
-
assert_equal authors(:david), result.first
|
35
|
-
end
|
36
|
-
|
37
32
|
def test_construct_finder_sql_unpacks_nested_joins
|
38
33
|
sql = Author.send(:construct_finder_sql, :joins => {:posts => [[:comments]]})
|
39
34
|
assert_no_match /inner join.*inner join.*inner join/i, sql, "only two join clauses should be present"
|
@@ -510,13 +510,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
|
|
510
510
|
assert !author.comments.loaded?
|
511
511
|
end
|
512
512
|
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
assert !author.comments.loaded?
|
519
|
-
end
|
513
|
+
def test_has_many_through_collection_size_uses_counter_cache_if_it_exists
|
514
|
+
author = authors(:david)
|
515
|
+
author.stubs(:read_attribute).with('comments_count').returns(100)
|
516
|
+
assert_equal 100, author.comments.size
|
517
|
+
assert !author.comments.loaded?
|
520
518
|
end
|
521
519
|
|
522
520
|
def test_adding_junk_to_has_many_through_should_raise_type_mismatch
|
@@ -56,6 +56,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|
56
56
|
assert_equal myobj, topic.content
|
57
57
|
end
|
58
58
|
|
59
|
+
def test_typecast_attribute_from_select_to_false
|
60
|
+
topic = Topic.create(:title => 'Budget')
|
61
|
+
topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test")
|
62
|
+
assert !topic.is_test?
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_typecast_attribute_from_select_to_true
|
66
|
+
topic = Topic.create(:title => 'Budget')
|
67
|
+
topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test")
|
68
|
+
assert topic.is_test?
|
69
|
+
end
|
70
|
+
|
59
71
|
def test_kernel_methods_not_implemented_in_activerecord
|
60
72
|
%w(test name display y).each do |method|
|
61
73
|
assert !ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined"
|
@@ -88,7 +100,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|
88
100
|
%w(save create_or_update).each do |method|
|
89
101
|
klass = Class.new ActiveRecord::Base
|
90
102
|
klass.class_eval "def #{method}() 'defined #{method}' end"
|
91
|
-
|
103
|
+
assert_raise ActiveRecord::DangerousAttributeError do
|
92
104
|
klass.instance_method_already_implemented?(method)
|
93
105
|
end
|
94
106
|
end
|
@@ -0,0 +1,901 @@
|
|
1
|
+
require 'cases/helper'
|
2
|
+
require 'models/bird'
|
3
|
+
require 'models/company'
|
4
|
+
require 'models/customer'
|
5
|
+
require 'models/developer'
|
6
|
+
require 'models/order'
|
7
|
+
require 'models/parrot'
|
8
|
+
require 'models/person'
|
9
|
+
require 'models/pirate'
|
10
|
+
require 'models/post'
|
11
|
+
require 'models/reader'
|
12
|
+
require 'models/ship'
|
13
|
+
require 'models/ship_part'
|
14
|
+
require 'models/treasure'
|
15
|
+
|
16
|
+
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
|
17
|
+
def test_autosave_should_be_a_valid_option_for_has_one
|
18
|
+
assert base.valid_keys_for_has_one_association.include?(:autosave)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_autosave_should_be_a_valid_option_for_belongs_to
|
22
|
+
assert base.valid_keys_for_belongs_to_association.include?(:autosave)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_autosave_should_be_a_valid_option_for_has_many
|
26
|
+
assert base.valid_keys_for_has_many_association.include?(:autosave)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_autosave_should_be_a_valid_option_for_has_and_belongs_to_many
|
30
|
+
assert base.valid_keys_for_has_and_belongs_to_many_association.include?(:autosave)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def base
|
36
|
+
ActiveRecord::Base
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
|
41
|
+
def test_save_fails_for_invalid_has_one
|
42
|
+
firm = Firm.find(:first)
|
43
|
+
assert firm.valid?
|
44
|
+
|
45
|
+
firm.account = Account.new
|
46
|
+
|
47
|
+
assert !firm.account.valid?
|
48
|
+
assert !firm.valid?
|
49
|
+
assert !firm.save
|
50
|
+
assert_equal "is invalid", firm.errors.on("account")
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_save_succeeds_for_invalid_has_one_with_validate_false
|
54
|
+
firm = Firm.find(:first)
|
55
|
+
assert firm.valid?
|
56
|
+
|
57
|
+
firm.unvalidated_account = Account.new
|
58
|
+
|
59
|
+
assert !firm.unvalidated_account.valid?
|
60
|
+
assert firm.valid?
|
61
|
+
assert firm.save
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_build_before_child_saved
|
65
|
+
firm = Firm.find(1)
|
66
|
+
|
67
|
+
account = firm.account.build("credit_limit" => 1000)
|
68
|
+
assert_equal account, firm.account
|
69
|
+
assert account.new_record?
|
70
|
+
assert firm.save
|
71
|
+
assert_equal account, firm.account
|
72
|
+
assert !account.new_record?
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_build_before_either_saved
|
76
|
+
firm = Firm.new("name" => "GlobalMegaCorp")
|
77
|
+
|
78
|
+
firm.account = account = Account.new("credit_limit" => 1000)
|
79
|
+
assert_equal account, firm.account
|
80
|
+
assert account.new_record?
|
81
|
+
assert firm.save
|
82
|
+
assert_equal account, firm.account
|
83
|
+
assert !account.new_record?
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_assignment_before_parent_saved
|
87
|
+
firm = Firm.new("name" => "GlobalMegaCorp")
|
88
|
+
firm.account = a = Account.find(1)
|
89
|
+
assert firm.new_record?
|
90
|
+
assert_equal a, firm.account
|
91
|
+
assert firm.save
|
92
|
+
assert_equal a, firm.account
|
93
|
+
assert_equal a, firm.account(true)
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_assignment_before_either_saved
|
97
|
+
firm = Firm.new("name" => "GlobalMegaCorp")
|
98
|
+
firm.account = a = Account.new("credit_limit" => 1000)
|
99
|
+
assert firm.new_record?
|
100
|
+
assert a.new_record?
|
101
|
+
assert_equal a, firm.account
|
102
|
+
assert firm.save
|
103
|
+
assert !firm.new_record?
|
104
|
+
assert !a.new_record?
|
105
|
+
assert_equal a, firm.account
|
106
|
+
assert_equal a, firm.account(true)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_not_resaved_when_unchanged
|
110
|
+
firm = Firm.find(:first, :include => :account)
|
111
|
+
firm.name += '-changed'
|
112
|
+
assert_queries(1) { firm.save! }
|
113
|
+
|
114
|
+
firm = Firm.find(:first)
|
115
|
+
firm.account = Account.find(:first)
|
116
|
+
assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
|
117
|
+
|
118
|
+
firm = Firm.find(:first).clone
|
119
|
+
firm.account = Account.find(:first)
|
120
|
+
assert_queries(2) { firm.save! }
|
121
|
+
|
122
|
+
firm = Firm.find(:first).clone
|
123
|
+
firm.account = Account.find(:first).clone
|
124
|
+
assert_queries(2) { firm.save! }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
|
129
|
+
def test_save_fails_for_invalid_belongs_to
|
130
|
+
assert log = AuditLog.create(:developer_id => 0, :message => "")
|
131
|
+
|
132
|
+
log.developer = Developer.new
|
133
|
+
assert !log.developer.valid?
|
134
|
+
assert !log.valid?
|
135
|
+
assert !log.save
|
136
|
+
assert_equal "is invalid", log.errors.on("developer")
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_save_succeeds_for_invalid_belongs_to_with_validate_false
|
140
|
+
assert log = AuditLog.create(:developer_id => 0, :message=> "")
|
141
|
+
|
142
|
+
log.unvalidated_developer = Developer.new
|
143
|
+
assert !log.unvalidated_developer.valid?
|
144
|
+
assert log.valid?
|
145
|
+
assert log.save
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_assignment_before_parent_saved
|
149
|
+
client = Client.find(:first)
|
150
|
+
apple = Firm.new("name" => "Apple")
|
151
|
+
client.firm = apple
|
152
|
+
assert_equal apple, client.firm
|
153
|
+
assert apple.new_record?
|
154
|
+
assert client.save
|
155
|
+
assert apple.save
|
156
|
+
assert !apple.new_record?
|
157
|
+
assert_equal apple, client.firm
|
158
|
+
assert_equal apple, client.firm(true)
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_assignment_before_either_saved
|
162
|
+
final_cut = Client.new("name" => "Final Cut")
|
163
|
+
apple = Firm.new("name" => "Apple")
|
164
|
+
final_cut.firm = apple
|
165
|
+
assert final_cut.new_record?
|
166
|
+
assert apple.new_record?
|
167
|
+
assert final_cut.save
|
168
|
+
assert !final_cut.new_record?
|
169
|
+
assert !apple.new_record?
|
170
|
+
assert_equal apple, final_cut.firm
|
171
|
+
assert_equal apple, final_cut.firm(true)
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_store_two_association_with_one_save
|
175
|
+
num_orders = Order.count
|
176
|
+
num_customers = Customer.count
|
177
|
+
order = Order.new
|
178
|
+
|
179
|
+
customer1 = order.billing = Customer.new
|
180
|
+
customer2 = order.shipping = Customer.new
|
181
|
+
assert order.save
|
182
|
+
assert_equal customer1, order.billing
|
183
|
+
assert_equal customer2, order.shipping
|
184
|
+
|
185
|
+
order.reload
|
186
|
+
|
187
|
+
assert_equal customer1, order.billing
|
188
|
+
assert_equal customer2, order.shipping
|
189
|
+
|
190
|
+
assert_equal num_orders +1, Order.count
|
191
|
+
assert_equal num_customers +2, Customer.count
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_store_association_in_two_relations_with_one_save
|
195
|
+
num_orders = Order.count
|
196
|
+
num_customers = Customer.count
|
197
|
+
order = Order.new
|
198
|
+
|
199
|
+
customer = order.billing = order.shipping = Customer.new
|
200
|
+
assert order.save
|
201
|
+
assert_equal customer, order.billing
|
202
|
+
assert_equal customer, order.shipping
|
203
|
+
|
204
|
+
order.reload
|
205
|
+
|
206
|
+
assert_equal customer, order.billing
|
207
|
+
assert_equal customer, order.shipping
|
208
|
+
|
209
|
+
assert_equal num_orders +1, Order.count
|
210
|
+
assert_equal num_customers +1, Customer.count
|
211
|
+
end
|
212
|
+
|
213
|
+
def test_store_association_in_two_relations_with_one_save_in_existing_object
|
214
|
+
num_orders = Order.count
|
215
|
+
num_customers = Customer.count
|
216
|
+
order = Order.create
|
217
|
+
|
218
|
+
customer = order.billing = order.shipping = Customer.new
|
219
|
+
assert order.save
|
220
|
+
assert_equal customer, order.billing
|
221
|
+
assert_equal customer, order.shipping
|
222
|
+
|
223
|
+
order.reload
|
224
|
+
|
225
|
+
assert_equal customer, order.billing
|
226
|
+
assert_equal customer, order.shipping
|
227
|
+
|
228
|
+
assert_equal num_orders +1, Order.count
|
229
|
+
assert_equal num_customers +1, Customer.count
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
|
233
|
+
num_orders = Order.count
|
234
|
+
num_customers = Customer.count
|
235
|
+
order = Order.create
|
236
|
+
|
237
|
+
customer = order.billing = order.shipping = Customer.new
|
238
|
+
assert order.save
|
239
|
+
assert_equal customer, order.billing
|
240
|
+
assert_equal customer, order.shipping
|
241
|
+
|
242
|
+
order.reload
|
243
|
+
|
244
|
+
customer = order.billing = order.shipping = Customer.new
|
245
|
+
|
246
|
+
assert order.save
|
247
|
+
order.reload
|
248
|
+
|
249
|
+
assert_equal customer, order.billing
|
250
|
+
assert_equal customer, order.shipping
|
251
|
+
|
252
|
+
assert_equal num_orders +1, Order.count
|
253
|
+
assert_equal num_customers +2, Customer.count
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
|
258
|
+
fixtures :companies, :people
|
259
|
+
|
260
|
+
def test_invalid_adding
|
261
|
+
firm = Firm.find(1)
|
262
|
+
assert !(firm.clients_of_firm << c = Client.new)
|
263
|
+
assert c.new_record?
|
264
|
+
assert !firm.valid?
|
265
|
+
assert !firm.save
|
266
|
+
assert c.new_record?
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_invalid_adding_before_save
|
270
|
+
no_of_firms = Firm.count
|
271
|
+
no_of_clients = Client.count
|
272
|
+
new_firm = Firm.new("name" => "A New Firm, Inc")
|
273
|
+
new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
|
274
|
+
assert c.new_record?
|
275
|
+
assert !c.valid?
|
276
|
+
assert !new_firm.valid?
|
277
|
+
assert !new_firm.save
|
278
|
+
assert c.new_record?
|
279
|
+
assert new_firm.new_record?
|
280
|
+
end
|
281
|
+
|
282
|
+
def test_invalid_adding_with_validate_false
|
283
|
+
firm = Firm.find(:first)
|
284
|
+
client = Client.new
|
285
|
+
firm.unvalidated_clients_of_firm << client
|
286
|
+
|
287
|
+
assert firm.valid?
|
288
|
+
assert !client.valid?
|
289
|
+
assert firm.save
|
290
|
+
assert client.new_record?
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_valid_adding_with_validate_false
|
294
|
+
no_of_clients = Client.count
|
295
|
+
|
296
|
+
firm = Firm.find(:first)
|
297
|
+
client = Client.new("name" => "Apple")
|
298
|
+
|
299
|
+
assert firm.valid?
|
300
|
+
assert client.valid?
|
301
|
+
assert client.new_record?
|
302
|
+
|
303
|
+
firm.unvalidated_clients_of_firm << client
|
304
|
+
|
305
|
+
assert firm.save
|
306
|
+
assert !client.new_record?
|
307
|
+
assert_equal no_of_clients+1, Client.count
|
308
|
+
end
|
309
|
+
|
310
|
+
def test_invalid_build
|
311
|
+
new_client = companies(:first_firm).clients_of_firm.build
|
312
|
+
assert new_client.new_record?
|
313
|
+
assert !new_client.valid?
|
314
|
+
assert_equal new_client, companies(:first_firm).clients_of_firm.last
|
315
|
+
assert !companies(:first_firm).save
|
316
|
+
assert new_client.new_record?
|
317
|
+
assert_equal 1, companies(:first_firm).clients_of_firm(true).size
|
318
|
+
end
|
319
|
+
|
320
|
+
def test_adding_before_save
|
321
|
+
no_of_firms = Firm.count
|
322
|
+
no_of_clients = Client.count
|
323
|
+
|
324
|
+
new_firm = Firm.new("name" => "A New Firm, Inc")
|
325
|
+
c = Client.new("name" => "Apple")
|
326
|
+
|
327
|
+
new_firm.clients_of_firm.push Client.new("name" => "Natural Company")
|
328
|
+
assert_equal 1, new_firm.clients_of_firm.size
|
329
|
+
new_firm.clients_of_firm << c
|
330
|
+
assert_equal 2, new_firm.clients_of_firm.size
|
331
|
+
|
332
|
+
assert_equal no_of_firms, Firm.count # Firm was not saved to database.
|
333
|
+
assert_equal no_of_clients, Client.count # Clients were not saved to database.
|
334
|
+
assert new_firm.save
|
335
|
+
assert !new_firm.new_record?
|
336
|
+
assert !c.new_record?
|
337
|
+
assert_equal new_firm, c.firm
|
338
|
+
assert_equal no_of_firms+1, Firm.count # Firm was saved to database.
|
339
|
+
assert_equal no_of_clients+2, Client.count # Clients were saved to database.
|
340
|
+
|
341
|
+
assert_equal 2, new_firm.clients_of_firm.size
|
342
|
+
assert_equal 2, new_firm.clients_of_firm(true).size
|
343
|
+
end
|
344
|
+
|
345
|
+
def test_assign_ids
|
346
|
+
firm = Firm.new("name" => "Apple")
|
347
|
+
firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
|
348
|
+
firm.save
|
349
|
+
firm.reload
|
350
|
+
assert_equal 2, firm.clients.length
|
351
|
+
assert firm.clients.include?(companies(:second_client))
|
352
|
+
end
|
353
|
+
|
354
|
+
def test_assign_ids_for_through_a_belongs_to
|
355
|
+
post = Post.new(:title => "Assigning IDs works!", :body => "You heared it here first, folks!")
|
356
|
+
post.person_ids = [people(:david).id, people(:michael).id]
|
357
|
+
post.save
|
358
|
+
post.reload
|
359
|
+
assert_equal 2, post.people.length
|
360
|
+
assert post.people.include?(people(:david))
|
361
|
+
end
|
362
|
+
|
363
|
+
def test_build_before_save
|
364
|
+
company = companies(:first_firm)
|
365
|
+
new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
|
366
|
+
assert !company.clients_of_firm.loaded?
|
367
|
+
|
368
|
+
company.name += '-changed'
|
369
|
+
assert_queries(2) { assert company.save }
|
370
|
+
assert !new_client.new_record?
|
371
|
+
assert_equal 2, company.clients_of_firm(true).size
|
372
|
+
end
|
373
|
+
|
374
|
+
def test_build_many_before_save
|
375
|
+
company = companies(:first_firm)
|
376
|
+
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
|
377
|
+
|
378
|
+
company.name += '-changed'
|
379
|
+
assert_queries(3) { assert company.save }
|
380
|
+
assert_equal 3, company.clients_of_firm(true).size
|
381
|
+
end
|
382
|
+
|
383
|
+
def test_build_via_block_before_save
|
384
|
+
company = companies(:first_firm)
|
385
|
+
new_client = assert_no_queries { company.clients_of_firm.build {|client| client.name = "Another Client" } }
|
386
|
+
assert !company.clients_of_firm.loaded?
|
387
|
+
|
388
|
+
company.name += '-changed'
|
389
|
+
assert_queries(2) { assert company.save }
|
390
|
+
assert !new_client.new_record?
|
391
|
+
assert_equal 2, company.clients_of_firm(true).size
|
392
|
+
end
|
393
|
+
|
394
|
+
def test_build_many_via_block_before_save
|
395
|
+
company = companies(:first_firm)
|
396
|
+
new_clients = assert_no_queries do
|
397
|
+
company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
|
398
|
+
client.name = "changed"
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
company.name += '-changed'
|
403
|
+
assert_queries(3) { assert company.save }
|
404
|
+
assert_equal 3, company.clients_of_firm(true).size
|
405
|
+
end
|
406
|
+
|
407
|
+
def test_replace_on_new_object
|
408
|
+
firm = Firm.new("name" => "New Firm")
|
409
|
+
firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
|
410
|
+
assert firm.save
|
411
|
+
firm.reload
|
412
|
+
assert_equal 2, firm.clients.length
|
413
|
+
assert firm.clients.include?(Client.find_by_name("New Client"))
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
|
418
|
+
self.use_transactional_fixtures = false
|
419
|
+
|
420
|
+
def setup
|
421
|
+
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
422
|
+
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
|
423
|
+
end
|
424
|
+
|
425
|
+
# reload
|
426
|
+
def test_a_marked_for_destruction_record_should_not_be_be_marked_after_reload
|
427
|
+
@pirate.mark_for_destruction
|
428
|
+
@pirate.ship.mark_for_destruction
|
429
|
+
|
430
|
+
assert !@pirate.reload.marked_for_destruction?
|
431
|
+
assert !@pirate.ship.marked_for_destruction?
|
432
|
+
end
|
433
|
+
|
434
|
+
# has_one
|
435
|
+
def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
|
436
|
+
assert !@pirate.ship.marked_for_destruction?
|
437
|
+
|
438
|
+
@pirate.ship.mark_for_destruction
|
439
|
+
id = @pirate.ship.id
|
440
|
+
|
441
|
+
assert @pirate.ship.marked_for_destruction?
|
442
|
+
assert Ship.find_by_id(id)
|
443
|
+
|
444
|
+
@pirate.save
|
445
|
+
assert_nil @pirate.reload.ship
|
446
|
+
assert_nil Ship.find_by_id(id)
|
447
|
+
end
|
448
|
+
|
449
|
+
def test_should_skip_validation_on_a_child_association_if_marked_for_destruction
|
450
|
+
@pirate.ship.name = ''
|
451
|
+
assert !@pirate.valid?
|
452
|
+
|
453
|
+
@pirate.ship.mark_for_destruction
|
454
|
+
assert_difference('Ship.count', -1) { @pirate.save! }
|
455
|
+
end
|
456
|
+
|
457
|
+
def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_child
|
458
|
+
# Stub the save method of the @pirate.ship instance to destroy and then raise an exception
|
459
|
+
class << @pirate.ship
|
460
|
+
def save(*args)
|
461
|
+
super
|
462
|
+
destroy
|
463
|
+
raise 'Oh noes!'
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
assert_raise(RuntimeError) { assert !@pirate.save }
|
468
|
+
assert_not_nil @pirate.reload.ship
|
469
|
+
end
|
470
|
+
|
471
|
+
# belongs_to
|
472
|
+
def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
|
473
|
+
assert !@ship.pirate.marked_for_destruction?
|
474
|
+
|
475
|
+
@ship.pirate.mark_for_destruction
|
476
|
+
id = @ship.pirate.id
|
477
|
+
|
478
|
+
assert @ship.pirate.marked_for_destruction?
|
479
|
+
assert Pirate.find_by_id(id)
|
480
|
+
|
481
|
+
@ship.save
|
482
|
+
assert_nil @ship.reload.pirate
|
483
|
+
assert_nil Pirate.find_by_id(id)
|
484
|
+
end
|
485
|
+
|
486
|
+
def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction
|
487
|
+
@ship.pirate.catchphrase = ''
|
488
|
+
assert !@ship.valid?
|
489
|
+
|
490
|
+
@ship.pirate.mark_for_destruction
|
491
|
+
assert_difference('Pirate.count', -1) { @ship.save! }
|
492
|
+
end
|
493
|
+
|
494
|
+
def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_parent
|
495
|
+
# Stub the save method of the @ship.pirate instance to destroy and then raise an exception
|
496
|
+
class << @ship.pirate
|
497
|
+
def save(*args)
|
498
|
+
super
|
499
|
+
destroy
|
500
|
+
raise 'Oh noes!'
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
assert_raise(RuntimeError) { assert !@ship.save }
|
505
|
+
assert_not_nil @ship.reload.pirate
|
506
|
+
end
|
507
|
+
|
508
|
+
# has_many & has_and_belongs_to
|
509
|
+
%w{ parrots birds }.each do |association_name|
|
510
|
+
define_method("test_should_destroy_#{association_name}_as_part_of_the_save_transaction_if_they_were_marked_for_destroyal") do
|
511
|
+
2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
|
512
|
+
|
513
|
+
assert !@pirate.send(association_name).any? { |child| child.marked_for_destruction? }
|
514
|
+
|
515
|
+
@pirate.send(association_name).each { |child| child.mark_for_destruction }
|
516
|
+
klass = @pirate.send(association_name).first.class
|
517
|
+
ids = @pirate.send(association_name).map(&:id)
|
518
|
+
|
519
|
+
assert @pirate.send(association_name).all? { |child| child.marked_for_destruction? }
|
520
|
+
ids.each { |id| assert klass.find_by_id(id) }
|
521
|
+
|
522
|
+
@pirate.save
|
523
|
+
assert @pirate.reload.send(association_name).empty?
|
524
|
+
ids.each { |id| assert_nil klass.find_by_id(id) }
|
525
|
+
end
|
526
|
+
|
527
|
+
define_method("test_should_skip_validation_on_the_#{association_name}_association_if_marked_for_destruction") do
|
528
|
+
2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
|
529
|
+
children = @pirate.send(association_name)
|
530
|
+
|
531
|
+
children.each { |child| child.name = '' }
|
532
|
+
assert !@pirate.valid?
|
533
|
+
|
534
|
+
children.each { |child| child.mark_for_destruction }
|
535
|
+
assert_difference("#{association_name.classify}.count", -2) { @pirate.save! }
|
536
|
+
end
|
537
|
+
|
538
|
+
define_method("test_should_rollback_destructions_if_an_exception_occurred_while_saving_#{association_name}") do
|
539
|
+
2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
|
540
|
+
before = @pirate.send(association_name).map { |c| c }
|
541
|
+
|
542
|
+
# Stub the save method of the first child to destroy and the second to raise an exception
|
543
|
+
class << before.first
|
544
|
+
def save(*args)
|
545
|
+
super
|
546
|
+
destroy
|
547
|
+
end
|
548
|
+
end
|
549
|
+
class << before.last
|
550
|
+
def save(*args)
|
551
|
+
super
|
552
|
+
raise 'Oh noes!'
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
assert_raise(RuntimeError) { assert !@pirate.save }
|
557
|
+
assert_equal before, @pirate.reload.send(association_name)
|
558
|
+
end
|
559
|
+
|
560
|
+
# Add and remove callbacks tests for association collections.
|
561
|
+
%w{ method proc }.each do |callback_type|
|
562
|
+
define_method("test_should_run_add_callback_#{callback_type}s_for_#{association_name}") do
|
563
|
+
association_name_with_callbacks = "#{association_name}_with_#{callback_type}_callbacks"
|
564
|
+
|
565
|
+
pirate = Pirate.new(:catchphrase => "Arr")
|
566
|
+
pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed")
|
567
|
+
|
568
|
+
expected = [
|
569
|
+
"before_adding_#{callback_type}_#{association_name.singularize}_<new>",
|
570
|
+
"after_adding_#{callback_type}_#{association_name.singularize}_<new>"
|
571
|
+
]
|
572
|
+
|
573
|
+
assert_equal expected, pirate.ship_log
|
574
|
+
end
|
575
|
+
|
576
|
+
define_method("test_should_run_remove_callback_#{callback_type}s_for_#{association_name}") do
|
577
|
+
association_name_with_callbacks = "#{association_name}_with_#{callback_type}_callbacks"
|
578
|
+
|
579
|
+
@pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
|
580
|
+
@pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction }
|
581
|
+
child_id = @pirate.send(association_name_with_callbacks).first.id
|
582
|
+
|
583
|
+
@pirate.ship_log.clear
|
584
|
+
@pirate.save
|
585
|
+
|
586
|
+
expected = [
|
587
|
+
"before_removing_#{callback_type}_#{association_name.singularize}_#{child_id}",
|
588
|
+
"after_removing_#{callback_type}_#{association_name.singularize}_#{child_id}"
|
589
|
+
]
|
590
|
+
|
591
|
+
assert_equal expected, @pirate.ship_log
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
|
598
|
+
self.use_transactional_fixtures = false
|
599
|
+
|
600
|
+
def setup
|
601
|
+
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
602
|
+
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
|
603
|
+
end
|
604
|
+
|
605
|
+
def test_should_still_work_without_an_associated_model
|
606
|
+
@ship.destroy
|
607
|
+
@pirate.reload.catchphrase = "Arr"
|
608
|
+
@pirate.save
|
609
|
+
assert 'Arr', @pirate.reload.catchphrase
|
610
|
+
end
|
611
|
+
|
612
|
+
def test_should_automatically_save_the_associated_model
|
613
|
+
@pirate.ship.name = 'The Vile Insanity'
|
614
|
+
@pirate.save
|
615
|
+
assert_equal 'The Vile Insanity', @pirate.reload.ship.name
|
616
|
+
end
|
617
|
+
|
618
|
+
def test_should_automatically_save_bang_the_associated_model
|
619
|
+
@pirate.ship.name = 'The Vile Insanity'
|
620
|
+
@pirate.save!
|
621
|
+
assert_equal 'The Vile Insanity', @pirate.reload.ship.name
|
622
|
+
end
|
623
|
+
|
624
|
+
def test_should_automatically_validate_the_associated_model
|
625
|
+
@pirate.ship.name = ''
|
626
|
+
assert !@pirate.valid?
|
627
|
+
assert !@pirate.errors.on(:ship_name).blank?
|
628
|
+
end
|
629
|
+
|
630
|
+
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
|
631
|
+
@pirate.ship.name = nil
|
632
|
+
@pirate.catchphrase = nil
|
633
|
+
assert !@pirate.valid?
|
634
|
+
assert !@pirate.errors.on(:ship_name).blank?
|
635
|
+
assert !@pirate.errors.on(:catchphrase).blank?
|
636
|
+
end
|
637
|
+
|
638
|
+
def test_should_still_allow_to_bypass_validations_on_the_associated_model
|
639
|
+
@pirate.catchphrase = ''
|
640
|
+
@pirate.ship.name = ''
|
641
|
+
@pirate.save(false)
|
642
|
+
assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name]
|
643
|
+
end
|
644
|
+
|
645
|
+
def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth
|
646
|
+
2.times { |i| @pirate.ship.parts.create!(:name => "part #{i}") }
|
647
|
+
|
648
|
+
@pirate.catchphrase = ''
|
649
|
+
@pirate.ship.name = ''
|
650
|
+
@pirate.ship.parts.each { |part| part.name = '' }
|
651
|
+
@pirate.save(false)
|
652
|
+
|
653
|
+
values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)]
|
654
|
+
assert_equal ['', '', '', ''], values
|
655
|
+
end
|
656
|
+
|
657
|
+
def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
|
658
|
+
@pirate.ship.name = ''
|
659
|
+
assert_raise(ActiveRecord::RecordInvalid) do
|
660
|
+
@pirate.save!
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
|
665
|
+
before = [@pirate.catchphrase, @pirate.ship.name]
|
666
|
+
|
667
|
+
@pirate.catchphrase = 'Arr'
|
668
|
+
@pirate.ship.name = 'The Vile Insanity'
|
669
|
+
|
670
|
+
# Stub the save method of the @pirate.ship instance to raise an exception
|
671
|
+
class << @pirate.ship
|
672
|
+
def save(*args)
|
673
|
+
super
|
674
|
+
raise 'Oh noes!'
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
assert_raise(RuntimeError) { assert !@pirate.save }
|
679
|
+
assert_equal before, [@pirate.reload.catchphrase, @pirate.ship.name]
|
680
|
+
end
|
681
|
+
|
682
|
+
def test_should_not_load_the_associated_model
|
683
|
+
assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
|
688
|
+
self.use_transactional_fixtures = false
|
689
|
+
|
690
|
+
def setup
|
691
|
+
@ship = Ship.create(:name => 'Nights Dirty Lightning')
|
692
|
+
@pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
693
|
+
end
|
694
|
+
|
695
|
+
def test_should_still_work_without_an_associated_model
|
696
|
+
@pirate.destroy
|
697
|
+
@ship.reload.name = "The Vile Insanity"
|
698
|
+
@ship.save
|
699
|
+
assert 'The Vile Insanity', @ship.reload.name
|
700
|
+
end
|
701
|
+
|
702
|
+
def test_should_automatically_save_the_associated_model
|
703
|
+
@ship.pirate.catchphrase = 'Arr'
|
704
|
+
@ship.save
|
705
|
+
assert_equal 'Arr', @ship.reload.pirate.catchphrase
|
706
|
+
end
|
707
|
+
|
708
|
+
def test_should_automatically_save_bang_the_associated_model
|
709
|
+
@ship.pirate.catchphrase = 'Arr'
|
710
|
+
@ship.save!
|
711
|
+
assert_equal 'Arr', @ship.reload.pirate.catchphrase
|
712
|
+
end
|
713
|
+
|
714
|
+
def test_should_automatically_validate_the_associated_model
|
715
|
+
@ship.pirate.catchphrase = ''
|
716
|
+
assert !@ship.valid?
|
717
|
+
assert !@ship.errors.on(:pirate_catchphrase).blank?
|
718
|
+
end
|
719
|
+
|
720
|
+
def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
|
721
|
+
@ship.name = nil
|
722
|
+
@ship.pirate.catchphrase = nil
|
723
|
+
assert !@ship.valid?
|
724
|
+
assert !@ship.errors.on(:name).blank?
|
725
|
+
assert !@ship.errors.on(:pirate_catchphrase).blank?
|
726
|
+
end
|
727
|
+
|
728
|
+
def test_should_still_allow_to_bypass_validations_on_the_associated_model
|
729
|
+
@ship.pirate.catchphrase = ''
|
730
|
+
@ship.name = ''
|
731
|
+
@ship.save(false)
|
732
|
+
assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase]
|
733
|
+
end
|
734
|
+
|
735
|
+
def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
|
736
|
+
@ship.pirate.catchphrase = ''
|
737
|
+
assert_raise(ActiveRecord::RecordInvalid) do
|
738
|
+
@ship.save!
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
742
|
+
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
|
743
|
+
before = [@ship.pirate.catchphrase, @ship.name]
|
744
|
+
|
745
|
+
@ship.pirate.catchphrase = 'Arr'
|
746
|
+
@ship.name = 'The Vile Insanity'
|
747
|
+
|
748
|
+
# Stub the save method of the @ship.pirate instance to raise an exception
|
749
|
+
class << @ship.pirate
|
750
|
+
def save(*args)
|
751
|
+
super
|
752
|
+
raise 'Oh noes!'
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|
756
|
+
assert_raise(RuntimeError) { assert !@ship.save }
|
757
|
+
# TODO: Why does using reload on @ship looses the associated pirate?
|
758
|
+
assert_equal before, [@ship.pirate.reload.catchphrase, @ship.reload.name]
|
759
|
+
end
|
760
|
+
|
761
|
+
def test_should_not_load_the_associated_model
|
762
|
+
assert_queries(1) { @ship.name = 'The Vile Insanity'; @ship.save! }
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
module AutosaveAssociationOnACollectionAssociationTests
|
767
|
+
def test_should_automatically_save_the_associated_models
|
768
|
+
new_names = ['Grace OMalley', 'Privateers Greed']
|
769
|
+
@pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
|
770
|
+
|
771
|
+
@pirate.save
|
772
|
+
assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
|
773
|
+
end
|
774
|
+
|
775
|
+
def test_should_automatically_save_bang_the_associated_models
|
776
|
+
new_names = ['Grace OMalley', 'Privateers Greed']
|
777
|
+
@pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
|
778
|
+
|
779
|
+
@pirate.save!
|
780
|
+
assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
|
781
|
+
end
|
782
|
+
|
783
|
+
def test_should_automatically_validate_the_associated_models
|
784
|
+
@pirate.send(@association_name).each { |child| child.name = '' }
|
785
|
+
|
786
|
+
assert !@pirate.valid?
|
787
|
+
assert_equal "can't be blank", @pirate.errors.on("#{@association_name}_name")
|
788
|
+
assert @pirate.errors.on(@association_name).blank?
|
789
|
+
end
|
790
|
+
|
791
|
+
def test_should_not_use_default_invalid_error_on_associated_models
|
792
|
+
@pirate.send(@association_name).build(:name => '')
|
793
|
+
|
794
|
+
assert !@pirate.valid?
|
795
|
+
assert_equal "can't be blank", @pirate.errors.on("#{@association_name}_name")
|
796
|
+
assert @pirate.errors.on(@association_name).blank?
|
797
|
+
end
|
798
|
+
|
799
|
+
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
|
800
|
+
@pirate.send(@association_name).each { |child| child.name = '' }
|
801
|
+
@pirate.catchphrase = nil
|
802
|
+
|
803
|
+
assert !@pirate.valid?
|
804
|
+
assert_equal "can't be blank", @pirate.errors.on("#{@association_name}_name")
|
805
|
+
assert !@pirate.errors.on(:catchphrase).blank?
|
806
|
+
end
|
807
|
+
|
808
|
+
def test_should_allow_to_bypass_validations_on_the_associated_models_on_update
|
809
|
+
@pirate.catchphrase = ''
|
810
|
+
@pirate.send(@association_name).each { |child| child.name = '' }
|
811
|
+
|
812
|
+
assert @pirate.save(false)
|
813
|
+
assert_equal ['', '', ''], [
|
814
|
+
@pirate.reload.catchphrase,
|
815
|
+
@pirate.send(@association_name).first.name,
|
816
|
+
@pirate.send(@association_name).last.name
|
817
|
+
]
|
818
|
+
end
|
819
|
+
|
820
|
+
def test_should_validation_the_associated_models_on_create
|
821
|
+
assert_no_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count") do
|
822
|
+
2.times { @pirate.send(@association_name).build }
|
823
|
+
@pirate.save(true)
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
def test_should_allow_to_bypass_validations_on_the_associated_models_on_create
|
828
|
+
assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", +2) do
|
829
|
+
2.times { @pirate.send(@association_name).build }
|
830
|
+
@pirate.save(false)
|
831
|
+
end
|
832
|
+
end
|
833
|
+
|
834
|
+
def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
|
835
|
+
before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)]
|
836
|
+
new_names = ['Grace OMalley', 'Privateers Greed']
|
837
|
+
|
838
|
+
@pirate.catchphrase = 'Arr'
|
839
|
+
@pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
|
840
|
+
|
841
|
+
# Stub the save method of the first child instance to raise an exception
|
842
|
+
class << @pirate.send(@association_name).first
|
843
|
+
def save(*args)
|
844
|
+
super
|
845
|
+
raise 'Oh noes!'
|
846
|
+
end
|
847
|
+
end
|
848
|
+
|
849
|
+
assert_raise(RuntimeError) { assert !@pirate.save }
|
850
|
+
assert_equal before, [@pirate.reload.catchphrase, *@pirate.send(@association_name).map(&:name)]
|
851
|
+
end
|
852
|
+
|
853
|
+
def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
|
854
|
+
@pirate.send(@association_name).each { |child| child.name = '' }
|
855
|
+
assert_raise(ActiveRecord::RecordInvalid) do
|
856
|
+
@pirate.save!
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet
|
861
|
+
assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
|
862
|
+
|
863
|
+
@pirate.send(@association_name).class # hack to load the target
|
864
|
+
|
865
|
+
assert_queries(3) do
|
866
|
+
@pirate.catchphrase = 'Yarr'
|
867
|
+
new_names = ['Grace OMalley', 'Privateers Greed']
|
868
|
+
@pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
|
869
|
+
@pirate.save!
|
870
|
+
end
|
871
|
+
end
|
872
|
+
end
|
873
|
+
|
874
|
+
class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
|
875
|
+
self.use_transactional_fixtures = false
|
876
|
+
|
877
|
+
def setup
|
878
|
+
@association_name = :birds
|
879
|
+
|
880
|
+
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
881
|
+
@child_1 = @pirate.birds.create(:name => 'Posideons Killer')
|
882
|
+
@child_2 = @pirate.birds.create(:name => 'Killer bandita Dionne')
|
883
|
+
end
|
884
|
+
|
885
|
+
include AutosaveAssociationOnACollectionAssociationTests
|
886
|
+
end
|
887
|
+
|
888
|
+
class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
|
889
|
+
self.use_transactional_fixtures = false
|
890
|
+
|
891
|
+
def setup
|
892
|
+
@association_name = :parrots
|
893
|
+
@habtm = true
|
894
|
+
|
895
|
+
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
|
896
|
+
@child_1 = @pirate.parrots.create(:name => 'Posideons Killer')
|
897
|
+
@child_2 = @pirate.parrots.create(:name => 'Killer bandita Dionne')
|
898
|
+
end
|
899
|
+
|
900
|
+
include AutosaveAssociationOnACollectionAssociationTests
|
901
|
+
end
|