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
@@ -40,11 +40,11 @@ module ActiveRecord
40
40
  # we are certain the current target is an empty array. This is a
41
41
  # documented side-effect of the method that may avoid an extra SELECT.
42
42
  @target ||= [] and loaded if count == 0
43
-
43
+
44
44
  if @reflection.options[:limit]
45
45
  count = [ @reflection.options[:limit], count ].min
46
46
  end
47
-
47
+
48
48
  return count
49
49
  end
50
50
 
@@ -58,7 +58,7 @@ module ActiveRecord
58
58
 
59
59
  def insert_record(record, force = false, validate = true)
60
60
  set_belongs_to_association_for(record)
61
- force ? record.save! : record.save(validate)
61
+ force ? record.save! : record.save(:validate => validate)
62
62
  end
63
63
 
64
64
  # Deletes the records according to the <tt>:dependent</tt> option.
@@ -69,11 +69,11 @@ module ActiveRecord
69
69
  when :delete_all
70
70
  @reflection.klass.delete(records.map { |record| record.id })
71
71
  else
72
- ids = quoted_record_ids(records)
73
- @reflection.klass.update_all(
74
- "#{@reflection.primary_key_name} = NULL",
75
- "#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
76
- )
72
+ relation = Arel::Table.new(@reflection.table_name)
73
+ relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
74
+ and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id)))
75
+ ).update(relation[@reflection.primary_key_name] => nil)
76
+
77
77
  @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
78
78
  end
79
79
  end
@@ -88,25 +88,17 @@ module ActiveRecord
88
88
  @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
89
89
 
90
90
  when @reflection.options[:as]
91
- @finder_sql =
91
+ @finder_sql =
92
92
  "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
93
93
  "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
94
94
  @finder_sql << " AND (#{conditions})" if conditions
95
-
95
+
96
96
  else
97
97
  @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
98
98
  @finder_sql << " AND (#{conditions})" if conditions
99
99
  end
100
100
 
101
- if @reflection.options[:counter_sql]
102
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
103
- elsif @reflection.options[:finder_sql]
104
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
105
- @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
106
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
107
- else
108
- @counter_sql = @finder_sql
109
- end
101
+ construct_counter_sql
110
102
  end
111
103
 
112
104
  def construct_scope
@@ -1,29 +1,24 @@
1
+ require "active_record/associations/through_association_scope"
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class HasManyThroughAssociation < HasManyAssociation #:nodoc:
6
+ include ThroughAssociationScope
7
+
4
8
  alias_method :new, :build
5
9
 
6
10
  def create!(attrs = nil)
7
- transaction do
8
- self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
9
- object
10
- end
11
+ create_record(attrs, true)
11
12
  end
12
13
 
13
14
  def create(attrs = nil)
15
+ create_record(attrs, false)
16
+ end
17
+
18
+ def destroy(*records)
14
19
  transaction do
15
- object = if attrs
16
- @reflection.klass.send(:with_scope, :create => attrs) {
17
- @reflection.create_association
18
- }
19
- else
20
- @reflection.create_association
21
- end
22
- raise_on_type_mismatch(object)
23
- add_record_to_target_with_callbacks(object) do |r|
24
- insert_record(object, false)
25
- end
26
- object
20
+ delete_records(flatten_deeper(records))
21
+ super
27
22
  end
28
23
  end
29
24
 
@@ -35,8 +30,18 @@ module ActiveRecord
35
30
  return @target.size if loaded?
36
31
  return count
37
32
  end
38
-
33
+
39
34
  protected
35
+ def create_record(attrs, force = true)
36
+ ensure_owner_is_not_new
37
+
38
+ transaction do
39
+ object = @reflection.klass.new(attrs)
40
+ add_record_to_target_with_callbacks(object) {|r| insert_record(object, force) }
41
+ object
42
+ end
43
+ end
44
+
40
45
  def target_reflection_has_associated_record?
41
46
  if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
42
47
  false
@@ -46,23 +51,22 @@ module ActiveRecord
46
51
  end
47
52
 
