activerecord 2.3.18 → 3.2.22

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 (454) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1014 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +222 -0
  5. data/examples/performance.rb +100 -126
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +93 -99
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +247 -0
  10. data/lib/active_record/associations/association_scope.rb +134 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +54 -61
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +17 -59
  13. data/lib/active_record/associations/builder/association.rb +55 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +88 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  17. data/lib/active_record/associations/builder/has_many.rb +71 -0
  18. data/lib/active_record/associations/builder/has_one.rb +62 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +580 -0
  21. data/lib/active_record/associations/collection_proxy.rb +133 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +39 -119
  23. data/lib/active_record/associations/has_many_association.rb +60 -79
  24. data/lib/active_record/associations/has_many_through_association.rb +127 -206
  25. data/lib/active_record/associations/has_one_association.rb +55 -114
  26. data/lib/active_record/associations/has_one_through_association.rb +25 -26
  27. data/lib/active_record/associations/join_dependency/join_association.rb +159 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +214 -0
  31. data/lib/active_record/associations/join_helper.rb +55 -0
  32. data/lib/active_record/associations/preloader/association.rb +125 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/preloader.rb +181 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +693 -1337
  46. data/lib/active_record/attribute_assignment.rb +221 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  48. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  49. data/lib/active_record/attribute_methods/dirty.rb +111 -0
  50. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  51. data/lib/active_record/attribute_methods/query.rb +39 -0
  52. data/lib/active_record/attribute_methods/read.rb +136 -0
  53. data/lib/active_record/attribute_methods/serialization.rb +120 -0
  54. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  55. data/lib/active_record/attribute_methods/write.rb +70 -0
  56. data/lib/active_record/attribute_methods.rb +211 -339
  57. data/lib/active_record/autosave_association.rb +179 -149
  58. data/lib/active_record/base.rb +401 -2907
  59. data/lib/active_record/callbacks.rb +91 -176
  60. data/lib/active_record/coders/yaml_column.rb +41 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +236 -119
  62. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +110 -58
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +175 -74
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -35
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +71 -21
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +81 -311
  68. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +194 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +130 -83
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
  71. data/lib/active_record/connection_adapters/column.rb +296 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +280 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +272 -493
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +650 -405
  75. data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
  76. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +30 -9
  77. data/lib/active_record/connection_adapters/sqlite_adapter.rb +276 -147
  78. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  79. data/lib/active_record/counter_cache.rb +123 -0
  80. data/lib/active_record/dynamic_finder_match.rb +41 -14
  81. data/lib/active_record/dynamic_matchers.rb +84 -0
  82. data/lib/active_record/dynamic_scope_match.rb +13 -15
  83. data/lib/active_record/errors.rb +195 -0
  84. data/lib/active_record/explain.rb +86 -0
  85. data/lib/active_record/explain_subscriber.rb +25 -0
  86. data/lib/active_record/fixtures/file.rb +65 -0
  87. data/lib/active_record/fixtures.rb +695 -770
  88. data/lib/active_record/identity_map.rb +162 -0
  89. data/lib/active_record/inheritance.rb +174 -0
  90. data/lib/active_record/integration.rb +60 -0
  91. data/lib/active_record/locale/en.yml +9 -27
  92. data/lib/active_record/locking/optimistic.rb +76 -73
  93. data/lib/active_record/locking/pessimistic.rb +32 -10
  94. data/lib/active_record/log_subscriber.rb +72 -0
  95. data/lib/active_record/migration/command_recorder.rb +105 -0
  96. data/lib/active_record/migration.rb +415 -205
  97. data/lib/active_record/model_schema.rb +368 -0
  98. data/lib/active_record/nested_attributes.rb +153 -63
  99. data/lib/active_record/observer.rb +27 -103
  100. data/lib/active_record/persistence.rb +376 -0
  101. data/lib/active_record/query_cache.rb +49 -8
  102. data/lib/active_record/querying.rb +58 -0
  103. data/lib/active_record/railtie.rb +131 -0
  104. data/lib/active_record/railties/console_sandbox.rb +6 -0
  105. data/lib/active_record/railties/controller_runtime.rb +49 -0
  106. data/lib/active_record/railties/databases.rake +659 -0
  107. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  108. data/lib/active_record/readonly_attributes.rb +26 -0
  109. data/lib/active_record/reflection.rb +269 -120
  110. data/lib/active_record/relation/batches.rb +90 -0
  111. data/lib/active_record/relation/calculations.rb +372 -0
  112. data/lib/active_record/relation/delegation.rb +49 -0
  113. data/lib/active_record/relation/finder_methods.rb +402 -0
  114. data/lib/active_record/relation/predicate_builder.rb +63 -0
  115. data/lib/active_record/relation/query_methods.rb +417 -0
  116. data/lib/active_record/relation/spawn_methods.rb +180 -0
  117. data/lib/active_record/relation.rb +537 -0
  118. data/lib/active_record/result.rb +40 -0
  119. data/lib/active_record/sanitization.rb +194 -0
  120. data/lib/active_record/schema.rb +9 -6
  121. data/lib/active_record/schema_dumper.rb +55 -32
  122. data/lib/active_record/scoping/default.rb +142 -0
  123. data/lib/active_record/scoping/named.rb +200 -0
  124. data/lib/active_record/scoping.rb +152 -0
  125. data/lib/active_record/serialization.rb +8 -91
  126. data/lib/active_record/serializers/xml_serializer.rb +43 -197
  127. data/lib/active_record/session_store.rb +129 -103
  128. data/lib/active_record/store.rb +52 -0
  129. data/lib/active_record/test_case.rb +30 -23
  130. data/lib/active_record/timestamp.rb +95 -52
  131. data/lib/active_record/transactions.rb +212 -66
  132. data/lib/active_record/translation.rb +22 -0
  133. data/lib/active_record/validations/associated.rb +43 -0
  134. data/lib/active_record/validations/uniqueness.rb +180 -0
  135. data/lib/active_record/validations.rb +43 -1106
  136. data/lib/active_record/version.rb +5 -4
  137. data/lib/active_record.rb +121 -48
  138. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  139. data/lib/rails/generators/active_record/migration/templates/migration.rb +34 -0
  140. data/lib/rails/generators/active_record/migration.rb +15 -0
  141. data/lib/rails/generators/active_record/model/model_generator.rb +47 -0
  142. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  143. data/lib/rails/generators/active_record/model/templates/model.rb +12 -0
  144. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  145. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  146. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  147. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  148. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  149. data/lib/rails/generators/active_record.rb +25 -0
  150. metadata +187 -363
  151. data/CHANGELOG +0 -5904
  152. data/README +0 -351
  153. data/RUNNING_UNIT_TESTS +0 -36
  154. data/Rakefile +0 -268
  155. data/install.rb +0 -30
  156. data/lib/active_record/association_preload.rb +0 -406
  157. data/lib/active_record/associations/association_collection.rb +0 -533
  158. data/lib/active_record/associations/association_proxy.rb +0 -288
  159. data/lib/active_record/batches.rb +0 -85
  160. data/lib/active_record/calculations.rb +0 -321
  161. data/lib/active_record/dirty.rb +0 -183
  162. data/lib/active_record/named_scope.rb +0 -197
  163. data/lib/active_record/serializers/json_serializer.rb +0 -91
  164. data/lib/activerecord.rb +0 -2
  165. data/test/assets/example.log +0 -1
  166. data/test/assets/flowers.jpg +0 -0
  167. data/test/cases/aaa_create_tables_test.rb +0 -24
  168. data/test/cases/active_schema_test_mysql.rb +0 -122
  169. data/test/cases/active_schema_test_postgresql.rb +0 -24
  170. data/test/cases/adapter_test.rb +0 -144
  171. data/test/cases/aggregations_test.rb +0 -167
  172. data/test/cases/ar_schema_test.rb +0 -32
  173. data/test/cases/associations/belongs_to_associations_test.rb +0 -438
  174. data/test/cases/associations/callbacks_test.rb +0 -161
  175. data/test/cases/associations/cascaded_eager_loading_test.rb +0 -131
  176. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +0 -36
  177. data/test/cases/associations/eager_load_nested_include_test.rb +0 -131
  178. data/test/cases/associations/eager_load_nested_polymorphic_include.rb +0 -19
  179. data/test/cases/associations/eager_singularization_test.rb +0 -145
  180. data/test/cases/associations/eager_test.rb +0 -852
  181. data/test/cases/associations/extension_test.rb +0 -62
  182. data/test/cases/associations/habtm_join_table_test.rb +0 -56
  183. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +0 -827
  184. data/test/cases/associations/has_many_associations_test.rb +0 -1273
  185. data/test/cases/associations/has_many_through_associations_test.rb +0 -360
  186. data/test/cases/associations/has_one_associations_test.rb +0 -330
  187. data/test/cases/associations/has_one_through_associations_test.rb +0 -209
  188. data/test/cases/associations/inner_join_association_test.rb +0 -93
  189. data/test/cases/associations/inverse_associations_test.rb +0 -566
  190. data/test/cases/associations/join_model_test.rb +0 -712
  191. data/test/cases/associations_test.rb +0 -282
  192. data/test/cases/attribute_methods_test.rb +0 -305
  193. data/test/cases/autosave_association_test.rb +0 -1218
  194. data/test/cases/base_test.rb +0 -2166
  195. data/test/cases/batches_test.rb +0 -81
  196. data/test/cases/binary_test.rb +0 -30
  197. data/test/cases/calculations_test.rb +0 -360
  198. data/test/cases/callbacks_observers_test.rb +0 -38
  199. data/test/cases/callbacks_test.rb +0 -438
  200. data/test/cases/class_inheritable_attributes_test.rb +0 -32
  201. data/test/cases/column_alias_test.rb +0 -17
  202. data/test/cases/column_definition_test.rb +0 -70
  203. data/test/cases/connection_pool_test.rb +0 -25
  204. data/test/cases/connection_test_firebird.rb +0 -8
  205. data/test/cases/connection_test_mysql.rb +0 -65
  206. data/test/cases/copy_table_test_sqlite.rb +0 -80
  207. data/test/cases/counter_cache_test.rb +0 -84
  208. data/test/cases/database_statements_test.rb +0 -12
  209. data/test/cases/datatype_test_postgresql.rb +0 -204
  210. data/test/cases/date_time_test.rb +0 -37
  211. data/test/cases/default_test_firebird.rb +0 -16
  212. data/test/cases/defaults_test.rb +0 -111
  213. data/test/cases/deprecated_finder_test.rb +0 -30
  214. data/test/cases/dirty_test.rb +0 -316
  215. data/test/cases/finder_respond_to_test.rb +0 -76
  216. data/test/cases/finder_test.rb +0 -1098
  217. data/test/cases/fixtures_test.rb +0 -661
  218. data/test/cases/helper.rb +0 -68
  219. data/test/cases/i18n_test.rb +0 -46
  220. data/test/cases/inheritance_test.rb +0 -262
  221. data/test/cases/invalid_date_test.rb +0 -24
  222. data/test/cases/json_serialization_test.rb +0 -219
  223. data/test/cases/lifecycle_test.rb +0 -193
  224. data/test/cases/locking_test.rb +0 -350
  225. data/test/cases/method_scoping_test.rb +0 -704
  226. data/test/cases/migration_test.rb +0 -1649
  227. data/test/cases/migration_test_firebird.rb +0 -124
  228. data/test/cases/mixin_test.rb +0 -96
  229. data/test/cases/modules_test.rb +0 -109
  230. data/test/cases/multiple_db_test.rb +0 -85
  231. data/test/cases/named_scope_test.rb +0 -372
  232. data/test/cases/nested_attributes_test.rb +0 -840
  233. data/test/cases/pk_test.rb +0 -119
  234. data/test/cases/pooled_connections_test.rb +0 -103
  235. data/test/cases/query_cache_test.rb +0 -129
  236. data/test/cases/readonly_test.rb +0 -107
  237. data/test/cases/reflection_test.rb +0 -234
  238. data/test/cases/reload_models_test.rb +0 -22
  239. data/test/cases/repair_helper.rb +0 -50
  240. data/test/cases/reserved_word_test_mysql.rb +0 -176
  241. data/test/cases/sanitize_test.rb +0 -25
  242. data/test/cases/schema_authorization_test_postgresql.rb +0 -75
  243. data/test/cases/schema_dumper_test.rb +0 -211
  244. data/test/cases/schema_test_postgresql.rb +0 -178
  245. data/test/cases/serialization_test.rb +0 -47
  246. data/test/cases/sp_test_mysql.rb +0 -16
  247. data/test/cases/synonym_test_oracle.rb +0 -17
  248. data/test/cases/timestamp_test.rb +0 -75
  249. data/test/cases/transactions_test.rb +0 -543
  250. data/test/cases/unconnected_test.rb +0 -32
  251. data/test/cases/validations_i18n_test.rb +0 -925
  252. data/test/cases/validations_test.rb +0 -1684
  253. data/test/cases/xml_serialization_test.rb +0 -240
  254. data/test/cases/yaml_serialization_test.rb +0 -11
  255. data/test/config.rb +0 -5
  256. data/test/connections/jdbc_jdbcderby/connection.rb +0 -18
  257. data/test/connections/jdbc_jdbch2/connection.rb +0 -18
  258. data/test/connections/jdbc_jdbchsqldb/connection.rb +0 -18
  259. data/test/connections/jdbc_jdbcmysql/connection.rb +0 -26
  260. data/test/connections/jdbc_jdbcpostgresql/connection.rb +0 -26
  261. data/test/connections/jdbc_jdbcsqlite3/connection.rb +0 -25
  262. data/test/connections/native_db2/connection.rb +0 -25
  263. data/test/connections/native_firebird/connection.rb +0 -26
  264. data/test/connections/native_frontbase/connection.rb +0 -27
  265. data/test/connections/native_mysql/connection.rb +0 -25
  266. data/test/connections/native_openbase/connection.rb +0 -21
  267. data/test/connections/native_oracle/connection.rb +0 -27
  268. data/test/connections/native_postgresql/connection.rb +0 -21
  269. data/test/connections/native_sqlite/connection.rb +0 -25
  270. data/test/connections/native_sqlite3/connection.rb +0 -25
  271. data/test/connections/native_sqlite3/in_memory_connection.rb +0 -18
  272. data/test/connections/native_sybase/connection.rb +0 -23
  273. data/test/fixtures/accounts.yml +0 -29
  274. data/test/fixtures/all/developers.yml +0 -0
  275. data/test/fixtures/all/people.csv +0 -0
  276. data/test/fixtures/all/tasks.yml +0 -0
  277. data/test/fixtures/author_addresses.yml +0 -5
  278. data/test/fixtures/author_favorites.yml +0 -4
  279. data/test/fixtures/authors.yml +0 -9
  280. data/test/fixtures/binaries.yml +0 -132
  281. data/test/fixtures/books.yml +0 -7
  282. data/test/fixtures/categories/special_categories.yml +0 -9
  283. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +0 -4
  284. data/test/fixtures/categories.yml +0 -14
  285. data/test/fixtures/categories_ordered.yml +0 -7
  286. data/test/fixtures/categories_posts.yml +0 -23
  287. data/test/fixtures/categorizations.yml +0 -17
  288. data/test/fixtures/clubs.yml +0 -6
  289. data/test/fixtures/comments.yml +0 -59
  290. data/test/fixtures/companies.yml +0 -56
  291. data/test/fixtures/computers.yml +0 -4
  292. data/test/fixtures/courses.yml +0 -7
  293. data/test/fixtures/customers.yml +0 -26
  294. data/test/fixtures/developers.yml +0 -21
  295. data/test/fixtures/developers_projects.yml +0 -17
  296. data/test/fixtures/edges.yml +0 -6
  297. data/test/fixtures/entrants.yml +0 -14
  298. data/test/fixtures/faces.yml +0 -11
  299. data/test/fixtures/fk_test_has_fk.yml +0 -3
  300. data/test/fixtures/fk_test_has_pk.yml +0 -2
  301. data/test/fixtures/funny_jokes.yml +0 -10
  302. data/test/fixtures/interests.yml +0 -33
  303. data/test/fixtures/items.yml +0 -4
  304. data/test/fixtures/jobs.yml +0 -7
  305. data/test/fixtures/legacy_things.yml +0 -3
  306. data/test/fixtures/mateys.yml +0 -4
  307. data/test/fixtures/member_types.yml +0 -6
  308. data/test/fixtures/members.yml +0 -6
  309. data/test/fixtures/memberships.yml +0 -20
  310. data/test/fixtures/men.yml +0 -5
  311. data/test/fixtures/minimalistics.yml +0 -2
  312. data/test/fixtures/mixed_case_monkeys.yml +0 -6
  313. data/test/fixtures/mixins.yml +0 -29
  314. data/test/fixtures/movies.yml +0 -7
  315. data/test/fixtures/naked/csv/accounts.csv +0 -1
  316. data/test/fixtures/naked/yml/accounts.yml +0 -1
  317. data/test/fixtures/naked/yml/companies.yml +0 -1
  318. data/test/fixtures/naked/yml/courses.yml +0 -1
  319. data/test/fixtures/organizations.yml +0 -5
  320. data/test/fixtures/owners.yml +0 -7
  321. data/test/fixtures/parrots.yml +0 -27
  322. data/test/fixtures/parrots_pirates.yml +0 -7
  323. data/test/fixtures/people.yml +0 -15
  324. data/test/fixtures/pets.yml +0 -14
  325. data/test/fixtures/pirates.yml +0 -9
  326. data/test/fixtures/polymorphic_designs.yml +0 -19
  327. data/test/fixtures/polymorphic_prices.yml +0 -19
  328. data/test/fixtures/posts.yml +0 -52
  329. data/test/fixtures/price_estimates.yml +0 -7
  330. data/test/fixtures/projects.yml +0 -7
  331. data/test/fixtures/readers.yml +0 -9
  332. data/test/fixtures/references.yml +0 -17
  333. data/test/fixtures/reserved_words/distinct.yml +0 -5
  334. data/test/fixtures/reserved_words/distincts_selects.yml +0 -11
  335. data/test/fixtures/reserved_words/group.yml +0 -14
  336. data/test/fixtures/reserved_words/select.yml +0 -8
  337. data/test/fixtures/reserved_words/values.yml +0 -7
  338. data/test/fixtures/ships.yml +0 -5
  339. data/test/fixtures/sponsors.yml +0 -9
  340. data/test/fixtures/subscribers.yml +0 -7
  341. data/test/fixtures/subscriptions.yml +0 -12
  342. data/test/fixtures/taggings.yml +0 -28
  343. data/test/fixtures/tags.yml +0 -7
  344. data/test/fixtures/tasks.yml +0 -7
  345. data/test/fixtures/tees.yml +0 -4
  346. data/test/fixtures/ties.yml +0 -4
  347. data/test/fixtures/topics.yml +0 -42
  348. data/test/fixtures/toys.yml +0 -4
  349. data/test/fixtures/treasures.yml +0 -10
  350. data/test/fixtures/vertices.yml +0 -4
  351. data/test/fixtures/warehouse-things.yml +0 -3
  352. data/test/fixtures/zines.yml +0 -5
  353. data/test/migrations/broken/100_migration_that_raises_exception.rb +0 -10
  354. data/test/migrations/decimal/1_give_me_big_numbers.rb +0 -15
  355. data/test/migrations/duplicate/1_people_have_last_names.rb +0 -9
  356. data/test/migrations/duplicate/2_we_need_reminders.rb +0 -12
  357. data/test/migrations/duplicate/3_foo.rb +0 -7
  358. data/test/migrations/duplicate/3_innocent_jointable.rb +0 -12
  359. data/test/migrations/duplicate_names/20080507052938_chunky.rb +0 -7
  360. data/test/migrations/duplicate_names/20080507053028_chunky.rb +0 -7
  361. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +0 -12
  362. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +0 -9
  363. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +0 -12
  364. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +0 -9
  365. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +0 -8
  366. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +0 -12
  367. data/test/migrations/missing/1000_people_have_middle_names.rb +0 -9
  368. data/test/migrations/missing/1_people_have_last_names.rb +0 -9
  369. data/test/migrations/missing/3_we_need_reminders.rb +0 -12
  370. data/test/migrations/missing/4_innocent_jointable.rb +0 -12
  371. data/test/migrations/valid/1_people_have_last_names.rb +0 -9
  372. data/test/migrations/valid/2_we_need_reminders.rb +0 -12
  373. data/test/migrations/valid/3_innocent_jointable.rb +0 -12
  374. data/test/models/author.rb +0 -151
  375. data/test/models/auto_id.rb +0 -4
  376. data/test/models/binary.rb +0 -2
  377. data/test/models/bird.rb +0 -9
  378. data/test/models/book.rb +0 -4
  379. data/test/models/categorization.rb +0 -5
  380. data/test/models/category.rb +0 -34
  381. data/test/models/citation.rb +0 -6
  382. data/test/models/club.rb +0 -13
  383. data/test/models/column_name.rb +0 -3
  384. data/test/models/comment.rb +0 -29
  385. data/test/models/company.rb +0 -173
  386. data/test/models/company_in_module.rb +0 -78
  387. data/test/models/computer.rb +0 -3
  388. data/test/models/contact.rb +0 -16
  389. data/test/models/contract.rb +0 -5
  390. data/test/models/course.rb +0 -3
  391. data/test/models/customer.rb +0 -73
  392. data/test/models/default.rb +0 -2
  393. data/test/models/developer.rb +0 -101
  394. data/test/models/edge.rb +0 -5
  395. data/test/models/entrant.rb +0 -3
  396. data/test/models/essay.rb +0 -3
  397. data/test/models/event.rb +0 -3
  398. data/test/models/event_author.rb +0 -8
  399. data/test/models/face.rb +0 -7
  400. data/test/models/guid.rb +0 -2
  401. data/test/models/interest.rb +0 -5
  402. data/test/models/invoice.rb +0 -4
  403. data/test/models/item.rb +0 -7
  404. data/test/models/job.rb +0 -5
  405. data/test/models/joke.rb +0 -3
  406. data/test/models/keyboard.rb +0 -3
  407. data/test/models/legacy_thing.rb +0 -3
  408. data/test/models/line_item.rb +0 -3
  409. data/test/models/man.rb +0 -9
  410. data/test/models/matey.rb +0 -4
  411. data/test/models/member.rb +0 -12
  412. data/test/models/member_detail.rb +0 -5
  413. data/test/models/member_type.rb +0 -3
  414. data/test/models/membership.rb +0 -9
  415. data/test/models/minimalistic.rb +0 -2
  416. data/test/models/mixed_case_monkey.rb +0 -3
  417. data/test/models/movie.rb +0 -5
  418. data/test/models/order.rb +0 -4
  419. data/test/models/organization.rb +0 -6
  420. data/test/models/owner.rb +0 -5
  421. data/test/models/parrot.rb +0 -22
  422. data/test/models/person.rb +0 -16
  423. data/test/models/pet.rb +0 -5
  424. data/test/models/pirate.rb +0 -80
  425. data/test/models/polymorphic_design.rb +0 -3
  426. data/test/models/polymorphic_price.rb +0 -3
  427. data/test/models/post.rb +0 -102
  428. data/test/models/price_estimate.rb +0 -3
  429. data/test/models/project.rb +0 -30
  430. data/test/models/reader.rb +0 -4
  431. data/test/models/reference.rb +0 -4
  432. data/test/models/reply.rb +0 -46
  433. data/test/models/ship.rb +0 -19
  434. data/test/models/ship_part.rb +0 -7
  435. data/test/models/sponsor.rb +0 -4
  436. data/test/models/subject.rb +0 -4
  437. data/test/models/subscriber.rb +0 -8
  438. data/test/models/subscription.rb +0 -4
  439. data/test/models/tag.rb +0 -7
  440. data/test/models/tagging.rb +0 -10
  441. data/test/models/task.rb +0 -3
  442. data/test/models/tee.rb +0 -4
  443. data/test/models/tie.rb +0 -4
  444. data/test/models/topic.rb +0 -80
  445. data/test/models/toy.rb +0 -6
  446. data/test/models/treasure.rb +0 -8
  447. data/test/models/vertex.rb +0 -9
  448. data/test/models/warehouse_thing.rb +0 -5
  449. data/test/models/zine.rb +0 -3
  450. data/test/schema/mysql_specific_schema.rb +0 -31
  451. data/test/schema/postgresql_specific_schema.rb +0 -114
  452. data/test/schema/schema.rb +0 -550
  453. data/test/schema/schema2.rb +0 -6
  454. data/test/schema/sqlite_specific_schema.rb +0 -25
