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,20 +1,19 @@
1
1
  module ActiveRecord
2
+ # = Active Record Aggregations
2
3
  module Aggregations # :nodoc:
3
- def self.included(base)
4
- base.extend(ClassMethods)
5
- end
4
+ extend ActiveSupport::Concern
6
5
 
7
6
  def clear_aggregation_cache #:nodoc:
8
- self.class.reflect_on_all_aggregations.to_a.each do |assoc|
9
- instance_variable_set "@#{assoc.name}", nil
10
- end unless self.new_record?
7
+ @aggregation_cache.clear if persisted?
11
8
  end
12
9
 
13
- # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
14
- # as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
15
- # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
16
- # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
17
- # and how it can be turned back into attributes (when the entity is saved to the database). Example:
10
+ # Active Record implements aggregation through a macro-like class method called +composed_of+
11
+ # for representing attributes as value objects. It expresses relationships like "Account [is]
12
+ # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
13
+ # to the macro adds a description of how the value objects are created from the attributes of
14
+ # the entity object (when the entity is initialized either as a new object or from finding an
15
+ # existing object) and how it can be turned back into attributes (when the entity is saved to
16
+ # the database).
18
17
  #
19
18
  # class Customer < ActiveRecord::Base
20
19
  # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
@@ -47,7 +46,7 @@ module ActiveRecord
47
46
  #
48
47
  # def <=>(other_money)
49
48
  # if currency == other_money.currency
50
- # amount <=> amount
49
+ # amount <=> other_money.amount
51
50
  # else
52
51
  # amount <=> other_money.exchange_to(currency).amount
53
52
  # end
@@ -69,9 +68,10 @@ module ActiveRecord
69
68
  # end
70
69
  # end
71
70
  #
72
- # Now it's possible to access attributes from the database through the value objects instead. If you choose to name the
73
- # composition the same as the attribute's name, it will be the only way to access that attribute. That's the case with our
74
- # +balance+ attribute. You interact with the value objects just like you would any other attribute, though:
71
+ # Now it's possible to access attributes from the database through the value objects instead. If
72
+ # you choose to name the composition the same as the attribute's name, it will be the only way to
73
+ # access that attribute. That's the case with our +balance+ attribute. You interact with the value
74
+ # objects just like you would any other attribute, though:
75
75
  #
76
76
  # customer.balance = Money.new(20) # sets the Money value object and the attribute
77
77
  # customer.balance # => Money value object
@@ -80,8 +80,8 @@ module ActiveRecord
80
80
  # customer.balance == Money.new(20) # => true
81
81
  # customer.balance < Money.new(5) # => false
82
82
  #
83
- # Value objects can also be composed of multiple attributes, such as the case of Address. The order of the mappings will
84
- # determine the order of the parameters. Example:
83
+ # Value objects can also be composed of multiple attributes, such as the case of Address. The order
84
+ # of the mappings will determine the order of the parameters.
85
85
  #
86
86
  # customer.address_street = "Hyancintvej"
87
87
  # customer.address_city = "Copenhagen"
@@ -92,38 +92,43 @@ module ActiveRecord
92
92
  #
93
93
  # == Writing value objects
94
94
  #
95
- # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
96
- # $5. Two Money objects both representing $5 should be equal (through methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking
97
- # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
98
- # easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
99
- # relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
95
+ # Value objects are immutable and interchangeable objects that represent a given value, such as
96
+ # a Money object representing $5. Two Money objects both representing $5 should be equal (through
97
+ # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
98
+ # unlike entity objects where equality is determined by identity. An entity class such as Customer can
99
+ # easily have two different objects that both have an address on Hyancintvej. Entity identity is
100
+ # determined by object or relational unique identifiers (such as primary keys). Normal
101
+ # ActiveRecord::Base classes are entity objects.
100
102
  #
101
- # It's also important to treat the value objects as immutable. Don't allow the Money object to have its amount changed after
102
- # creation. Create a new Money object with the new value instead. This is exemplified by the Money#exchange_to method that
103
- # returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
104
- # changed through means other than the writer method.
103
+ # It's also important to treat the value objects as immutable. Don't allow the Money object to have
104
+ # its amount changed after creation. Create a new Money object with the new value instead. This
105
+ # is exemplified by the Money#exchange_to method that returns a new value object instead of changing
106
+ # its own values. Active Record won't persist value objects that have been changed through means
107
+ # other than the writer method.
105
108
  #
