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
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  # ones created with +build+ are added to the target. So, the target may be
12
12
  # non-empty and still lack children waiting to be read from the database.
13
13
  # If you look directly to the database you cannot assume that's the entire
14
- # collection because new records may have beed added to the target, etc.
14
+ # collection because new records may have been added to the target, etc.
15
15
  #
16
16
  # If you need to work on all current children, new and existing records,
17
17
  # +load_target+ and the +loaded+ flag are your friends.
@@ -20,7 +20,22 @@ module ActiveRecord
20
20
  super
21
21
  construct_sql
22
22
  end
23
-
23
+
24
+ delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
25
+
26
+ def select(select = nil, &block)
27
+ if block_given?
28
+ load_target
29
+ @target.select(&block)
30
+ else
31
+ scoped.select(select)
32
+ end
33
+ end
34
+
35
+ def scoped
36
+ with_scope(construct_scope) { @reflection.klass.scoped }
37
+ end
38
+
24
39
  def find(*args)
25
40
  options = args.extract_options!
26
41
 
@@ -37,27 +52,24 @@ module ActiveRecord
37
52
  load_target.select { |r| ids.include?(r.id) }
38
53
  end
39
54
  else
40
- conditions = "#{@finder_sql}"
41
- if sanitized_conditions = sanitize_sql(options[:conditions])
42
- conditions << " AND (#{sanitized_conditions})"
43
- end
44
-
45
- options[:conditions] = conditions
55
+ merge_options_from_reflection!(options)
56
+ construct_find_options!(options)
57
+
58
+ find_scope = construct_scope[:find].slice(:conditions, :order)
46
59
 
47
- if options[:order] && @reflection.options[:order]
48
- options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
49
- elsif @reflection.options[:order]
50
- options[:order] = @reflection.options[:order]
60
+ with_scope(:find => find_scope) do
61
+ relation = @reflection.klass.send(:construct_finder_arel, options, @reflection.klass.send(:current_scoped_methods))
62
+
63
+ case args.first
64
+ when :first, :last
65
+ relation.send(args.first)
66
+ when :all
67
+ records = relation.all
68
+ @reflection.options[:uniq] ? uniq(records) : records
69
+ else
70
+ relation.find(*args)
71
+ end
51
72
  end
52
-
53
- # Build options specific to association
54
- construct_find_options!(options)
55
-
56
- merge_options_from_reflection!(options)
57
-
58
- # Pass through args exactly as we received them.
59
- args << options
60
- @reflection.klass.find(*args)
61
73
  end
62
74
  end
63
75
 
@@ -164,14 +176,15 @@ module ActiveRecord
164
176
  # be used for the query. If no +:counter_sql+ was supplied, but +:finder_sql+ was set, the
165
177
  # descendant's +construct_sql+ method will have set :counter_sql automatically.
166
178
  # Otherwise, construct options and pass them with scope to the target class's +count+.
167
- def count(*args)
179
+ def count(column_name = nil, options = {})
168
180
  if @reflection.options[:counter_sql]
169
181
  @reflection.klass.count_by_sql(@counter_sql)
170
182
  else
171
- column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
183
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
184
+
172
185
  if @reflection.options[:uniq]
173
186
  # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
174
- column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
187
+ column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" unless column_name
175
188
  options.merge!(:distinct => true)
176
189
  end
177
190
 
@@ -229,14 +242,13 @@ module ActiveRecord
229
242
  self
230
243
  end
231
244
 
232
- # Destory all the records from this association.
245
+ # Destroy all the records from this association.
233
246
  #
234
247
  # See destroy for more info.
235
248
  def destroy_all
236
249
  load_target
237
- destroy(@target).tap do
238
- reset_target!
239
- end
250
+ destroy(@target)
251
+ reset_target!
240
252
  end
241
253
 
242
254
  def create(attrs = {})
@@ -304,6 +316,15 @@ module ActiveRecord
304
316
  end
305
317
  end
306
318
 
319
+ # Returns true if the collection has more than 1 record. Equivalent to collection.size > 1.
320
+ def many?
321
+ if block_given?
322
+ method_missing(:many?) { |*block_args| yield(*block_args) }
323
+ else
324
+ size > 1
325
+ end
326
+ end
327
+
307
328
  def uniq(collection = self)
308
329
  seen = Set.new
309
330
  collection.inject([]) do |kept, record|
