activerecord 2.3.18 → 3.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (378) hide show
  1. data/CHANGELOG +105 -34
  2. data/examples/performance.rb +3 -39
  3. data/examples/simple.rb +14 -0
  4. data/lib/active_record.rb +81 -47
  5. data/lib/active_record/aggregations.rb +1 -3
  6. data/lib/active_record/association_preload.rb +39 -54
  7. data/lib/active_record/associations.rb +262 -419
  8. data/lib/active_record/associations/association_collection.rb +85 -100
  9. data/lib/active_record/associations/association_proxy.rb +20 -18
  10. data/lib/active_record/associations/belongs_to_association.rb +8 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -35
  12. data/lib/active_record/associations/has_many_association.rb +11 -19
  13. data/lib/active_record/associations/has_many_through_association.rb +30 -183
  14. data/lib/active_record/associations/has_one_association.rb +10 -10
  15. data/lib/active_record/associations/has_one_through_association.rb +13 -11
  16. data/lib/active_record/associations/through_association_scope.rb +153 -0
  17. data/lib/active_record/attribute_methods.rb +17 -370
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +87 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +44 -0
  21. data/lib/active_record/attribute_methods/query.rb +37 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +60 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/autosave_association.rb +20 -41
  26. data/lib/active_record/base.rb +357 -1180
  27. data/lib/active_record/batches.rb +10 -16
  28. data/lib/active_record/callbacks.rb +66 -126
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +17 -13
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +5 -25
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +4 -43
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -2
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -4
  34. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  35. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -72
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +16 -49
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +15 -27
  38. data/lib/active_record/connection_adapters/postgresql_adapter.rb +84 -46
  39. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +9 -3
  40. data/lib/active_record/connection_adapters/sqlite_adapter.rb +34 -65
  41. data/lib/active_record/fixtures.rb +21 -25
  42. data/lib/active_record/locale/en.yml +9 -27
  43. data/lib/active_record/locking/optimistic.rb +16 -48
  44. data/lib/active_record/migration.rb +59 -46
  45. data/lib/active_record/named_scope.rb +85 -92
  46. data/lib/active_record/nested_attributes.rb +18 -24
  47. data/lib/active_record/observer.rb +18 -94
  48. data/lib/active_record/railtie.rb +83 -0
  49. data/lib/active_record/railties/controller_runtime.rb +38 -0
  50. data/lib/active_record/railties/databases.rake +477 -0
  51. data/lib/active_record/railties/subscriber.rb +27 -0
  52. data/lib/active_record/reflection.rb +2 -15
  53. data/lib/active_record/relation.rb +339 -0
  54. data/lib/active_record/relation/calculations.rb +259 -0
  55. data/lib/active_record/relation/finder_methods.rb +315 -0
  56. data/lib/active_record/relation/predicate_builder.rb +46 -0
  57. data/lib/active_record/relation/query_methods.rb +218 -0
  58. data/lib/active_record/relation/spawn_methods.rb +102 -0
  59. data/lib/active_record/schema_dumper.rb +10 -6
  60. data/lib/active_record/serialization.rb +31 -74
  61. data/lib/active_record/serializers/xml_serializer.rb +33 -121
  62. data/lib/active_record/session_store.rb +1 -9
  63. data/lib/active_record/test_case.rb +1 -3
  64. data/lib/active_record/timestamp.rb +7 -5
  65. data/lib/active_record/transactions.rb +9 -9
  66. data/lib/active_record/validations.rb +51 -1100
  67. data/lib/active_record/validations/associated.rb +47 -0
  68. data/lib/active_record/validations/uniqueness.rb +181 -0
  69. data/lib/active_record/version.rb +3 -3
  70. data/lib/generators/active_record.rb +30 -0
  71. data/lib/generators/active_record/migration/migration_generator.rb +25 -0
  72. data/lib/generators/active_record/migration/templates/migration.rb +11 -0
  73. data/lib/generators/active_record/model/model_generator.rb +33 -0
  74. data/lib/generators/active_record/model/templates/migration.rb +16 -0
  75. data/lib/generators/active_record/model/templates/model.rb +5 -0
  76. data/lib/generators/active_record/observer/observer_generator.rb +15 -0
  77. data/lib/generators/active_record/observer/templates/observer.rb +2 -0
  78. data/lib/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  79. data/lib/generators/active_record/session_migration/templates/migration.rb +16 -0
  80. metadata +67 -325
  81. data/RUNNING_UNIT_TESTS +0 -36
  82. data/Rakefile +0 -268
  83. data/install.rb +0 -30
  84. data/lib/active_record/calculations.rb +0 -321
  85. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -57
  86. data/lib/active_record/dirty.rb +0 -183
  87. data/lib/active_record/serializers/json_serializer.rb +0 -91
  88. data/lib/activerecord.rb +0 -2
  89. data/test/assets/example.log +0 -1
  90. data/test/assets/flowers.jpg +0 -0
  91. data/test/cases/aaa_create_tables_test.rb +0 -24
  92. data/test/cases/active_schema_test_mysql.rb +0 -122
  93. data/test/cases/active_schema_test_postgresql.rb +0 -24
  94. data/test/cases/adapter_test.rb +0 -144
  95. data/test/cases/aggregations_test.rb +0 -167
  96. data/test/cases/ar_schema_test.rb +0 -32
  97. data/test/cases/associations/belongs_to_associations_test.rb +0 -438
  98. data/test/cases/associations/callbacks_test.rb +0 -161
  99. data/test/cases/associations/cascaded_eager_loading_test.rb +0 -131
  100. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +0 -36
  101. data/test/cases/associations/eager_load_nested_include_test.rb +0 -131
  102. data/test/cases/associations/eager_load_nested_polymorphic_include.rb +0 -19
  103. data/test/cases/associations/eager_singularization_test.rb +0 -145
  104. data/test/cases/associations/eager_test.rb +0 -852
  105. data/test/cases/associations/extension_test.rb +0 -62
  106. data/test/cases/associations/habtm_join_table_test.rb +0 -56
  107. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +0 -827
  108. data/test/cases/associations/has_many_associations_test.rb +0 -1273
  109. data/test/cases/associations/has_many_through_associations_test.rb +0 -360
  110. data/test/cases/associations/has_one_associations_test.rb +0 -330
  111. data/test/cases/associations/has_one_through_associations_test.rb +0 -209
  112. data/test/cases/associations/inner_join_association_test.rb +0 -93
  113. data/test/cases/associations/inverse_associations_test.rb +0 -566
  114. data/test/cases/associations/join_model_test.rb +0 -712
  115. data/test/cases/associations_test.rb +0 -282
  116. data/test/cases/attribute_methods_test.rb +0 -305
  117. data/test/cases/autosave_association_test.rb +0 -1218
  118. data/test/cases/base_test.rb +0 -2166
  119. data/test/cases/batches_test.rb +0 -81
  120. data/test/cases/binary_test.rb +0 -30
  121. data/test/cases/calculations_test.rb +0 -360
  122. data/test/cases/callbacks_observers_test.rb +0 -38
  123. data/test/cases/callbacks_test.rb +0 -438
  124. data/test/cases/class_inheritable_attributes_test.rb +0 -32
  125. data/test/cases/column_alias_test.rb +0 -17
  126. data/test/cases/column_definition_test.rb +0 -70
  127. data/test/cases/connection_pool_test.rb +0 -25
  128. data/test/cases/connection_test_firebird.rb +0 -8
  129. data/test/cases/connection_test_mysql.rb +0 -65
  130. data/test/cases/copy_table_test_sqlite.rb +0 -80
  131. data/test/cases/counter_cache_test.rb +0 -84
  132. data/test/cases/database_statements_test.rb +0 -12
  133. data/test/cases/datatype_test_postgresql.rb +0 -204
  134. data/test/cases/date_time_test.rb +0 -37
  135. data/test/cases/default_test_firebird.rb +0 -16
  136. data/test/cases/defaults_test.rb +0 -111
  137. data/test/cases/deprecated_finder_test.rb +0 -30
  138. data/test/cases/dirty_test.rb +0 -316
  139. data/test/cases/finder_respond_to_test.rb +0 -76
  140. data/test/cases/finder_test.rb +0 -1098
  141. data/test/cases/fixtures_test.rb +0 -661
  142. data/test/cases/helper.rb +0 -68
  143. data/test/cases/i18n_test.rb +0 -46
  144. data/test/cases/inheritance_test.rb +0 -262
  145. data/test/cases/invalid_date_test.rb +0 -24
  146. data/test/cases/json_serialization_test.rb +0 -219
  147. data/test/cases/lifecycle_test.rb +0 -193
  148. data/test/cases/locking_test.rb +0 -350
  149. data/test/cases/method_scoping_test.rb +0 -704
  150. data/test/cases/migration_test.rb +0 -1649
  151. data/test/cases/migration_test_firebird.rb +0 -124
  152. data/test/cases/mixin_test.rb +0 -96
  153. data/test/cases/modules_test.rb +0 -109
  154. data/test/cases/multiple_db_test.rb +0 -85
  155. data/test/cases/named_scope_test.rb +0 -372
  156. data/test/cases/nested_attributes_test.rb +0 -840
  157. data/test/cases/pk_test.rb +0 -119
  158. data/test/cases/pooled_connections_test.rb +0 -103
  159. data/test/cases/query_cache_test.rb +0 -129
  160. data/test/cases/readonly_test.rb +0 -107
  161. data/test/cases/reflection_test.rb +0 -234
  162. data/test/cases/reload_models_test.rb +0 -22
  163. data/test/cases/repair_helper.rb +0 -50
  164. data/test/cases/reserved_word_test_mysql.rb +0 -176
  165. data/test/cases/sanitize_test.rb +0 -25
  166. data/test/cases/schema_authorization_test_postgresql.rb +0 -75
  167. data/test/cases/schema_dumper_test.rb +0 -211
  168. data/test/cases/schema_test_postgresql.rb +0 -178
  169. data/test/cases/serialization_test.rb +0 -47
  170. data/test/cases/sp_test_mysql.rb +0 -16
  171. data/test/cases/synonym_test_oracle.rb +0 -17
  172. data/test/cases/timestamp_test.rb +0 -75
  173. data/test/cases/transactions_test.rb +0 -543
  174. data/test/cases/unconnected_test.rb +0 -32
  175. data/test/cases/validations_i18n_test.rb +0 -925
  176. data/test/cases/validations_test.rb +0 -1684
  177. data/test/cases/xml_serialization_test.rb +0 -240
  178. data/test/cases/yaml_serialization_test.rb +0 -11
  179. data/test/config.rb +0 -5
  180. data/test/connections/jdbc_jdbcderby/connection.rb +0 -18
  181. data/test/connections/jdbc_jdbch2/connection.rb +0 -18
  182. data/test/connections/jdbc_jdbchsqldb/connection.rb +0 -18
  183. data/test/connections/jdbc_jdbcmysql/connection.rb +0 -26
  184. data/test/connections/jdbc_jdbcpostgresql/connection.rb +0 -26
  185. data/test/connections/jdbc_jdbcsqlite3/connection.rb +0 -25
  186. data/test/connections/native_db2/connection.rb +0 -25
  187. data/test/connections/native_firebird/connection.rb +0 -26
  188. data/test/connections/native_frontbase/connection.rb +0 -27
  189. data/test/connections/native_mysql/connection.rb +0 -25
  190. data/test/connections/native_openbase/connection.rb +0 -21
  191. data/test/connections/native_oracle/connection.rb +0 -27
  192. data/test/connections/native_postgresql/connection.rb +0 -21
  193. data/test/connections/native_sqlite/connection.rb +0 -25
  194. data/test/connections/native_sqlite3/connection.rb +0 -25
  195. data/test/connections/native_sqlite3/in_memory_connection.rb +0 -18
  196. data/test/connections/native_sybase/connection.rb +0 -23
  197. data/test/fixtures/accounts.yml +0 -29
  198. data/test/fixtures/all/developers.yml +0 -0
  199. data/test/fixtures/all/people.csv +0 -0
  200. data/test/fixtures/all/tasks.yml +0 -0
  201. data/test/fixtures/author_addresses.yml +0 -5
  202. data/test/fixtures/author_favorites.yml +0 -4
  203. data/test/fixtures/authors.yml +0 -9
  204. data/test/fixtures/binaries.yml +0 -132
  205. data/test/fixtures/books.yml +0 -7
  206. data/test/fixtures/categories.yml +0 -14
  207. data/test/fixtures/categories/special_categories.yml +0 -9
  208. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +0 -4
  209. data/test/fixtures/categories_ordered.yml +0 -7
  210. data/test/fixtures/categories_posts.yml +0 -23
  211. data/test/fixtures/categorizations.yml +0 -17
  212. data/test/fixtures/clubs.yml +0 -6
  213. data/test/fixtures/comments.yml +0 -59
  214. data/test/fixtures/companies.yml +0 -56
  215. data/test/fixtures/computers.yml +0 -4
  216. data/test/fixtures/courses.yml +0 -7
  217. data/test/fixtures/customers.yml +0 -26
  218. data/test/fixtures/developers.yml +0 -21
  219. data/test/fixtures/developers_projects.yml +0 -17
  220. data/test/fixtures/edges.yml +0 -6
  221. data/test/fixtures/entrants.yml +0 -14
  222. data/test/fixtures/faces.yml +0 -11
  223. data/test/fixtures/fk_test_has_fk.yml +0 -3
  224. data/test/fixtures/fk_test_has_pk.yml +0 -2
  225. data/test/fixtures/funny_jokes.yml +0 -10
  226. data/test/fixtures/interests.yml +0 -33
  227. data/test/fixtures/items.yml +0 -4
  228. data/test/fixtures/jobs.yml +0 -7
  229. data/test/fixtures/legacy_things.yml +0 -3
  230. data/test/fixtures/mateys.yml +0 -4
  231. data/test/fixtures/member_types.yml +0 -6
  232. data/test/fixtures/members.yml +0 -6
  233. data/test/fixtures/memberships.yml +0 -20
  234. data/test/fixtures/men.yml +0 -5
  235. data/test/fixtures/minimalistics.yml +0 -2
  236. data/test/fixtures/mixed_case_monkeys.yml +0 -6
  237. data/test/fixtures/mixins.yml +0 -29
  238. data/test/fixtures/movies.yml +0 -7
  239. data/test/fixtures/naked/csv/accounts.csv +0 -1
  240. data/test/fixtures/naked/yml/accounts.yml +0 -1
  241. data/test/fixtures/naked/yml/companies.yml +0 -1
  242. data/test/fixtures/naked/yml/courses.yml +0 -1
  243. data/test/fixtures/organizations.yml +0 -5
  244. data/test/fixtures/owners.yml +0 -7
  245. data/test/fixtures/parrots.yml +0 -27
  246. data/test/fixtures/parrots_pirates.yml +0 -7
  247. data/test/fixtures/people.yml +0 -15
  248. data/test/fixtures/pets.yml +0 -14
  249. data/test/fixtures/pirates.yml +0 -9
  250. data/test/fixtures/polymorphic_designs.yml +0 -19
  251. data/test/fixtures/polymorphic_prices.yml +0 -19
  252. data/test/fixtures/posts.yml +0 -52
  253. data/test/fixtures/price_estimates.yml +0 -7
  254. data/test/fixtures/projects.yml +0 -7
  255. data/test/fixtures/readers.yml +0 -9
  256. data/test/fixtures/references.yml +0 -17
  257. data/test/fixtures/reserved_words/distinct.yml +0 -5
  258. data/test/fixtures/reserved_words/distincts_selects.yml +0 -11
  259. data/test/fixtures/reserved_words/group.yml +0 -14
  260. data/test/fixtures/reserved_words/select.yml +0 -8
  261. data/test/fixtures/reserved_words/values.yml +0 -7
  262. data/test/fixtures/ships.yml +0 -5
  263. data/test/fixtures/sponsors.yml +0 -9
  264. data/test/fixtures/subscribers.yml +0 -7
  265. data/test/fixtures/subscriptions.yml +0 -12
  266. data/test/fixtures/taggings.yml +0 -28
  267. data/test/fixtures/tags.yml +0 -7
  268. data/test/fixtures/tasks.yml +0 -7
  269. data/test/fixtures/tees.yml +0 -4
  270. data/test/fixtures/ties.yml +0 -4
  271. data/test/fixtures/topics.yml +0 -42
  272. data/test/fixtures/toys.yml +0 -4
  273. data/test/fixtures/treasures.yml +0 -10
  274. data/test/fixtures/vertices.yml +0 -4
  275. data/test/fixtures/warehouse-things.yml +0 -3
  276. data/test/fixtures/zines.yml +0 -5
  277. data/test/migrations/broken/100_migration_that_raises_exception.rb +0 -10
  278. data/test/migrations/decimal/1_give_me_big_numbers.rb +0 -15
  279. data/test/migrations/duplicate/1_people_have_last_names.rb +0 -9
  280. data/test/migrations/duplicate/2_we_need_reminders.rb +0 -12
  281. data/test/migrations/duplicate/3_foo.rb +0 -7
  282. data/test/migrations/duplicate/3_innocent_jointable.rb +0 -12
  283. data/test/migrations/duplicate_names/20080507052938_chunky.rb +0 -7
  284. data/test/migrations/duplicate_names/20080507053028_chunky.rb +0 -7
  285. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +0 -12
  286. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +0 -9
  287. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +0 -12
  288. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +0 -9
  289. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +0 -8
  290. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +0 -12
  291. data/test/migrations/missing/1000_people_have_middle_names.rb +0 -9
  292. data/test/migrations/missing/1_people_have_last_names.rb +0 -9
  293. data/test/migrations/missing/3_we_need_reminders.rb +0 -12
  294. data/test/migrations/missing/4_innocent_jointable.rb +0 -12
  295. data/test/migrations/valid/1_people_have_last_names.rb +0 -9
  296. data/test/migrations/valid/2_we_need_reminders.rb +0 -12
  297. data/test/migrations/valid/3_innocent_jointable.rb +0 -12
  298. data/test/models/author.rb +0 -151
  299. data/test/models/auto_id.rb +0 -4
  300. data/test/models/binary.rb +0 -2
  301. data/test/models/bird.rb +0 -9
  302. data/test/models/book.rb +0 -4
  303. data/test/models/categorization.rb +0 -5
  304. data/test/models/category.rb +0 -34
  305. data/test/models/citation.rb +0 -6
  306. data/test/models/club.rb +0 -13
  307. data/test/models/column_name.rb +0 -3
  308. data/test/models/comment.rb +0 -29
  309. data/test/models/company.rb +0 -173
  310. data/test/models/company_in_module.rb +0 -78
  311. data/test/models/computer.rb +0 -3
  312. data/test/models/contact.rb +0 -16
  313. data/test/models/contract.rb +0 -5
  314. data/test/models/course.rb +0 -3
  315. data/test/models/customer.rb +0 -73
  316. data/test/models/default.rb +0 -2
  317. data/test/models/developer.rb +0 -101
  318. data/test/models/edge.rb +0 -5
  319. data/test/models/entrant.rb +0 -3
  320. data/test/models/essay.rb +0 -3
  321. data/test/models/event.rb +0 -3
  322. data/test/models/event_author.rb +0 -8
  323. data/test/models/face.rb +0 -7
  324. data/test/models/guid.rb +0 -2
  325. data/test/models/interest.rb +0 -5
  326. data/test/models/invoice.rb +0 -4
  327. data/test/models/item.rb +0 -7
  328. data/test/models/job.rb +0 -5
  329. data/test/models/joke.rb +0 -3
  330. data/test/models/keyboard.rb +0 -3
  331. data/test/models/legacy_thing.rb +0 -3
  332. data/test/models/line_item.rb +0 -3
  333. data/test/models/man.rb +0 -9
  334. data/test/models/matey.rb +0 -4
  335. data/test/models/member.rb +0 -12
  336. data/test/models/member_detail.rb +0 -5
  337. data/test/models/member_type.rb +0 -3
  338. data/test/models/membership.rb +0 -9
  339. data/test/models/minimalistic.rb +0 -2
  340. data/test/models/mixed_case_monkey.rb +0 -3
  341. data/test/models/movie.rb +0 -5
  342. data/test/models/order.rb +0 -4
  343. data/test/models/organization.rb +0 -6
  344. data/test/models/owner.rb +0 -5
  345. data/test/models/parrot.rb +0 -22
  346. data/test/models/person.rb +0 -16
  347. data/test/models/pet.rb +0 -5
  348. data/test/models/pirate.rb +0 -80
  349. data/test/models/polymorphic_design.rb +0 -3
  350. data/test/models/polymorphic_price.rb +0 -3
  351. data/test/models/post.rb +0 -102
  352. data/test/models/price_estimate.rb +0 -3
  353. data/test/models/project.rb +0 -30
  354. data/test/models/reader.rb +0 -4
  355. data/test/models/reference.rb +0 -4
  356. data/test/models/reply.rb +0 -46
  357. data/test/models/ship.rb +0 -19
  358. data/test/models/ship_part.rb +0 -7
  359. data/test/models/sponsor.rb +0 -4
  360. data/test/models/subject.rb +0 -4
  361. data/test/models/subscriber.rb +0 -8
  362. data/test/models/subscription.rb +0 -4
  363. data/test/models/tag.rb +0 -7
  364. data/test/models/tagging.rb +0 -10
  365. data/test/models/task.rb +0 -3
  366. data/test/models/tee.rb +0 -4
  367. data/test/models/tie.rb +0 -4
  368. data/test/models/topic.rb +0 -80
  369. data/test/models/toy.rb +0 -6
  370. data/test/models/treasure.rb +0 -8
  371. data/test/models/vertex.rb +0 -9
  372. data/test/models/warehouse_thing.rb +0 -5
  373. data/test/models/zine.rb +0 -3
  374. data/test/schema/mysql_specific_schema.rb +0 -31
  375. data/test/schema/postgresql_specific_schema.rb +0 -114
  376. data/test/schema/schema.rb +0 -550
  377. data/test/schema/schema2.rb +0 -6
  378. data/test/schema/sqlite_specific_schema.rb +0 -25
