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,6 +1,18 @@
1
1
  require 'yaml'
2
2
  require 'set'
3
- require 'active_support/core_ext/class/attribute'
3
+ require 'active_support/benchmarkable'
4
+ require 'active_support/dependencies'
5
+ require 'active_support/time'
6
+ require 'active_support/core_ext/class/attribute_accessors'
7
+ require 'active_support/core_ext/class/delegating_attributes'
8
+ require 'active_support/core_ext/class/inheritable_attributes'
9
+ require 'active_support/core_ext/array/extract_options'
10
+ require 'active_support/core_ext/hash/deep_merge'
11
+ require 'active_support/core_ext/hash/indifferent_access'
12
+ require 'active_support/core_ext/hash/slice'
13
+ require 'active_support/core_ext/string/behavior'
14
+ require 'active_support/core_ext/object/metaclass'
15
+ require 'active_support/core_ext/module/delegation'
4
16
 
5
17
  module ActiveRecord #:nodoc:
6
18
  # Generic Active Record exception class.
@@ -56,6 +68,29 @@ module ActiveRecord #:nodoc:
56
68
  class StatementInvalid < ActiveRecordError
57
69
  end
58
70
 
71
+ # Raised when SQL statement is invalid and the application gets a blank result.
72
+ class ThrowResult < ActiveRecordError
73
+ end
74
+
75
+ # Parent class for all specific exceptions which wrap database driver exceptions
76
+ # provides access to the original exception also.
77
+ class WrappedDatabaseException < StatementInvalid
78
+ attr_reader :original_exception
79
+
80
+ def initialize(message, original_exception)
81
+ super(message)
82
+ @original_exception = original_exception
83
+ end
84
+ end
85
+
86
+ # Raised when a record cannot be inserted because it would violate a uniqueness constraint.
87
+ class RecordNotUnique < WrappedDatabaseException
88
+ end
89
+
90
+ # Raised when a record cannot be inserted or updated because it references a non-existent record.
91
+ class InvalidForeignKey < WrappedDatabaseException
92
+ end
93
+
59
94
  # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method)
60
95
  # does not match number of expected variables.
61
96
  #
@@ -118,11 +153,6 @@ module ActiveRecord #:nodoc:
118
153
  class DangerousAttributeError < ActiveRecordError
119
154
  end
120
155
 
121
- # Raised when you've tried to access a column which wasn't loaded by your finder.
122
- # Typically this is because <tt>:select</tt> has been specified.
123
- class MissingAttributeError < NoMethodError
124
- end
125
-
126
156
  # Raised when unknown attributes are supplied via mass assignment.
127
157
  class UnknownAttributeError < NoMethodError
128
158
  end
@@ -226,6 +256,12 @@ module ActiveRecord #:nodoc:
226
256
  #
227
257
  # Student.find(:all, :conditions => { :grade => [9,11,12] })
228
258
  #
259
+ # When joining tables, nested hashes or keys written in the form 'table_name.column_name' can be used to qualify the table name of a
260
+ # particular condition. For instance:
261
+ #
262
+ # Student.find(:all, :conditions => { :schools => { :type => 'public' }}, :joins => :schools)
263
+ # Student.find(:all, :conditions => { 'schools.type' => 'public' }, :joins => :schools)
264
+ #
229
265
  # == Overwriting default accessors
230
266
  #
231
267
  # All column values are automatically available through basic accessors on the Active Record object, but sometimes you
@@ -390,7 +426,7 @@ module ActiveRecord #:nodoc:
390
426
  # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
391
427
  # instances in the current object space.
392
428
  class Base
393
- ##
429
+ ##
394
430
  # :singleton-method:
395
431
  # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
396
432
  # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
@@ -424,11 +460,11 @@ module ActiveRecord #:nodoc:
424
460
  # as a Hash.
425
461
  #
426
462
  # For example, the following database.yml...
427
- #
463
+ #
428
464
  # development:
429
465
  # adapter: sqlite3
430
466
  # database: db/development.sqlite3
431
- #
467
+ #
432
468
  # production:
433
469
  # adapter: sqlite3
434
470
  # database: db/production.sqlite3
@@ -462,9 +498,6 @@ module ActiveRecord #:nodoc:
462
498
  # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
463
499
  # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
464
500
  # for tables in a shared database. By default, the prefix is the empty string.
465
- #
466
- # If you are organising your models within modules you can add a prefix to the models within a namespace by defining
467
- # a singleton method in the parent module called table_name_prefix which returns your chosen prefix.
468
501
  cattr_accessor :table_name_prefix, :instance_writer => false
469
502
  @@table_name_prefix = ""
470
503
 
@@ -483,14 +516,6 @@ module ActiveRecord #:nodoc:
483
516
  cattr_accessor :pluralize_table_names, :instance_writer => false
484
517
  @@pluralize_table_names = true
485
518
 
486
- ##
487
- # :singleton-method:
488
- # Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
489
- # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
490
- # may complicate matters if you use software like syslog. This is true, by default.
491
- cattr_accessor :colorize_logging, :instance_writer => false
492
- @@colorize_logging = true
493
-
494
519
  ##
495
520
  # :singleton-method:
496
521
  # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
@@ -516,128 +541,24 @@ module ActiveRecord #:nodoc:
516
541
  @@timestamped_migrations = true
517
542
 
518
543
  # Determine whether to store the full constant name including namespace when using STI
519
- class_attribute :store_full_sti_class
520
- self.store_full_sti_class = false
544
+ superclass_delegating_accessor :store_full_sti_class
545
+ self.store_full_sti_class = true
521
546
 
522
547
  # Stores the default scope for the class
523
548
  class_inheritable_accessor :default_scoping, :instance_writer => false
524
549
  self.default_scoping = []
525
550
 
526
551
  class << self # Class methods
527
- # Find operates with four different retrieval approaches:
528
- #
529
- # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
530
- # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
531
- # * Find first - This will return the first record matched by the options used. These options can either be specific
532
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
533
- # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
534
- # * Find last - This will return the last record matched by the options used. These options can either be specific
535
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
536
- # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
537
- # * Find all - This will return all the records matched by the options used.
538
- # If no records are found, an empty array is returned. Use
539
- # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
540
- #
541
- # All approaches accept an options hash as their last parameter.
542
- #
543
- # ==== Parameters
544
- #
545
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
546
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
547
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
548
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
549
- # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
550
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
551
- # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
552
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s),
553
- # or an array containing a mixture of both strings and named associations.
554
- # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
555
- # Pass <tt>:readonly => false</tt> to override.
556
- # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
557
- # to already defined associations. See eager loading under Associations.
558
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
559
- # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
560
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
561
- # of a database view).
562
- # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
563
- # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
564
- # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
565
- #
566
- # ==== Examples
567
- #
568
- # # find by id
569
- # Person.find(1) # returns the object for ID = 1
570
- # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
571
- # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
572
- # Person.find([1]) # returns an array for the object with ID = 1
573
- # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
574
- #
575
- # Note that returned records may not be in the same order as the ids you
576
- # provide since database rows are unordered. Give an explicit <tt>:order</tt>
577
- # to ensure the results are sorted.
578
- #
579
- # ==== Examples
580
- #
581
- # # find first
582
- # Person.find(:first) # returns the first object fetched by SELECT * FROM people
583
- # Person.find(:first, :conditions => [ "user_name = ?", user_name])
584
- # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
585
- # Person.find(:first, :order => "created_on DESC", :offset => 5)
586
- #
587
- # # find last
588
- # Person.find(:last) # returns the last object fetched by SELECT * FROM people
589
- # Person.find(:last, :conditions => [ "user_name = ?", user_name])
590
- # Person.find(:last, :order => "created_on DESC", :offset => 5)
591
- #
592
- # # find all
593
- # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
594
- # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
595
- # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
596
- # Person.find(:all, :offset => 10, :limit => 10)
597
- # Person.find(:all, :include => [ :account, :friends ])
598
- # Person.find(:all, :group => "category")
599
- #
600
- # Example for find with a lock: Imagine two concurrent transactions:
601
- # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
602
- # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
603
- # transaction has to wait until the first is finished; we get the
604
- # expected <tt>person.visits == 4</tt>.
605
- #
606
- # Person.transaction do
607
- # person = Person.find(1, :lock => true)
608
- # person.visits += 1
609
- # person.save!
610
- # end
611
- def find(*args)
612
- options = args.extract_options!
613
- validate_find_options(options)
614
- set_readonly_option!(options)
615
-
616
- case args.first
617
- when :first then find_initial(options)
618
- when :last then find_last(options)
619
- when :all then find_every(options)
620
- else find_from_ids(args, options)
621
- end
622
- end
623
-
624
- # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
625
- # same arguments to this method as you can to <tt>find(:first)</tt>.
626
- def first(*args)
627
- find(:first, *args)
552
+ def colorize_logging(*args)
553
+ ActiveSupport::Deprecation.warn "ActiveRecord::Base.colorize_logging and " <<
554
+ "config.active_record.colorize_logging are deprecated. Please use " <<
555
+ "Rails::Subscriber.colorize_logging or config.colorize_logging instead", caller
628
556
  end
557
+ alias :colorize_logging= :colorize_logging
629
558
 
630
- # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
631
- # same arguments to this method as you can to <tt>find(:last)</tt>.
632
- def last(*args)
633
- find(:last, *args)
634
- end
635
-
636
- # This is an alias for find(:all). You can pass in all the same arguments to this method as you can
637
- # to find(:all)
638
- def all(*args)
639
- find(:all, *args)
640
- end
559
+ delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
560
+ delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
561
+ delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
641
562
 
642
563
  # Executes a custom SQL query against your database and returns all the results. The results will
643
564
  # be returned as an array with columns requested encapsulated as attributes of the model you call
@@ -665,37 +586,6 @@ module ActiveRecord #:nodoc:
665
586
  connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
666
587
  end
667
588
 
