activerecord_csi 2.3.5.p6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (333) hide show
  1. data/CHANGELOG +5858 -0
  2. data/README +351 -0
  3. data/RUNNING_UNIT_TESTS +36 -0
  4. data/Rakefile +270 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/performance.rb +162 -0
  7. data/install.rb +30 -0
  8. data/lib/active_record/aggregations.rb +261 -0
  9. data/lib/active_record/association_preload.rb +389 -0
  10. data/lib/active_record/associations/association_collection.rb +475 -0
  11. data/lib/active_record/associations/association_proxy.rb +278 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +76 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +53 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +122 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +266 -0
  17. data/lib/active_record/associations/has_one_association.rb +133 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +37 -0
  19. data/lib/active_record/associations.rb +2241 -0
  20. data/lib/active_record/attribute_methods.rb +388 -0
  21. data/lib/active_record/autosave_association.rb +364 -0
  22. data/lib/active_record/base.rb +3171 -0
  23. data/lib/active_record/batches.rb +81 -0
  24. data/lib/active_record/calculations.rb +311 -0
  25. data/lib/active_record/callbacks.rb +360 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +371 -0
  27. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +139 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +289 -0
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +94 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +722 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +434 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +241 -0
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +630 -0
  35. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1113 -0
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  37. data/lib/active_record/connection_adapters/sqlite_adapter.rb +453 -0
  38. data/lib/active_record/dirty.rb +183 -0
  39. data/lib/active_record/dynamic_finder_match.rb +41 -0
  40. data/lib/active_record/dynamic_scope_match.rb +25 -0
  41. data/lib/active_record/fixtures.rb +996 -0
  42. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  43. data/lib/active_record/locale/en.yml +58 -0
  44. data/lib/active_record/locking/optimistic.rb +148 -0
  45. data/lib/active_record/locking/pessimistic.rb +55 -0
  46. data/lib/active_record/migration.rb +566 -0
  47. data/lib/active_record/named_scope.rb +192 -0
  48. data/lib/active_record/nested_attributes.rb +392 -0
  49. data/lib/active_record/observer.rb +197 -0
  50. data/lib/active_record/query_cache.rb +33 -0
  51. data/lib/active_record/reflection.rb +320 -0
  52. data/lib/active_record/schema.rb +51 -0
  53. data/lib/active_record/schema_dumper.rb +182 -0
  54. data/lib/active_record/serialization.rb +101 -0
  55. data/lib/active_record/serializers/json_serializer.rb +91 -0
  56. data/lib/active_record/serializers/xml_serializer.rb +357 -0
  57. data/lib/active_record/session_store.rb +326 -0
  58. data/lib/active_record/test_case.rb +66 -0
  59. data/lib/active_record/timestamp.rb +71 -0
  60. data/lib/active_record/transactions.rb +235 -0
  61. data/lib/active_record/validations.rb +1135 -0
  62. data/lib/active_record/version.rb +9 -0
  63. data/lib/active_record.rb +84 -0
  64. data/lib/activerecord.rb +2 -0
  65. data/test/assets/example.log +1 -0
  66. data/test/assets/flowers.jpg +0 -0
  67. data/test/cases/aaa_create_tables_test.rb +24 -0
  68. data/test/cases/active_schema_test_mysql.rb +100 -0
  69. data/test/cases/active_schema_test_postgresql.rb +24 -0
  70. data/test/cases/adapter_test.rb +145 -0
  71. data/test/cases/aggregations_test.rb +167 -0
  72. data/test/cases/ar_schema_test.rb +32 -0
  73. data/test/cases/associations/belongs_to_associations_test.rb +425 -0
  74. data/test/cases/associations/callbacks_test.rb +161 -0
  75. data/test/cases/associations/cascaded_eager_loading_test.rb +131 -0
  76. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
  77. data/test/cases/associations/eager_load_nested_include_test.rb +130 -0
  78. data/test/cases/associations/eager_singularization_test.rb +145 -0
  79. data/test/cases/associations/eager_test.rb +834 -0
  80. data/test/cases/associations/extension_test.rb +62 -0
  81. data/test/cases/associations/habtm_join_table_test.rb +56 -0
  82. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +822 -0
  83. data/test/cases/associations/has_many_associations_test.rb +1134 -0
  84. data/test/cases/associations/has_many_through_associations_test.rb +346 -0
  85. data/test/cases/associations/has_one_associations_test.rb +330 -0
  86. data/test/cases/associations/has_one_through_associations_test.rb +209 -0
  87. data/test/cases/associations/inner_join_association_test.rb +93 -0
  88. data/test/cases/associations/join_model_test.rb +712 -0
  89. data/test/cases/associations_test.rb +262 -0
  90. data/test/cases/attribute_methods_test.rb +305 -0
  91. data/test/cases/autosave_association_test.rb +1142 -0
  92. data/test/cases/base_test.rb +2154 -0
  93. data/test/cases/batches_test.rb +61 -0
  94. data/test/cases/binary_test.rb +30 -0
  95. data/test/cases/calculations_test.rb +348 -0
  96. data/test/cases/callbacks_observers_test.rb +38 -0
  97. data/test/cases/callbacks_test.rb +438 -0
  98. data/test/cases/class_inheritable_attributes_test.rb +32 -0
  99. data/test/cases/column_alias_test.rb +17 -0
  100. data/test/cases/column_definition_test.rb +70 -0
  101. data/test/cases/connection_pool_test.rb +25 -0
  102. data/test/cases/connection_test_firebird.rb +8 -0
  103. data/test/cases/connection_test_mysql.rb +64 -0
  104. data/test/cases/copy_table_test_sqlite.rb +80 -0
  105. data/test/cases/database_statements_test.rb +12 -0
  106. data/test/cases/datatype_test_postgresql.rb +204 -0
  107. data/test/cases/date_time_test.rb +37 -0
  108. data/test/cases/default_test_firebird.rb +16 -0
  109. data/test/cases/defaults_test.rb +111 -0
  110. data/test/cases/deprecated_finder_test.rb +30 -0
  111. data/test/cases/dirty_test.rb +316 -0
  112. data/test/cases/finder_respond_to_test.rb +76 -0
  113. data/test/cases/finder_test.rb +1066 -0
  114. data/test/cases/fixtures_test.rb +656 -0
  115. data/test/cases/helper.rb +68 -0
  116. data/test/cases/i18n_test.rb +46 -0
  117. data/test/cases/inheritance_test.rb +262 -0
  118. data/test/cases/invalid_date_test.rb +24 -0
  119. data/test/cases/json_serialization_test.rb +205 -0
  120. data/test/cases/lifecycle_test.rb +193 -0
  121. data/test/cases/locking_test.rb +304 -0
  122. data/test/cases/method_scoping_test.rb +704 -0
  123. data/test/cases/migration_test.rb +1523 -0
  124. data/test/cases/migration_test_firebird.rb +124 -0
  125. data/test/cases/mixin_test.rb +96 -0
  126. data/test/cases/modules_test.rb +81 -0
  127. data/test/cases/multiple_db_test.rb +85 -0
  128. data/test/cases/named_scope_test.rb +361 -0
  129. data/test/cases/nested_attributes_test.rb +581 -0
  130. data/test/cases/pk_test.rb +119 -0
  131. data/test/cases/pooled_connections_test.rb +103 -0
  132. data/test/cases/query_cache_test.rb +123 -0
  133. data/test/cases/readonly_test.rb +107 -0
  134. data/test/cases/reflection_test.rb +194 -0
  135. data/test/cases/reload_models_test.rb +22 -0
  136. data/test/cases/repair_helper.rb +50 -0
  137. data/test/cases/reserved_word_test_mysql.rb +176 -0
  138. data/test/cases/sanitize_test.rb +25 -0
  139. data/test/cases/schema_authorization_test_postgresql.rb +75 -0
  140. data/test/cases/schema_dumper_test.rb +211 -0
  141. data/test/cases/schema_test_postgresql.rb +178 -0
  142. data/test/cases/serialization_test.rb +47 -0
  143. data/test/cases/synonym_test_oracle.rb +17 -0
  144. data/test/cases/timestamp_test.rb +75 -0
  145. data/test/cases/transactions_test.rb +522 -0
  146. data/test/cases/unconnected_test.rb +32 -0
  147. data/test/cases/validations_i18n_test.rb +955 -0
  148. data/test/cases/validations_test.rb +1640 -0
  149. data/test/cases/xml_serialization_test.rb +240 -0
  150. data/test/config.rb +5 -0
  151. data/test/connections/jdbc_jdbcderby/connection.rb +18 -0
  152. data/test/connections/jdbc_jdbch2/connection.rb +18 -0
  153. data/test/connections/jdbc_jdbchsqldb/connection.rb +18 -0
  154. data/test/connections/jdbc_jdbcmysql/connection.rb +26 -0
  155. data/test/connections/jdbc_jdbcpostgresql/connection.rb +26 -0
  156. data/test/connections/jdbc_jdbcsqlite3/connection.rb +25 -0
  157. data/test/connections/native_db2/connection.rb +25 -0
  158. data/test/connections/native_firebird/connection.rb +26 -0
  159. data/test/connections/native_frontbase/connection.rb +27 -0
  160. data/test/connections/native_mysql/connection.rb +25 -0
  161. data/test/connections/native_openbase/connection.rb +21 -0
  162. data/test/connections/native_oracle/connection.rb +27 -0
  163. data/test/connections/native_postgresql/connection.rb +25 -0
  164. data/test/connections/native_sqlite/connection.rb +25 -0
  165. data/test/connections/native_sqlite3/connection.rb +25 -0
  166. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  167. data/test/connections/native_sybase/connection.rb +23 -0
  168. data/test/fixtures/accounts.yml +29 -0
  169. data/test/fixtures/all/developers.yml +0 -0
  170. data/test/fixtures/all/people.csv +0 -0
  171. data/test/fixtures/all/tasks.yml +0 -0
  172. data/test/fixtures/author_addresses.yml +5 -0
  173. data/test/fixtures/author_favorites.yml +4 -0
  174. data/test/fixtures/authors.yml +9 -0
  175. data/test/fixtures/binaries.yml +132 -0
  176. data/test/fixtures/books.yml +7 -0
  177. data/test/fixtures/categories/special_categories.yml +9 -0
  178. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  179. data/test/fixtures/categories.yml +14 -0
  180. data/test/fixtures/categories_ordered.yml +7 -0
  181. data/test/fixtures/categories_posts.yml +23 -0
  182. data/test/fixtures/categorizations.yml +17 -0
  183. data/test/fixtures/clubs.yml +6 -0
  184. data/test/fixtures/comments.yml +59 -0
  185. data/test/fixtures/companies.yml +56 -0
  186. data/test/fixtures/computers.yml +4 -0
  187. data/test/fixtures/courses.yml +7 -0
  188. data/test/fixtures/customers.yml +26 -0
  189. data/test/fixtures/developers.yml +21 -0
  190. data/test/fixtures/developers_projects.yml +17 -0
  191. data/test/fixtures/edges.yml +6 -0
  192. data/test/fixtures/entrants.yml +14 -0
  193. data/test/fixtures/fixture_database.sqlite3 +0 -0
  194. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  195. data/test/fixtures/fk_test_has_fk.yml +3 -0
  196. data/test/fixtures/fk_test_has_pk.yml +2 -0
  197. data/test/fixtures/funny_jokes.yml +10 -0
  198. data/test/fixtures/items.yml +4 -0
  199. data/test/fixtures/jobs.yml +7 -0
  200. data/test/fixtures/legacy_things.yml +3 -0
  201. data/test/fixtures/mateys.yml +4 -0
  202. data/test/fixtures/member_types.yml +6 -0
  203. data/test/fixtures/members.yml +6 -0
  204. data/test/fixtures/memberships.yml +20 -0
  205. data/test/fixtures/minimalistics.yml +2 -0
  206. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  207. data/test/fixtures/mixins.yml +29 -0
  208. data/test/fixtures/movies.yml +7 -0
  209. data/test/fixtures/naked/csv/accounts.csv +1 -0
  210. data/test/fixtures/naked/yml/accounts.yml +1 -0
  211. data/test/fixtures/naked/yml/companies.yml +1 -0
  212. data/test/fixtures/naked/yml/courses.yml +1 -0
  213. data/test/fixtures/organizations.yml +5 -0
  214. data/test/fixtures/owners.yml +7 -0
  215. data/test/fixtures/parrots.yml +27 -0
  216. data/test/fixtures/parrots_pirates.yml +7 -0
  217. data/test/fixtures/people.yml +15 -0
  218. data/test/fixtures/pets.yml +14 -0
  219. data/test/fixtures/pirates.yml +9 -0
  220. data/test/fixtures/posts.yml +52 -0
  221. data/test/fixtures/price_estimates.yml +7 -0
  222. data/test/fixtures/projects.yml +7 -0
  223. data/test/fixtures/readers.yml +9 -0
  224. data/test/fixtures/references.yml +17 -0
  225. data/test/fixtures/reserved_words/distinct.yml +5 -0
  226. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  227. data/test/fixtures/reserved_words/group.yml +14 -0
  228. data/test/fixtures/reserved_words/select.yml +8 -0
  229. data/test/fixtures/reserved_words/values.yml +7 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/sponsors.yml +9 -0
  232. data/test/fixtures/subscribers.yml +7 -0
  233. data/test/fixtures/subscriptions.yml +12 -0
  234. data/test/fixtures/taggings.yml +28 -0
  235. data/test/fixtures/tags.yml +7 -0
  236. data/test/fixtures/tasks.yml +7 -0
  237. data/test/fixtures/topics.yml +42 -0
  238. data/test/fixtures/toys.yml +4 -0
  239. data/test/fixtures/treasures.yml +10 -0
  240. data/test/fixtures/vertices.yml +4 -0
  241. data/test/fixtures/warehouse-things.yml +3 -0
  242. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  243. data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -0
  244. data/test/migrations/duplicate/1_people_have_last_names.rb +9 -0
  245. data/test/migrations/duplicate/2_we_need_reminders.rb +12 -0
  246. data/test/migrations/duplicate/3_foo.rb +7 -0
  247. data/test/migrations/duplicate/3_innocent_jointable.rb +12 -0
  248. data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
  249. data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
  250. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +12 -0
  251. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +9 -0
  252. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +12 -0
  253. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +9 -0
  254. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
  255. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
  256. data/test/migrations/missing/1000_people_have_middle_names.rb +9 -0
  257. data/test/migrations/missing/1_people_have_last_names.rb +9 -0
  258. data/test/migrations/missing/3_we_need_reminders.rb +12 -0
  259. data/test/migrations/missing/4_innocent_jointable.rb +12 -0
  260. data/test/migrations/valid/1_people_have_last_names.rb +9 -0
  261. data/test/migrations/valid/2_we_need_reminders.rb +12 -0
  262. data/test/migrations/valid/3_innocent_jointable.rb +12 -0
  263. data/test/models/author.rb +146 -0
  264. data/test/models/auto_id.rb +4 -0
  265. data/test/models/binary.rb +2 -0
  266. data/test/models/bird.rb +3 -0
  267. data/test/models/book.rb +4 -0
  268. data/test/models/categorization.rb +5 -0
  269. data/test/models/category.rb +34 -0
  270. data/test/models/citation.rb +6 -0
  271. data/test/models/club.rb +13 -0
  272. data/test/models/column_name.rb +3 -0
  273. data/test/models/comment.rb +29 -0
  274. data/test/models/company.rb +171 -0
  275. data/test/models/company_in_module.rb +61 -0
  276. data/test/models/computer.rb +3 -0
  277. data/test/models/contact.rb +16 -0
  278. data/test/models/contract.rb +5 -0
  279. data/test/models/course.rb +3 -0
  280. data/test/models/customer.rb +73 -0
  281. data/test/models/default.rb +2 -0
  282. data/test/models/developer.rb +101 -0
  283. data/test/models/edge.rb +5 -0
  284. data/test/models/entrant.rb +3 -0
  285. data/test/models/essay.rb +3 -0
  286. data/test/models/event.rb +3 -0
  287. data/test/models/guid.rb +2 -0
  288. data/test/models/item.rb +7 -0
  289. data/test/models/job.rb +5 -0
  290. data/test/models/joke.rb +3 -0
  291. data/test/models/keyboard.rb +3 -0
  292. data/test/models/legacy_thing.rb +3 -0
  293. data/test/models/matey.rb +4 -0
  294. data/test/models/member.rb +12 -0
  295. data/test/models/member_detail.rb +5 -0
  296. data/test/models/member_type.rb +3 -0
  297. data/test/models/membership.rb +9 -0
  298. data/test/models/minimalistic.rb +2 -0
  299. data/test/models/mixed_case_monkey.rb +3 -0
  300. data/test/models/movie.rb +5 -0
  301. data/test/models/order.rb +4 -0
  302. data/test/models/organization.rb +6 -0
  303. data/test/models/owner.rb +5 -0
  304. data/test/models/parrot.rb +16 -0
  305. data/test/models/person.rb +16 -0
  306. data/test/models/pet.rb +5 -0
  307. data/test/models/pirate.rb +70 -0
  308. data/test/models/post.rb +100 -0
  309. data/test/models/price_estimate.rb +3 -0
  310. data/test/models/project.rb +30 -0
  311. data/test/models/reader.rb +4 -0
  312. data/test/models/reference.rb +4 -0
  313. data/test/models/reply.rb +46 -0
  314. data/test/models/ship.rb +10 -0
  315. data/test/models/ship_part.rb +5 -0
  316. data/test/models/sponsor.rb +4 -0
  317. data/test/models/subject.rb +4 -0
  318. data/test/models/subscriber.rb +8 -0
  319. data/test/models/subscription.rb +4 -0
  320. data/test/models/tag.rb +7 -0
  321. data/test/models/tagging.rb +10 -0
  322. data/test/models/task.rb +3 -0
  323. data/test/models/topic.rb +80 -0
  324. data/test/models/toy.rb +6 -0
  325. data/test/models/treasure.rb +8 -0
  326. data/test/models/vertex.rb +9 -0
  327. data/test/models/warehouse_thing.rb +5 -0
  328. data/test/schema/mysql_specific_schema.rb +24 -0
  329. data/test/schema/postgresql_specific_schema.rb +114 -0
  330. data/test/schema/schema.rb +493 -0
  331. data/test/schema/schema2.rb +6 -0
  332. data/test/schema/sqlite_specific_schema.rb +25 -0
  333. metadata +420 -0