@@ -1,15 +1,22 @@
1
+ require 'active_support/core_ext/hash/except'
2
+ require 'active_support/core_ext/object/try'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+ require 'active_support/core_ext/class/attribute'
6
+
1
7
  module ActiveRecord
2
8
  module NestedAttributes #:nodoc:
3
9
  class TooManyRecords < ActiveRecordError
4
10
  end
5
11
 
6
- def self.included(base)
7
- base.extend(ClassMethods)
8
- base.class_inheritable_accessor :nested_attributes_options, :instance_writer => false
9
- base.nested_attributes_options = {}
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ class_attribute :nested_attributes_options, :instance_writer => false
16
+ self.nested_attributes_options = {}
10
17
  end
11
18
 
12
- # == Nested Attributes
19
+ # = Active Record Nested Attributes
13
20
  #
14
21
  # Nested attributes allow you to save attributes on associated records
15
22
  # through the parent. By default nested attribute updating is turned off,
@@ -19,6 +26,7 @@ module ActiveRecord
19
26
  #
20
27
  # The attribute writer is named after the association, which means that
21
28
  # in the following example, two new methods are added to your model:
29
+ #
22
30
  # <tt>author_attributes=(attributes)</tt> and
23
31
  # <tt>pages_attributes=(attributes)</tt>.