668
- # Returns true if a record exists in the table that matches the +id+ or
669
- # conditions given, or false otherwise. The argument can take five forms:
670
- #
671
- # * Integer - Finds the record with this primary key.
672
- # * String - Finds the record with a primary key corresponding to this
673
- # string (such as <tt>'5'</tt>).
674
- # * Array - Finds the record that matches these +find+-style conditions
675
- # (such as <tt>['color = ?', 'red']</tt>).
676
- # * Hash - Finds the record that matches these +find+-style conditions
677
- # (such as <tt>{:color => 'red'}</tt>).
678
- # * No args - Returns false if the table is empty, true otherwise.
679
- #
680
- # For more information about specifying conditions as a Hash or Array,
681
- # see the Conditions section in the introduction to ActiveRecord::Base.
682
- #
683
- # Note: You can't pass in a condition as a string (like <tt>name =
684
- # 'Jamie'</tt>), since it would be sanitized and then queried against
685
- # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
686
- #
687
- # ==== Examples
688
- # Person.exists?(5)
689
- # Person.exists?('5')
690
- # Person.exists?(:name => "David")
691
- # Person.exists?(['name LIKE ?', "%#{query}%"])
692
- # Person.exists?
693
- def exists?(id_or_conditions = {})
694
- find_initial(
695
- :select => "#{quoted_table_name}.#{primary_key}",
696
- :conditions => expand_id_conditions(id_or_conditions)) ? true : false
697
- end
698
-
699
589
  # Creates an object (or multiple objects) and saves it to the database, if validations pass.
700
590
  # The resulting object is returned whether the object was saved successfully to the database or not.
701
591
  #
@@ -729,181 +619,6 @@ module ActiveRecord #:nodoc:
729
619
  end
730
620
  end
731
621
 
732
- # Updates an object (or multiple objects) and saves it to the database, if validations pass.
733
- # The resulting object is returned whether the object was saved successfully to the database or not.
734
- #
735
- # ==== Parameters
736
- #
737
- # * +id+ - This should be the id or an array of ids to be updated.
738
- # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes.
739
- #
740
- # ==== Examples
741
- #
742
- # # Updating one record:
743
- # Person.update(15, :user_name => 'Samuel', :group => 'expert')
744
- #
745
- # # Updating multiple records:
746
- # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
747
- # Person.update(people.keys, people.values)
748
- def update(id, attributes)
749
- if id.is_a?(Array)
750
- idx = -1
751
- id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
752
- else
753
- object = find(id)
754
- object.update_attributes(attributes)
755
- object
756
- end
757
- end
758
-
759
- # Deletes the row with a primary key matching the +id+ argument, using a
760
- # SQL +DELETE+ statement, and returns the number of rows deleted. Active
761
- # Record objects are not instantiated, so the object's callbacks are not
762
- # executed, including any <tt>:dependent</tt> association options or
763
- # Observer methods.
764
- #
765
- # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
766
- #
767
- # Note: Although it is often much faster than the alternative,
768
- # <tt>#destroy</tt>, skipping callbacks might bypass business logic in
769
- # your application that ensures referential integrity or performs other
770
- # essential jobs.
771
- #
772
- # ==== Examples
773
- #
774
- # # Delete a single row
775
- # Todo.delete(1)
776
- #
777
- # # Delete multiple rows
778
- # Todo.delete([2,3,4])
779
- def delete(id)
780
- delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
781
- end
782
-
783
- # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
784
- # therefore all callbacks and filters are fired off before the object is deleted. This method is
785
- # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
786
- #
787
- # This essentially finds the object (or multiple objects) with the given id, creates a new object
788
- # from the attributes, and then calls destroy on it.
789
- #
790
- # ==== Parameters
791
- #
792
- # * +id+ - Can be either an Integer or an Array of Integers.
793
- #
794
- # ==== Examples
795
- #
796
- # # Destroy a single object
797
- # Todo.destroy(1)
798
- #
799
- # # Destroy multiple objects
800
- # todos = [1,2,3]
801
- # Todo.destroy(todos)
802
- def destroy(id)
803
- if id.is_a?(Array)
804
- id.map { |one_id| destroy(one_id) }
805
- else
806
- find(id).destroy
807
- end
808
- end
809
-
810
- # Updates all records with details given if they match a set of conditions supplied, limits and order can
811
- # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
812
- # database. It does not instantiate the involved models and it does not trigger Active Record callbacks.
813
- #
814
- # ==== Parameters
815
- #
816
- # * +updates+ - A string of column and value pairs that will be set on any records that match conditions. This creates the SET clause of the generated SQL.
817
- # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
818
- # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
819
- #
820
- # ==== Examples
821
- #
822
- # # Update all billing objects with the 3 different attributes given
823
- # Billing.update_all( "category = 'authorized', approved = 1, author = 'David'" )
824
- #
825
- # # Update records that match our conditions
826
- # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'" )
827
- #
828
- # # Update records that match our conditions but limit it to 5 ordered by date
829
- # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'",
830
- # :order => 'created_at', :limit => 5 )
831
- def update_all(updates, conditions = nil, options = {})
832
- sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
833
-
834
- scope = scope(:find)
835
-
836
- select_sql = ""
837
- add_conditions!(select_sql, conditions, scope)
838
-
839
- if options.has_key?(:limit) || (scope && scope[:limit])
840
- # Only take order from scope if limit is also provided by scope, this
841
- # is useful for updating a has_many association with a limit.
842
- add_order!(select_sql, options[:order], scope)
843
-
844
- add_limit!(select_sql, options, scope)
845
- sql.concat(connection.limited_update_conditions(select_sql, quoted_table_name, connection.quote_column_name(primary_key)))
846
- else
847
- add_order!(select_sql, options[:order], nil)
848
- sql.concat(select_sql)
849
- end
850
-
851
- connection.update(sql, "#{name} Update")
852
- end
853
-
854
- # Destroys the records matching +conditions+ by instantiating each
855
- # record and calling its +destroy+ method. Each object's callbacks are
856
- # executed (including <tt>:dependent</tt> association options and
857
- # +before_destroy+/+after_destroy+ Observer methods). Returns the
858
- # collection of objects that were destroyed; each will be frozen, to
859
- # reflect that no changes should be made (since they can't be
860
- # persisted).
861
- #
862
- # Note: Instantiation, callback execution, and deletion of each
863
- # record can be time consuming when you're removing many records at
864
- # once. It generates at least one SQL +DELETE+ query per record (or
865
- # possibly more, to enforce your callbacks). If you want to delete many
866
- # rows quickly, without concern for their associations or callbacks, use
867
- # +delete_all+ instead.
868
- #
869
- # ==== Parameters
870
- #
871
- # * +conditions+ - A string, array, or hash that specifies which records
872
- # to destroy. If omitted, all records are destroyed. See the
873
- # Conditions section in the introduction to ActiveRecord::Base for
874
- # more information.
875
- #
876
- # ==== Examples
877
- #
878
- # Person.destroy_all("last_login < '2004-04-04'")
879
- # Person.destroy_all(:status => "inactive")
880
- def destroy_all(conditions = nil)
881
- find(:all, :conditions => conditions).each { |object| object.destroy }
882
- end
883
-
884
- # Deletes the records matching +conditions+ without instantiating the records first, and hence not
885
- # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
886
- # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
887
- # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
888
- # the number of rows affected.
889
- #
890
- # ==== Parameters
891
- #
892
- # * +conditions+ - Conditions are specified the same way as with +find+ method.
893
- #
894
- # ==== Example
895
- #
896
- # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
897
- # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
898
- #
899
- # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
900
- # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
901
- def delete_all(conditions = nil)
902
- sql = "DELETE FROM #{quoted_table_name} "
903
- add_conditions!(sql, conditions, scope(:find))
904
- connection.delete(sql, "#{name} Delete all")
905
- end
906
-
907
622
  # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
908
623
  # The use of this method should be restricted to complicated SQL queries that can't be executed
909
624
  # using the ActiveRecord::Calculations class methods. Look into those before using this.
@@ -936,18 +651,11 @@ module ActiveRecord #:nodoc:
936
651
  def reset_counters(id, *counters)
937
652
  object = find(id)
938
653
  counters.each do |association|
939
- child_class = reflect_on_association(association.to_sym).klass
940
- belongs_name = self.name.demodulize.underscore.to_sym
941
- counter_name = child_class.reflect_on_association(belongs_name).counter_cache_column
942
- value = object.send(association).count
654
+ child_class = reflect_on_association(association).klass
655
+ counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column
943
656
 
944
- connection.update(<<-CMD, "#{name} UPDATE")
945
- UPDATE #{quoted_table_name}
946
- SET #{connection.quote_column_name(counter_name)} = #{value}
947
- WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}
948
- CMD
657
+ connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE")
949
658
  end
950
- return true
951
659
  end
952
660
 
953
661
  # A generic "counter updater" implementation, intended primarily to be
@@ -980,13 +688,19 @@ module ActiveRecord #:nodoc:
980
688
  # # SET comment_count = comment_count + 1,
981
689
  # # WHERE id IN (10, 15)
982
690
  def update_counters(id, counters)
983
- updates = counters.map do |counter_name, value|
984
- operator = value < 0 ? '-' : '+'
985
- quoted_column = connection.quote_column_name(counter_name)
986
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
691
+ updates = counters.inject([]) { |list, (counter_name, increment)|
692
+ sign = increment < 0 ? "-" : "+"
693
+ list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
694
+ }.join(", ")
695
+
696
+ if id.is_a?(Array)
697
+ ids_list = id.map {|i| quote_value(i)}.join(', ')
698
+ condition = "IN (#{ids_list})"
699
+ else
700
+ condition = "= #{quote_value(id)}"
987
701
  end
988
702
 
989
- update_all(updates.join(', '), primary_key => id )
703
+ update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
990
704
  end
991
705
 
992
706
  # Increment a number field by one, usually representing a count.
@@ -1049,8 +763,23 @@ module ActiveRecord #:nodoc:
1049
763
  #
1050
764
  # To start from an all-closed default and enable attributes as needed,