@@ -332,7 +353,6 @@ module ActiveRecord
332
353
 
333
354
  def include?(record)
334
355
  return false unless record.is_a?(@reflection.klass)
335
- return include_in_memory?(record) if record.new_record?
336
356
  load_target if @reflection.options[:finder_sql] && !loaded?
337
357
  return @target.include?(record) if loaded?
338
358
  exists?(record)
@@ -345,22 +365,25 @@ module ActiveRecord
345
365
  protected
346
366
  def construct_find_options!(options)
347
367
  end
348
-
368
+
369
+ def construct_counter_sql
370
+ if @reflection.options[:counter_sql]
371
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
372
+ elsif @reflection.options[:finder_sql]
373
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
374
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
375
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
376
+ else
377
+ @counter_sql = @finder_sql
378
+ end
379
+ end
380
+
349
381
  def load_target
350
382
  if !@owner.new_record? || foreign_key_present
351
383
  begin
352
384
  if !loaded?
353
385
  if @target.is_a?(Array) && @target.any?
354
- @target = find_target.map do |f|
355
- i = @target.index(f)
356
- t = @target.delete_at(i) if i
357
- if t && t.changed?
358
- t
359
- else
360
- f.mark_for_destruction if t && t.marked_for_destruction?
361
- f
362
- end
363
- end + @target.find_all {|t| t.new_record?}
386
+ @target = find_target + @target.find_all {|t| t.new_record? }
364
387
  else
365
388
  @target = find_target
366
389
  end
@@ -373,29 +396,15 @@ module ActiveRecord
373
396
  loaded if target
374
397
  target
375
398
  end
376
-
377
- def method_missing(method, *args, &block)
378
- case method.to_s
379
- when 'find_or_create'
380
- return find(:first, :conditions => args.first) || create(args.first)
381
- when /^find_or_create_by_(.*)$/
382
- rest = $1
383
- find_args = pull_finder_args_from(DynamicFinderMatch.match(method).attribute_names, *args)
384
- return send("find_by_#{rest}", *find_args) ||
385
- method_missing("create_by_#{rest}", *args, &block)
386
- when /^create_by_(.*)$/
387
- return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h }, &block)
388
- end
389
399
 
400
+ def method_missing(method, *args)
390
401
  if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
391
402
  if block_given?
392
403
  super { |*block_args| yield(*block_args) }
393
404
  else
394
405
  super
395
406
  end
396
- elsif @reflection.klass.scopes.include?(method)
397
- @reflection.klass.scopes[method].call(self, *args)
398
- else
407
+ else
399
408
  with_scope(construct_scope) do
400
409
  if block_given?
401
410
  @reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
@@ -430,36 +439,7 @@ module ActiveRecord
430
439
  records
431
440
  end
432
441
 
433
- def add_record_to_target_with_callbacks(record)
434
- callback(:before_add, record)
435
- yield(record) if block_given?
436
- @target ||= [] unless loaded?
437
- @target << record unless @reflection.options[:uniq] && @target.include?(record)
438
- callback(:after_add, record)
439
- set_inverse_instance(record, @owner)
440
- record
441
- end
442
-
443
442
  private
444
- # Separate the "finder" args from the "create" args given to a
445
- # find_or_create_by_ call. Returns an array with the
446
- # parameter values in the same order as the keys in the
447
- # "names" array. This code was based on code in base.rb's
448
- # method_missing method.
449
- def pull_finder_args_from(names, *args)
450
- attributes = names.collect { |name| name.intern }
451
- attribute_hash = {}
452
- args.each_with_index do |arg, i|
453
- if arg.is_a?(Hash)
454
- attribute_hash.merge! arg
455
- else
456
- attribute_hash[attributes[i]] = arg
457
- end
458
- end
459
- attribute_hash = attribute_hash.with_indifferent_access
460
- attributes.collect { |attr| attribute_hash[attr] }
461
- end
462
-
463
443
  def create_record(attrs)
464
444
  attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
465
445
  ensure_owner_is_not_new
@@ -483,6 +463,16 @@ module ActiveRecord
483
463
  end
484
464
  end
485
465
 
466
+ def add_record_to_target_with_callbacks(record)
467
+ callback(:before_add, record)
468
+ yield(record) if block_given?
469
+ @target ||= [] unless loaded?
470
+ @target << record unless @reflection.options[:uniq] && @target.include?(record)
471
+ callback(:after_add, record)
472
+ set_inverse_instance(record, @owner)
473
+ record
474
+ end
475
+
486
476
  def remove_records(*records)