24
32
  #
@@ -45,14 +53,14 @@ module ActiveRecord
45
53
  # create the member and avatar in one go:
46
54
  #
47
55
  # params = { :member => { :name => 'Jack', :avatar_attributes => { :icon => 'smiling' } } }
48
- # member = Member.create(params)
56
+ # member = Member.create(params[:member])
49
57
  # member.avatar.id # => 2
50
58
  # member.avatar.icon # => 'smiling'
51
59
  #
52
60
  # It also allows you to update the avatar through the member:
53
61
  #
54
- # params = { :member' => { :avatar_attributes => { :id => '2', :icon => 'sad' } } }
55
- # member.update_attributes params['member']
62
+ # params = { :member => { :avatar_attributes => { :id => '2', :icon => 'sad' } } }
63
+ # member.update_attributes params[:member]
56
64
  # member.avatar.icon # => 'sad'
57
65
  #
58
66
  # By default you will only be able to set and update attributes on the
@@ -71,7 +79,7 @@ module ActiveRecord
71
79
  # member.avatar_attributes = { :id => '2', :_destroy => '1' }
72
80
  # member.avatar.marked_for_destruction? # => true
73
81
  # member.save
74
- # member.avatar #=> nil
82
+ # member.reload.avatar # => nil
75
83
  #