1051
765
  # have a look at +attr_accessible+.
766
+ #
767
+ # If the access logic of your application is richer you can use <tt>Hash#except</tt>
768
+ # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
769
+ # passed to Active Record.
770
+ #
771
+ # For example, it could be the case that the list of protected attributes
772
+ # for a given model depends on the role of the user:
773
+ #
774
+ # # Assumes plan_id is not protected because it depends on the role.
775
+ # params[:account] = params[:account].except(:plan_id) unless admin?
776
+ # @account.update_attributes(params[:account])
777
+ #
778
+ # Note that +attr_protected+ is still applied to the received hash. Thus,
779
+ # with this technique you can at most _extend_ the list of protected
780
+ # attributes for a particular mass-assignment call.
1052
781
  def attr_protected(*attributes)
1053
- write_inheritable_attribute(:attr_protected, Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
782
+ write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || []))
1054
783
  end
1055
784
 
1056
785
  # Returns an array of all the attributes that have been protected from mass-assignment.
@@ -1082,6 +811,21 @@ module ActiveRecord #:nodoc:
1082
811
  #
1083
812
  # customer.credit_rating = "Average"
1084
813
  # customer.credit_rating # => "Average"
814
+ #
815
+ # If the access logic of your application is richer you can use <tt>Hash#except</tt>
816
+ # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are
817
+ # passed to Active Record.
818
+ #
819
+ # For example, it could be the case that the list of accessible attributes
820
+ # for a given model depends on the role of the user:
821
+ #
822
+ # # Assumes plan_id is accessible because it depends on the role.
823
+ # params[:account] = params[:account].except(:plan_id) unless admin?
824
+ # @account.update_attributes(params[:account])
825
+ #
826
+ # Note that +attr_accessible+ is still applied to the received hash. Thus,
827
+ # with this technique you can at most _narrow_ the list of accessible
828
+ # attributes for a particular mass-assignment call.
1085
829
  def attr_accessible(*attributes)
1086
830
  write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
1087
831
  end
@@ -1098,7 +842,7 @@ module ActiveRecord #:nodoc:
1098
842
 
1099
843
  # Returns an array of all the attributes that have been specified as readonly.
1100
844
  def readonly_attributes
1101
- read_inheritable_attribute(:attr_readonly)
845
+ read_inheritable_attribute(:attr_readonly) || []
1102
846
  end
1103
847
 
1104
848
  # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
@@ -1162,6 +906,10 @@ module ActiveRecord #:nodoc:
1162
906
  reset_table_name
1163
907
  end
1164
908
 
909
+ def quoted_table_name
910
+ @quoted_table_name ||= connection.quote_table_name(table_name)
911
+ end
912
+
1165
913
  def reset_table_name #:nodoc:
1166
914
  base = base_class
1167
915
 
@@ -1176,40 +924,14 @@ module ActiveRecord #:nodoc:
1176
924
  contained = contained.singularize if parent.pluralize_table_names
1177
925
  contained << '_'
1178
926
  end
1179
- name = "#{full_table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
927
+ name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
1180
928
  end
1181
929
 
930
+ @quoted_table_name = nil
1182
931
  set_table_name(name)
1183
932
  name
1184
933
  end
1185
934
 
1186
- # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
1187
- # primary_key_prefix_type setting, though.
1188
- def primary_key
1189
- reset_primary_key
1190
- end
1191
-
1192
- def reset_primary_key #:nodoc:
1193
- key = get_primary_key(base_class.name)
1194
- set_primary_key(key)
1195
- key
1196
- end
1197
-
1198
- def get_primary_key(base_name) #:nodoc:
1199
- key = 'id'
1200
- case primary_key_prefix_type
1201
- when :table_name
1202
- key = base_name.to_s.foreign_key(false)
1203
- when :table_name_with_underscore
1204
- key = base_name.to_s.foreign_key
1205
- end
1206
- key
1207
- end
1208
-
1209
- def full_table_name_prefix #:nodoc:
1210
- (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
1211
- end
1212
-
1213
935
  # Defines the column name for use with single table inheritance
1214
936
  # -- can be set in subclasses like so: self.inheritance_column = "type_id"
1215
937
  def inheritance_column
@@ -1239,18 +961,6 @@ module ActiveRecord #:nodoc:
1239
961
  end
1240
962
  alias :table_name= :set_table_name
1241
963
 
1242
- # Sets the name of the primary key column to use to the given value,
1243
- # or (if the value is nil or false) to the value returned by the given
1244
- # block.
1245
- #
1246
- # class Project < ActiveRecord::Base
1247
- # set_primary_key "sysid"
1248
- # end
1249
- def set_primary_key(value = nil, &block)
1250
- define_attr_method :primary_key, value, &block
1251
- end
1252
- alias :primary_key= :set_primary_key
1253
-
1254
964
  # Sets the name of the inheritance column to use to the given value,
1255
965
  # or (if the value # is nil or false) to the value returned by the
1256
966
  # given block.
@@ -1286,8 +996,6 @@ module ActiveRecord #:nodoc:
1286
996
 
1287
997
  # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
1288
998
  def class_name(table_name = table_name) # :nodoc:
1289
- ActiveSupport::Deprecation.warn("ActiveRecord::Base#class_name is deprecated and will be removed in Rails 3.", caller)
1290
-
1291
999
  # remove any prefix and/or suffix from the table name
1292
1000
  class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
1293
1001
  class_name = class_name.singularize if pluralize_table_names
@@ -1365,54 +1073,33 @@ module ActiveRecord #:nodoc:
1365
1073
  # end
1366
1074
  # end
1367
1075
  def reset_column_information
1368
- generated_methods.each { |name| undef_method(name) }
1369
- @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @generated_methods = @inheritance_column = nil
1076
+ undefine_attribute_methods
1077
+ @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
1078
+ @arel_engine = @unscoped = @arel_table = nil
1370
1079
  end
1371
1080
 
1372
1081
  def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
1373
1082
  subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
1374
1083
  end
1375
1084
 
1376
- def self_and_descendants_from_active_record#nodoc:
1085
+ # Set the lookup ancestors for ActiveModel.
1086
+ def lookup_ancestors #:nodoc:
1377
1087
  klass = self
1378
1088
  classes = [klass]
1379
- while klass != klass.base_class
1089
+ while klass != klass.base_class
1380
1090
  classes << klass = klass.superclass
1381
1091
  end
1382
1092
  classes
1383
1093
  rescue
1384
1094
  # OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
1385
- # Appearantly the method base_class causes some trouble.
1095
+ # Apparently the method base_class causes some trouble.
1386
1096
  # It now works for sure.
1387
1097
  [self]
1388
1098
  end
1389
1099
 
1390
- # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
1391
- # Person.human_attribute_name("first_name") # => "First name"
1392
- # This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
1393
- # module now.
1394
- # Specify +options+ with additional translating options.
1395
- def human_attribute_name(attribute_key_name, options = {})
1396
- defaults = self_and_descendants_from_active_record.map do |klass|
1397
- :"#{klass.name.underscore}.#{attribute_key_name}"
1398
- end
1399
- defaults << options[:default] if options[:default]
1400
- defaults.flatten!
1401
- defaults << attribute_key_name.to_s.humanize
1402
- options[:count] ||= 1
1403
- I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes]))
1404
- end
1405
-
1406
- # Transform the modelname into a more humane format, using I18n.
1407
- # Defaults to the basic humanize method.
1408
- # Default scope of the translation is activerecord.models
1409
- # Specify +options+ with additional translating options.
1410
- def human_name(options = {})
1411
- defaults = self_and_descendants_from_active_record.map do |klass|
1412
- :"#{klass.name.underscore}"
1413
- end
1414
- defaults << self.name.humanize
1415
- I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
1100
+ # Set the i18n scope to overwrite ActiveModel.
1101
+ def i18n_scope #:nodoc:
1102
+ :activerecord
1416
1103
  end
1417
1104
 
1418
1105
  # True if this isn't a concrete subclass needing a STI type condition.
@@ -1452,38 +1139,6 @@ module ActiveRecord #:nodoc:
1452
1139
  connection.quote(object)
1453
1140
  end
1454
1141
 
1455
- # Log and benchmark multiple statements in a single block. Example:
1456
- #
1457
- # Project.benchmark("Creating project") do
1458
- # project = Project.create("name" => "stuff")
1459
- # project.create_manager("name" => "David")
1460
- # project.milestones << Milestone.find(:all)
1461
- # end
1462
- #
1463
- # The benchmark is only recorded if the current level of the logger is less than or equal to the <tt>log_level</tt>,
1464
- # which makes it easy to include benchmarking statements in production software that will remain inexpensive because
1465
- # the benchmark will only be conducted if the log level is low enough.
1466
- #
1467
- # The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
1468
- def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
1469
- if logger && logger.level <= log_level
1470
- result = nil
1471
- ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
1472
- logger.add(log_level, '%s (%.1fms)' % [title, ms])
1473
- result
1474
- else
1475
- yield
1476
- end
1477
- end
1478
-
1479
- # Silences the logger for the duration of the block.
1480
- def silence
1481
- old_logger_level, logger.level = logger.level, Logger::ERROR if logger
1482
- yield
1483
- ensure
1484
- logger.level = old_logger_level if logger
1485
- end
1486
-
1487
1142
  # Overwrite the default class equality method to provide support for association proxies.
1488
1143
  def ===(object)
1489
1144
  object.is_a?(self)
@@ -1519,179 +1174,55 @@ module ActiveRecord #:nodoc:
1519
1174
  store_full_sti_class ? name : name.demodulize
1520
1175
  end
1521
1176
 
1522
- # Merges conditions so that the result is a valid +condition+
1523
- def merge_conditions(*conditions)
1524
- segments = []
1525
-
1526
- conditions.each do |condition|
1527
- unless condition.blank?
1528
- sql = sanitize_sql(condition)
1529
- segments << sql unless sql.blank?
1530
- end
1531
- end
1532
-
1533
- "(#{segments.join(') AND (')})" unless segments.empty?
1177
+ def unscoped
1178
+ @unscoped ||= Relation.new(self, arel_table)
1179
+ finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
1534
1180
  end
