activerecord 2.3.18 → 3.0.0.beta

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

Potentially problematic release.


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

Files changed (378) hide show
  1. data/CHANGELOG +105 -34
  2. data/examples/performance.rb +3 -39
  3. data/examples/simple.rb +14 -0
  4. data/lib/active_record.rb +81 -47
  5. data/lib/active_record/aggregations.rb +1 -3
  6. data/lib/active_record/association_preload.rb +39 -54
  7. data/lib/active_record/associations.rb +262 -419
  8. data/lib/active_record/associations/association_collection.rb +85 -100
  9. data/lib/active_record/associations/association_proxy.rb +20 -18
  10. data/lib/active_record/associations/belongs_to_association.rb +8 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -35
  12. data/lib/active_record/associations/has_many_association.rb +11 -19
  13. data/lib/active_record/associations/has_many_through_association.rb +30 -183
  14. data/lib/active_record/associations/has_one_association.rb +10 -10
  15. data/lib/active_record/associations/has_one_through_association.rb +13 -11
  16. data/lib/active_record/associations/through_association_scope.rb +153 -0
  17. data/lib/active_record/attribute_methods.rb +17 -370
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +87 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +44 -0
  21. data/lib/active_record/attribute_methods/query.rb +37 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +60 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/autosave_association.rb +20 -41
  26. data/lib/active_record/base.rb +357 -1180
  27. data/lib/active_record/batches.rb +10 -16
  28. data/lib/active_record/callbacks.rb +66 -126
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +17 -13
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +5 -25
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +4 -43
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -2
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -4
  34. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  35. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -72
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +16 -49
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +15 -27
  38. data/lib/active_record/connection_adapters/postgresql_adapter.rb +84 -46
  39. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +9 -3
  40. data/lib/active_record/connection_adapters/sqlite_adapter.rb +34 -65
  41. data/lib/active_record/fixtures.rb +21 -25
  42. data/lib/active_record/locale/en.yml +9 -27
  43. data/lib/active_record/locking/optimistic.rb +16 -48
  44. data/lib/active_record/migration.rb +59 -46
  45. data/lib/active_record/named_scope.rb +85 -92
  46. data/lib/active_record/nested_attributes.rb +18 -24
  47. data/lib/active_record/observer.rb +18 -94
  48. data/lib/active_record/railtie.rb +83 -0
  49. data/lib/active_record/railties/controller_runtime.rb +38 -0
  50. data/lib/active_record/railties/databases.rake +477 -0
  51. data/lib/active_record/railties/subscriber.rb +27 -0
  52. data/lib/active_record/reflection.rb +2 -15
  53. data/lib/active_record/relation.rb +339 -0
  54. data/lib/active_record/relation/calculations.rb +259 -0
  55. data/lib/active_record/relation/finder_methods.rb +315 -0
  56. data/lib/active_record/relation/predicate_builder.rb +46 -0
  57. data/lib/active_record/relation/query_methods.rb +218 -0
  58. data/lib/active_record/relation/spawn_methods.rb +102 -0
  59. data/lib/active_record/schema_dumper.rb +10 -6
  60. data/lib/active_record/serialization.rb +31 -74
  61. data/lib/active_record/serializers/xml_serializer.rb +33 -121
  62. data/lib/active_record/session_store.rb +1 -9
  63. data/lib/active_record/test_case.rb +1 -3
  64. data/lib/active_record/timestamp.rb +7 -5
  65. data/lib/active_record/transactions.rb +9 -9
  66. data/lib/active_record/validations.rb +51 -1100
  67. data/lib/active_record/validations/associated.rb +47 -0
  68. data/lib/active_record/validations/uniqueness.rb +181 -0
  69. data/lib/active_record/version.rb +3 -3
  70. data/lib/generators/active_record.rb +30 -0
  71. data/lib/generators/active_record/migration/migration_generator.rb +25 -0
  72. data/lib/generators/active_record/migration/templates/migration.rb +11 -0
  73. data/lib/generators/active_record/model/model_generator.rb +33 -0
  74. data/lib/generators/active_record/model/templates/migration.rb +16 -0
  75. data/lib/generators/active_record/model/templates/model.rb +5 -0
  76. data/lib/generators/active_record/observer/observer_generator.rb +15 -0
  77. data/lib/generators/active_record/observer/templates/observer.rb +2 -0
  78. data/lib/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  79. data/lib/generators/active_record/session_migration/templates/migration.rb +16 -0
  80. metadata +67 -325
  81. data/RUNNING_UNIT_TESTS +0 -36
  82. data/Rakefile +0 -268
  83. data/install.rb +0 -30
  84. data/lib/active_record/calculations.rb +0 -321
  85. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -57
  86. data/lib/active_record/dirty.rb +0 -183
  87. data/lib/active_record/serializers/json_serializer.rb +0 -91
  88. data/lib/activerecord.rb +0 -2
  89. data/test/assets/example.log +0 -1
  90. data/test/assets/flowers.jpg +0 -0
  91. data/test/cases/aaa_create_tables_test.rb +0 -24
  92. data/test/cases/active_schema_test_mysql.rb +0 -122
  93. data/test/cases/active_schema_test_postgresql.rb +0 -24
  94. data/test/cases/adapter_test.rb +0 -144
  95. data/test/cases/aggregations_test.rb +0 -167
  96. data/test/cases/ar_schema_test.rb +0 -32
  97. data/test/cases/associations/belongs_to_associations_test.rb +0 -438
  98. data/test/cases/associations/callbacks_test.rb +0 -161
  99. data/test/cases/associations/cascaded_eager_loading_test.rb +0 -131
  100. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +0 -36
  101. data/test/cases/associations/eager_load_nested_include_test.rb +0 -131
  102. data/test/cases/associations/eager_load_nested_polymorphic_include.rb +0 -19
  103. data/test/cases/associations/eager_singularization_test.rb +0 -145
  104. data/test/cases/associations/eager_test.rb +0 -852
  105. data/test/cases/associations/extension_test.rb +0 -62
  106. data/test/cases/associations/habtm_join_table_test.rb +0 -56
  107. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +0 -827
  108. data/test/cases/associations/has_many_associations_test.rb +0 -1273
  109. data/test/cases/associations/has_many_through_associations_test.rb +0 -360
  110. data/test/cases/associations/has_one_associations_test.rb +0 -330
  111. data/test/cases/associations/has_one_through_associations_test.rb +0 -209
  112. data/test/cases/associations/inner_join_association_test.rb +0 -93
  113. data/test/cases/associations/inverse_associations_test.rb +0 -566
  114. data/test/cases/associations/join_model_test.rb +0 -712
  115. data/test/cases/associations_test.rb +0 -282
  116. data/test/cases/attribute_methods_test.rb +0 -305
  117. data/test/cases/autosave_association_test.rb +0 -1218
  118. data/test/cases/base_test.rb +0 -2166
  119. data/test/cases/batches_test.rb +0 -81
  120. data/test/cases/binary_test.rb +0 -30
  121. data/test/cases/calculations_test.rb +0 -360
  122. data/test/cases/callbacks_observers_test.rb +0 -38
  123. data/test/cases/callbacks_test.rb +0 -438
  124. data/test/cases/class_inheritable_attributes_test.rb +0 -32
  125. data/test/cases/column_alias_test.rb +0 -17
  126. data/test/cases/column_definition_test.rb +0 -70
  127. data/test/cases/connection_pool_test.rb +0 -25
  128. data/test/cases/connection_test_firebird.rb +0 -8
  129. data/test/cases/connection_test_mysql.rb +0 -65
  130. data/test/cases/copy_table_test_sqlite.rb +0 -80
  131. data/test/cases/counter_cache_test.rb +0 -84
  132. data/test/cases/database_statements_test.rb +0 -12
  133. data/test/cases/datatype_test_postgresql.rb +0 -204
  134. data/test/cases/date_time_test.rb +0 -37
  135. data/test/cases/default_test_firebird.rb +0 -16
  136. data/test/cases/defaults_test.rb +0 -111
  137. data/test/cases/deprecated_finder_test.rb +0 -30
  138. data/test/cases/dirty_test.rb +0 -316
  139. data/test/cases/finder_respond_to_test.rb +0 -76
  140. data/test/cases/finder_test.rb +0 -1098
  141. data/test/cases/fixtures_test.rb +0 -661
  142. data/test/cases/helper.rb +0 -68
  143. data/test/cases/i18n_test.rb +0 -46
  144. data/test/cases/inheritance_test.rb +0 -262
  145. data/test/cases/invalid_date_test.rb +0 -24
  146. data/test/cases/json_serialization_test.rb +0 -219
  147. data/test/cases/lifecycle_test.rb +0 -193
  148. data/test/cases/locking_test.rb +0 -350
  149. data/test/cases/method_scoping_test.rb +0 -704
  150. data/test/cases/migration_test.rb +0 -1649
  151. data/test/cases/migration_test_firebird.rb +0 -124
  152. data/test/cases/mixin_test.rb +0 -96
  153. data/test/cases/modules_test.rb +0 -109
  154. data/test/cases/multiple_db_test.rb +0 -85
  155. data/test/cases/named_scope_test.rb +0 -372
  156. data/test/cases/nested_attributes_test.rb +0 -840
  157. data/test/cases/pk_test.rb +0 -119
  158. data/test/cases/pooled_connections_test.rb +0 -103
  159. data/test/cases/query_cache_test.rb +0 -129
  160. data/test/cases/readonly_test.rb +0 -107
  161. data/test/cases/reflection_test.rb +0 -234
  162. data/test/cases/reload_models_test.rb +0 -22
  163. data/test/cases/repair_helper.rb +0 -50
  164. data/test/cases/reserved_word_test_mysql.rb +0 -176
  165. data/test/cases/sanitize_test.rb +0 -25
  166. data/test/cases/schema_authorization_test_postgresql.rb +0 -75
  167. data/test/cases/schema_dumper_test.rb +0 -211
  168. data/test/cases/schema_test_postgresql.rb +0 -178
  169. data/test/cases/serialization_test.rb +0 -47
  170. data/test/cases/sp_test_mysql.rb +0 -16
  171. data/test/cases/synonym_test_oracle.rb +0 -17
  172. data/test/cases/timestamp_test.rb +0 -75
  173. data/test/cases/transactions_test.rb +0 -543
  174. data/test/cases/unconnected_test.rb +0 -32
  175. data/test/cases/validations_i18n_test.rb +0 -925
  176. data/test/cases/validations_test.rb +0 -1684
  177. data/test/cases/xml_serialization_test.rb +0 -240
  178. data/test/cases/yaml_serialization_test.rb +0 -11
  179. data/test/config.rb +0 -5
  180. data/test/connections/jdbc_jdbcderby/connection.rb +0 -18
  181. data/test/connections/jdbc_jdbch2/connection.rb +0 -18
  182. data/test/connections/jdbc_jdbchsqldb/connection.rb +0 -18
  183. data/test/connections/jdbc_jdbcmysql/connection.rb +0 -26
  184. data/test/connections/jdbc_jdbcpostgresql/connection.rb +0 -26
  185. data/test/connections/jdbc_jdbcsqlite3/connection.rb +0 -25
  186. data/test/connections/native_db2/connection.rb +0 -25
  187. data/test/connections/native_firebird/connection.rb +0 -26
  188. data/test/connections/native_frontbase/connection.rb +0 -27
  189. data/test/connections/native_mysql/connection.rb +0 -25
  190. data/test/connections/native_openbase/connection.rb +0 -21
  191. data/test/connections/native_oracle/connection.rb +0 -27
  192. data/test/connections/native_postgresql/connection.rb +0 -21
  193. data/test/connections/native_sqlite/connection.rb +0 -25
  194. data/test/connections/native_sqlite3/connection.rb +0 -25
  195. data/test/connections/native_sqlite3/in_memory_connection.rb +0 -18
  196. data/test/connections/native_sybase/connection.rb +0 -23
  197. data/test/fixtures/accounts.yml +0 -29
  198. data/test/fixtures/all/developers.yml +0 -0
  199. data/test/fixtures/all/people.csv +0 -0
  200. data/test/fixtures/all/tasks.yml +0 -0
  201. data/test/fixtures/author_addresses.yml +0 -5
  202. data/test/fixtures/author_favorites.yml +0 -4
  203. data/test/fixtures/authors.yml +0 -9
  204. data/test/fixtures/binaries.yml +0 -132
  205. data/test/fixtures/books.yml +0 -7
  206. data/test/fixtures/categories.yml +0 -14
  207. data/test/fixtures/categories/special_categories.yml +0 -9
  208. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +0 -4
  209. data/test/fixtures/categories_ordered.yml +0 -7
  210. data/test/fixtures/categories_posts.yml +0 -23
  211. data/test/fixtures/categorizations.yml +0 -17
  212. data/test/fixtures/clubs.yml +0 -6
  213. data/test/fixtures/comments.yml +0 -59
  214. data/test/fixtures/companies.yml +0 -56
  215. data/test/fixtures/computers.yml +0 -4
  216. data/test/fixtures/courses.yml +0 -7
  217. data/test/fixtures/customers.yml +0 -26
  218. data/test/fixtures/developers.yml +0 -21
  219. data/test/fixtures/developers_projects.yml +0 -17
  220. data/test/fixtures/edges.yml +0 -6
  221. data/test/fixtures/entrants.yml +0 -14
  222. data/test/fixtures/faces.yml +0 -11
  223. data/test/fixtures/fk_test_has_fk.yml +0 -3
  224. data/test/fixtures/fk_test_has_pk.yml +0 -2
  225. data/test/fixtures/funny_jokes.yml +0 -10
  226. data/test/fixtures/interests.yml +0 -33
  227. data/test/fixtures/items.yml +0 -4
  228. data/test/fixtures/jobs.yml +0 -7
  229. data/test/fixtures/legacy_things.yml +0 -3
  230. data/test/fixtures/mateys.yml +0 -4
  231. data/test/fixtures/member_types.yml +0 -6
  232. data/test/fixtures/members.yml +0 -6
  233. data/test/fixtures/memberships.yml +0 -20
  234. data/test/fixtures/men.yml +0 -5
  235. data/test/fixtures/minimalistics.yml +0 -2
  236. data/test/fixtures/mixed_case_monkeys.yml +0 -6
  237. data/test/fixtures/mixins.yml +0 -29
  238. data/test/fixtures/movies.yml +0 -7
  239. data/test/fixtures/naked/csv/accounts.csv +0 -1
  240. data/test/fixtures/naked/yml/accounts.yml +0 -1
  241. data/test/fixtures/naked/yml/companies.yml +0 -1
  242. data/test/fixtures/naked/yml/courses.yml +0 -1
  243. data/test/fixtures/organizations.yml +0 -5
  244. data/test/fixtures/owners.yml +0 -7
  245. data/test/fixtures/parrots.yml +0 -27
  246. data/test/fixtures/parrots_pirates.yml +0 -7
  247. data/test/fixtures/people.yml +0 -15
  248. data/test/fixtures/pets.yml +0 -14
  249. data/test/fixtures/pirates.yml +0 -9
  250. data/test/fixtures/polymorphic_designs.yml +0 -19
  251. data/test/fixtures/polymorphic_prices.yml +0 -19
  252. data/test/fixtures/posts.yml +0 -52
  253. data/test/fixtures/price_estimates.yml +0 -7
  254. data/test/fixtures/projects.yml +0 -7
  255. data/test/fixtures/readers.yml +0 -9
  256. data/test/fixtures/references.yml +0 -17
  257. data/test/fixtures/reserved_words/distinct.yml +0 -5
  258. data/test/fixtures/reserved_words/distincts_selects.yml +0 -11
  259. data/test/fixtures/reserved_words/group.yml +0 -14
  260. data/test/fixtures/reserved_words/select.yml +0 -8
  261. data/test/fixtures/reserved_words/values.yml +0 -7
  262. data/test/fixtures/ships.yml +0 -5
  263. data/test/fixtures/sponsors.yml +0 -9
  264. data/test/fixtures/subscribers.yml +0 -7
  265. data/test/fixtures/subscriptions.yml +0 -12
  266. data/test/fixtures/taggings.yml +0 -28
  267. data/test/fixtures/tags.yml +0 -7
  268. data/test/fixtures/tasks.yml +0 -7
  269. data/test/fixtures/tees.yml +0 -4
  270. data/test/fixtures/ties.yml +0 -4
  271. data/test/fixtures/topics.yml +0 -42
  272. data/test/fixtures/toys.yml +0 -4
  273. data/test/fixtures/treasures.yml +0 -10
  274. data/test/fixtures/vertices.yml +0 -4
  275. data/test/fixtures/warehouse-things.yml +0 -3
  276. data/test/fixtures/zines.yml +0 -5
  277. data/test/migrations/broken/100_migration_that_raises_exception.rb +0 -10
  278. data/test/migrations/decimal/1_give_me_big_numbers.rb +0 -15
  279. data/test/migrations/duplicate/1_people_have_last_names.rb +0 -9
  280. data/test/migrations/duplicate/2_we_need_reminders.rb +0 -12
  281. data/test/migrations/duplicate/3_foo.rb +0 -7
  282. data/test/migrations/duplicate/3_innocent_jointable.rb +0 -12
  283. data/test/migrations/duplicate_names/20080507052938_chunky.rb +0 -7
  284. data/test/migrations/duplicate_names/20080507053028_chunky.rb +0 -7
  285. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +0 -12
  286. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +0 -9
  287. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +0 -12
  288. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +0 -9
  289. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +0 -8
  290. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +0 -12
  291. data/test/migrations/missing/1000_people_have_middle_names.rb +0 -9
  292. data/test/migrations/missing/1_people_have_last_names.rb +0 -9
  293. data/test/migrations/missing/3_we_need_reminders.rb +0 -12
  294. data/test/migrations/missing/4_innocent_jointable.rb +0 -12
  295. data/test/migrations/valid/1_people_have_last_names.rb +0 -9
  296. data/test/migrations/valid/2_we_need_reminders.rb +0 -12
  297. data/test/migrations/valid/3_innocent_jointable.rb +0 -12
  298. data/test/models/author.rb +0 -151
  299. data/test/models/auto_id.rb +0 -4
  300. data/test/models/binary.rb +0 -2
  301. data/test/models/bird.rb +0 -9
  302. data/test/models/book.rb +0 -4
  303. data/test/models/categorization.rb +0 -5
  304. data/test/models/category.rb +0 -34
  305. data/test/models/citation.rb +0 -6
  306. data/test/models/club.rb +0 -13
  307. data/test/models/column_name.rb +0 -3
  308. data/test/models/comment.rb +0 -29
  309. data/test/models/company.rb +0 -173
  310. data/test/models/company_in_module.rb +0 -78
  311. data/test/models/computer.rb +0 -3
  312. data/test/models/contact.rb +0 -16
  313. data/test/models/contract.rb +0 -5
  314. data/test/models/course.rb +0 -3
  315. data/test/models/customer.rb +0 -73
  316. data/test/models/default.rb +0 -2
  317. data/test/models/developer.rb +0 -101
  318. data/test/models/edge.rb +0 -5
  319. data/test/models/entrant.rb +0 -3
  320. data/test/models/essay.rb +0 -3
  321. data/test/models/event.rb +0 -3
  322. data/test/models/event_author.rb +0 -8
  323. data/test/models/face.rb +0 -7
  324. data/test/models/guid.rb +0 -2
  325. data/test/models/interest.rb +0 -5
  326. data/test/models/invoice.rb +0 -4
  327. data/test/models/item.rb +0 -7
  328. data/test/models/job.rb +0 -5
  329. data/test/models/joke.rb +0 -3
  330. data/test/models/keyboard.rb +0 -3
  331. data/test/models/legacy_thing.rb +0 -3
  332. data/test/models/line_item.rb +0 -3
  333. data/test/models/man.rb +0 -9
  334. data/test/models/matey.rb +0 -4
  335. data/test/models/member.rb +0 -12
  336. data/test/models/member_detail.rb +0 -5
  337. data/test/models/member_type.rb +0 -3
  338. data/test/models/membership.rb +0 -9
  339. data/test/models/minimalistic.rb +0 -2
  340. data/test/models/mixed_case_monkey.rb +0 -3
  341. data/test/models/movie.rb +0 -5
  342. data/test/models/order.rb +0 -4
  343. data/test/models/organization.rb +0 -6
  344. data/test/models/owner.rb +0 -5
  345. data/test/models/parrot.rb +0 -22
  346. data/test/models/person.rb +0 -16
  347. data/test/models/pet.rb +0 -5
  348. data/test/models/pirate.rb +0 -80
  349. data/test/models/polymorphic_design.rb +0 -3
  350. data/test/models/polymorphic_price.rb +0 -3
  351. data/test/models/post.rb +0 -102
  352. data/test/models/price_estimate.rb +0 -3
  353. data/test/models/project.rb +0 -30
  354. data/test/models/reader.rb +0 -4
  355. data/test/models/reference.rb +0 -4
  356. data/test/models/reply.rb +0 -46
  357. data/test/models/ship.rb +0 -19
  358. data/test/models/ship_part.rb +0 -7
  359. data/test/models/sponsor.rb +0 -4
  360. data/test/models/subject.rb +0 -4
  361. data/test/models/subscriber.rb +0 -8
  362. data/test/models/subscription.rb +0 -4
  363. data/test/models/tag.rb +0 -7
  364. data/test/models/tagging.rb +0 -10
  365. data/test/models/task.rb +0 -3
  366. data/test/models/tee.rb +0 -4
  367. data/test/models/tie.rb +0 -4
  368. data/test/models/topic.rb +0 -80
  369. data/test/models/toy.rb +0 -6
  370. data/test/models/treasure.rb +0 -8
  371. data/test/models/vertex.rb +0 -9
  372. data/test/models/warehouse_thing.rb +0 -5
  373. data/test/models/zine.rb +0 -3
  374. data/test/schema/mysql_specific_schema.rb +0 -31
  375. data/test/schema/postgresql_specific_schema.rb +0 -114
  376. data/test/schema/schema.rb +0 -550
  377. data/test/schema/schema2.rb +0 -6
  378. data/test/schema/sqlite_specific_schema.rb +0 -25
