ibm_db 3.0.5-x86-mingw32 → 4.0.0-x86-mingw32

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 (586) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +4 -0
  3. data/LICENSE +1 -1
  4. data/MANIFEST +14 -14
  5. data/ParameterizedQueries README +6 -6
  6. data/README +208 -225
  7. data/ext/Makefile.nt32 +181 -181
  8. data/ext/Makefile.nt32.191 +212 -212
  9. data/ext/extconf.rb +291 -291
  10. data/ext/ibm_db.c +11887 -11887
  11. data/ext/ruby_ibm_db.h +241 -241
  12. data/ext/ruby_ibm_db_cli.c +866 -866
  13. data/ext/ruby_ibm_db_cli.h +500 -500
  14. data/init.rb +41 -41
  15. data/lib/IBM_DB.rb +27 -27
  16. data/lib/active_record/connection_adapters/ibm_db_adapter.rb +3452 -3177
  17. data/lib/active_record/connection_adapters/ibmdb_adapter.rb +5 -2
  18. data/lib/active_record/vendor/db2-i5-zOS.yaml +328 -328
  19. data/lib/mswin32/ibm_db.rb +91 -123
  20. data/lib/mswin32/rb2x/i386/ibm_db.so +0 -0
  21. data/test/active_record/connection_adapters/fake_adapter.rb +49 -46
  22. data/test/assets/example.log +1 -1
  23. data/test/assets/test.txt +1 -1
  24. data/test/cases/adapter_test.rb +351 -276
  25. data/test/cases/adapters/mysql2/active_schema_test.rb +193 -0
  26. data/test/cases/adapters/mysql2/bind_parameter_test.rb +50 -0
  27. data/test/cases/adapters/mysql2/boolean_test.rb +100 -0
  28. data/test/cases/adapters/mysql2/case_sensitivity_test.rb +63 -0
  29. data/test/cases/adapters/mysql2/charset_collation_test.rb +54 -0
  30. data/test/cases/adapters/mysql2/connection_test.rb +210 -0
  31. data/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb +45 -0
  32. data/test/cases/adapters/mysql2/enum_test.rb +26 -0
  33. data/test/cases/adapters/mysql2/explain_test.rb +21 -0
  34. data/test/cases/adapters/mysql2/json_test.rb +195 -0
  35. data/test/cases/adapters/mysql2/mysql2_adapter_test.rb +83 -0
  36. data/test/cases/adapters/mysql2/reserved_word_test.rb +152 -0
  37. data/test/cases/adapters/mysql2/schema_migrations_test.rb +59 -0
  38. data/test/cases/adapters/mysql2/schema_test.rb +126 -0
  39. data/test/cases/adapters/mysql2/sp_test.rb +36 -0
  40. data/test/cases/adapters/mysql2/sql_types_test.rb +14 -0
  41. data/test/cases/adapters/mysql2/table_options_test.rb +42 -0
  42. data/test/cases/adapters/mysql2/unsigned_type_test.rb +66 -0
  43. data/test/cases/adapters/postgresql/active_schema_test.rb +98 -0
  44. data/test/cases/adapters/postgresql/array_test.rb +339 -0
  45. data/test/cases/adapters/postgresql/bit_string_test.rb +82 -0
  46. data/test/cases/adapters/postgresql/bytea_test.rb +134 -0
  47. data/test/cases/adapters/postgresql/case_insensitive_test.rb +26 -0
  48. data/test/cases/adapters/postgresql/change_schema_test.rb +38 -0
  49. data/test/cases/adapters/postgresql/cidr_test.rb +25 -0
  50. data/test/cases/adapters/postgresql/citext_test.rb +78 -0
  51. data/test/cases/adapters/postgresql/collation_test.rb +53 -0
  52. data/test/cases/adapters/postgresql/composite_test.rb +132 -0
  53. data/test/cases/adapters/postgresql/connection_test.rb +257 -0
  54. data/test/cases/adapters/postgresql/datatype_test.rb +92 -0
  55. data/test/cases/adapters/postgresql/domain_test.rb +47 -0
  56. data/test/cases/adapters/postgresql/enum_test.rb +91 -0
  57. data/test/cases/adapters/postgresql/explain_test.rb +20 -0
  58. data/test/cases/adapters/postgresql/extension_migration_test.rb +63 -0
  59. data/test/cases/adapters/postgresql/full_text_test.rb +44 -0
  60. data/test/cases/adapters/postgresql/geometric_test.rb +378 -0
  61. data/test/cases/adapters/postgresql/hstore_test.rb +382 -0
  62. data/test/cases/adapters/postgresql/infinity_test.rb +69 -0
  63. data/test/cases/adapters/postgresql/integer_test.rb +25 -0
  64. data/test/cases/adapters/postgresql/json_test.rb +237 -0
  65. data/test/cases/adapters/postgresql/ltree_test.rb +53 -0
  66. data/test/cases/adapters/postgresql/money_test.rb +96 -0
  67. data/test/cases/adapters/postgresql/network_test.rb +94 -0
  68. data/test/cases/adapters/postgresql/numbers_test.rb +49 -0
  69. data/test/cases/adapters/postgresql/postgresql_adapter_test.rb +405 -0
  70. data/test/cases/adapters/postgresql/prepared_statements_test.rb +22 -0
  71. data/test/cases/adapters/postgresql/quoting_test.rb +44 -0
  72. data/test/cases/adapters/postgresql/range_test.rb +343 -0
  73. data/test/cases/adapters/postgresql/referential_integrity_test.rb +111 -0
  74. data/test/cases/adapters/postgresql/rename_table_test.rb +34 -0
  75. data/test/cases/adapters/postgresql/schema_authorization_test.rb +119 -0
  76. data/test/cases/adapters/postgresql/schema_test.rb +597 -0
  77. data/test/cases/adapters/postgresql/serial_test.rb +154 -0
  78. data/test/cases/adapters/postgresql/statement_pool_test.rb +41 -0
  79. data/test/cases/adapters/postgresql/timestamp_test.rb +90 -0
  80. data/test/cases/adapters/postgresql/type_lookup_test.rb +33 -0
  81. data/test/cases/adapters/postgresql/utils_test.rb +62 -0
  82. data/test/cases/adapters/postgresql/uuid_test.rb +294 -0
  83. data/test/cases/adapters/postgresql/xml_test.rb +54 -0
  84. data/test/cases/adapters/sqlite3/collation_test.rb +53 -0
  85. data/test/cases/adapters/sqlite3/copy_table_test.rb +98 -0
  86. data/test/cases/adapters/sqlite3/explain_test.rb +21 -0
  87. data/test/cases/adapters/sqlite3/quoting_test.rb +101 -0
  88. data/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +441 -0
  89. data/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb +24 -0
  90. data/test/cases/adapters/sqlite3/statement_pool_test.rb +20 -0
  91. data/test/cases/aggregations_test.rb +168 -158
  92. data/test/cases/ar_schema_test.rb +146 -161
  93. data/test/cases/associations/association_scope_test.rb +16 -21
  94. data/test/cases/associations/belongs_to_associations_test.rb +1141 -1029
  95. data/test/cases/associations/bidirectional_destroy_dependencies_test.rb +41 -0
  96. data/test/cases/associations/callbacks_test.rb +190 -192
  97. data/test/cases/associations/cascaded_eager_loading_test.rb +188 -188
  98. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -36
  99. data/test/cases/associations/eager_load_nested_include_test.rb +126 -128
  100. data/test/cases/associations/eager_singularization_test.rb +148 -148
  101. data/test/cases/associations/eager_test.rb +1514 -1429
  102. data/test/cases/associations/extension_test.rb +87 -82
  103. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +1004 -972
  104. data/test/cases/associations/has_many_associations_test.rb +2501 -2182
  105. data/test/cases/associations/has_many_through_associations_test.rb +1271 -1204
  106. data/test/cases/associations/has_one_associations_test.rb +707 -610
  107. data/test/cases/associations/has_one_through_associations_test.rb +383 -380
  108. data/test/cases/associations/inner_join_association_test.rb +139 -139
  109. data/test/cases/associations/inverse_associations_test.rb +733 -706
  110. data/test/cases/associations/join_model_test.rb +777 -754
  111. data/test/cases/associations/left_outer_join_association_test.rb +88 -0
  112. data/test/cases/associations/nested_through_associations_test.rb +579 -579
  113. data/test/cases/associations/required_test.rb +102 -82
  114. data/test/cases/associations_test.rb +385 -380
  115. data/test/cases/attribute_decorators_test.rb +125 -125
  116. data/test/cases/attribute_methods/read_test.rb +60 -60
  117. data/test/cases/attribute_methods_test.rb +1009 -952
  118. data/test/cases/attribute_set_test.rb +270 -210
  119. data/test/cases/attribute_test.rb +246 -180
  120. data/test/cases/attributes_test.rb +253 -136
  121. data/test/cases/autosave_association_test.rb +1708 -1595
  122. data/test/cases/base_test.rb +1713 -1664
  123. data/test/cases/batches_test.rb +489 -212
  124. data/test/cases/binary_test.rb +44 -52
  125. data/test/cases/bind_parameter_test.rb +110 -100
  126. data/test/cases/cache_key_test.rb +25 -0
  127. data/test/cases/calculations_test.rb +798 -646
  128. data/test/cases/callbacks_test.rb +636 -543
  129. data/test/cases/clone_test.rb +40 -40
  130. data/test/cases/coders/json_test.rb +15 -0
  131. data/test/cases/coders/yaml_column_test.rb +63 -63
  132. data/test/cases/collection_cache_key_test.rb +115 -0
  133. data/test/cases/column_alias_test.rb +17 -17
  134. data/test/cases/column_definition_test.rb +92 -123
  135. data/test/cases/comment_test.rb +143 -0
  136. data/test/cases/connection_adapters/adapter_leasing_test.rb +56 -54
  137. data/test/cases/connection_adapters/connection_handler_test.rb +160 -53
  138. data/test/cases/connection_adapters/connection_specification_test.rb +12 -12
  139. data/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb +255 -293
  140. data/test/cases/connection_adapters/mysql_type_lookup_test.rb +69 -65
  141. data/test/cases/connection_adapters/quoting_test.rb +13 -13
  142. data/test/cases/connection_adapters/schema_cache_test.rb +61 -56
  143. data/test/cases/connection_adapters/type_lookup_test.rb +118 -110
  144. data/test/cases/connection_management_test.rb +112 -122
  145. data/test/cases/connection_pool_test.rb +521 -346
  146. data/test/cases/connection_specification/resolver_test.rb +131 -116
  147. data/test/cases/core_test.rb +112 -112
  148. data/test/cases/counter_cache_test.rb +214 -209
  149. data/test/cases/custom_locking_test.rb +17 -17
  150. data/test/cases/database_statements_test.rb +34 -19
  151. data/test/cases/{invalid_date_test.rb → date_test.rb} +44 -32
  152. data/test/cases/date_time_precision_test.rb +106 -0
  153. data/test/cases/date_time_test.rb +61 -61
  154. data/test/cases/defaults_test.rb +218 -223
  155. data/test/cases/dirty_test.rb +763 -785
  156. data/test/cases/disconnected_test.rb +30 -28
  157. data/test/cases/dup_test.rb +157 -157
  158. data/test/cases/enum_test.rb +444 -290
  159. data/test/cases/errors_test.rb +16 -0
  160. data/test/cases/explain_subscriber_test.rb +64 -64
  161. data/test/cases/explain_test.rb +87 -76
  162. data/test/cases/finder_respond_to_test.rb +60 -60
  163. data/test/cases/finder_test.rb +1294 -1169
  164. data/test/cases/fixture_set/file_test.rb +156 -138
  165. data/test/cases/fixtures_test.rb +988 -908
  166. data/test/cases/forbidden_attributes_protection_test.rb +165 -99
  167. data/test/cases/habtm_destroy_order_test.rb +61 -61
  168. data/test/cases/helper.rb +204 -210
  169. data/test/cases/hot_compatibility_test.rb +142 -54
  170. data/test/cases/i18n_test.rb +45 -45
  171. data/test/cases/inheritance_test.rb +606 -375
  172. data/test/cases/integration_test.rb +155 -139
  173. data/test/cases/invalid_connection_test.rb +24 -22
  174. data/test/cases/invertible_migration_test.rb +387 -295
  175. data/test/cases/json_serialization_test.rb +311 -302
  176. data/test/cases/locking_test.rb +493 -477
  177. data/test/cases/log_subscriber_test.rb +225 -136
  178. data/test/cases/migration/change_schema_test.rb +458 -512
  179. data/test/cases/migration/change_table_test.rb +256 -224
  180. data/test/cases/migration/column_attributes_test.rb +176 -192
  181. data/test/cases/migration/column_positioning_test.rb +56 -56
  182. data/test/cases/migration/columns_test.rb +310 -304
  183. data/test/cases/migration/command_recorder_test.rb +350 -305
  184. data/test/cases/migration/compatibility_test.rb +118 -0
  185. data/test/cases/migration/create_join_table_test.rb +157 -148
  186. data/test/cases/migration/foreign_key_test.rb +360 -328
  187. data/test/cases/migration/helper.rb +39 -39
  188. data/test/cases/migration/index_test.rb +218 -216
  189. data/test/cases/migration/logger_test.rb +36 -36
  190. data/test/cases/migration/pending_migrations_test.rb +52 -53
  191. data/test/cases/migration/references_foreign_key_test.rb +216 -169
  192. data/test/cases/migration/references_index_test.rb +101 -101
  193. data/test/cases/migration/references_statements_test.rb +136 -116
  194. data/test/cases/migration/rename_table_test.rb +93 -93
  195. data/test/cases/migration_test.rb +1157 -959
  196. data/test/cases/migrator_test.rb +470 -388
  197. data/test/cases/mixin_test.rb +68 -70
  198. data/test/cases/modules_test.rb +172 -173
  199. data/test/cases/multiparameter_attributes_test.rb +372 -350
  200. data/test/cases/multiple_db_test.rb +122 -115
  201. data/test/cases/nested_attributes_test.rb +1098 -1070
  202. data/test/cases/nested_attributes_with_callbacks_test.rb +144 -144
  203. data/test/cases/persistence_test.rb +1001 -909
  204. data/test/cases/pooled_connections_test.rb +81 -81
  205. data/test/cases/primary_keys_test.rb +376 -237
  206. data/test/cases/query_cache_test.rb +446 -326
  207. data/test/cases/quoting_test.rb +202 -156
  208. data/test/cases/readonly_test.rb +119 -118
  209. data/test/cases/reaper_test.rb +85 -85
  210. data/test/cases/reflection_test.rb +509 -463
  211. data/test/cases/relation/delegation_test.rb +63 -68
  212. data/test/cases/relation/merging_test.rb +157 -161
  213. data/test/cases/relation/mutation_test.rb +183 -165
  214. data/test/cases/relation/or_test.rb +92 -0
  215. data/test/cases/relation/predicate_builder_test.rb +16 -14
  216. data/test/cases/relation/record_fetch_warning_test.rb +40 -0
  217. data/test/cases/relation/where_chain_test.rb +105 -181
  218. data/test/cases/relation/where_clause_test.rb +182 -0
  219. data/test/cases/relation/where_test.rb +322 -300
  220. data/test/cases/relation_test.rb +328 -319
  221. data/test/cases/relations_test.rb +2026 -1815
  222. data/test/cases/reload_models_test.rb +22 -22
  223. data/test/cases/result_test.rb +90 -80
  224. data/test/cases/sanitize_test.rb +176 -83
  225. data/test/cases/schema_dumper_test.rb +457 -463
  226. data/test/cases/schema_loading_test.rb +52 -0
  227. data/test/cases/scoping/default_scoping_test.rb +528 -454
  228. data/test/cases/scoping/named_scoping_test.rb +561 -524
  229. data/test/cases/scoping/relation_scoping_test.rb +400 -357
  230. data/test/cases/secure_token_test.rb +32 -0
  231. data/test/cases/serialization_test.rb +104 -104
  232. data/test/cases/serialized_attribute_test.rb +364 -277
  233. data/test/cases/statement_cache_test.rb +136 -98
  234. data/test/cases/store_test.rb +195 -194
  235. data/test/cases/suppressor_test.rb +63 -0
  236. data/test/cases/tasks/database_tasks_test.rb +462 -398
  237. data/test/cases/tasks/mysql_rake_test.rb +345 -324
  238. data/test/cases/tasks/postgresql_rake_test.rb +304 -250
  239. data/test/cases/tasks/sqlite_rake_test.rb +220 -193
  240. data/test/cases/test_case.rb +131 -123
  241. data/test/cases/test_fixtures_test.rb +36 -0
  242. data/test/cases/time_precision_test.rb +102 -0
  243. data/test/cases/timestamp_test.rb +501 -467
  244. data/test/cases/touch_later_test.rb +121 -0
  245. data/test/cases/transaction_callbacks_test.rb +518 -452
  246. data/test/cases/transaction_isolation_test.rb +106 -106
  247. data/test/cases/transactions_test.rb +834 -817
  248. data/test/cases/type/adapter_specific_registry_test.rb +133 -0
  249. data/test/cases/type/date_time_test.rb +14 -0
  250. data/test/cases/type/integer_test.rb +27 -121
  251. data/test/cases/type/string_test.rb +22 -36
  252. data/test/cases/type/type_map_test.rb +177 -177
  253. data/test/cases/type_test.rb +39 -0
  254. data/test/cases/types_test.rb +24 -141
  255. data/test/cases/unconnected_test.rb +33 -33
  256. data/test/cases/validations/absence_validation_test.rb +73 -0
  257. data/test/cases/validations/association_validation_test.rb +97 -86
  258. data/test/cases/validations/i18n_generate_message_validation_test.rb +84 -84
  259. data/test/cases/validations/i18n_validation_test.rb +86 -90
  260. data/test/cases/validations/length_validation_test.rb +79 -47
  261. data/test/cases/validations/presence_validation_test.rb +103 -68
  262. data/test/cases/validations/uniqueness_validation_test.rb +548 -457
  263. data/test/cases/validations_repair_helper.rb +19 -23
  264. data/test/cases/validations_test.rb +194 -165
  265. data/test/cases/view_test.rb +216 -119
  266. data/test/cases/yaml_serialization_test.rb +121 -126
  267. data/test/config.example.yml +97 -0
  268. data/test/config.rb +5 -5
  269. data/test/fixtures/accounts.yml +29 -29
  270. data/test/fixtures/admin/accounts.yml +2 -2
  271. data/test/fixtures/admin/users.yml +10 -10
  272. data/test/fixtures/author_addresses.original +11 -0
  273. data/test/fixtures/author_addresses.yml +17 -17
  274. data/test/fixtures/author_favorites.yml +3 -3
  275. data/test/fixtures/authors.original +17 -0
  276. data/test/fixtures/authors.yml +23 -23
  277. data/test/fixtures/bad_posts.yml +9 -0
  278. data/test/fixtures/binaries.yml +133 -133
  279. data/test/fixtures/books.yml +31 -11
  280. data/test/fixtures/bulbs.yml +5 -5
  281. data/test/fixtures/cars.yml +9 -9
  282. data/test/fixtures/categories.yml +19 -19
  283. data/test/fixtures/categories/special_categories.yml +9 -9
  284. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -4
  285. data/test/fixtures/categories_ordered.yml +7 -7
  286. data/test/fixtures/categories_posts.yml +31 -31
  287. data/test/fixtures/categorizations.yml +23 -23
  288. data/test/fixtures/clubs.yml +8 -8
  289. data/test/fixtures/collections.yml +3 -3
  290. data/test/fixtures/colleges.yml +3 -3
  291. data/test/fixtures/comments.yml +65 -65
  292. data/test/fixtures/companies.yml +67 -67
  293. data/test/fixtures/computers.yml +10 -10
  294. data/test/fixtures/content.yml +3 -0
  295. data/test/fixtures/content_positions.yml +3 -0
  296. data/test/fixtures/courses.yml +8 -8
  297. data/test/fixtures/customers.yml +25 -25
  298. data/test/fixtures/dashboards.yml +6 -6
  299. data/test/fixtures/dead_parrots.yml +5 -0
  300. data/test/fixtures/developers.yml +22 -22
  301. data/test/fixtures/developers_projects.yml +16 -16
  302. data/test/fixtures/dog_lovers.yml +7 -7
  303. data/test/fixtures/dogs.yml +4 -4
  304. data/test/fixtures/doubloons.yml +3 -3
  305. data/test/fixtures/edges.yml +5 -5
  306. data/test/fixtures/entrants.yml +14 -14
  307. data/test/fixtures/essays.yml +6 -6
  308. data/test/fixtures/faces.yml +11 -11
  309. data/test/fixtures/fk_test_has_fk.yml +3 -3
  310. data/test/fixtures/fk_test_has_pk.yml +1 -1
  311. data/test/fixtures/friendships.yml +4 -4
  312. data/test/fixtures/funny_jokes.yml +10 -10
  313. data/test/fixtures/interests.yml +33 -33
  314. data/test/fixtures/items.yml +3 -3
  315. data/test/fixtures/jobs.yml +7 -7
  316. data/test/fixtures/legacy_things.yml +3 -3
  317. data/test/fixtures/live_parrots.yml +4 -0
  318. data/test/fixtures/mateys.yml +4 -4
  319. data/test/fixtures/member_details.yml +8 -8
  320. data/test/fixtures/member_types.yml +6 -6
  321. data/test/fixtures/members.yml +11 -11
  322. data/test/fixtures/memberships.yml +34 -34
  323. data/test/fixtures/men.yml +5 -5
  324. data/test/fixtures/minimalistics.yml +2 -2
  325. data/test/fixtures/minivans.yml +5 -5
  326. data/test/fixtures/mixed_case_monkeys.yml +6 -6
  327. data/test/fixtures/mixins.yml +29 -29
  328. data/test/fixtures/movies.yml +7 -7
  329. data/test/fixtures/naked/yml/accounts.yml +1 -1
  330. data/test/fixtures/naked/yml/companies.yml +1 -1
  331. data/test/fixtures/naked/yml/courses.yml +1 -1
  332. data/test/fixtures/naked/yml/parrots.yml +2 -0
  333. data/test/fixtures/naked/yml/trees.yml +3 -0
  334. data/test/fixtures/nodes.yml +29 -0
  335. data/test/fixtures/organizations.yml +5 -5
  336. data/test/fixtures/other_comments.yml +6 -0
  337. data/test/fixtures/other_dogs.yml +2 -0
  338. data/test/fixtures/other_posts.yml +7 -0
  339. data/test/fixtures/other_topics.yml +42 -42
  340. data/test/fixtures/owners.yml +9 -9
  341. data/test/fixtures/parrots.yml +27 -27
  342. data/test/fixtures/parrots_pirates.yml +7 -7
  343. data/test/fixtures/people.yml +24 -24
  344. data/test/fixtures/peoples_treasures.yml +3 -3
  345. data/test/fixtures/pets.yml +19 -19
  346. data/test/fixtures/pirates.yml +15 -12
  347. data/test/fixtures/posts.yml +80 -80
  348. data/test/fixtures/price_estimates.yml +16 -7
  349. data/test/fixtures/products.yml +4 -4
  350. data/test/fixtures/projects.yml +7 -7
  351. data/test/fixtures/ratings.yml +14 -14
  352. data/test/fixtures/readers.yml +11 -11
  353. data/test/fixtures/references.yml +17 -17
  354. data/test/fixtures/reserved_words/distinct.yml +5 -5
  355. data/test/fixtures/reserved_words/distinct_select.yml +11 -11
  356. data/test/fixtures/reserved_words/group.yml +14 -14
  357. data/test/fixtures/reserved_words/select.yml +8 -8
  358. data/test/fixtures/reserved_words/values.yml +7 -7
  359. data/test/fixtures/ships.yml +6 -6
  360. data/test/fixtures/speedometers.yml +8 -8
  361. data/test/fixtures/sponsors.yml +12 -12
  362. data/test/fixtures/string_key_objects.yml +7 -7
  363. data/test/fixtures/subscribers.yml +10 -10
  364. data/test/fixtures/subscriptions.yml +12 -12
  365. data/test/fixtures/taggings.yml +78 -78
  366. data/test/fixtures/tags.yml +11 -11
  367. data/test/fixtures/tasks.yml +7 -7
  368. data/test/fixtures/teapots.yml +3 -3
  369. data/test/fixtures/to_be_linked/accounts.yml +2 -2
  370. data/test/fixtures/to_be_linked/users.yml +10 -10
  371. data/test/fixtures/topics.yml +49 -49
  372. data/test/fixtures/toys.yml +14 -14
  373. data/test/fixtures/traffic_lights.yml +9 -9
  374. data/test/fixtures/treasures.yml +10 -10
  375. data/test/fixtures/trees.yml +3 -0
  376. data/test/fixtures/uuid_children.yml +3 -3
  377. data/test/fixtures/uuid_parents.yml +2 -2
  378. data/test/fixtures/variants.yml +4 -4
  379. data/test/fixtures/vegetables.yml +19 -19
  380. data/test/fixtures/vertices.yml +3 -3
  381. data/test/fixtures/warehouse_things.yml +2 -2
  382. data/test/fixtures/zines.yml +5 -5
  383. data/test/migrations/10_urban/9_add_expressions.rb +11 -11
  384. data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -15
  385. data/test/migrations/magic/1_currencies_have_symbols.rb +12 -12
  386. data/test/migrations/missing/1000_people_have_middle_names.rb +9 -9
  387. data/test/migrations/missing/1_people_have_last_names.rb +9 -9
  388. data/test/migrations/missing/3_we_need_reminders.rb +12 -12
  389. data/test/migrations/missing/4_innocent_jointable.rb +12 -12
  390. data/test/migrations/rename/1_we_need_things.rb +11 -11
  391. data/test/migrations/rename/2_rename_things.rb +9 -9
  392. data/test/migrations/to_copy/1_people_have_hobbies.rb +9 -9
  393. data/test/migrations/to_copy/2_people_have_descriptions.rb +9 -9
  394. data/test/migrations/to_copy2/1_create_articles.rb +7 -7
  395. data/test/migrations/to_copy2/2_create_comments.rb +7 -7
  396. data/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb +9 -9
  397. data/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb +9 -9
  398. data/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb +9 -9
  399. data/test/migrations/to_copy_with_timestamps2/20090101010101_create_articles.rb +7 -7
  400. data/test/migrations/to_copy_with_timestamps2/20090101010202_create_comments.rb +7 -7
  401. data/test/migrations/valid/1_valid_people_have_last_names.rb +9 -9
  402. data/test/migrations/valid/2_we_need_reminders.rb +12 -12
  403. data/test/migrations/valid/3_innocent_jointable.rb +12 -12
  404. data/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb +9 -9
  405. data/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb +12 -12
  406. data/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb +12 -12
  407. data/test/migrations/valid_with_timestamps/20100101010101_valid_with_timestamps_people_have_last_names.rb +9 -9
  408. data/test/migrations/valid_with_timestamps/20100201010101_valid_with_timestamps_we_need_reminders.rb +12 -12
  409. data/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb +12 -12
  410. data/test/migrations/version_check/20131219224947_migration_version_check.rb +8 -8
  411. data/test/models/admin.rb +5 -5
  412. data/test/models/admin/account.rb +3 -3
  413. data/test/models/admin/randomly_named_c1.rb +6 -2
  414. data/test/models/admin/user.rb +40 -40
  415. data/test/models/aircraft.rb +5 -4
  416. data/test/models/arunit2_model.rb +3 -3
  417. data/test/models/author.rb +209 -212
  418. data/test/models/auto_id.rb +4 -4
  419. data/test/models/autoloadable/extra_firm.rb +2 -2
  420. data/test/models/binary.rb +2 -2
  421. data/test/models/bird.rb +12 -12
  422. data/test/models/book.rb +23 -18
  423. data/test/models/boolean.rb +2 -2
  424. data/test/models/bulb.rb +52 -51
  425. data/test/models/cake_designer.rb +3 -3
  426. data/test/models/car.rb +29 -26
  427. data/test/models/carrier.rb +2 -2
  428. data/test/models/cat.rb +10 -0
  429. data/test/models/categorization.rb +19 -19
  430. data/test/models/category.rb +35 -35
  431. data/test/models/chef.rb +8 -7
  432. data/test/models/citation.rb +3 -3
  433. data/test/models/club.rb +25 -23
  434. data/test/models/college.rb +10 -10
  435. data/test/models/column.rb +3 -3
  436. data/test/models/column_name.rb +3 -3
  437. data/test/models/comment.rb +76 -64
  438. data/test/models/company.rb +230 -228
  439. data/test/models/company_in_module.rb +98 -98
  440. data/test/models/computer.rb +3 -3
  441. data/test/models/contact.rb +41 -41
  442. data/test/models/content.rb +40 -0
  443. data/test/models/contract.rb +20 -20
  444. data/test/models/country.rb +7 -7
  445. data/test/models/course.rb +6 -6
  446. data/test/models/customer.rb +83 -77
  447. data/test/models/customer_carrier.rb +14 -14
  448. data/test/models/dashboard.rb +3 -3
  449. data/test/models/default.rb +2 -2
  450. data/test/models/department.rb +4 -4
  451. data/test/models/developer.rb +274 -255
  452. data/test/models/dog.rb +5 -5
  453. data/test/models/dog_lover.rb +5 -5
  454. data/test/models/doubloon.rb +12 -12
  455. data/test/models/drink_designer.rb +3 -3
  456. data/test/models/edge.rb +5 -5
  457. data/test/models/electron.rb +5 -5
  458. data/test/models/engine.rb +4 -4
  459. data/test/models/entrant.rb +3 -3
  460. data/test/models/essay.rb +5 -5
  461. data/test/models/event.rb +3 -3
  462. data/test/models/eye.rb +37 -37
  463. data/test/models/face.rb +9 -9
  464. data/test/models/friendship.rb +6 -6
  465. data/test/models/guid.rb +2 -2
  466. data/test/models/guitar.rb +4 -0
  467. data/test/models/hotel.rb +11 -9
  468. data/test/models/image.rb +3 -3
  469. data/test/models/interest.rb +5 -5
  470. data/test/models/invoice.rb +4 -4
  471. data/test/models/item.rb +7 -7
  472. data/test/models/job.rb +7 -7
  473. data/test/models/joke.rb +7 -7
  474. data/test/models/keyboard.rb +3 -3
  475. data/test/models/legacy_thing.rb +3 -3
  476. data/test/models/lesson.rb +11 -11
  477. data/test/models/line_item.rb +3 -3
  478. data/test/models/liquid.rb +4 -4
  479. data/test/models/man.rb +11 -11
  480. data/test/models/matey.rb +4 -4
  481. data/test/models/member.rb +42 -41
  482. data/test/models/member_detail.rb +8 -7
  483. data/test/models/member_type.rb +3 -3
  484. data/test/models/membership.rb +35 -35
  485. data/test/models/mentor.rb +3 -0
  486. data/test/models/minimalistic.rb +2 -2
  487. data/test/models/minivan.rb +9 -9
  488. data/test/models/mixed_case_monkey.rb +3 -3
  489. data/test/models/mocktail_designer.rb +2 -0
  490. data/test/models/molecule.rb +6 -6
  491. data/test/models/movie.rb +5 -5
  492. data/test/models/node.rb +5 -0
  493. data/test/models/non_primary_key.rb +2 -0
  494. data/test/models/notification.rb +3 -0
  495. data/test/models/order.rb +4 -4
  496. data/test/models/organization.rb +14 -14
  497. data/test/models/other_dog.rb +5 -0
  498. data/test/models/owner.rb +37 -34
  499. data/test/models/parrot.rb +28 -29
  500. data/test/models/person.rb +142 -143
  501. data/test/models/personal_legacy_thing.rb +4 -4
  502. data/test/models/pet.rb +18 -15
  503. data/test/models/pet_treasure.rb +6 -0
  504. data/test/models/pirate.rb +92 -92
  505. data/test/models/possession.rb +3 -3
  506. data/test/models/post.rb +273 -264
  507. data/test/models/price_estimate.rb +4 -4
  508. data/test/models/professor.rb +5 -5
  509. data/test/models/project.rb +40 -31
  510. data/test/models/publisher.rb +2 -2
  511. data/test/models/publisher/article.rb +4 -4
  512. data/test/models/publisher/magazine.rb +3 -3
  513. data/test/models/randomly_named_c1.rb +1 -1
  514. data/test/models/rating.rb +4 -4
  515. data/test/models/reader.rb +23 -23
  516. data/test/models/recipe.rb +3 -0
  517. data/test/models/record.rb +2 -2
  518. data/test/models/reference.rb +22 -22
  519. data/test/models/reply.rb +61 -61
  520. data/test/models/ship.rb +39 -33
  521. data/test/models/ship_part.rb +8 -8
  522. data/test/models/shop.rb +17 -17
  523. data/test/models/shop_account.rb +6 -6
  524. data/test/models/speedometer.rb +6 -6
  525. data/test/models/sponsor.rb +7 -7
  526. data/test/models/string_key_object.rb +3 -3
  527. data/test/models/student.rb +4 -4
  528. data/test/models/subject.rb +16 -16
  529. data/test/models/subscriber.rb +8 -8
  530. data/test/models/subscription.rb +4 -4
  531. data/test/models/tag.rb +13 -7
  532. data/test/models/tagging.rb +13 -13
  533. data/test/models/task.rb +5 -5
  534. data/test/models/topic.rb +118 -124
  535. data/test/models/toy.rb +6 -6
  536. data/test/models/traffic_light.rb +4 -4
  537. data/test/models/treasure.rb +14 -14
  538. data/test/models/treaty.rb +7 -7
  539. data/test/models/tree.rb +3 -0
  540. data/test/models/tuning_peg.rb +4 -0
  541. data/test/models/tyre.rb +11 -11
  542. data/test/models/user.rb +14 -0
  543. data/test/models/uuid_child.rb +3 -3
  544. data/test/models/uuid_item.rb +6 -0
  545. data/test/models/uuid_parent.rb +3 -3
  546. data/test/models/vegetables.rb +24 -24
  547. data/test/models/vehicle.rb +6 -6
  548. data/test/models/vertex.rb +9 -9
  549. data/test/models/warehouse_thing.rb +5 -5
  550. data/test/models/wheel.rb +3 -3
  551. data/test/models/without_table.rb +3 -3
  552. data/test/models/zine.rb +3 -3
  553. data/test/schema/mysql2_specific_schema.rb +68 -58
  554. data/test/schema/oracle_specific_schema.rb +40 -43
  555. data/test/schema/postgresql_specific_schema.rb +114 -202
  556. data/test/schema/schema.rb +1057 -952
  557. data/test/schema/schema.rb.original +1057 -0
  558. data/test/schema/sqlite_specific_schema.rb +18 -22
  559. data/test/support/config.rb +43 -43
  560. data/test/support/connection.rb +23 -22
  561. data/test/support/connection_helper.rb +14 -14
  562. data/test/support/ddl_helper.rb +8 -8
  563. data/test/support/schema_dumping_helper.rb +20 -20
  564. data/test/support/yaml_compatibility_fixtures/rails_4_1.yml +22 -0
  565. data/test/support/yaml_compatibility_fixtures/rails_4_2_0.yml +182 -0
  566. metadata +129 -28
  567. data/lib/mswin32/rb19x/ibm_db.so +0 -0
  568. data/lib/mswin32/rb21x/i386/ibm_db.so +0 -0
  569. data/lib/mswin32/rb22x/i386/ibm_db.so +0 -0
  570. data/lib/mswin32/rb23x/i386/ibm_db.so +0 -0
  571. data/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb +0 -26
  572. data/test/cases/attribute_methods/serialization_test.rb +0 -29
  573. data/test/cases/migration/change_schema_test - Copy.rb +0 -448
  574. data/test/cases/migration/foreign_key_test - Changed.rb +0 -325
  575. data/test/cases/migration/table_and_index_test.rb +0 -24
  576. data/test/cases/relation/where_test2.rb +0 -36
  577. data/test/cases/type/decimal_test.rb +0 -56
  578. data/test/cases/type/unsigned_integer_test.rb +0 -18
  579. data/test/cases/xml_serialization_test.rb +0 -457
  580. data/test/connections/native_ibm_db/connection.rb +0 -44
  581. data/test/fixtures/naked/csv/accounts.csv +0 -1
  582. data/test/schema/i5/ibm_db_specific_schema.rb +0 -137
  583. data/test/schema/ids/ibm_db_specific_schema.rb +0 -140
  584. data/test/schema/luw/ibm_db_specific_schema.rb +0 -137
  585. data/test/schema/mysql_specific_schema.rb +0 -70
  586. data/test/schema/zOS/ibm_db_specific_schema.rb +0 -208