1535
1181
 
1536
- private
1537
- def find_initial(options)
1538
- options.update(:limit => 1)
1539
- find_every(options).first
1540
- end
1541
-
1542
- def find_last(options)
1543
- order = options[:order]
1544
-
1545
- if order
1546
- order = reverse_sql_order(order)
1547
- elsif !scoped?(:find, :order)
1548
- order = "#{table_name}.#{primary_key} DESC"
1549
- end
1550
-
1551
- if scoped?(:find, :order)
1552
- scope = scope(:find)
1553
- original_scoped_order = scope[:order]
1554
- scope[:order] = reverse_sql_order(original_scoped_order)
1555
- end
1556
-
1557
- begin
1558
- find_initial(options.merge({ :order => order }))
1559
- ensure
1560
- scope[:order] = original_scoped_order if original_scoped_order
1561
- end
1562
- end
1563
-
1564
- def reverse_sql_order(order_query)
1565
- reversed_query = order_query.to_s.split(/,/).each { |s|
1566
- if s.match(/\s(asc|ASC)$/)
1567
- s.gsub!(/\s(asc|ASC)$/, ' DESC')
1568
- elsif s.match(/\s(desc|DESC)$/)
1569
- s.gsub!(/\s(desc|DESC)$/, ' ASC')
1570
- elsif !s.match(/\s(asc|ASC|desc|DESC)$/)
1571
- s.concat(' DESC')
1572
- end
1573
- }.join(',')
1574
- end
1575
-
1576
- def find_every(options)
1577
- include_associations = merge_includes(scope(:find, :include), options[:include])
1578
-
1579
- if include_associations.any? && references_eager_loaded_tables?(options)
1580
- records = find_with_associations(options)
1581
- else
1582
- records = find_by_sql(construct_finder_sql(options))
1583
- if include_associations.any?
1584
- preload_associations(records, include_associations)
1585
- end
1586
- end
1587
-
1588
- records.each { |record| record.readonly! } if options[:readonly]
1589
-
1590
- records
1591
- end
1592
-
1593
- def find_from_ids(ids, options)
1594
- expects_array = ids.first.kind_of?(Array)
1595
- return ids.first if expects_array && ids.first.empty?
1596
-
1597
- ids = ids.flatten.compact.uniq
1598
-
1599
- case ids.size
1600
- when 0
1601
- raise RecordNotFound, "Couldn't find #{name} without an ID"
1602
- when 1
1603
- result = find_one(ids.first, options)
1604
- expects_array ? [ result ] : result
1605
- else
1606
- find_some(ids, options)
1607
- end
1608
- end
1609
-
1610
- def find_one(id, options)
1611
- conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
1612
- options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} = #{quote_value(id,columns_hash[primary_key])}#{conditions}"
1613
-
1614
- # Use find_every(options).first since the primary key condition
1615
- # already ensures we have a single record. Using find_initial adds
1616
- # a superfluous :limit => 1.
1617
- if result = find_every(options).first
1618
- result
1619
- else
1620
- raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}"
1621
- end
1622
- end
1623
-
1624
- def find_some(ids, options)
1625
- conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
1626
- ids_list = ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',')
1627
- options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} IN (#{ids_list})#{conditions}"
1628
-
1629
- result = find_every(options)
1630
-
1631
- # Determine expected size from limit and offset, not just ids.size.
1632
- expected_size =
1633
- if options[:limit] && ids.size > options[:limit]
1634
- options[:limit]
1635
- else
1636
- ids.size
1637
- end
1638
-
1639
- # 11 ids with limit 3, offset 9 should give 2 results.
1640
- if options[:offset] && (ids.size - options[:offset] < expected_size)
1641
- expected_size = ids.size - options[:offset]
1642
- end
1182
+ def arel_table
1183
+ @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
1184
+ end
1643
1185
 
1644
- if result.size == expected_size
1645
- result
1186
+ def arel_engine
1187
+ @arel_engine ||= begin
1188
+ if self == ActiveRecord::Base
1189
+ Arel::Table.engine
1646
1190
  else
1647
- raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
1191
+ connection_handler.connection_pools[name] ? Arel::Sql::Engine.new(self) : superclass.arel_engine
1648
1192
  end
1649
1193
  end
1194
+ end
1650
1195
 
1196
+ private
1651
1197
  # Finder methods must instantiate through this method to work with the
1652
1198
  # single-table inheritance model that makes it possible to create
1653
1199
  # objects of different types from the same table.
1654
1200
  def instantiate(record)
1655
- object =
1656
- if subclass_name = record[inheritance_column]
1657
- # No type given.
1658
- if subclass_name.empty?
1659
- allocate
1201
+ object = find_sti_class(record[inheritance_column]).allocate
1660
1202
 
1661
- else
1662
- # Ignore type if no column is present since it was probably
1663
- # pulled in from a sloppy join.
1664
- unless columns_hash.include?(inheritance_column)
1665
- allocate
1666
-
1667
- else
1668
- begin
1669
- compute_type(subclass_name).allocate
1670
- rescue NameError
1671
- raise SubclassNotFound,
1672
- "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
1673
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
1674
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
1675
- "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
1676
- end
1677
- end
1678
- end
1679
- else
1680
- allocate
1681
- end
1203
+ object.instance_variable_set(:'@attributes', record)
1204
+ object.instance_variable_set(:'@attributes_cache', {})
1682
1205
 
1683
- object.instance_variable_set("@attributes", record)
1684
- object.instance_variable_set("@attributes_cache", Hash.new)
1206
+ object.send(:_run_find_callbacks)
1207
+ object.send(:_run_initialize_callbacks)
1685
1208
 
1686
- if object.respond_to_without_attributes?(:after_find)
1687
- object.send(:callback, :after_find)
1688
- end
1209
+ object
1210
+ end
1689
1211
 
1690
- if object.respond_to_without_attributes?(:after_initialize)
1691
- object.send(:callback, :after_initialize)
1212
+ def find_sti_class(type_name)
1213
+ if type_name.blank? || !columns_hash.include?(inheritance_column)
1214
+ self
1215
+ else
1216
+ begin
1217
+ compute_type(type_name)
1218
+ rescue NameError
1219
+ raise SubclassNotFound,
1220
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
1221
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
1222
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
1223
+ "or overwrite #{name}.inheritance_column to use another column for that information."
1224
+ end
1692
1225
  end
1693
-
1694
- object
1695
1226
  end
1696
1227
 
1697
1228
  # Nest the type name in the same module as this class.
@@ -1704,149 +1235,18 @@ module ActiveRecord #:nodoc:
1704
1235
  end
1705
1236
  end
1706
1237
 
1707
- def default_select(qualified)
1708
- if qualified
1709
- quoted_table_name + '.*'
1710
- else
1711
- '*'
1712
- end
1238
+ def construct_finder_arel(options = {}, scope = nil)
1239
+ relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : unscoped.merge(options)
1240
+ relation = scope.merge(relation) if scope
1241
+ relation
1713
1242
  end
1714
1243
 
1715
- def construct_finder_sql(options)
1716
- scope = scope(:find)
1717
- sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
1718
- sql << "FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} "
1719
-
1720
- add_joins!(sql, options[:joins], scope)
1721
- add_conditions!(sql, options[:conditions], scope)
1244
+ def type_condition
1245
+ sti_column = arel_table[inheritance_column]
1246
+ condition = sti_column.eq(sti_name)
1247
+ subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
1722
1248
 
1723
- add_group!(sql, options[:group], options[:having], scope)
1724
- add_order!(sql, options[:order], scope)
1725
- add_limit!(sql, options, scope)
1726
- add_lock!(sql, options, scope)
1727
-
1728
- sql
1729
- end
1730
-
1731
- # Merges includes so that the result is a valid +include+
1732
- def merge_includes(first, second)
1733
- (safe_to_array(first) + safe_to_array(second)).uniq
1734
- end
1735
-
1736
- def merge_joins(*joins)
1737
- if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) }
1738
- joins = joins.collect do |join|
1739
- join = [join] if join.is_a?(String)
1740
- unless array_of_strings?(join)
1741
- join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
1742
- join = join_dependency.join_associations.collect { |assoc| assoc.association_join }
1743
- end
1744
- join
1745
- end
1746
- joins.flatten.map{|j| j.strip}.uniq
1747
- else
1748
- joins.collect{|j| safe_to_array(j)}.flatten.uniq
1749
- end
1750
- end
1751
-
1752
- # Object#to_a is deprecated, though it does have the desired behavior
1753
- def safe_to_array(o)
1754
- case o
1755
- when NilClass
1756
- []
1757
- when Array
1758
- o
1759
- else
1760
- [o]
1761
- end
1762
- end
1763
-
1764
- def array_of_strings?(o)
1765
- o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
1766
- end
1767
-
1768
- def add_order!(sql, order, scope = :auto)
1769
- scope = scope(:find) if :auto == scope
1770
- scoped_order = scope[:order] if scope
1771
- if order
1772
- sql << " ORDER BY #{order}"
1773
- if scoped_order && scoped_order != order
1774
- sql << ", #{scoped_order}"
1775
- end
1776
- else
1777
- sql << " ORDER BY #{scoped_order}" if scoped_order
1778
- end
1779
- end
1780
-
1781
- def add_group!(sql, group, having, scope = :auto)
1782
- if group
1783
- sql << " GROUP BY #{group}"
1784
- sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having
1785
- else
1786
- scope = scope(:find) if :auto == scope
1787
- if scope && (scoped_group = scope[:group])
1788
- sql << " GROUP BY #{scoped_group}"
1789
- sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having]
1790
- end
1791
- end
1792
- end
1793
-
1794
- # The optional scope argument is for the current <tt>:find</tt> scope.
1795
- def add_limit!(sql, options, scope = :auto)
1796
- scope = scope(:find) if :auto == scope
1797
-
1798
- if scope
1799
- options[:limit] ||= scope[:limit]
1800
- options[:offset] ||= scope[:offset]
1801
- end
1802
-
1803
- connection.add_limit_offset!(sql, options)
1804
- end
1805
-
1806
- # The optional scope argument is for the current <tt>:find</tt> scope.
1807
- # The <tt>:lock</tt> option has precedence over a scoped <tt>:lock</tt>.
1808
- def add_lock!(sql, options, scope = :auto)
1809
- scope = scope(:find) if :auto == scope
1810
- options = options.reverse_merge(:lock => scope[:lock]) if scope
1811
- connection.add_lock!(sql, options)
1812
- end
1813
-
1814
- # The optional scope argument is for the current <tt>:find</tt> scope.
1815
- def add_joins!(sql, joins, scope = :auto)
1816
- scope = scope(:find) if :auto == scope
1817
- merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
1818
- case merged_joins
1819
- when Symbol, Hash, Array
1820
- if array_of_strings?(merged_joins)
1821
- sql << merged_joins.join(' ') + " "
1822
- else
1823
- join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
1824
- sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
1825
- end
1826
- when String
1827
- sql << " #{merged_joins} "
1828
- end
1829
- end
1830
-
1831
- # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
1832
- # The optional scope argument is for the current <tt>:find</tt> scope.
1833
- def add_conditions!(sql, conditions, scope = :auto)
1834
- scope = scope(:find) if :auto == scope
1835
- conditions = [conditions]
1836
- conditions << scope[:conditions] if scope
1837
- conditions << type_condition if finder_needs_type_condition?
1838
- merged_conditions = merge_conditions(*conditions)
1839
- sql << "WHERE #{merged_conditions} " unless merged_conditions.blank?
1840
- end
1841
-
1842
- def type_condition(table_alias=nil)
1843
- quoted_table_alias = self.connection.quote_table_name(table_alias || table_name)
1844
- quoted_inheritance_column = connection.quote_column_name(inheritance_column)
1845
- type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
1846
- condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
1847
- end
1848
-
1849
- " (#{type_condition}) "
1249
+ condition
1850
1250
  end