@@ -1,3 +1,6 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'active_support/core_ext/enumerable'
3
+
1
4
  module ActiveRecord
2
5
  class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
3
6
  def initialize(reflection, associated_class = nil)
@@ -45,7 +48,6 @@ module ActiveRecord
45
48
  super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
46
49
  end
47
50
  end
48
- HasManyThroughCantAssociateThroughHasManyReflection = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection', 'ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection')
49
51
 
50
52
  class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
51
53
  def initialize(owner, reflection)
@@ -59,6 +61,12 @@ module ActiveRecord
59
61
  end
60
62
  end
61
63
 
64
+ class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc:
65
+ def initialize(reflection)
66
+ super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).")
67
+ end
68
+ end
69
+
62
70
  class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc:
63
71
  def initialize(reflection)
64
72
  super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.")
@@ -79,6 +87,8 @@ module ActiveRecord
79
87
 
80
88
  # See ActiveRecord::Associations::ClassMethods for documentation.
81
89
  module Associations # :nodoc:
90
+ extend ActiveSupport::Concern
91
+
82
92
  # These classes will be loaded when associations are created.
83
93
  # So there is no need to eager load them.
84
94
  autoload :AssociationCollection, 'active_record/associations/association_collection'
@@ -91,10 +101,6 @@ module ActiveRecord
91
101
  autoload :HasOneAssociation, 'active_record/associations/has_one_association'