48
53
  def construct_find_options!(options)
49
- options[:select] = construct_select(options[:select])
50
- options[:from] ||= construct_from
51
54
  options[:joins] = construct_joins(options[:joins])
52
55
  options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
53
56
  end
54
-
57
+
55
58
  def insert_record(record, force = true, validate = true)
56
59
  if record.new_record?
57
60
  if force
58
61
  record.save!
59
62
  else
60
- return false unless record.save(validate)
63
+ return false unless record.save(:validate => validate)
61
64
  end
62
65
  end
63
- through_reflection = @reflection.through_reflection
64
- klass = through_reflection.klass
65
- @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { through_reflection.create_association! }
66
+
67
+ through_association = @owner.send(@reflection.through_reflection.name)
68
+ through_record = through_association.create!(construct_join_attributes(record))
69
+ through_association.proxy_target << through_record
66
70
  end
67
71
 
68
72
  # TODO - add dependent option support
@@ -75,114 +79,7 @@ module ActiveRecord
75
79
 
76
80
  def find_target
77
81
  return [] unless target_reflection_has_associated_record?
78
- @reflection.klass.find(:all,
79
- :select => construct_select,
80
- :conditions => construct_conditions,
81
- :from => construct_from,
82
- :joins => construct_joins,
83
- :order => @reflection.options[:order],
84
- :limit => @reflection.options[:limit],
85
- :group => @reflection.options[:group],
86
- :readonly => @reflection.options[:readonly],
87
- :include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
88
- )
89
- end
90
-
91
- # Construct attributes for associate pointing to owner.
92
- def construct_owner_attributes(reflection)
93
- if as = reflection.options[:as]
94
- { "#{as}_id" => @owner.id,
95
- "#{as}_type" => @owner.class.base_class.name.to_s }
96
- else
97
- { reflection.primary_key_name => @owner.id }
98
- end
99
- end
100
-
101
- # Construct attributes for :through pointing to owner and associate.
102
- def construct_join_attributes(associate)
103
- # TODO: revist this to allow it for deletion, supposing dependent option is supported
104
- raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
105
- join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
106
- if @reflection.options[:source_type]
107
- join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
108
- end
109
- join_attributes
110
- end
111
-
112
- # Associate attributes pointing to owner, quoted.
113
- def construct_quoted_owner_attributes(reflection)
114
- if as = reflection.options[:as]
115
- { "#{as}_id" => owner_quoted_id,
116
- "#{as}_type" => reflection.klass.quote_value(
117
- @owner.class.base_class.name.to_s,
118
- reflection.klass.columns_hash["#{as}_type"]) }
119
- elsif reflection.macro == :belongs_to
120
- { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
121
- else
122
- { reflection.primary_key_name => owner_quoted_id }
123
- end
124
- end
125
-
126
- # Build SQL conditions from attributes, qualified by table name.
127
- def construct_conditions
128
- table_name = @reflection.through_reflection.quoted_table_name
129
- conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
130
- "#{table_name}.#{attr} = #{value}"
131
- end
132
- conditions << sql_conditions if sql_conditions
133
- "(" + conditions.join(') AND (') + ")"
134
- end
135
-
136
- def construct_from
137
- @reflection.quoted_table_name
138
- end
139
-
140
- def construct_select(custom_select = nil)
141
- distinct = "DISTINCT " if @reflection.options[:uniq]
142
- selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
143
- end
144
-
145
- def construct_joins(custom_joins = nil)
146
- polymorphic_join = nil
147
- if @reflection.source_reflection.macro == :belongs_to
148
- reflection_primary_key = @reflection.klass.primary_key
149
- source_primary_key = @reflection.source_reflection.primary_key_name
150
- if @reflection.options[:source_type]
151
- polymorphic_join = "AND %s.%s = %s" % [
152
- @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
153
- @owner.class.quote_value(@reflection.options[:source_type])
154
- ]
155
- end
156
- else
157
- reflection_primary_key = @reflection.source_reflection.primary_key_name
158
- source_primary_key = @reflection.through_reflection.klass.primary_key
159
- if @reflection.source_reflection.options[:as]
160
- polymorphic_join = "AND %s.%s = %s" % [
161
- @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
162
- @owner.class.quote_value(@reflection.through_reflection.klass.name)
163
- ]
164
- end
165
- end
166
-
167
- "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
168
- @reflection.through_reflection.quoted_table_name,
169
- @reflection.quoted_table_name, reflection_primary_key,
170
- @reflection.through_reflection.quoted_table_name, source_primary_key,
171
- polymorphic_join
172
- ]
173
- end
174
-
175
- def construct_scope
176
- { :create => construct_owner_attributes(@reflection),
177
- :find => { :from => construct_from,
178
- :conditions => construct_conditions,
179
- :joins => construct_joins,
180
- :include => @reflection.options[:include],
181
- :select => construct_select,
182
- :order => @reflection.options[:order],
183
- :limit => @reflection.options[:limit],
184
- :readonly => @reflection.options[:readonly],
185
- } }
82
+ with_scope(construct_scope) { @reflection.klass.find(:all) }
186
83
  end