1851
1251
 
1852
1252
  # Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -1857,9 +1257,8 @@ module ActiveRecord #:nodoc:
1857
1257
  end
1858
1258
 
1859
1259
  # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
1860
- # that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and
1861
- # <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for
1862
- # <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>.
1260
+ # that are turned into <tt>where(:user_name => user_name).first</tt> and <tt>where(:user_name => user_name, :password => :password).first</tt>
1261
+ # respectively. Also works for <tt>all</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>where(:amount => 50).all</tt>.
1863
1262
  #
1864
1263
  # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
1865
1264
  # is actually <tt>find_all_by_amount(amount, options)</tt>.
@@ -1875,127 +1274,26 @@ module ActiveRecord #:nodoc:
1875
1274
  attribute_names = match.attribute_names
1876
1275
  super unless all_attributes_exists?(attribute_names)
1877
1276
  if match.finder?
1878
- finder = match.finder
1879
- bang = match.bang?
1880
- # def self.find_by_login_and_activated(*args)
1881
- # options = args.extract_options!
1882
- # attributes = construct_attributes_from_arguments(
1883
- # [:login,:activated],
1884
- # args
1885
- # )
1886
- # finder_options = { :conditions => attributes }
1887
- # validate_find_options(options)
1888
- # set_readonly_option!(options)
1889
- #
1890
- # if options[:conditions]
1891
- # with_scope(:find => finder_options) do
1892
- # find(:first, options)
1893
- # end
1894
- # else
1895
- # find(:first, options.merge(finder_options))
1896
- # end
1897
- # end
1898
- self.class_eval <<-EOS, __FILE__, __LINE__ + 1
1899
- def self.#{method_id}(*args)
1900
- options = if args.length > #{attribute_names.size}
1901
- args.extract_options!
1902
- else
1903
- {}
1904
- end
1905
- attributes = construct_attributes_from_arguments(
1906
- [:#{attribute_names.join(',:')}],
1907
- args
1908
- )
1909
- finder_options = { :conditions => attributes }
1910
- validate_find_options(options)
1911
- set_readonly_option!(options)
1912
-
1913
- #{'result = ' if bang}if options[:conditions]
1914
- with_scope(:find => finder_options) do
1915
- find(:#{finder}, options)
1916
- end
1917
- else
1918
- find(:#{finder}, options.merge(finder_options))
1919
- end
1920
- #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
1921
- end
1922
- EOS
1923
- send(method_id, *arguments)
1277
+ options = arguments.extract_options!
1278
+ relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped
1279
+ relation.send :find_by_attributes, match, attribute_names, *arguments
1924
1280
  elsif match.instantiator?
1925
- instantiator = match.instantiator
1926
- # def self.find_or_create_by_user_id(*args)
1927
- # guard_protected_attributes = false
1928
- #
1929
- # if args[0].is_a?(Hash)
1930
- # guard_protected_attributes = true
1931
- # attributes = args[0].with_indifferent_access
1932
- # find_attributes = attributes.slice(*[:user_id])
1933
- # else
1934
- # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args)
1935
- # end
1936
- #
1937
- # options = { :conditions => find_attributes }
1938
- # set_readonly_option!(options)
1939
- #
1940
- # record = find(:first, options)
1941
- #
1942
- # if record.nil?
1943
- # record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
1944
- # yield(record) if block_given?
1945
- # record.save
1946
- # record
1947
- # else
1948
- # record
1949
- # end
1950
- # end
1951
- self.class_eval <<-EOS, __FILE__, __LINE__ + 1
1952
- def self.#{method_id}(*args)
1953
- attributes = [:#{attribute_names.join(',:')}]
1954
- protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
1955
- args.each_with_index do |arg, i|
1956
- if arg.is_a?(Hash)
1957
- protected_attributes_for_create = args[i].with_indifferent_access
1958
- else
1959
- unprotected_attributes_for_create[attributes[i]] = args[i]
1960
- end
1961
- end
1962
-
1963
- find_attributes = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes)
1964
-
1965
- options = { :conditions => find_attributes }
1966
- set_readonly_option!(options)
1967
-
1968
- record = find(:first, options)
1969
-
1970
- if record.nil?
1971
- record = self.new do |r|
1972
- r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
1973
- r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
1974
- end
1975
- #{'yield(record) if block_given?'}
1976
- #{'record.save' if instantiator == :create}
1977
- record
1978
- else
1979
- record
1980
- end
1981
- end
1982
- EOS
1983
- send(method_id, *arguments, &block)
1281
+ scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
1984
1282
  end
1985
1283
  elsif match = DynamicScopeMatch.match(method_id)
1986
1284
  attribute_names = match.attribute_names
1987
1285
  super unless all_attributes_exists?(attribute_names)
1988
1286
  if match.scope?
1989
- self.class_eval <<-EOS, __FILE__, __LINE__ + 1
1287
+ self.class_eval %{
1990
1288
  def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
1991
1289
  options = args.extract_options! # options = args.extract_options!
1992
1290
  attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
1993
1291
  [:#{attribute_names.join(',:')}], args # [:user_name, :password], args
1994
1292
  ) # )
1995
- #
1293
+ #
1996
1294
  scoped(:conditions => attributes) # scoped(:conditions => attributes)
1997
1295
  end # end
1998
- EOS
1296
+ }, __FILE__, __LINE__
1999
1297
  send(method_id, *arguments)
2000
1298
  end
2001
1299
  else
@@ -2042,44 +1340,6 @@ module ActiveRecord #:nodoc:
2042
1340
  end
2043
1341
  end
2044
1342
 
2045
- # Interpret Array and Hash as conditions and anything else as an id.
2046
- def expand_id_conditions(id_or_conditions)
2047
- case id_or_conditions
2048
- when Array, Hash then id_or_conditions
2049
- else sanitize_sql(primary_key => id_or_conditions)
2050
- end
2051
- end
2052
-
2053
- # Defines an "attribute" method (like +inheritance_column+ or
2054
- # +table_name+). A new (class) method will be created with the
2055
- # given name. If a value is specified, the new method will
2056
- # return that value (as a string). Otherwise, the given block
2057
- # will be used to compute the value of the method.
2058
- #
2059
- # The original method will be aliased, with the new name being
2060
- # prefixed with "original_". This allows the new method to
2061
- # access the original value.
2062
- #
2063
- # Example:
2064
- #
2065
- # class A < ActiveRecord::Base
2066
- # define_attr_method :primary_key, "sysid"
2067
- # define_attr_method( :inheritance_column ) do
2068
- # original_inheritance_column + "_id"
2069
- # end
2070
- # end
2071
- def define_attr_method(name, value=nil, &block)
2072
- sing = class << self; self; end
2073
- sing.send :alias_method, "original_#{name}", name
2074
- if block_given?
2075
- sing.send :define_method, name, &block
2076
- else
2077
- # use eval instead of a block to work around a memory leak in dev
2078
- # mode in fcgi
2079
- sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
2080
- end
2081
- end
2082
-
2083
1343
  protected
2084
1344
  # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
2085
1345
  # method_name may be <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
@@ -2105,10 +1365,10 @@ module ActiveRecord #:nodoc:
2105
1365
  # class Article < ActiveRecord::Base
2106
1366
  # def self.find_with_scope
2107
1367
  # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
2108
- # with_scope(:find => { :limit => 10 })
1368
+ # with_scope(:find => { :limit => 10 }) do
2109
1369
  # find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
2110
1370
  # end
2111
- # with_scope(:find => { :conditions => "author_id = 3" })
1371
+ # with_scope(:find => { :conditions => "author_id = 3" }) do
2112
1372
  # find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
2113
1373
  # end
2114
1374
  # end
@@ -2132,55 +1392,35 @@ module ActiveRecord #:nodoc:
2132
1392
  def with_scope(method_scoping = {}, action = :merge, &block)
2133
1393
  method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
2134
1394
 
2135
- # Dup first and second level of hash (method and params).
2136
- method_scoping = method_scoping.inject({}) do |hash, (method, params)|
2137
- hash[method] = (params == true) ? params : params.dup
2138
- hash
2139
- end
2140
-
2141
- method_scoping.assert_valid_keys([ :find, :create ])
1395
+ if method_scoping.is_a?(Hash)
1396
+ # Dup first and second level of hash (method and params).
1397
+ method_scoping = method_scoping.inject({}) do |hash, (method, params)|
1398
+ hash[method] = (params == true) ? params : params.dup
1399
+ hash
1400
+ end
2142
1401
 
2143
- if f = method_scoping[:find]
2144
- f.assert_valid_keys(VALID_FIND_OPTIONS)
2145
- set_readonly_option! f
2146
- end
1402
+ method_scoping.assert_valid_keys([ :find, :create ])
1403
+ relation = construct_finder_arel(method_scoping[:find] || {})
2147
1404
 
2148
- # Merge scopings
2149
- if [:merge, :reverse_merge].include?(action) && current_scoped_methods
2150
- method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
2151
- case hash[method]
2152
- when Hash
2153
- if method == :find
2154
- (hash[method].keys + params.keys).uniq.each do |key|
2155
- merge = hash[method][key] && params[key] # merge if both scopes have the same key
2156
- if key == :conditions && merge
2157
- if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash)
2158
- hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key]))
2159
- else
2160
- hash[method][key] = merge_conditions(params[key], hash[method][key])
2161
- end
2162
- elsif key == :include && merge
2163
- hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
2164
- elsif key == :joins && merge
2165
- hash[method][key] = merge_joins(params[key], hash[method][key])
2166
- else
2167
- hash[method][key] = hash[method][key] || params[key]
2168
- end
2169
- end
2170
- else
2171
- if action == :reverse_merge
2172
- hash[method] = hash[method].merge(params)
2173
- else
2174
- hash[method] = params.merge(hash[method])
2175
- end
2176
- end
2177
- else
2178
- hash[method] = params
1405
+ if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create]
1406
+ scope_for_create = if action == :merge
1407
+ current_scoped_methods.create_with_value.merge(method_scoping[:create])
1408
+ else
1409
+ method_scoping[:create]
2179
1410
  end