76
84
  # Note that the model will _not_ be destroyed until the parent is saved.
77
85
  #
@@ -84,8 +92,9 @@ module ActiveRecord
84
92
  # accepts_nested_attributes_for :posts
85
93
  # end
86
94
  #
87
- # You can now set or update attributes on an associated post model through
88
- # the attribute hash.
95
+ # You can now set or update attributes on the associated posts through
96
+ # an attribute hash for a member: include the key +:posts_attributes+
97
+ # with an array of hashes of post attributes as a value.
89
98
  #
90
99
  # For each hash that does _not_ have an <tt>id</tt> key a new record will
91
100
  # be instantiated, unless the hash also contains a <tt>_destroy</tt> key
@@ -108,10 +117,10 @@ module ActiveRecord
108
117
  # hashes if they fail to pass your criteria. For example, the previous
109
118
  # example could be rewritten as:
110
119
  #
111
- # class Member < ActiveRecord::Base
112
- # has_many :posts
113
- # accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
114
- # end
120
+ # class Member < ActiveRecord::Base
121
+ # has_many :posts
122
+ # accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
123
+ # end
115
124
  #
116
125
  # params = { :member => {
117
126
  # :name => 'joe', :posts_attributes => [
@@ -126,21 +135,21 @@ module ActiveRecord
126
135
  # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