487
477
  records = flatten_deeper(records)
488
478
  records.each { |record| raise_on_type_mismatch(record) }
@@ -497,15 +487,22 @@ module ActiveRecord
497
487
 
498
488
  def callback(method, record)
499
489
  callbacks_for(method).each do |callback|
500
- ActiveSupport::Callbacks::Callback.new(method, callback, record).call(@owner, record)
490
+ case callback
491
+ when Symbol
492
+ @owner.send(callback, record)
493
+ when Proc
494
+ callback.call(@owner, record)
495
+ else
496
+ callback.send(method, @owner, record)
497
+ end
501
498
  end
502
499
  end
503
500
 
504
501
  def callbacks_for(callback_name)
505
502
  full_callback_name = "#{callback_name}_for_#{@reflection.name}"
506
503
  @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
507
- end
508
-
504
+ end
505
+
509
506
  def ensure_owner_is_not_new
510
507
  if @owner.new_record?
511
508
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
@@ -516,18 +513,6 @@ module ActiveRecord
516
513
  args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
517
514
  @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
518
515
  end
519
-
520
- def include_in_memory?(record)
521
- if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
522
- @owner.send(proxy_reflection.through_reflection.name.to_sym).each do |source|
523
- source_reflection_target = source.send(proxy_reflection.source_reflection.name)
524
- return true if source_reflection_target.respond_to?(:include?) ? source_reflection_target.include?(record) : source_reflection_target == record
525
- end
526
- false
527
- else
528
- @target.include?(record)
529
- end
530
- end
531
516
  end
532
517
  end
533
518
  end
@@ -156,21 +156,12 @@ module ActiveRecord
156
156
  @reflection.options[:dependent]
157
157
  end
158
158
 
159
- # Returns a string with the IDs of +records+ joined with a comma, quoted
160
- # if needed. The result is ready to be inserted into a SQL IN clause.
161
- #
162
- # quoted_record_ids(records) # => "23,56,58,67"
163
- #
164
- def quoted_record_ids(records)
165
- records.map { |record| record.quoted_id }.join(',')
166
- end
167
-
168
159
  def interpolate_sql(sql, record = nil)
169
160
  @owner.send(:interpolate_sql, sql, record)
170
161
  end
171
162
 
172
163
  # Forwards the call to the reflection class.
173
- def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name)
164
+ def sanitize_sql(sql, table_name = @reflection.klass.table_name)
174
165
  @reflection.klass.send(:sanitize_sql, sql, table_name)
175
166
  end
176
167
 
@@ -209,12 +200,17 @@ module ActiveRecord
209
200
 
210
201
  private
211
202
  # Forwards any missing method call to the \target.
212
- def method_missing(method, *args, &block)
203
+ def method_missing(method, *args)
213
204
  if load_target
214
- if @target.respond_to?(method)
215
- @target.send(method, *args, &block)
205
+ unless @target.respond_to?(method)
206
+ message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}"
207
+ raise NoMethodError, message
208
+ end
209
+
210
+ if block_given?
211
+ @target.send(method, *args) { |*block_args| yield(*block_args) }
216
212
  else
217
- super
213
+ @target.send(method, *args)
218
214
  end
219
215
  end
220
216
  end
@@ -260,10 +256,16 @@ module ActiveRecord
260
256
  end
261
257
  end
262
258
 
263
- # Array#flatten has problems with recursive arrays. Going one level
264
- # deeper solves the majority of the problems.
265
- def flatten_deeper(array)
266
- array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
259
+ if RUBY_VERSION < '1.9.2'
260
+ # Array#flatten has problems with recursive arrays before Ruby 1.9.2.
261
+ # Going one level deeper solves the majority of the problems.
262
+ def flatten_deeper(array)
263
+ array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
264
+ end
265
+ else
266
+ def flatten_deeper(array)
267
+ array.flatten
268
+ end
267
269
  end
268
270
 
269
271
  # Returns the ID of the owner, quoted if needed.
@@ -36,11 +36,11 @@ module ActiveRecord
36
36
  loaded
37
37
  record
38
38
  end
39
-
39
+
40
40
  def updated?
41
41
  @updated
42
42
  end
43
-
43
+
44
44
  private
45
45
  def find_target
46
46
  find_method = if @reflection.options[:primary_key]