106
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
107
- # change it afterwards will result in a ActiveSupport::FrozenObjectError.
109
+ # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
110
+ # object. Attempting to change it afterwards will result in a ActiveSupport::FrozenObjectError.
108
111
  #
109
- # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
110
- # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
112
+ # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
113
+ # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
111
114
  #
112
115
  # == Custom constructors and converters
113
116
  #
114
- # By default value objects are initialized by calling the <tt>new</tt> constructor of the value class passing each of the
115
- # mapped attributes, in the order specified by the <tt>:mapping</tt> option, as arguments. If the value class doesn't support
116
- # this convention then +composed_of+ allows a custom constructor to be specified.
117
+ # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
118
+ # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
119
+ # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
120
+ # a custom constructor to be specified.
117
121
  #
118
- # When a new value is assigned to the value object the default assumption is that the new value is an instance of the value
119
- # class. Specifying a custom converter allows the new value to be automatically converted to an instance of value class if
120
- # necessary.
122
+ # When a new value is assigned to the value object the default assumption is that the new value
123
+ # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
124
+ # converted to an instance of value class if necessary.
121
125
  #
122
- # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be aggregated using the
123
- # NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor for the value class is called +create+ and it
124
- # expects a CIDR address string as a parameter. New values can be assigned to the value object using either another
125
- # NetAddr::CIDR object, a string or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to
126
- # meet these requirements:
126
+ # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
127
+ # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
128
+ # for the value class is called +create+ and it expects a CIDR address string as a parameter. New
129
+ # values can be assigned to the value object using either another NetAddr::CIDR object, a string
130
+ # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
131
+ # these requirements:
127
132
  #
128
133
  # class NetworkResource < ActiveRecord::Base
129
134
  # composed_of :cidr,
@@ -150,38 +155,44 @@ module ActiveRecord
150
155
  #
151
156
  # == Finding records by a value object
152
157
  #
153
- # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance
154
- # of the value object in the conditions hash. The following example finds all customers with +balance_amount+ equal to 20 and
155
- # +balance_currency+ equal to "USD":
158
+ # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
159
+ # by specifying an instance of the value object in the conditions hash. The following example
160
+ # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
156
161
  #
157
- # Customer.find(:all, :conditions => {:balance => Money.new(20, "USD")})
162
+ # Customer.where(:balance => Money.new(20, "USD")).all
158
163
  #
159
164
  module ClassMethods
160
165
  # Adds reader and writer methods for manipulating a value object:
161
166
  # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
162
167
  #
163
168
  # Options are:
164
- # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name can't be inferred
165
- # from the part id. So <tt>composed_of :address</tt> will by default be linked to the Address class, but
166
- # if the real class name is CompanyAddress, you'll have to specify it with this option.
167
- # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value object. Each mapping
168
- # is represented as an array where the first item is the name of the entity attribute and the second item is the
169
- # name the attribute in the value object. The order in which mappings are defined determine the order in which
170
- # attributes are sent to the value class constructor.
169
+ # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
170
+ # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
171
+ # to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
172
+ # with this option.
173
+ # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
174
+ # object. Each mapping is represented as an array where the first item is the name of the
175
+ # entity attribute and the second item is the name of the attribute in the value object. The
176
+ # order in which mappings are defined determines the order in which attributes are sent to the
177
+ # value class constructor.
171
178
  # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
172
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all mapped attributes.
179
+ # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
180
+ # mapped attributes.
173
181
  # This defaults to +false+.
174
- # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that is called to
175
- # initialize the value object. The constructor is passed all of the mapped attributes, in the order that they
176
- # are defined in the <tt>:mapping option</tt>, as arguments and uses them to instantiate a <tt>:class_name</tt> object.
182
+ # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
183
+ # is called to initialize the value object. The constructor is passed all of the mapped attributes,
184
+ # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
185
+ # to instantiate a <tt>:class_name</tt> object.
177
186
  # The default is <tt>:new</tt>.
178
- # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> or a Proc that is
179
- # called when a new value is assigned to the value object. The converter is passed the single value that is used
180
- # in the assignment and is only called if the new value is not an instance of <tt>:class_name</tt>.
187
+ # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
188
+ # or a Proc that is called when a new value is assigned to the value object. The converter is
189
+ # passed the single value that is used in the assignment and is only called if the new value is
190
+ # not an instance of <tt>:class_name</tt>.
181
191
  #
182
192
  # Option examples:
183
193
  # composed_of :temperature, :mapping => %w(reading celsius)
184
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
194
+ # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
195
+ # :converter => Proc.new { |balance| balance.to_money }
185
196
  # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