92
102
  autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
93
103
 
94
- def self.included(base)
95
- base.extend(ClassMethods)
96
- end
97
-
98
104
  # Clears out the association cache
99
105
  def clear_association_cache #:nodoc:
100
106
  self.class.reflect_on_all_associations.to_a.each do |assoc|
@@ -439,7 +445,7 @@ module ActiveRecord
439
445
  # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
440
446
  # @group.avatars # selects all avatars by going through the User join model.
441
447
  #
442
- # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
448
+ # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
443
449
  # *read-only*. For example, the following would not work following the previous example:
444
450
  #
445
451
  # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
@@ -545,16 +551,16 @@ module ActiveRecord
545
551
  #
546
552
  # Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case
547
553
  # Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
548
- #
554
+ #
549
555
  # Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
550
556
  #
551
- # will result in a single SQL query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
557
+ # This will result in a single SQL query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
552
558
  # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions like this can have unintended consequences.
553
559
  # In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole
554
560
  # and not just to the association. You must disambiguate column references for this fallback to happen, for example
555
- # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
561
+ # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
556
562
  #
557
- # If you do want eagerload only some members of an association it is usually more natural to <tt>:include</tt> an association
563
+ # If you do want eager load only some members of an association it is usually more natural to <tt>:include</tt> an association
558
564
  # which has conditions defined on it:
559
565
  #
560
566
  # class Post < ActiveRecord::Base
@@ -563,7 +569,7 @@ module ActiveRecord
563
569
  #
564
570
  # Post.find(:all, :include => :approved_comments)
565
571
  #
566
- # will load posts and eager load the +approved_comments+ association, which contains only those comments that have been approved.
572
+ # This will load posts and eager load the +approved_comments+ association, which contains only those comments that have been approved.
567
573
  #
568
574
  # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored, returning all the associated objects:
569
575
  #
@@ -586,10 +592,10 @@ module ActiveRecord
586
592
  #
587
593
  # Address.find(:all, :include => :addressable)
588
594
  #
589
- # will execute one query to load the addresses and load the addressables with one query per addressable type.
595
+ # This will execute one query to load the addresses and load the addressables with one query per addressable type.
590
596
  # For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of
591
597
  # addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback
592
- # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
598
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
593
599
  # model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query.
594
600
  #
595
601
  # == Table Aliasing
@@ -670,6 +676,60 @@ module ActiveRecord
670
676
  # end
671
677
  # end
672
678
  #
679
+ # == Bi-directional associations
680
+ #
681
+ # When you specify an association there is usually an association on the associated model that specifies the same
682
+ # relationship in reverse. For example, with the following models:
683
+ #
684
+ # class Dungeon < ActiveRecord::Base
685
+ # has_many :traps
686
+ # has_one :evil_wizard
687
+ # end
688
+ #
689
+ # class Trap < ActiveRecord::Base
690
+ # belongs_to :dungeon
691
+ # end
692
+ #
693
+ # class EvilWizard < ActiveRecord::Base
694
+ # belongs_to :dungeon
695
+ # end
696
+ #
697
+ # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are the inverse of each other and the
698
+ # inverse of the +dungeon+ association on +EvilWizard+ is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
699
+ # +ActiveRecord+ doesn't do know anything about these inverse relationships and so no object loading optimisation is possible. For example:
700
+ #
701
+ # d = Dungeon.first
702
+ # t = d.traps.first
703
+ # d.level == t.dungeon.level # => true
704
+ # d.level = 10
705
+ # d.level == t.dungeon.level # => false
706
+ #
707
+ # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to the same object data from the database, but are
708
+ # actually different in-memory copies of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
709
+ # +ActiveRecord+ about inverse relationships and it will optimise object loading. For example, if we changed our model definitions to:
710
+ #
711
+ # class Dungeon < ActiveRecord::Base
712
+ # has_many :traps, :inverse_of => :dungeon
713
+ # has_one :evil_wizard, :inverse_of => :dungeon
714
+ # end
715
+ #
716
+ # class Trap < ActiveRecord::Base
717
+ # belongs_to :dungeon, :inverse_of => :traps
718
+ # end
719
+ #
720
+ # class EvilWizard < ActiveRecord::Base
721
+ # belongs_to :dungeon, :inverse_of => :evil_wizard
722
+ # end
723
+ #
724
+ # Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same in-memory instance and our final <tt>d.level == t.dungeon.level</tt>
725
+ # will return +true+.
726
+ #
727
+ # There are limitations to <tt>:inverse_of</tt> support:
728
+ #
729
+ # * does not work with <tt>:through</tt> associations.
730
+ # * does not work with <tt>:polymorphic</tt> associations.
731
+ # * for +belongs_to+ associations +has_many+ inverse associations are ignored.
732
+ #
673
733
  # == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
674
734
  #
675
735
  # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
@@ -714,13 +774,12 @@ module ActiveRecord
714
774
  # [collection.build(attributes = {}, ...)]
715
775
  # Returns one or more new objects of the collection type that have been instantiated
716
776
  # with +attributes+ and linked to this object through a foreign key, but have not yet
717
- # been saved. <b>Note:</b> This only works if an associated object already exists, not if
718
- # it's +nil+!
777
+ # been saved.
719
778
  # [collection.create(attributes = {})]
720
779
  # Returns a new object of the collection type that has been instantiated
721
780
  # with +attributes+, linked to this object through a foreign key, and that has already
722
- # been saved (if it passed the validation). <b>Note:</b> This only works if an associated
723
- # object already exists, not if it's +nil+!
781
+ # been saved (if it passed the validation). *Note*: This only works if the base model
782
+ # already exists in the DB, not if it is a new (unsaved) record!
724
783
  #