187
84
 
188
85
  def construct_sql
@@ -196,59 +93,9 @@ module ActiveRecord
196
93
  @finder_sql = construct_conditions
197
94
  end
198
95
 
199
- if @reflection.options[:counter_sql]
200
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
201
- elsif @reflection.options[:finder_sql]
202
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
203
- @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
204
- @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
205
- else
206
- @counter_sql = @finder_sql
207
- end
208
- end
209
-
210
- def conditions
211
- @conditions = build_conditions unless defined?(@conditions)
212
- @conditions
96
+ construct_counter_sql
213
97
  end
214
98
 
215
- def build_conditions
216
- association_conditions = @reflection.options[:conditions]
217
- through_conditions = build_through_conditions
218
- source_conditions = @reflection.source_reflection.options[:conditions]
219
- uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
220
-
221
- if association_conditions || through_conditions || source_conditions || uses_sti
222
- all = []
223
-
224
- [association_conditions, source_conditions].each do |conditions|
225
- all << interpolate_sql(sanitize_sql(conditions)) if conditions
226
- end
227
-
228
- all << through_conditions if through_conditions
229
- all << build_sti_condition if uses_sti
230
-
231
- all.map { |sql| "(#{sql})" } * ' AND '
232
- end
233
- end
234
-
235
- def build_through_conditions
236
- conditions = @reflection.through_reflection.options[:conditions]
237
- if conditions.is_a?(Hash)
238
- interpolate_sql(sanitize_sql(conditions)).gsub(
239
- @reflection.quoted_table_name,
240
- @reflection.through_reflection.quoted_table_name)
241
- elsif conditions
242
- interpolate_sql(sanitize_sql(conditions))
243
- end
244
- end
245
-
246
- def build_sti_condition
247
- @reflection.through_reflection.klass.send(:type_condition)
248
- end
249
-
250
- alias_method :sql_conditions, :conditions
251
-
252
99
  def has_cached_counter?
253
100
  @owner.attribute_present?(cached_counter_attribute_name)
254
101
  end
@@ -1,6 +1,6 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
- class HasOneAssociation < BelongsToAssociation #:nodoc:
3
+ class HasOneAssociation < AssociationProxy #:nodoc:
4
4
  def initialize(owner, reflection)
5
5
  super
6
6
  construct_sql
@@ -81,7 +81,7 @@ module ActiveRecord
81
81
  the_target = @reflection.klass.find(:first,
82
82
  :conditions => @finder_sql,
83
83
  :select => @reflection.options[:select],
84
- :order => @reflection.options[:order],
84
+ :order => @reflection.options[:order],
85
85
  :include => @reflection.options[:include],
86
86
  :readonly => @reflection.options[:readonly]
87
87
  )
@@ -92,7 +92,7 @@ module ActiveRecord
92
92
  def construct_sql
93
93
  case
94
94
  when @reflection.options[:as]
95
- @finder_sql =
95
+ @finder_sql =
96
96
  "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
97
97
  "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
