ibm_db 3.0.4-x86-mingw32 → 5.0.4-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 (593) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +8 -1
  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 +269 -0
  8. data/ext/Makefile.nt32 +181 -181
  9. data/ext/Makefile.nt32.191 +212 -212
  10. data/ext/extconf.rb +322 -291
  11. data/ext/gil_release_version +3 -0
  12. data/ext/ibm_db-i386-mingw32.def +2 -0
  13. data/ext/ibm_db.c +11879 -11884
  14. data/ext/ibm_db.o +0 -0
  15. data/ext/ibm_db.so +0 -0
  16. data/ext/mkmf.log +110 -0
  17. data/ext/ruby_ibm_db.h +241 -241
  18. data/ext/ruby_ibm_db_cli.c +866 -866
  19. data/ext/ruby_ibm_db_cli.h +500 -500
  20. data/ext/ruby_ibm_db_cli.o +0 -0
  21. data/ext/unicode_support_version +3 -0
  22. data/init.rb +41 -41
  23. data/lib/IBM_DB.rb +27 -27
  24. data/lib/active_record/connection_adapters/ibm_db_adapter.rb +3561 -3177
  25. data/lib/active_record/connection_adapters/ibmdb_adapter.rb +5 -2
  26. data/lib/active_record/vendor/db2-i5-zOS.yaml +328 -328
  27. data/lib/mswin32/ibm_db.rb +91 -123
  28. data/lib/mswin32/rb2x/i386/ibm_db.so +0 -0
  29. data/test/active_record/connection_adapters/fake_adapter.rb +49 -46
  30. data/test/assets/example.log +1 -1
  31. data/test/assets/test.txt +1 -1
  32. data/test/cases/adapter_test.rb +351 -261
  33. data/test/cases/adapters/mysql2/active_schema_test.rb +193 -0
  34. data/test/cases/adapters/mysql2/bind_parameter_test.rb +50 -0
  35. data/test/cases/adapters/mysql2/boolean_test.rb +100 -0
  36. data/test/cases/adapters/mysql2/case_sensitivity_test.rb +63 -0
  37. data/test/cases/adapters/mysql2/charset_collation_test.rb +54 -0
  38. data/test/cases/adapters/mysql2/connection_test.rb +210 -0
  39. data/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb +45 -0
  40. data/test/cases/adapters/mysql2/enum_test.rb +26 -0
  41. data/test/cases/adapters/mysql2/explain_test.rb +21 -0
  42. data/test/cases/adapters/mysql2/json_test.rb +195 -0
  43. data/test/cases/adapters/mysql2/mysql2_adapter_test.rb +83 -0
  44. data/test/cases/adapters/mysql2/reserved_word_test.rb +152 -0
  45. data/test/cases/adapters/mysql2/schema_migrations_test.rb +59 -0
  46. data/test/cases/adapters/mysql2/schema_test.rb +126 -0
  47. data/test/cases/adapters/mysql2/sp_test.rb +36 -0
  48. data/test/cases/adapters/mysql2/sql_types_test.rb +14 -0
  49. data/test/cases/adapters/mysql2/table_options_test.rb +42 -0
  50. data/test/cases/adapters/mysql2/unsigned_type_test.rb +66 -0
  51. data/test/cases/adapters/postgresql/active_schema_test.rb +98 -0
  52. data/test/cases/adapters/postgresql/array_test.rb +339 -0
  53. data/test/cases/adapters/postgresql/bit_string_test.rb +82 -0
  54. data/test/cases/adapters/postgresql/bytea_test.rb +134 -0
  55. data/test/cases/adapters/postgresql/case_insensitive_test.rb +26 -0
  56. data/test/cases/adapters/postgresql/change_schema_test.rb +38 -0
  57. data/test/cases/adapters/postgresql/cidr_test.rb +25 -0
  58. data/test/cases/adapters/postgresql/citext_test.rb +78 -0
  59. data/test/cases/adapters/postgresql/collation_test.rb +53 -0
  60. data/test/cases/adapters/postgresql/composite_test.rb +132 -0
  61. data/test/cases/adapters/postgresql/connection_test.rb +257 -0
  62. data/test/cases/adapters/postgresql/datatype_test.rb +92 -0
  63. data/test/cases/adapters/postgresql/domain_test.rb +47 -0
  64. data/test/cases/adapters/postgresql/enum_test.rb +91 -0
  65. data/test/cases/adapters/postgresql/explain_test.rb +20 -0
  66. data/test/cases/adapters/postgresql/extension_migration_test.rb +63 -0
  67. data/test/cases/adapters/postgresql/full_text_test.rb +44 -0
  68. data/test/cases/adapters/postgresql/geometric_test.rb +378 -0
  69. data/test/cases/adapters/postgresql/hstore_test.rb +382 -0
  70. data/test/cases/adapters/postgresql/infinity_test.rb +69 -0
  71. data/test/cases/adapters/postgresql/integer_test.rb +25 -0
  72. data/test/cases/adapters/postgresql/json_test.rb +237 -0
  73. data/test/cases/adapters/postgresql/ltree_test.rb +53 -0
  74. data/test/cases/adapters/postgresql/money_test.rb +96 -0
  75. data/test/cases/adapters/postgresql/network_test.rb +94 -0
  76. data/test/cases/adapters/postgresql/numbers_test.rb +49 -0
  77. data/test/cases/adapters/postgresql/postgresql_adapter_test.rb +405 -0
  78. data/test/cases/adapters/postgresql/prepared_statements_test.rb +22 -0
  79. data/test/cases/adapters/postgresql/quoting_test.rb +44 -0
  80. data/test/cases/adapters/postgresql/range_test.rb +343 -0
  81. data/test/cases/adapters/postgresql/referential_integrity_test.rb +111 -0
  82. data/test/cases/adapters/postgresql/rename_table_test.rb +34 -0
  83. data/test/cases/adapters/postgresql/schema_authorization_test.rb +119 -0
  84. data/test/cases/adapters/postgresql/schema_test.rb +597 -0
  85. data/test/cases/adapters/postgresql/serial_test.rb +154 -0
  86. data/test/cases/adapters/postgresql/statement_pool_test.rb +41 -0
  87. data/test/cases/adapters/postgresql/timestamp_test.rb +90 -0
  88. data/test/cases/adapters/postgresql/type_lookup_test.rb +33 -0
  89. data/test/cases/adapters/postgresql/utils_test.rb +62 -0
  90. data/test/cases/adapters/postgresql/uuid_test.rb +294 -0
  91. data/test/cases/adapters/postgresql/xml_test.rb +54 -0
  92. data/test/cases/adapters/sqlite3/collation_test.rb +53 -0
  93. data/test/cases/adapters/sqlite3/copy_table_test.rb +98 -0
  94. data/test/cases/adapters/sqlite3/explain_test.rb +21 -0
  95. data/test/cases/adapters/sqlite3/quoting_test.rb +101 -0
  96. data/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +441 -0
  97. data/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb +24 -0
  98. data/test/cases/adapters/sqlite3/statement_pool_test.rb +20 -0
  99. data/test/cases/aggregations_test.rb +168 -158
  100. data/test/cases/ar_schema_test.rb +146 -161
  101. data/test/cases/associations/association_scope_test.rb +16 -21
  102. data/test/cases/associations/belongs_to_associations_test.rb +1141 -1029
  103. data/test/cases/associations/bidirectional_destroy_dependencies_test.rb +41 -0
  104. data/test/cases/associations/callbacks_test.rb +190 -192
  105. data/test/cases/associations/cascaded_eager_loading_test.rb +188 -188
  106. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -36
  107. data/test/cases/associations/eager_load_nested_include_test.rb +126 -128
  108. data/test/cases/associations/eager_singularization_test.rb +148 -148
  109. data/test/cases/associations/eager_test.rb +1514 -1411
  110. data/test/cases/associations/extension_test.rb +87 -82
  111. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +1004 -932
  112. data/test/cases/associations/has_many_associations_test.rb +2501 -2162
  113. data/test/cases/associations/has_many_through_associations_test.rb +1271 -1204
  114. data/test/cases/associations/has_one_associations_test.rb +707 -610
  115. data/test/cases/associations/has_one_through_associations_test.rb +383 -380
  116. data/test/cases/associations/inner_join_association_test.rb +139 -139
  117. data/test/cases/associations/inverse_associations_test.rb +733 -693
  118. data/test/cases/associations/join_model_test.rb +777 -754
  119. data/test/cases/associations/left_outer_join_association_test.rb +88 -0
  120. data/test/cases/associations/nested_through_associations_test.rb +579 -579
  121. data/test/cases/associations/required_test.rb +102 -82
  122. data/test/cases/associations_test.rb +385 -380
  123. data/test/cases/attribute_decorators_test.rb +126 -125
  124. data/test/cases/attribute_methods/read_test.rb +60 -60
  125. data/test/cases/attribute_methods_test.rb +1009 -952
  126. data/test/cases/attribute_set_test.rb +270 -200
  127. data/test/cases/attribute_test.rb +246 -180
  128. data/test/cases/attributes_test.rb +253 -136
  129. data/test/cases/autosave_association_test.rb +1708 -1595
  130. data/test/cases/base_test.rb +1713 -1638
  131. data/test/cases/batches_test.rb +489 -212
  132. data/test/cases/binary_test.rb +44 -52
  133. data/test/cases/bind_parameter_test.rb +110 -100
  134. data/test/cases/cache_key_test.rb +26 -0
  135. data/test/cases/calculations_test.rb +798 -646
  136. data/test/cases/callbacks_test.rb +636 -543
  137. data/test/cases/clone_test.rb +40 -40
  138. data/test/cases/coders/json_test.rb +15 -0
  139. data/test/cases/coders/yaml_column_test.rb +63 -63
  140. data/test/cases/collection_cache_key_test.rb +115 -0
  141. data/test/cases/column_alias_test.rb +17 -17
  142. data/test/cases/column_definition_test.rb +92 -123
  143. data/test/cases/comment_test.rb +145 -0
  144. data/test/cases/connection_adapters/adapter_leasing_test.rb +56 -54
  145. data/test/cases/connection_adapters/connection_handler_test.rb +160 -53
  146. data/test/cases/connection_adapters/connection_specification_test.rb +12 -12
  147. data/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb +255 -293
  148. data/test/cases/connection_adapters/mysql_type_lookup_test.rb +69 -65
  149. data/test/cases/connection_adapters/quoting_test.rb +13 -13
  150. data/test/cases/connection_adapters/schema_cache_test.rb +61 -56
  151. data/test/cases/connection_adapters/type_lookup_test.rb +118 -110
  152. data/test/cases/connection_management_test.rb +112 -122
  153. data/test/cases/connection_pool_test.rb +521 -346
  154. data/test/cases/connection_specification/resolver_test.rb +131 -116
  155. data/test/cases/core_test.rb +112 -112
  156. data/test/cases/counter_cache_test.rb +214 -209
  157. data/test/cases/custom_locking_test.rb +17 -17
  158. data/test/cases/database_statements_test.rb +34 -19
  159. data/test/cases/{invalid_date_test.rb → date_test.rb} +44 -32
  160. data/test/cases/date_time_precision_test.rb +107 -0
  161. data/test/cases/date_time_test.rb +61 -61
  162. data/test/cases/defaults_test.rb +219 -223
  163. data/test/cases/dirty_test.rb +763 -775
  164. data/test/cases/disconnected_test.rb +30 -28
  165. data/test/cases/dup_test.rb +157 -157
  166. data/test/cases/enum_test.rb +444 -290
  167. data/test/cases/errors_test.rb +16 -0
  168. data/test/cases/explain_subscriber_test.rb +64 -64
  169. data/test/cases/explain_test.rb +87 -76
  170. data/test/cases/finder_respond_to_test.rb +60 -60
  171. data/test/cases/finder_test.rb +1294 -1166
  172. data/test/cases/fixture_set/file_test.rb +156 -138
  173. data/test/cases/fixtures_test.rb +988 -897
  174. data/test/cases/forbidden_attributes_protection_test.rb +165 -99
  175. data/test/cases/habtm_destroy_order_test.rb +61 -61
  176. data/test/cases/helper.rb +204 -210
  177. data/test/cases/hot_compatibility_test.rb +142 -54
  178. data/test/cases/i18n_test.rb +45 -45
  179. data/test/cases/inheritance_test.rb +606 -375
  180. data/test/cases/integration_test.rb +155 -139
  181. data/test/cases/invalid_connection_test.rb +24 -22
  182. data/test/cases/invertible_migration_test.rb +387 -295
  183. data/test/cases/json_serialization_test.rb +311 -302
  184. data/test/cases/locking_test.rb +493 -477
  185. data/test/cases/log_subscriber_test.rb +225 -136
  186. data/test/cases/migration/change_schema_test.rb +458 -472
  187. data/test/cases/migration/change_table_test.rb +256 -224
  188. data/test/cases/migration/column_attributes_test.rb +176 -192
  189. data/test/cases/migration/column_positioning_test.rb +56 -56
  190. data/test/cases/migration/columns_test.rb +310 -304
  191. data/test/cases/migration/command_recorder_test.rb +350 -305
  192. data/test/cases/migration/compatibility_test.rb +118 -0
  193. data/test/cases/migration/create_join_table_test.rb +157 -148
  194. data/test/cases/migration/foreign_key_test.rb +362 -360
  195. data/test/cases/migration/helper.rb +39 -39
  196. data/test/cases/migration/index_test.rb +218 -216
  197. data/test/cases/migration/logger_test.rb +36 -36
  198. data/test/cases/migration/pending_migrations_test.rb +52 -53
  199. data/test/cases/migration/references_foreign_key_test.rb +221 -214
  200. data/test/cases/migration/references_index_test.rb +101 -101
  201. data/test/cases/migration/references_statements_test.rb +136 -116
  202. data/test/cases/migration/rename_table_test.rb +93 -93
  203. data/test/cases/migration_test.rb +1157 -959
  204. data/test/cases/migrator_test.rb +471 -388
  205. data/test/cases/mixin_test.rb +68 -70
  206. data/test/cases/modules_test.rb +172 -173
  207. data/test/cases/multiparameter_attributes_test.rb +372 -350
  208. data/test/cases/multiple_db_test.rb +122 -115
  209. data/test/cases/nested_attributes_test.rb +1098 -1057
  210. data/test/cases/nested_attributes_with_callbacks_test.rb +144 -144
  211. data/test/cases/persistence_test.rb +1001 -909
  212. data/test/cases/pooled_connections_test.rb +81 -81
  213. data/test/cases/primary_keys_test.rb +376 -237
  214. data/test/cases/query_cache_test.rb +446 -326
  215. data/test/cases/quoting_test.rb +202 -156
  216. data/test/cases/readonly_test.rb +119 -118
  217. data/test/cases/reaper_test.rb +85 -85
  218. data/test/cases/reflection_test.rb +509 -454
  219. data/test/cases/relation/delegation_test.rb +63 -68
  220. data/test/cases/relation/merging_test.rb +157 -161
  221. data/test/cases/relation/mutation_test.rb +183 -165
  222. data/test/cases/relation/or_test.rb +92 -0
  223. data/test/cases/relation/predicate_builder_test.rb +16 -14
  224. data/test/cases/relation/record_fetch_warning_test.rb +40 -0
  225. data/test/cases/relation/where_chain_test.rb +105 -181
  226. data/test/cases/relation/where_clause_test.rb +182 -0
  227. data/test/cases/relation/where_test.rb +322 -300
  228. data/test/cases/relation_test.rb +328 -297
  229. data/test/cases/relations_test.rb +2026 -1815
  230. data/test/cases/reload_models_test.rb +22 -22
  231. data/test/cases/result_test.rb +90 -80
  232. data/test/cases/sanitize_test.rb +176 -83
  233. data/test/cases/schema_dumper_test.rb +457 -463
  234. data/test/cases/schema_loading_test.rb +52 -0
  235. data/test/cases/scoping/default_scoping_test.rb +528 -454
  236. data/test/cases/scoping/named_scoping_test.rb +561 -524
  237. data/test/cases/scoping/relation_scoping_test.rb +400 -357
  238. data/test/cases/secure_token_test.rb +32 -0
  239. data/test/cases/serialization_test.rb +104 -104
  240. data/test/cases/serialized_attribute_test.rb +364 -277
  241. data/test/cases/statement_cache_test.rb +136 -98
  242. data/test/cases/store_test.rb +195 -194
  243. data/test/cases/suppressor_test.rb +63 -0
  244. data/test/cases/tasks/database_tasks_test.rb +462 -396
  245. data/test/cases/tasks/mysql_rake_test.rb +345 -311
  246. data/test/cases/tasks/postgresql_rake_test.rb +304 -245
  247. data/test/cases/tasks/sqlite_rake_test.rb +220 -193
  248. data/test/cases/test_case.rb +131 -123
  249. data/test/cases/test_fixtures_test.rb +36 -0
  250. data/test/cases/time_precision_test.rb +103 -0
  251. data/test/cases/timestamp_test.rb +501 -468
  252. data/test/cases/touch_later_test.rb +121 -0
  253. data/test/cases/transaction_callbacks_test.rb +518 -452
  254. data/test/cases/transaction_isolation_test.rb +106 -106
  255. data/test/cases/transactions_test.rb +835 -817
  256. data/test/cases/type/adapter_specific_registry_test.rb +133 -0
  257. data/test/cases/type/date_time_test.rb +14 -0
  258. data/test/cases/type/integer_test.rb +27 -121
  259. data/test/cases/type/string_test.rb +22 -36
  260. data/test/cases/type/type_map_test.rb +177 -177
  261. data/test/cases/type_test.rb +39 -0
  262. data/test/cases/types_test.rb +24 -141
  263. data/test/cases/unconnected_test.rb +33 -33
  264. data/test/cases/validations/absence_validation_test.rb +73 -0
  265. data/test/cases/validations/association_validation_test.rb +97 -86
  266. data/test/cases/validations/i18n_generate_message_validation_test.rb +84 -84
  267. data/test/cases/validations/i18n_validation_test.rb +86 -90
  268. data/test/cases/validations/length_validation_test.rb +79 -47
  269. data/test/cases/validations/presence_validation_test.rb +103 -68
  270. data/test/cases/validations/uniqueness_validation_test.rb +548 -434
  271. data/test/cases/validations_repair_helper.rb +19 -23
  272. data/test/cases/validations_test.rb +194 -165
  273. data/test/cases/view_test.rb +216 -113
  274. data/test/cases/yaml_serialization_test.rb +121 -86
  275. data/test/config.example.yml +97 -0
  276. data/test/config.rb +5 -5
  277. data/test/config.yml +154 -154
  278. data/test/connections/native_ibm_db/connection.rb +43 -43
  279. data/test/fixtures/accounts.yml +29 -29
  280. data/test/fixtures/admin/accounts.yml +2 -2
  281. data/test/fixtures/admin/randomly_named_a9.yml +7 -7
  282. data/test/fixtures/admin/randomly_named_b0.yml +7 -7
  283. data/test/fixtures/admin/users.yml +10 -10
  284. data/test/fixtures/author_addresses.yml +17 -17
  285. data/test/fixtures/author_favorites.yml +3 -3
  286. data/test/fixtures/authors.yml +23 -23
  287. data/test/fixtures/bad_posts.yml +9 -0
  288. data/test/fixtures/binaries.yml +133 -133
  289. data/test/fixtures/books.yml +31 -11
  290. data/test/fixtures/bulbs.yml +5 -5
  291. data/test/fixtures/cars.yml +9 -9
  292. data/test/fixtures/categories.yml +19 -19
  293. data/test/fixtures/categories/special_categories.yml +9 -9
  294. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -4
  295. data/test/fixtures/categories_ordered.yml +7 -7
  296. data/test/fixtures/categories_posts.yml +31 -31
  297. data/test/fixtures/categorizations.yml +23 -23
  298. data/test/fixtures/clubs.yml +8 -8
  299. data/test/fixtures/collections.yml +3 -3
  300. data/test/fixtures/colleges.yml +3 -3
  301. data/test/fixtures/comments.yml +65 -65
  302. data/test/fixtures/companies.yml +67 -67
  303. data/test/fixtures/computers.yml +10 -10
  304. data/test/fixtures/content.yml +3 -0
  305. data/test/fixtures/content_positions.yml +3 -0
  306. data/test/fixtures/courses.yml +8 -8
  307. data/test/fixtures/customers.yml +25 -25
  308. data/test/fixtures/dashboards.yml +6 -6
  309. data/test/fixtures/dead_parrots.yml +5 -0
  310. data/test/fixtures/developers.yml +21 -21
  311. data/test/fixtures/developers_projects.yml +16 -16
  312. data/test/fixtures/dog_lovers.yml +7 -7
  313. data/test/fixtures/dogs.yml +4 -4
  314. data/test/fixtures/doubloons.yml +3 -3
  315. data/test/fixtures/edges.yml +5 -5
  316. data/test/fixtures/entrants.yml +14 -14
  317. data/test/fixtures/essays.yml +6 -6
  318. data/test/fixtures/faces.yml +11 -11
  319. data/test/fixtures/fk_test_has_fk.yml +3 -3
  320. data/test/fixtures/fk_test_has_pk.yml +1 -1
  321. data/test/fixtures/friendships.yml +4 -4
  322. data/test/fixtures/funny_jokes.yml +10 -10
  323. data/test/fixtures/interests.yml +33 -33
  324. data/test/fixtures/items.yml +3 -3
  325. data/test/fixtures/jobs.yml +7 -7
  326. data/test/fixtures/legacy_things.yml +3 -3
  327. data/test/fixtures/live_parrots.yml +4 -0
  328. data/test/fixtures/mateys.yml +4 -4
  329. data/test/fixtures/member_details.yml +8 -8
  330. data/test/fixtures/member_types.yml +6 -6
  331. data/test/fixtures/members.yml +11 -11
  332. data/test/fixtures/memberships.yml +34 -34
  333. data/test/fixtures/men.yml +5 -5
  334. data/test/fixtures/minimalistics.yml +2 -2
  335. data/test/fixtures/minivans.yml +5 -5
  336. data/test/fixtures/mixed_case_monkeys.yml +6 -6
  337. data/test/fixtures/mixins.yml +29 -29
  338. data/test/fixtures/movies.yml +7 -7
  339. data/test/fixtures/naked/yml/accounts.yml +1 -1
  340. data/test/fixtures/naked/yml/companies.yml +1 -1
  341. data/test/fixtures/naked/yml/courses.yml +1 -1
  342. data/test/fixtures/naked/yml/parrots.yml +2 -0
  343. data/test/fixtures/naked/yml/trees.yml +3 -0
  344. data/test/fixtures/nodes.yml +29 -0
  345. data/test/fixtures/organizations.yml +5 -5
  346. data/test/fixtures/other_comments.yml +6 -0
  347. data/test/fixtures/other_dogs.yml +2 -0
  348. data/test/fixtures/other_posts.yml +7 -0
  349. data/test/fixtures/other_topics.yml +42 -42
  350. data/test/fixtures/owners.yml +9 -9
  351. data/test/fixtures/parrots.yml +27 -27
  352. data/test/fixtures/parrots_pirates.yml +7 -7
  353. data/test/fixtures/people.yml +24 -24
  354. data/test/fixtures/peoples_treasures.yml +3 -3
  355. data/test/fixtures/pets.yml +19 -19
  356. data/test/fixtures/pirates.yml +12 -12
  357. data/test/fixtures/posts.yml +80 -80
  358. data/test/fixtures/price_estimates.yml +16 -7
  359. data/test/fixtures/products.yml +4 -4
  360. data/test/fixtures/projects.yml +7 -7
  361. data/test/fixtures/randomly_named_a9.yml +7 -7
  362. data/test/fixtures/ratings.yml +14 -14
  363. data/test/fixtures/readers.yml +11 -11
  364. data/test/fixtures/references.yml +17 -17
  365. data/test/fixtures/reserved_words/distinct.yml +5 -5
  366. data/test/fixtures/reserved_words/distinct_select.yml +11 -11
  367. data/test/fixtures/reserved_words/group.yml +14 -14
  368. data/test/fixtures/reserved_words/select.yml +8 -8
  369. data/test/fixtures/reserved_words/values.yml +7 -7
  370. data/test/fixtures/ships.yml +6 -6
  371. data/test/fixtures/speedometers.yml +8 -8
  372. data/test/fixtures/sponsors.yml +12 -12
  373. data/test/fixtures/string_key_objects.yml +7 -7
  374. data/test/fixtures/subscribers.yml +10 -10
  375. data/test/fixtures/subscriptions.yml +12 -12
  376. data/test/fixtures/taggings.yml +78 -78
  377. data/test/fixtures/tags.yml +11 -11
  378. data/test/fixtures/tasks.yml +7 -7
  379. data/test/fixtures/teapots.yml +3 -3
  380. data/test/fixtures/to_be_linked/accounts.yml +2 -2
  381. data/test/fixtures/to_be_linked/users.yml +10 -10
  382. data/test/fixtures/topics.yml +49 -49
  383. data/test/fixtures/toys.yml +14 -14
  384. data/test/fixtures/traffic_lights.yml +9 -9
  385. data/test/fixtures/treasures.yml +10 -10
  386. data/test/fixtures/trees.yml +3 -0
  387. data/test/fixtures/uuid_children.yml +3 -3
  388. data/test/fixtures/uuid_parents.yml +2 -2
  389. data/test/fixtures/variants.yml +4 -4
  390. data/test/fixtures/vegetables.yml +19 -19
  391. data/test/fixtures/vertices.yml +3 -3
  392. data/test/fixtures/warehouse_things.yml +2 -2
  393. data/test/fixtures/zines.yml +5 -5
  394. data/test/ibm_db_test.rb +24 -24
  395. data/test/migrations/10_urban/9_add_expressions.rb +11 -11
  396. data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -15
  397. data/test/migrations/magic/1_currencies_have_symbols.rb +12 -12
  398. data/test/migrations/missing/1000_people_have_middle_names.rb +9 -9
  399. data/test/migrations/missing/1_people_have_last_names.rb +9 -9
  400. data/test/migrations/missing/3_we_need_reminders.rb +12 -12
  401. data/test/migrations/missing/4_innocent_jointable.rb +12 -12
  402. data/test/migrations/rename/1_we_need_things.rb +11 -11
  403. data/test/migrations/rename/2_rename_things.rb +9 -9
  404. data/test/migrations/to_copy/1_people_have_hobbies.rb +9 -9
  405. data/test/migrations/to_copy/2_people_have_descriptions.rb +9 -9
  406. data/test/migrations/to_copy2/1_create_articles.rb +7 -7
  407. data/test/migrations/to_copy2/2_create_comments.rb +7 -7
  408. data/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb +9 -9
  409. data/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb +9 -9
  410. data/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb +9 -9
  411. data/test/migrations/to_copy_with_timestamps2/20090101010101_create_articles.rb +7 -7
  412. data/test/migrations/to_copy_with_timestamps2/20090101010202_create_comments.rb +7 -7
  413. data/test/migrations/valid/1_valid_people_have_last_names.rb +9 -9
  414. data/test/migrations/valid/2_we_need_reminders.rb +12 -12
  415. data/test/migrations/valid/3_innocent_jointable.rb +12 -12
  416. data/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb +9 -9
  417. data/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb +12 -12
  418. data/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb +12 -12
  419. data/test/migrations/valid_with_timestamps/20100101010101_valid_with_timestamps_people_have_last_names.rb +9 -9
  420. data/test/migrations/valid_with_timestamps/20100201010101_valid_with_timestamps_we_need_reminders.rb +12 -12
  421. data/test/migrations/valid_with_timestamps/20100301010101_valid_with_timestamps_innocent_jointable.rb +12 -12
  422. data/test/migrations/version_check/20131219224947_migration_version_check.rb +8 -8
  423. data/test/models/admin.rb +4 -4
  424. data/test/models/admin/account.rb +2 -2
  425. data/test/models/admin/randomly_named_c1.rb +7 -3
  426. data/test/models/admin/user.rb +40 -40
  427. data/test/models/aircraft.rb +5 -4
  428. data/test/models/arunit2_model.rb +3 -3
  429. data/test/models/author.rb +209 -212
  430. data/test/models/auto_id.rb +4 -4
  431. data/test/models/autoloadable/extra_firm.rb +2 -2
  432. data/test/models/binary.rb +1 -1
  433. data/test/models/bird.rb +12 -12
  434. data/test/models/book.rb +23 -18
  435. data/test/models/boolean.rb +2 -2
  436. data/test/models/bulb.rb +52 -51
  437. data/test/models/cake_designer.rb +3 -3
  438. data/test/models/car.rb +29 -26
  439. data/test/models/carrier.rb +2 -2
  440. data/test/models/cat.rb +10 -0
  441. data/test/models/categorization.rb +19 -19
  442. data/test/models/category.rb +35 -35
  443. data/test/models/chef.rb +8 -3
  444. data/test/models/citation.rb +3 -3
  445. data/test/models/club.rb +25 -23
  446. data/test/models/college.rb +10 -10
  447. data/test/models/column.rb +3 -3
  448. data/test/models/column_name.rb +3 -3
  449. data/test/models/comment.rb +76 -64
  450. data/test/models/company.rb +230 -225
  451. data/test/models/company_in_module.rb +98 -98
  452. data/test/models/computer.rb +3 -3
  453. data/test/models/contact.rb +41 -41
  454. data/test/models/content.rb +40 -0
  455. data/test/models/contract.rb +20 -20
  456. data/test/models/country.rb +7 -7
  457. data/test/models/course.rb +6 -6
  458. data/test/models/customer.rb +83 -77
  459. data/test/models/customer_carrier.rb +14 -14
  460. data/test/models/dashboard.rb +3 -3
  461. data/test/models/default.rb +2 -2
  462. data/test/models/department.rb +4 -4
  463. data/test/models/developer.rb +274 -252
  464. data/test/models/dog.rb +5 -5
  465. data/test/models/dog_lover.rb +5 -5
  466. data/test/models/doubloon.rb +12 -12
  467. data/test/models/drink_designer.rb +3 -3
  468. data/test/models/edge.rb +5 -5
  469. data/test/models/electron.rb +5 -5
  470. data/test/models/engine.rb +4 -4
  471. data/test/models/entrant.rb +3 -3
  472. data/test/models/essay.rb +5 -5
  473. data/test/models/event.rb +2 -2
  474. data/test/models/eye.rb +37 -37
  475. data/test/models/face.rb +9 -9
  476. data/test/models/friendship.rb +6 -6
  477. data/test/models/guid.rb +1 -1
  478. data/test/models/guitar.rb +4 -0
  479. data/test/models/hotel.rb +11 -6
  480. data/test/models/image.rb +3 -3
  481. data/test/models/interest.rb +5 -5
  482. data/test/models/invoice.rb +4 -4
  483. data/test/models/item.rb +7 -7
  484. data/test/models/job.rb +7 -7
  485. data/test/models/joke.rb +7 -7
  486. data/test/models/keyboard.rb +3 -3
  487. data/test/models/legacy_thing.rb +3 -3
  488. data/test/models/lesson.rb +11 -11
  489. data/test/models/line_item.rb +3 -3
  490. data/test/models/liquid.rb +4 -4
  491. data/test/models/man.rb +11 -11
  492. data/test/models/matey.rb +4 -4
  493. data/test/models/member.rb +42 -41
  494. data/test/models/member_detail.rb +8 -7
  495. data/test/models/member_type.rb +3 -3
  496. data/test/models/membership.rb +35 -35
  497. data/test/models/mentor.rb +3 -0
  498. data/test/models/minimalistic.rb +2 -2
  499. data/test/models/minivan.rb +9 -9
  500. data/test/models/mixed_case_monkey.rb +3 -3
  501. data/test/models/mocktail_designer.rb +2 -0
  502. data/test/models/molecule.rb +6 -6
  503. data/test/models/movie.rb +5 -5
  504. data/test/models/node.rb +5 -0
  505. data/test/models/non_primary_key.rb +2 -0
  506. data/test/models/notification.rb +3 -0
  507. data/test/models/order.rb +4 -4
  508. data/test/models/organization.rb +14 -14
  509. data/test/models/other_dog.rb +5 -0
  510. data/test/models/owner.rb +37 -34
  511. data/test/models/parrot.rb +28 -29
  512. data/test/models/person.rb +142 -143
  513. data/test/models/personal_legacy_thing.rb +4 -4
  514. data/test/models/pet.rb +18 -15
  515. data/test/models/pet_treasure.rb +6 -0
  516. data/test/models/pirate.rb +92 -92
  517. data/test/models/possession.rb +3 -3
  518. data/test/models/post.rb +273 -264
  519. data/test/models/price_estimate.rb +4 -4
  520. data/test/models/professor.rb +5 -5
  521. data/test/models/project.rb +40 -29
  522. data/test/models/publisher.rb +2 -2
  523. data/test/models/publisher/article.rb +4 -4
  524. data/test/models/publisher/magazine.rb +3 -3
  525. data/test/models/randomly_named_c1.rb +3 -3
  526. data/test/models/rating.rb +4 -4
  527. data/test/models/reader.rb +23 -23
  528. data/test/models/recipe.rb +3 -0
  529. data/test/models/record.rb +2 -2
  530. data/test/models/reference.rb +22 -22
  531. data/test/models/reply.rb +61 -61
  532. data/test/models/ship.rb +39 -33
  533. data/test/models/ship_part.rb +7 -7
  534. data/test/models/shop.rb +17 -17
  535. data/test/models/shop_account.rb +6 -6
  536. data/test/models/speedometer.rb +6 -6
  537. data/test/models/sponsor.rb +7 -7
  538. data/test/models/string_key_object.rb +3 -3
  539. data/test/models/student.rb +4 -4
  540. data/test/models/subject.rb +16 -16
  541. data/test/models/subscriber.rb +8 -8
  542. data/test/models/subscription.rb +4 -4
  543. data/test/models/tag.rb +13 -7
  544. data/test/models/tagging.rb +13 -13
  545. data/test/models/task.rb +5 -5
  546. data/test/models/topic.rb +118 -124
  547. data/test/models/toy.rb +6 -6
  548. data/test/models/traffic_light.rb +4 -4
  549. data/test/models/treasure.rb +14 -14
  550. data/test/models/treaty.rb +7 -7
  551. data/test/models/tree.rb +3 -0
  552. data/test/models/tuning_peg.rb +4 -0
  553. data/test/models/tyre.rb +11 -11
  554. data/test/models/user.rb +14 -0
  555. data/test/models/uuid_child.rb +3 -3
  556. data/test/models/uuid_item.rb +6 -0
  557. data/test/models/uuid_parent.rb +3 -3
  558. data/test/models/vegetables.rb +24 -24
  559. data/test/models/vehicle.rb +6 -6
  560. data/test/models/vertex.rb +9 -9
  561. data/test/models/warehouse_thing.rb +5 -5
  562. data/test/models/wheel.rb +3 -3
  563. data/test/models/without_table.rb +3 -3
  564. data/test/models/zine.rb +3 -3
  565. data/test/schema/mysql2_specific_schema.rb +68 -58
  566. data/test/schema/oracle_specific_schema.rb +40 -43
  567. data/test/schema/postgresql_specific_schema.rb +114 -202
  568. data/test/schema/schema.rb +1057 -938
  569. data/test/schema/schema.rb.original +1057 -0
  570. data/test/schema/sqlite_specific_schema.rb +18 -22
  571. data/test/support/config.rb +43 -43
  572. data/test/support/connection.rb +23 -22
  573. data/test/support/connection_helper.rb +14 -14
  574. data/test/support/ddl_helper.rb +8 -8
  575. data/test/support/schema_dumping_helper.rb +20 -20
  576. data/test/support/yaml_compatibility_fixtures/rails_4_1.yml +22 -0
  577. data/test/support/yaml_compatibility_fixtures/rails_4_2_0.yml +182 -0
  578. metadata +146 -30
  579. data/lib/mswin32/rb19x/ibm_db.so +0 -0
  580. data/lib/mswin32/rb21x/i386/ibm_db.so +0 -0
  581. data/lib/mswin32/rb22x/i386/ibm_db.so +0 -0
  582. data/lib/mswin32/rb23x/i386/ibm_db.so +0 -0
  583. data/test/cases/associations/deprecated_counter_cache_on_has_many_through_test.rb +0 -26
  584. data/test/cases/attribute_methods/serialization_test.rb +0 -29
  585. data/test/cases/migration/change_schema_test - Copy.rb +0 -448
  586. data/test/cases/migration/foreign_key_test - Changed.rb +0 -325
  587. data/test/cases/migration/table_and_index_test.rb +0 -24
  588. data/test/cases/relation/where_test2.rb +0 -36
  589. data/test/cases/type/decimal_test.rb +0 -51
  590. data/test/cases/type/unsigned_integer_test.rb +0 -18
  591. data/test/cases/xml_serialization_test.rb +0 -457
  592. data/test/fixtures/naked/csv/accounts.csv +0 -1
  593. data/test/schema/mysql_specific_schema.rb +0 -70