725
784
  # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
726
785
  # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
@@ -810,6 +869,10 @@ module ActiveRecord
810
869
  # If false, don't validate the associated objects when saving the parent object. true by default.
811
870
  # [:autosave]
812
871
  # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
872
+ # [:inverse_of]
873
+ # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_many</tt>
874
+ # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
875
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail.
813
876
  #
814
877
  # Option examples:
815
878
  # has_many :comments, :order => "posted_on"
@@ -906,7 +969,7 @@ module ActiveRecord
906
969
  # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
907
970
  # [:through]
908
971
  # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
909
- # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
972
+ # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
910
973
  # <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model.
911
974
  # [:source]
912
975
  # Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
@@ -921,6 +984,10 @@ module ActiveRecord
921
984
  # If false, don't validate the associated object when saving the parent object. +false+ by default.
922
985
  # [:autosave]
923
986
  # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
987
+ # [:inverse_of]
988
+ # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_one</tt>
989
+ # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
990
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail.
924
991
  #
925
992
  # Option examples:
926
993
  # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -972,7 +1039,6 @@ module ActiveRecord
972
1039
  # A Post class declares <tt>belongs_to :author</tt>, which will add:
973
1040
  # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
974
1041
  # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
975
- # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
976
1042
  # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
977
1043
  # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
978
1044
  # The declaration can also include an options hash to specialize the behavior of the association.
@@ -1023,6 +1089,10 @@ module ActiveRecord
1023
1089
  # [:touch]
1024
1090
  # If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or
1025
1091
  # destroyed. If you specify a symbol, that attribute will be updated with the current time instead of the updated_at/on attribute.
1092
+ # [:inverse_of]
1093
+ # Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated object that is the inverse of this <tt>belongs_to</tt>
1094
+ # association. Does not work in combination with the <tt>:polymorphic</tt> options.
1095
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail.
1026
1096
  #
1027
1097
  # Option examples:
1028
1098
  # belongs_to :firm, :foreign_key => "client_of"
@@ -1163,8 +1233,8 @@ module ActiveRecord
1163
1233
  # the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
1164
1234
  # [:conditions]
1165
1235
  # Specify the conditions that the associated object must meet in order to be included as a +WHERE+
1166
- # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
1167
- # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
1236
+ # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
1237
+ # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
1168
1238
  # or <tt>@blog.posts.build</tt>.
1169
1239
  # [:order]
1170
1240
  # Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
@@ -1232,7 +1302,7 @@ module ActiveRecord
1232
1302
 
1233
1303
  private
1234
1304
  # Generates a join table name from two provided table names.
1235
- # The names in the join table namesme end up in lexicographic order.
1305
+ # The names in the join table names end up in lexicographic order.
1236
1306
  #
1237
1307
  # join_table_name("members", "clubs") # => "clubs_members"
1238
1308
  # join_table_name("members", "special_clubs") # => "members_special_clubs"
@@ -1276,17 +1346,8 @@ module ActiveRecord
1276
1346
  association = association_proxy_class.new(self, reflection)
1277
1347
  end
1278
1348
 
1279
- if association_proxy_class == HasOneThroughAssociation
1280
- association.create_through_record(new_value)
1281
- if new_record?
1282
- association_instance_set(reflection.name, new_value.nil? ? nil : association)
1283
- else
1284
- self.send(reflection.name, new_value)
1285
- end
1286
- else
1287
- association.replace(new_value)
1288
- association_instance_set(reflection.name, new_value.nil? ? nil : association)
1289
- end
1349
+ association.replace(new_value)
1350
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
1290
1351
  end
1291
1352
 
1292
1353
  define_method("set_#{reflection.name}_target") do |target|
@@ -1316,9 +1377,16 @@ module ActiveRecord
1316
1377
  if send(reflection.name).loaded? || reflection.options[:finder_sql]
1317
1378
  send(reflection.name).map(&:id)
1318
1379
  else
1319
- send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)
1380
+ if reflection.through_reflection && reflection.source_reflection.belongs_to?
1381
+ through = reflection.through_reflection
1382
+ primary_key = reflection.source_reflection.primary_key_name
1383
+ send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}")
1384
+ else
1385
+ send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id)
1386
+ end
1320
1387
  end
1321
1388
  end
1389
+
1322
1390
  end
1323
1391
 
1324
1392
  def collection_accessor_methods(reflection, association_proxy_class, writer = true)
@@ -1379,12 +1447,12 @@ module ActiveRecord
1379
1447
  "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
1380
1448
  )
1381
1449
  end
1382
-
1450
+
1383
1451
  def add_touch_callbacks(reflection, touch_attribute)
1384
1452
  method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".to_sym
1385
1453
  define_method(method_name) do
1386
1454
  association = send(reflection.name)
1387
-
1455
+
1388
1456
  if touch_attribute == true
1389
1457
  association.touch unless association.nil?
1390
1458
  else
@@ -1395,15 +1463,6 @@ module ActiveRecord
1395
1463
  after_destroy(method_name)
1396
1464
  end
1397
1465
 
1398
- def find_with_associations(options = {})
1399
- catch :invalid_query do
1400
- join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
1401
- rows = select_all_rows(options, join_dependency)
1402
- return join_dependency.instantiate(rows)
1403
- end
1404
- []
1405
- end
1406
-
1407
1466
  # Creates before_destroy callback methods that nullify, delete or destroy
1408
1467
  # has_many associated objects, according to the defined :dependent rule.
1409
1468
  #
@@ -1415,39 +1474,59 @@ module ActiveRecord
1415
1474
  # finder conditions.
1416
1475
  def configure_dependency_for_has_many(reflection, extra_conditions = nil)
1417
1476
  if reflection.options.include?(:dependent)
1477
+ # Add polymorphic type if the :as option is present
1478
+ dependent_conditions = []
1479
+ dependent_conditions << "#{reflection.primary_key_name} = \#{record.#{reflection.name}.send(:owner_quoted_id)}"
1480
+ dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]
1481
+ dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.table_name) if reflection.options[:conditions]
1482
+ dependent_conditions << extra_conditions if extra_conditions
1483
+ dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
1484
+ dependent_conditions = dependent_conditions.gsub('@', '\@')
1418
1485
  case reflection.options[:dependent]
1419
1486
  when :destroy
1420
1487
  method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
1421
1488
  define_method(method_name) do
1422
- send(reflection.name).each do |o|
1423
- # No point in executing the counter update since we're going to destroy the parent anyway
1424
- counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
1425
- if(o.respond_to? counter_method) then
1426
- class << o
1427
- self
1428
- end.send(:define_method, counter_method, Proc.new {})
1429
- end
1430
- o.destroy
1431
- end
1489
+ send(reflection.name).each { |o| o.destroy }
1432
1490
  end
1433
1491
  before_destroy method_name
1434
1492
  when :delete_all