127
136
  # member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
128
137
  #
129
- # Alternatively, :reject_if also accepts a symbol for using methods:
138
+ # Alternatively, :reject_if also accepts a symbol for using methods:
130
139
  #
131
- # class Member < ActiveRecord::Base
132
- # has_many :posts
133
- # accepts_nested_attributes_for :posts, :reject_if => :new_record?
134
- # end
140
+ # class Member < ActiveRecord::Base
141
+ # has_many :posts
142
+ # accepts_nested_attributes_for :posts, :reject_if => :new_record?
143
+ # end
135
144
  #
136
- # class Member < ActiveRecord::Base
137
- # has_many :posts
138
- # accepts_nested_attributes_for :posts, :reject_if => :reject_posts
145
+ # class Member < ActiveRecord::Base
146
+ # has_many :posts
147
+ # accepts_nested_attributes_for :posts, :reject_if => :reject_posts
139
148
  #
140
- # def reject_posts(attributed)
141
- # attributed['title].blank?
142
- # end
143
- # end
149
+ # def reject_posts(attributed)
150
+ # attributed['title'].blank?
151
+ # end
152
+ # end
144
153
  #
145
154
  # If the hash contains an <tt>id</tt> key that matches an already
146
155
  # associated record, the matching record will be modified:
@@ -173,9 +182,32 @@ module ActiveRecord
173
182
  #