2180
- hash
1411
+
1412
+ relation = relation.create_with(scope_for_create)
1413
+ else
1414
+ scope_for_create = method_scoping[:create]
1415
+ scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods
1416
+ relation = relation.create_with(scope_for_create) if scope_for_create
2181
1417
  end
1418
+
1419
+ method_scoping = relation
2182
1420
  end
2183
1421
 
1422
+ method_scoping = current_scoped_methods.merge(method_scoping) if current_scoped_methods && action == :merge
1423
+
2184
1424
  self.scoped_methods << method_scoping
2185
1425
  begin
2186
1426
  yield
@@ -2206,25 +1446,12 @@ module ActiveRecord #:nodoc:
2206
1446
  # default_scope :order => 'last_name, first_name'
2207
1447
  # end
2208
1448
  def default_scope(options = {})
2209
- self.default_scoping << { :find => options, :create => options[:conditions].is_a?(Hash) ? options[:conditions] : {} }
2210
- end
2211
-
2212
- # Test whether the given method and optional key are scoped.
2213
- def scoped?(method, key = nil) #:nodoc:
2214
- if current_scoped_methods && (scope = current_scoped_methods[method])
2215
- !key || !scope[key].nil?
2216
- end
2217
- end
2218
-
2219
- # Retrieve the scope for the given method and optional key.
2220
- def scope(method, key = nil) #:nodoc:
2221
- if current_scoped_methods && (scope = current_scoped_methods[method])
2222
- key ? scope[key] : scope
2223
- end
1449
+ self.default_scoping << construct_finder_arel(options)
2224
1450
  end
2225
1451
 
2226
1452
  def scoped_methods #:nodoc:
2227
- Thread.current[:"#{self}_scoped_methods"] ||= self.default_scoping.dup
1453
+ key = :"#{self}_scoped_methods"
1454
+ Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
2228
1455
  end
2229
1456
 
2230
1457
  def current_scoped_methods #:nodoc:
@@ -2237,9 +1464,9 @@ module ActiveRecord #:nodoc:
2237
1464
  modularized_name = type_name_with_module(type_name)
2238
1465
  silence_warnings do
2239
1466
  begin
2240
- class_eval(modularized_name, __FILE__)
1467
+ class_eval(modularized_name, __FILE__, __LINE__)
2241
1468
  rescue NameError
2242
- class_eval(type_name, __FILE__)
1469
+ class_eval(type_name, __FILE__, __LINE__)
2243
1470
  end
2244
1471
  end
2245
1472
  end
@@ -2266,7 +1493,7 @@ module ActiveRecord #:nodoc:
2266
1493
  # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
2267
1494
  # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
2268
1495
  # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
2269
- def sanitize_sql_for_conditions(condition, table_name = quoted_table_name)
1496
+ def sanitize_sql_for_conditions(condition, table_name = self.table_name)
2270
1497
  return nil if condition.blank?
2271
1498
 
2272
1499
  case condition
@@ -2307,7 +1534,7 @@ module ActiveRecord #:nodoc:
2307
1534
  def expand_hash_conditions_for_aggregates(attrs)
2308
1535
  expanded_attrs = {}
2309
1536
  attrs.each do |attr, value|
2310
- unless (aggregation = reflect_on_aggregation(attr)).nil?
1537
+ unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
2311
1538
  mapping = aggregate_mapping(aggregation)
2312
1539
  mapping.each do |field_attr, aggregate_attr|
2313
1540
  if mapping.size == 1 && !value.respond_to?(aggregate_attr)
@@ -2337,34 +1564,12 @@ module ActiveRecord #:nodoc:
2337
1564
  # And for value objects on a composed_of relationship:
2338
1565
  # { :address => Address.new("123 abc st.", "chicago") }
2339
1566
  # # => "address_street='123 abc st.' and address_city='chicago'"
2340
- def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name, top_level = true)
1567
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
2341
1568
  attrs = expand_hash_conditions_for_aggregates(attrs)
2342
1569
 
2343
- return '1 = 2' if !top_level && attrs.is_a?(Hash) && attrs.empty?
2344
-
2345
- conditions = attrs.map do |attr, value|
2346
- table_name = default_table_name
2347
-
2348
- if not value.is_a?(Hash)
2349
- attr = attr.to_s
2350
-
2351
- # Extract table name from qualified attribute names.
2352
- if attr.include?('.') and top_level
2353
- attr_table_name, attr = attr.split('.', 2)
2354
- attr_table_name = connection.quote_table_name(attr_table_name)
2355
- else
2356
- attr_table_name = table_name
2357
- end
2358
-
2359
- attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
2360
- elsif top_level
2361
- sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s), false)
2362
- else
2363
- raise ActiveRecord::StatementInvalid
2364
- end
2365
- end.join(' AND ')
2366
-
2367
- replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
1570
+ table = Arel::Table.new(self.table_name, :engine => arel_engine, :as => default_table_name)
1571
+ builder = PredicateBuilder.new(arel_engine)
1572
+ builder.build_from_hash(attrs, table).map(&:to_sql).join(' AND ')
2368
1573
  end
2369
1574
  alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
2370
1575
 
@@ -2446,25 +1651,6 @@ module ActiveRecord #:nodoc:
2446
1651
  end
2447
1652
  end
2448
1653
 
2449
- VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
2450
- :order, :select, :readonly, :group, :having, :from, :lock ]
2451
-
2452
- def validate_find_options(options) #:nodoc:
2453
- options.assert_valid_keys(VALID_FIND_OPTIONS)
2454
- end
2455
-
2456
- def set_readonly_option!(options) #:nodoc:
2457
- # Inherit :readonly from finder scope if set. Otherwise,
2458
- # if :joins is not blank then :readonly defaults to true.
2459
- unless options.has_key?(:readonly)
2460
- if scoped_readonly = scope(:find, :readonly)
2461
- options[:readonly] = scoped_readonly
2462
- elsif !options[:joins].blank? && !options[:select]
2463
- options[:readonly] = true
2464
- end
2465
- end
2466
- end
2467
-
2468
1654
  def encode_quoted_value(value) #:nodoc:
2469
1655
  quoted_value = connection.quote(value)
2470
1656
  quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "
@@ -2483,22 +1669,42 @@ module ActiveRecord #:nodoc:
2483
1669
  @new_record = true
2484
1670
  ensure_proper_type
2485
1671
  self.attributes = attributes unless attributes.nil?
2486
- assign_attributes(self.class.send(:scope, :create)) if self.class.send(:scoped?, :create)
1672
+
1673
+ if scope = self.class.send(:current_scoped_methods)
1674
+ create_with = scope.scope_for_create
1675
+ create_with.each { |att,value| self.send("#{att}=", value) } if create_with
1676
+ end
1677
+
2487
1678
  result = yield self if block_given?
2488
- callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
1679
+ _run_initialize_callbacks
2489
1680
  result
2490
1681
  end
2491
1682
 
2492
- # A model instance's primary key is always available as model.id
2493
- # whether you name it the default 'id' or set it to something else.
2494
- def id
2495
- attr_name = self.class.primary_key
2496
- column = column_for_attribute(attr_name)
2497
-
2498
- self.class.send(:define_read_method, :id, attr_name, column)
2499
- # now that the method exists, call it
2500
- self.send attr_name.to_sym
1683
+ # Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone
1684
+ # as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
1685
+ # application specific and is therefore left to the application to implement according to its need.
1686
+ def initialize_copy(other)
1687
+ # Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
1688
+ # deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
1689
+ # over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
1690
+ # For example in the test suite the topic model's after_initialize method sets the author_email_address to
1691
+ # test@test.com. I would have thought this would mean that all cloned models would have an author email address
1692
+ # of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
1693
+ # after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
1694
+ # for all tests to pass. This makes no sense to me.
1695
+ callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
1696
+ cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
1697
+ cloned_attributes.delete(self.class.primary_key)
1698
+ @attributes = cloned_attributes
1699
+ clear_aggregation_cache
1700
+ @attributes_cache = {}
1701
+ @new_record = true
1702
+ ensure_proper_type
2501
1703
 