1435
- before_destroy do |record|
1436
- record.class.send(:delete_all_has_many_dependencies,
1437
- record,
1438
- reflection.name,
1439
- reflection.klass,
1440
- reflection.dependent_conditions(record, record.class, extra_conditions))
1441
- end
1493
+ # before_destroy do |record|
1494
+ # self.class.send(:delete_all_has_many_dependencies,
1495
+ # record,
1496
+ # "posts",
1497
+ # Post,
1498
+ # %@...@) # this is a string literal like %(...)
1499
+ # end
1500
+ # end
1501
+ module_eval <<-CALLBACK
1502
+ before_destroy do |record|
1503
+ self.class.send(:delete_all_has_many_dependencies,
1504
+ record,
1505
+ "#{reflection.name}",
1506
+ #{reflection.class_name},
1507
+ %@#{dependent_conditions}@)
1508
+ end
1509
+ CALLBACK
1442
1510
  when :nullify
1443
- before_destroy do |record|
1444
- record.class.send(:nullify_has_many_dependencies,
1445
- record,
1446
- reflection.name,
1447
- reflection.klass,
1448
- reflection.primary_key_name,
1449
- reflection.dependent_conditions(record, record.class, extra_conditions))
1450
- end
1511
+ # before_destroy do |record|
1512
+ # self.class.send(:nullify_has_many_dependencies,
1513
+ # record,
1514
+ # "posts",
1515
+ # Post,
1516
+ # "user_id",
1517
+ # %@...@) # this is a string literal like %(...)
1518
+ # end
1519
+ # end
1520
+ module_eval <<-CALLBACK
1521
+ before_destroy do |record|
1522
+ self.class.send(:nullify_has_many_dependencies,
1523
+ record,
1524
+ "#{reflection.name}",
1525
+ #{reflection.class_name},
1526
+ "#{reflection.primary_key_name}",
1527
+ %@#{dependent_conditions}@)
1528
+ end
1529
+ CALLBACK
1451
1530
  else
1452
1531
  raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
1453
1532
  end
@@ -1591,198 +1670,24 @@ module ActiveRecord
1591
1670
 
1592
1671
  def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1593
1672
  options.assert_valid_keys(valid_keys_for_has_and_belongs_to_many_association)
1594
-
1595
1673
  options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1596
1674
 
1597
1675
  reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
1598
-
1676
+
1599
1677
  if reflection.association_foreign_key == reflection.primary_key_name
1600
1678
  raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
1601
1679
  end
1602
1680
 
1603
1681
  reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
1604
-
1605
- reflection
1606
- end
1607
-
1608
- def reflect_on_included_associations(associations)
1609
- [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
1610
- end
1611
-
1612
- def guard_against_unlimitable_reflections(reflections, options)
1613
- if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
1614
- raise(
1615
- ConfigurationError,
1616
- "You can not use offset and limit together with has_many or has_and_belongs_to_many associations"
1617
- )
1618
- end
1619
- end
1620
-
1621
- def select_all_rows(options, join_dependency)
1622
- connection.select_all(
1623
- construct_finder_sql_with_included_associations(options, join_dependency),
1624
- "#{name} Load Including Associations"
1625
- )
1626
- end
1627
-
1628
- def construct_finder_sql_with_included_associations(options, join_dependency)
1629
- scope = scope(:find)
1630
- sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
1631
- sql << join_dependency.join_associations.collect{|join| join.association_join }.join
1632
-
1633
- add_joins!(sql, options[:joins], scope)
1634
- add_conditions!(sql, options[:conditions], scope)
1635
- add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
1636
-
1637
- add_group!(sql, options[:group], options[:having], scope)
1638
- add_order!(sql, options[:order], scope)
1639
- add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
1640
- add_lock!(sql, options, scope)
1641
-
1642
- return sanitize_sql(sql)
1643
- end
1644
-
1645
- def add_limited_ids_condition!(sql, options, join_dependency)
1646
- unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
1647
- sql << "#{condition_word(sql)} #{connection.quote_table_name table_name}.#{primary_key} IN (#{id_list}) "
1648
- else
1649
- throw :invalid_query
1650
- end
1651
- end
1652
-
1653
- def select_limited_ids_list(options, join_dependency)
1654
- pk = columns_hash[primary_key]
1655
-
1656
- connection.select_all(
1657
- construct_finder_sql_for_association_limiting(options, join_dependency),
1658
- "#{name} Load IDs For Limited Eager Loading"
1659
- ).collect { |row| connection.quote(row[primary_key], pk) }.join(", ")
1660
- end
1661
-
1662
- def construct_finder_sql_for_association_limiting(options, join_dependency)
1663
- scope = scope(:find)
1664
-
1665
- # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
1666
- tables_from_conditions = conditions_tables(options)
1667
- tables_from_order = order_tables(options)
1668
- all_tables = tables_from_conditions + tables_from_order
1669
- distinct_join_associations = all_tables.uniq.map{|table|
1670
- join_dependency.joins_for_table_name(table)
1671
- }.flatten.compact.uniq
1672
-
1673
- order = options[:order]
1674
- if scoped_order = (scope && scope[:order])
1675
- order = order ? "#{order}, #{scoped_order}" : scoped_order
1682
+ if connection.supports_primary_key? && (connection.primary_key(reflection.options[:join_table]) rescue false)
1683
+ raise HasAndBelongsToManyAssociationWithPrimaryKeyError.new(reflection)
1676
1684
  end
1677
1685
 
1678
- is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
1679
- sql = "SELECT "
1680
- if is_distinct
1681
- sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order)
1682
- else
1683
- sql << primary_key
1684
- end
1685
- sql << " FROM #{connection.quote_table_name table_name} "
1686
-
1687
- if is_distinct
1688
- sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join
1689
- add_joins!(sql, options[:joins], scope)
1690
- end
1691
-
1692
- add_conditions!(sql, options[:conditions], scope)
1693
- add_group!(sql, options[:group], options[:having], scope)
1694
-
1695
- if order && is_distinct
1696
- connection.add_order_by_for_association_limiting!(sql, :order => order)
1697
- else
1698
- add_order!(sql, options[:order], scope)
1699
- end
1700
-
1701
- add_limit!(sql, options, scope)
1702
-
1703
- return sanitize_sql(sql)
1704
- end
1705
-
1706
- def tables_in_string(string)
1707
- return [] if string.blank?
1708
- string.scan(/([\.a-zA-Z_]+).?\./).flatten
1709
- end
1710
-
1711
- def tables_in_hash(hash)
1712
- return [] if hash.blank?
1713
- tables = hash.map do |key, value|
1714
- if value.is_a?(Hash)
1715
- key.to_s
1716
- else
1717
- tables_in_string(key) if key.is_a?(String)
1718
- end
1719
- end
1720
- tables.flatten.compact
1721
- end
1722
-
1723
- def conditions_tables(options)
1724
- # look in both sets of conditions
1725
- conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
1726
- case cond
1727
- when nil then all
1728
- when Array then all << tables_in_string(cond.first)
1729
- when Hash then all << tables_in_hash(cond)
1730
- else all << tables_in_string(cond)
1731
- end
1732
- end
1733
- conditions.flatten
1734
- end
1735
-
1736
- def order_tables(options)
1737
- order = [options[:order], scope(:find, :order) ].join(", ")
1738
- return [] unless order && order.is_a?(String)
1739
- tables_in_string(order)
1740
- end
1741
-
1742
- def selects_tables(options)
1743
- select = options[:select]
1744
- return [] unless select && select.is_a?(String)
1745
- tables_in_string(select)
1746
- end
1747
-
1748
- def joined_tables(options)
1749
- scope = scope(:find)
1750
- joins = options[:joins]
1751
- merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
1752
- [table_name] + case merged_joins
1753
- when Symbol, Hash, Array
1754
- if array_of_strings?(merged_joins)
1755
- tables_in_string(merged_joins.join(' '))
1756
- else
1757
- join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
1758
- join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
1759
- end
1760
- else
1761
- tables_in_string(merged_joins)
1762
- end
1763
- end
1764
-
1765
- # Checks if the conditions reference a table other than the current model table
1766
- def include_eager_conditions?(options, tables = nil, joined_tables = nil)
1767
- ((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
1768
- end
1769
-
1770
- # Checks if the query order references a table other than the current model's table.
1771
- def include_eager_order?(options, tables = nil, joined_tables = nil)
1772
- ((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
1773
- end
1774
-
1775
- def include_eager_select?(options, joined_tables = nil)
1776
- (selects_tables(options) - (joined_tables || joined_tables(options))).any?
1777
- end
1778
-
1779
- def references_eager_loaded_tables?(options)
1780
- joined_tables = joined_tables(options)
1781
- include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
1686
+ reflection
1782
1687
  end
1783
1688
 
1784
1689
  def using_limitable_reflections?(reflections)
1785
- reflections.none?(&:collection?)
1690
+ reflections.collect(&:collection?).length.zero?
1786
1691
  end
1787
1692
 
1788
1693
  def column_aliases(join_dependency)
@@ -1804,10 +1709,6 @@ module ActiveRecord
1804
1709
  end
1805
1710
  end
1806
1711
 
1807
- def condition_word(sql)
1808
- sql =~ /where/i ? " AND " : "WHERE "
1809
- end
1810
-
1811
1712
  def create_extension_modules(association_id, block_extension, extensions)
1812
1713
  if block_extension
1813
1714
  extension_module_name = "#{self.to_s.demodulize}#{association_id.to_s.camelize}AssociationExtension"
@@ -1870,37 +1771,22 @@ module ActiveRecord
1870
1771
  associations.keys.each do |name|
1871
1772
  reflection = base.reflections[name]
1872
1773
 
1873
- parent_records = records.map do |record|
1874
- descendant = record.send(reflection.name)
1875
- next unless descendant
1876
- descendant.target.uniq! if reflection.collection?
1877
- descendant
1878
- end.flatten.compact
1774
+ parent_records = []
1775
+ records.each do |record|
1776
+ if descendant = record.send(reflection.name)
1777
+ if reflection.collection?
1778
+ parent_records.concat descendant.target.uniq
1779
+ else
1780
+ parent_records << descendant
1781
+ end
1782
+ end
1783
+ end
1879
1784
 
1880
1785
  remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
1881
1786
  end
1882
1787
  end
1883
1788
  end
1884
1789
 
1885
- def join_for_table_name(table_name)
1886
- join = (@joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first) rescue nil
1887
- return join unless join.nil?
1888
- @joins.select{|j|j.is_a?(JoinAssociation) && j.aliased_join_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
1889
- end
1890
-
1891
- def joins_for_table_name(table_name)
1892
- join = join_for_table_name(table_name)
1893
- result = nil
1894
- if join && join.is_a?(JoinAssociation)
1895
- result = [join]
1896
- if join.parent && join.parent.is_a?(JoinAssociation)
1897
- result = joins_for_table_name(join.parent.aliased_table_name) +
1898
- result
1899
- end
1900
- end
1901
- result
1902
- end
1903
-
1904
1790
  protected
1905
1791
  def build(associations, parent = nil)
1906
1792
  parent ||= @joins.last
@@ -1924,7 +1810,6 @@ module ActiveRecord
1924
1810
  end
1925
1811
  end
1926
1812
 
1927
- # overridden in InnerJoinDependency subclass
1928
1813
  def build_join_association(reflection, parent)
1929
1814
  JoinAssociation.new(reflection, self, parent)
1930
1815
  end
@@ -1987,7 +1872,7 @@ module ActiveRecord
1987
1872
 
1988
1873
  class JoinBase # :nodoc:
1989
1874
  attr_reader :active_record, :table_joins
1990
- delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
1875
+ delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :arel_engine, :to => :active_record
1991
1876
 
1992
1877
  def initialize(active_record, joins = nil)
1993
1878
  @active_record = active_record
@@ -2049,6 +1934,7 @@ module ActiveRecord
2049
1934
  @aliased_prefix = "t#{ join_dependency.joins.size }"
2050
1935
  @parent_table_name = parent.active_record.table_name
2051
1936
  @aliased_table_name = aliased_table_name_for(table_name)
1937
+ @join = nil
2052
1938
 
2053
1939
  if reflection.macro == :has_and_belongs_to_many
2054
1940
  @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
@@ -2060,133 +1946,110 @@ module ActiveRecord
2060
1946
  end
2061
1947
 
2062
1948
  def association_join
2063
- connection = reflection.active_record.connection
2064
- join = case reflection.macro
2065
- when :has_and_belongs_to_many
2066
- " #{join_type} %s ON %s.%s = %s.%s " % [
2067
- table_alias_for(options[:join_table], aliased_join_table_name),
2068
- connection.quote_table_name(aliased_join_table_name),
2069
- options[:foreign_key] || reflection.active_record.to_s.foreign_key,
2070
- connection.quote_table_name(parent.aliased_table_name),
2071
- reflection.active_record.primary_key] +
2072
- " #{join_type} %s ON %s.%s = %s.%s " % [
2073
- table_name_and_alias,
2074
- connection.quote_table_name(aliased_table_name),
2075
- klass.primary_key,
2076
- connection.quote_table_name(aliased_join_table_name),
2077
- options[:association_foreign_key] || klass.to_s.foreign_key
2078
- ]
2079
- when :has_many, :has_one
2080
- case
2081
- when reflection.options[:through]
2082
- through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
2083
-
2084
- jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
2085
- first_key = second_key = as_extra = nil
2086
-
2087
- if through_reflection.options[:as] # has_many :through against a polymorphic join
2088
- jt_foreign_key = through_reflection.options[:as].to_s + '_id'
2089
- jt_as_extra = " AND %s.%s = %s" % [
2090
- connection.quote_table_name(aliased_join_table_name),
2091
- connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
2092
- klass.quote_value(parent.active_record.base_class.name)
2093
- ]
2094
- else
2095
- jt_foreign_key = through_reflection.primary_key_name
2096
- end
1949
+ return @join if @join
1950
+
1951
+ aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
1952
+ parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine)
1953
+
1954
+ @join = case reflection.macro
1955
+ when :has_and_belongs_to_many
1956
+ join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
1957
+ fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
1958
+ klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
1959
+
1960
+ [
1961
+ join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
1962
+ aliased_table[klass.primary_key].eq(join_table[klass_fk])
1963
+ ]
1964
+ when :has_many, :has_one
1965
+ if reflection.options[:through]
1966
+ join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
1967
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
1968
+ first_key = second_key = as_extra = nil
1969
+
1970
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
1971
+ jt_foreign_key = through_reflection.options[:as].to_s + '_id'
1972
+ jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name)
1973
+ else
1974
+ jt_foreign_key = through_reflection.primary_key_name
1975
+ end
2097
1976
 
2098
- case source_reflection.macro
2099
- when :has_many
2100
- if source_reflection.options[:as]
2101
- first_key = "#{source_reflection.options[:as]}_id"
2102
- second_key = options[:foreign_key] || primary_key
2103
- as_extra = " AND %s.%s = %s" % [
2104
- connection.quote_table_name(aliased_table_name),
2105
- connection.quote_column_name("#{source_reflection.options[:as]}_type"),
2106
- klass.quote_value(source_reflection.active_record.base_class.name)
2107
- ]
2108
- else
2109
- first_key = through_reflection.klass.base_class.to_s.foreign_key
2110
- second_key = options[:foreign_key] || primary_key
2111
- end
2112
-
2113
- unless through_reflection.klass.descends_from_active_record?
2114
- jt_sti_extra = " AND %s.%s = %s" % [
2115
- connection.quote_table_name(aliased_join_table_name),
2116
- connection.quote_column_name(through_reflection.active_record.inheritance_column),
2117
- through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
2118
- end
2119
- when :belongs_to
2120
- first_key = primary_key
2121
- if reflection.options[:source_type]
2122
- second_key = source_reflection.association_foreign_key
2123
- jt_source_extra = " AND %s.%s = %s" % [
2124
- connection.quote_table_name(aliased_join_table_name),
2125
- connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
2126
- klass.quote_value(reflection.options[:source_type])
2127
- ]
2128
- else
2129
- second_key = source_reflection.primary_key_name
2130
- end
2131
- end
1977
+ case source_reflection.macro
1978
+ when :has_many
1979
+ if source_reflection.options[:as]
1980
+ first_key = "#{source_reflection.options[:as]}_id"
1981
+ second_key = options[:foreign_key] || primary_key
1982
+ as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(source_reflection.active_record.base_class.name)
1983
+ else
1984
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
1985
+ second_key = options[:foreign_key] || primary_key
1986
+ end
2132
1987
 
2133
- " #{join_type} %s ON (%s.%s = %s.%s%s%s%s) " % [
2134
- table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
2135
- connection.quote_table_name(parent.aliased_table_name),
2136
- connection.quote_column_name(parent.primary_key),
2137
- connection.quote_table_name(aliased_join_table_name),
2138
- connection.quote_column_name(jt_foreign_key),
2139
- jt_as_extra, jt_source_extra, jt_sti_extra
2140
- ] +
2141
- " #{join_type} %s ON (%s.%s = %s.%s%s) " % [
2142
- table_name_and_alias,
2143
- connection.quote_table_name(aliased_table_name),
2144
- connection.quote_column_name(first_key),
2145
- connection.quote_table_name(aliased_join_table_name),
2146
- connection.quote_column_name(second_key),
2147
- as_extra
2148
- ]
2149
-
2150
- when reflection.options[:as] && [:has_many, :has_one].include?(reflection.macro)
2151
- " #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %s" % [
2152
- table_name_and_alias,
2153
- connection.quote_table_name(aliased_table_name),
2154
- "#{reflection.options[:as]}_id",
2155
- connection.quote_table_name(parent.aliased_table_name),
2156
- parent.primary_key,
2157
- connection.quote_table_name(aliased_table_name),
2158
- "#{reflection.options[:as]}_type",
2159
- klass.quote_value(parent.active_record.base_class.name)
2160
- ]
1988
+ unless through_reflection.klass.descends_from_active_record?
1989
+ jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
1990
+ end
1991
+ when :belongs_to
1992
+ first_key = primary_key
1993
+ if reflection.options[:source_type]
1994
+ second_key = source_reflection.association_foreign_key
1995
+ jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type])
2161
1996
  else
2162
- foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
2163
- " #{join_type} %s ON %s.%s = %s.%s " % [
2164
- table_name_and_alias,
2165
- aliased_table_name,
2166
- foreign_key,
2167
- parent.aliased_table_name,
2168
- reflection.options[:primary_key] || parent.primary_key
2169
- ]
1997
+ second_key = source_reflection.primary_key_name
1998
+ end
2170
1999
  end
2171
- when :belongs_to
2172
- " #{join_type} %s ON %s.%s = %s.%s " % [
2173
- table_name_and_alias,
2174
- connection.quote_table_name(aliased_table_name),
2175
- reflection.options[:primary_key] || reflection.klass.primary_key,
2176
- connection.quote_table_name(parent.aliased_table_name),
2177
- options[:foreign_key] || reflection.primary_key_name
2178
- ]
2000
+
2001
+ [
2002
+ [parent_table[parent.primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? },
2003
+ aliased_table[first_key].eq(join_table[second_key])
2004
+ ]
2005
+ elsif reflection.options[:as]
2006
+ id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
2007
+ type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name)
2008
+ [id_rel, type_rel]
2179
2009
  else
2180
- ""
2181
- end || ''
2182
- join << %(AND %s) % [
2183
- klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
2010
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
2011
+ [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])]
2012
+ end
2013
+ when :belongs_to
2014
+ [aliased_table[reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])]
2015
+ end
2016
+
2017
+ unless klass.descends_from_active_record?
2018
+ sti_column = aliased_table[klass.inheritance_column]
2019
+ sti_condition = sti_column.eq(klass.sti_name)
2020
+ klass.send(:subclasses).each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
2021
+
2022
+ @join << sti_condition
2023
+ end
2184
2024
 
2185
2025
  [through_reflection, reflection].each do |ref|
2186
- join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions]
2026
+ if ref && ref.options[:conditions]
2027
+ @join << interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))
2028
+ end
2187
2029
  end
