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