1704
+ if scope = self.class.send(:current_scoped_methods)
1705
+ create_with = scope.scope_for_create
1706
+ create_with.each { |att,value| self.send("#{att}=", value) } if create_with
1707
+ end
2502
1708
  end
2503
1709
 
2504
1710
  # Returns a String, which Action Pack uses for constructing an URL to this
@@ -2520,7 +1726,7 @@ module ActiveRecord #:nodoc:
2520
1726
  # name
2521
1727
  # end
2522
1728
  # end
2523
- #
1729
+ #
2524
1730
  # user = User.find_by_name('Phusion')
2525
1731
  # user_path(user) # => "/users/Phusion"
2526
1732
  def to_param
@@ -2546,41 +1752,37 @@ module ActiveRecord #:nodoc:
2546
1752
  end
2547
1753
  end
2548
1754
 
2549
- def id_before_type_cast #:nodoc:
2550
- read_attribute_before_type_cast(self.class.primary_key)
2551
- end
2552
-
2553
1755
  def quoted_id #:nodoc:
2554
1756
  quote_value(id, column_for_attribute(self.class.primary_key))
2555
1757
  end
2556
1758
 
2557
- # Sets the primary ID.
2558
- def id=(value)
2559
- write_attribute(self.class.primary_key, value)
2560
- end
2561
-
2562
1759
  # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
2563
1760
  def new_record?
2564
1761
  @new_record || false
2565
1762
  end
2566
1763
 
1764
+ # Returns true if this object has been destroyed, otherwise returns false.
1765
+ def destroyed?
1766
+ @destroyed || false
1767
+ end
1768
+
2567
1769
  # :call-seq:
2568
- # save(perform_validation = true)
1770
+ # save(options)
2569
1771
  #
2570
1772
  # Saves the model.
2571
1773
  #
2572
1774
  # If the model is new a record gets created in the database, otherwise
2573
1775
  # the existing record gets updated.
2574
1776
  #
2575
- # If +perform_validation+ is true validations run. If any of them fail
2576
- # the action is cancelled and +save+ returns +false+. If the flag is
2577
- # false validations are bypassed altogether. See
2578
- # ActiveRecord::Validations for more information.
1777
+ # By default, save always run validations. If any of them fail the action
1778
+ # is cancelled and +save+ returns +false+. However, if you supply
1779
+ # :validate => false, validations are bypassed altogether. See
1780
+ # ActiveRecord::Validations for more information.
2579
1781
  #
2580
1782
  # There's a series of callbacks associated with +save+. If any of the
2581
1783
  # <tt>before_*</tt> callbacks return +false+ the action is cancelled and
2582
1784
  # +save+ returns +false+. See ActiveRecord::Callbacks for further
2583
- # details.
1785
+ # details.
2584
1786
  def save
2585
1787
  create_or_update
2586
1788
  end
@@ -2592,12 +1794,12 @@ module ActiveRecord #:nodoc:
2592
1794
  #
2593
1795
  # With <tt>save!</tt> validations always run. If any of them fail
2594
1796
  # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
2595
- # for more information.
1797
+ # for more information.
2596
1798
  #
2597
1799
  # There's a series of callbacks associated with <tt>save!</tt>. If any of
2598
1800
  # the <tt>before_*</tt> callbacks return +false+ the action is cancelled
2599
1801
  # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
2600
- # ActiveRecord::Callbacks for further details.
1802
+ # ActiveRecord::Callbacks for further details.
2601
1803
  def save!
2602
1804
  create_or_update || raise(RecordNotSaved)
2603
1805
  end
@@ -2622,30 +1824,13 @@ module ActiveRecord #:nodoc:
2622
1824
  # be made (since they can't be persisted).
2623
1825
  def destroy
2624
1826
  unless new_record?
2625
- connection.delete(
2626
- "DELETE FROM #{self.class.quoted_table_name} " +
2627
- "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}",
2628
- "#{self.class.name} Destroy"
2629
- )
1827
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
2630
1828
  end
2631
1829
 
2632
1830
  @destroyed = true
2633
1831
  freeze
2634
1832
  end
2635
1833
 
2636
- # Returns a clone of the record that hasn't been assigned an id yet and
2637
- # is treated as a new record. Note that this is a "shallow" clone:
2638
- # it copies the object's attributes only, not its associations.
2639
- # The extent of a "deep" clone is application-specific and is therefore
2640
- # left to the application to implement according to its need.
2641
- def clone
2642
- attrs = clone_attributes(:read_attribute_before_type_cast)
2643
- attrs.delete(self.class.primary_key)
2644
- record = self.class.new
2645
- record.send :instance_variable_set, '@attributes', attrs
2646
- record
2647
- end
2648
-
2649
1834
  # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
2650
1835
  # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
2651
1836
  # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
@@ -2654,11 +1839,11 @@ module ActiveRecord #:nodoc:
2654
1839
  # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
2655
1840
  # instance will affect the other.
2656
1841
  def becomes(klass)
2657
- klass.new.tap do |became|
2658
- became.instance_variable_set("@attributes", @attributes)
2659
- became.instance_variable_set("@attributes_cache", @attributes_cache)
2660
- became.instance_variable_set("@new_record", new_record?)
2661
- end
1842
+ became = klass.new
1843
+ became.instance_variable_set("@attributes", @attributes)
1844
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
1845
+ became.instance_variable_set("@new_record", new_record?)
1846
+ became
2662
1847
  end
2663
1848
 
2664
1849
  # Updates a single attribute and saves the record without going through the normal validation procedure.
@@ -2666,26 +1851,18 @@ module ActiveRecord #:nodoc:
2666
1851
  # in Base is replaced with this when the validations module is mixed in, which it is by default.
2667
1852
  def update_attribute(name, value)
2668
1853
  send(name.to_s + '=', value)
2669
- save(false)
1854
+ save(:validate => false)
2670
1855
  end
2671
1856
 
2672
1857
  # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
2673
1858
  # fail and false will be returned.
2674
1859
  def update_attributes(attributes)
2675
- with_transaction_returning_status(:update_attributes_inside_transaction, attributes)
2676
- end
2677
-
2678
- def update_attributes_inside_transaction(attributes) #:nodoc:
2679
1860
  self.attributes = attributes
2680
1861
  save
2681
1862
  end
2682
1863
 
2683
1864
  # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
2684
1865
  def update_attributes!(attributes)
2685
- with_transaction_returning_status(:update_attributes_inside_transaction!, attributes)
2686
- end
2687
-
2688
- def update_attributes_inside_transaction!(attributes) #:nodoc:
2689
1866
  self.attributes = attributes
2690
1867
  save!
2691
1868
  end
@@ -2748,11 +1925,21 @@ module ActiveRecord #:nodoc:
2748
1925
  def reload(options = nil)
2749
1926
  clear_aggregation_cache
2750
1927
  clear_association_cache
2751
- @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
1928
+ @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
2752
1929
  @attributes_cache = {}
2753
1930
  self
2754
1931
  end
2755
1932
 
1933
+ # Returns true if the given attribute is in the attributes hash
1934
+ def has_attribute?(attr_name)
1935
+ @attributes.has_key?(attr_name.to_s)
1936
+ end
1937
+
1938
+ # Returns an array of names for the attributes available on this object sorted alphabetically.
1939
+ def attribute_names
1940
+ @attributes.keys.sort
1941
+ end
1942
+
2756
1943
  # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
2757
1944
  # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
2758
1945
  # (Alias for the protected read_attribute method).
@@ -2778,12 +1965,12 @@ module ActiveRecord #:nodoc:
2778
1965
  # class User < ActiveRecord::Base
2779
1966
  # attr_protected :is_admin
2780
1967
  # end
2781
- #
1968
+ #
2782
1969
  # user = User.new
2783
1970
  # user.attributes = { :username => 'Phusion', :is_admin => true }
2784
1971
  # user.username # => "Phusion"
2785
1972
  # user.is_admin? # => false
2786
- #
1973
+ #
2787
1974
  # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
2788
1975
  # user.is_admin? # => true
2789
1976
  def attributes=(new_attributes, guard_protected_attributes = true)
@@ -2791,21 +1978,24 @@ module ActiveRecord #:nodoc:
2791
1978
  attributes = new_attributes.dup
2792
1979
  attributes.stringify_keys!
2793
1980
 
1981
+ multi_parameter_attributes = []
2794
1982
  attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
2795
- assign_attributes(attributes) if attributes and attributes.any?
1983
+
1984
+ attributes.each do |k, v|
1985
+ if k.include?("(")
1986
+ multi_parameter_attributes << [ k, v ]
1987
+ else
1988
+ respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
1989
+ end
1990
+ end
1991
+
1992
+ assign_multiparameter_attributes(multi_parameter_attributes)
2796
1993
  end
2797
1994
 
2798
1995
  # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
2799
1996
  def attributes
2800
- attrs = {}
2801
- attribute_names.each { |name| attrs[name] = read_attribute(name) }
2802
- attrs
2803
- end
2804
-
2805
- # Returns a hash of attributes before typecasting and deserialization.
2806
- def attributes_before_type_cast
2807
1997
  self.attribute_names.inject({}) do |attrs, name|
2808
- attrs[name] = read_attribute_before_type_cast(name)
1998
+ attrs[name] = read_attribute(name)
2809
1999
  attrs
2810
2000
  end
2811
2001
  end
@@ -2842,16 +2032,6 @@ module ActiveRecord #:nodoc:
2842
2032
  !value.blank?
2843
2033
  end
2844
2034
 