98
98
  else
@@ -100,7 +100,7 @@ module ActiveRecord
100
100
  end
101
101
  @finder_sql << " AND (#{conditions})" if conditions
102
102
  end
103
-
103
+
104
104
  def construct_scope
105
105
  create_scoping = {}
106
106
  set_belongs_to_association_for(create_scoping)
@@ -117,7 +117,7 @@ module ActiveRecord
117
117
  end
118
118
 
119
119
  if replace_existing
120
- replace(record, true)
120
+ replace(record, true)
121
121
  else
122
122
  record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
123
123
  self.target = record
@@ -127,16 +127,16 @@ module ActiveRecord
127
127
  record
128
128
  end
129
129
 
130
+ def we_can_set_the_inverse_on_this?(record)
131
+ inverse = @reflection.inverse_of
132
+ return !inverse.nil?
133
+ end
134
+
130
135
  def merge_with_conditions(attrs={})
131
136
  attrs ||= {}
132
137
  attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
133
138
  attrs
134
139
  end
135
-
136
- def we_can_set_the_inverse_on_this?(record)
137
- inverse = @reflection.inverse_of
138
- return !inverse.nil?
139
- end
140
140
  end
141
141
  end
142
142
  end
@@ -1,6 +1,16 @@
1
+ require "active_record/associations/through_association_scope"
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
- class HasOneThroughAssociation < HasManyThroughAssociation
5
+ class HasOneThroughAssociation < HasOneAssociation
6
+ include ThroughAssociationScope
7
+
8
+ def replace(new_value)
9
+ create_through_record(new_value)
10
+ @target = new_value
11
+ end
12
+
13
+ private
4
14
 
5
15
  def create_through_record(new_value) #nodoc:
6
16
  klass = @reflection.through_reflection.klass
@@ -8,7 +18,7 @@ module ActiveRecord
8
18
  current_object = @owner.send(@reflection.through_reflection.name)
9
19
 
10
20
  if current_object
11
- new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
21
+ new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
12
22
  elsif new_value
13
23
  if @owner.new_record?
14
24
  self.target = new_value
@@ -21,16 +31,8 @@ module ActiveRecord
21
31
  end
22
32
 
23
33
  private
24
- def find(*args)
25
- super(args.merge(:limit => 1))
26
- end
27
-
28
34
  def find_target
29
- super.first
30
- end
31
-
32
- def reset_target!
33
- @target = nil
35
+ with_scope(construct_scope) { @reflection.klass.find(:first) }
34
36
  end
35
37
  end
36
38
  end