186
197
  # composed_of :gps_location
187
198
  # composed_of :gps_location, :allow_nil => true
@@ -191,7 +202,7 @@ module ActiveRecord
191
202
  # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
192
203
  # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
193
204
  #
194
- def composed_of(part_id, options = {}, &block)
205
+ def composed_of(part_id, options = {})
195
206
  options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
196
207
 
197
208
  name = part_id.id2name
@@ -200,9 +211,7 @@ module ActiveRecord
200
211
  mapping = [ mapping ] unless mapping.first.is_a?(Array)
201
212
  allow_nil = options[:allow_nil] || false
202
213
  constructor = options[:constructor] || :new
203
- converter = options[:converter] || block
204
-
205
- ActiveSupport::Deprecation.warn('The conversion block has been deprecated, use the :converter option instead.', caller) if block_given?
214
+ converter = options[:converter]
206
215
 
207
216
  reader_method(name, class_name, mapping, allow_nil, constructor)
208
217
  writer_method(name, class_name, mapping, allow_nil, converter)
@@ -212,47 +221,32 @@ module ActiveRecord
212
221
 
213
222
  private
214
223
  def reader_method(name, class_name, mapping, allow_nil, constructor)
215
- module_eval do
216
- define_method(name) do |*args|
217
- force_reload = args.first || false
218
- if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
219
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
220
- object = case constructor
221
- when Symbol
222
- class_name.constantize.send(constructor, *attrs)
223
- when Proc, Method
224
- constructor.call(*attrs)
225
- else
226
- raise ArgumentError, 'Constructor must be a symbol denoting the constructor method to call or a Proc to be invoked.'
227
- end
228
- instance_variable_set("@#{name}", object)
229
- end
230
- instance_variable_get("@#{name}")
224
+ define_method(name) do
225
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
226
+ attrs = mapping.collect {|pair| read_attribute(pair.first)}
227
+ object = constructor.respond_to?(:call) ?
228
+ constructor.call(*attrs) :
229
+ class_name.constantize.send(constructor, *attrs)
230
+ @aggregation_cache[name] = object
231
231
  end
232
+ @aggregation_cache[name]
232
233
  end
233
-
234
234
  end
235
235
 
236
236
  def writer_method(name, class_name, mapping, allow_nil, converter)
237
- module_eval do
238
- define_method("#{name}=") do |part|
239
- if part.nil? && allow_nil
240
- mapping.each { |pair| self[pair.first] = nil }
241
- instance_variable_set("@#{name}", nil)
242
- else
243
- unless part.is_a?(class_name.constantize) || converter.nil?
244
- part = case converter
245
- when Symbol
246
- class_name.constantize.send(converter, part)
247
- when Proc, Method
248
- converter.call(part)
249
- else
250
- raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'
251
- end
252
- end
253
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
254
- instance_variable_set("@#{name}", part.freeze)
237
+ define_method("#{name}=") do |part|
238
+ if part.nil? && allow_nil
239
+ mapping.each { |pair| self[pair.first] = nil }
240
+ @aggregation_cache[name] = nil
241
+ else
242
+ unless part.is_a?(class_name.constantize) || converter.nil?
243
+ part = converter.respond_to?(:call) ?
244
+ converter.call(part) :
245
+ class_name.constantize.send(converter, part)
255
246
  end
247
+
248
+ mapping.each { |pair| self[pair.first] = part.send(pair.last) }
249
+ @aggregation_cache[name] = part.freeze
256
250
  end
257
251
  end
258
252
  end