2845
- # Returns true if the given attribute is in the attributes hash
2846
- def has_attribute?(attr_name)
2847
- @attributes.has_key?(attr_name.to_s)
2848
- end
2849
-
2850
- # Returns an array of names for the attributes available on this object sorted alphabetically.
2851
- def attribute_names
2852
- @attributes.keys.sort
2853
- end
2854
-
2855
2035
  # Returns the column object for the named attribute.
2856
2036
  def column_for_attribute(name)
2857
2037
  self.class.columns_hash[name.to_s]
@@ -2886,9 +2066,11 @@ module ActiveRecord #:nodoc:
2886
2066
  @attributes.frozen?
2887
2067
  end
2888
2068
 
2889
- # Returns +true+ if the record has been destroyed.
2890
- def destroyed?
2891
- @destroyed
2069
+ # Returns duplicated record with unfreezed attributes.
2070
+ def dup
2071
+ obj = super
2072
+ obj.instance_variable_set('@attributes', instance_variable_get('@attributes').dup)
2073
+ obj
2892
2074
  end
2893
2075
 
2894
2076
  # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
@@ -2912,24 +2094,22 @@ module ActiveRecord #:nodoc:
2912
2094
  "#<#{self.class} #{attributes_as_nice_string}>"
2913
2095
  end
2914
2096
 
2915
- private
2916
- # Assigns attributes, dealing nicely with both multi and single paramater attributes
2917
- # Assumes attributes is a hash
2918
-
2919
- def assign_attributes(attributes={})
2920
- multiparameter_attributes = []
2921
-
2922
- attributes.each do |k, v|
2923
- if k.to_s.include?("(")
2924
- multiparameter_attributes << [ k, v ]
2925
- else
2926
- respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
2927
- end
2097
+ protected
2098
+ def clone_attributes(reader_method = :read_attribute, attributes = {})
2099
+ self.attribute_names.inject(attributes) do |attrs, name|
2100
+ attrs[name] = clone_attribute_value(reader_method, name)
2101
+ attrs
2928
2102
  end
2103
+ end
2929
2104
 
2930
- assign_multiparameter_attributes(multiparameter_attributes) unless multiparameter_attributes.empty?
2105
+ def clone_attribute_value(reader_method, attribute_name)
2106
+ value = send(reader_method, attribute_name)
2107
+ value.duplicable? ? value.clone : value
2108
+ rescue TypeError, NoMethodError
2109
+ value
2931
2110
  end
2932
-
2111
+
2112
+ private
2933
2113
  def create_or_update
2934
2114
  raise ReadOnlyRecord if readonly?
2935
2115
  result = new_record? ? create : update
@@ -2939,14 +2119,9 @@ module ActiveRecord #:nodoc:
2939
2119
  # Updates the associated record with values matching those of the instance attributes.
2940
2120
  # Returns the number of affected rows.
2941
2121
  def update(attribute_names = @attributes.keys)
2942
- quoted_attributes = attributes_with_quotes(false, false, attribute_names)
2943
- return 0 if quoted_attributes.empty?
2944
- connection.update(
2945
- "UPDATE #{self.class.quoted_table_name} " +
2946
- "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " +
2947
- "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
2948
- "#{self.class.name} Update"
2949
- )
2122
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
2123
+ return 0 if attributes_with_values.empty?
2124
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
2950
2125
  end
2951
2126
 
2952
2127
  # Creates a record with values matching those of the instance attributes
@@ -2956,18 +2131,15 @@ module ActiveRecord #:nodoc:
2956
2131
  self.id = connection.next_sequence_value(self.class.sequence_name)
2957
2132
  end
2958
2133
 
2959
- quoted_attributes = attributes_with_quotes
2134
+ attributes_values = arel_attributes_values
2960
2135
 
2961
- statement = if quoted_attributes.empty?
2962
- connection.empty_insert_statement(self.class.table_name)
2136
+ new_id = if attributes_values.empty?
2137
+ self.class.unscoped.insert connection.empty_insert_statement_value
2963
2138
  else
2964
- "INSERT INTO #{self.class.quoted_table_name} " +
2965
- "(#{quoted_column_names.join(', ')}) " +
2966
- "VALUES(#{quoted_attributes.values.join(', ')})"
2139
+ self.class.unscoped.insert attributes_values
2967
2140
  end
2968
2141
 
2969
- self.id = connection.insert(statement, "#{self.class.name} Create",
2970
- self.class.primary_key, self.id, self.class.sequence_name)
2142
+ self.id ||= new_id
2971
2143
 
2972
2144
  @new_record = false
2973
2145
  id
@@ -2983,26 +2155,14 @@ module ActiveRecord #:nodoc:
2983
2155
  end
2984
2156
  end
2985
2157
 
2986
- def convert_number_column_value(value)
2987
- if value == false
2988
- 0
2989
- elsif value == true
2990
- 1
2991
- elsif value.is_a?(String) && value.blank?
2992
- nil
2993
- else
2994
- value
2995
- end
2996
- end
2997
-
2998
2158
  def remove_attributes_protected_from_mass_assignment(attributes)
2999
2159
  safe_attributes =
3000
2160
  if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
3001
- attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/m, "")) }
2161
+ attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
3002
2162
  elsif self.class.protected_attributes.nil?
3003
- attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/m, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/m, "")) }
2163
+ attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
3004
2164
  elsif self.class.accessible_attributes.nil?
3005
- attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/m,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/m, "")) }
2165
+ attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
3006
2166
  else
3007
2167
  raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
3008
2168
  end
@@ -3026,7 +2186,9 @@ module ActiveRecord #:nodoc:
3026
2186
  end
3027
2187
 
3028
2188
  def log_protected_attribute_removal(*attributes)
3029
- logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
2189
+ if logger
2190
+ logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
2191
+ end
3030
2192
  end
3031
2193
 
3032
2194
  # The primary key and inheritance column can never be set by mass-assignment for security reasons.
@@ -3056,6 +2218,26 @@ module ActiveRecord #:nodoc:
3056
2218
  include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
3057
2219
  end
3058
2220
 
2221
+ # Returns a copy of the attributes hash where all the values have been safely quoted for use in
2222
+ # an Arel insert/update method.
2223
+ def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
2224
+ attrs = {}
2225
+ attribute_names.each do |name|
2226
+ if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
2227
+
2228
+ if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
2229
+ value = read_attribute(name)
2230
+
2231
+ if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array))
2232
+ value = value.to_yaml
2233
+ end
2234
+ attrs[self.class.arel_table[name]] = value
2235
+ end
2236
+ end
2237
+ end
2238
+ attrs
2239
+ end
2240
+
3059
2241
  # Quote strings appropriately for SQL statements.
3060
2242
  def quote_value(value, column = nil)
3061
2243
  self.class.connection.quote(value, column)
@@ -3160,18 +2342,7 @@ module ActiveRecord #:nodoc:
3160
2342
 
3161
2343
  # Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
3162
2344
  def comma_pair_list(hash)
3163
- hash.map { |k,v| "#{k} = #{v}" }.join(", ")
3164
- end
3165
-
3166
- def quoted_column_names(attributes = attributes_with_quotes)
3167
- connection = self.class.connection
3168
- attributes.keys.collect do |column_name|
3169
- connection.quote_column_name(column_name)
3170
- end
3171
- end
3172
-
3173
- def self.quoted_table_name
3174
- self.connection.quote_table_name(self.table_name)
2345
+ hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
3175
2346
  end
3176
2347
 
3177
2348
  def quote_columns(quoter, hash)
@@ -3185,40 +2356,46 @@ module ActiveRecord #:nodoc:
3185
2356
  comma_pair_list(quote_columns(quoter, hash))
3186
2357
  end
3187
2358
 
3188
- def object_from_yaml(string)
3189
- return string unless string.is_a?(String) && string =~ /^---/
3190
- YAML::load(string) rescue string
3191
- end
3192
-
3193
- def clone_attributes(reader_method = :read_attribute, attributes = {})
3194
- self.attribute_names.inject(attributes) do |attrs, name|
3195
- attrs[name] = clone_attribute_value(reader_method, name)
3196
- attrs
2359
+ def convert_number_column_value(value)
2360
+ if value == false
2361
+ 0
2362
+ elsif value == true
2363
+ 1
2364
+ elsif value.is_a?(String) && value.blank?
2365
+ nil
2366
+ else
2367
+ value
3197
2368
  end
3198
2369
  end
3199
2370
 
3200
- def clone_attribute_value(reader_method, attribute_name)
3201
- value = send(reader_method, attribute_name)
3202
- value.duplicable? ? value.clone : value
3203
- rescue TypeError, NoMethodError
3204
- value
2371
+ def object_from_yaml(string)
2372
+ return string unless string.is_a?(String) && string =~ /^---/
2373
+ YAML::load(string) rescue string
3205
2374
  end
3206
2375
  end
3207
2376
 
3208
2377
  Base.class_eval do
2378
+ extend ActiveModel::Naming
3209
2379
  extend QueryCache::ClassMethods
2380
+ extend ActiveSupport::Benchmarkable
2381
+
3210
2382
  include Validations
3211
2383
  include Locking::Optimistic, Locking::Pessimistic
3212
2384
  include AttributeMethods
3213
- include Dirty
3214
- include Callbacks, Observing, Timestamp
2385
+ include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
2386
+ include AttributeMethods::PrimaryKey
2387
+ include AttributeMethods::TimeZoneConversion
2388
+ include AttributeMethods::Dirty
2389
+ include Callbacks, ActiveModel::Observing, Timestamp
3215
2390
  include Associations, AssociationPreload, NamedScope
2391
+ include ActiveModel::Conversion
3216
2392
 
3217
2393
  # AutosaveAssociation needs to be included before Transactions, because we want
3218
2394
  # #save_with_autosave_associations to be wrapped inside a transaction.
3219
2395
  include AutosaveAssociation, NestedAttributes
3220
2396
 
3221
- include Aggregations, Transactions, Reflection, Batches, Calculations, Serialization
2397
+ include Aggregations, Transactions, Reflection, Batches, Serialization
2398
+
3222
2399
  end
3223
2400
  end
3224
2401