@@ -0,0 +1,153 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ module ThroughAssociationScope
4
+
5
+ protected
6
+
7
+ def construct_scope
8
+ { :create => construct_owner_attributes(@reflection),
9
+ :find => { :conditions => construct_conditions,
10
+ :joins => construct_joins,
11
+ :include => @reflection.options[:include] || @reflection.source_reflection.options[:include],
12
+ :select => construct_select,
13
+ :order => @reflection.options[:order],
14
+ :limit => @reflection.options[:limit],
15
+ :readonly => @reflection.options[:readonly],
16
+ } }
17
+ end
18
+
19
+ # Build SQL conditions from attributes, qualified by table name.
20
+ def construct_conditions
21
+ table_name = @reflection.through_reflection.quoted_table_name
22
+ conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
23
+ "#{table_name}.#{attr} = #{value}"
24
+ end
25
+ conditions << sql_conditions if sql_conditions
26
+ "(" + conditions.join(') AND (') + ")"
27
+ end
28
+
29
+ # Associate attributes pointing to owner, quoted.
30
+ def construct_quoted_owner_attributes(reflection)
31
+ if as = reflection.options[:as]
32
+ { "#{as}_id" => owner_quoted_id,
33
+ "#{as}_type" => reflection.klass.quote_value(
34
+ @owner.class.base_class.name.to_s,
35
+ reflection.klass.columns_hash["#{as}_type"]) }
36
+ elsif reflection.macro == :belongs_to
37
+ { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
38
+ else
39
+ { reflection.primary_key_name => owner_quoted_id }
40
+ end
41
+ end
42
+
43
+ def construct_from
44
+ @reflection.table_name
45
+ end
46
+
47
+ def construct_select(custom_select = nil)
48
+ distinct = "DISTINCT " if @reflection.options[:uniq]
49
+ selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
50
+ end
51
+
52
+ def construct_joins(custom_joins = nil)
53
+ polymorphic_join = nil
54
+ if @reflection.source_reflection.macro == :belongs_to
55
+ reflection_primary_key = @reflection.klass.primary_key
56
+ source_primary_key = @reflection.source_reflection.primary_key_name
57
+ if @reflection.options[:source_type]
58
+ polymorphic_join = "AND %s.%s = %s" % [
59
+ @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
60
+ @owner.class.quote_value(@reflection.options[:source_type])
61
+ ]
62
+ end
63
+ else
64
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
65
+ source_primary_key = @reflection.through_reflection.klass.primary_key
66
+ if @reflection.source_reflection.options[:as]
67
+ polymorphic_join = "AND %s.%s = %s" % [
68
+ @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
69
+ @owner.class.quote_value(@reflection.through_reflection.klass.name)
70
+ ]
71
+ end
72
+ end
73
+
74
+ "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
75
+ @reflection.through_reflection.quoted_table_name,
76
+ @reflection.quoted_table_name, reflection_primary_key,
77
+ @reflection.through_reflection.quoted_table_name, source_primary_key,
78
+ polymorphic_join
79
+ ]
80
+ end
81
+
82
+ # Construct attributes for associate pointing to owner.
83
+ def construct_owner_attributes(reflection)
84
+ if as = reflection.options[:as]
85
+ { "#{as}_id" => @owner.id,
86
+ "#{as}_type" => @owner.class.base_class.name.to_s }
87
+ else
88
+ { reflection.primary_key_name => @owner.id }
89
+ end
90
+ end
91
+
92
+ # Construct attributes for :through pointing to owner and associate.
93
+ def construct_join_attributes(associate)
94
+ # TODO: revist this to allow it for deletion, supposing dependent option is supported
95
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
96
+
97
+ join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
98
+
99
+ if @reflection.options[:source_type]
100
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
101
+ end
102
+
103
+ if @reflection.through_reflection.options[:conditions].is_a?(Hash)
104
+ join_attributes.merge!(@reflection.through_reflection.options[:conditions])
105
+ end
106
+
107
+ join_attributes
108
+ end
109
+
110
+ def conditions
111
+ @conditions = build_conditions unless defined?(@conditions)
112
+ @conditions
113
+ end
114
+
115
+ def build_conditions
116
+ association_conditions = @reflection.options[:conditions]
117
+ through_conditions = build_through_conditions
118
+ source_conditions = @reflection.source_reflection.options[:conditions]
119
+ uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
120
+
121
+ if association_conditions || through_conditions || source_conditions || uses_sti
122
+ all = []
123
+
124
+ [association_conditions, source_conditions].each do |conditions|
125
+ all << interpolate_sql(sanitize_sql(conditions)) if conditions
126
+ end
127
+
128
+ all << through_conditions if through_conditions
129
+ all << build_sti_condition if uses_sti
130
+
131
+ all.map { |sql| "(#{sql})" } * ' AND '
132
+ end
133
+ end
134
+
135
+ def build_through_conditions
136
+ conditions = @reflection.through_reflection.options[:conditions]
137
+ if conditions.is_a?(Hash)
138
+ interpolate_sql(sanitize_sql(conditions)).gsub(
139
+ @reflection.quoted_table_name,
140
+ @reflection.through_reflection.quoted_table_name)
141
+ elsif conditions
142
+ interpolate_sql(sanitize_sql(conditions))
143
+ end
144
+ end
145
+
146
+ def build_sti_condition
147
+ @reflection.through_reflection.klass.send(:type_condition).to_sql
148
+ end
149
+
150
+ alias_method :sql_conditions, :conditions
151
+ end
152
+ end
153
+ end