@@ -0,0 +1,1142 @@
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_should_save_parent_but_not_invalid_child
42
+ firm = Firm.new(:name => 'GlobalMegaCorp')
43
+ assert firm.valid?
44
+
45
+ firm.build_account_using_primary_key
46
+ assert !firm.build_account_using_primary_key.valid?
47
+
48
+ assert firm.save
49
+ assert firm.account_using_primary_key.new_record?
50
+ end
51
+
52
+ def test_save_fails_for_invalid_has_one
53
+ firm = Firm.find(:first)
54
+ assert firm.valid?
55
+
56
+ firm.account = Account.new
57
+
58
+ assert !firm.account.valid?
59
+ assert !firm.valid?
60
+ assert !firm.save
61
+ assert_equal "is invalid", firm.errors.on("account")
62
+ end
63
+
64
+ def test_save_succeeds_for_invalid_has_one_with_validate_false
65
+ firm = Firm.find(:first)
66
+ assert firm.valid?
67
+
68
+ firm.unvalidated_account = Account.new
69
+
70
+ assert !firm.unvalidated_account.valid?
71
+ assert firm.valid?
72
+ assert firm.save
73
+ end
74
+
75
+ def test_build_before_child_saved
76
+ firm = Firm.find(1)
77
+
78
+ account = firm.account.build("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_build_before_either_saved
87
+ firm = Firm.new("name" => "GlobalMegaCorp")
88
+
89
+ firm.account = account = Account.new("credit_limit" => 1000)
90
+ assert_equal account, firm.account
91
+ assert account.new_record?
92
+ assert firm.save
93
+ assert_equal account, firm.account
94
+ assert !account.new_record?
95
+ end
96
+
97
+ def test_assignment_before_parent_saved
98
+ firm = Firm.new("name" => "GlobalMegaCorp")
99
+ firm.account = a = Account.find(1)
100
+ assert firm.new_record?
101
+ assert_equal a, firm.account
102
+ assert firm.save
103
+ assert_equal a, firm.account
104
+ assert_equal a, firm.account(true)
105
+ end
106
+
107
+ def test_assignment_before_either_saved
108
+ firm = Firm.new("name" => "GlobalMegaCorp")
109
+ firm.account = a = Account.new("credit_limit" => 1000)
110
+ assert firm.new_record?
111
+ assert a.new_record?
112
+ assert_equal a, firm.account
113
+ assert firm.save
114
+ assert !firm.new_record?
115
+ assert !a.new_record?
116
+ assert_equal a, firm.account
117
+ assert_equal a, firm.account(true)
118
+ end
119
+
120
+ def test_not_resaved_when_unchanged
121
+ firm = Firm.find(:first, :include => :account)
122
+ firm.name += '-changed'
123
+ assert_queries(1) { firm.save! }
124
+
125
+ firm = Firm.find(:first)
126
+ firm.account = Account.find(:first)
127
+ assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
128
+
129
+ firm = Firm.find(:first).clone
130
+ firm.account = Account.find(:first)
131
+ assert_queries(2) { firm.save! }
132
+
133
+ firm = Firm.find(:first).clone
134
+ firm.account = Account.find(:first).clone
135
+ assert_queries(2) { firm.save! }
136
+ end
137
+ end
138
+
139
+ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
140
+ def test_should_save_parent_but_not_invalid_child
141
+ client = Client.new(:name => 'Joe (the Plumber)')
142
+ assert client.valid?
143
+
144
+ client.build_firm
145
+ assert !client.firm.valid?
146
+
147
+ assert client.save
148
+ assert client.firm.new_record?
149
+ end
150
+
151
+ def test_save_fails_for_invalid_belongs_to
152
+ assert log = AuditLog.create(:developer_id => 0, :message => "")
153
+
154
+ log.developer = Developer.new
155
+ assert !log.developer.valid?
156
+ assert !log.valid?
157
+ assert !log.save
158
+ assert_equal "is invalid", log.errors.on("developer")
159
+ end
160
+
161
+ def test_save_succeeds_for_invalid_belongs_to_with_validate_false
162
+ assert log = AuditLog.create(:developer_id => 0, :message=> "")
163
+
164
+ log.unvalidated_developer = Developer.new
165
+ assert !log.unvalidated_developer.valid?
166
+ assert log.valid?
167
+ assert log.save
168
+ end
169
+
170
+ def test_assignment_before_parent_saved
171
+ client = Client.find(:first)
172
+ apple = Firm.new("name" => "Apple")
173
+ client.firm = apple
174
+ assert_equal apple, client.firm
175
+ assert apple.new_record?
176
+ assert client.save
177
+ assert apple.save
178
+ assert !apple.new_record?
179
+ assert_equal apple, client.firm
180
+ assert_equal apple, client.firm(true)
181
+ end
182
+
183
+ def test_assignment_before_either_saved
184
+ final_cut = Client.new("name" => "Final Cut")
185
+ apple = Firm.new("name" => "Apple")
186
+ final_cut.firm = apple
187
+ assert final_cut.new_record?
188
+ assert apple.new_record?
189
+ assert final_cut.save
190
+ assert !final_cut.new_record?
191
+ assert !apple.new_record?
192
+ assert_equal apple, final_cut.firm
193
+ assert_equal apple, final_cut.firm(true)
194
+ end
195
+
196
+ def test_store_two_association_with_one_save
197
+ num_orders = Order.count
198
+ num_customers = Customer.count
199
+ order = Order.new
200
+
201
+ customer1 = order.billing = Customer.new
202
+ customer2 = order.shipping = Customer.new
203
+ assert order.save
204
+ assert_equal customer1, order.billing
205
+ assert_equal customer2, order.shipping
206
+
207
+ order.reload
208
+
209
+ assert_equal customer1, order.billing
210
+ assert_equal customer2, order.shipping
211
+
212
+ assert_equal num_orders +1, Order.count
213
+ assert_equal num_customers +2, Customer.count
214
+ end
215
+
216
+ def test_store_association_in_two_relations_with_one_save
217
+ num_orders = Order.count
218
+ num_customers = Customer.count
219
+ order = Order.new
220
+
221
+ customer = order.billing = order.shipping = Customer.new
222
+ assert order.save
223
+ assert_equal customer, order.billing
224
+ assert_equal customer, order.shipping
225
+
226
+ order.reload
227
+
228
+ assert_equal customer, order.billing
229
+ assert_equal customer, order.shipping
230
+
231
+ assert_equal num_orders +1, Order.count
232
+ assert_equal num_customers +1, Customer.count
233
+ end
234
+
235
+ def test_store_association_in_two_relations_with_one_save_in_existing_object
236
+ num_orders = Order.count
237
+ num_customers = Customer.count
238
+ order = Order.create
239
+
240
+ customer = order.billing = order.shipping = Customer.new
241
+ assert order.save
242
+ assert_equal customer, order.billing
243
+ assert_equal customer, order.shipping
244
+
245
+ order.reload
246
+
247
+ assert_equal customer, order.billing
248
+ assert_equal customer, order.shipping
249
+
250
+ assert_equal num_orders +1, Order.count
251
+ assert_equal num_customers +1, Customer.count
252
+ end
253
+
254
+ def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
255
+ num_orders = Order.count
256
+ num_customers = Customer.count
257
+ order = Order.create
258
+
259
+ customer = order.billing = order.shipping = Customer.new
260
+ assert order.save
261
+ assert_equal customer, order.billing
262
+ assert_equal customer, order.shipping
263
+
264
+ order.reload
265
+
266
+ customer = order.billing = order.shipping = Customer.new
267
+
268
+ assert order.save
269
+ order.reload
270
+
271
+ assert_equal customer, order.billing
272
+ assert_equal customer, order.shipping
273
+
274
+ assert_equal num_orders +1, Order.count
275
+ assert_equal num_customers +2, Customer.count
276
+ end
277
+ end
278
+
279
+ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
280
+ fixtures :companies, :people
281
+
282
+ def test_invalid_adding
283
+ firm = Firm.find(1)
284
+ assert !(firm.clients_of_firm << c = Client.new)
285
+ assert c.new_record?
286
+ assert !firm.valid?
287
+ assert !firm.save
288
+ assert c.new_record?
289
+ end
290
+
291
+ def test_invalid_adding_before_save
292
+ no_of_firms = Firm.count
293
+ no_of_clients = Client.count
294
+ new_firm = Firm.new("name" => "A New Firm, Inc")
295
+ new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
296
+ assert c.new_record?
297
+ assert !c.valid?
298
+ assert !new_firm.valid?
299
+ assert !new_firm.save
300
+ assert c.new_record?
301
+ assert new_firm.new_record?
302
+ end
303
+
304
+ def test_invalid_adding_with_validate_false
305
+ firm = Firm.find(:first)
306
+ client = Client.new
307
+ firm.unvalidated_clients_of_firm << client
308
+
309
+ assert firm.valid?
310
+ assert !client.valid?
311
+ assert firm.save
312
+ assert client.new_record?
313
+ end
314
+
315
+ def test_valid_adding_with_validate_false
316
+ no_of_clients = Client.count
317
+
318
+ firm = Firm.find(:first)
319
+ client = Client.new("name" => "Apple")
320
+
321
+ assert firm.valid?
322
+ assert client.valid?
323
+ assert client.new_record?
324
+
325
+ firm.unvalidated_clients_of_firm << client
326
+
327
+ assert firm.save
328
+ assert !client.new_record?
329
+ assert_equal no_of_clients+1, Client.count
330
+ end
331
+
332
+ def test_invalid_build
333
+ new_client = companies(:first_firm).clients_of_firm.build
334
+ assert new_client.new_record?
335
+ assert !new_client.valid?
336
+ assert_equal new_client, companies(:first_firm).clients_of_firm.last
337
+ assert !companies(:first_firm).save
338
+ assert new_client.new_record?
339
+ assert_equal 1, companies(:first_firm).clients_of_firm(true).size
340
+ end
341
+
342
+ def test_adding_before_save
343
+ no_of_firms = Firm.count
344
+ no_of_clients = Client.count
345
+
346
+ new_firm = Firm.new("name" => "A New Firm, Inc")
347
+ c = Client.new("name" => "Apple")
348
+
349
+ new_firm.clients_of_firm.push Client.new("name" => "Natural Company")
350
+ assert_equal 1, new_firm.clients_of_firm.size
351
+ new_firm.clients_of_firm << c
352
+ assert_equal 2, new_firm.clients_of_firm.size
353
+
354
+ assert_equal no_of_firms, Firm.count # Firm was not saved to database.
355
+ assert_equal no_of_clients, Client.count # Clients were not saved to database.
356
+ assert new_firm.save
357
+ assert !new_firm.new_record?
358
+ assert !c.new_record?
359
+ assert_equal new_firm, c.firm
360
+ assert_equal no_of_firms+1, Firm.count # Firm was saved to database.
361
+ assert_equal no_of_clients+2, Client.count # Clients were saved to database.
362
+
363
+ assert_equal 2, new_firm.clients_of_firm.size
364
+ assert_equal 2, new_firm.clients_of_firm(true).size
365
+ end
366
+
367
+ def test_assign_ids
368
+ firm = Firm.new("name" => "Apple")
369
+ firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
370
+ firm.save
371
+ firm.reload
372
+ assert_equal 2, firm.clients.length
373
+ assert firm.clients.include?(companies(:second_client))
374
+ end
375
+
376
+ def test_assign_ids_for_through_a_belongs_to
377
+ post = Post.new(:title => "Assigning IDs works!", :body => "You heared it here first, folks!")
378
+ post.person_ids = [people(:david).id, people(:michael).id]
379
+ post.save
380
+ post.reload
381
+ assert_equal 2, post.people.length
382
+ assert post.people.include?(people(:david))
383
+ end
384
+
385
+ def test_build_before_save
386
+ company = companies(:first_firm)
387
+ new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
388
+ assert !company.clients_of_firm.loaded?
389
+
390
+ company.name += '-changed'
391
+ assert_queries(2) { assert company.save }
392
+ assert !new_client.new_record?
393
+ assert_equal 2, company.clients_of_firm(true).size
394
+ end
395
+
396
+ def test_build_many_before_save
397
+ company = companies(:first_firm)
398
+ new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
399
+
400
+ company.name += '-changed'
401
+ assert_queries(3) { assert company.save }
402
+ assert_equal 3, company.clients_of_firm(true).size
403
+ end
404
+
405
+ def test_build_via_block_before_save
406
+ company = companies(:first_firm)
407
+ new_client = assert_no_queries { company.clients_of_firm.build {|client| client.name = "Another Client" } }
408
+ assert !company.clients_of_firm.loaded?
409
+
410
+ company.name += '-changed'
411
+ assert_queries(2) { assert company.save }
412
+ assert !new_client.new_record?
413
+ assert_equal 2, company.clients_of_firm(true).size
414
+ end
415
+
416
+ def test_build_many_via_block_before_save
417
+ company = companies(:first_firm)
418
+ new_clients = assert_no_queries do
419
+ company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
420
+ client.name = "changed"
421
+ end
422
+ end
423
+
424
+ company.name += '-changed'
425
+ assert_queries(3) { assert company.save }
426
+ assert_equal 3, company.clients_of_firm(true).size
427
+ end
428
+
429
+ def test_replace_on_new_object
430
+ firm = Firm.new("name" => "New Firm")
431
+ firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
432
+ assert firm.save
433
+ firm.reload
434
+ assert_equal 2, firm.clients.length
435
+ assert firm.clients.include?(Client.find_by_name("New Client"))
436
+ end
437
+ end
438
+
439
+ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase
440
+ def test_autosave_new_record_on_belongs_to_can_be_disabled_per_relationship
441
+ new_account = Account.new("credit_limit" => 1000)
442
+ new_firm = Firm.new("name" => "some firm")
443
+
444
+ assert new_firm.new_record?
445
+ new_account.firm = new_firm
446
+ new_account.save!
447
+
448
+ assert !new_firm.new_record?
449
+
450
+ new_account = Account.new("credit_limit" => 1000)
451
+ new_autosaved_firm = Firm.new("name" => "some firm")
452
+
453
+ assert new_autosaved_firm.new_record?
454
+ new_account.unautosaved_firm = new_autosaved_firm
455
+ new_account.save!
456
+
457
+ assert new_autosaved_firm.new_record?
458
+ end
459
+
460
+ def test_autosave_new_record_on_has_one_can_be_disabled_per_relationship
461
+ firm = Firm.new("name" => "some firm")
462
+ account = Account.new("credit_limit" => 1000)
463
+
464
+ assert account.new_record?
465
+ firm.account = account
466
+ firm.save!
467
+
468
+ assert !account.new_record?
469
+
470
+ firm = Firm.new("name" => "some firm")
471
+ account = Account.new("credit_limit" => 1000)
472
+
473
+ firm.unautosaved_account = account
474
+
475
+ assert account.new_record?
476
+ firm.unautosaved_account = account
477
+ firm.save!
478
+
479
+ assert account.new_record?
480
+ end
481
+
482
+ def test_autosave_new_record_on_has_many_can_be_disabled_per_relationship
483
+ firm = Firm.new("name" => "some firm")
484
+ account = Account.new("credit_limit" => 1000)
485
+
486
+ assert account.new_record?
487
+ firm.accounts << account
488
+
489
+ firm.save!
490
+ assert !account.new_record?
491
+
492
+ firm = Firm.new("name" => "some firm")
493
+ account = Account.new("credit_limit" => 1000)
494
+
495
+ assert account.new_record?
496
+ firm.unautosaved_accounts << account
497
+
498
+ firm.save!
499
+ assert account.new_record?
500
+ end
501
+ end
502
+
503
+ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
504
+ self.use_transactional_fixtures = false
505
+
506
+ def setup
507
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
508
+ @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
509
+ end
510
+
511
+ # reload
512
+ def test_a_marked_for_destruction_record_should_not_be_be_marked_after_reload
513
+ @pirate.mark_for_destruction
514
+ @pirate.ship.mark_for_destruction
515
+
516
+ assert !@pirate.reload.marked_for_destruction?
517
+ assert !@pirate.ship.marked_for_destruction?
518
+ end
519
+
520
+ # has_one
521
+ def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
522
+ assert !@pirate.ship.marked_for_destruction?
523
+
524
+ @pirate.ship.mark_for_destruction
525
+ id = @pirate.ship.id
526
+
527
+ assert @pirate.ship.marked_for_destruction?
528
+ assert Ship.find_by_id(id)
529
+
530
+ @pirate.save
531
+ assert_nil @pirate.reload.ship
532
+ assert_nil Ship.find_by_id(id)
533
+ end
534
+
535
+ def test_should_skip_validation_on_a_child_association_if_marked_for_destruction
536
+ @pirate.ship.name = ''
537
+ assert !@pirate.valid?
538
+
539
+ @pirate.ship.mark_for_destruction
540
+ @pirate.ship.expects(:valid?).never
541
+ assert_difference('Ship.count', -1) { @pirate.save! }
542
+ end
543
+
544
+ def test_a_child_marked_for_destruction_should_not_be_destroyed_twice
545
+ @pirate.ship.mark_for_destruction
546
+ assert @pirate.save
547
+ @pirate.ship.expects(:destroy).never
548
+ assert @pirate.save
549
+ end
550
+
551
+ def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_child
552
+ # Stub the save method of the @pirate.ship instance to destroy and then raise an exception
553
+ class << @pirate.ship
554
+ def save(*args)
555
+ super
556
+ destroy
557
+ raise 'Oh noes!'
558
+ end
559
+ end
560
+
561
+ assert_raise(RuntimeError) { assert !@pirate.save }
562
+ assert_not_nil @pirate.reload.ship
563
+ end
564
+
565
+ # belongs_to
566
+ def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
567
+ assert !@ship.pirate.marked_for_destruction?
568
+
569
+ @ship.pirate.mark_for_destruction
570
+ id = @ship.pirate.id
571
+
572
+ assert @ship.pirate.marked_for_destruction?
573
+ assert Pirate.find_by_id(id)
574
+
575
+ @ship.save
576
+ assert_nil @ship.reload.pirate
577
+ assert_nil Pirate.find_by_id(id)
578
+ end
579
+
580
+ def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction
581
+ @ship.pirate.catchphrase = ''
582
+ assert !@ship.valid?
583
+
584
+ @ship.pirate.mark_for_destruction
585
+ @ship.pirate.expects(:valid?).never
586
+ assert_difference('Pirate.count', -1) { @ship.save! }
587
+ end
588
+
589
+ def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice
590
+ @ship.pirate.mark_for_destruction
591
+ assert @ship.save
592
+ @ship.pirate.expects(:destroy).never
593
+ assert @ship.save
594
+ end
595
+
596
+ def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_parent
597
+ # Stub the save method of the @ship.pirate instance to destroy and then raise an exception
598
+ class << @ship.pirate
599
+ def save(*args)
600
+ super
601
+ destroy
602
+ raise 'Oh noes!'
603
+ end
604
+ end
605
+
606
+ assert_raise(RuntimeError) { assert !@ship.save }
607
+ assert_not_nil @ship.reload.pirate
608
+ end
609
+
610
+ # has_many & has_and_belongs_to
611
+ %w{ parrots birds }.each do |association_name|
612
+ define_method("test_should_destroy_#{association_name}_as_part_of_the_save_transaction_if_they_were_marked_for_destroyal") do
613
+ 2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
614
+
615
+ assert !@pirate.send(association_name).any? { |child| child.marked_for_destruction? }
616
+
617
+ @pirate.send(association_name).each { |child| child.mark_for_destruction }
618
+ klass = @pirate.send(association_name).first.class
619
+ ids = @pirate.send(association_name).map(&:id)
620
+
621
+ assert @pirate.send(association_name).all? { |child| child.marked_for_destruction? }
622
+ ids.each { |id| assert klass.find_by_id(id) }
623
+
624
+ @pirate.save
625
+ assert @pirate.reload.send(association_name).empty?
626
+ ids.each { |id| assert_nil klass.find_by_id(id) }
627
+ end
628
+
629
+ define_method("test_should_skip_validation_on_the_#{association_name}_association_if_marked_for_destruction") do
630
+ 2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
631
+ children = @pirate.send(association_name)
632
+
633
+ children.each { |child| child.name = '' }
634
+ assert !@pirate.valid?
635
+
636
+ children.each do |child|
637
+ child.mark_for_destruction
638
+ child.expects(:valid?).never
639
+ end
640
+ assert_difference("#{association_name.classify}.count", -2) { @pirate.save! }
641
+ end
642
+
643
+ define_method("test_should_skip_validation_on_the_#{association_name}_association_if_destroyed") do
644
+ @pirate.send(association_name).create!(:name => "#{association_name}_1")
645
+ children = @pirate.send(association_name)
646
+
647
+ children.each { |child| child.name = '' }
648
+ assert !@pirate.valid?
649
+
650
+ children.each { |child| child.destroy }
651
+ assert @pirate.valid?
652
+ end
653
+
654
+ define_method("test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_#{association_name}") do
655
+ @pirate.send(association_name).create!(:name => "#{association_name}_1")
656
+ children = @pirate.send(association_name)
657
+
658
+ children.each { |child| child.mark_for_destruction }
659
+ assert @pirate.save
660
+ children.each { |child| child.expects(:destroy).never }
661
+ assert @pirate.save
662
+ end
663
+
664
+ define_method("test_should_rollback_destructions_if_an_exception_occurred_while_saving_#{association_name}") do
665
+ 2.times { |i| @pirate.send(association_name).create!(:name => "#{association_name}_#{i}") }
666
+ before = @pirate.send(association_name).map { |c| c }
667
+
668
+ # Stub the save method of the first child to destroy and the second to raise an exception
669
+ class << before.first
670
+ def save(*args)
671
+ super
672
+ destroy
673
+ end
674
+ end
675
+ class << before.last
676
+ def save(*args)
677
+ super
678
+ raise 'Oh noes!'
679
+ end
680
+ end
681
+
682
+ assert_raise(RuntimeError) { assert !@pirate.save }
683
+ assert_equal before, @pirate.reload.send(association_name)
684
+ end
685
+
686
+ # Add and remove callbacks tests for association collections.
687
+ %w{ method proc }.each do |callback_type|
688
+ define_method("test_should_run_add_callback_#{callback_type}s_for_#{association_name}") do
689
+ association_name_with_callbacks = "#{association_name}_with_#{callback_type}_callbacks"
690
+
691
+ pirate = Pirate.new(:catchphrase => "Arr")
692
+ pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed")
693
+
694
+ expected = [
695
+ "before_adding_#{callback_type}_#{association_name.singularize}_<new>",
696
+ "after_adding_#{callback_type}_#{association_name.singularize}_<new>"
697
+ ]
698
+
699
+ assert_equal expected, pirate.ship_log
700
+ end
701
+
702
+ define_method("test_should_run_remove_callback_#{callback_type}s_for_#{association_name}") do
703
+ association_name_with_callbacks = "#{association_name}_with_#{callback_type}_callbacks"
704
+
705
+ @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
706
+ @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction }
707
+ child_id = @pirate.send(association_name_with_callbacks).first.id
708
+
709
+ @pirate.ship_log.clear
710
+ @pirate.save
711
+
712
+ expected = [
713
+ "before_removing_#{callback_type}_#{association_name.singularize}_#{child_id}",
714
+ "after_removing_#{callback_type}_#{association_name.singularize}_#{child_id}"
715
+ ]
716
+
717
+ assert_equal expected, @pirate.ship_log
718
+ end
719
+ end
720
+ end
721
+ end
722
+
723
+ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
724
+ self.use_transactional_fixtures = false
725
+
726
+ def setup
727
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
728
+ @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
729
+ end
730
+
731
+ def test_should_still_work_without_an_associated_model
732
+ @ship.destroy
733
+ @pirate.reload.catchphrase = "Arr"
734
+ @pirate.save
735
+ assert 'Arr', @pirate.reload.catchphrase
736
+ end
737
+
738
+ def test_should_automatically_save_the_associated_model
739
+ @pirate.ship.name = 'The Vile Insanity'
740
+ @pirate.save
741
+ assert_equal 'The Vile Insanity', @pirate.reload.ship.name
742
+ end
743
+
744
+ def test_should_automatically_save_bang_the_associated_model
745
+ @pirate.ship.name = 'The Vile Insanity'
746
+ @pirate.save!
747
+ assert_equal 'The Vile Insanity', @pirate.reload.ship.name
748
+ end
749
+
750
+ def test_should_automatically_validate_the_associated_model
751
+ @pirate.ship.name = ''
752
+ assert !@pirate.valid?
753
+ assert_equal "can't be blank", @pirate.errors.on(:"ship.name")
754
+ end
755
+
756
+ def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
757
+ @pirate.ship.name = nil
758
+ @pirate.catchphrase = nil
759
+ assert !@pirate.valid?
760
+ assert @pirate.errors.full_messages.include?("Name can't be blank")
761
+ assert @pirate.errors.full_messages.include?("Catchphrase can't be blank")
762
+ end
763
+
764
+ def test_should_still_allow_to_bypass_validations_on_the_associated_model
765
+ @pirate.catchphrase = ''
766
+ @pirate.ship.name = ''
767
+ @pirate.save(false)
768
+ assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name]
769
+ end
770
+
771
+ def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth
772
+ 2.times { |i| @pirate.ship.parts.create!(:name => "part #{i}") }
773
+
774
+ @pirate.catchphrase = ''
775
+ @pirate.ship.name = ''
776
+ @pirate.ship.parts.each { |part| part.name = '' }
777
+ @pirate.save(false)
778
+
779
+ values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)]
780
+ assert_equal ['', '', '', ''], values
781
+ end
782
+
783
+ def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
784
+ @pirate.ship.name = ''
785
+ assert_raise(ActiveRecord::RecordInvalid) do
786
+ @pirate.save!
787
+ end
788
+ end
789
+
790
+ def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
791
+ before = [@pirate.catchphrase, @pirate.ship.name]
792
+
793
+ @pirate.catchphrase = 'Arr'
794
+ @pirate.ship.name = 'The Vile Insanity'
795
+
796
+ # Stub the save method of the @pirate.ship instance to raise an exception
797
+ class << @pirate.ship
798
+ def save(*args)
799
+ super
800
+ raise 'Oh noes!'
801
+ end
802
+ end
803
+
804
+ assert_raise(RuntimeError) { assert !@pirate.save }
805
+ assert_equal before, [@pirate.reload.catchphrase, @pirate.ship.name]
806
+ end
807
+
808
+ def test_should_not_load_the_associated_model
809
+ assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
810
+ end
811
+ end
812
+
813
+ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
814
+ self.use_transactional_fixtures = false
815
+
816
+ def setup
817
+ @ship = Ship.create(:name => 'Nights Dirty Lightning')
818
+ @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
819
+ end
820
+
821
+ def test_should_still_work_without_an_associated_model
822
+ @pirate.destroy
823
+ @ship.reload.name = "The Vile Insanity"
824
+ @ship.save
825
+ assert 'The Vile Insanity', @ship.reload.name
826
+ end
827
+
828
+ def test_should_automatically_save_the_associated_model
829
+ @ship.pirate.catchphrase = 'Arr'
830
+ @ship.save
831
+ assert_equal 'Arr', @ship.reload.pirate.catchphrase
832
+ end
833
+
834
+ def test_should_automatically_save_bang_the_associated_model
835
+ @ship.pirate.catchphrase = 'Arr'
836
+ @ship.save!
837
+ assert_equal 'Arr', @ship.reload.pirate.catchphrase
838
+ end
839
+
840
+ def test_should_automatically_validate_the_associated_model
841
+ @ship.pirate.catchphrase = ''
842
+ assert !@ship.valid?
843
+ assert_equal "can't be blank", @ship.errors.on(:"pirate.catchphrase")
844
+ end
845
+
846
+ def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
847
+ @ship.name = nil
848
+ @ship.pirate.catchphrase = nil
849
+ assert !@ship.valid?
850
+ assert @ship.errors.full_messages.include?("Name can't be blank")
851
+ assert @ship.errors.full_messages.include?("Catchphrase can't be blank")
852
+ end
853
+
854
+ def test_should_still_allow_to_bypass_validations_on_the_associated_model
855
+ @ship.pirate.catchphrase = ''
856
+ @ship.name = ''
857
+ @ship.save(false)
858
+ assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase]
859
+ end
860
+
861
+ def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
862
+ @ship.pirate.catchphrase = ''
863
+ assert_raise(ActiveRecord::RecordInvalid) do
864
+ @ship.save!
865
+ end
866
+ end
867
+
868
+ def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
869
+ before = [@ship.pirate.catchphrase, @ship.name]
870
+
871
+ @ship.pirate.catchphrase = 'Arr'
872
+ @ship.name = 'The Vile Insanity'
873
+
874
+ # Stub the save method of the @ship.pirate instance to raise an exception
875
+ class << @ship.pirate
876
+ def save(*args)
877
+ super
878
+ raise 'Oh noes!'
879
+ end
880
+ end
881
+
882
+ assert_raise(RuntimeError) { assert !@ship.save }
883
+ # TODO: Why does using reload on @ship looses the associated pirate?
884
+ assert_equal before, [@ship.pirate.reload.catchphrase, @ship.reload.name]
885
+ end
886
+
887
+ def test_should_not_load_the_associated_model
888
+ assert_queries(1) { @ship.name = 'The Vile Insanity'; @ship.save! }
889
+ end
890
+ end
891
+
892
+ module AutosaveAssociationOnACollectionAssociationTests
893
+ def test_should_automatically_save_the_associated_models
894
+ new_names = ['Grace OMalley', 'Privateers Greed']
895
+ @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
896
+
897
+ @pirate.save
898
+ assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
899
+ end
900
+
901
+ def test_should_automatically_save_bang_the_associated_models
902
+ new_names = ['Grace OMalley', 'Privateers Greed']
903
+ @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
904
+
905
+ @pirate.save!
906
+ assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
907
+ end
908
+
909
+ def test_should_automatically_validate_the_associated_models
910
+ @pirate.send(@association_name).each { |child| child.name = '' }
911
+
912
+ assert !@pirate.valid?
913
+ assert @pirate.errors.full_messages.include?("Name can't be blank")
914
+ assert @pirate.errors.on(@association_name).blank?
915
+ end
916
+
917
+ def test_should_not_use_default_invalid_error_on_associated_models
918
+ @pirate.send(@association_name).build(:name => '')
919
+
920
+ assert !@pirate.valid?
921
+ assert_equal "can't be blank", @pirate.errors.on("#{@association_name}.name")
922
+ assert @pirate.errors.on(@association_name).blank?
923
+ end
924
+
925
+ def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
926
+ @pirate.send(@association_name).each { |child| child.name = '' }
927
+ @pirate.catchphrase = nil
928
+
929
+ assert !@pirate.valid?
930
+ assert_equal "can't be blank", @pirate.errors.on("#{@association_name}.name")
931
+ assert !@pirate.errors.on(:catchphrase).blank?
932
+ end
933
+
934
+ def test_should_allow_to_bypass_validations_on_the_associated_models_on_update
935
+ @pirate.catchphrase = ''
936
+ @pirate.send(@association_name).each { |child| child.name = '' }
937
+
938
+ assert @pirate.save(false)
939
+ assert_equal ['', '', ''], [
940
+ @pirate.reload.catchphrase,
941
+ @pirate.send(@association_name).first.name,
942
+ @pirate.send(@association_name).last.name
943
+ ]
944
+ end
945
+
946
+ def test_should_validation_the_associated_models_on_create
947
+ assert_no_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count") do
948
+ 2.times { @pirate.send(@association_name).build }
949
+ @pirate.save(true)
950
+ end
951
+ end
952
+
953
+ def test_should_allow_to_bypass_validations_on_the_associated_models_on_create
954
+ assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", +2) do
955
+ 2.times { @pirate.send(@association_name).build }
956
+ @pirate.save(false)
957
+ end
958
+ end
959
+
960
+ def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
961
+ before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)]
962
+ new_names = ['Grace OMalley', 'Privateers Greed']
963
+
964
+ @pirate.catchphrase = 'Arr'
965
+ @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
966
+
967
+ # Stub the save method of the first child instance to raise an exception
968
+ class << @pirate.send(@association_name).first
969
+ def save(*args)
970
+ super
971
+ raise 'Oh noes!'
972
+ end
973
+ end
974
+
975
+ assert_raise(RuntimeError) { assert !@pirate.save }
976
+ assert_equal before, [@pirate.reload.catchphrase, *@pirate.send(@association_name).map(&:name)]
977
+ end
978
+
979
+ def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
980
+ @pirate.send(@association_name).each { |child| child.name = '' }
981
+ assert_raise(ActiveRecord::RecordInvalid) do
982
+ @pirate.save!
983
+ end
984
+ end
985
+
986
+ def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet
987
+ assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
988
+
989
+ @pirate.send(@association_name).class # hack to load the target
990
+
991
+ assert_queries(3) do
992
+ @pirate.catchphrase = 'Yarr'
993
+ new_names = ['Grace OMalley', 'Privateers Greed']
994
+ @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
995
+ @pirate.save!
996
+ end
997
+ end
998
+ end
999
+
1000
+ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
1001
+ self.use_transactional_fixtures = false
1002
+
1003
+ def setup
1004
+ @association_name = :birds
1005
+
1006
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1007
+ @child_1 = @pirate.birds.create(:name => 'Posideons Killer')
1008
+ @child_2 = @pirate.birds.create(:name => 'Killer bandita Dionne')
1009
+ end
1010
+
1011
+ include AutosaveAssociationOnACollectionAssociationTests
1012
+ end
1013
+
1014
+ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
1015
+ self.use_transactional_fixtures = false
1016
+
1017
+ def setup
1018
+ @association_name = :parrots
1019
+ @habtm = true
1020
+
1021
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1022
+ @child_1 = @pirate.parrots.create(:name => 'Posideons Killer')
1023
+ @child_2 = @pirate.parrots.create(:name => 'Killer bandita Dionne')
1024
+ end
1025
+
1026
+ include AutosaveAssociationOnACollectionAssociationTests
1027
+ end
1028
+
1029
+ class TestAutosaveAssociationValidationsOnAHasManyAssocication < ActiveRecord::TestCase
1030
+ self.use_transactional_fixtures = false
1031
+
1032
+ def setup
1033
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1034
+ @pirate.birds.create(:name => 'cookoo')
1035
+ end
1036
+
1037
+ test "should automatically validate associations" do
1038
+ assert @pirate.valid?
1039
+ @pirate.birds.each { |bird| bird.name = '' }
1040
+
1041
+ assert !@pirate.valid?
1042
+ end
1043
+ end
1044
+
1045
+ class TestAutosaveAssociationValidationsOnAHasOneAssocication < ActiveRecord::TestCase
1046
+ self.use_transactional_fixtures = false
1047
+
1048
+ def setup
1049
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1050
+ @pirate.create_ship(:name => 'titanic')
1051
+ end
1052
+
1053
+ test "should automatically validate associations with :validate => true" do
1054
+ assert @pirate.valid?
1055
+ @pirate.ship.name = ''
1056
+ assert !@pirate.valid?
1057
+ end
1058
+
1059
+ test "should not automatically validate associations without :validate => true" do
1060
+ assert @pirate.valid?
1061
+ @pirate.non_validated_ship.name = ''
1062
+ assert @pirate.valid?
1063
+ end
1064
+ end
1065
+
1066
+ class TestAutosaveAssociationValidationsOnABelongsToAssocication < ActiveRecord::TestCase
1067
+ self.use_transactional_fixtures = false
1068
+
1069
+ def setup
1070
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1071
+ end
1072
+
1073
+ test "should automatically validate associations with :validate => true" do
1074
+ assert @pirate.valid?
1075
+ @pirate.parrot = Parrot.new(:name => '')
1076
+ assert !@pirate.valid?
1077
+ end
1078
+
1079
+ test "should not automatically validate associations without :validate => true" do
1080
+ assert @pirate.valid?
1081
+ @pirate.non_validated_parrot = Parrot.new(:name => '')
1082
+ assert @pirate.valid?
1083
+ end
1084
+ end
1085
+
1086
+ class TestAutosaveAssociationValidationsOnAHABTMAssocication < ActiveRecord::TestCase
1087
+ self.use_transactional_fixtures = false
1088
+
1089
+ def setup
1090
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1091
+ end
1092
+
1093
+ test "should automatically validate associations with :validate => true" do
1094
+ assert @pirate.valid?
1095
+ @pirate.parrots = [ Parrot.new(:name => 'popuga') ]
1096
+ @pirate.parrots.each { |parrot| parrot.name = '' }
1097
+ assert !@pirate.valid?
1098
+ end
1099
+
1100
+ test "should not automatically validate associations without :validate => true" do
1101
+ assert @pirate.valid?
1102
+ @pirate.non_validated_parrots = [ Parrot.new(:name => 'popuga') ]
1103
+ @pirate.non_validated_parrots.each { |parrot| parrot.name = '' }
1104
+ assert @pirate.valid?
1105
+ end
1106
+ end
1107
+
1108
+ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCase
1109
+ self.use_transactional_fixtures = false
1110
+
1111
+ def setup
1112
+ @pirate = Pirate.new
1113
+ end
1114
+
1115
+ test "should generate validation methods for has_many associations" do
1116
+ assert @pirate.respond_to?(:validate_associated_records_for_birds)
1117
+ end
1118
+
1119
+ test "should generate validation methods for has_one associations with :validate => true" do
1120
+ assert @pirate.respond_to?(:validate_associated_records_for_ship)
1121
+ end
1122
+
1123
+ test "should not generate validation methods for has_one associations without :validate => true" do
1124
+ assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_ship)
1125
+ end
1126
+
1127
+ test "should generate validation methods for belongs_to associations with :validate => true" do
1128
+ assert @pirate.respond_to?(:validate_associated_records_for_parrot)
1129
+ end
1130
+
1131
+ test "should not generate validation methods for belongs_to associations without :validate => true" do
1132
+ assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrot)
1133
+ end
1134
+
1135
+ test "should generate validation methods for HABTM associations with :validate => true" do
1136
+ assert @pirate.respond_to?(:validate_associated_records_for_parrots)
1137
+ end
1138
+
1139
+ test "should not generate validation methods for HABTM associations without :validate => true" do
1140
+ assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrots)
1141
+ end
1142
+ end