@@ -1,115 +1,122 @@
1
- require "cases/helper"
2
- require 'models/entrant'
3
- require 'models/bird'
4
- require 'models/course'
5
-
6
- class MultipleDbTest < ActiveRecord::TestCase
7
- self.use_transactional_fixtures = false
8
-
9
- def setup
10
- @courses = create_fixtures("courses") { Course.retrieve_connection }
11
- @colleges = create_fixtures("colleges") { College.retrieve_connection }
12
- @entrants = create_fixtures("entrants")
13
- end
14
-
15
- def test_connected
16
- assert_not_nil Entrant.connection
17
- assert_not_nil Course.connection
18
- end
19
-
20
- def test_proper_connection
21
- assert_not_equal(Entrant.connection, Course.connection)
22
- assert_equal(Entrant.connection, Entrant.retrieve_connection)
23
- assert_equal(Course.connection, Course.retrieve_connection)
24
- assert_equal(ActiveRecord::Base.connection, Entrant.connection)
25
- end
26
-
27
- def test_find
28
- c1 = Course.find(1)
29
- assert_equal "Ruby Development", c1.name
30
- c2 = Course.find(2)
31
- assert_equal "Java Development", c2.name
32
- e1 = Entrant.find(1)
33
- assert_equal "Ruby Developer", e1.name
34
- e2 = Entrant.find(2)
35
- assert_equal "Ruby Guru", e2.name
36
- e3 = Entrant.find(3)
37
- assert_equal "Java Lover", e3.name
38
- end
39
-
40
- def test_associations
41
- c1 = Course.find(1)
42
- assert_equal 2, c1.entrants.count
43
- e1 = Entrant.find(1)
44
- assert_equal e1.course.id, c1.id
45
- c2 = Course.find(2)
46
- assert_equal 1, c2.entrants.count
47
- e3 = Entrant.find(3)
48
- assert_equal e3.course.id, c2.id
49
- end
50
-
51
- def test_course_connection_should_survive_dependency_reload
52
- assert Course.connection
53
-
54
- ActiveSupport::Dependencies.clear
55
- Object.send(:remove_const, :Course)
56
- require_dependency 'models/course'
57
-
58
- assert Course.connection
59
- end
60
-
61
- def test_transactions_across_databases
62
- c1 = Course.find(1)
63
- e1 = Entrant.find(1)
64
-
65
- begin
66
- Course.transaction do
67
- Entrant.transaction do
68
- c1.name = "Typo"
69
- e1.name = "Typo"
70
- c1.save
71
- e1.save
72
- raise "No I messed up."
73
- end
74
- end
75
- rescue
76
- # Yup caught it
77
- end
78
-
79
- assert_equal "Typo", c1.name
80
- assert_equal "Typo", e1.name
81
-
82
- assert_equal "Ruby Development", Course.find(1).name
83
- assert_equal "Ruby Developer", Entrant.find(1).name
84
- end
85
-
86
- def test_arel_table_engines
87
- assert_not_equal Entrant.arel_engine, Bird.arel_engine
88
- assert_not_equal Entrant.arel_engine, Course.arel_engine
89
- end
90
-
91
- def test_connection
92
- assert_equal Entrant.arel_engine.connection, Bird.arel_engine.connection
93
- assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection
94
- end
95
-
96
- def test_count_on_custom_connection
97
- ActiveRecord::Base.remove_connection
98
- assert_equal 1, College.count
99
- ensure
100
- ActiveRecord::Base.establish_connection :arunit
101
- end
102
-
103
- unless in_memory_db?
104
- def test_associations_should_work_when_model_has_no_connection
105
- begin
106
- ActiveRecord::Base.remove_connection
107
- assert_nothing_raised ActiveRecord::ConnectionNotEstablished do
108
- College.first.courses.first
109
- end
110
- ensure
111
- ActiveRecord::Base.establish_connection :arunit
112
- end
113
- end
114
- end
115
- end
1
+ require "cases/helper"
2
+ require 'models/entrant'
3
+ require 'models/bird'
4
+ require 'models/course'
5
+
6
+ class MultipleDbTest < ActiveRecord::TestCase
7
+ self.use_transactional_tests = false
8
+
9
+ def setup
10
+ @courses = create_fixtures("courses") { Course.retrieve_connection }
11
+ @colleges = create_fixtures("colleges") { College.retrieve_connection }
12
+ @entrants = create_fixtures("entrants")
13
+ end
14
+
15
+ def test_connected
16
+ assert_not_nil Entrant.connection
17
+ assert_not_nil Course.connection
18
+ end
19
+
20
+ def test_proper_connection
21
+ assert_not_equal(Entrant.connection, Course.connection)
22
+ assert_equal(Entrant.connection, Entrant.retrieve_connection)
23
+ assert_equal(Course.connection, Course.retrieve_connection)
24
+ assert_equal(ActiveRecord::Base.connection, Entrant.connection)
25
+ end
26
+
27
+ def test_swapping_the_connection
28
+ old_spec_name, Course.connection_specification_name = Course.connection_specification_name, "primary"
29
+ assert_equal(Entrant.connection, Course.connection)
30
+ ensure
31
+ Course.connection_specification_name = old_spec_name
32
+ end
33
+
34
+ def test_find
35
+ c1 = Course.find(1)
36
+ assert_equal "Ruby Development", c1.name
37
+ c2 = Course.find(2)
38
+ assert_equal "Java Development", c2.name
39
+ e1 = Entrant.find(1)
40
+ assert_equal "Ruby Developer", e1.name
41
+ e2 = Entrant.find(2)
42
+ assert_equal "Ruby Guru", e2.name
43
+ e3 = Entrant.find(3)
44
+ assert_equal "Java Lover", e3.name
45
+ end
46
+
47
+ def test_associations
48
+ c1 = Course.find(1)
49
+ assert_equal 2, c1.entrants.count
50
+ e1 = Entrant.find(1)
51
+ assert_equal e1.course.id, c1.id
52
+ c2 = Course.find(2)
53
+ assert_equal 1, c2.entrants.count
54
+ e3 = Entrant.find(3)
55
+ assert_equal e3.course.id, c2.id
56
+ end
57
+
58
+ def test_course_connection_should_survive_dependency_reload
59
+ assert Course.connection
60
+
61
+ ActiveSupport::Dependencies.clear
62
+ Object.send(:remove_const, :Course)
63
+ require_dependency 'models/course'
64
+
65
+ assert Course.connection
66
+ end
67
+
68
+ def test_transactions_across_databases
69
+ c1 = Course.find(1)
70
+ e1 = Entrant.find(1)
71
+
72
+ begin
73
+ Course.transaction do
74
+ Entrant.transaction do
75
+ c1.name = "Typo"
76
+ e1.name = "Typo"
77
+ c1.save
78
+ e1.save
79
+ raise "No I messed up."
80
+ end
81
+ end
82
+ rescue
83
+ # Yup caught it
84
+ end
85
+
86
+ assert_equal "Typo", c1.name
87
+ assert_equal "Typo", e1.name
88
+
89
+ assert_equal "Ruby Development", Course.find(1).name
90
+ assert_equal "Ruby Developer", Entrant.find(1).name
91
+ end
92
+
93
+ def test_arel_table_engines
94
+ assert_not_equal Entrant.arel_engine, Bird.arel_engine
95
+ assert_not_equal Entrant.arel_engine, Course.arel_engine
96
+ end
97
+
98
+ def test_connection
99
+ assert_equal Entrant.arel_engine.connection.object_id, Bird.arel_engine.connection.object_id
100
+ assert_not_equal Entrant.arel_engine.connection.object_id, Course.arel_engine.connection.object_id
101
+ end
102
+
103
+ unless in_memory_db?
104
+ def test_count_on_custom_connection
105
+ ActiveRecord::Base.remove_connection
106
+ assert_equal 1, College.count
107
+ ensure
108
+ ActiveRecord::Base.establish_connection :arunit
109
+ end
110
+
111
+ def test_associations_should_work_when_model_has_no_connection
112
+ begin
113
+ ActiveRecord::Base.remove_connection
114
+ assert_nothing_raised do
115
+ College.first.courses.first
116
+ end
117
+ ensure
118
+ ActiveRecord::Base.establish_connection :arunit
119
+ end
120
+ end
121
+ end
122
+ end
@@ -1,1057 +1,1098 @@
1
- require "cases/helper"
2
- require "models/pirate"
3
- require "models/ship"
4
- require "models/ship_part"
5
- require "models/bird"
6
- require "models/parrot"
7
- require "models/treasure"
8
- require "models/man"
9
- require "models/interest"
10
- require "models/owner"
11
- require "models/pet"
12
- require 'active_support/hash_with_indifferent_access'
13
-
14
- class TestNestedAttributesInGeneral < ActiveRecord::TestCase
15
- teardown do
16
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
17
- end
18
-
19
- def test_base_should_have_an_empty_nested_attributes_options
20
- assert_equal Hash.new, ActiveRecord::Base.nested_attributes_options
21
- end
22
-
23
- def test_should_add_a_proc_to_nested_attributes_options
24
- assert_equal ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC,
25
- Pirate.nested_attributes_options[:birds_with_reject_all_blank][:reject_if]
26
-
27
- [:parrots, :birds].each do |name|
28
- assert_instance_of Proc, Pirate.nested_attributes_options[name][:reject_if]
29
- end
30
- end
31
-
32
- def test_should_not_build_a_new_record_using_reject_all_even_if_destroy_is_given
33
- pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
34
- pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => '', :_destroy => '0'}]
35
- pirate.save!
36
-
37
- assert pirate.birds_with_reject_all_blank.empty?
38
- end
39
-
40
- def test_should_not_build_a_new_record_if_reject_all_blank_returns_false
41
- pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
42
- pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => ''}]
43
- pirate.save!
44
-
45
- assert pirate.birds_with_reject_all_blank.empty?
46
- end
47
-
48
- def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false
49
- pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
50
- pirate.birds_with_reject_all_blank_attributes = [{:name => 'Tweetie', :color => ''}]
51
- pirate.save!
52
-
53
- assert_equal 1, pirate.birds_with_reject_all_blank.count
54
- assert_equal 'Tweetie', pirate.birds_with_reject_all_blank.first.name
55
- end
56
-
57
- def test_should_raise_an_ArgumentError_for_non_existing_associations
58
- exception = assert_raise ArgumentError do
59
- Pirate.accepts_nested_attributes_for :honesty
60
- end
61
- assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message
62
- end
63
-
64
- def test_should_disable_allow_destroy_by_default
65
- Pirate.accepts_nested_attributes_for :ship
66
-
67
- pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
68
- ship = pirate.create_ship(name: 'Nights Dirty Lightning')
69
-
70
- pirate.update(ship_attributes: { '_destroy' => true, :id => ship.id })
71
-
72
- assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.ship.reload }
73
- end
74
-
75
- def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked_for_destruction
76
- ship = Ship.create!(:name => 'Nights Dirty Lightning')
77
- assert !ship._destroy
78
- ship.mark_for_destruction
79
- assert ship._destroy
80
- end
81
-
82
- def test_reject_if_method_without_arguments
83
- Pirate.accepts_nested_attributes_for :ship, :reject_if => :new_record?
84
-
85
- pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
86
- pirate.ship_attributes = { :name => 'Black Pearl' }
87
- assert_no_difference('Ship.count') { pirate.save! }
88
- end
89
-
90
- def test_reject_if_method_with_arguments
91
- Pirate.accepts_nested_attributes_for :ship, :reject_if => :reject_empty_ships_on_create
92
-
93
- pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
94
- pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
95
- assert_no_difference('Ship.count') { pirate.save! }
96
-
97
- # pirate.reject_empty_ships_on_create returns false for saved pirate records
98
- # in the previous step note that pirate gets saved but ship fails
99
- pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
100
- assert_difference('Ship.count') { pirate.save! }
101
- end
102
-
103
- def test_reject_if_with_indifferent_keys
104
- Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:name].blank? }
105
-
106
- pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
107
- pirate.ship_attributes = { :name => 'Hello Pearl' }
108
- assert_difference('Ship.count') { pirate.save! }
109
- end
110
-
111
- def test_reject_if_with_a_proc_which_returns_true_always_for_has_one
112
- Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| true }
113
- pirate = Pirate.new(catchphrase: "Stop wastin' me time")
114
- ship = pirate.create_ship(name: 's1')
115
- pirate.update({ship_attributes: { name: 's2', id: ship.id } })
116
- assert_equal 's1', ship.reload.name
117
- end
118
-
119
- def test_reuse_already_built_new_record
120
- pirate = Pirate.new
121
- ship_built_first = pirate.build_ship
122
- pirate.ship_attributes = { name: 'Ship 1' }
123
- assert_equal ship_built_first.object_id, pirate.ship.object_id
124
- end
125
-
126
- def test_do_not_allow_assigning_foreign_key_when_reusing_existing_new_record
127
- pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
128
- pirate.build_ship
129
- pirate.ship_attributes = { name: 'Ship 1', pirate_id: pirate.id + 1 }
130
- assert_equal pirate.id, pirate.ship.pirate_id
131
- end
132
-
133
- def test_reject_if_with_a_proc_which_returns_true_always_for_has_many
134
- Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }
135
- man = Man.create(name: "John")
136
- interest = man.interests.create(topic: 'photography')
137
- man.update({interests_attributes: { topic: 'gardening', id: interest.id } })
138
- assert_equal 'photography', interest.reload.topic
139
- end
140
-
141
- def test_destroy_works_independent_of_reject_if
142
- Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }, :allow_destroy => true
143
- man = Man.create(name: "Jon")
144
- interest = man.interests.create(topic: 'the ladies')
145
- man.update({interests_attributes: { _destroy: "1", id: interest.id } })
146
- assert man.reload.interests.empty?
147
- end
148
-
149
- def test_has_many_association_updating_a_single_record
150
- Man.accepts_nested_attributes_for(:interests)
151
- man = Man.create(name: 'John')
152
- interest = man.interests.create(topic: 'photography')
153
- man.update({interests_attributes: {topic: 'gardening', id: interest.id}})
154
- assert_equal 'gardening', interest.reload.topic
155
- end
156
-
157
- def test_reject_if_with_blank_nested_attributes_id
158
- # When using a select list to choose an existing 'ship' id, with include_blank: true
159
- Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:id].blank? }
160
-
161
- pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
162
- pirate.ship_attributes = { :id => "" }
163
- assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.save! }
164
- end
165
-
166
- def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record
167
- Man.accepts_nested_attributes_for(:interests)
168
- man = Man.create(:name => "John")
169
- interest = man.interests.create :topic => 'gardening'
170
- man = Man.find man.id
171
- man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}]
172
- assert_equal man.interests.first.topic, man.interests[0].topic
173
- end
174
-
175
- def test_allows_class_to_override_setter_and_call_super
176
- mean_pirate_class = Class.new(Pirate) do
177
- accepts_nested_attributes_for :parrot
178
- def parrot_attributes=(attrs)
179
- super(attrs.merge(:color => "blue"))
180
- end
181
- end
182
- mean_pirate = mean_pirate_class.new
183
- mean_pirate.parrot_attributes = { :name => "James" }
184
- assert_equal "James", mean_pirate.parrot.name
185
- assert_equal "blue", mean_pirate.parrot.color
186
- end
187
-
188
- def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses
189
- Pirate.accepts_nested_attributes_for(:parrot)
190
-
191
- mean_pirate_class = Class.new(Pirate) do
192
- accepts_nested_attributes_for :parrot
193
- end
194
- mean_pirate = mean_pirate_class.new
195
- mean_pirate.parrot_attributes = { :name => "James" }
196
- assert_equal "James", mean_pirate.parrot.name
197
- end
198
- end
199
-
200
- class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
201
- def setup
202
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
203
- @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
204
- end
205
-
206
- def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to
207
- exception = assert_raise ArgumentError do
208
- Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"})
209
- end
210
- assert_equal "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?", exception.message
211
- end
212
-
213
- def test_should_define_an_attribute_writer_method_for_the_association
214
- assert_respond_to @pirate, :ship_attributes=
215
- end
216
-
217
- def test_should_build_a_new_record_if_there_is_no_id
218
- @ship.destroy
219
- @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' }
220
-
221
- assert !@pirate.ship.persisted?
222
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
223
- end
224
-
225
- def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy
226
- @ship.destroy
227
- @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' }
228
-
229
- assert_nil @pirate.ship
230
- end
231
-
232
- def test_should_not_build_a_new_record_if_a_reject_if_proc_returns_false
233
- @ship.destroy
234
- @pirate.reload.ship_attributes = {}
235
-
236
- assert_nil @pirate.ship
237
- end
238
-
239
- def test_should_replace_an_existing_record_if_there_is_no_id
240
- @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' }
241
-
242
- assert !@pirate.ship.persisted?
243
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
244
- assert_equal 'Nights Dirty Lightning', @ship.name
245
- end
246
-
247
- def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy
248
- @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' }
249
-
250
- assert_equal @ship, @pirate.ship
251
- assert_equal 'Nights Dirty Lightning', @pirate.ship.name
252
- end
253
-
254
- def test_should_modify_an_existing_record_if_there_is_a_matching_id
255
- @pirate.reload.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' }
256
-
257
- assert_equal @ship, @pirate.ship
258
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
259
- end
260
-
261
- def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
262
- exception = assert_raise ActiveRecord::RecordNotFound do
263
- @pirate.ship_attributes = { :id => 1234567890 }
264
- end
265
- assert_equal "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
266
- end
267
-
268
- def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
269
- @pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' }
270
-
271
- assert_equal @ship, @pirate.ship
272
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
273
- end
274
-
275
- def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id
276
- @ship.stubs(:id).returns('ABC1X')
277
- @pirate.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' }
278
-
279
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
280
- end
281
-
282
- def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy
283
- @pirate.ship.destroy
284
-
285
- [1, '1', true, 'true'].each do |truth|
286
- ship = @pirate.reload.create_ship(name: 'Mister Pablo')
287
- @pirate.update(ship_attributes: { id: ship.id, _destroy: truth })
288
-
289
- assert_nil @pirate.reload.ship
290
- assert_raise(ActiveRecord::RecordNotFound) { Ship.find(ship.id) }
291
- end
292
- end
293
-
294
- def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
295
- [nil, '0', 0, 'false', false].each do |not_truth|
296
- @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: not_truth })
297
-
298
- assert_equal @ship, @pirate.reload.ship
299
- end
300
- end
301
-
302
- def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
303
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
304
-
305
- @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: '1' })
306
-
307
- assert_equal @ship, @pirate.reload.ship
308
-
309
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
310
- end
311
-
312
- def test_should_also_work_with_a_HashWithIndifferentAccess
313
- @pirate.ship_attributes = ActiveSupport::HashWithIndifferentAccess.new(:id => @ship.id, :name => 'Davy Jones Gold Dagger')
314
-
315
- assert @pirate.ship.persisted?
316
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
317
- end
318
-
319
- def test_should_work_with_update_as_well
320
- @pirate.update({ catchphrase: 'Arr', ship_attributes: { id: @ship.id, name: 'Mister Pablo' } })
321
- @pirate.reload
322
-
323
- assert_equal 'Arr', @pirate.catchphrase
324
- assert_equal 'Mister Pablo', @pirate.ship.name
325
- end
326
-
327
- def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
328
- @pirate.attributes = { :ship_attributes => { :id => @ship.id, :_destroy => '1' } }
329
-
330
- assert !@pirate.ship.destroyed?
331
- assert @pirate.ship.marked_for_destruction?
332
-
333
- @pirate.save
334
-
335
- assert @pirate.ship.destroyed?
336
- assert_nil @pirate.reload.ship
337
- end
338
-
339
- def test_should_automatically_enable_autosave_on_the_association
340
- assert Pirate.reflect_on_association(:ship).options[:autosave]
341
- end
342
-
343
- def test_should_accept_update_only_option
344
- @pirate.update(update_only_ship_attributes: { id: @pirate.ship.id, name: 'Mayflower' })
345
- end
346
-
347
- def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
348
- @ship.delete
349
-
350
- @pirate.reload.update(update_only_ship_attributes: { name: 'Mayflower' })
351
-
352
- assert_not_nil @pirate.ship
353
- end
354
-
355
- def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
356
- @ship.delete
357
- @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning')
358
-
359
- @pirate.update(update_only_ship_attributes: { name: 'Mayflower' })
360
-
361
- assert_equal 'Mayflower', @ship.reload.name
362
- assert_equal @ship, @pirate.reload.ship
363
- end
364
-
365
- def test_should_update_existing_when_update_only_is_true_and_id_is_given
366
- @ship.delete
367
- @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning')
368
-
369
- @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id })
370
-
371
- assert_equal 'Mayflower', @ship.reload.name
372
- assert_equal @ship, @pirate.reload.ship
373
- end
374
-
375
- def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
376
- Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => true
377
- @ship.delete
378
- @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning')
379
-
380
- @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id, _destroy: true })
381
-
382
- assert_nil @pirate.reload.ship
383
- assert_raise(ActiveRecord::RecordNotFound) { Ship.find(@ship.id) }
384
-
385
- Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => false
386
- end
387
-
388
- end
389
-
390
- class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
391
- def setup
392
- @ship = Ship.new(:name => 'Nights Dirty Lightning')
393
- @pirate = @ship.build_pirate(:catchphrase => 'Aye')
394
- @ship.save!
395
- end
396
-
397
- def test_should_define_an_attribute_writer_method_for_the_association
398
- assert_respond_to @ship, :pirate_attributes=
399
- end
400
-
401
- def test_should_build_a_new_record_if_there_is_no_id
402
- @pirate.destroy
403
- @ship.reload.pirate_attributes = { :catchphrase => 'Arr' }
404
-
405
- assert !@ship.pirate.persisted?
406
- assert_equal 'Arr', @ship.pirate.catchphrase
407
- end
408
-
409
- def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy
410
- @pirate.destroy
411
- @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' }
412
-
413
- assert_nil @ship.pirate
414
- end
415
-
416
- def test_should_not_build_a_new_record_if_a_reject_if_proc_returns_false
417
- @pirate.destroy
418
- @ship.reload.pirate_attributes = {}
419
-
420
- assert_nil @ship.pirate
421
- end
422
-
423
- def test_should_replace_an_existing_record_if_there_is_no_id
424
- @ship.reload.pirate_attributes = { :catchphrase => 'Arr' }
425
-
426
- assert !@ship.pirate.persisted?
427
- assert_equal 'Arr', @ship.pirate.catchphrase
428
- assert_equal 'Aye', @pirate.catchphrase
429
- end
430
-
431
- def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy
432
- @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' }
433
-
434
- assert_equal @pirate, @ship.pirate
435
- assert_equal 'Aye', @ship.pirate.catchphrase
436
- end
437
-
438
- def test_should_modify_an_existing_record_if_there_is_a_matching_id
439
- @ship.reload.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' }
440
-
441
- assert_equal @pirate, @ship.pirate
442
- assert_equal 'Arr', @ship.pirate.catchphrase
443
- end
444
-
445
- def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
446
- exception = assert_raise ActiveRecord::RecordNotFound do
447
- @ship.pirate_attributes = { :id => 1234567890 }
448
- end
449
- assert_equal "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}", exception.message
450
- end
451
-
452
- def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
453
- @ship.reload.pirate_attributes = { 'id' => @pirate.id, 'catchphrase' => 'Arr' }
454
-
455
- assert_equal @pirate, @ship.pirate
456
- assert_equal 'Arr', @ship.pirate.catchphrase
457
- end
458
-
459
- def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id
460
- @pirate.stubs(:id).returns('ABC1X')
461
- @ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' }
462
-
463
- assert_equal 'Arr', @ship.pirate.catchphrase
464
- end
465
-
466
- def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy
467
- @ship.pirate.destroy
468
- [1, '1', true, 'true'].each do |truth|
469
- pirate = @ship.reload.create_pirate(catchphrase: 'Arr')
470
- @ship.update(pirate_attributes: { id: pirate.id, _destroy: truth })
471
- assert_raise(ActiveRecord::RecordNotFound) { pirate.reload }
472
- end
473
- end
474
-
475
- def test_should_unset_association_when_an_existing_record_is_destroyed
476
- original_pirate_id = @ship.pirate.id
477
- @ship.update! pirate_attributes: { id: @ship.pirate.id, _destroy: true }
478
-
479
- assert_empty Pirate.where(id: original_pirate_id)
480
- assert_nil @ship.pirate_id
481
- assert_nil @ship.pirate
482
-
483
- @ship.reload
484
- assert_empty Pirate.where(id: original_pirate_id)
485
- assert_nil @ship.pirate_id
486
- assert_nil @ship.pirate
487
- end
488
-
489
- def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
490
- [nil, '0', 0, 'false', false].each do |not_truth|
491
- @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: not_truth })
492
- assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
493
- end
494
- end
495
-
496
- def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
497
- Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
498
-
499
- @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' })
500
- assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
501
- ensure
502
- Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
503
- end
504
-
505
- def test_should_work_with_update_as_well
506
- @ship.update({ name: 'Mister Pablo', pirate_attributes: { catchphrase: 'Arr' } })
507
- @ship.reload
508
-
509
- assert_equal 'Mister Pablo', @ship.name
510
- assert_equal 'Arr', @ship.pirate.catchphrase
511
- end
512
-
513
- def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
514
- pirate = @ship.pirate
515
-
516
- @ship.attributes = { :pirate_attributes => { :id => pirate.id, '_destroy' => true } }
517
- assert_nothing_raised(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) }
518
- @ship.save
519
- assert_raise(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) }
520
- end
521
-
522
- def test_should_automatically_enable_autosave_on_the_association
523
- assert Ship.reflect_on_association(:pirate).options[:autosave]
524
- end
525
-
526
- def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
527
- @pirate.delete
528
- @ship.reload.attributes = { :update_only_pirate_attributes => { :catchphrase => 'Arr' } }
529
-
530
- assert !@ship.update_only_pirate.persisted?
531
- end
532
-
533
- def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
534
- @pirate.delete
535
- @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye')
536
-
537
- @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr' })
538
- assert_equal 'Arr', @pirate.reload.catchphrase
539
- assert_equal @pirate, @ship.reload.update_only_pirate
540
- end
541
-
542
- def test_should_update_existing_when_update_only_is_true_and_id_is_given
543
- @pirate.delete
544
- @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye')
545
-
546
- @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id })
547
-
548
- assert_equal 'Arr', @pirate.reload.catchphrase
549
- assert_equal @pirate, @ship.reload.update_only_pirate
550
- end
551
-
552
- def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
553
- Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => true
554
- @pirate.delete
555
- @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye')
556
-
557
- @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id, _destroy: true })
558
-
559
- assert_raise(ActiveRecord::RecordNotFound) { @pirate.reload }
560
-
561
- Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => false
562
- end
563
- end
564
-
565
- module NestedAttributesOnACollectionAssociationTests
566
- def test_should_define_an_attribute_writer_method_for_the_association
567
- assert_respond_to @pirate, association_setter
568
- end
569
-
570
- def test_should_save_only_one_association_on_create
571
- pirate = Pirate.create!({
572
- :catchphrase => 'Arr',
573
- association_getter => { 'foo' => { :name => 'Grace OMalley' } }
574
- })
575
-
576
- assert_equal 1, pirate.reload.send(@association_name).count
577
- end
578
-
579
- def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_associated_models
580
- @alternate_params[association_getter].stringify_keys!
581
- @pirate.update @alternate_params
582
- assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
583
- end
584
-
585
- def test_should_take_an_array_and_assign_the_attributes_to_the_associated_models
586
- @pirate.send(association_setter, @alternate_params[association_getter].values)
587
- @pirate.save
588
- assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
589
- end
590
-
591
- def test_should_also_work_with_a_HashWithIndifferentAccess
592
- @pirate.send(association_setter, ActiveSupport::HashWithIndifferentAccess.new('foo' => ActiveSupport::HashWithIndifferentAccess.new(:id => @child_1.id, :name => 'Grace OMalley')))
593
- @pirate.save
594
- assert_equal 'Grace OMalley', @child_1.reload.name
595
- end
596
-
597
- def test_should_take_a_hash_and_assign_the_attributes_to_the_associated_models
598
- @pirate.attributes = @alternate_params
599
- assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
600
- assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name
601
- end
602
-
603
- def test_should_not_load_association_when_updating_existing_records
604
- @pirate.reload
605
- @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
606
- assert ! @pirate.send(@association_name).loaded?
607
-
608
- @pirate.save
609
- assert ! @pirate.send(@association_name).loaded?
610
- assert_equal 'Grace OMalley', @child_1.reload.name
611
- end
612
-
613
- def test_should_not_overwrite_unsaved_updates_when_loading_association
614
- @pirate.reload
615
- @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
616
- assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name
617
- end
618
-
619
- def test_should_preserve_order_when_not_overwriting_unsaved_updates
620
- @pirate.reload
621
- @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
622
- assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id
623
- end
624
-
625
- def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates
626
- @pirate.reload
627
- record = @pirate.class.reflect_on_association(@association_name).klass.new(name: 'Grace OMalley')
628
- @pirate.send(@association_name) << record
629
- record.save!
630
- @pirate.send(@association_name).last.update!(name: 'Polly')
631
- assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name
632
- end
633
-
634
- def test_should_not_remove_scheduled_destroys_when_loading_association
635
- @pirate.reload
636
- @pirate.send(association_setter, [{ :id => @child_1.id, :_destroy => '1' }])
637
- assert @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.marked_for_destruction?
638
- end
639
-
640
- def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
641
- @child_1.stubs(:id).returns('ABC1X')
642
- @child_2.stubs(:id).returns('ABC2X')
643
-
644
- @pirate.attributes = {
645
- association_getter => [
646
- { :id => @child_1.id, :name => 'Grace OMalley' },
647
- { :id => @child_2.id, :name => 'Privateers Greed' }
648
- ]
649
- }
650
-
651
- assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name]
652
- end
653
-
654
- def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
655
- exception = assert_raise ActiveRecord::RecordNotFound do
656
- @pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
657
- end
658
- assert_equal "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
659
- end
660
-
661
- def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing
662
- @pirate.send(@association_name).destroy_all
663
- @pirate.reload.attributes = {
664
- association_getter => { 'foo' => { :name => 'Grace OMalley' }, 'bar' => { :name => 'Privateers Greed' }}
665
- }
666
-
667
- assert !@pirate.send(@association_name).first.persisted?
668
- assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
669
-
670
- assert !@pirate.send(@association_name).last.persisted?
671
- assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name
672
- end
673
-
674
- def test_should_not_assign_destroy_key_to_a_record
675
- assert_nothing_raised ActiveRecord::UnknownAttributeError do
676
- @pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }})
677
- end
678
- end
679
-
680
- def test_should_ignore_new_associated_records_with_truthy_destroy_attribute
681
- @pirate.send(@association_name).destroy_all
682
- @pirate.reload.attributes = {
683
- association_getter => {
684
- 'foo' => { :name => 'Grace OMalley' },
685
- 'bar' => { :name => 'Privateers Greed', '_destroy' => '1' }
686
- }
687
- }
688
-
689
- assert_equal 1, @pirate.send(@association_name).length
690
- assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
691
- end
692
-
693
- def test_should_ignore_new_associated_records_if_a_reject_if_proc_returns_false
694
- @alternate_params[association_getter]['baz'] = {}
695
- assert_no_difference("@pirate.send(@association_name).count") do
696
- @pirate.attributes = @alternate_params
697
- end
698
- end
699
-
700
- def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models
701
- attributes = {}
702
- attributes['123726353'] = { :name => 'Grace OMalley' }
703
- attributes['2'] = { :name => 'Privateers Greed' } # 2 is lower then 123726353
704
- @pirate.send(association_setter, attributes)
705
-
706
- assert_equal ['Posideons Killer', 'Killer bandita Dionne', 'Privateers Greed', 'Grace OMalley'].to_set, @pirate.send(@association_name).map(&:name).to_set
707
- end
708
-
709
- def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed
710
- assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) }
711
- assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) }
712
-
713
- exception = assert_raise ArgumentError do
714
- @pirate.send(association_setter, "foo")
715
- end
716
- assert_equal 'Hash or Array expected, got String ("foo")', exception.message
717
- end
718
-
719
- def test_should_work_with_update_as_well
720
- @pirate.update(catchphrase: 'Arr',
721
- association_getter => { 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }})
722
-
723
- assert_equal 'Grace OMalley', @child_1.reload.name
724
- end
725
-
726
- def test_should_update_existing_records_and_add_new_ones_that_have_no_id
727
- @alternate_params[association_getter]['baz'] = { name: 'Buccaneers Servant' }
728
- assert_difference('@pirate.send(@association_name).count', +1) do
729
- @pirate.update @alternate_params
730
- end
731
- assert_equal ['Grace OMalley', 'Privateers Greed', 'Buccaneers Servant'].to_set, @pirate.reload.send(@association_name).map(&:name).to_set
732
- end
733
-
734
- def test_should_be_possible_to_destroy_a_record
735
- ['1', 1, 'true', true].each do |true_variable|
736
- record = @pirate.reload.send(@association_name).create!(:name => 'Grace OMalley')
737
- @pirate.send(association_setter,
738
- @alternate_params[association_getter].merge('baz' => { :id => record.id, '_destroy' => true_variable })
739
- )
740
-
741
- assert_difference('@pirate.send(@association_name).count', -1) do
742
- @pirate.save
743
- end
744
- end
745
- end
746
-
747
- def test_should_not_destroy_the_associated_model_with_a_non_truthy_argument
748
- [nil, '', '0', 0, 'false', false].each do |false_variable|
749
- @alternate_params[association_getter]['foo']['_destroy'] = false_variable
750
- assert_no_difference('@pirate.send(@association_name).count') do
751
- @pirate.update(@alternate_params)
752
- end
753
- end
754
- end
755
-
756
- def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
757
- assert_no_difference('@pirate.send(@association_name).count') do
758
- @pirate.send(association_setter, @alternate_params[association_getter].merge('baz' => { :id => @child_1.id, '_destroy' => true }))
759
- end
760
- assert_difference('@pirate.send(@association_name).count', -1) { @pirate.save }
761
- end
762
-
763
- def test_should_automatically_enable_autosave_on_the_association
764
- assert Pirate.reflect_on_association(@association_name).options[:autosave]
765
- end
766
-
767
- def test_validate_presence_of_parent_works_with_inverse_of
768
- Man.accepts_nested_attributes_for(:interests)
769
- assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of]
770
- assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of]
771
-
772
- repair_validations(Interest) do
773
- Interest.validates_presence_of(:man)
774
- assert_difference 'Man.count' do
775
- assert_difference 'Interest.count', 2 do
776
- man = Man.create!(:name => 'John',
777
- :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}])
778
- assert_equal 2, man.interests.count
779
- end
780
- end
781
- end
782
- end
783
-
784
- def test_can_use_symbols_as_object_identifier
785
- @pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } }
786
- assert_nothing_raised(NoMethodError) { @pirate.save! }
787
- end
788
-
789
- def test_numeric_column_changes_from_zero_to_no_empty_string
790
- Man.accepts_nested_attributes_for(:interests)
791
-
792
- repair_validations(Interest) do
793
- Interest.validates_numericality_of(:zine_id)
794
- man = Man.create(name: 'John')
795
- interest = man.interests.create(topic: 'bar', zine_id: 0)
796
- assert interest.save
797
- assert !man.update({interests_attributes: { id: interest.id, zine_id: 'foo' }})
798
- end
799
- end
800
-
801
- private
802
-
803
- def association_setter
804
- @association_setter ||= "#{@association_name}_attributes=".to_sym
805
- end
806
-
807
- def association_getter
808
- @association_getter ||= "#{@association_name}_attributes".to_sym
809
- end
810
- end
811
-
812
- class TestNestedAttributesOnAHasManyAssociation < ActiveRecord::TestCase
813
- def setup
814
- @association_type = :has_many
815
- @association_name = :birds
816
-
817
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
818
- @pirate.birds.create!(:name => 'Posideons Killer')
819
- @pirate.birds.create!(:name => 'Killer bandita Dionne')
820
-
821
- @child_1, @child_2 = @pirate.birds
822
-
823
- @alternate_params = {
824
- :birds_attributes => {
825
- 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' },
826
- 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' }
827
- }
828
- }
829
- end
830
-
831
- include NestedAttributesOnACollectionAssociationTests
832
- end
833
-
834
- class TestNestedAttributesOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
835
- def setup
836
- @association_type = :has_and_belongs_to_many
837
- @association_name = :parrots
838
-
839
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
840
- @pirate.parrots.create!(:name => 'Posideons Killer')
841
- @pirate.parrots.create!(:name => 'Killer bandita Dionne')
842
-
843
- @child_1, @child_2 = @pirate.parrots
844
-
845
- @alternate_params = {
846
- :parrots_attributes => {
847
- 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' },
848
- 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' }
849
- }
850
- }
851
- end
852
-
853
- include NestedAttributesOnACollectionAssociationTests
854
- end
855
-
856
- module NestedAttributesLimitTests
857
- def teardown
858
- Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
859
- end
860
-
861
- def test_limit_with_less_records
862
- @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Big Big Love' } } }
863
- assert_difference('Parrot.count') { @pirate.save! }
864
- end
865
-
866
- def test_limit_with_number_exact_records
867
- @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, 'bar' => { :name => 'Blown Away' } } }
868
- assert_difference('Parrot.count', 2) { @pirate.save! }
869
- end
870
-
871
- def test_limit_with_exceeding_records
872
- assert_raises(ActiveRecord::NestedAttributes::TooManyRecords) do
873
- @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' },
874
- 'bar' => { :name => 'Blown Away' },
875
- 'car' => { :name => 'The Happening' }} }
876
- end
877
- end
878
- end
879
-
880
- class TestNestedAttributesLimitNumeric < ActiveRecord::TestCase
881
- def setup
882
- Pirate.accepts_nested_attributes_for :parrots, :limit => 2
883
-
884
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
885
- end
886
-
887
- include NestedAttributesLimitTests
888
- end
889
-
890
- class TestNestedAttributesLimitSymbol < ActiveRecord::TestCase
891
- def setup
892
- Pirate.accepts_nested_attributes_for :parrots, :limit => :parrots_limit
893
-
894
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?", :parrots_limit => 2)
895
- end
896
-
897
- include NestedAttributesLimitTests
898
- end
899
-
900
- class TestNestedAttributesLimitProc < ActiveRecord::TestCase
901
- def setup
902
- Pirate.accepts_nested_attributes_for :parrots, :limit => proc { 2 }
903
-
904
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
905
- end
906
-
907
- include NestedAttributesLimitTests
908
- end
909
-
910
- class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
911
- fixtures :owners, :pets
912
-
913
- def setup
914
- Owner.accepts_nested_attributes_for :pets, :allow_destroy => true
915
-
916
- @owner = owners(:ashley)
917
- @pet1, @pet2 = pets(:chew), pets(:mochi)
918
-
919
- @params = {
920
- :pets_attributes => {
921
- '0' => { :id => @pet1.id, :name => 'Foo' },
922
- '1' => { :id => @pet2.id, :name => 'Bar' }
923
- }
924
- }
925
- end
926
-
927
- def test_should_update_existing_records_with_non_standard_primary_key
928
- @owner.update(@params)
929
- assert_equal ['Foo', 'Bar'], @owner.pets.map(&:name)
930
- end
931
-
932
- def test_attr_accessor_of_child_should_be_value_provided_during_update
933
- @owner = owners(:ashley)
934
- @pet1 = pets(:chew)
935
- attributes = {:pets_attributes => { "1"=> { :id => @pet1.id,
936
- :name => "Foo2",
937
- :current_user => "John",
938
- :_destroy=>true }}}
939
- @owner.update(attributes)
940
- assert_equal 'John', Pet.after_destroy_output
941
- end
942
-
943
- end
944
-
945
- class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
946
- self.use_transactional_fixtures = false unless supports_savepoints?
947
-
948
- def setup
949
- @pirate = Pirate.create!(:catchphrase => "My baby takes tha mornin' train!")
950
- @ship = @pirate.create_ship(:name => "The good ship Dollypop")
951
- @part = @ship.parts.create!(:name => "Mast")
952
- @trinket = @part.trinkets.create!(:name => "Necklace")
953
- end
954
-
955
- test "when great-grandchild changed in memory, saving parent should save great-grandchild" do
956
- @trinket.name = "changed"
957
- @pirate.save
958
- assert_equal "changed", @trinket.reload.name
959
- end
960
-
961
- test "when great-grandchild changed via attributes, saving parent should save great-grandchild" do
962
- @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]}}
963
- @pirate.save
964
- assert_equal "changed", @trinket.reload.name
965
- end
966
-
967
- test "when great-grandchild marked_for_destruction via attributes, saving parent should destroy great-grandchild" do
968
- @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]}}
969
- assert_difference('@part.trinkets.count', -1) { @pirate.save }
970
- end
971
-
972
- test "when great-grandchild added via attributes, saving parent should create great-grandchild" do
973
- @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]}}
974
- assert_difference('@part.trinkets.count', 1) { @pirate.save }
975
- end
976
-
977
- test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do
978
- @trinket.name = "changed"
979
- Ship.create!(:pirate => @pirate, :name => "The Black Rock")
980
- ShipPart.create!(:ship => @ship, :name => "Stern")
981
- assert_no_queries { @pirate.valid? }
982
- end
983
- end
984
-
985
- class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
986
- self.use_transactional_fixtures = false unless supports_savepoints?
987
-
988
- def setup
989
- @ship = Ship.create!(:name => "The good ship Dollypop")
990
- @part = @ship.parts.create!(:name => "Mast")
991
- @trinket = @part.trinkets.create!(:name => "Necklace")
992
- end
993
-
994
- test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do
995
- @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}]
996
- assert_equal 1, @ship.association(:parts).target.size
997
- assert_equal 'Deck', @ship.parts[0].name
998
- end
999
-
1000
- test "if association is not loaded and child doesn't change and I am saving a grandchild then in memory record should be used" do
1001
- @ship.parts_attributes=[{:id => @part.id,:trinkets_attributes =>[{:id => @trinket.id, :name => 'Ruby'}]}]
1002
- assert_equal 1, @ship.association(:parts).target.size
1003
- assert_equal 'Mast', @ship.parts[0].name
1004
- assert_no_difference("@ship.parts[0].association(:trinkets).target.size") do
1005
- @ship.parts[0].association(:trinkets).target.size
1006
- end
1007
- assert_equal 'Ruby', @ship.parts[0].trinkets[0].name
1008
- @ship.save
1009
- assert_equal 'Ruby', @ship.parts[0].trinkets[0].name
1010
- end
1011
-
1012
- test "when grandchild changed in memory, saving parent should save grandchild" do
1013
- @trinket.name = "changed"
1014
- @ship.save
1015
- assert_equal "changed", @trinket.reload.name
1016
- end
1017
-
1018
- test "when grandchild changed via attributes, saving parent should save grandchild" do
1019
- @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]}
1020
- @ship.save
1021
- assert_equal "changed", @trinket.reload.name
1022
- end
1023
-
1024
- test "when grandchild marked_for_destruction via attributes, saving parent should destroy grandchild" do
1025
- @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]}
1026
- assert_difference('@part.trinkets.count', -1) { @ship.save }
1027
- end
1028
-
1029
- test "when grandchild added via attributes, saving parent should create grandchild" do
1030
- @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]}
1031
- assert_difference('@part.trinkets.count', 1) { @ship.save }
1032
- end
1033
-
1034
- test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do
1035
- @trinket.name = "changed"
1036
- Ship.create!(:name => "The Black Rock")
1037
- ShipPart.create!(:ship => @ship, :name => "Stern")
1038
- assert_no_queries { @ship.valid? }
1039
- end
1040
-
1041
- test "circular references do not perform unnecessary queries" do
1042
- ship = Ship.new(name: "The Black Rock")
1043
- part = ship.parts.build(name: "Stern")
1044
- ship.treasures.build(looter: part)
1045
-
1046
- assert_queries 3 do
1047
- ship.save!
1048
- end
1049
- end
1050
-
1051
- test "nested singular associations are validated" do
1052
- part = ShipPart.new(name: "Stern", ship_attributes: { name: nil })
1053
-
1054
- assert_not part.valid?
1055
- assert_equal ["Ship name can't be blank"], part.errors.full_messages
1056
- end
1057
- end
1
+ require "cases/helper"
2
+ require "models/pirate"
3
+ require "models/ship"
4
+ require "models/ship_part"
5
+ require "models/bird"
6
+ require "models/parrot"
7
+ require "models/treasure"
8
+ require "models/man"
9
+ require "models/interest"
10
+ require "models/owner"
11
+ require "models/pet"
12
+ require 'active_support/hash_with_indifferent_access'
13
+
14
+ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
15
+ teardown do
16
+ Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?)
17
+ end
18
+
19
+ def test_base_should_have_an_empty_nested_attributes_options
20
+ assert_equal Hash.new, ActiveRecord::Base.nested_attributes_options
21
+ end
22
+
23
+ def test_should_add_a_proc_to_nested_attributes_options
24
+ assert_equal ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC,
25
+ Pirate.nested_attributes_options[:birds_with_reject_all_blank][:reject_if]
26
+
27
+ [:parrots, :birds].each do |name|
28
+ assert_instance_of Proc, Pirate.nested_attributes_options[name][:reject_if]
29
+ end
30
+ end
31
+
32
+ def test_should_not_build_a_new_record_using_reject_all_even_if_destroy_is_given
33
+ pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
34
+ pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => '', :_destroy => '0'}]
35
+ pirate.save!
36
+
37
+ assert pirate.birds_with_reject_all_blank.empty?
38
+ end
39
+
40
+ def test_should_not_build_a_new_record_if_reject_all_blank_returns_false
41
+ pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
42
+ pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => ''}]
43
+ pirate.save!
44
+
45
+ assert pirate.birds_with_reject_all_blank.empty?
46
+ end
47
+
48
+ def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false
49
+ pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
50
+ pirate.birds_with_reject_all_blank_attributes = [{:name => 'Tweetie', :color => ''}]
51
+ pirate.save!
52
+
53
+ assert_equal 1, pirate.birds_with_reject_all_blank.count
54
+ assert_equal 'Tweetie', pirate.birds_with_reject_all_blank.first.name
55
+ end
56
+
57
+ def test_should_raise_an_ArgumentError_for_non_existing_associations
58
+ exception = assert_raise ArgumentError do
59
+ Pirate.accepts_nested_attributes_for :honesty
60
+ end
61
+ assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message
62
+ end
63
+
64
+ def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes
65
+ exception = assert_raise ActiveModel::UnknownAttributeError do
66
+ Pirate.new(:ship_attributes => { :sail => true })
67
+ end
68
+ assert_equal "unknown attribute 'sail' for Ship.", exception.message
69
+ end
70
+
71
+ def test_should_disable_allow_destroy_by_default
72
+ Pirate.accepts_nested_attributes_for :ship
73
+
74
+ pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
75
+ ship = pirate.create_ship(name: 'Nights Dirty Lightning')
76
+
77
+ pirate.update(ship_attributes: { '_destroy' => true, :id => ship.id })
78
+
79
+ assert_nothing_raised { pirate.ship.reload }
80
+ end
81
+
82
+ def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked_for_destruction
83
+ ship = Ship.create!(:name => 'Nights Dirty Lightning')
84
+ assert !ship._destroy
85
+ ship.mark_for_destruction
86
+ assert ship._destroy
87
+ end
88
+
89
+ def test_reject_if_method_without_arguments
90
+ Pirate.accepts_nested_attributes_for :ship, :reject_if => :new_record?
91
+
92
+ pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
93
+ pirate.ship_attributes = { :name => 'Black Pearl' }
94
+ assert_no_difference('Ship.count') { pirate.save! }
95
+ end
96
+
97
+ def test_reject_if_method_with_arguments
98
+ Pirate.accepts_nested_attributes_for :ship, :reject_if => :reject_empty_ships_on_create
99
+
100
+ pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
101
+ pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
102
+ assert_no_difference('Ship.count') { pirate.save! }
103
+
104
+ # pirate.reject_empty_ships_on_create returns false for saved pirate records
105
+ # in the previous step note that pirate gets saved but ship fails
106
+ pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
107
+ assert_difference('Ship.count') { pirate.save! }
108
+ end
109
+
110
+ def test_reject_if_with_indifferent_keys
111
+ Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:name].blank? }
112
+
113
+ pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
114
+ pirate.ship_attributes = { :name => 'Hello Pearl' }
115
+ assert_difference('Ship.count') { pirate.save! }
116
+ end
117
+
118
+ def test_reject_if_with_a_proc_which_returns_true_always_for_has_one
119
+ Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| true }
120
+ pirate = Pirate.new(catchphrase: "Stop wastin' me time")
121
+ ship = pirate.create_ship(name: 's1')
122
+ pirate.update({ship_attributes: { name: 's2', id: ship.id } })
123
+ assert_equal 's1', ship.reload.name
124
+ end
125
+
126
+ def test_reuse_already_built_new_record
127
+ pirate = Pirate.new
128
+ ship_built_first = pirate.build_ship
129
+ pirate.ship_attributes = { name: 'Ship 1' }
130
+ assert_equal ship_built_first.object_id, pirate.ship.object_id
131
+ end
132
+
133
+ def test_do_not_allow_assigning_foreign_key_when_reusing_existing_new_record
134
+ pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
135
+ pirate.build_ship
136
+ pirate.ship_attributes = { name: 'Ship 1', pirate_id: pirate.id + 1 }
137
+ assert_equal pirate.id, pirate.ship.pirate_id
138
+ end
139
+
140
+ def test_reject_if_with_a_proc_which_returns_true_always_for_has_many
141
+ Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }
142
+ man = Man.create(name: "John")
143
+ interest = man.interests.create(topic: 'photography')
144
+ man.update({interests_attributes: { topic: 'gardening', id: interest.id } })
145
+ assert_equal 'photography', interest.reload.topic
146
+ end
147
+
148
+ def test_destroy_works_independent_of_reject_if
149
+ Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }, :allow_destroy => true
150
+ man = Man.create(name: "Jon")
151
+ interest = man.interests.create(topic: 'the ladies')
152
+ man.update({interests_attributes: { _destroy: "1", id: interest.id } })
153
+ assert man.reload.interests.empty?
154
+ end
155
+
156
+ def test_reject_if_is_not_short_circuited_if_allow_destroy_is_false
157
+ Pirate.accepts_nested_attributes_for :ship, reject_if: ->(a) { a[:name] == "The Golden Hind" }, allow_destroy: false
158
+
159
+ pirate = Pirate.create!(catchphrase: "Stop wastin' me time", ship_attributes: { name: "White Pearl", _destroy: "1" })
160
+ assert_equal "White Pearl", pirate.reload.ship.name
161
+
162
+ pirate.update!(ship_attributes: { id: pirate.ship.id, name: "The Golden Hind", _destroy: "1" })
163
+ assert_equal "White Pearl", pirate.reload.ship.name
164
+
165
+ pirate.update!(ship_attributes: { id: pirate.ship.id, name: "Black Pearl", _destroy: "1" })
166
+ assert_equal "Black Pearl", pirate.reload.ship.name
167
+ end
168
+
169
+ def test_has_many_association_updating_a_single_record
170
+ Man.accepts_nested_attributes_for(:interests)
171
+ man = Man.create(name: 'John')
172
+ interest = man.interests.create(topic: 'photography')
173
+ man.update({interests_attributes: {topic: 'gardening', id: interest.id}})
174
+ assert_equal 'gardening', interest.reload.topic
175
+ end
176
+
177
+ def test_reject_if_with_blank_nested_attributes_id
178
+ # When using a select list to choose an existing 'ship' id, with include_blank: true
179
+ Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:id].blank? }
180
+
181
+ pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
182
+ pirate.ship_attributes = { :id => "" }
183
+ assert_nothing_raised { pirate.save! }
184
+ end
185
+
186
+ def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record
187
+ Man.accepts_nested_attributes_for(:interests)
188
+ man = Man.create(:name => "John")
189
+ interest = man.interests.create :topic => 'gardening'
190
+ man = Man.find man.id
191
+ man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}]
192
+ assert_equal man.interests.first.topic, man.interests[0].topic
193
+ end
194
+
195
+ def test_allows_class_to_override_setter_and_call_super
196
+ mean_pirate_class = Class.new(Pirate) do
197
+ accepts_nested_attributes_for :parrot
198
+ def parrot_attributes=(attrs)
199
+ super(attrs.merge(:color => "blue"))
200
+ end
201
+ end
202
+ mean_pirate = mean_pirate_class.new
203
+ mean_pirate.parrot_attributes = { :name => "James" }
204
+ assert_equal "James", mean_pirate.parrot.name
205
+ assert_equal "blue", mean_pirate.parrot.color
206
+ end
207
+
208
+ def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses
209
+ Pirate.accepts_nested_attributes_for(:parrot)
210
+
211
+ mean_pirate_class = Class.new(Pirate) do
212
+ accepts_nested_attributes_for :parrot
213
+ end
214
+ mean_pirate = mean_pirate_class.new
215
+ mean_pirate.parrot_attributes = { :name => "James" }
216
+ assert_equal "James", mean_pirate.parrot.name
217
+ end
218
+ end
219
+
220
+ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
221
+ def setup
222
+ @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
223
+ @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
224
+ end
225
+
226
+ def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to
227
+ exception = assert_raise ArgumentError do
228
+ Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"})
229
+ end
230
+ assert_equal "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?", exception.message
231
+ end
232
+
233
+ def test_should_define_an_attribute_writer_method_for_the_association
234
+ assert_respond_to @pirate, :ship_attributes=
235
+ end
236
+
237
+ def test_should_build_a_new_record_if_there_is_no_id
238
+ @ship.destroy
239
+ @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' }
240
+
241
+ assert !@pirate.ship.persisted?
242
+ assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
243
+ end
244
+
245
+ def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy
246
+ @ship.destroy
247
+ @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' }
248
+
249
+ assert_nil @pirate.ship
250
+ end
251
+
252
+ def test_should_not_build_a_new_record_if_a_reject_if_proc_returns_false
253
+ @ship.destroy
254
+ @pirate.reload.ship_attributes = {}
255
+
256
+ assert_nil @pirate.ship
257
+ end
258
+
259
+ def test_should_replace_an_existing_record_if_there_is_no_id
260
+ @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' }
261
+
262
+ assert !@pirate.ship.persisted?
263
+ assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
264
+ assert_equal 'Nights Dirty Lightning', @ship.name
265
+ end
266
+
267
+ def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy
268
+ @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' }
269
+
270
+ assert_equal @ship, @pirate.ship
271
+ assert_equal 'Nights Dirty Lightning', @pirate.ship.name
272
+ end
273
+
274
+ def test_should_modify_an_existing_record_if_there_is_a_matching_id
275
+ @pirate.reload.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' }
276
+
277
+ assert_equal @ship, @pirate.ship
278
+ assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
279
+ end
280
+
281
+ def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
282
+ exception = assert_raise ActiveRecord::RecordNotFound do
283
+ @pirate.ship_attributes = { :id => 1234567890 }
284
+ end
285
+ assert_equal "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
286
+ end
287
+
288
+ def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
289
+ @pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' }
290
+
291
+ assert_equal @ship, @pirate.ship
292
+ assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
293
+ end
294
+
295
+ def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id
296
+ @ship.stub(:id, 'ABC1X') do
297
+ @pirate.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' }
298
+
299
+ assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
300
+ end
301
+ end
302
+
303
+ def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy
304
+ @pirate.ship.destroy
305
+
306
+ [1, '1', true, 'true'].each do |truth|
307
+ ship = @pirate.reload.create_ship(name: 'Mister Pablo')
308
+ @pirate.update(ship_attributes: { id: ship.id, _destroy: truth })
309
+
310
+ assert_nil @pirate.reload.ship
311
+ assert_raise(ActiveRecord::RecordNotFound) { Ship.find(ship.id) }
312
+ end
313
+ end
314
+
315
+ def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
316
+ [nil, '0', 0, 'false', false].each do |not_truth|
317
+ @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: not_truth })
318
+
319
+ assert_equal @ship, @pirate.reload.ship
320
+ end
321
+ end
322
+
323
+ def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
324
+ Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc(&:empty?)
325
+
326
+ @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: '1' })
327
+
328
+ assert_equal @ship, @pirate.reload.ship
329
+
330
+ Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?)
331
+ end
332
+
333
+ def test_should_also_work_with_a_HashWithIndifferentAccess
334
+ @pirate.ship_attributes = ActiveSupport::HashWithIndifferentAccess.new(:id => @ship.id, :name => 'Davy Jones Gold Dagger')
335
+
336
+ assert @pirate.ship.persisted?
337
+ assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
338
+ end
339
+
340
+ def test_should_work_with_update_as_well
341
+ @pirate.update({ catchphrase: 'Arr', ship_attributes: { id: @ship.id, name: 'Mister Pablo' } })
342
+ @pirate.reload
343
+
344
+ assert_equal 'Arr', @pirate.catchphrase
345
+ assert_equal 'Mister Pablo', @pirate.ship.name
346
+ end
347
+
348
+ def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
349
+ @pirate.attributes = { :ship_attributes => { :id => @ship.id, :_destroy => '1' } }
350
+
351
+ assert !@pirate.ship.destroyed?
352
+ assert @pirate.ship.marked_for_destruction?
353
+
354
+ @pirate.save
355
+
356
+ assert @pirate.ship.destroyed?
357
+ assert_nil @pirate.reload.ship
358
+ end
359
+
360
+ def test_should_automatically_enable_autosave_on_the_association
361
+ assert Pirate.reflect_on_association(:ship).options[:autosave]
362
+ end
363
+
364
+ def test_should_accept_update_only_option
365
+ @pirate.update(update_only_ship_attributes: { id: @pirate.ship.id, name: 'Mayflower' })
366
+ end
367
+
368
+ def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
369
+ @ship.delete
370
+
371
+ @pirate.reload.update(update_only_ship_attributes: { name: 'Mayflower' })
372
+
373
+ assert_not_nil @pirate.ship
374
+ end
375
+
376
+ def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
377
+ @ship.delete
378
+ @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning')
379
+
380
+ @pirate.update(update_only_ship_attributes: { name: 'Mayflower' })
381
+
382
+ assert_equal 'Mayflower', @ship.reload.name
383
+ assert_equal @ship, @pirate.reload.ship
384
+ end
385
+
386
+ def test_should_update_existing_when_update_only_is_true_and_id_is_given
387
+ @ship.delete
388
+ @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning')
389
+
390
+ @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id })
391
+
392
+ assert_equal 'Mayflower', @ship.reload.name
393
+ assert_equal @ship, @pirate.reload.ship
394
+ end
395
+
396
+ def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
397
+ Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => true
398
+ @ship.delete
399
+ @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning')
400
+
401
+ @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id, _destroy: true })
402
+
403
+ assert_nil @pirate.reload.ship
404
+ assert_raise(ActiveRecord::RecordNotFound) { Ship.find(@ship.id) }
405
+
406
+ Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => false
407
+ end
408
+
409
+ end
410
+
411
+ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
412
+ def setup
413
+ @ship = Ship.new(:name => 'Nights Dirty Lightning')
414
+ @pirate = @ship.build_pirate(:catchphrase => 'Aye')
415
+ @ship.save!
416
+ end
417
+
418
+ def test_should_define_an_attribute_writer_method_for_the_association
419
+ assert_respond_to @ship, :pirate_attributes=
420
+ end
421
+
422
+ def test_should_build_a_new_record_if_there_is_no_id
423
+ @pirate.destroy
424
+ @ship.reload.pirate_attributes = { :catchphrase => 'Arr' }
425
+
426
+ assert !@ship.pirate.persisted?
427
+ assert_equal 'Arr', @ship.pirate.catchphrase
428
+ end
429
+
430
+ def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy
431
+ @pirate.destroy
432
+ @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' }
433
+
434
+ assert_nil @ship.pirate
435
+ end
436
+
437
+ def test_should_not_build_a_new_record_if_a_reject_if_proc_returns_false
438
+ @pirate.destroy
439
+ @ship.reload.pirate_attributes = {}
440
+
441
+ assert_nil @ship.pirate
442
+ end
443
+
444
+ def test_should_replace_an_existing_record_if_there_is_no_id
445
+ @ship.reload.pirate_attributes = { :catchphrase => 'Arr' }
446
+
447
+ assert !@ship.pirate.persisted?
448
+ assert_equal 'Arr', @ship.pirate.catchphrase
449
+ assert_equal 'Aye', @pirate.catchphrase
450
+ end
451
+
452
+ def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy
453
+ @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' }
454
+
455
+ assert_equal @pirate, @ship.pirate
456
+ assert_equal 'Aye', @ship.pirate.catchphrase
457
+ end
458
+
459
+ def test_should_modify_an_existing_record_if_there_is_a_matching_id
460
+ @ship.reload.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' }
461
+
462
+ assert_equal @pirate, @ship.pirate
463
+ assert_equal 'Arr', @ship.pirate.catchphrase
464
+ end
465
+
466
+ def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
467
+ exception = assert_raise ActiveRecord::RecordNotFound do
468
+ @ship.pirate_attributes = { :id => 1234567890 }
469
+ end
470
+ assert_equal "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}", exception.message
471
+ end
472
+
473
+ def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
474
+ @ship.reload.pirate_attributes = { 'id' => @pirate.id, 'catchphrase' => 'Arr' }
475
+
476
+ assert_equal @pirate, @ship.pirate
477
+ assert_equal 'Arr', @ship.pirate.catchphrase
478
+ end
479
+
480
+ def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id
481
+ @pirate.stub(:id, 'ABC1X') do
482
+ @ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' }
483
+
484
+ assert_equal 'Arr', @ship.pirate.catchphrase
485
+ end
486
+ end
487
+
488
+ def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy
489
+ @ship.pirate.destroy
490
+ [1, '1', true, 'true'].each do |truth|
491
+ pirate = @ship.reload.create_pirate(catchphrase: 'Arr')
492
+ @ship.update(pirate_attributes: { id: pirate.id, _destroy: truth })
493
+ assert_raise(ActiveRecord::RecordNotFound) { pirate.reload }
494
+ end
495
+ end
496
+
497
+ def test_should_unset_association_when_an_existing_record_is_destroyed
498
+ original_pirate_id = @ship.pirate.id
499
+ @ship.update! pirate_attributes: { id: @ship.pirate.id, _destroy: true }
500
+
501
+ assert_empty Pirate.where(id: original_pirate_id)
502
+ assert_nil @ship.pirate_id
503
+ assert_nil @ship.pirate
504
+
505
+ @ship.reload
506
+ assert_empty Pirate.where(id: original_pirate_id)
507
+ assert_nil @ship.pirate_id
508
+ assert_nil @ship.pirate
509
+ end
510
+
511
+ def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
512
+ [nil, '0', 0, 'false', false].each do |not_truth|
513
+ @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: not_truth })
514
+ assert_nothing_raised { @ship.pirate.reload }
515
+ end
516
+ end
517
+
518
+ def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
519
+ Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc(&:empty?)
520
+
521
+ @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' })
522
+ assert_nothing_raised { @ship.pirate.reload }
523
+ ensure
524
+ Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?)
525
+ end
526
+
527
+ def test_should_work_with_update_as_well
528
+ @ship.update({ name: 'Mister Pablo', pirate_attributes: { catchphrase: 'Arr' } })
529
+ @ship.reload
530
+
531
+ assert_equal 'Mister Pablo', @ship.name
532
+ assert_equal 'Arr', @ship.pirate.catchphrase
533
+ end
534
+
535
+ def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
536
+ pirate = @ship.pirate
537
+
538
+ @ship.attributes = { :pirate_attributes => { :id => pirate.id, '_destroy' => true } }
539
+ assert_nothing_raised { Pirate.find(pirate.id) }
540
+ @ship.save
541
+ assert_raise(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) }
542
+ end
543
+
544
+ def test_should_automatically_enable_autosave_on_the_association
545
+ assert Ship.reflect_on_association(:pirate).options[:autosave]
546
+ end
547
+
548
+ def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
549
+ @pirate.delete
550
+ @ship.reload.attributes = { :update_only_pirate_attributes => { :catchphrase => 'Arr' } }
551
+
552
+ assert !@ship.update_only_pirate.persisted?
553
+ end
554
+
555
+ def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
556
+ @pirate.delete
557
+ @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye')
558
+
559
+ @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr' })
560
+ assert_equal 'Arr', @pirate.reload.catchphrase
561
+ assert_equal @pirate, @ship.reload.update_only_pirate
562
+ end
563
+
564
+ def test_should_update_existing_when_update_only_is_true_and_id_is_given
565
+ @pirate.delete
566
+ @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye')
567
+
568
+ @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id })
569
+
570
+ assert_equal 'Arr', @pirate.reload.catchphrase
571
+ assert_equal @pirate, @ship.reload.update_only_pirate
572
+ end
573
+
574
+ def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
575
+ Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => true
576
+ @pirate.delete
577
+ @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye')
578
+
579
+ @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id, _destroy: true })
580
+
581
+ assert_raise(ActiveRecord::RecordNotFound) { @pirate.reload }
582
+
583
+ Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => false
584
+ end
585
+ end
586
+
587
+ module NestedAttributesOnACollectionAssociationTests
588
+ def test_should_define_an_attribute_writer_method_for_the_association
589
+ assert_respond_to @pirate, association_setter
590
+ end
591
+
592
+ def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes_for_has_many
593
+ exception = assert_raise ActiveModel::UnknownAttributeError do
594
+ @pirate.parrots_attributes = [{ peg_leg: true }]
595
+ end
596
+ assert_equal "unknown attribute 'peg_leg' for Parrot.", exception.message
597
+ end
598
+
599
+ def test_should_save_only_one_association_on_create
600
+ pirate = Pirate.create!({
601
+ :catchphrase => 'Arr',
602
+ association_getter => { 'foo' => { :name => 'Grace OMalley' } }
603
+ })
604
+
605
+ assert_equal 1, pirate.reload.send(@association_name).count
606
+ end
607
+
608
+ def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_associated_models
609
+ @alternate_params[association_getter].stringify_keys!
610
+ @pirate.update @alternate_params
611
+ assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
612
+ end
613
+
614
+ def test_should_take_an_array_and_assign_the_attributes_to_the_associated_models
615
+ @pirate.send(association_setter, @alternate_params[association_getter].values)
616
+ @pirate.save
617
+ assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
618
+ end
619
+
620
+ def test_should_also_work_with_a_HashWithIndifferentAccess
621
+ @pirate.send(association_setter, ActiveSupport::HashWithIndifferentAccess.new('foo' => ActiveSupport::HashWithIndifferentAccess.new(:id => @child_1.id, :name => 'Grace OMalley')))
622
+ @pirate.save
623
+ assert_equal 'Grace OMalley', @child_1.reload.name
624
+ end
625
+
626
+ def test_should_take_a_hash_and_assign_the_attributes_to_the_associated_models
627
+ @pirate.attributes = @alternate_params
628
+ assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
629
+ assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name
630
+ end
631
+
632
+ def test_should_not_load_association_when_updating_existing_records
633
+ @pirate.reload
634
+ @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
635
+ assert ! @pirate.send(@association_name).loaded?
636
+
637
+ @pirate.save
638
+ assert ! @pirate.send(@association_name).loaded?
639
+ assert_equal 'Grace OMalley', @child_1.reload.name
640
+ end
641
+
642
+ def test_should_not_overwrite_unsaved_updates_when_loading_association
643
+ @pirate.reload
644
+ @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
645
+ assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name
646
+ end
647
+
648
+ def test_should_preserve_order_when_not_overwriting_unsaved_updates
649
+ @pirate.reload
650
+ @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
651
+ assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id
652
+ end
653
+
654
+ def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates
655
+ @pirate.reload
656
+ record = @pirate.class.reflect_on_association(@association_name).klass.new(name: 'Grace OMalley')
657
+ @pirate.send(@association_name) << record
658
+ record.save!
659
+ @pirate.send(@association_name).last.update!(name: 'Polly')
660
+ assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name
661
+ end
662
+
663
+ def test_should_not_remove_scheduled_destroys_when_loading_association
664
+ @pirate.reload
665
+ @pirate.send(association_setter, [{ :id => @child_1.id, :_destroy => '1' }])
666
+ assert @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.marked_for_destruction?
667
+ end
668
+
669
+ def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
670
+ @child_1.stub(:id, 'ABC1X') do
671
+ @child_2.stub(:id, 'ABC2X') do
672
+
673
+ @pirate.attributes = {
674
+ association_getter => [
675
+ { :id => @child_1.id, :name => 'Grace OMalley' },
676
+ { :id => @child_2.id, :name => 'Privateers Greed' }
677
+ ]
678
+ }
679
+
680
+ assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name]
681
+ end
682
+ end
683
+ end
684
+
685
+ def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
686
+ exception = assert_raise ActiveRecord::RecordNotFound do
687
+ @pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
688
+ end
689
+ assert_equal "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
690
+ end
691
+
692
+ def test_should_raise_RecordNotFound_if_an_id_belonging_to_a_different_record_is_given
693
+ other_pirate = Pirate.create! catchphrase: 'Ahoy!'
694
+ other_child = other_pirate.send(@association_name).create! name: 'Buccaneers Servant'
695
+
696
+ exception = assert_raise ActiveRecord::RecordNotFound do
697
+ @pirate.attributes = { association_getter => [{ id: other_child.id }] }
698
+ end
699
+ assert_equal "Couldn't find #{@child_1.class.name} with ID=#{other_child.id} for Pirate with ID=#{@pirate.id}", exception.message
700
+ end
701
+
702
+ def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing
703
+ @pirate.send(@association_name).destroy_all
704
+ @pirate.reload.attributes = {
705
+ association_getter => { 'foo' => { :name => 'Grace OMalley' }, 'bar' => { :name => 'Privateers Greed' }}
706
+ }
707
+
708
+ assert !@pirate.send(@association_name).first.persisted?
709
+ assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
710
+
711
+ assert !@pirate.send(@association_name).last.persisted?
712
+ assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name
713
+ end
714
+
715
+ def test_should_not_assign_destroy_key_to_a_record
716
+ assert_nothing_raised do
717
+ @pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }})
718
+ end
719
+ end
720
+
721
+ def test_should_ignore_new_associated_records_with_truthy_destroy_attribute
722
+ @pirate.send(@association_name).destroy_all
723
+ @pirate.reload.attributes = {
724
+ association_getter => {
725
+ 'foo' => { :name => 'Grace OMalley' },
726
+ 'bar' => { :name => 'Privateers Greed', '_destroy' => '1' }
727
+ }
728
+ }
729
+
730
+ assert_equal 1, @pirate.send(@association_name).length
731
+ assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
732
+ end
733
+
734
+ def test_should_ignore_new_associated_records_if_a_reject_if_proc_returns_false
735
+ @alternate_params[association_getter]['baz'] = {}
736
+ assert_no_difference("@pirate.send(@association_name).count") do
737
+ @pirate.attributes = @alternate_params
738
+ end
739
+ end
740
+
741
+ def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models
742
+ attributes = {}
743
+ attributes['123726353'] = { :name => 'Grace OMalley' }
744
+ attributes['2'] = { :name => 'Privateers Greed' } # 2 is lower then 123726353
745
+ @pirate.send(association_setter, attributes)
746
+
747
+ assert_equal ['Posideons Killer', 'Killer bandita Dionne', 'Privateers Greed', 'Grace OMalley'].to_set, @pirate.send(@association_name).map(&:name).to_set
748
+ end
749
+
750
+ def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed
751
+ assert_nothing_raised { @pirate.send(association_setter, {}) }
752
+ assert_nothing_raised { @pirate.send(association_setter, Hash.new) }
753
+
754
+ exception = assert_raise ArgumentError do
755
+ @pirate.send(association_setter, "foo")
756
+ end
757
+ assert_equal 'Hash or Array expected, got String ("foo")', exception.message
758
+ end
759
+
760
+ def test_should_work_with_update_as_well
761
+ @pirate.update(catchphrase: 'Arr',
762
+ association_getter => { 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }})
763
+
764
+ assert_equal 'Grace OMalley', @child_1.reload.name
765
+ end
766
+
767
+ def test_should_update_existing_records_and_add_new_ones_that_have_no_id
768
+ @alternate_params[association_getter]['baz'] = { name: 'Buccaneers Servant' }
769
+ assert_difference('@pirate.send(@association_name).count', +1) do
770
+ @pirate.update @alternate_params
771
+ end
772
+ assert_equal ['Grace OMalley', 'Privateers Greed', 'Buccaneers Servant'].to_set, @pirate.reload.send(@association_name).map(&:name).to_set
773
+ end
774
+
775
+ def test_should_be_possible_to_destroy_a_record
776
+ ['1', 1, 'true', true].each do |true_variable|
777
+ record = @pirate.reload.send(@association_name).create!(:name => 'Grace OMalley')
778
+ @pirate.send(association_setter,
779
+ @alternate_params[association_getter].merge('baz' => { :id => record.id, '_destroy' => true_variable })
780
+ )
781
+
782
+ assert_difference('@pirate.send(@association_name).count', -1) do
783
+ @pirate.save
784
+ end
785
+ end
786
+ end
787
+
788
+ def test_should_not_destroy_the_associated_model_with_a_non_truthy_argument
789
+ [nil, '', '0', 0, 'false', false].each do |false_variable|
790
+ @alternate_params[association_getter]['foo']['_destroy'] = false_variable
791
+ assert_no_difference('@pirate.send(@association_name).count') do
792
+ @pirate.update(@alternate_params)
793
+ end
794
+ end
795
+ end
796
+
797
+ def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
798
+ assert_no_difference('@pirate.send(@association_name).count') do
799
+ @pirate.send(association_setter, @alternate_params[association_getter].merge('baz' => { :id => @child_1.id, '_destroy' => true }))
800
+ end
801
+ assert_difference('@pirate.send(@association_name).count', -1) { @pirate.save }
802
+ end
803
+
804
+ def test_should_automatically_enable_autosave_on_the_association
805
+ assert Pirate.reflect_on_association(@association_name).options[:autosave]
806
+ end
807
+
808
+ def test_validate_presence_of_parent_works_with_inverse_of
809
+ Man.accepts_nested_attributes_for(:interests)
810
+ assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of]
811
+ assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of]
812
+
813
+ repair_validations(Interest) do
814
+ Interest.validates_presence_of(:man)
815
+ assert_difference 'Man.count' do
816
+ assert_difference 'Interest.count', 2 do
817
+ man = Man.create!(:name => 'John',
818
+ :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}])
819
+ assert_equal 2, man.interests.count
820
+ end
821
+ end
822
+ end
823
+ end
824
+
825
+ def test_can_use_symbols_as_object_identifier
826
+ @pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } }
827
+ assert_nothing_raised { @pirate.save! }
828
+ end
829
+
830
+ def test_numeric_column_changes_from_zero_to_no_empty_string
831
+ Man.accepts_nested_attributes_for(:interests)
832
+
833
+ repair_validations(Interest) do
834
+ Interest.validates_numericality_of(:zine_id)
835
+ man = Man.create(name: 'John')
836
+ interest = man.interests.create(topic: 'bar', zine_id: 0)
837
+ assert interest.save
838
+ assert !man.update({interests_attributes: { id: interest.id, zine_id: 'foo' }})
839
+ end
840
+ end
841
+
842
+ private
843
+
844
+ def association_setter
845
+ @association_setter ||= "#{@association_name}_attributes=".to_sym
846
+ end
847
+
848
+ def association_getter
849
+ @association_getter ||= "#{@association_name}_attributes".to_sym
850
+ end
851
+ end
852
+
853
+ class TestNestedAttributesOnAHasManyAssociation < ActiveRecord::TestCase
854
+ def setup
855
+ @association_type = :has_many
856
+ @association_name = :birds
857
+
858
+ @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
859
+ @pirate.birds.create!(:name => 'Posideons Killer')
860
+ @pirate.birds.create!(:name => 'Killer bandita Dionne')
861
+
862
+ @child_1, @child_2 = @pirate.birds
863
+
864
+ @alternate_params = {
865
+ :birds_attributes => {
866
+ 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' },
867
+ 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' }
868
+ }
869
+ }
870
+ end
871
+
872
+ include NestedAttributesOnACollectionAssociationTests
873
+ end
874
+
875
+ class TestNestedAttributesOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
876
+ def setup
877
+ @association_type = :has_and_belongs_to_many
878
+ @association_name = :parrots
879
+
880
+ @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
881
+ @pirate.parrots.create!(:name => 'Posideons Killer')
882
+ @pirate.parrots.create!(:name => 'Killer bandita Dionne')
883
+
884
+ @child_1, @child_2 = @pirate.parrots
885
+
886
+ @alternate_params = {
887
+ :parrots_attributes => {
888
+ 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' },
889
+ 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' }
890
+ }
891
+ }
892
+ end
893
+
894
+ include NestedAttributesOnACollectionAssociationTests
895
+ end
896
+
897
+ module NestedAttributesLimitTests
898
+ def teardown
899
+ Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc(&:empty?)
900
+ end
901
+
902
+ def test_limit_with_less_records
903
+ @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Big Big Love' } } }
904
+ assert_difference('Parrot.count') { @pirate.save! }
905
+ end
906
+
907
+ def test_limit_with_number_exact_records
908
+ @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, 'bar' => { :name => 'Blown Away' } } }
909
+ assert_difference('Parrot.count', 2) { @pirate.save! }
910
+ end
911
+
912
+ def test_limit_with_exceeding_records
913
+ assert_raises(ActiveRecord::NestedAttributes::TooManyRecords) do
914
+ @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' },
915
+ 'bar' => { :name => 'Blown Away' },
916
+ 'car' => { :name => 'The Happening' }} }
917
+ end
918
+ end
919
+ end
920
+
921
+ class TestNestedAttributesLimitNumeric < ActiveRecord::TestCase
922
+ def setup
923
+ Pirate.accepts_nested_attributes_for :parrots, :limit => 2
924
+
925
+ @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
926
+ end
927
+
928
+ include NestedAttributesLimitTests
929
+ end
930
+
931
+ class TestNestedAttributesLimitSymbol < ActiveRecord::TestCase
932
+ def setup
933
+ Pirate.accepts_nested_attributes_for :parrots, :limit => :parrots_limit
934
+
935
+ @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?", :parrots_limit => 2)
936
+ end
937
+
938
+ include NestedAttributesLimitTests
939
+ end
940
+
941
+ class TestNestedAttributesLimitProc < ActiveRecord::TestCase
942
+ def setup
943
+ Pirate.accepts_nested_attributes_for :parrots, :limit => proc { 2 }
944
+
945
+ @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
946
+ end
947
+
948
+ include NestedAttributesLimitTests
949
+ end
950
+
951
+ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
952
+ fixtures :owners, :pets
953
+
954
+ def setup
955
+ Owner.accepts_nested_attributes_for :pets, :allow_destroy => true
956
+
957
+ @owner = owners(:ashley)
958
+ @pet1, @pet2 = pets(:chew), pets(:mochi)
959
+
960
+ @params = {
961
+ :pets_attributes => {
962
+ '0' => { :id => @pet1.id, :name => 'Foo' },
963
+ '1' => { :id => @pet2.id, :name => 'Bar' }
964
+ }
965
+ }
966
+ end
967
+
968
+ def test_should_update_existing_records_with_non_standard_primary_key
969
+ @owner.update(@params)
970
+ assert_equal ['Foo', 'Bar'], @owner.pets.map(&:name)
971
+ end
972
+
973
+ def test_attr_accessor_of_child_should_be_value_provided_during_update
974
+ @owner = owners(:ashley)
975
+ @pet1 = pets(:chew)
976
+ attributes = {:pets_attributes => { "1"=> { :id => @pet1.id,
977
+ :name => "Foo2",
978
+ :current_user => "John",
979
+ :_destroy=>true }}}
980
+ @owner.update(attributes)
981
+ assert_equal 'John', Pet.after_destroy_output
982
+ end
983
+
984
+ end
985
+
986
+ class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
987
+ self.use_transactional_tests = false unless supports_savepoints?
988
+
989
+ def setup
990
+ @pirate = Pirate.create!(:catchphrase => "My baby takes tha mornin' train!")
991
+ @ship = @pirate.create_ship(:name => "The good ship Dollypop")
992
+ @part = @ship.parts.create!(:name => "Mast")
993
+ @trinket = @part.trinkets.create!(:name => "Necklace")
994
+ end
995
+
996
+ test "when great-grandchild changed in memory, saving parent should save great-grandchild" do
997
+ @trinket.name = "changed"
998
+ @pirate.save
999
+ assert_equal "changed", @trinket.reload.name
1000
+ end
1001
+
1002
+ test "when great-grandchild changed via attributes, saving parent should save great-grandchild" do
1003
+ @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]}}
1004
+ @pirate.save
1005
+ assert_equal "changed", @trinket.reload.name
1006
+ end
1007
+
1008
+ test "when great-grandchild marked_for_destruction via attributes, saving parent should destroy great-grandchild" do
1009
+ @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]}}
1010
+ assert_difference('@part.trinkets.count', -1) { @pirate.save }
1011
+ end
1012
+
1013
+ test "when great-grandchild added via attributes, saving parent should create great-grandchild" do
1014
+ @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]}}
1015
+ assert_difference('@part.trinkets.count', 1) { @pirate.save }
1016
+ end
1017
+
1018
+ test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do
1019
+ @trinket.name = "changed"
1020
+ Ship.create!(:pirate => @pirate, :name => "The Black Rock")
1021
+ ShipPart.create!(:ship => @ship, :name => "Stern")
1022
+ assert_no_queries { @pirate.valid? }
1023
+ end
1024
+ end
1025
+
1026
+ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
1027
+ self.use_transactional_tests = false unless supports_savepoints?
1028
+
1029
+ def setup
1030
+ @ship = Ship.create!(:name => "The good ship Dollypop")
1031
+ @part = @ship.parts.create!(:name => "Mast")
1032
+ @trinket = @part.trinkets.create!(:name => "Necklace")
1033
+ end
1034
+
1035
+ test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do
1036
+ @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}]
1037
+ assert_equal 1, @ship.association(:parts).target.size
1038
+ assert_equal 'Deck', @ship.parts[0].name
1039
+ end
1040
+
1041
+ test "if association is not loaded and child doesn't change and I am saving a grandchild then in memory record should be used" do
1042
+ @ship.parts_attributes=[{:id => @part.id,:trinkets_attributes =>[{:id => @trinket.id, :name => 'Ruby'}]}]
1043
+ assert_equal 1, @ship.association(:parts).target.size
1044
+ assert_equal 'Mast', @ship.parts[0].name
1045
+ assert_no_difference("@ship.parts[0].association(:trinkets).target.size") do
1046
+ @ship.parts[0].association(:trinkets).target.size
1047
+ end
1048
+ assert_equal 'Ruby', @ship.parts[0].trinkets[0].name
1049
+ @ship.save
1050
+ assert_equal 'Ruby', @ship.parts[0].trinkets[0].name
1051
+ end
1052
+
1053
+ test "when grandchild changed in memory, saving parent should save grandchild" do
1054
+ @trinket.name = "changed"
1055
+ @ship.save
1056
+ assert_equal "changed", @trinket.reload.name
1057
+ end
1058
+
1059
+ test "when grandchild changed via attributes, saving parent should save grandchild" do
1060
+ @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]}
1061
+ @ship.save
1062
+ assert_equal "changed", @trinket.reload.name
1063
+ end
1064
+
1065
+ test "when grandchild marked_for_destruction via attributes, saving parent should destroy grandchild" do
1066
+ @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]}
1067
+ assert_difference('@part.trinkets.count', -1) { @ship.save }
1068
+ end
1069
+
1070
+ test "when grandchild added via attributes, saving parent should create grandchild" do
1071
+ @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]}
1072
+ assert_difference('@part.trinkets.count', 1) { @ship.save }
1073
+ end
1074
+
1075
+ test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do
1076
+ @trinket.name = "changed"
1077
+ Ship.create!(:name => "The Black Rock")
1078
+ ShipPart.create!(:ship => @ship, :name => "Stern")
1079
+ assert_no_queries { @ship.valid? }
1080
+ end
1081
+
1082
+ test "circular references do not perform unnecessary queries" do
1083
+ ship = Ship.new(name: "The Black Rock")
1084
+ part = ship.parts.build(name: "Stern")
1085
+ ship.treasures.build(looter: part)
1086
+
1087
+ assert_queries 3 do
1088
+ ship.save!
1089
+ end
1090
+ end
1091
+
1092
+ test "nested singular associations are validated" do
1093
+ part = ShipPart.new(name: "Stern", ship_attributes: { name: nil })
1094
+
1095
+ assert_not part.valid?
1096
+ assert_equal ["Ship name can't be blank"], part.errors.full_messages
1097
+ end
1098
+ end