174
183
  # member.attributes = params['member']
175
184
  # member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
176
- # member.posts.length #=> 2
185
+ # member.posts.length # => 2
177
186
  # member.save
178
- # member.posts.length # => 1
187
+ # member.reload.posts.length # => 1
188
+ #
189
+ # Nested attributes for an associated collection can also be passed in
190
+ # the form of a hash of hashes instead of an array of hashes:
191
+ #
192
+ # Member.create(:name => 'joe',
193
+ # :posts_attributes => { :first => { :title => 'Foo' },
194
+ # :second => { :title => 'Bar' } })
195
+ #
196
+ # has the same effect as
197
+ #
198
+ # Member.create(:name => 'joe',
199
+ # :posts_attributes => [ { :title => 'Foo' },
200
+ # { :title => 'Bar' } ])
201
+ #
202
+ # The keys of the hash which is the value for +:posts_attributes+ are
203
+ # ignored in this case.
204
+ # However, it is not allowed to use +'id'+ or +:id+ for one of
205
+ # such keys, otherwise the hash will be wrapped in an array and
206
+ # interpreted as an attribute hash for a single post.
207
+ #
208
+ # Passing attributes for an associated collection in the form of a hash
209
+ # of hashes can be used with hashes generated from HTTP/HTML parameters,
210
+ # where there maybe no natural way to submit an array of hashes.
179
211
  #
180
212
  # === Saving
181
213
  #
@@ -183,8 +215,36 @@ module ActiveRecord
183
215
  # destruction, are saved and destroyed automatically and atomically when
184
216
  # the parent model is saved. This happens inside the transaction initiated
185
217
  # by the parents save method. See ActiveRecord::AutosaveAssociation.
218
+ #
219
+ # === Using with attr_accessible
220
+ #
221
+ # The use of <tt>attr_accessible</tt> can interfere with nested attributes
222
+ # if you're not careful. For example, if the <tt>Member</tt> model above
223
+ # was using <tt>attr_accessible</tt> like this:
224
+ #
225
+ # attr_accessible :name
226
+ #
227
+ # You would need to modify it to look like this:
228
+ #
229
+ # attr_accessible :name, :posts_attributes
230
+ #
231
+ # === Validating the presence of a parent model
232
+ #
233
+ # If you want to validate that a child record is associated with a parent
234
+ # record, you can use <tt>validates_presence_of</tt> and
235
+ # <tt>inverse_of</tt> as this example illustrates:
236
+ #
237
+ # class Member < ActiveRecord::Base
238
+ # has_many :posts, :inverse_of => :member
239
+ # accepts_nested_attributes_for :posts
240
+ # end
241
+ #
242
+ # class Post < ActiveRecord::Base
243
+ # belongs_to :member, :inverse_of => :posts
244
+ # validates_presence_of :member
245
+ # end
186
246
  module ClassMethods
187
- REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
247
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
188
248
 
189
249
  # Defines an attributes writer for the specified association(s). If you
190
250
  # are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
@@ -203,10 +263,11 @@ module ActiveRecord
203
263
  # is specified, a record will be built for all attribute hashes that
204
264
  # do not have a <tt>_destroy</tt> value that evaluates to true.
205
265
  # Passing <tt>:all_blank</tt> instead of a Proc will create a proc
206
- # that will reject a record where all the attributes are blank.
266
+ # that will reject a record where all the attributes are blank excluding
267
+ # any value for _destroy.
207
268
  # [:limit]
208
269
  # Allows you to specify the maximum number of the associated records that
209
- # can be processes with the nested attributes. If the size of the
270
+ # can be processed with the nested attributes. If the size of the
210
271
  # nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
211
272
  # exception is raised. If omitted, any number associations can be processed.
212
273
  # Note that the :limit option is only applicable to one-to-many associations.
@@ -233,17 +294,25 @@ module ActiveRecord
233
294
  if reflection = reflect_on_association(association_name)
234
295
  reflection.options[:autosave] = true
235
296
  add_autosave_association_callbacks(reflection)
297
+
298
+ nested_attributes_options = self.nested_attributes_options.dup
236
299
  nested_attributes_options[association_name.to_sym] = options
300
+ self.nested_attributes_options = nested_attributes_options
301
+
237
302
  type = (reflection.collection? ? :collection : :one_to_one)
238
303
 
304
+ # remove_possible_method :pirate_attributes=
305
+ #
239
306
  # def pirate_attributes=(attributes)
240
- # assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
307
+ # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
241
308
  # end