@@ -1,1595 +1,1708 @@
1
- require 'cases/helper'
2
- require 'models/bird'
3
- require 'models/comment'
4
- require 'models/company'
5
- require 'models/customer'
6
- require 'models/developer'
7
- require 'models/computer'
8
- require 'models/invoice'
9
- require 'models/line_item'
10
- require 'models/order'
11
- require 'models/parrot'
12
- require 'models/person'
13
- require 'models/pirate'
14
- require 'models/post'
15
- require 'models/reader'
16
- require 'models/ship'
17
- require 'models/ship_part'
18
- require 'models/tag'
19
- require 'models/tagging'
20
- require 'models/treasure'
21
- require 'models/eye'
22
- require 'models/electron'
23
- require 'models/molecule'
24
- require 'models/member'
25
- require 'models/member_detail'
26
- require 'models/organization'
27
-
28
- class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
29
- def test_autosave_validation
30
- person = Class.new(ActiveRecord::Base) {
31
- self.table_name = 'people'
32
- validate :should_be_cool, :on => :create
33
- def self.name; 'Person'; end
34
-
35
- private
36
-
37
- def should_be_cool
38
- unless self.first_name == 'cool'
39
- errors.add :first_name, "not cool"
40
- end
41
- end
42
- }
43
- reference = Class.new(ActiveRecord::Base) {
44
- self.table_name = "references"
45
- def self.name; 'Reference'; end
46
- belongs_to :person, autosave: true, anonymous_class: person
47
- }
48
-
49
- u = person.create!(first_name: 'cool')
50
- u.update_attributes!(first_name: 'nah') # still valid because validation only applies on 'create'
51
- assert reference.create!(person: u).persisted?
52
- end
53
-
54
- def test_should_not_add_the_same_callbacks_multiple_times_for_has_one
55
- assert_no_difference_when_adding_callbacks_twice_for Pirate, :ship
56
- end
57
-
58
- def test_should_not_add_the_same_callbacks_multiple_times_for_belongs_to
59
- assert_no_difference_when_adding_callbacks_twice_for Ship, :pirate
60
- end
61
-
62
- def test_should_not_add_the_same_callbacks_multiple_times_for_has_many
63
- assert_no_difference_when_adding_callbacks_twice_for Pirate, :birds
64
- end
65
-
66
- def test_should_not_add_the_same_callbacks_multiple_times_for_has_and_belongs_to_many
67
- assert_no_difference_when_adding_callbacks_twice_for Pirate, :parrots
68
- end
69
-
70
- private
71
-
72
- def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
73
- reflection = model.reflect_on_association(association_name)
74
- assert_no_difference "callbacks_for_model(#{model.name}).length" do
75
- model.send(:add_autosave_association_callbacks, reflection)
76
- end
77
- end
78
-
79
- def callbacks_for_model(model)
80
- model.instance_variables.grep(/_callbacks$/).flat_map do |ivar|
81
- model.instance_variable_get(ivar)
82
- end
83
- end
84
- end
85
-
86
- class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
87
- fixtures :companies, :accounts
88
-
89
- def test_should_save_parent_but_not_invalid_child
90
- firm = Firm.new(:name => 'GlobalMegaCorp')
91
- assert firm.valid?
92
-
93
- firm.build_account_using_primary_key
94
- assert !firm.build_account_using_primary_key.valid?
95
-
96
- assert firm.save
97
- assert !firm.account_using_primary_key.persisted?
98
- end
99
-
100
- def test_save_fails_for_invalid_has_one
101
- firm = Firm.first
102
- assert firm.valid?
103
-
104
- firm.build_account
105
-
106
- assert !firm.account.valid?
107
- assert !firm.valid?
108
- assert !firm.save
109
- assert_equal ["is invalid"], firm.errors["account"]
110
- end
111
-
112
- def test_save_succeeds_for_invalid_has_one_with_validate_false
113
- firm = Firm.first
114
- assert firm.valid?
115
-
116
- firm.build_unvalidated_account
117
-
118
- assert !firm.unvalidated_account.valid?
119
- assert firm.valid?
120
- assert firm.save
121
- end
122
-
123
- def test_build_before_child_saved
124
- firm = Firm.find(1)
125
-
126
- account = firm.build_account("credit_limit" => 1000)
127
- assert_equal account, firm.account
128
- assert !account.persisted?
129
- assert firm.save
130
- assert_equal account, firm.account
131
- assert account.persisted?
132
- end
133
-
134
- def test_build_before_either_saved
135
- firm = Firm.new("name" => "GlobalMegaCorp")
136
-
137
- firm.account = account = Account.new("credit_limit" => 1000)
138
- assert_equal account, firm.account
139
- assert !account.persisted?
140
- assert firm.save
141
- assert_equal account, firm.account
142
- assert account.persisted?
143
- end
144
-
145
- def test_assignment_before_parent_saved
146
- firm = Firm.new("name" => "GlobalMegaCorp")
147
- firm.account = a = Account.find(1)
148
- assert !firm.persisted?
149
- assert_equal a, firm.account
150
- assert firm.save
151
- assert_equal a, firm.account
152
- assert_equal a, firm.account(true)
153
- end
154
-
155
- def test_assignment_before_either_saved
156
- firm = Firm.new("name" => "GlobalMegaCorp")
157
- firm.account = a = Account.new("credit_limit" => 1000)
158
- assert !firm.persisted?
159
- assert !a.persisted?
160
- assert_equal a, firm.account
161
- assert firm.save
162
- assert firm.persisted?
163
- assert a.persisted?
164
- assert_equal a, firm.account
165
- assert_equal a, firm.account(true)
166
- end
167
-
168
- def test_not_resaved_when_unchanged
169
- firm = Firm.all.merge!(:includes => :account).first
170
- firm.name += '-changed'
171
- assert_queries(1) { firm.save! }
172
-
173
- firm = Firm.first
174
- firm.account = Account.first
175
- assert_queries(Firm.partial_writes? ? 0 : 1) { firm.save! }
176
-
177
- firm = Firm.first.dup
178
- firm.account = Account.first
179
- assert_queries(2) { firm.save! }
180
-
181
- firm = Firm.first.dup
182
- firm.account = Account.first.dup
183
- assert_queries(2) { firm.save! }
184
- end
185
-
186
- def test_callbacks_firing_order_on_create
187
- eye = Eye.create(:iris_attributes => {:color => 'honey'})
188
- assert_equal [true, false], eye.after_create_callbacks_stack
189
- end
190
-
191
- def test_callbacks_firing_order_on_update
192
- eye = Eye.create(iris_attributes: {color: 'honey'})
193
- eye.update(iris_attributes: {color: 'green'})
194
- assert_equal [true, false], eye.after_update_callbacks_stack
195
- end
196
-
197
- def test_callbacks_firing_order_on_save
198
- eye = Eye.create(iris_attributes: {color: 'honey'})
199
- assert_equal [false, false], eye.after_save_callbacks_stack
200
-
201
- eye.update(iris_attributes: {color: 'blue'})
202
- assert_equal [false, false, false, false], eye.after_save_callbacks_stack
203
- end
204
- end
205
-
206
- class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
207
- fixtures :companies, :posts, :tags, :taggings
208
-
209
- def test_should_save_parent_but_not_invalid_child
210
- client = Client.new(:name => 'Joe (the Plumber)')
211
- assert client.valid?
212
-
213
- client.build_firm
214
- assert !client.firm.valid?
215
-
216
- assert client.save
217
- assert !client.firm.persisted?
218
- end
219
-
220
- def test_save_fails_for_invalid_belongs_to
221
- # Oracle saves empty string as NULL therefore :message changed to one space
222
- assert log = AuditLog.create(:developer_id => 0, :message => " ")
223
-
224
- log.developer = Developer.new
225
- assert !log.developer.valid?
226
- assert !log.valid?
227
- assert !log.save
228
- assert_equal ["is invalid"], log.errors["developer"]
229
- end
230
-
231
- def test_save_succeeds_for_invalid_belongs_to_with_validate_false
232
- # Oracle saves empty string as NULL therefore :message changed to one space
233
- assert log = AuditLog.create(:developer_id => 0, :message=> " ")
234
-
235
- log.unvalidated_developer = Developer.new
236
- assert !log.unvalidated_developer.valid?
237
- assert log.valid?
238
- assert log.save
239
- end
240
-
241
- def test_assignment_before_parent_saved
242
- client = Client.first
243
- apple = Firm.new("name" => "Apple")
244
- client.firm = apple
245
- assert_equal apple, client.firm
246
- assert !apple.persisted?
247
- assert client.save
248
- assert apple.save
249
- assert apple.persisted?
250
- assert_equal apple, client.firm
251
- assert_equal apple, client.firm(true)
252
- end
253
-
254
- def test_assignment_before_either_saved
255
- final_cut = Client.new("name" => "Final Cut")
256
- apple = Firm.new("name" => "Apple")
257
- final_cut.firm = apple
258
- assert !final_cut.persisted?
259
- assert !apple.persisted?
260
- assert final_cut.save
261
- assert final_cut.persisted?
262
- assert apple.persisted?
263
- assert_equal apple, final_cut.firm
264
- assert_equal apple, final_cut.firm(true)
265
- end
266
-
267
- def test_store_two_association_with_one_save
268
- num_orders = Order.count
269
- num_customers = Customer.count
270
- order = Order.new
271
-
272
- customer1 = order.billing = Customer.new
273
- customer2 = order.shipping = Customer.new
274
- assert order.save
275
- assert_equal customer1, order.billing
276
- assert_equal customer2, order.shipping
277
-
278
- order.reload
279
-
280
- assert_equal customer1, order.billing
281
- assert_equal customer2, order.shipping
282
-
283
- assert_equal num_orders + 1, Order.count
284
- assert_equal num_customers + 2, Customer.count
285
- end
286
-
287
- def test_store_association_in_two_relations_with_one_save
288
- num_orders = Order.count
289
- num_customers = Customer.count
290
- order = Order.new
291
-
292
- customer = order.billing = order.shipping = Customer.new
293
- assert order.save
294
- assert_equal customer, order.billing
295
- assert_equal customer, order.shipping
296
-
297
- order.reload
298
-
299
- assert_equal customer, order.billing
300
- assert_equal customer, order.shipping
301
-
302
- assert_equal num_orders + 1, Order.count
303
- assert_equal num_customers + 1, Customer.count
304
- end
305
-
306
- def test_store_association_in_two_relations_with_one_save_in_existing_object
307
- num_orders = Order.count
308
- num_customers = Customer.count
309
- order = Order.create
310
-
311
- customer = order.billing = order.shipping = Customer.new
312
- assert order.save
313
- assert_equal customer, order.billing
314
- assert_equal customer, order.shipping
315
-
316
- order.reload
317
-
318
- assert_equal customer, order.billing
319
- assert_equal customer, order.shipping
320
-
321
- assert_equal num_orders + 1, Order.count
322
- assert_equal num_customers + 1, Customer.count
323
- end
324
-
325
- def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
326
- num_orders = Order.count
327
- num_customers = Customer.count
328
- order = Order.create
329
-
330
- customer = order.billing = order.shipping = Customer.new
331
- assert order.save
332
- assert_equal customer, order.billing
333
- assert_equal customer, order.shipping
334
-
335
- order.reload
336
-
337
- customer = order.billing = order.shipping = Customer.new
338
-
339
- assert order.save
340
- order.reload
341
-
342
- assert_equal customer, order.billing
343
- assert_equal customer, order.shipping
344
-
345
- assert_equal num_orders + 1, Order.count
346
- assert_equal num_customers + 2, Customer.count
347
- end
348
-
349
- def test_store_association_with_a_polymorphic_relationship
350
- num_tagging = Tagging.count
351
- tags(:misc).create_tagging(:taggable => posts(:thinking))
352
- assert_equal num_tagging + 1, Tagging.count
353
- end
354
-
355
- def test_build_and_then_save_parent_should_not_reload_target
356
- client = Client.first
357
- apple = client.build_firm(:name => "Apple")
358
- client.save!
359
- assert_no_queries { assert_equal apple, client.firm }
360
- end
361
-
362
- def test_validation_does_not_validate_stale_association_target
363
- valid_developer = Developer.create!(:name => "Dude", :salary => 50_000)
364
- invalid_developer = Developer.new()
365
-
366
- auditlog = AuditLog.new(:message => "foo")
367
- auditlog.developer = invalid_developer
368
- auditlog.developer_id = valid_developer.id
369
-
370
- assert auditlog.valid?
371
- end
372
- end
373
-
374
- class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
375
- def test_invalid_adding_with_nested_attributes
376
- molecule = Molecule.new
377
- valid_electron = Electron.new(name: 'electron')
378
- invalid_electron = Electron.new
379
-
380
- molecule.electrons = [valid_electron, invalid_electron]
381
- molecule.save
382
-
383
- assert_not invalid_electron.valid?
384
- assert valid_electron.valid?
385
- assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid'
386
- end
387
-
388
- def test_valid_adding_with_nested_attributes
389
- molecule = Molecule.new
390
- valid_electron = Electron.new(name: 'electron')
391
-
392
- molecule.electrons = [valid_electron]
393
- molecule.save
394
-
395
- assert valid_electron.valid?
396
- assert molecule.persisted?
397
- assert_equal 1, molecule.electrons.count
398
- end
399
- end
400
-
401
- class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
402
- fixtures :companies, :people
403
-
404
- def test_invalid_adding
405
- firm = Firm.find(1)
406
- assert !(firm.clients_of_firm << c = Client.new)
407
- assert !c.persisted?
408
- assert !firm.valid?
409
- assert !firm.save
410
- assert !c.persisted?
411
- end
412
-
413
- def test_invalid_adding_before_save
414
- new_firm = Firm.new("name" => "A New Firm, Inc")
415
- new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
416
- assert !c.persisted?
417
- assert !c.valid?
418
- assert !new_firm.valid?
419
- assert !new_firm.save
420
- assert !c.persisted?
421
- assert !new_firm.persisted?
422
- end
423
-
424
- def test_invalid_adding_with_validate_false
425
- firm = Firm.first
426
- client = Client.new
427
- firm.unvalidated_clients_of_firm << client
428
-
429
- assert firm.valid?
430
- assert !client.valid?
431
- assert firm.save
432
- assert !client.persisted?
433
- end
434
-
435
- def test_valid_adding_with_validate_false
436
- no_of_clients = Client.count
437
-
438
- firm = Firm.first
439
- client = Client.new("name" => "Apple")
440
-
441
- assert firm.valid?
442
- assert client.valid?
443
- assert !client.persisted?
444
-
445
- firm.unvalidated_clients_of_firm << client
446
-
447
- assert firm.save
448
- assert client.persisted?
449
- assert_equal no_of_clients + 1, Client.count
450
- end
451
-
452
- def test_invalid_build
453
- new_client = companies(:first_firm).clients_of_firm.build
454
- assert !new_client.persisted?
455
- assert !new_client.valid?
456
- assert_equal new_client, companies(:first_firm).clients_of_firm.last
457
- assert !companies(:first_firm).save
458
- assert !new_client.persisted?
459
- assert_equal 2, companies(:first_firm).clients_of_firm(true).size
460
- end
461
-
462
- def test_adding_before_save
463
- no_of_firms = Firm.count
464
- no_of_clients = Client.count
465
-
466
- new_firm = Firm.new("name" => "A New Firm, Inc")
467
- c = Client.new("name" => "Apple")
468
-
469
- new_firm.clients_of_firm.push Client.new("name" => "Natural Company")
470
- assert_equal 1, new_firm.clients_of_firm.size
471
- new_firm.clients_of_firm << c
472
- assert_equal 2, new_firm.clients_of_firm.size
473
-
474
- assert_equal no_of_firms, Firm.count # Firm was not saved to database.
475
- assert_equal no_of_clients, Client.count # Clients were not saved to database.
476
- assert new_firm.save
477
- assert new_firm.persisted?
478
- assert c.persisted?
479
- assert_equal new_firm, c.firm
480
- assert_equal no_of_firms + 1, Firm.count # Firm was saved to database.
481
- assert_equal no_of_clients + 2, Client.count # Clients were saved to database.
482
-
483
- assert_equal 2, new_firm.clients_of_firm.size
484
- assert_equal 2, new_firm.clients_of_firm(true).size
485
- end
486
-
487
- def test_assign_ids
488
- firm = Firm.new("name" => "Apple")
489
- firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
490
- firm.save
491
- firm.reload
492
- assert_equal 2, firm.clients.length
493
- assert firm.clients.include?(companies(:second_client))
494
- end
495
-
496
- def test_assign_ids_for_through_a_belongs_to
497
- post = Post.new(:title => "Assigning IDs works!", :body => "You heard it here first, folks!")
498
- post.person_ids = [people(:david).id, people(:michael).id]
499
- post.save
500
- post.reload
501
- assert_equal 2, post.people.length
502
- assert post.people.include?(people(:david))
503
- end
504
-
505
- def test_build_before_save
506
- company = companies(:first_firm)
507
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
508
- assert !company.clients_of_firm.loaded?
509
-
510
- company.name += '-changed'
511
- assert_queries(2) { assert company.save }
512
- assert new_client.persisted?
513
- assert_equal 3, company.clients_of_firm(true).size
514
- end
515
-
516
- def test_build_many_before_save
517
- company = companies(:first_firm)
518
- assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
519
-
520
- company.name += '-changed'
521
- assert_queries(3) { assert company.save }
522
- assert_equal 4, company.clients_of_firm(true).size
523
- end
524
-
525
- def test_build_via_block_before_save
526
- company = companies(:first_firm)
527
- new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build {|client| client.name = "Another Client" } }
528
- assert !company.clients_of_firm.loaded?
529
-
530
- company.name += '-changed'
531
- assert_queries(2) { assert company.save }
532
- assert new_client.persisted?
533
- assert_equal 3, company.clients_of_firm(true).size
534
- end
535
-
536
- def test_build_many_via_block_before_save
537
- company = companies(:first_firm)
538
- assert_no_queries(ignore_none: false) do
539
- company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
540
- client.name = "changed"
541
- end
542
- end
543
-
544
- company.name += '-changed'
545
- assert_queries(3) { assert company.save }
546
- assert_equal 4, company.clients_of_firm(true).size
547
- end
548
-
549
- def test_replace_on_new_object
550
- firm = Firm.new("name" => "New Firm")
551
- firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
552
- assert firm.save
553
- firm.reload
554
- assert_equal 2, firm.clients.length
555
- assert firm.clients.include?(Client.find_by_name("New Client"))
556
- end
557
- end
558
-
559
- class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase
560
- def test_autosave_new_record_on_belongs_to_can_be_disabled_per_relationship
561
- new_account = Account.new("credit_limit" => 1000)
562
- new_firm = Firm.new("name" => "some firm")
563
-
564
- assert !new_firm.persisted?
565
- new_account.firm = new_firm
566
- new_account.save!
567
-
568
- assert new_firm.persisted?
569
-
570
- new_account = Account.new("credit_limit" => 1000)
571
- new_autosaved_firm = Firm.new("name" => "some firm")
572
-
573
- assert !new_autosaved_firm.persisted?
574
- new_account.unautosaved_firm = new_autosaved_firm
575
- new_account.save!
576
-
577
- assert !new_autosaved_firm.persisted?
578
- end
579
-
580
- def test_autosave_new_record_on_has_one_can_be_disabled_per_relationship
581
- firm = Firm.new("name" => "some firm")
582
- account = Account.new("credit_limit" => 1000)
583
-
584
- assert !account.persisted?
585
- firm.account = account
586
- firm.save!
587
-
588
- assert account.persisted?
589
-
590
- firm = Firm.new("name" => "some firm")
591
- account = Account.new("credit_limit" => 1000)
592
-
593
- firm.unautosaved_account = account
594
-
595
- assert !account.persisted?
596
- firm.unautosaved_account = account
597
- firm.save!
598
-
599
- assert !account.persisted?
600
- end
601
-
602
- def test_autosave_new_record_on_has_many_can_be_disabled_per_relationship
603
- firm = Firm.new("name" => "some firm")
604
- account = Account.new("credit_limit" => 1000)
605
-
606
- assert !account.persisted?
607
- firm.accounts << account
608
-
609
- firm.save!
610
- assert account.persisted?
611
-
612
- firm = Firm.new("name" => "some firm")
613
- account = Account.new("credit_limit" => 1000)
614
-
615
- assert !account.persisted?
616
- firm.unautosaved_accounts << account
617
-
618
- firm.save!
619
- assert !account.persisted?
620
- end
621
-
622
- def test_autosave_new_record_with_after_create_callback
623
- post = PostWithAfterCreateCallback.new(title: 'Captain Murphy', body: 'is back')
624
- post.comments.build(body: 'foo')
625
- post.save!
626
-
627
- assert_not_nil post.author_id
628
- end
629
- end
630
-
631
- class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
632
- self.use_transactional_fixtures = false
633
-
634
- setup do
635
- @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
636
- @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
637
- end
638
-
639
- teardown do
640
- # We are running without transactional fixtures and need to cleanup.
641
- Bird.delete_all
642
- Parrot.delete_all
643
- @ship.delete
644
- @pirate.delete
645
- end
646
-
647
- # reload
648
- def test_a_marked_for_destruction_record_should_not_be_be_marked_after_reload
649
- @pirate.mark_for_destruction
650
- @pirate.ship.mark_for_destruction
651
-
652
- assert !@pirate.reload.marked_for_destruction?
653
- assert !@pirate.ship.reload.marked_for_destruction?
654
- end
655
-
656
- # has_one
657
- def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
658
- assert !@pirate.ship.marked_for_destruction?
659
-
660
- @pirate.ship.mark_for_destruction
661
- id = @pirate.ship.id
662
-
663
- assert @pirate.ship.marked_for_destruction?
664
- assert Ship.find_by_id(id)
665
-
666
- @pirate.save
667
- assert_nil @pirate.reload.ship
668
- assert_nil Ship.find_by_id(id)
669
- end
670
-
671
- def test_should_skip_validation_on_a_child_association_if_marked_for_destruction
672
- @pirate.ship.name = ''
673
- assert !@pirate.valid?
674
-
675
- @pirate.ship.mark_for_destruction
676
- @pirate.ship.expects(:valid?).never
677
- assert_difference('Ship.count', -1) { @pirate.save! }
678
- end
679
-
680
- def test_a_child_marked_for_destruction_should_not_be_destroyed_twice
681
- @pirate.ship.mark_for_destruction
682
- assert @pirate.save
683
- class << @pirate.ship
684
- def destroy; raise "Should not be called" end
685
- end
686
- assert @pirate.save
687
- end
688
-
689
- def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_child
690
- # Stub the save method of the @pirate.ship instance to destroy and then raise an exception
691
- class << @pirate.ship
692
- def save(*args)
693
- super
694
- destroy
695
- raise 'Oh noes!'
696
- end
697
- end
698
-
699
- @ship.pirate.catchphrase = "Changed Catchphrase"
700
-
701
- assert_raise(RuntimeError) { assert !@pirate.save }
702
- assert_not_nil @pirate.reload.ship
703
- end
704
-
705
- def test_should_save_changed_has_one_changed_object_if_child_is_saved
706
- @pirate.ship.name = "NewName"
707
- assert @pirate.save
708
- assert_equal "NewName", @pirate.ship.reload.name
709
- end
710
-
711
- def test_should_not_save_changed_has_one_unchanged_object_if_child_is_saved
712
- @pirate.ship.expects(:save).never
713
- assert @pirate.save
714
- end
715
-
716
- # belongs_to
717
- def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
718
- assert !@ship.pirate.marked_for_destruction?
719
-
720
- @ship.pirate.mark_for_destruction
721
- id = @ship.pirate.id
722
-
723
- assert @ship.pirate.marked_for_destruction?
724
- assert Pirate.find_by_id(id)
725
-
726
- @ship.save
727
- assert_nil @ship.reload.pirate
728
- assert_nil Pirate.find_by_id(id)
729
- end
730
-
731
- def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction
732
- @ship.pirate.catchphrase = ''
733
- assert !@ship.valid?
734
-
735
- @ship.pirate.mark_for_destruction
736
- @ship.pirate.expects(:valid?).never
737
- assert_difference('Pirate.count', -1) { @ship.save! }
738
- end
739
-
740
- def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice
741
- @ship.pirate.mark_for_destruction
742
- assert @ship.save
743
- class << @ship.pirate
744
- def destroy; raise "Should not be called" end
745
- end
746
- assert @ship.save
747
- end
748
-
749
- def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_parent
750
- # Stub the save method of the @ship.pirate instance to destroy and then raise an exception
751
- class << @ship.pirate
752
- def save(*args)
753
- super
754
- destroy
755
- raise 'Oh noes!'
756
- end
757
- end
758
-
759
- @ship.pirate.catchphrase = "Changed Catchphrase"
760
-
761
- assert_raise(RuntimeError) { assert !@ship.save }
762
- assert_not_nil @ship.reload.pirate
763
- end
764
-
765
- def test_should_save_changed_child_objects_if_parent_is_saved
766
- @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
767
- @parrot = @pirate.parrots.create!(:name => 'Posideons Killer')
768
- @parrot.name = "NewName"
769
- @ship.save
770
-
771
- assert_equal 'NewName', @parrot.reload.name
772
- end
773
-
774
- def test_should_destroy_has_many_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
775
- 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") }
776
-
777
- assert !@pirate.birds.any? { |child| child.marked_for_destruction? }
778
-
779
- @pirate.birds.each { |child| child.mark_for_destruction }
780
- klass = @pirate.birds.first.class
781
- ids = @pirate.birds.map(&:id)
782
-
783
- assert @pirate.birds.all? { |child| child.marked_for_destruction? }
784
- ids.each { |id| assert klass.find_by_id(id) }
785
-
786
- @pirate.save
787
- assert @pirate.reload.birds.empty?
788
- ids.each { |id| assert_nil klass.find_by_id(id) }
789
- end
790
-
791
- def test_should_not_resave_destroyed_association
792
- @pirate.birds.create!(name: :parrot)
793
- @pirate.birds.first.destroy
794
- @pirate.save!
795
- assert @pirate.reload.birds.empty?
796
- end
797
-
798
- def test_should_skip_validation_on_has_many_if_marked_for_destruction
799
- 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") }
800
-
801
- @pirate.birds.each { |bird| bird.name = '' }
802
- assert !@pirate.valid?
803
-
804
- @pirate.birds.each do |bird|
805
- bird.mark_for_destruction
806
- bird.expects(:valid?).never
807
- end
808
- assert_difference("Bird.count", -2) { @pirate.save! }
809
- end
810
-
811
- def test_should_skip_validation_on_has_many_if_destroyed
812
- @pirate.birds.create!(:name => "birds_1")
813
-
814
- @pirate.birds.each { |bird| bird.name = '' }
815
- assert !@pirate.valid?
816
-
817
- @pirate.birds.each { |bird| bird.destroy }
818
- assert @pirate.valid?
819
- end
820
-
821
- def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many
822
- @pirate.birds.create!(:name => "birds_1")
823
-
824
- @pirate.birds.each { |bird| bird.mark_for_destruction }
825
- assert @pirate.save
826
-
827
- @pirate.birds.each { |bird| bird.expects(:destroy).never }
828
- assert @pirate.save
829
- end
830
-
831
- def test_should_rollback_destructions_if_an_exception_occurred_while_saving_has_many
832
- 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") }
833
- before = @pirate.birds.map { |c| c.mark_for_destruction ; c }
834
-
835
- # Stub the destroy method of the second child to raise an exception
836
- class << before.last
837
- def destroy(*args)
838
- super
839
- raise 'Oh noes!'
840
- end
841
- end
842
-
843
- assert_raise(RuntimeError) { assert !@pirate.save }
844
- assert_equal before, @pirate.reload.birds
845
- end
846
-
847
- def test_when_new_record_a_child_marked_for_destruction_should_not_affect_other_records_from_saving
848
- @pirate = @ship.build_pirate(:catchphrase => "Arr' now I shall keep me eye on you matey!") # new record
849
-
850
- 3.times { |i| @pirate.birds.build(:name => "birds_#{i}") }
851
- @pirate.birds[1].mark_for_destruction
852
- @pirate.save!
853
-
854
- assert_equal 2, @pirate.birds.reload.length
855
- end
856
-
857
- def test_should_save_new_record_that_has_same_value_as_existing_record_marked_for_destruction_on_field_that_has_unique_index
858
- Bird.connection.add_index :birds, :name, unique: true
859
-
860
- 3.times { |i| @pirate.birds.create(name: "unique_birds_#{i}") }
861
-
862
- @pirate.birds[0].mark_for_destruction
863
- @pirate.birds.build(name: @pirate.birds[0].name)
864
- @pirate.save!
865
-
866
- assert_equal 3, @pirate.birds.reload.length
867
- ensure
868
- Bird.connection.remove_index :birds, column: :name
869
- end
870
-
871
- # Add and remove callbacks tests for association collections.
872
- %w{ method proc }.each do |callback_type|
873
- define_method("test_should_run_add_callback_#{callback_type}s_for_has_many") do
874
- association_name_with_callbacks = "birds_with_#{callback_type}_callbacks"
875
-
876
- pirate = Pirate.new(:catchphrase => "Arr")
877
- pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed")
878
-
879
- expected = [
880
- "before_adding_#{callback_type}_bird_<new>",
881
- "after_adding_#{callback_type}_bird_<new>"
882
- ]
883
-
884
- assert_equal expected, pirate.ship_log
885
- end
886
-
887
- define_method("test_should_run_remove_callback_#{callback_type}s_for_has_many") do
888
- association_name_with_callbacks = "birds_with_#{callback_type}_callbacks"
889
-
890
- @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
891
- @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction }
892
- child_id = @pirate.send(association_name_with_callbacks).first.id
893
-
894
- @pirate.ship_log.clear
895
- @pirate.save
896
-
897
- expected = [
898
- "before_removing_#{callback_type}_bird_#{child_id}",
899
- "after_removing_#{callback_type}_bird_#{child_id}"
900
- ]
901
-
902
- assert_equal expected, @pirate.ship_log
903
- end
904
- end
905
-
906
- def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
907
- 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
908
-
909
- assert !@pirate.parrots.any? { |parrot| parrot.marked_for_destruction? }
910
- @pirate.parrots.each { |parrot| parrot.mark_for_destruction }
911
-
912
- assert_no_difference "Parrot.count" do
913
- @pirate.save
914
- end
915
-
916
- assert @pirate.reload.parrots.empty?
917
-
918
- join_records = Pirate.connection.select_all("SELECT * FROM parrots_pirates WHERE pirate_id = #{@pirate.id}")
919
- assert join_records.empty?
920
- end
921
-
922
- def test_should_skip_validation_on_habtm_if_marked_for_destruction
923
- 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
924
-
925
- @pirate.parrots.each { |parrot| parrot.name = '' }
926
- assert !@pirate.valid?
927
-
928
- @pirate.parrots.each do |parrot|
929
- parrot.mark_for_destruction
930
- parrot.expects(:valid?).never
931
- end
932
-
933
- @pirate.save!
934
- assert @pirate.reload.parrots.empty?
935
- end
936
-
937
- def test_should_skip_validation_on_habtm_if_destroyed
938
- @pirate.parrots.create!(:name => "parrots_1")
939
-
940
- @pirate.parrots.each { |parrot| parrot.name = '' }
941
- assert !@pirate.valid?
942
-
943
- @pirate.parrots.each { |parrot| parrot.destroy }
944
- assert @pirate.valid?
945
- end
946
-
947
- def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm
948
- @pirate.parrots.create!(:name => "parrots_1")
949
-
950
- @pirate.parrots.each { |parrot| parrot.mark_for_destruction }
951
- assert @pirate.save
952
-
953
- Pirate.transaction do
954
- assert_queries(0) do
955
- assert @pirate.save
956
- end
957
- end
958
- end
959
-
960
- def test_should_rollback_destructions_if_an_exception_occurred_while_saving_habtm
961
- 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
962
- before = @pirate.parrots.map { |c| c.mark_for_destruction ; c }
963
-
964
- class << @pirate.association(:parrots)
965
- def destroy(*args)
966
- super
967
- raise 'Oh noes!'
968
- end
969
- end
970
-
971
- assert_raise(RuntimeError) { assert !@pirate.save }
972
- assert_equal before, @pirate.reload.parrots
973
- end
974
-
975
- # Add and remove callbacks tests for association collections.
976
- %w{ method proc }.each do |callback_type|
977
- define_method("test_should_run_add_callback_#{callback_type}s_for_habtm") do
978
- association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks"
979
-
980
- pirate = Pirate.new(:catchphrase => "Arr")
981
- pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed")
982
-
983
- expected = [
984
- "before_adding_#{callback_type}_parrot_<new>",
985
- "after_adding_#{callback_type}_parrot_<new>"
986
- ]
987
-
988
- assert_equal expected, pirate.ship_log
989
- end
990
-
991
- define_method("test_should_run_remove_callback_#{callback_type}s_for_habtm") do
992
- association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks"
993
-
994
- @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
995
- @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction }
996
- child_id = @pirate.send(association_name_with_callbacks).first.id
997
-
998
- @pirate.ship_log.clear
999
- @pirate.save
1000
-
1001
- expected = [
1002
- "before_removing_#{callback_type}_parrot_#{child_id}",
1003
- "after_removing_#{callback_type}_parrot_#{child_id}"
1004
- ]
1005
-
1006
- assert_equal expected, @pirate.ship_log
1007
- end
1008
- end
1009
- end
1010
-
1011
- class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
1012
- self.use_transactional_fixtures = false unless supports_savepoints?
1013
-
1014
- def setup
1015
- super
1016
- @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1017
- @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
1018
- end
1019
-
1020
- def test_should_still_work_without_an_associated_model
1021
- @ship.destroy
1022
- @pirate.reload.catchphrase = "Arr"
1023
- @pirate.save
1024
- assert_equal 'Arr', @pirate.reload.catchphrase
1025
- end
1026
-
1027
- def test_should_automatically_save_the_associated_model
1028
- @pirate.ship.name = 'The Vile Insanity'
1029
- @pirate.save
1030
- assert_equal 'The Vile Insanity', @pirate.reload.ship.name
1031
- end
1032
-
1033
- def test_changed_for_autosave_should_handle_cycles
1034
- @ship.pirate = @pirate
1035
- assert_queries(0) { @ship.save! }
1036
-
1037
- @parrot = @pirate.parrots.create(name: "some_name")
1038
- @parrot.name="changed_name"
1039
- assert_queries(1) { @ship.save! }
1040
- assert_queries(0) { @ship.save! }
1041
- end
1042
-
1043
- def test_should_automatically_save_bang_the_associated_model
1044
- @pirate.ship.name = 'The Vile Insanity'
1045
- @pirate.save!
1046
- assert_equal 'The Vile Insanity', @pirate.reload.ship.name
1047
- end
1048
-
1049
- def test_should_automatically_validate_the_associated_model
1050
- @pirate.ship.name = ''
1051
- assert @pirate.invalid?
1052
- assert @pirate.errors[:"ship.name"].any?
1053
- end
1054
-
1055
- def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
1056
- @pirate.ship.name = nil
1057
- @pirate.catchphrase = nil
1058
- assert @pirate.invalid?
1059
- assert @pirate.errors[:"ship.name"].any?
1060
- assert @pirate.errors[:catchphrase].any?
1061
- end
1062
-
1063
- def test_should_not_ignore_different_error_messages_on_the_same_attribute
1064
- old_validators = Ship._validators.deep_dup
1065
- old_callbacks = Ship._validate_callbacks.deep_dup
1066
- Ship.validates_format_of :name, :with => /\w/
1067
- @pirate.ship.name = ""
1068
- @pirate.catchphrase = nil
1069
- assert @pirate.invalid?
1070
- assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"]
1071
- ensure
1072
- Ship._validators = old_validators if old_validators
1073
- Ship._validate_callbacks = old_callbacks if old_callbacks
1074
- end
1075
-
1076
- def test_should_still_allow_to_bypass_validations_on_the_associated_model
1077
- @pirate.catchphrase = ''
1078
- @pirate.ship.name = ''
1079
- @pirate.save(:validate => false)
1080
- # Oracle saves empty string as NULL
1081
- if current_adapter?(:OracleAdapter)
1082
- assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name]
1083
- else
1084
- assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name]
1085
- end
1086
- end
1087
-
1088
- def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth
1089
- 2.times { |i| @pirate.ship.parts.create!(:name => "part #{i}") }
1090
-
1091
- @pirate.catchphrase = ''
1092
- @pirate.ship.name = ''
1093
- @pirate.ship.parts.each { |part| part.name = '' }
1094
- @pirate.save(:validate => false)
1095
-
1096
- values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)]
1097
- # Oracle saves empty string as NULL
1098
- if current_adapter?(:OracleAdapter)
1099
- assert_equal [nil, nil, nil, nil], values
1100
- else
1101
- assert_equal ['', '', '', ''], values
1102
- end
1103
- end
1104
-
1105
- def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
1106
- @pirate.ship.name = ''
1107
- assert_raise(ActiveRecord::RecordInvalid) do
1108
- @pirate.save!
1109
- end
1110
- end
1111
-
1112
- def test_should_not_save_and_return_false_if_a_callback_cancelled_saving
1113
- pirate = Pirate.new(:catchphrase => 'Arr')
1114
- ship = pirate.build_ship(:name => 'The Vile Insanity')
1115
- ship.cancel_save_from_callback = true
1116
-
1117
- assert_no_difference 'Pirate.count' do
1118
- assert_no_difference 'Ship.count' do
1119
- assert !pirate.save
1120
- end
1121
- end
1122
- end
1123
-
1124
- def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
1125
- before = [@pirate.catchphrase, @pirate.ship.name]
1126
-
1127
- @pirate.catchphrase = 'Arr'
1128
- @pirate.ship.name = 'The Vile Insanity'
1129
-
1130
- # Stub the save method of the @pirate.ship instance to raise an exception
1131
- class << @pirate.ship
1132
- def save(*args)
1133
- super
1134
- raise 'Oh noes!'
1135
- end
1136
- end
1137
-
1138
- assert_raise(RuntimeError) { assert !@pirate.save }
1139
- assert_equal before, [@pirate.reload.catchphrase, @pirate.ship.name]
1140
- end
1141
-
1142
- def test_should_not_load_the_associated_model
1143
- assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
1144
- end
1145
-
1146
- def test_mark_for_destruction_is_ignored_without_autosave_true
1147
- ship = ShipWithoutNestedAttributes.new(name: "The Black Flag")
1148
- ship.parts.build.mark_for_destruction
1149
-
1150
- assert_not ship.valid?
1151
- end
1152
- end
1153
-
1154
- class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase
1155
- self.use_transactional_fixtures = false unless supports_savepoints?
1156
-
1157
- def setup
1158
- super
1159
- organization = Organization.create
1160
- @member = Member.create
1161
- MemberDetail.create(organization: organization, member: @member)
1162
- end
1163
-
1164
- def test_should_not_has_one_through_model
1165
- class << @member.organization
1166
- def save(*args)
1167
- super
1168
- raise 'Oh noes!'
1169
- end
1170
- end
1171
- assert_nothing_raised { @member.save }
1172
- end
1173
- end
1174
-
1175
- class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
1176
- self.use_transactional_fixtures = false unless supports_savepoints?
1177
-
1178
- def setup
1179
- super
1180
- @ship = Ship.create(:name => 'Nights Dirty Lightning')
1181
- @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1182
- end
1183
-
1184
- def test_should_still_work_without_an_associated_model
1185
- @pirate.destroy
1186
- @ship.reload.name = "The Vile Insanity"
1187
- @ship.save
1188
- assert_equal 'The Vile Insanity', @ship.reload.name
1189
- end
1190
-
1191
- def test_should_automatically_save_the_associated_model
1192
- @ship.pirate.catchphrase = 'Arr'
1193
- @ship.save
1194
- assert_equal 'Arr', @ship.reload.pirate.catchphrase
1195
- end
1196
-
1197
- def test_should_automatically_save_bang_the_associated_model
1198
- @ship.pirate.catchphrase = 'Arr'
1199
- @ship.save!
1200
- assert_equal 'Arr', @ship.reload.pirate.catchphrase
1201
- end
1202
-
1203
- def test_should_automatically_validate_the_associated_model
1204
- @ship.pirate.catchphrase = ''
1205
- assert @ship.invalid?
1206
- assert @ship.errors[:"pirate.catchphrase"].any?
1207
- end
1208
-
1209
- def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
1210
- @ship.name = nil
1211
- @ship.pirate.catchphrase = nil
1212
- assert @ship.invalid?
1213
- assert @ship.errors[:name].any?
1214
- assert @ship.errors[:"pirate.catchphrase"].any?
1215
- end
1216
-
1217
- def test_should_still_allow_to_bypass_validations_on_the_associated_model
1218
- @ship.pirate.catchphrase = ''
1219
- @ship.name = ''
1220
- @ship.save(:validate => false)
1221
- # Oracle saves empty string as NULL
1222
- if current_adapter?(:OracleAdapter)
1223
- assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase]
1224
- else
1225
- assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase]
1226
- end
1227
- end
1228
-
1229
- def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
1230
- @ship.pirate.catchphrase = ''
1231
- assert_raise(ActiveRecord::RecordInvalid) do
1232
- @ship.save!
1233
- end
1234
- end
1235
-
1236
- def test_should_not_save_and_return_false_if_a_callback_cancelled_saving
1237
- ship = Ship.new(:name => 'The Vile Insanity')
1238
- pirate = ship.build_pirate(:catchphrase => 'Arr')
1239
- pirate.cancel_save_from_callback = true
1240
-
1241
- assert_no_difference 'Ship.count' do
1242
- assert_no_difference 'Pirate.count' do
1243
- assert !ship.save
1244
- end
1245
- end
1246
- end
1247
-
1248
- def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
1249
- before = [@ship.pirate.catchphrase, @ship.name]
1250
-
1251
- @ship.pirate.catchphrase = 'Arr'
1252
- @ship.name = 'The Vile Insanity'
1253
-
1254
- # Stub the save method of the @ship.pirate instance to raise an exception
1255
- class << @ship.pirate
1256
- def save(*args)
1257
- super
1258
- raise 'Oh noes!'
1259
- end
1260
- end
1261
-
1262
- assert_raise(RuntimeError) { assert !@ship.save }
1263
- assert_equal before, [@ship.pirate.reload.catchphrase, @ship.reload.name]
1264
- end
1265
-
1266
- def test_should_not_load_the_associated_model
1267
- assert_queries(1) { @ship.name = 'The Vile Insanity'; @ship.save! }
1268
- end
1269
- end
1270
-
1271
- module AutosaveAssociationOnACollectionAssociationTests
1272
- def test_should_automatically_save_the_associated_models
1273
- new_names = ['Grace OMalley', 'Privateers Greed']
1274
- @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
1275
-
1276
- @pirate.save
1277
- assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
1278
- end
1279
-
1280
- def test_should_automatically_save_bang_the_associated_models
1281
- new_names = ['Grace OMalley', 'Privateers Greed']
1282
- @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
1283
-
1284
- @pirate.save!
1285
- assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
1286
- end
1287
-
1288
- def test_should_automatically_validate_the_associated_models
1289
- @pirate.send(@association_name).each { |child| child.name = '' }
1290
-
1291
- assert !@pirate.valid?
1292
- assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
1293
- assert @pirate.errors[@association_name].empty?
1294
- end
1295
-
1296
- def test_should_not_use_default_invalid_error_on_associated_models
1297
- @pirate.send(@association_name).build(:name => '')
1298
-
1299
- assert !@pirate.valid?
1300
- assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
1301
- assert @pirate.errors[@association_name].empty?
1302
- end
1303
-
1304
- def test_should_default_invalid_error_from_i18n
1305
- I18n.backend.store_translations(:en, activerecord: {errors: { models:
1306
- { @associated_model_name.to_s.to_sym => { blank: "cannot be blank" } }
1307
- }})
1308
-
1309
- @pirate.send(@association_name).build(name: '')
1310
-
1311
- assert !@pirate.valid?
1312
- assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"]
1313
- assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages
1314
- assert @pirate.errors[@association_name].empty?
1315
- ensure
1316
- I18n.backend = I18n::Backend::Simple.new
1317
- end
1318
-
1319
- def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
1320
- @pirate.send(@association_name).each { |child| child.name = '' }
1321
- @pirate.catchphrase = nil
1322
-
1323
- assert !@pirate.valid?
1324
- assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
1325
- assert @pirate.errors[:catchphrase].any?
1326
- end
1327
-
1328
- def test_should_allow_to_bypass_validations_on_the_associated_models_on_update
1329
- @pirate.catchphrase = ''
1330
- @pirate.send(@association_name).each { |child| child.name = '' }
1331
-
1332
- assert @pirate.save(:validate => false)
1333
- # Oracle saves empty string as NULL
1334
- if current_adapter?(:OracleAdapter)
1335
- assert_equal [nil, nil, nil], [
1336
- @pirate.reload.catchphrase,
1337
- @pirate.send(@association_name).first.name,
1338
- @pirate.send(@association_name).last.name
1339
- ]
1340
- else
1341
- assert_equal ['', '', ''], [
1342
- @pirate.reload.catchphrase,
1343
- @pirate.send(@association_name).first.name,
1344
- @pirate.send(@association_name).last.name
1345
- ]
1346
- end
1347
- end
1348
-
1349
- def test_should_validation_the_associated_models_on_create
1350
- assert_no_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count") do
1351
- 2.times { @pirate.send(@association_name).build }
1352
- @pirate.save
1353
- end
1354
- end
1355
-
1356
- def test_should_allow_to_bypass_validations_on_the_associated_models_on_create
1357
- assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", 2) do
1358
- 2.times { @pirate.send(@association_name).build }
1359
- @pirate.save(:validate => false)
1360
- end
1361
- end
1362
-
1363
- def test_should_not_save_and_return_false_if_a_callback_cancelled_saving_in_either_create_or_update
1364
- @pirate.catchphrase = 'Changed'
1365
- @child_1.name = 'Changed'
1366
- @child_1.cancel_save_from_callback = true
1367
-
1368
- assert !@pirate.save
1369
- assert_equal "Don' botharrr talkin' like one, savvy?", @pirate.reload.catchphrase
1370
- assert_equal "Posideons Killer", @child_1.reload.name
1371
-
1372
- new_pirate = Pirate.new(:catchphrase => 'Arr')
1373
- new_child = new_pirate.send(@association_name).build(:name => 'Grace OMalley')
1374
- new_child.cancel_save_from_callback = true
1375
-
1376
- assert_no_difference 'Pirate.count' do
1377
- assert_no_difference "#{new_child.class.name}.count" do
1378
- assert !new_pirate.save
1379
- end
1380
- end
1381
- end
1382
-
1383
- def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
1384
- before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)]
1385
- new_names = ['Grace OMalley', 'Privateers Greed']
1386
-
1387
- @pirate.catchphrase = 'Arr'
1388
- @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
1389
-
1390
- # Stub the save method of the first child instance to raise an exception
1391
- class << @pirate.send(@association_name).first
1392
- def save(*args)
1393
- super
1394
- raise 'Oh noes!'
1395
- end
1396
- end
1397
-
1398
- assert_raise(RuntimeError) { assert !@pirate.save }
1399
- assert_equal before, [@pirate.reload.catchphrase, *@pirate.send(@association_name).map(&:name)]
1400
- end
1401
-
1402
- def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
1403
- @pirate.send(@association_name).each { |child| child.name = '' }
1404
- assert_raise(ActiveRecord::RecordInvalid) do
1405
- @pirate.save!
1406
- end
1407
- end
1408
-
1409
- def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet
1410
- assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
1411
-
1412
- @pirate.send(@association_name).load_target
1413
-
1414
- assert_queries(3) do
1415
- @pirate.catchphrase = 'Yarr'
1416
- new_names = ['Grace OMalley', 'Privateers Greed']
1417
- @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
1418
- @pirate.save!
1419
- end
1420
- end
1421
- end
1422
-
1423
- class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
1424
- self.use_transactional_fixtures = false unless supports_savepoints?
1425
-
1426
- def setup
1427
- super
1428
- @association_name = :birds
1429
- @associated_model_name = :bird
1430
-
1431
- @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1432
- @child_1 = @pirate.birds.create(:name => 'Posideons Killer')
1433
- @child_2 = @pirate.birds.create(:name => 'Killer bandita Dionne')
1434
- end
1435
-
1436
- include AutosaveAssociationOnACollectionAssociationTests
1437
- end
1438
-
1439
- class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
1440
- self.use_transactional_fixtures = false unless supports_savepoints?
1441
-
1442
- def setup
1443
- super
1444
- @association_name = :autosaved_parrots
1445
- @associated_model_name = :parrot
1446
- @habtm = true
1447
-
1448
- @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?")
1449
- @child_1 = @pirate.parrots.create(name: 'Posideons Killer')
1450
- @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne')
1451
- end
1452
-
1453
- include AutosaveAssociationOnACollectionAssociationTests
1454
- end
1455
-
1456
- class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
1457
- self.use_transactional_fixtures = false unless supports_savepoints?
1458
-
1459
- def setup
1460
- super
1461
- @association_name = :parrots
1462
- @associated_model_name = :parrot
1463
- @habtm = true
1464
-
1465
- @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?")
1466
- @child_1 = @pirate.parrots.create(name: 'Posideons Killer')
1467
- @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne')
1468
- end
1469
-
1470
- include AutosaveAssociationOnACollectionAssociationTests
1471
- end
1472
-
1473
- class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::TestCase
1474
- self.use_transactional_fixtures = false unless supports_savepoints?
1475
-
1476
- def setup
1477
- super
1478
- @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1479
- @pirate.birds.create(:name => 'cookoo')
1480
- end
1481
-
1482
- test "should automatically validate associations" do
1483
- assert @pirate.valid?
1484
- @pirate.birds.each { |bird| bird.name = '' }
1485
-
1486
- assert !@pirate.valid?
1487
- end
1488
- end
1489
-
1490
- class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::TestCase
1491
- self.use_transactional_fixtures = false unless supports_savepoints?
1492
-
1493
- def setup
1494
- super
1495
- @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1496
- @pirate.create_ship(:name => 'titanic')
1497
- super
1498
- end
1499
-
1500
- test "should automatically validate associations with :validate => true" do
1501
- assert @pirate.valid?
1502
- @pirate.ship.name = ''
1503
- assert !@pirate.valid?
1504
- end
1505
-
1506
- test "should not automatically add validate associations without :validate => true" do
1507
- assert @pirate.valid?
1508
- @pirate.non_validated_ship.name = ''
1509
- assert @pirate.valid?
1510
- end
1511
- end
1512
-
1513
- class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::TestCase
1514
- self.use_transactional_fixtures = false unless supports_savepoints?
1515
-
1516
- def setup
1517
- super
1518
- @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1519
- end
1520
-
1521
- test "should automatically validate associations with :validate => true" do
1522
- assert @pirate.valid?
1523
- @pirate.parrot = Parrot.new(:name => '')
1524
- assert !@pirate.valid?
1525
- end
1526
-
1527
- test "should not automatically validate associations without :validate => true" do
1528
- assert @pirate.valid?
1529
- @pirate.non_validated_parrot = Parrot.new(:name => '')
1530
- assert @pirate.valid?
1531
- end
1532
- end
1533
-
1534
- class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::TestCase
1535
- self.use_transactional_fixtures = false unless supports_savepoints?
1536
-
1537
- def setup
1538
- super
1539
- @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1540
- end
1541
-
1542
- test "should automatically validate associations with :validate => true" do
1543
- assert @pirate.valid?
1544
- @pirate.parrots = [ Parrot.new(:name => 'popuga') ]
1545
- @pirate.parrots.each { |parrot| parrot.name = '' }
1546
- assert !@pirate.valid?
1547
- end
1548
-
1549
- test "should not automatically validate associations without :validate => true" do
1550
- assert @pirate.valid?
1551
- @pirate.non_validated_parrots = [ Parrot.new(:name => 'popuga') ]
1552
- @pirate.non_validated_parrots.each { |parrot| parrot.name = '' }
1553
- assert @pirate.valid?
1554
- end
1555
- end
1556
-
1557
- class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCase
1558
- self.use_transactional_fixtures = false unless supports_savepoints?
1559
-
1560
- def setup
1561
- super
1562
- @pirate = Pirate.new
1563
- end
1564
-
1565
- test "should generate validation methods for has_many associations" do
1566
- assert_respond_to @pirate, :validate_associated_records_for_birds
1567
- end
1568
-
1569
- test "should generate validation methods for has_one associations with :validate => true" do
1570
- assert_respond_to @pirate, :validate_associated_records_for_ship
1571
- end
1572
-
1573
- test "should not generate validation methods for has_one associations without :validate => true" do
1574
- assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_ship)
1575
- end
1576
-
1577
- test "should generate validation methods for belongs_to associations with :validate => true" do
1578
- assert_respond_to @pirate, :validate_associated_records_for_parrot
1579
- end
1580
-
1581
- test "should not generate validation methods for belongs_to associations without :validate => true" do
1582
- assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrot)
1583
- end
1584
-
1585
- test "should generate validation methods for HABTM associations with :validate => true" do
1586
- assert_respond_to @pirate, :validate_associated_records_for_parrots
1587
- end
1588
- end
1589
-
1590
- class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase
1591
- def test_autosave_with_touch_should_not_raise_system_stack_error
1592
- invoice = Invoice.create
1593
- assert_nothing_raised { invoice.line_items.create(:amount => 10) }
1594
- end
1595
- end
1
+ require 'cases/helper'
2
+ require 'models/bird'
3
+ require 'models/comment'
4
+ require 'models/company'
5
+ require 'models/customer'
6
+ require 'models/developer'
7
+ require 'models/computer'
8
+ require 'models/invoice'
9
+ require 'models/line_item'
10
+ require 'models/order'
11
+ require 'models/parrot'
12
+ require 'models/person'
13
+ require 'models/pirate'
14
+ require 'models/post'
15
+ require 'models/reader'
16
+ require 'models/ship'
17
+ require 'models/ship_part'
18
+ require 'models/tag'
19
+ require 'models/tagging'
20
+ require 'models/treasure'
21
+ require 'models/eye'
22
+ require 'models/electron'
23
+ require 'models/molecule'
24
+ require 'models/member'
25
+ require 'models/member_detail'
26
+ require 'models/organization'
27
+ require 'models/guitar'
28
+ require 'models/tuning_peg'
29
+
30
+ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
31
+ def test_autosave_validation
32
+ person = Class.new(ActiveRecord::Base) {
33
+ self.table_name = 'people'
34
+ validate :should_be_cool, :on => :create
35
+ def self.name; 'Person'; end
36
+
37
+ private
38
+
39
+ def should_be_cool
40
+ unless self.first_name == 'cool'
41
+ errors.add :first_name, "not cool"
42
+ end
43
+ end
44
+ }
45
+ reference = Class.new(ActiveRecord::Base) {
46
+ self.table_name = "references"
47
+ def self.name; 'Reference'; end
48
+ belongs_to :person, autosave: true, anonymous_class: person
49
+ }
50
+
51
+ u = person.create!(first_name: 'cool')
52
+ u.update_attributes!(first_name: 'nah') # still valid because validation only applies on 'create'
53
+ assert reference.create!(person: u).persisted?
54
+ end
55
+
56
+ def test_should_not_add_the_same_callbacks_multiple_times_for_has_one
57
+ assert_no_difference_when_adding_callbacks_twice_for Pirate, :ship
58
+ end
59
+
60
+ def test_should_not_add_the_same_callbacks_multiple_times_for_belongs_to
61
+ assert_no_difference_when_adding_callbacks_twice_for Ship, :pirate
62
+ end
63
+
64
+ def test_should_not_add_the_same_callbacks_multiple_times_for_has_many
65
+ assert_no_difference_when_adding_callbacks_twice_for Pirate, :birds
66
+ end
67
+
68
+ def test_should_not_add_the_same_callbacks_multiple_times_for_has_and_belongs_to_many
69
+ assert_no_difference_when_adding_callbacks_twice_for Pirate, :parrots
70
+ end
71
+
72
+ def test_cyclic_autosaves_do_not_add_multiple_validations
73
+ ship = ShipWithoutNestedAttributes.new
74
+ ship.prisoners.build
75
+
76
+ assert_not ship.valid?
77
+ assert_equal 1, ship.errors[:name].length
78
+ end
79
+
80
+ private
81
+
82
+ def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
83
+ reflection = model.reflect_on_association(association_name)
84
+ assert_no_difference "callbacks_for_model(#{model.name}).length" do
85
+ model.send(:add_autosave_association_callbacks, reflection)
86
+ end
87
+ end
88
+
89
+ def callbacks_for_model(model)
90
+ model.instance_variables.grep(/_callbacks$/).flat_map do |ivar|
91
+ model.instance_variable_get(ivar)
92
+ end
93
+ end
94
+ end
95
+
96
+ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
97
+ fixtures :companies, :accounts
98
+
99
+ def test_should_save_parent_but_not_invalid_child
100
+ firm = Firm.new(:name => 'GlobalMegaCorp')
101
+ assert firm.valid?
102
+
103
+ firm.build_account_using_primary_key
104
+ assert !firm.build_account_using_primary_key.valid?
105
+
106
+ assert firm.save
107
+ assert !firm.account_using_primary_key.persisted?
108
+ end
109
+
110
+ def test_save_fails_for_invalid_has_one
111
+ firm = Firm.first
112
+ assert firm.valid?
113
+
114
+ firm.build_account
115
+
116
+ assert !firm.account.valid?
117
+ assert !firm.valid?
118
+ assert !firm.save
119
+ assert_equal ["is invalid"], firm.errors["account"]
120
+ end
121
+
122
+ def test_save_succeeds_for_invalid_has_one_with_validate_false
123
+ firm = Firm.first
124
+ assert firm.valid?
125
+
126
+ firm.build_unvalidated_account
127
+
128
+ assert !firm.unvalidated_account.valid?
129
+ assert firm.valid?
130
+ assert firm.save
131
+ end
132
+
133
+ def test_build_before_child_saved
134
+ firm = Firm.find(1)
135
+
136
+ account = firm.build_account("credit_limit" => 1000)
137
+ assert_equal account, firm.account
138
+ assert !account.persisted?
139
+ assert firm.save
140
+ assert_equal account, firm.account
141
+ assert account.persisted?
142
+ end
143
+
144
+ def test_build_before_either_saved
145
+ firm = Firm.new("name" => "GlobalMegaCorp")
146
+
147
+ firm.account = account = Account.new("credit_limit" => 1000)
148
+ assert_equal account, firm.account
149
+ assert !account.persisted?
150
+ assert firm.save
151
+ assert_equal account, firm.account
152
+ assert account.persisted?
153
+ end
154
+
155
+ def test_assignment_before_parent_saved
156
+ firm = Firm.new("name" => "GlobalMegaCorp")
157
+ firm.account = a = Account.find(1)
158
+ assert !firm.persisted?
159
+ assert_equal a, firm.account
160
+ assert firm.save
161
+ assert_equal a, firm.account
162
+ firm.association(:account).reload
163
+ assert_equal a, firm.account
164
+ end
165
+
166
+ def test_assignment_before_either_saved
167
+ firm = Firm.new("name" => "GlobalMegaCorp")
168
+ firm.account = a = Account.new("credit_limit" => 1000)
169
+ assert !firm.persisted?
170
+ assert !a.persisted?
171
+ assert_equal a, firm.account
172
+ assert firm.save
173
+ assert firm.persisted?
174
+ assert a.persisted?
175
+ assert_equal a, firm.account
176
+ firm.association(:account).reload
177
+ assert_equal a, firm.account
178
+ end
179
+
180
+ def test_not_resaved_when_unchanged
181
+ firm = Firm.all.merge!(:includes => :account).first
182
+ firm.name += '-changed'
183
+ assert_queries(1) { firm.save! }
184
+
185
+ firm = Firm.first
186
+ firm.account = Account.first
187
+ assert_queries(Firm.partial_writes? ? 0 : 1) { firm.save! }
188
+
189
+ firm = Firm.first.dup
190
+ firm.account = Account.first
191
+ assert_queries(2) { firm.save! }
192
+
193
+ firm = Firm.first.dup
194
+ firm.account = Account.first.dup
195
+ assert_queries(2) { firm.save! }
196
+ end
197
+
198
+ def test_callbacks_firing_order_on_create
199
+ eye = Eye.create(:iris_attributes => {:color => 'honey'})
200
+ assert_equal [true, false], eye.after_create_callbacks_stack
201
+ end
202
+
203
+ def test_callbacks_firing_order_on_update
204
+ eye = Eye.create(iris_attributes: {color: 'honey'})
205
+ eye.update(iris_attributes: {color: 'green'})
206
+ assert_equal [true, false], eye.after_update_callbacks_stack
207
+ end
208
+
209
+ def test_callbacks_firing_order_on_save
210
+ eye = Eye.create(iris_attributes: {color: 'honey'})
211
+ assert_equal [false, false], eye.after_save_callbacks_stack
212
+
213
+ eye.update(iris_attributes: {color: 'blue'})
214
+ assert_equal [false, false, false, false], eye.after_save_callbacks_stack
215
+ end
216
+ end
217
+
218
+ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
219
+ fixtures :companies, :posts, :tags, :taggings
220
+
221
+ def test_should_save_parent_but_not_invalid_child
222
+ client = Client.new(:name => 'Joe (the Plumber)')
223
+ assert client.valid?
224
+
225
+ client.build_firm
226
+ assert !client.firm.valid?
227
+
228
+ assert client.save
229
+ assert !client.firm.persisted?
230
+ end
231
+
232
+ def test_save_fails_for_invalid_belongs_to
233
+ # Oracle saves empty string as NULL therefore :message changed to one space
234
+ assert log = AuditLog.create(:developer_id => 0, :message => " ")
235
+
236
+ log.developer = Developer.new
237
+ assert !log.developer.valid?
238
+ assert !log.valid?
239
+ assert !log.save
240
+ assert_equal ["is invalid"], log.errors["developer"]
241
+ end
242
+
243
+ def test_save_succeeds_for_invalid_belongs_to_with_validate_false
244
+ # Oracle saves empty string as NULL therefore :message changed to one space
245
+ assert log = AuditLog.create(:developer_id => 0, :message=> " ")
246
+
247
+ log.unvalidated_developer = Developer.new
248
+ assert !log.unvalidated_developer.valid?
249
+ assert log.valid?
250
+ assert log.save
251
+ end
252
+
253
+ def test_assignment_before_parent_saved
254
+ client = Client.first
255
+ apple = Firm.new("name" => "Apple")
256
+ client.firm = apple
257
+ assert_equal apple, client.firm
258
+ assert !apple.persisted?
259
+ assert client.save
260
+ assert apple.save
261
+ assert apple.persisted?
262
+ assert_equal apple, client.firm
263
+ client.association(:firm).reload
264
+ assert_equal apple, client.firm
265
+ end
266
+
267
+ def test_assignment_before_either_saved
268
+ final_cut = Client.new("name" => "Final Cut")
269
+ apple = Firm.new("name" => "Apple")
270
+ final_cut.firm = apple
271
+ assert !final_cut.persisted?
272
+ assert !apple.persisted?
273
+ assert final_cut.save
274
+ assert final_cut.persisted?
275
+ assert apple.persisted?
276
+ assert_equal apple, final_cut.firm
277
+ final_cut.association(:firm).reload
278
+ assert_equal apple, final_cut.firm
279
+ end
280
+
281
+ def test_store_two_association_with_one_save
282
+ num_orders = Order.count
283
+ num_customers = Customer.count
284
+ order = Order.new
285
+
286
+ customer1 = order.billing = Customer.new
287
+ customer2 = order.shipping = Customer.new
288
+ assert order.save
289
+ assert_equal customer1, order.billing
290
+ assert_equal customer2, order.shipping
291
+
292
+ order.reload
293
+
294
+ assert_equal customer1, order.billing
295
+ assert_equal customer2, order.shipping
296
+
297
+ assert_equal num_orders + 1, Order.count
298
+ assert_equal num_customers + 2, Customer.count
299
+ end
300
+
301
+ def test_store_association_in_two_relations_with_one_save
302
+ num_orders = Order.count
303
+ num_customers = Customer.count
304
+ order = Order.new
305
+
306
+ customer = order.billing = order.shipping = Customer.new
307
+ assert order.save
308
+ assert_equal customer, order.billing
309
+ assert_equal customer, order.shipping
310
+
311
+ order.reload
312
+
313
+ assert_equal customer, order.billing
314
+ assert_equal customer, order.shipping
315
+
316
+ assert_equal num_orders + 1, Order.count
317
+ assert_equal num_customers + 1, Customer.count
318
+ end
319
+
320
+ def test_store_association_in_two_relations_with_one_save_in_existing_object
321
+ num_orders = Order.count
322
+ num_customers = Customer.count
323
+ order = Order.create
324
+
325
+ customer = order.billing = order.shipping = Customer.new
326
+ assert order.save
327
+ assert_equal customer, order.billing
328
+ assert_equal customer, order.shipping
329
+
330
+ order.reload
331
+
332
+ assert_equal customer, order.billing
333
+ assert_equal customer, order.shipping
334
+
335
+ assert_equal num_orders + 1, Order.count
336
+ assert_equal num_customers + 1, Customer.count
337
+ end
338
+
339
+ def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
340
+ num_orders = Order.count
341
+ num_customers = Customer.count
342
+ order = Order.create
343
+
344
+ customer = order.billing = order.shipping = Customer.new
345
+ assert order.save
346
+ assert_equal customer, order.billing
347
+ assert_equal customer, order.shipping
348
+
349
+ order.reload
350
+
351
+ customer = order.billing = order.shipping = Customer.new
352
+
353
+ assert order.save
354
+ order.reload
355
+
356
+ assert_equal customer, order.billing
357
+ assert_equal customer, order.shipping
358
+
359
+ assert_equal num_orders + 1, Order.count
360
+ assert_equal num_customers + 2, Customer.count
361
+ end
362
+
363
+ def test_store_association_with_a_polymorphic_relationship
364
+ num_tagging = Tagging.count
365
+ tags(:misc).create_tagging(:taggable => posts(:thinking))
366
+ assert_equal num_tagging + 1, Tagging.count
367
+ end
368
+
369
+ def test_build_and_then_save_parent_should_not_reload_target
370
+ client = Client.first
371
+ apple = client.build_firm(:name => "Apple")
372
+ client.save!
373
+ assert_no_queries { assert_equal apple, client.firm }
374
+ end
375
+
376
+ def test_validation_does_not_validate_stale_association_target
377
+ valid_developer = Developer.create!(:name => "Dude", :salary => 50_000)
378
+ invalid_developer = Developer.new()
379
+
380
+ auditlog = AuditLog.new(:message => "foo")
381
+ auditlog.developer = invalid_developer
382
+ auditlog.developer_id = valid_developer.id
383
+
384
+ assert auditlog.valid?
385
+ end
386
+ end
387
+
388
+ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
389
+ def test_invalid_adding_with_nested_attributes
390
+ molecule = Molecule.new
391
+ valid_electron = Electron.new(name: 'electron')
392
+ invalid_electron = Electron.new
393
+
394
+ molecule.electrons = [valid_electron, invalid_electron]
395
+ molecule.save
396
+
397
+ assert_not invalid_electron.valid?
398
+ assert valid_electron.valid?
399
+ assert_not molecule.persisted?, 'Molecule should not be persisted when its electrons are invalid'
400
+ end
401
+
402
+ def test_errors_should_be_indexed_when_passed_as_array
403
+ guitar = Guitar.new
404
+ tuning_peg_valid = TuningPeg.new
405
+ tuning_peg_valid.pitch = 440.0
406
+ tuning_peg_invalid = TuningPeg.new
407
+
408
+ guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
409
+
410
+ assert_not tuning_peg_invalid.valid?
411
+ assert tuning_peg_valid.valid?
412
+ assert_not guitar.valid?
413
+ assert_equal ["is not a number"], guitar.errors["tuning_pegs[1].pitch"]
414
+ assert_not_equal ["is not a number"], guitar.errors["tuning_pegs.pitch"]
415
+ end
416
+
417
+ def test_errors_should_be_indexed_when_global_flag_is_set
418
+ old_attribute_config = ActiveRecord::Base.index_nested_attribute_errors
419
+ ActiveRecord::Base.index_nested_attribute_errors = true
420
+
421
+ molecule = Molecule.new
422
+ valid_electron = Electron.new(name: 'electron')
423
+ invalid_electron = Electron.new
424
+
425
+ molecule.electrons = [valid_electron, invalid_electron]
426
+
427
+ assert_not invalid_electron.valid?
428
+ assert valid_electron.valid?
429
+ assert_not molecule.valid?
430
+ assert_equal ["can't be blank"], molecule.errors["electrons[1].name"]
431
+ assert_not_equal ["can't be blank"], molecule.errors["electrons.name"]
432
+ ensure
433
+ ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config
434
+ end
435
+
436
+ def test_errors_details_should_be_set
437
+ molecule = Molecule.new
438
+ valid_electron = Electron.new(name: 'electron')
439
+ invalid_electron = Electron.new
440
+
441
+ molecule.electrons = [valid_electron, invalid_electron]
442
+
443
+ assert_not invalid_electron.valid?
444
+ assert valid_electron.valid?
445
+ assert_not molecule.valid?
446
+ assert_equal [{ error: :blank }], molecule.errors.details[:"electrons.name"]
447
+ end
448
+
449
+ def test_errors_details_should_be_indexed_when_passed_as_array
450
+ guitar = Guitar.new
451
+ tuning_peg_valid = TuningPeg.new
452
+ tuning_peg_valid.pitch = 440.0
453
+ tuning_peg_invalid = TuningPeg.new
454
+
455
+ guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
456
+
457
+ assert_not tuning_peg_invalid.valid?
458
+ assert tuning_peg_valid.valid?
459
+ assert_not guitar.valid?
460
+ assert_equal [{ error: :not_a_number, value: nil }], guitar.errors.details[:"tuning_pegs[1].pitch"]
461
+ assert_equal [], guitar.errors.details[:"tuning_pegs.pitch"]
462
+ end
463
+
464
+ def test_errors_details_should_be_indexed_when_global_flag_is_set
465
+ old_attribute_config = ActiveRecord::Base.index_nested_attribute_errors
466
+ ActiveRecord::Base.index_nested_attribute_errors = true
467
+
468
+ molecule = Molecule.new
469
+ valid_electron = Electron.new(name: 'electron')
470
+ invalid_electron = Electron.new
471
+
472
+ molecule.electrons = [valid_electron, invalid_electron]
473
+
474
+ assert_not invalid_electron.valid?
475
+ assert valid_electron.valid?
476
+ assert_not molecule.valid?
477
+ assert_equal [{ error: :blank }], molecule.errors.details[:"electrons[1].name"]
478
+ assert_equal [], molecule.errors.details[:"electrons.name"]
479
+ ensure
480
+ ActiveRecord::Base.index_nested_attribute_errors = old_attribute_config
481
+ end
482
+
483
+ def test_valid_adding_with_nested_attributes
484
+ molecule = Molecule.new
485
+ valid_electron = Electron.new(name: 'electron')
486
+
487
+ molecule.electrons = [valid_electron]
488
+ molecule.save
489
+
490
+ assert valid_electron.valid?
491
+ assert molecule.persisted?
492
+ assert_equal 1, molecule.electrons.count
493
+ end
494
+ end
495
+
496
+ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
497
+ fixtures :companies, :people
498
+
499
+ def test_invalid_adding
500
+ firm = Firm.find(1)
501
+ assert !(firm.clients_of_firm << c = Client.new)
502
+ assert !c.persisted?
503
+ assert !firm.valid?
504
+ assert !firm.save
505
+ assert !c.persisted?
506
+ end
507
+
508
+ def test_invalid_adding_before_save
509
+ new_firm = Firm.new("name" => "A New Firm, Inc")
510
+ new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
511
+ assert !c.persisted?
512
+ assert !c.valid?
513
+ assert !new_firm.valid?
514
+ assert !new_firm.save
515
+ assert !c.persisted?
516
+ assert !new_firm.persisted?
517
+ end
518
+
519
+ def test_invalid_adding_with_validate_false
520
+ firm = Firm.first
521
+ client = Client.new
522
+ firm.unvalidated_clients_of_firm << client
523
+
524
+ assert firm.valid?
525
+ assert !client.valid?
526
+ assert firm.save
527
+ assert !client.persisted?
528
+ end
529
+
530
+ def test_valid_adding_with_validate_false
531
+ no_of_clients = Client.count
532
+
533
+ firm = Firm.first
534
+ client = Client.new("name" => "Apple")
535
+
536
+ assert firm.valid?
537
+ assert client.valid?
538
+ assert !client.persisted?
539
+
540
+ firm.unvalidated_clients_of_firm << client
541
+
542
+ assert firm.save
543
+ assert client.persisted?
544
+ assert_equal no_of_clients + 1, Client.count
545
+ end
546
+
547
+ def test_invalid_build
548
+ new_client = companies(:first_firm).clients_of_firm.build
549
+ assert !new_client.persisted?
550
+ assert !new_client.valid?
551
+ assert_equal new_client, companies(:first_firm).clients_of_firm.last
552
+ assert !companies(:first_firm).save
553
+ assert !new_client.persisted?
554
+ assert_equal 2, companies(:first_firm).clients_of_firm.reload.size
555
+ end
556
+
557
+ def test_adding_before_save
558
+ no_of_firms = Firm.count
559
+ no_of_clients = Client.count
560
+
561
+ new_firm = Firm.new("name" => "A New Firm, Inc")
562
+ c = Client.new("name" => "Apple")
563
+
564
+ new_firm.clients_of_firm.push Client.new("name" => "Natural Company")
565
+ assert_equal 1, new_firm.clients_of_firm.size
566
+ new_firm.clients_of_firm << c
567
+ assert_equal 2, new_firm.clients_of_firm.size
568
+
569
+ assert_equal no_of_firms, Firm.count # Firm was not saved to database.
570
+ assert_equal no_of_clients, Client.count # Clients were not saved to database.
571
+ assert new_firm.save
572
+ assert new_firm.persisted?
573
+ assert c.persisted?
574
+ assert_equal new_firm, c.firm
575
+ assert_equal no_of_firms + 1, Firm.count # Firm was saved to database.
576
+ assert_equal no_of_clients + 2, Client.count # Clients were saved to database.
577
+
578
+ assert_equal 2, new_firm.clients_of_firm.size
579
+ assert_equal 2, new_firm.clients_of_firm.reload.size
580
+ end
581
+
582
+ def test_assign_ids
583
+ firm = Firm.new("name" => "Apple")
584
+ firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
585
+ firm.save
586
+ firm.reload
587
+ assert_equal 2, firm.clients.length
588
+ assert firm.clients.include?(companies(:second_client))
589
+ end
590
+
591
+ def test_assign_ids_for_through_a_belongs_to
592
+ post = Post.new(:title => "Assigning IDs works!", :body => "You heard it here first, folks!")
593
+ post.person_ids = [people(:david).id, people(:michael).id]
594
+ post.save
595
+ post.reload
596
+ assert_equal 2, post.people.length
597
+ assert post.people.include?(people(:david))
598
+ end
599
+
600
+ def test_build_before_save
601
+ company = companies(:first_firm)
602
+ new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
603
+ assert !company.clients_of_firm.loaded?
604
+
605
+ company.name += '-changed'
606
+ assert_queries(2) { assert company.save }
607
+ assert new_client.persisted?
608
+ assert_equal 3, company.clients_of_firm.reload.size
609
+ end
610
+
611
+ def test_build_many_before_save
612
+ company = companies(:first_firm)
613
+ assert_no_queries(ignore_none: false) { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
614
+
615
+ company.name += '-changed'
616
+ assert_queries(3) { assert company.save }
617
+ assert_equal 4, company.clients_of_firm.reload.size
618
+ end
619
+
620
+ def test_build_via_block_before_save
621
+ company = companies(:first_firm)
622
+ new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build {|client| client.name = "Another Client" } }
623
+ assert !company.clients_of_firm.loaded?
624
+
625
+ company.name += '-changed'
626
+ assert_queries(2) { assert company.save }
627
+ assert new_client.persisted?
628
+ assert_equal 3, company.clients_of_firm.reload.size
629
+ end
630
+
631
+ def test_build_many_via_block_before_save
632
+ company = companies(:first_firm)
633
+ assert_no_queries(ignore_none: false) do
634
+ company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
635
+ client.name = "changed"
636
+ end
637
+ end
638
+
639
+ company.name += '-changed'
640
+ assert_queries(3) { assert company.save }
641
+ assert_equal 4, company.clients_of_firm.reload.size
642
+ end
643
+
644
+ def test_replace_on_new_object
645
+ firm = Firm.new("name" => "New Firm")
646
+ firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
647
+ assert firm.save
648
+ firm.reload
649
+ assert_equal 2, firm.clients.length
650
+ assert firm.clients.include?(Client.find_by_name("New Client"))
651
+ end
652
+ end
653
+
654
+ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase
655
+ def test_autosave_new_record_on_belongs_to_can_be_disabled_per_relationship
656
+ new_account = Account.new("credit_limit" => 1000)
657
+ new_firm = Firm.new("name" => "some firm")
658
+
659
+ assert !new_firm.persisted?
660
+ new_account.firm = new_firm
661
+ new_account.save!
662
+
663
+ assert new_firm.persisted?
664
+
665
+ new_account = Account.new("credit_limit" => 1000)
666
+ new_autosaved_firm = Firm.new("name" => "some firm")
667
+
668
+ assert !new_autosaved_firm.persisted?
669
+ new_account.unautosaved_firm = new_autosaved_firm
670
+ new_account.save!
671
+
672
+ assert !new_autosaved_firm.persisted?
673
+ end
674
+
675
+ def test_autosave_new_record_on_has_one_can_be_disabled_per_relationship
676
+ firm = Firm.new("name" => "some firm")
677
+ account = Account.new("credit_limit" => 1000)
678
+
679
+ assert !account.persisted?
680
+ firm.account = account
681
+ firm.save!
682
+
683
+ assert account.persisted?
684
+
685
+ firm = Firm.new("name" => "some firm")
686
+ account = Account.new("credit_limit" => 1000)
687
+
688
+ firm.unautosaved_account = account
689
+
690
+ assert !account.persisted?
691
+ firm.unautosaved_account = account
692
+ firm.save!
693
+
694
+ assert !account.persisted?
695
+ end
696
+
697
+ def test_autosave_new_record_on_has_many_can_be_disabled_per_relationship
698
+ firm = Firm.new("name" => "some firm")
699
+ account = Account.new("credit_limit" => 1000)
700
+
701
+ assert !account.persisted?
702
+ firm.accounts << account
703
+
704
+ firm.save!
705
+ assert account.persisted?
706
+
707
+ firm = Firm.new("name" => "some firm")
708
+ account = Account.new("credit_limit" => 1000)
709
+
710
+ assert !account.persisted?
711
+ firm.unautosaved_accounts << account
712
+
713
+ firm.save!
714
+ assert !account.persisted?
715
+ end
716
+
717
+ def test_autosave_new_record_with_after_create_callback
718
+ post = PostWithAfterCreateCallback.new(title: 'Captain Murphy', body: 'is back')
719
+ post.comments.build(body: 'foo')
720
+ post.save!
721
+
722
+ assert_not_nil post.author_id
723
+ end
724
+ end
725
+
726
+ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
727
+ self.use_transactional_tests = false
728
+
729
+ setup do
730
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
731
+ @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
732
+ end
733
+
734
+ teardown do
735
+ # We are running without transactional tests and need to cleanup.
736
+ Bird.delete_all
737
+ Parrot.delete_all
738
+ @ship.delete
739
+ @pirate.delete
740
+ end
741
+
742
+ # reload
743
+ def test_a_marked_for_destruction_record_should_not_be_be_marked_after_reload
744
+ @pirate.mark_for_destruction
745
+ @pirate.ship.mark_for_destruction
746
+
747
+ assert !@pirate.reload.marked_for_destruction?
748
+ assert !@pirate.ship.reload.marked_for_destruction?
749
+ end
750
+
751
+ # has_one
752
+ def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
753
+ assert !@pirate.ship.marked_for_destruction?
754
+
755
+ @pirate.ship.mark_for_destruction
756
+ id = @pirate.ship.id
757
+
758
+ assert @pirate.ship.marked_for_destruction?
759
+ assert Ship.find_by_id(id)
760
+
761
+ @pirate.save
762
+ assert_nil @pirate.reload.ship
763
+ assert_nil Ship.find_by_id(id)
764
+ end
765
+
766
+ def test_should_skip_validation_on_a_child_association_if_marked_for_destruction
767
+ @pirate.ship.name = ''
768
+ assert !@pirate.valid?
769
+
770
+ @pirate.ship.mark_for_destruction
771
+ @pirate.ship.expects(:valid?).never
772
+ assert_difference('Ship.count', -1) { @pirate.save! }
773
+ end
774
+
775
+ def test_a_child_marked_for_destruction_should_not_be_destroyed_twice
776
+ @pirate.ship.mark_for_destruction
777
+ assert @pirate.save
778
+ class << @pirate.ship
779
+ def destroy; raise "Should not be called" end
780
+ end
781
+ assert @pirate.save
782
+ end
783
+
784
+ def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_child
785
+ # Stub the save method of the @pirate.ship instance to destroy and then raise an exception
786
+ class << @pirate.ship
787
+ def save(*args)
788
+ super
789
+ destroy
790
+ raise 'Oh noes!'
791
+ end
792
+ end
793
+
794
+ @ship.pirate.catchphrase = "Changed Catchphrase"
795
+
796
+ assert_raise(RuntimeError) { assert !@pirate.save }
797
+ assert_not_nil @pirate.reload.ship
798
+ end
799
+
800
+ def test_should_save_changed_has_one_changed_object_if_child_is_saved
801
+ @pirate.ship.name = "NewName"
802
+ assert @pirate.save
803
+ assert_equal "NewName", @pirate.ship.reload.name
804
+ end
805
+
806
+ def test_should_not_save_changed_has_one_unchanged_object_if_child_is_saved
807
+ @pirate.ship.expects(:save).never
808
+ assert @pirate.save
809
+ end
810
+
811
+ # belongs_to
812
+ def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
813
+ assert !@ship.pirate.marked_for_destruction?
814
+
815
+ @ship.pirate.mark_for_destruction
816
+ id = @ship.pirate.id
817
+
818
+ assert @ship.pirate.marked_for_destruction?
819
+ assert Pirate.find_by_id(id)
820
+
821
+ @ship.save
822
+ assert_nil @ship.reload.pirate
823
+ assert_nil Pirate.find_by_id(id)
824
+ end
825
+
826
+ def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction
827
+ @ship.pirate.catchphrase = ''
828
+ assert !@ship.valid?
829
+
830
+ @ship.pirate.mark_for_destruction
831
+ @ship.pirate.expects(:valid?).never
832
+ assert_difference('Pirate.count', -1) { @ship.save! }
833
+ end
834
+
835
+ def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice
836
+ @ship.pirate.mark_for_destruction
837
+ assert @ship.save
838
+ class << @ship.pirate
839
+ def destroy; raise "Should not be called" end
840
+ end
841
+ assert @ship.save
842
+ end
843
+
844
+ def test_should_rollback_destructions_if_an_exception_occurred_while_saving_a_parent
845
+ # Stub the save method of the @ship.pirate instance to destroy and then raise an exception
846
+ class << @ship.pirate
847
+ def save(*args)
848
+ super
849
+ destroy
850
+ raise 'Oh noes!'
851
+ end
852
+ end
853
+
854
+ @ship.pirate.catchphrase = "Changed Catchphrase"
855
+
856
+ assert_raise(RuntimeError) { assert !@ship.save }
857
+ assert_not_nil @ship.reload.pirate
858
+ end
859
+
860
+ def test_should_save_changed_child_objects_if_parent_is_saved
861
+ @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
862
+ @parrot = @pirate.parrots.create!(:name => 'Posideons Killer')
863
+ @parrot.name = "NewName"
864
+ @ship.save
865
+
866
+ assert_equal 'NewName', @parrot.reload.name
867
+ end
868
+
869
+ def test_should_destroy_has_many_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
870
+ 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") }
871
+
872
+ assert !@pirate.birds.any?(&:marked_for_destruction?)
873
+
874
+ @pirate.birds.each(&:mark_for_destruction)
875
+ klass = @pirate.birds.first.class
876
+ ids = @pirate.birds.map(&:id)
877
+
878
+ assert @pirate.birds.all?(&:marked_for_destruction?)
879
+ ids.each { |id| assert klass.find_by_id(id) }
880
+
881
+ @pirate.save
882
+ assert @pirate.reload.birds.empty?
883
+ ids.each { |id| assert_nil klass.find_by_id(id) }
884
+ end
885
+
886
+ def test_should_not_resave_destroyed_association
887
+ @pirate.birds.create!(name: :parrot)
888
+ @pirate.birds.first.destroy
889
+ @pirate.save!
890
+ assert @pirate.reload.birds.empty?
891
+ end
892
+
893
+ def test_should_skip_validation_on_has_many_if_marked_for_destruction
894
+ 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") }
895
+
896
+ @pirate.birds.each { |bird| bird.name = '' }
897
+ assert !@pirate.valid?
898
+
899
+ @pirate.birds.each do |bird|
900
+ bird.mark_for_destruction
901
+ bird.expects(:valid?).never
902
+ end
903
+ assert_difference("Bird.count", -2) { @pirate.save! }
904
+ end
905
+
906
+ def test_should_skip_validation_on_has_many_if_destroyed
907
+ @pirate.birds.create!(:name => "birds_1")
908
+
909
+ @pirate.birds.each { |bird| bird.name = '' }
910
+ assert !@pirate.valid?
911
+
912
+ @pirate.birds.each(&:destroy)
913
+ assert @pirate.valid?
914
+ end
915
+
916
+ def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many
917
+ @pirate.birds.create!(:name => "birds_1")
918
+
919
+ @pirate.birds.each(&:mark_for_destruction)
920
+ assert @pirate.save
921
+
922
+ @pirate.birds.each { |bird| bird.expects(:destroy).never }
923
+ assert @pirate.save
924
+ end
925
+
926
+ def test_should_rollback_destructions_if_an_exception_occurred_while_saving_has_many
927
+ 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") }
928
+ before = @pirate.birds.map { |c| c.mark_for_destruction ; c }
929
+
930
+ # Stub the destroy method of the second child to raise an exception
931
+ class << before.last
932
+ def destroy(*args)
933
+ super
934
+ raise 'Oh noes!'
935
+ end
936
+ end
937
+
938
+ assert_raise(RuntimeError) { assert !@pirate.save }
939
+ assert_equal before, @pirate.reload.birds
940
+ end
941
+
942
+ def test_when_new_record_a_child_marked_for_destruction_should_not_affect_other_records_from_saving
943
+ @pirate = @ship.build_pirate(:catchphrase => "Arr' now I shall keep me eye on you matey!") # new record
944
+
945
+ 3.times { |i| @pirate.birds.build(:name => "birds_#{i}") }
946
+ @pirate.birds[1].mark_for_destruction
947
+ @pirate.save!
948
+
949
+ assert_equal 2, @pirate.birds.reload.length
950
+ end
951
+
952
+ def test_should_save_new_record_that_has_same_value_as_existing_record_marked_for_destruction_on_field_that_has_unique_index
953
+ Bird.connection.add_index :birds, :name, unique: true
954
+
955
+ 3.times { |i| @pirate.birds.create(name: "unique_birds_#{i}") }
956
+
957
+ @pirate.birds[0].mark_for_destruction
958
+ @pirate.birds.build(name: @pirate.birds[0].name)
959
+ @pirate.save!
960
+
961
+ assert_equal 3, @pirate.birds.reload.length
962
+ ensure
963
+ Bird.connection.remove_index :birds, column: :name
964
+ end
965
+
966
+ # Add and remove callbacks tests for association collections.
967
+ %w{ method proc }.each do |callback_type|
968
+ define_method("test_should_run_add_callback_#{callback_type}s_for_has_many") do
969
+ association_name_with_callbacks = "birds_with_#{callback_type}_callbacks"
970
+
971
+ pirate = Pirate.new(:catchphrase => "Arr")
972
+ pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed")
973
+
974
+ expected = [
975
+ "before_adding_#{callback_type}_bird_<new>",
976
+ "after_adding_#{callback_type}_bird_<new>"
977
+ ]
978
+
979
+ assert_equal expected, pirate.ship_log
980
+ end
981
+
982
+ define_method("test_should_run_remove_callback_#{callback_type}s_for_has_many") do
983
+ association_name_with_callbacks = "birds_with_#{callback_type}_callbacks"
984
+
985
+ @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
986
+ @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction)
987
+ child_id = @pirate.send(association_name_with_callbacks).first.id
988
+
989
+ @pirate.ship_log.clear
990
+ @pirate.save
991
+
992
+ expected = [
993
+ "before_removing_#{callback_type}_bird_#{child_id}",
994
+ "after_removing_#{callback_type}_bird_#{child_id}"
995
+ ]
996
+
997
+ assert_equal expected, @pirate.ship_log
998
+ end
999
+ end
1000
+
1001
+ def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marked_for_destruction
1002
+ 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
1003
+
1004
+ assert !@pirate.parrots.any?(&:marked_for_destruction?)
1005
+ @pirate.parrots.each(&:mark_for_destruction)
1006
+
1007
+ assert_no_difference "Parrot.count" do
1008
+ @pirate.save
1009
+ end
1010
+
1011
+ assert @pirate.reload.parrots.empty?
1012
+
1013
+ join_records = Pirate.connection.select_all("SELECT * FROM parrots_pirates WHERE pirate_id = #{@pirate.id}")
1014
+ assert join_records.empty?
1015
+ end
1016
+
1017
+ def test_should_skip_validation_on_habtm_if_marked_for_destruction
1018
+ 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
1019
+
1020
+ @pirate.parrots.each { |parrot| parrot.name = '' }
1021
+ assert !@pirate.valid?
1022
+
1023
+ @pirate.parrots.each do |parrot|
1024
+ parrot.mark_for_destruction
1025
+ parrot.expects(:valid?).never
1026
+ end
1027
+
1028
+ @pirate.save!
1029
+ assert @pirate.reload.parrots.empty?
1030
+ end
1031
+
1032
+ def test_should_skip_validation_on_habtm_if_destroyed
1033
+ @pirate.parrots.create!(:name => "parrots_1")
1034
+
1035
+ @pirate.parrots.each { |parrot| parrot.name = '' }
1036
+ assert !@pirate.valid?
1037
+
1038
+ @pirate.parrots.each(&:destroy)
1039
+ assert @pirate.valid?
1040
+ end
1041
+
1042
+ def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm
1043
+ @pirate.parrots.create!(:name => "parrots_1")
1044
+
1045
+ @pirate.parrots.each(&:mark_for_destruction)
1046
+ assert @pirate.save
1047
+
1048
+ Pirate.transaction do
1049
+ assert_queries(0) do
1050
+ assert @pirate.save
1051
+ end
1052
+ end
1053
+ end
1054
+
1055
+ def test_should_rollback_destructions_if_an_exception_occurred_while_saving_habtm
1056
+ 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
1057
+ before = @pirate.parrots.map { |c| c.mark_for_destruction ; c }
1058
+
1059
+ class << @pirate.association(:parrots)
1060
+ def destroy(*args)
1061
+ super
1062
+ raise 'Oh noes!'
1063
+ end
1064
+ end
1065
+
1066
+ assert_raise(RuntimeError) { assert !@pirate.save }
1067
+ assert_equal before, @pirate.reload.parrots
1068
+ end
1069
+
1070
+ # Add and remove callbacks tests for association collections.
1071
+ %w{ method proc }.each do |callback_type|
1072
+ define_method("test_should_run_add_callback_#{callback_type}s_for_habtm") do
1073
+ association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks"
1074
+
1075
+ pirate = Pirate.new(:catchphrase => "Arr")
1076
+ pirate.send(association_name_with_callbacks).build(:name => "Crowe the One-Eyed")
1077
+
1078
+ expected = [
1079
+ "before_adding_#{callback_type}_parrot_<new>",
1080
+ "after_adding_#{callback_type}_parrot_<new>"
1081
+ ]
1082
+
1083
+ assert_equal expected, pirate.ship_log
1084
+ end
1085
+
1086
+ define_method("test_should_run_remove_callback_#{callback_type}s_for_habtm") do
1087
+ association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks"
1088
+
1089
+ @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed")
1090
+ @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction)
1091
+ child_id = @pirate.send(association_name_with_callbacks).first.id
1092
+
1093
+ @pirate.ship_log.clear
1094
+ @pirate.save
1095
+
1096
+ expected = [
1097
+ "before_removing_#{callback_type}_parrot_#{child_id}",
1098
+ "after_removing_#{callback_type}_parrot_#{child_id}"
1099
+ ]
1100
+
1101
+ assert_equal expected, @pirate.ship_log
1102
+ end
1103
+ end
1104
+ end
1105
+
1106
+ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
1107
+ self.use_transactional_tests = false unless supports_savepoints?
1108
+
1109
+ def setup
1110
+ super
1111
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1112
+ @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
1113
+ end
1114
+
1115
+ def test_should_still_work_without_an_associated_model
1116
+ @ship.destroy
1117
+ @pirate.reload.catchphrase = "Arr"
1118
+ @pirate.save
1119
+ assert_equal 'Arr', @pirate.reload.catchphrase
1120
+ end
1121
+
1122
+ def test_should_automatically_save_the_associated_model
1123
+ @pirate.ship.name = 'The Vile Insanity'
1124
+ @pirate.save
1125
+ assert_equal 'The Vile Insanity', @pirate.reload.ship.name
1126
+ end
1127
+
1128
+ def test_changed_for_autosave_should_handle_cycles
1129
+ @ship.pirate = @pirate
1130
+ assert_queries(0) { @ship.save! }
1131
+
1132
+ @parrot = @pirate.parrots.create(name: "some_name")
1133
+ @parrot.name="changed_name"
1134
+ assert_queries(1) { @ship.save! }
1135
+ assert_queries(0) { @ship.save! }
1136
+ end
1137
+
1138
+ def test_should_automatically_save_bang_the_associated_model
1139
+ @pirate.ship.name = 'The Vile Insanity'
1140
+ @pirate.save!
1141
+ assert_equal 'The Vile Insanity', @pirate.reload.ship.name
1142
+ end
1143
+
1144
+ def test_should_automatically_validate_the_associated_model
1145
+ @pirate.ship.name = ''
1146
+ assert @pirate.invalid?
1147
+ assert @pirate.errors[:"ship.name"].any?
1148
+ end
1149
+
1150
+ def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
1151
+ @pirate.ship.name = nil
1152
+ @pirate.catchphrase = nil
1153
+ assert @pirate.invalid?
1154
+ assert @pirate.errors[:"ship.name"].any?
1155
+ assert @pirate.errors[:catchphrase].any?
1156
+ end
1157
+
1158
+ def test_should_not_ignore_different_error_messages_on_the_same_attribute
1159
+ old_validators = Ship._validators.deep_dup
1160
+ old_callbacks = Ship._validate_callbacks.deep_dup
1161
+ Ship.validates_format_of :name, :with => /\w/
1162
+ @pirate.ship.name = ""
1163
+ @pirate.catchphrase = nil
1164
+ assert @pirate.invalid?
1165
+ assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"]
1166
+ ensure
1167
+ Ship._validators = old_validators if old_validators
1168
+ Ship._validate_callbacks = old_callbacks if old_callbacks
1169
+ end
1170
+
1171
+ def test_should_still_allow_to_bypass_validations_on_the_associated_model
1172
+ @pirate.catchphrase = ''
1173
+ @pirate.ship.name = ''
1174
+ @pirate.save(:validate => false)
1175
+ # Oracle saves empty string as NULL
1176
+ if current_adapter?(:OracleAdapter)
1177
+ assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name]
1178
+ else
1179
+ assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name]
1180
+ end
1181
+ end
1182
+
1183
+ def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth
1184
+ 2.times { |i| @pirate.ship.parts.create!(:name => "part #{i}") }
1185
+
1186
+ @pirate.catchphrase = ''
1187
+ @pirate.ship.name = ''
1188
+ @pirate.ship.parts.each { |part| part.name = '' }
1189
+ @pirate.save(:validate => false)
1190
+
1191
+ values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)]
1192
+ # Oracle saves empty string as NULL
1193
+ if current_adapter?(:OracleAdapter)
1194
+ assert_equal [nil, nil, nil, nil], values
1195
+ else
1196
+ assert_equal ['', '', '', ''], values
1197
+ end
1198
+ end
1199
+
1200
+ def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
1201
+ @pirate.ship.name = ''
1202
+ assert_raise(ActiveRecord::RecordInvalid) do
1203
+ @pirate.save!
1204
+ end
1205
+ end
1206
+
1207
+ def test_should_not_save_and_return_false_if_a_callback_cancelled_saving
1208
+ pirate = Pirate.new(:catchphrase => 'Arr')
1209
+ ship = pirate.build_ship(:name => 'The Vile Insanity')
1210
+ ship.cancel_save_from_callback = true
1211
+
1212
+ assert_no_difference 'Pirate.count' do
1213
+ assert_no_difference 'Ship.count' do
1214
+ assert !pirate.save
1215
+ end
1216
+ end
1217
+ end
1218
+
1219
+ def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
1220
+ before = [@pirate.catchphrase, @pirate.ship.name]
1221
+
1222
+ @pirate.catchphrase = 'Arr'
1223
+ @pirate.ship.name = 'The Vile Insanity'
1224
+
1225
+ # Stub the save method of the @pirate.ship instance to raise an exception
1226
+ class << @pirate.ship
1227
+ def save(*args)
1228
+ super
1229
+ raise 'Oh noes!'
1230
+ end
1231
+ end
1232
+
1233
+ assert_raise(RuntimeError) { assert !@pirate.save }
1234
+ assert_equal before, [@pirate.reload.catchphrase, @pirate.ship.name]
1235
+ end
1236
+
1237
+ def test_should_not_load_the_associated_model
1238
+ assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
1239
+ end
1240
+
1241
+ def test_mark_for_destruction_is_ignored_without_autosave_true
1242
+ ship = ShipWithoutNestedAttributes.new(name: "The Black Flag")
1243
+ ship.parts.build.mark_for_destruction
1244
+
1245
+ assert_not ship.valid?
1246
+ end
1247
+ end
1248
+
1249
+ class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase
1250
+ self.use_transactional_tests = false unless supports_savepoints?
1251
+
1252
+ def setup
1253
+ super
1254
+ organization = Organization.create
1255
+ @member = Member.create
1256
+ MemberDetail.create(organization: organization, member: @member)
1257
+ end
1258
+
1259
+ def test_should_not_has_one_through_model
1260
+ class << @member.organization
1261
+ def save(*args)
1262
+ super
1263
+ raise 'Oh noes!'
1264
+ end
1265
+ end
1266
+ assert_nothing_raised { @member.save }
1267
+ end
1268
+ end
1269
+
1270
+ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
1271
+ self.use_transactional_tests = false unless supports_savepoints?
1272
+
1273
+ def setup
1274
+ super
1275
+ @ship = Ship.create(:name => 'Nights Dirty Lightning')
1276
+ @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1277
+ end
1278
+
1279
+ def test_should_still_work_without_an_associated_model
1280
+ @pirate.destroy
1281
+ @ship.reload.name = "The Vile Insanity"
1282
+ @ship.save
1283
+ assert_equal 'The Vile Insanity', @ship.reload.name
1284
+ end
1285
+
1286
+ def test_should_automatically_save_the_associated_model
1287
+ @ship.pirate.catchphrase = 'Arr'
1288
+ @ship.save
1289
+ assert_equal 'Arr', @ship.reload.pirate.catchphrase
1290
+ end
1291
+
1292
+ def test_should_automatically_save_bang_the_associated_model
1293
+ @ship.pirate.catchphrase = 'Arr'
1294
+ @ship.save!
1295
+ assert_equal 'Arr', @ship.reload.pirate.catchphrase
1296
+ end
1297
+
1298
+ def test_should_automatically_validate_the_associated_model
1299
+ @ship.pirate.catchphrase = ''
1300
+ assert @ship.invalid?
1301
+ assert @ship.errors[:"pirate.catchphrase"].any?
1302
+ end
1303
+
1304
+ def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
1305
+ @ship.name = nil
1306
+ @ship.pirate.catchphrase = nil
1307
+ assert @ship.invalid?
1308
+ assert @ship.errors[:name].any?
1309
+ assert @ship.errors[:"pirate.catchphrase"].any?
1310
+ end
1311
+
1312
+ def test_should_still_allow_to_bypass_validations_on_the_associated_model
1313
+ @ship.pirate.catchphrase = ''
1314
+ @ship.name = ''
1315
+ @ship.save(:validate => false)
1316
+ # Oracle saves empty string as NULL
1317
+ if current_adapter?(:OracleAdapter)
1318
+ assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase]
1319
+ else
1320
+ assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase]
1321
+ end
1322
+ end
1323
+
1324
+ def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
1325
+ @ship.pirate.catchphrase = ''
1326
+ assert_raise(ActiveRecord::RecordInvalid) do
1327
+ @ship.save!
1328
+ end
1329
+ end
1330
+
1331
+ def test_should_not_save_and_return_false_if_a_callback_cancelled_saving
1332
+ ship = Ship.new(:name => 'The Vile Insanity')
1333
+ pirate = ship.build_pirate(:catchphrase => 'Arr')
1334
+ pirate.cancel_save_from_callback = true
1335
+
1336
+ assert_no_difference 'Ship.count' do
1337
+ assert_no_difference 'Pirate.count' do
1338
+ assert !ship.save
1339
+ end
1340
+ end
1341
+ end
1342
+
1343
+ def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
1344
+ before = [@ship.pirate.catchphrase, @ship.name]
1345
+
1346
+ @ship.pirate.catchphrase = 'Arr'
1347
+ @ship.name = 'The Vile Insanity'
1348
+
1349
+ # Stub the save method of the @ship.pirate instance to raise an exception
1350
+ class << @ship.pirate
1351
+ def save(*args)
1352
+ super
1353
+ raise 'Oh noes!'
1354
+ end
1355
+ end
1356
+
1357
+ assert_raise(RuntimeError) { assert !@ship.save }
1358
+ assert_equal before, [@ship.pirate.reload.catchphrase, @ship.reload.name]
1359
+ end
1360
+
1361
+ def test_should_not_load_the_associated_model
1362
+ assert_queries(1) { @ship.name = 'The Vile Insanity'; @ship.save! }
1363
+ end
1364
+ end
1365
+
1366
+ module AutosaveAssociationOnACollectionAssociationTests
1367
+ def test_should_automatically_save_the_associated_models
1368
+ new_names = ['Grace OMalley', 'Privateers Greed']
1369
+ @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
1370
+
1371
+ @pirate.save
1372
+ assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
1373
+ end
1374
+
1375
+ def test_should_automatically_save_bang_the_associated_models
1376
+ new_names = ['Grace OMalley', 'Privateers Greed']
1377
+ @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
1378
+
1379
+ @pirate.save!
1380
+ assert_equal new_names, @pirate.reload.send(@association_name).map(&:name)
1381
+ end
1382
+
1383
+ def test_should_update_children_when_autosave_is_true_and_parent_is_new_but_child_is_not
1384
+ parrot = Parrot.create!(name: "Polly")
1385
+ parrot.name = "Squawky"
1386
+ pirate = Pirate.new(parrots: [parrot], catchphrase: "Arrrr")
1387
+
1388
+ pirate.save!
1389
+
1390
+ assert_equal "Squawky", parrot.reload.name
1391
+ end
1392
+
1393
+ def test_should_not_update_children_when_parent_creation_with_no_reason
1394
+ parrot = Parrot.create!(name: "Polly")
1395
+ assert_equal 0, parrot.updated_count
1396
+
1397
+ Pirate.create!(parrot_ids: [parrot.id], catchphrase: "Arrrr")
1398
+ assert_equal 0, parrot.reload.updated_count
1399
+ end
1400
+
1401
+ def test_should_automatically_validate_the_associated_models
1402
+ @pirate.send(@association_name).each { |child| child.name = '' }
1403
+
1404
+ assert !@pirate.valid?
1405
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
1406
+ assert @pirate.errors[@association_name].empty?
1407
+ end
1408
+
1409
+ def test_should_not_use_default_invalid_error_on_associated_models
1410
+ @pirate.send(@association_name).build(:name => '')
1411
+
1412
+ assert !@pirate.valid?
1413
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
1414
+ assert @pirate.errors[@association_name].empty?
1415
+ end
1416
+
1417
+ def test_should_default_invalid_error_from_i18n
1418
+ I18n.backend.store_translations(:en, activerecord: {errors: { models:
1419
+ { @associated_model_name.to_s.to_sym => { blank: "cannot be blank" } }
1420
+ }})
1421
+
1422
+ @pirate.send(@association_name).build(name: '')
1423
+
1424
+ assert !@pirate.valid?
1425
+ assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"]
1426
+ assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages
1427
+ assert @pirate.errors[@association_name].empty?
1428
+ ensure
1429
+ I18n.backend = I18n::Backend::Simple.new
1430
+ end
1431
+
1432
+ def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
1433
+ @pirate.send(@association_name).each { |child| child.name = '' }
1434
+ @pirate.catchphrase = nil
1435
+
1436
+ assert !@pirate.valid?
1437
+ assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
1438
+ assert @pirate.errors[:catchphrase].any?
1439
+ end
1440
+
1441
+ def test_should_allow_to_bypass_validations_on_the_associated_models_on_update
1442
+ @pirate.catchphrase = ''
1443
+ @pirate.send(@association_name).each { |child| child.name = '' }
1444
+
1445
+ assert @pirate.save(:validate => false)
1446
+ # Oracle saves empty string as NULL
1447
+ if current_adapter?(:OracleAdapter)
1448
+ assert_equal [nil, nil, nil], [
1449
+ @pirate.reload.catchphrase,
1450
+ @pirate.send(@association_name).first.name,
1451
+ @pirate.send(@association_name).last.name
1452
+ ]
1453
+ else
1454
+ assert_equal ['', '', ''], [
1455
+ @pirate.reload.catchphrase,
1456
+ @pirate.send(@association_name).first.name,
1457
+ @pirate.send(@association_name).last.name
1458
+ ]
1459
+ end
1460
+ end
1461
+
1462
+ def test_should_validation_the_associated_models_on_create
1463
+ assert_no_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count") do
1464
+ 2.times { @pirate.send(@association_name).build }
1465
+ @pirate.save
1466
+ end
1467
+ end
1468
+
1469
+ def test_should_allow_to_bypass_validations_on_the_associated_models_on_create
1470
+ assert_difference("#{ @association_name == :birds ? 'Bird' : 'Parrot' }.count", 2) do
1471
+ 2.times { @pirate.send(@association_name).build }
1472
+ @pirate.save(:validate => false)
1473
+ end
1474
+ end
1475
+
1476
+ def test_should_not_save_and_return_false_if_a_callback_cancelled_saving_in_either_create_or_update
1477
+ @pirate.catchphrase = 'Changed'
1478
+ @child_1.name = 'Changed'
1479
+ @child_1.cancel_save_from_callback = true
1480
+
1481
+ assert !@pirate.save
1482
+ assert_equal "Don' botharrr talkin' like one, savvy?", @pirate.reload.catchphrase
1483
+ assert_equal "Posideons Killer", @child_1.reload.name
1484
+
1485
+ new_pirate = Pirate.new(:catchphrase => 'Arr')
1486
+ new_child = new_pirate.send(@association_name).build(:name => 'Grace OMalley')
1487
+ new_child.cancel_save_from_callback = true
1488
+
1489
+ assert_no_difference 'Pirate.count' do
1490
+ assert_no_difference "#{new_child.class.name}.count" do
1491
+ assert !new_pirate.save
1492
+ end
1493
+ end
1494
+ end
1495
+
1496
+ def test_should_rollback_any_changes_if_an_exception_occurred_while_saving
1497
+ before = [@pirate.catchphrase, *@pirate.send(@association_name).map(&:name)]
1498
+ new_names = ['Grace OMalley', 'Privateers Greed']
1499
+
1500
+ @pirate.catchphrase = 'Arr'
1501
+ @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
1502
+
1503
+ # Stub the save method of the first child instance to raise an exception
1504
+ class << @pirate.send(@association_name).first
1505
+ def save(*args)
1506
+ super
1507
+ raise 'Oh noes!'
1508
+ end
1509
+ end
1510
+
1511
+ assert_raise(RuntimeError) { assert !@pirate.save }
1512
+ assert_equal before, [@pirate.reload.catchphrase, *@pirate.send(@association_name).map(&:name)]
1513
+ end
1514
+
1515
+ def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
1516
+ @pirate.send(@association_name).each { |child| child.name = '' }
1517
+ assert_raise(ActiveRecord::RecordInvalid) do
1518
+ @pirate.save!
1519
+ end
1520
+ end
1521
+
1522
+ def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet
1523
+ assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
1524
+
1525
+ @pirate.send(@association_name).load_target
1526
+
1527
+ assert_queries(3) do
1528
+ @pirate.catchphrase = 'Yarr'
1529
+ new_names = ['Grace OMalley', 'Privateers Greed']
1530
+ @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] }
1531
+ @pirate.save!
1532
+ end
1533
+ end
1534
+ end
1535
+
1536
+ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
1537
+ self.use_transactional_tests = false unless supports_savepoints?
1538
+
1539
+ def setup
1540
+ super
1541
+ @association_name = :birds
1542
+ @associated_model_name = :bird
1543
+
1544
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1545
+ @child_1 = @pirate.birds.create(:name => 'Posideons Killer')
1546
+ @child_2 = @pirate.birds.create(:name => 'Killer bandita Dionne')
1547
+ end
1548
+
1549
+ include AutosaveAssociationOnACollectionAssociationTests
1550
+ end
1551
+
1552
+ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
1553
+ self.use_transactional_tests = false unless supports_savepoints?
1554
+
1555
+ def setup
1556
+ super
1557
+ @association_name = :autosaved_parrots
1558
+ @associated_model_name = :parrot
1559
+ @habtm = true
1560
+
1561
+ @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?")
1562
+ @child_1 = @pirate.parrots.create(name: 'Posideons Killer')
1563
+ @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne')
1564
+ end
1565
+
1566
+ include AutosaveAssociationOnACollectionAssociationTests
1567
+ end
1568
+
1569
+ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
1570
+ self.use_transactional_tests = false unless supports_savepoints?
1571
+
1572
+ def setup
1573
+ super
1574
+ @association_name = :parrots
1575
+ @associated_model_name = :parrot
1576
+ @habtm = true
1577
+
1578
+ @pirate = Pirate.create(catchphrase: "Don' botharrr talkin' like one, savvy?")
1579
+ @child_1 = @pirate.parrots.create(name: 'Posideons Killer')
1580
+ @child_2 = @pirate.parrots.create(name: 'Killer bandita Dionne')
1581
+ end
1582
+
1583
+ include AutosaveAssociationOnACollectionAssociationTests
1584
+ end
1585
+
1586
+ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::TestCase
1587
+ self.use_transactional_tests = false unless supports_savepoints?
1588
+
1589
+ def setup
1590
+ super
1591
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1592
+ @pirate.birds.create(:name => 'cookoo')
1593
+ end
1594
+
1595
+ test "should automatically validate associations" do
1596
+ assert @pirate.valid?
1597
+ @pirate.birds.each { |bird| bird.name = '' }
1598
+
1599
+ assert !@pirate.valid?
1600
+ end
1601
+ end
1602
+
1603
+ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::TestCase
1604
+ self.use_transactional_tests = false unless supports_savepoints?
1605
+
1606
+ def setup
1607
+ super
1608
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1609
+ @pirate.create_ship(:name => 'titanic')
1610
+ super
1611
+ end
1612
+
1613
+ test "should automatically validate associations with :validate => true" do
1614
+ assert @pirate.valid?
1615
+ @pirate.ship.name = ''
1616
+ assert !@pirate.valid?
1617
+ end
1618
+
1619
+ test "should not automatically add validate associations without :validate => true" do
1620
+ assert @pirate.valid?
1621
+ @pirate.non_validated_ship.name = ''
1622
+ assert @pirate.valid?
1623
+ end
1624
+ end
1625
+
1626
+ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::TestCase
1627
+ self.use_transactional_tests = false unless supports_savepoints?
1628
+
1629
+ def setup
1630
+ super
1631
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1632
+ end
1633
+
1634
+ test "should automatically validate associations with :validate => true" do
1635
+ assert @pirate.valid?
1636
+ @pirate.parrot = Parrot.new(:name => '')
1637
+ assert !@pirate.valid?
1638
+ end
1639
+
1640
+ test "should not automatically validate associations without :validate => true" do
1641
+ assert @pirate.valid?
1642
+ @pirate.non_validated_parrot = Parrot.new(:name => '')
1643
+ assert @pirate.valid?
1644
+ end
1645
+ end
1646
+
1647
+ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::TestCase
1648
+ self.use_transactional_tests = false unless supports_savepoints?
1649
+
1650
+ def setup
1651
+ super
1652
+ @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
1653
+ end
1654
+
1655
+ test "should automatically validate associations with :validate => true" do
1656
+ assert @pirate.valid?
1657
+ @pirate.parrots = [ Parrot.new(:name => 'popuga') ]
1658
+ @pirate.parrots.each { |parrot| parrot.name = '' }
1659
+ assert !@pirate.valid?
1660
+ end
1661
+
1662
+ test "should not automatically validate associations without :validate => true" do
1663
+ assert @pirate.valid?
1664
+ @pirate.non_validated_parrots = [ Parrot.new(:name => 'popuga') ]
1665
+ @pirate.non_validated_parrots.each { |parrot| parrot.name = '' }
1666
+ assert @pirate.valid?
1667
+ end
1668
+ end
1669
+
1670
+ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCase
1671
+ self.use_transactional_tests = false unless supports_savepoints?
1672
+
1673
+ def setup
1674
+ super
1675
+ @pirate = Pirate.new
1676
+ end
1677
+
1678
+ test "should generate validation methods for has_many associations" do
1679
+ assert_respond_to @pirate, :validate_associated_records_for_birds
1680
+ end
1681
+
1682
+ test "should generate validation methods for has_one associations with :validate => true" do
1683
+ assert_respond_to @pirate, :validate_associated_records_for_ship
1684
+ end
1685
+
1686
+ test "should not generate validation methods for has_one associations without :validate => true" do
1687
+ assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_ship)
1688
+ end
1689
+
1690
+ test "should generate validation methods for belongs_to associations with :validate => true" do
1691
+ assert_respond_to @pirate, :validate_associated_records_for_parrot
1692
+ end
1693
+
1694
+ test "should not generate validation methods for belongs_to associations without :validate => true" do
1695
+ assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrot)
1696
+ end
1697
+
1698
+ test "should generate validation methods for HABTM associations with :validate => true" do
1699
+ assert_respond_to @pirate, :validate_associated_records_for_parrots
1700
+ end
1701
+ end
1702
+
1703
+ class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase
1704
+ def test_autosave_with_touch_should_not_raise_system_stack_error
1705
+ invoice = Invoice.create
1706
+ assert_nothing_raised { invoice.line_items.create(:amount => 10) }
1707
+ end
1708
+ end