@@ -1,840 +0,0 @@
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
-
13
- module AssertRaiseWithMessage
14
- def assert_raise_with_message(expected_exception, expected_message)
15
- begin
16
- error_raised = false
17
- yield
18
- rescue expected_exception => error
19
- error_raised = true
20
- actual_message = error.message
21
- end
22
- assert error_raised
23
- assert_equal expected_message, actual_message
24
- end
25
- end
26
-
27
- class TestNestedAttributesInGeneral < ActiveRecord::TestCase
28
- include AssertRaiseWithMessage
29
-
30
- def teardown
31
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
32
- end
33
-
34
- def test_base_should_have_an_empty_nested_attributes_options
35
- assert_equal Hash.new, ActiveRecord::Base.nested_attributes_options
36
- end
37
-
38
- def test_should_add_a_proc_to_nested_attributes_options
39
- assert_equal ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC,
40
- Pirate.nested_attributes_options[:birds_with_reject_all_blank][:reject_if]
41
-
42
- [:parrots, :birds].each do |name|
43
- assert_instance_of Proc, Pirate.nested_attributes_options[name][:reject_if]
44
- end
45
- end
46
-
47
- def test_should_not_build_a_new_record_if_reject_all_blank_returns_false
48
- pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
49
- pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => ''}]
50
- pirate.save!
51
-
52
- assert pirate.birds_with_reject_all_blank.empty?
53
- end
54
-
55
- def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false
56
- pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
57
- pirate.birds_with_reject_all_blank_attributes = [{:name => 'Tweetie', :color => ''}]
58
- pirate.save!
59
-
60
- assert_equal 1, pirate.birds_with_reject_all_blank.count
61
- end
62
-
63
- def test_should_raise_an_ArgumentError_for_non_existing_associations
64
- assert_raise_with_message ArgumentError, "No association found for name `honesty'. Has it been defined yet?" do
65
- Pirate.accepts_nested_attributes_for :honesty
66
- end
67
- end
68
-
69
- def test_should_disable_allow_destroy_by_default
70
- Pirate.accepts_nested_attributes_for :ship
71
-
72
- pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
73
- ship = pirate.create_ship(:name => 'Nights Dirty Lightning')
74
-
75
- assert_no_difference('Ship.count') do
76
- pirate.update_attributes(:ship_attributes => { '_destroy' => true })
77
- end
78
- end
79
-
80
- def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked_for_destruction
81
- ship = Ship.create!(:name => 'Nights Dirty Lightning')
82
- assert !ship._destroy
83
- ship.mark_for_destruction
84
- assert ship._destroy
85
- end
86
-
87
- def test_reject_if_method_without_arguments
88
- Pirate.accepts_nested_attributes_for :ship, :reject_if => :new_record?
89
-
90
- pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
91
- pirate.ship_attributes = { :name => 'Black Pearl' }
92
- assert_no_difference('Ship.count') { pirate.save! }
93
- end
94
-
95
- def test_reject_if_method_with_arguments
96
- Pirate.accepts_nested_attributes_for :ship, :reject_if => :reject_empty_ships_on_create
97
-
98
- pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
99
- pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
100
- assert_no_difference('Ship.count') { pirate.save! }
101
-
102
- # pirate.reject_empty_ships_on_create returns false for saved records
103
- pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
104
- assert_difference('Ship.count') { pirate.save! }
105
- end
106
-
107
- def test_reject_if_with_indifferent_keys
108
- Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:name].blank? }
109
-
110
- pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
111
- pirate.ship_attributes = { :name => 'Hello Pearl' }
112
- assert_difference('Ship.count') { pirate.save! }
113
- end
114
- end
115
-
116
- class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
117
- include AssertRaiseWithMessage
118
-
119
- def setup
120
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
121
- @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
122
- end
123
-
124
- def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to
125
- assert_raise_with_message ArgumentError, "Cannot build association looter. Are you trying to build a polymorphic one-to-one association?" do
126
- Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"})
127
- end
128
- end
129
-
130
- def test_should_define_an_attribute_writer_method_for_the_association
131
- assert_respond_to @pirate, :ship_attributes=
132
- end
133
-
134
- def test_should_build_a_new_record_if_there_is_no_id
135
- @ship.destroy
136
- @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' }
137
-
138
- assert @pirate.ship.new_record?
139
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
140
- end
141
-
142
- def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy
143
- @ship.destroy
144
- @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' }
145
-
146
- assert_nil @pirate.ship
147
- end
148
-
149
- def test_should_not_build_a_new_record_if_a_reject_if_proc_returns_false
150
- @ship.destroy
151
- @pirate.reload.ship_attributes = {}
152
-
153
- assert_nil @pirate.ship
154
- end
155
-
156
- def test_should_replace_an_existing_record_if_there_is_no_id
157
- @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' }
158
-
159
- assert @pirate.ship.new_record?
160
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
161
- assert_equal 'Nights Dirty Lightning', @ship.name
162
- end
163
-
164
- def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy
165
- @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger', :_destroy => '1' }
166
-
167
- assert_equal @ship, @pirate.ship
168
- assert_equal 'Nights Dirty Lightning', @pirate.ship.name
169
- end
170
-
171
- def test_should_modify_an_existing_record_if_there_is_a_matching_id
172
- @pirate.reload.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' }
173
-
174
- assert_equal @ship, @pirate.ship
175
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
176
- end
177
-
178
- def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
179
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
180
- @pirate.ship_attributes = { :id => 1234567890 }
181
- end
182
- end
183
-
184
- def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
185
- @pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' }
186
-
187
- assert_equal @ship, @pirate.ship
188
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
189
- end
190
-
191
- def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id
192
- @ship.stubs(:id).returns('ABC1X')
193
- @pirate.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' }
194
-
195
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
196
- end
197
-
198
- def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy
199
- @pirate.ship.destroy
200
- [1, '1', true, 'true'].each do |truth|
201
- @pirate.reload.create_ship(:name => 'Mister Pablo')
202
- assert_difference('Ship.count', -1) do
203
- @pirate.update_attribute(:ship_attributes, { :id => @pirate.ship.id, :_destroy => truth })
204
- end
205
- end
206
- end
207
-
208
- def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
209
- [nil, '0', 0, 'false', false].each do |not_truth|
210
- assert_no_difference('Ship.count') do
211
- @pirate.update_attribute(:ship_attributes, { :id => @pirate.ship.id, :_destroy => not_truth })
212
- end
213
- end
214
- end
215
-
216
- def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
217
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
218
-
219
- assert_no_difference('Ship.count') do
220
- @pirate.update_attribute(:ship_attributes, { :id => @pirate.ship.id, :_destroy => '1' })
221
- end
222
-
223
- Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
224
- end
225
-
226
- def test_should_also_work_with_a_HashWithIndifferentAccess
227
- @pirate.ship_attributes = HashWithIndifferentAccess.new(:id => @ship.id, :name => 'Davy Jones Gold Dagger')
228
-
229
- assert !@pirate.ship.new_record?
230
- assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
231
- end
232
-
233
- def test_should_work_with_update_attributes_as_well
234
- @pirate.update_attributes({ :catchphrase => 'Arr', :ship_attributes => { :id => @ship.id, :name => 'Mister Pablo' } })
235
- @pirate.reload
236
-
237
- assert_equal 'Arr', @pirate.catchphrase
238
- assert_equal 'Mister Pablo', @pirate.ship.name
239
- end
240
-
241
- def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
242
- assert_no_difference('Ship.count') do
243
- @pirate.attributes = { :ship_attributes => { :id => @ship.id, :_destroy => '1' } }
244
- end
245
- assert_difference('Ship.count', -1) do
246
- @pirate.save
247
- end
248
- end
249
-
250
- def test_should_automatically_enable_autosave_on_the_association
251
- assert Pirate.reflect_on_association(:ship).options[:autosave]
252
- end
253
-
254
- def test_should_accept_update_only_option
255
- @pirate.update_attribute(:update_only_ship_attributes, { :id => @pirate.ship.id, :name => 'Mayflower' })
256
- end
257
-
258
- def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
259
- @ship.delete
260
- assert_difference('Ship.count', 1) do
261
- @pirate.reload.update_attribute(:update_only_ship_attributes, { :name => 'Mayflower' })
262
- end
263
- end
264
-
265
- def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
266
- @ship.delete
267
- @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning')
268
-
269
- assert_no_difference('Ship.count') do
270
- @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' })
271
- end
272
- assert_equal 'Mayflower', @ship.reload.name
273
- end
274
- end
275
-
276
- class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
277
- include AssertRaiseWithMessage
278
-
279
- def setup
280
- @ship = Ship.new(:name => 'Nights Dirty Lightning')
281
- @pirate = @ship.build_pirate(:catchphrase => 'Aye')
282
- @ship.save!
283
- end
284
-
285
- def test_should_define_an_attribute_writer_method_for_the_association
286
- assert_respond_to @ship, :pirate_attributes=
287
- end
288
-
289
- def test_should_build_a_new_record_if_there_is_no_id
290
- @pirate.destroy
291
- @ship.reload.pirate_attributes = { :catchphrase => 'Arr' }
292
-
293
- assert @ship.pirate.new_record?
294
- assert_equal 'Arr', @ship.pirate.catchphrase
295
- end
296
-
297
- def test_should_not_build_a_new_record_if_there_is_no_id_and_destroy_is_truthy
298
- @pirate.destroy
299
- @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' }
300
-
301
- assert_nil @ship.pirate
302
- end
303
-
304
- def test_should_not_build_a_new_record_if_a_reject_if_proc_returns_false
305
- @pirate.destroy
306
- @ship.reload.pirate_attributes = {}
307
-
308
- assert_nil @ship.pirate
309
- end
310
-
311
- def test_should_replace_an_existing_record_if_there_is_no_id
312
- @ship.reload.pirate_attributes = { :catchphrase => 'Arr' }
313
-
314
- assert @ship.pirate.new_record?
315
- assert_equal 'Arr', @ship.pirate.catchphrase
316
- assert_equal 'Aye', @pirate.catchphrase
317
- end
318
-
319
- def test_should_not_replace_an_existing_record_if_there_is_no_id_and_destroy_is_truthy
320
- @ship.reload.pirate_attributes = { :catchphrase => 'Arr', :_destroy => '1' }
321
-
322
- assert_equal @pirate, @ship.pirate
323
- assert_equal 'Aye', @ship.pirate.catchphrase
324
- end
325
-
326
- def test_should_modify_an_existing_record_if_there_is_a_matching_id
327
- @ship.reload.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' }
328
-
329
- assert_equal @pirate, @ship.pirate
330
- assert_equal 'Arr', @ship.pirate.catchphrase
331
- end
332
-
333
- def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
334
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do
335
- @ship.pirate_attributes = { :id => 1234567890 }
336
- end
337
- end
338
-
339
- def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
340
- @ship.reload.pirate_attributes = { 'id' => @pirate.id, 'catchphrase' => 'Arr' }
341
-
342
- assert_equal @pirate, @ship.pirate
343
- assert_equal 'Arr', @ship.pirate.catchphrase
344
- end
345
-
346
- def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id
347
- @pirate.stubs(:id).returns('ABC1X')
348
- @ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' }
349
-
350
- assert_equal 'Arr', @ship.pirate.catchphrase
351
- end
352
-
353
- def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy
354
- @ship.pirate.destroy
355
- [1, '1', true, 'true'].each do |truth|
356
- @ship.reload.create_pirate(:catchphrase => 'Arr')
357
- assert_difference('Pirate.count', -1) do
358
- @ship.update_attribute(:pirate_attributes, { :id => @ship.pirate.id, :_destroy => truth })
359
- end
360
- end
361
- end
362
-
363
- def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
364
- [nil, '0', 0, 'false', false].each do |not_truth|
365
- assert_no_difference('Pirate.count') do
366
- @ship.update_attribute(:pirate_attributes, { :id => @ship.pirate.id, :_destroy => not_truth })
367
- end
368
- end
369
- end
370
-
371
- def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
372
- Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
373
-
374
- assert_no_difference('Pirate.count') do
375
- @ship.update_attribute(:pirate_attributes, { :id => @ship.pirate.id, :_destroy => '1' })
376
- end
377
-
378
- Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
379
- end
380
-
381
- def test_should_work_with_update_attributes_as_well
382
- @ship.update_attributes({ :name => 'Mister Pablo', :pirate_attributes => { :catchphrase => 'Arr' } })
383
- @ship.reload
384
-
385
- assert_equal 'Mister Pablo', @ship.name
386
- assert_equal 'Arr', @ship.pirate.catchphrase
387
- end
388
-
389
- def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
390
- assert_no_difference('Pirate.count') do
391
- @ship.attributes = { :pirate_attributes => { :id => @ship.pirate.id, '_destroy' => true } }
392
- end
393
- assert_difference('Pirate.count', -1) { @ship.save }
394
- end
395
-
396
- def test_should_automatically_enable_autosave_on_the_association
397
- assert Ship.reflect_on_association(:pirate).options[:autosave]
398
- end
399
-
400
- def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
401
- @pirate.delete
402
- assert_difference('Pirate.count', 1) do
403
- @ship.reload.update_attribute(:update_only_pirate_attributes, { :catchphrase => 'Arr' })
404
- end
405
- end
406
-
407
- def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
408
- @pirate.delete
409
- @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye')
410
-
411
- assert_no_difference('Pirate.count') do
412
- @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr' })
413
- end
414
- assert_equal 'Arr', @pirate.reload.catchphrase
415
- end
416
- end
417
-
418
- module NestedAttributesOnACollectionAssociationTests
419
- include AssertRaiseWithMessage
420
-
421
- def test_should_define_an_attribute_writer_method_for_the_association
422
- assert_respond_to @pirate, association_setter
423
- end
424
-
425
- def test_should_save_only_one_association_on_create
426
- pirate = Pirate.create!({
427
- :catchphrase => 'Arr',
428
- association_getter => { 'foo' => { :name => 'Grace OMalley' } }
429
- })
430
-
431
- assert_equal 1, pirate.reload.send(@association_name).count
432
- end
433
-
434
- def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_associated_models
435
- @alternate_params[association_getter].stringify_keys!
436
- @pirate.update_attributes @alternate_params
437
- assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
438
- end
439
-
440
- def test_should_take_an_array_and_assign_the_attributes_to_the_associated_models
441
- @pirate.send(association_setter, @alternate_params[association_getter].values)
442
- @pirate.save
443
- assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
444
- end
445
-
446
- def test_should_also_work_with_a_HashWithIndifferentAccess
447
- @pirate.send(association_setter, HashWithIndifferentAccess.new('foo' => HashWithIndifferentAccess.new(:id => @child_1.id, :name => 'Grace OMalley')))
448
- @pirate.save
449
- assert_equal 'Grace OMalley', @child_1.reload.name
450
- end
451
-
452
- def test_should_take_a_hash_and_assign_the_attributes_to_the_associated_models
453
- @pirate.attributes = @alternate_params
454
- assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
455
- assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name
456
- end
457
-
458
- def test_should_not_load_association_when_updating_existing_records
459
- @pirate.reload
460
- @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
461
- assert ! @pirate.send(@association_name).loaded?
462
-
463
- @pirate.save
464
- assert ! @pirate.send(@association_name).loaded?
465
- assert_equal 'Grace OMalley', @child_1.reload.name
466
- end
467
-
468
- def test_should_not_overwrite_unsaved_updates_when_loading_association
469
- @pirate.reload
470
- @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
471
- assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name
472
- end
473
-
474
- def test_should_preserve_order_when_not_overwriting_unsaved_updates
475
- @pirate.reload
476
- @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
477
- assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id
478
- end
479
-
480
- def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates
481
- @pirate.reload
482
- record = @pirate.class.reflect_on_association(@association_name).klass.new(:name => 'Grace OMalley')
483
- @pirate.send(@association_name) << record
484
- record.save!
485
- @pirate.send(@association_name).last.update_attributes!(:name => 'Polly')
486
- assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name
487
- end
488
-
489
- def test_should_not_remove_scheduled_destroys_when_loading_association
490
- @pirate.reload
491
- @pirate.send(association_setter, [{ :id => @child_1.id, :_destroy => '1' }])
492
- assert @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.marked_for_destruction?
493
- end
494
-
495
- def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
496
- @child_1.stubs(:id).returns('ABC1X')
497
- @child_2.stubs(:id).returns('ABC2X')
498
-
499
- @pirate.attributes = {
500
- association_getter => [
501
- { :id => @child_1.id, :name => 'Grace OMalley' },
502
- { :id => @child_2.id, :name => 'Privateers Greed' }
503
- ]
504
- }
505
-
506
- assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name]
507
- end
508
-
509
- def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
510
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
511
- @pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
512
- end
513
- end
514
-
515
- def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing
516
- @pirate.send(@association_name).destroy_all
517
- @pirate.reload.attributes = {
518
- association_getter => { 'foo' => { :name => 'Grace OMalley' }, 'bar' => { :name => 'Privateers Greed' }}
519
- }
520
-
521
- assert @pirate.send(@association_name).first.new_record?
522
- assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
523
-
524
- assert @pirate.send(@association_name).last.new_record?
525
- assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name
526
- end
527
-
528
- def test_should_not_assign_destroy_key_to_a_record
529
- assert_nothing_raised ActiveRecord::UnknownAttributeError do
530
- @pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }})
531
- end
532
- end
533
-
534
- def test_should_ignore_new_associated_records_with_truthy_destroy_attribute
535
- @pirate.send(@association_name).destroy_all
536
- @pirate.reload.attributes = {
537
- association_getter => {
538
- 'foo' => { :name => 'Grace OMalley' },
539
- 'bar' => { :name => 'Privateers Greed', '_destroy' => '1' }
540
- }
541
- }
542
-
543
- assert_equal 1, @pirate.send(@association_name).length
544
- assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name
545
- end
546
-
547
- def test_should_ignore_new_associated_records_if_a_reject_if_proc_returns_false
548
- @alternate_params[association_getter]['baz'] = {}
549
- assert_no_difference("@pirate.send(@association_name).count") do
550
- @pirate.attributes = @alternate_params
551
- end
552
- end
553
-
554
- def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models
555
- attributes = ActiveSupport::OrderedHash.new
556
- attributes['123726353'] = { :name => 'Grace OMalley' }
557
- attributes['2'] = { :name => 'Privateers Greed' } # 2 is lower then 123726353
558
- @pirate.send(association_setter, attributes)
559
-
560
- assert_equal ['Posideons Killer', 'Killer bandita Dionne', 'Privateers Greed', 'Grace OMalley'].to_set, @pirate.send(@association_name).map(&:name).to_set
561
- end
562
-
563
- def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed
564
- assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) }
565
- assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, ActiveSupport::OrderedHash.new) }
566
-
567
- assert_raise_with_message ArgumentError, 'Hash or Array expected, got String ("foo")' do
568
- @pirate.send(association_setter, "foo")
569
- end
570
- end
571
-
572
- def test_should_work_with_update_attributes_as_well
573
- @pirate.update_attributes(:catchphrase => 'Arr',
574
- association_getter => { 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }})
575
-
576
- assert_equal 'Grace OMalley', @child_1.reload.name
577
- end
578
-
579
- def test_should_update_existing_records_and_add_new_ones_that_have_no_id
580
- @alternate_params[association_getter]['baz'] = { :name => 'Buccaneers Servant' }
581
- assert_difference('@pirate.send(@association_name).count', +1) do
582
- @pirate.update_attributes @alternate_params
583
- end
584
- assert_equal ['Grace OMalley', 'Privateers Greed', 'Buccaneers Servant'].to_set, @pirate.reload.send(@association_name).map(&:name).to_set
585
- end
586
-
587
- def test_should_be_possible_to_destroy_a_record
588
- ['1', 1, 'true', true].each do |true_variable|
589
- record = @pirate.reload.send(@association_name).create!(:name => 'Grace OMalley')
590
- @pirate.send(association_setter,
591
- @alternate_params[association_getter].merge('baz' => { :id => record.id, '_destroy' => true_variable })
592
- )
593
-
594
- assert_difference('@pirate.send(@association_name).count', -1) do
595
- @pirate.save
596
- end
597
- end
598
- end
599
-
600
- def test_should_not_destroy_the_associated_model_with_a_non_truthy_argument
601
- [nil, '', '0', 0, 'false', false].each do |false_variable|
602
- @alternate_params[association_getter]['foo']['_destroy'] = false_variable
603
- assert_no_difference('@pirate.send(@association_name).count') do
604
- @pirate.update_attributes(@alternate_params)
605
- end
606
- end
607
- end
608
-
609
- def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
610
- assert_no_difference('@pirate.send(@association_name).count') do
611
- @pirate.send(association_setter, @alternate_params[association_getter].merge('baz' => { :id => @child_1.id, '_destroy' => true }))
612
- end
613
- assert_difference('@pirate.send(@association_name).count', -1) { @pirate.save }
614
- end
615
-
616
- def test_should_automatically_enable_autosave_on_the_association
617
- assert Pirate.reflect_on_association(@association_name).options[:autosave]
618
- end
619
-
620
- def test_validate_presence_of_parent_works_with_inverse_of
621
- Man.accepts_nested_attributes_for(:interests)
622
- assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of]
623
- assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of]
624
-
625
- repair_validations(Interest) do
626
- Interest.validates_presence_of(:man)
627
- assert_difference 'Man.count' do
628
- assert_difference 'Interest.count', 2 do
629
- man = Man.create!(:name => 'John',
630
- :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}])
631
- assert_equal 2, man.interests.count
632
- end
633
- end
634
- end
635
- end
636
-
637
- def test_validate_presence_of_parent_fails_without_inverse_of
638
- Man.accepts_nested_attributes_for(:interests)
639
- Man.reflect_on_association(:interests).options.delete(:inverse_of)
640
- Interest.reflect_on_association(:man).options.delete(:inverse_of)
641
-
642
- repair_validations(Interest) do
643
- Interest.validates_presence_of(:man)
644
- assert_no_difference ['Man.count', 'Interest.count'] do
645
- man = Man.create(:name => 'John',
646
- :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}])
647
- assert !man.errors[:'interests.man'].empty?
648
- end
649
- end
650
- # restore :inverse_of
651
- Man.reflect_on_association(:interests).options[:inverse_of] = :man
652
- Interest.reflect_on_association(:man).options[:inverse_of] = :interests
653
- end
654
-
655
- private
656
-
657
- def association_setter
658
- @association_setter ||= "#{@association_name}_attributes=".to_sym
659
- end
660
-
661
- def association_getter
662
- @association_getter ||= "#{@association_name}_attributes".to_sym
663
- end
664
- end
665
-
666
- class TestNestedAttributesOnAHasManyAssociation < ActiveRecord::TestCase
667
- def setup
668
- @association_type = :has_many
669
- @association_name = :birds
670
-
671
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
672
- @pirate.birds.create!(:name => 'Posideons Killer')
673
- @pirate.birds.create!(:name => 'Killer bandita Dionne')
674
-
675
- @child_1, @child_2 = @pirate.birds
676
-
677
- @alternate_params = {
678
- :birds_attributes => {
679
- 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' },
680
- 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' }
681
- }
682
- }
683
- end
684
-
685
- include NestedAttributesOnACollectionAssociationTests
686
- end
687
-
688
- class TestNestedAttributesOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
689
- def setup
690
- @association_type = :has_and_belongs_to_many
691
- @association_name = :parrots
692
-
693
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
694
- @pirate.parrots.create!(:name => 'Posideons Killer')
695
- @pirate.parrots.create!(:name => 'Killer bandita Dionne')
696
-
697
- @child_1, @child_2 = @pirate.parrots
698
-
699
- @alternate_params = {
700
- :parrots_attributes => {
701
- 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' },
702
- 'bar' => { :id => @child_2.id, :name => 'Privateers Greed' }
703
- }
704
- }
705
- end
706
-
707
- include NestedAttributesOnACollectionAssociationTests
708
- end
709
-
710
- class TestNestedAttributesLimit < ActiveRecord::TestCase
711
- def setup
712
- Pirate.accepts_nested_attributes_for :parrots, :limit => 2
713
-
714
- @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
715
- end
716
-
717
- def teardown
718
- Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
719
- end
720
-
721
- def test_limit_with_less_records
722
- @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Big Big Love' } } }
723
- assert_difference('Parrot.count') { @pirate.save! }
724
- end
725
-
726
- def test_limit_with_number_exact_records
727
- @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' }, 'bar' => { :name => 'Blown Away' } } }
728
- assert_difference('Parrot.count', 2) { @pirate.save! }
729
- end
730
-
731
- def test_limit_with_exceeding_records
732
- assert_raises(ActiveRecord::NestedAttributes::TooManyRecords) do
733
- @pirate.attributes = { :parrots_attributes => { 'foo' => { :name => 'Lovely Day' },
734
- 'bar' => { :name => 'Blown Away' },
735
- 'car' => { :name => 'The Happening' }} }
736
- end
737
- end
738
- end
739
-
740
- class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
741
- fixtures :owners, :pets
742
-
743
- def setup
744
- Owner.accepts_nested_attributes_for :pets
745
-
746
- @owner = owners(:ashley)
747
- @pet1, @pet2 = pets(:chew), pets(:mochi)
748
-
749
- @params = {
750
- :pets_attributes => {
751
- '0' => { :id => @pet1.id, :name => 'Foo' },
752
- '1' => { :id => @pet2.id, :name => 'Bar' }
753
- }
754
- }
755
- end
756
-
757
- def test_should_update_existing_records_with_non_standard_primary_key
758
- @owner.update_attributes(@params)
759
- assert_equal ['Foo', 'Bar'], @owner.pets.map(&:name)
760
- end
761
- end
762
-
763
- class TestHasOneAutosaveAssoictaionWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
764
- self.use_transactional_fixtures = false
765
-
766
- def setup
767
- @pirate = Pirate.create!(:catchphrase => "My baby takes tha mornin' train!")
768
- @ship = @pirate.create_ship(:name => "The good ship Dollypop")
769
- @part = @ship.parts.create!(:name => "Mast")
770
- @trinket = @part.trinkets.create!(:name => "Necklace")
771
- end
772
-
773
- test "when great-grandchild changed in memory, saving parent should save great-grandchild" do
774
- @trinket.name = "changed"
775
- @pirate.save
776
- assert_equal "changed", @trinket.reload.name
777
- end
778
-
779
- test "when great-grandchild changed via attributes, saving parent should save great-grandchild" do
780
- @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]}}
781
- @pirate.save
782
- assert_equal "changed", @trinket.reload.name
783
- end
784
-
785
- test "when great-grandchild marked_for_destruction via attributes, saving parent should destroy great-grandchild" do
786
- @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]}}
787
- assert_difference('@part.trinkets.count', -1) { @pirate.save }
788
- end
789
-
790
- test "when great-grandchild added via attributes, saving parent should create great-grandchild" do
791
- @pirate.attributes = {:ship_attributes => {:id => @ship.id, :parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]}}
792
- assert_difference('@part.trinkets.count', 1) { @pirate.save }
793
- end
794
-
795
- test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do
796
- @trinket.name = "changed"
797
- Ship.create!(:pirate => @pirate, :name => "The Black Rock")
798
- ShipPart.create!(:ship => @ship, :name => "Stern")
799
- assert_no_queries { @pirate.valid? }
800
- end
801
- end
802
-
803
- class TestHasManyAutosaveAssoictaionWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
804
- self.use_transactional_fixtures = false
805
-
806
- def setup
807
- @ship = Ship.create!(:name => "The good ship Dollypop")
808
- @part = @ship.parts.create!(:name => "Mast")
809
- @trinket = @part.trinkets.create!(:name => "Necklace")
810
- end
811
-
812
- test "when grandchild changed in memory, saving parent should save grandchild" do
813
- @trinket.name = "changed"
814
- @ship.save
815
- assert_equal "changed", @trinket.reload.name
816
- end
817
-
818
- test "when grandchild changed via attributes, saving parent should save grandchild" do
819
- @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :name => "changed"}]}]}
820
- @ship.save
821
- assert_equal "changed", @trinket.reload.name
822
- end
823
-
824
- test "when grandchild marked_for_destruction via attributes, saving parent should destroy grandchild" do
825
- @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:id => @trinket.id, :_destroy => true}]}]}
826
- assert_difference('@part.trinkets.count', -1) { @ship.save }
827
- end
828
-
829
- test "when grandchild added via attributes, saving parent should create grandchild" do
830
- @ship.attributes = {:parts_attributes => [{:id => @part.id, :trinkets_attributes => [{:name => "created"}]}]}
831
- assert_difference('@part.trinkets.count', 1) { @ship.save }
832
- end
833
-
834
- test "when extra records exist for associations, validate (which calls nested_records_changed_for_autosave?) should not load them up" do
835
- @trinket.name = "changed"
836
- Ship.create!(:name => "The Black Rock")
837
- ShipPart.create!(:ship => @ship, :name => "Stern")
838
- assert_no_queries { @ship.valid? }
839
- end
840
- end