2188
2030
 
2189
- join
2031
+ @join
2032
+ end
2033
+
2034
+ def relation
2035
+ aliased = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
2036
+
2037
+ if reflection.macro == :has_and_belongs_to_many
2038
+ [Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine), aliased]
2039
+ elsif reflection.options[:through]
2040
+ [Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine), aliased]
2041
+ else
2042
+ aliased
2043
+ end
2044
+ end
2045
+
2046
+ def join_relation(joining_relation, join = nil)
2047
+ if (relations = relation).is_a?(Array)
2048
+ joining_relation.joins(Relation::JoinOperation.new(relations.first, Arel::OuterJoin, association_join.first)).
2049
+ joins(Relation::JoinOperation.new(relations.last, Arel::OuterJoin, association_join.last))
2050
+ else
2051
+ joining_relation.joins(Relation::JoinOperation.new(relations, Arel::OuterJoin, association_join))
2052
+ end
2190
2053
  end
2191
2054
 
2192
2055
  protected
@@ -2214,7 +2077,7 @@ module ActiveRecord
2214
2077
  end
2215
2078
 
2216
2079
  def table_alias_for(table_name, table_alias)
2217
- "#{reflection.active_record.connection.quote_table_name(table_name)} #{table_alias if table_name != table_alias}".strip
2080
+ "#{table_name} #{table_alias if table_name != table_alias}".strip
2218
2081
  end
2219
2082
 
2220
2083
  def table_name_and_alias
@@ -2224,28 +2087,8 @@ module ActiveRecord
2224
2087
  def interpolate_sql(sql)
2225
2088
  instance_eval("%@#{sql.gsub('@', '\@')}@")
2226
2089
  end
2227
-
2228
- private
2229
- def join_type
2230
- "LEFT OUTER JOIN"
2231
- end
2232
2090
  end
2233
2091
  end
2234
-
2235
- class InnerJoinDependency < JoinDependency # :nodoc:
2236
- protected
2237
- def build_join_association(reflection, parent)
2238
- InnerJoinAssociation.new(reflection, self, parent)
2239
- end
2240
-
2241
- class InnerJoinAssociation < JoinAssociation
2242
- private
2243
- def join_type
2244
- "INNER JOIN"
2245
- end
2246
- end
2247
- end
2248
-
2249
2092
  end
2250
2093
  end
2251
2094
  end