242
- class_eval <<-EOS, __FILE__, __LINE__ + 1
309
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
310
+ remove_possible_method(:#{association_name}_attributes=)
311
+
243
312
  def #{association_name}_attributes=(attributes)
244
- assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
313
+ assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, mass_assignment_options)
245
314
  end
246
- EOS
315
+ eoruby
247
316
  else
248
317
  raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
249
318
  end
@@ -269,29 +338,28 @@ module ActiveRecord
269
338
  # Assigns the given attributes to the association.
270
339
  #
271
340
  # If update_only is false and the given attributes include an <tt>:id</tt>
272
- # that matches the existing records id, then the existing record will be
341
+ # that matches the existing record's id, then the existing record will be
273
342
  # modified. If update_only is true, a new record is only created when no
274
343
  # object exists. Otherwise a new record will be built.
275
344
  #
276
345
  # If the given attributes include a matching <tt>:id</tt> attribute, or
277
346
  # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
278
347
  # then the existing record will be marked for destruction.
279
- def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
280
- options = nested_attributes_options[association_name]
348
+ def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {})
349
+ options = self.nested_attributes_options[association_name]
281
350
  attributes = attributes.with_indifferent_access
282
- check_existing_record = (options[:update_only] || !attributes['id'].blank?)
283
351
 
284
- if check_existing_record && (record = send(association_name)) &&
352
+ if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
285
353
  (options[:update_only] || record.id.to_s == attributes['id'].to_s)
286
- assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
354
+ assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes)
287
355
 
288
- elsif attributes['id']
356
+ elsif attributes['id'].present? && !assignment_opts[:without_protection]
289
357
  raise_nested_attributes_record_not_found(association_name, attributes['id'])
290
358
 
291
359
  elsif !reject_new_record?(association_name, attributes)
292
360
  method = "build_#{association_name}"
293
361
  if respond_to?(method)
294
- send(method, attributes.except(*UNASSIGNABLE_KEYS))
362
+ send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
295
363
  else
296
364
  raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
297
365
  end
@@ -315,7 +383,7 @@ module ActiveRecord
315
383
  # })
316
384
  #
317
385
  # Will update the name of the Person with ID 1, build a new associated
318
- # person with the name `John', and mark the associatied Person with ID 2
386
+ # person with the name `John', and mark the associated Person with ID 2
319
387
  # for destruction.
320
388
  #
321
389
  # Also accepts an Array of attribute hashes:
@@ -325,8 +393,8 @@ module ActiveRecord
325
393
  # { :name => 'John' },
326
394
  # { :id => '2', :_destroy => true }
327
395
  # ])
328
- def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
329
- options = nested_attributes_options[association_name]
396
+ def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {})
397
+ options = self.nested_attributes_options[association_name]
330
398
 
331
399
  unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
332
400
  raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
@@ -337,16 +405,21 @@ module ActiveRecord
337
405
  end
338
406
 
339
407
  if attributes_collection.is_a? Hash
340
- attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
408
+ keys = attributes_collection.keys
409
+ attributes_collection = if keys.include?('id') || keys.include?(:id)
410
+ Array.wrap(attributes_collection)
411
+ else
412
+ attributes_collection.values
413
+ end
341
414
  end
342
415
 
343
- association = send(association_name)
416
+ association = association(association_name)
344
417
 
345
418
  existing_records = if association.loaded?
346
- association.to_a
419
+ association.target
347
420
  else
348
421
  attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
349
- attribute_ids.present? ? association.all(:conditions => {association.primary_key => attribute_ids}) : []
422
+ attribute_ids.empty? ? [] : association.scoped.where(association.klass.primary_key => attribute_ids)
350
423
  end
351
424
 
352
425
  attributes_collection.each do |attributes|
@@ -354,11 +427,27 @@ module ActiveRecord
354
427
 
355
428
  if attributes['id'].blank?
356
429
  unless reject_new_record?(association_name, attributes)
357
- association.build(attributes.except(*UNASSIGNABLE_KEYS))
430
+ association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
358
431
  end
359
432
  elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
360
- association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
361
- assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
433
+ unless association.loaded? || call_reject_if(association_name, attributes)
434
+ # Make sure we are operating on the actual object which is in the association's
435
+ # proxy_target array (either by finding it, or adding it if not found)
436
+ target_record = association.target.detect { |record| record == existing_record }
437
+
438
+ if target_record
439
+ existing_record = target_record
440
+ else
441
+ association.add_to_target(existing_record)
442
+ end
443
+
444
+ end
445
+
446
+ if !call_reject_if(association_name, attributes)
447
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts)
448
+ end
449
+ elsif assignment_opts[:without_protection]
450
+ association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
362
451
  else
363
452
  raise_nested_attributes_record_not_found(association_name, attributes['id'])
364
453
  end
@@ -367,12 +456,9 @@ module ActiveRecord
367
456
 
368
457
  # Updates a record with the +attributes+ or marks it for destruction if
369
458
  # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
