activerecord_csi 2.3.5.p6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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