@@ -0,0 +1,76 @@
1
+ require 'active_support/core_ext/string/conversions'
2
+
3
+ module ActiveRecord
4
+ module Associations
5
+ # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
6
+ # ActiveRecord::Associations::ThroughAssociationScope
7
+ class AliasTracker # :nodoc:
8
+ attr_reader :aliases, :table_joins, :connection
9
+
10
+ # table_joins is an array of arel joins which might conflict with the aliases we assign here
11
+ def initialize(connection = ActiveRecord::Model.connection, table_joins = [])
12
+ @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
13
+ @table_joins = table_joins
14
+ @connection = connection
15
+ end
16
+
17
+ def aliased_table_for(table_name, aliased_name = nil)
18
+ table_alias = aliased_name_for(table_name, aliased_name)
19
+
20
+ if table_alias == table_name
21
+ Arel::Table.new(table_name)
22
+ else
23
+ Arel::Table.new(table_name).alias(table_alias)
24
+ end
25
+ end
26
+
27
+ def aliased_name_for(table_name, aliased_name = nil)
28
+ aliased_name ||= table_name
29
+
30
+ if aliases[table_name].zero?
31
+ # If it's zero, we can have our table_name
32
+ aliases[table_name] = 1
33
+ table_name
34
+ else
35
+ # Otherwise, we need to use an alias
36
+ aliased_name = connection.table_alias_for(aliased_name)
37
+
38
+ # Update the count
39
+ aliases[aliased_name] += 1
40
+
41
+ if aliases[aliased_name] > 1
42
+ "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
43
+ else
44
+ aliased_name
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def initial_count_for(name)
52
+ return 0 if Arel::Table === table_joins
53
+
54
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
55
+ quoted_name = connection.quote_table_name(name).downcase
56
+
57
+ counts = table_joins.map do |join|
58
+ if join.is_a?(Arel::Nodes::StringJoin)
59
+ # Table names + table aliases
60
+ join.left.downcase.scan(
61
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
62
+ ).size
63
+ else
64
+ join.left.table_name == name ? 1 : 0
65
+ end
66
+ end
67
+
68
+ counts.sum
69
+ end
70
+
71
+ def truncate(name)
72
+ name.slice(0, connection.table_alias_length - 2)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,247 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+ require 'active_support/core_ext/object/inclusion'
3
+
4
+ module ActiveRecord
5
+ module Associations
6
+ # = Active Record Associations
7
+ #
8
+ # This is the root class of all associations ('+ Foo' signifies an included module Foo):
9
+ #
10
+ # Association
11
+ # SingularAssociation
12
+ # HasOneAssociation
13
+ # HasOneThroughAssociation + ThroughAssociation
14
+ # BelongsToAssociation
15
+ # BelongsToPolymorphicAssociation
16
+ # CollectionAssociation
17
+ # HasAndBelongsToManyAssociation
18
+ # HasManyAssociation
19
+ # HasManyThroughAssociation + ThroughAssociation
20
+ class Association #:nodoc:
21
+ attr_reader :owner, :target, :reflection
22
+
23
+ delegate :options, :to => :reflection
24
+
25
+ def initialize(owner, reflection)
26
+ reflection.check_validity!
27
+
28
+ @target = nil
29
+ @owner, @reflection = owner, reflection
30
+ @updated = false
31
+
32
+ reset
33
+ reset_scope
34
+ end
35
+
36
+ # Returns the name of the table of the related class:
37
+ #
38
+ # post.comments.aliased_table_name # => "comments"
39
+ #
40
+ def aliased_table_name
41
+ reflection.klass.table_name
42
+ end
43
+
44
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
45
+ def reset
46
+ @loaded = false
47
+ IdentityMap.remove(target) if IdentityMap.enabled? && target
48
+ @target = nil
49
+ @stale_state = nil
50
+ end
51
+
52
+ # Reloads the \target and returns +self+ on success.
53
+ def reload
54
+ reset
55
+ reset_scope
56
+ load_target
57
+ self unless target.nil?
58
+ end
59
+
60
+ # Has the \target been already \loaded?
61
+ def loaded?
62
+ @loaded
63
+ end
64
+
65
+ # Asserts the \target has been loaded setting the \loaded flag to +true+.
66
+ def loaded!
67
+ @loaded = true
68
+ @stale_state = stale_state
69
+ end
70
+
71
+ # The target is stale if the target no longer points to the record(s) that the
72
+ # relevant foreign_key(s) refers to. If stale, the association accessor method
73
+ # on the owner will reload the target. It's up to subclasses to implement the
74
+ # state_state method if relevant.
75
+ #
76
+ # Note that if the target has not been loaded, it is not considered stale.
77
+ def stale_target?
78
+ loaded? && @stale_state != stale_state
79
+ end
80
+
81
+ # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
82
+ def target=(target)
83
+ @target = target
84
+ loaded!
85
+ end
86
+
87
+ def scoped
88
+ target_scope.merge(association_scope)
89
+ end
90
+
91
+ # The scope for this association.
92
+ #
93
+ # Note that the association_scope is merged into the target_scope only when the
94
+ # scoped method is called. This is because at that point the call may be surrounded
95
+ # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
96
+ # actually gets built.
97
+ def association_scope
98
+ if klass
99
+ @association_scope ||= AssociationScope.new(self).scope
100
+ end
101
+ end
102
+
103
+ def reset_scope
104
+ @association_scope = nil
105
+ end
106
+
107
+ # Set the inverse association, if possible
108
+ def set_inverse_instance(record)
109
+ if record && invertible_for?(record)
110
+ inverse = record.association(inverse_reflection_for(record).name)
111
+ inverse.target = owner
112
+ end
113
+ end
114
+
115
+ # This class of the target. belongs_to polymorphic overrides this to look at the
116
+ # polymorphic_type field on the owner.
117
+ def klass
118
+ reflection.klass
119
+ end
120
+
121
+ # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
122
+ # through association's scope)
123
+ def target_scope
124
+ klass.scoped
125
+ end
126
+
127
+ # Loads the \target if needed and returns it.
128
+ #
129
+ # This method is abstract in the sense that it relies on +find_target+,
130
+ # which is expected to be provided by descendants.
131
+ #
132
+ # If the \target is stale(the target no longer points to the record(s) that the
133
+ # relevant foreign_key(s) refers to.), force reload the \target.
134
+ #
135
+ # Otherwise if the \target is already \loaded it is just returned. Thus, you can
136
+ # call +load_target+ unconditionally to get the \target.
137
+ #
138
+ # ActiveRecord::RecordNotFound is rescued within the method, and it is
139
+ # not reraised. The proxy is \reset and +nil+ is the return value.
140
+ def load_target
141
+ if (@stale_state && stale_target?) || find_target?
142
+ begin
143
+ if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
144
+ @target = IdentityMap.get(association_class, owner[reflection.foreign_key])
145
+ elsif @stale_state && stale_target?
146
+ @target = find_target
147
+ end
148
+ rescue NameError
149
+ nil
150
+ ensure
151
+ @target ||= find_target
152
+ end
153
+ end
154
+ loaded! unless loaded?
155
+ target
156
+ rescue ActiveRecord::RecordNotFound
157
+ reset
158
+ end
159
+
160
+ def interpolate(sql, record = nil)
161
+ if sql.respond_to?(:to_proc)
162
+ owner.send(:instance_exec, record, &sql)
163
+ else
164
+ sql
165
+ end
166
+ end
167
+
168
+ private
169
+
170
+ def find_target?
171
+ !loaded? && (!owner.new_record? || foreign_key_present?) && klass
172
+ end
173
+
174
+ def creation_attributes
175
+ attributes = {}
176
+
177
+ if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
178
+ attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
179
+
180
+ if reflection.options[:as]
181
+ attributes[reflection.type] = owner.class.base_class.name
182
+ end
183
+ end
184
+
185
+ attributes
186
+ end
187
+
188
+ # Sets the owner attributes on the given record
189
+ def set_owner_attributes(record)
190
+ creation_attributes.each { |key, value| record[key] = value }
191
+ end
192
+
193
+ # Should be true if there is a foreign key present on the owner which
194
+ # references the target. This is used to determine whether we can load
195
+ # the target if the owner is currently a new record (and therefore
196
+ # without a key).
197
+ #
198
+ # Currently implemented by belongs_to (vanilla and polymorphic) and
199
+ # has_one/has_many :through associations which go through a belongs_to
200
+ def foreign_key_present?
201
+ false
202
+ end
203
+
204
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
205
+ # the kind of the class of the associated objects. Meant to be used as
206
+ # a sanity check when you are about to assign an associated record.
207
+ def raise_on_type_mismatch(record)
208
+ unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
209
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
210
+ raise ActiveRecord::AssociationTypeMismatch, message
211
+ end
212
+ end
213
+
214
+ # Can be redefined by subclasses, notably polymorphic belongs_to
215
+ # The record parameter is necessary to support polymorphic inverses as we must check for
216
+ # the association in the specific class of the record.
217
+ def inverse_reflection_for(record)
218
+ reflection.inverse_of
219
+ end
220
+
221
+ # Is this association invertible? Can be redefined by subclasses.
222
+ def invertible_for?(record)
223
+ inverse_reflection_for(record)
224
+ end
225
+
226
+ # This should be implemented to return the values of the relevant key(s) on the owner,
227
+ # so that when state_state is different from the value stored on the last find_target,
228
+ # the target is stale.
229
+ #
230
+ # This is only relevant to certain associations, which is why it returns nil by default.
231
+ def stale_state
232
+ end
233
+
234
+ def association_class
235
+ @reflection.klass
236
+ end
237
+
238
+ def build_record(attributes, options)
239
+ reflection.build_association(attributes, options) do |record|
240
+ skip_assign = [reflection.foreign_key, reflection.type].compact
241
+ attributes = create_scope.except(*(record.changed - skip_assign))
242
+ record.assign_attributes(attributes, :without_protection => true)
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end