370
- def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
371
- if has_destroy_flag?(attributes) && allow_destroy
372
- record.mark_for_destruction
373
- else
374
- record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
375
- end
459
+ def assign_to_or_mark_for_destruction(record, attributes, allow_destroy, assignment_opts)
460
+ record.assign_attributes(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
461
+ record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
376
462
  end
377
463
 
378
464
  # Determines if a hash contains a truthy _destroy key.
@@ -388,7 +474,8 @@ module ActiveRecord
388
474
  end
389
475
 
390
476
  def call_reject_if(association_name, attributes)
391
- case callback = nested_attributes_options[association_name][:reject_if]
477
+ return false if has_destroy_flag?(attributes)
478
+ case callback = self.nested_attributes_options[association_name][:reject_if]
392
479
  when Symbol
393
480
  method(callback).arity == 0 ? send(callback) : send(callback, attributes)
394
481
  when Proc
@@ -397,8 +484,11 @@ module ActiveRecord
397
484
  end
398
485
 
399
486
  def raise_nested_attributes_record_not_found(association_name, record_id)
400
- reflection = self.class.reflect_on_association(association_name)
401
- raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
487
+ raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
488
+ end
489
+
490
+ def unassignable_keys(assignment_opts)
491
+ assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS
402
492
  end
403
493
  end
404
494
  end
@@ -1,60 +1,9 @@
1
- require 'singleton'
2
- require 'set'
1
+ require 'active_support/core_ext/class/attribute'
3
2
 
4
3
  module ActiveRecord
5
- module Observing # :nodoc:
6
- def self.included(base)
7
- base.extend ClassMethods
8
- end
9
-
10
- module ClassMethods
11
- # Activates the observers assigned. Examples:
12
- #
13
- # # Calls PersonObserver.instance
14
- # ActiveRecord::Base.observers = :person_observer
15
- #
16
- # # Calls Cacher.instance and GarbageCollector.instance
17
- # ActiveRecord::Base.observers = :cacher, :garbage_collector
18
- #
19
- # # Same as above, just using explicit class references
20
- # ActiveRecord::Base.observers = Cacher, GarbageCollector
21
- #
22
- # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
23
- # called during startup, and before each development request.
24
- def observers=(*observers)
25
- @observers = observers.flatten
26
- end
27
-
28
- # Gets the current observers.
29
- def observers
30
- @observers ||= []
31
- end
32
-
33
- # Instantiate the global Active Record observers.
34
- def instantiate_observers
35
- return if @observers.blank?
36
- @observers.each do |observer|
37
- if observer.respond_to?(:to_sym) # Symbol or String
38
- observer.to_s.camelize.constantize.instance
39
- elsif observer.respond_to?(:instance)
40
- observer.instance
41
- else
42
- raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
43
- end
44
- end
45
- end
46
-
47
- protected
48
- # Notify observers when the observed class is subclassed.
49
- def inherited(subclass)
50
- super
51
- changed
52
- notify_observers :observed_class_inherited, subclass
53
- end
54
- end
55
- end
56
-
57
- # Observer classes respond to lifecycle callbacks to implement trigger-like
4
+ # = Active Record Observer
5
+ #
6
+ # Observer classes respond to life cycle callbacks to implement trigger-like
58
7
  # behavior outside the original class. This is a great way to reduce the
59
8
  # clutter that normally comes when the model class is burdened with
60
9
  # functionality that doesn't pertain to the core responsibility of the
@@ -62,7 +11,7 @@ module ActiveRecord
62
11
  #
63
12
  # class CommentObserver < ActiveRecord::Observer
64
13
  # def after_save(comment)
65
- # Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
14
+ # Notifications.comment("admin@do.com", "New comment was posted", comment).deliver
66
15
  # end
67
16
  # end
68
17
  #
@@ -118,8 +67,8 @@ module ActiveRecord
118
67
  #
119
68
  # == Configuration
120
69
  #
121
- # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
122
- # <tt>config/environment.rb</tt> file.
70
+ # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration
71
+ # setting in your <tt>config/application.rb</tt> file.
123
72
  #
124
73
  # config.active_record.observers = :comment_observer, :signup_observer
125
74
  #
@@ -139,58 +88,33 @@ module ActiveRecord
139
88
  # load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
140
89
  # singletons and that call instantiates and registers them.
141
90
  #
142
- class Observer
143
- include Singleton
144
-
145
- class << self
146
- # Attaches the observer to the supplied model classes.
147
- def observe(*models)
148
- models.flatten!
149
- models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
150
- define_method(:observed_classes) { Set.new(models) }
151
- end
152
-
153
- # The class observed by default is inferred from the observer's class name:
154
- # assert_equal Person, PersonObserver.observed_class
155
- def observed_class
156
- if observed_class_name = name[/(.*)Observer/, 1]
157
- observed_class_name.constantize
158
- else
159
- nil
160
- end
161
- end
162
- end
163
-
164
- # Start observing the declared classes and their subclasses.
165
- def initialize
166
- Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
167
- end
168
-
169
- # Send observed_method(object) if the method exists.
170
- def update(observed_method, object) #:nodoc:
171
- send(observed_method, object) if respond_to?(observed_method)
172
- end
173
-
174
- # Special method sent by the observed class when it is inherited.
175
- # Passes the new subclass.
176
- def observed_class_inherited(subclass) #:nodoc:
177
- self.class.observe(observed_classes + [subclass])
178
- add_observer!(subclass)
179
- end
91
+ class Observer < ActiveModel::Observer
180
92
 
181
93
  protected
94
+
182
95
  def observed_classes
183
- Set.new([self.class.observed_class].compact.flatten)
96
+ klasses = super
97
+ klasses + klasses.map { |klass| klass.descendants }.flatten
184
98
  end
185
99
 
186
- def observed_subclasses
187
- observed_classes.sum([]) { |klass| klass.send(:subclasses) }
100
+ def add_observer!(klass)
101
+ super
102
+ define_callbacks klass
188
103
  end
189
104
 
190
- def add_observer!(klass)
191
- klass.add_observer(self)
192
- if respond_to?(:after_find) && !klass.method_defined?(:after_find)
193
- klass.class_eval 'def after_find() end'
105
+ def define_callbacks(klass)
106
+ observer = self
107
+ observer_name = observer.class.name.underscore.gsub('/', '__')
108
+
109
+ ActiveRecord::Callbacks::CALLBACKS.each do |callback|
110
+ next unless respond_to?(callback)
111
+ callback_meth = :"_notify_#{observer_name}_for_#{callback}"
112
+ unless klass.respond_to?(callback_meth)
113
+ klass.send(:define_method, callback_meth) do |&block|
114
+ observer.update(callback, self, &block)
115
+ end
116
+ klass.send(callback, callback_meth)
117
+ end
194
118
  end
195
119
  end
196
120
  end