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

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