@@ -63,6 +63,12 @@ module ActiveRecord
63
63
  !@owner[@reflection.primary_key_name].nil?
64
64
  end
65
65
 
66
+ # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
67
+ # has_one associations.
68
+ def we_can_set_the_inverse_on_this?(record)
69
+ @reflection.has_inverse? && @reflection.inverse_of.macro == :has_one
70
+ end
71
+
66
72
  def record_id(record)
67
73
  record.send(@reflection.options[:primary_key] || :id)
68
74
  end
@@ -75,12 +81,6 @@ module ActiveRecord
75
81
  @owner[@reflection.primary_key_name]
76
82
  end
77
83
  end
78
-
79
- # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
80
- # has_one associations.
81
- def we_can_set_the_inverse_on_this?(record)
82
- @reflection.has_inverse? && @reflection.inverse_of.macro == :has_one
83
- end
84
84
  end
85
85
  end
86
86
  end
@@ -1,11 +1,6 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
4
- def initialize(owner, reflection)
5
- super
6
- @primary_key_list = {}
7
- end
8
-
9
4
  def create(attributes = {})
10
5
  create_record(attributes) { |record| insert_record(record) }
11
6
  end
@@ -23,9 +18,7 @@ module ActiveRecord
23
18
  end
24
19
 
25
20
  def has_primary_key?
26
- return @has_primary_key unless @has_primary_key.nil?
27
- @has_primary_key = (@owner.connection.supports_primary_key? &&
28
- @owner.connection.primary_key(@reflection.options[:join_table]))
21
+ @has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@reflection.options[:join_table])
29
22
  end
30
23
 
31
24
  protected
@@ -34,48 +27,40 @@ module ActiveRecord
34
27
  options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
35
28
  options[:select] ||= (@reflection.options[:select] || '*')
36
29
  end
37
-
30
+
38
31
  def count_records
39
32
  load_target.size
40
33
  end
41
34
 
42
35
  def insert_record(record, force = true, validate = true)
43
- if has_primary_key?
44
- raise ActiveRecord::ConfigurationError,
45
- "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
46
- end
47
-
48
36
  if record.new_record?
49
37
  if force
50
38
  record.save!
51
39
  else
52
- return false unless record.save(validate)
40
+ return false unless record.save(:validate => validate)
53
41
  end
54
42
  end
55
43
 
56
44
  if @reflection.options[:insert_sql]
57
45
  @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
58
46
  else
47
+ relation = Arel::Table.new(@reflection.options[:join_table])
59
48
  attributes = columns.inject({}) do |attrs, column|
60
49
  case column.name.to_s
61
50
  when @reflection.primary_key_name.to_s
62
- attrs[column.name] = owner_quoted_id
51
+ attrs[relation[column.name]] = owner_quoted_id
63
52
  when @reflection.association_foreign_key.to_s
64
- attrs[column.name] = record.quoted_id
53
+ attrs[relation[column.name]] = record.quoted_id
65
54
  else
66
55
  if record.has_attribute?(column.name)
67
56
  value = @owner.send(:quote_value, record[column.name], column)
68
- attrs[column.name] = value unless value.nil?
57
+ attrs[relation[column.name]] = value unless value.nil?
69
58
  end
70
59
  end
71
60
  attrs
72
61
  end
73
62
 
74
- sql =
75
- "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
76
- "VALUES (#{attributes.values.join(', ')})"
77
-
78
- @owner.connection.insert(sql)
63
+ relation.insert(attributes)
79
64
  end
80
65
 
81
66
  return true
@@ -85,9 +70,10 @@ module ActiveRecord
85
70
  if sql = @reflection.options[:delete_sql]
86
71
  records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
87
72
  else
88
- ids = quoted_record_ids(records)
89
- sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
90
- @owner.connection.delete(sql)
73
+ relation = Arel::Table.new(@reflection.options[:join_table])
74
+ relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
75
+ and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id)))
76
+ ).delete
91
77
  end
92
78
  end
93
79
 
@@ -101,15 +87,7 @@ module ActiveRecord
101
87
 
102
88
  @join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
103
89
 
104
- if @reflection.options[:counter_sql]
105
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
106
- elsif @reflection.options[:finder_sql]
107
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
108
- @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
109
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
110
- else
111
- @counter_sql = @finder_sql
112
- end
90
+ construct_counter_sql
113
91
  end
114
92
 
115
93
  def construct_scope