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
@@ -0,0 +1,315 @@
1
+ module ActiveRecord
2
+ module FinderMethods
3
+ # Find operates with four different retrieval approaches:
4
+ #
5
+ # * 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]).
6
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
7
+ # * Find first - This will return the first record matched by the options used. These options can either be specific
8
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
9
+ # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
10
+ # * Find last - This will return the last record matched by the options used. These options can either be specific
11
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
12
+ # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
13
+ # * Find all - This will return all the records matched by the options used.
14
+ # If no records are found, an empty array is returned. Use
15
+ # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
16
+ #
17
+ # All approaches accept an options hash as their last parameter.
18
+ #
19
+ # ==== Parameters
20
+ #
21
+ # * <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.
22
+ # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
23
+ # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
24
+ # * <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.
25
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
26
+ # * <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.
27
+ # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
28
+ # 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),
29
+ # or an array containing a mixture of both strings and named associations.
30
+ # 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.
31
+ # Pass <tt>:readonly => false</tt> to override.
32
+ # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
33
+ # to already defined associations. See eager loading under Associations.
34
+ # * <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
35
+ # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
36
+ # * <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
37
+ # of a database view).
38
+ # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
39
+ # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
40
+ # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
41
+ #
42
+ # ==== Examples
43
+ #
44
+ # # find by id
45
+ # Person.find(1) # returns the object for ID = 1
46
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
47
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
48
+ # Person.find([1]) # returns an array for the object with ID = 1
49
+ # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
50
+ #
51
+ # Note that returned records may not be in the same order as the ids you
52
+ # provide since database rows are unordered. Give an explicit <tt>:order</tt>
53
+ # to ensure the results are sorted.
54
+ #
55
+ # ==== Examples
56
+ #
57
+ # # find first
58
+ # Person.find(:first) # returns the first object fetched by SELECT * FROM people
59
+ # Person.find(:first, :conditions => [ "user_name = ?", user_name])
60
+ # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
61
+ # Person.find(:first, :order => "created_on DESC", :offset => 5)
62
+ #
63
+ # # find last
64
+ # Person.find(:last) # returns the last object fetched by SELECT * FROM people
65
+ # Person.find(:last, :conditions => [ "user_name = ?", user_name])
66
+ # Person.find(:last, :order => "created_on DESC", :offset => 5)
67
+ #
68
+ # # find all
69
+ # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
70
+ # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
71
+ # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
72
+ # Person.find(:all, :offset => 10, :limit => 10)
73
+ # Person.find(:all, :include => [ :account, :friends ])
74
+ # Person.find(:all, :group => "category")
75
+ #
76
+ # Example for find with a lock: Imagine two concurrent transactions:
77
+ # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
78
+ # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
79
+ # transaction has to wait until the first is finished; we get the
80
+ # expected <tt>person.visits == 4</tt>.
81
+ #
82
+ # Person.transaction do
83
+ # person = Person.find(1, :lock => true)
84
+ # person.visits += 1
85
+ # person.save!
86
+ # end
87
+ def find(*args, &block)
88
+ return to_a.find(&block) if block_given?
89
+
90
+ options = args.extract_options!
91
+
92
+ if options.present?
93
+ apply_finder_options(options).find(*args)
94
+ else
95
+ case args.first
96
+ when :first, :last, :all
97
+ send(args.first)
98
+ else
99
+ find_with_ids(*args)
100
+ end
101
+ end
102
+ end
103
+
104
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
105
+ # same arguments to this method as you can to <tt>find(:first)</tt>.
106
+ def first(*args)
107
+ args.any? ? apply_finder_options(args.first).first : find_first
108
+ end
109
+
110
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
111
+ # same arguments to this method as you can to <tt>find(:last)</tt>.
112
+ def last(*args)
113
+ args.any? ? apply_finder_options(args.first).last : find_last
114
+ end
115
+
116
+ # A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
117
+ # same arguments to this method as you can to <tt>find(:all)</tt>.
118
+ def all(*args)
119
+ args.any? ? apply_finder_options(args.first).to_a : to_a
120
+ end
121
+
122
+ # Returns true if a record exists in the table that matches the +id+ or
123
+ # conditions given, or false otherwise. The argument can take five forms:
124
+ #
125
+ # * Integer - Finds the record with this primary key.
126
+ # * String - Finds the record with a primary key corresponding to this
127
+ # string (such as <tt>'5'</tt>).
128
+ # * Array - Finds the record that matches these +find+-style conditions
129
+ # (such as <tt>['color = ?', 'red']</tt>).
130
+ # * Hash - Finds the record that matches these +find+-style conditions
131
+ # (such as <tt>{:color => 'red'}</tt>).
132
+ # * No args - Returns false if the table is empty, true otherwise.
133
+ #
134
+ # For more information about specifying conditions as a Hash or Array,
135
+ # see the Conditions section in the introduction to ActiveRecord::Base.
136
+ #
137
+ # Note: You can't pass in a condition as a string (like <tt>name =
138
+ # 'Jamie'</tt>), since it would be sanitized and then queried against
139
+ # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
140
+ #
141
+ # ==== Examples
142
+ # Person.exists?(5)
143
+ # Person.exists?('5')
144
+ # Person.exists?(:name => "David")
145
+ # Person.exists?(['name LIKE ?', "%#{query}%"])
146
+ # Person.exists?
147
+ def exists?(id = nil)
148
+ case id
149
+ when Array, Hash
150
+ where(id).exists?
151
+ else
152
+ relation = select(primary_key).limit(1)
153
+ relation = relation.where(primary_key.eq(id)) if id
154
+ relation.first ? true : false
155
+ end
156
+ end
157
+
158
+ protected
159
+
160
+ def find_with_associations
161
+ including = (@eager_load_values + @includes_values).uniq
162
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
163
+ rows = construct_relation_for_association_find(join_dependency).to_a
164
+ join_dependency.instantiate(rows)
165
+ rescue ThrowResult
166
+ []
167
+ end
168
+
169
+ def construct_relation_for_association_calculations
170
+ including = (@eager_load_values + @includes_values).uniq
171
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.joins(arel))
172
+
173
+ relation = except(:includes, :eager_load, :preload)
174
+ apply_join_dependency(relation, join_dependency)
175
+ end
176
+
177
+ def construct_relation_for_association_find(join_dependency)
178
+ relation = except(:includes, :eager_load, :preload, :select).select(@klass.send(:column_aliases, join_dependency))
179
+ apply_join_dependency(relation, join_dependency)
180
+ end
181
+
182
+ def apply_join_dependency(relation, join_dependency)
183
+ for association in join_dependency.join_associations
184
+ relation = association.join_relation(relation)
185
+ end
186
+
187
+ limitable_reflections = @klass.send(:using_limitable_reflections?, join_dependency.reflections)
188
+
189
+ if !limitable_reflections && relation.limit_value
190
+ limited_id_condition = construct_limited_ids_condition(relation.except(:select))
191
+ relation = relation.where(limited_id_condition)
192
+ end
193
+
194
+ relation = relation.except(:limit, :offset) unless limitable_reflections
195
+
196
+ relation
197
+ end
198
+
199
+ def construct_limited_ids_condition(relation)
200
+ orders = relation.order_values.join(", ")
201
+ values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders)
202
+
203
+ ids_array = relation.select(values).collect {|row| row[@klass.primary_key]}
204
+ ids_array.empty? ? raise(ThrowResult) : primary_key.in(ids_array)
205
+ end
206
+
207
+ def find_by_attributes(match, attributes, *args)
208
+ conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h}
209
+ result = where(conditions).send(match.finder)
210
+
211
+ if match.bang? && result.blank?
212
+ raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
213
+ else
214
+ result
215
+ end
216
+ end
217
+
218
+ def find_or_instantiator_by_attributes(match, attributes, *args)
219
+ guard_protected_attributes = false
220
+
221
+ if args[0].is_a?(Hash)
222
+ guard_protected_attributes = true
223
+ attributes_for_create = args[0].with_indifferent_access
224
+ conditions = attributes_for_create.slice(*attributes).symbolize_keys
225
+ else
226
+ attributes_for_create = conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h}
227
+ end
228
+
229
+ record = where(conditions).first
230
+
231
+ unless record
232
+ record = @klass.new { |r| r.send(:attributes=, attributes_for_create, guard_protected_attributes) }
233
+ yield(record) if block_given?
234
+ record.save if match.instantiator == :create
235
+ end
236
+
237
+ record
238
+ end
239
+
240
+ def find_with_ids(*ids, &block)
241
+ return to_a.find(&block) if block_given?
242
+
243
+ expects_array = ids.first.kind_of?(Array)
244
+ return ids.first if expects_array && ids.first.empty?
245
+
246
+ ids = ids.flatten.compact.uniq
247
+
248
+ case ids.size
249
+ when 0
250
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
251
+ when 1
252
+ result = find_one(ids.first)
253
+ expects_array ? [ result ] : result
254
+ else
255
+ find_some(ids)
256
+ end
257
+ end
258
+
259
+ def find_one(id)
260
+ record = where(primary_key.eq(id)).first
261
+
262
+ unless record
263
+ conditions = arel.send(:where_clauses).join(', ')
264
+ conditions = " [WHERE #{conditions}]" if conditions.present?
265
+ raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}"
266
+ end
267
+
268
+ record
269
+ end
270
+
271
+ def find_some(ids)
272
+ result = where(primary_key.in(ids)).all
273
+
274
+ expected_size =
275
+ if @limit_value && ids.size > @limit_value
276
+ @limit_value
277
+ else
278
+ ids.size
279
+ end
280
+
281
+ # 11 ids with limit 3, offset 9 should give 2 results.
282
+ if @offset_value && (ids.size - @offset_value < expected_size)
283
+ expected_size = ids.size - @offset_value
284
+ end
285
+
286
+ if result.size == expected_size
287
+ result
288
+ else
289
+ conditions = arel.send(:where_clauses).join(', ')
290
+ conditions = " [WHERE #{conditions}]" if conditions.present?
291
+
292
+ error = "Couldn't find all #{@klass.name.pluralize} with IDs "
293
+ error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
294
+ raise RecordNotFound, error
295
+ end
296
+ end
297
+
298
+ def find_first
299
+ if loaded?
300
+ @records.first
301
+ else
302
+ @first ||= limit(1).to_a[0]
303
+ end
304
+ end
305
+
306
+ def find_last
307
+ if loaded?
308
+ @records.last
309
+ else
310
+ @last ||= reverse_order.limit(1).to_a[0]
311
+ end
312
+ end
313
+
314
+ end
315
+ end
@@ -0,0 +1,46 @@
1
+ module ActiveRecord
2
+ class PredicateBuilder
3
+
4
+ def initialize(engine)
5
+ @engine = engine
6
+ end
7
+
8
+ def build_from_hash(attributes, default_table)
9
+ predicates = attributes.map do |column, value|
10
+ table = default_table
11
+
12
+ if value.is_a?(Hash)
13
+ table = Arel::Table.new(column, :engine => @engine)
14
+ build_from_hash(value, table)
15
+ else
16
+ column = column.to_s
17
+
18
+ if column.include?('.')
19
+ table_name, column = column.split('.', 2)
20
+ table = Arel::Table.new(table_name, :engine => @engine)
21
+ end
22
+
23
+ attribute = table[column] || Arel::Attribute.new(table, column.to_sym)
24
+
25
+ case value
26
+ when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope
27
+ values = value.to_a
28
+ attribute.in(values)
29
+ when Range
30
+ # TODO : Arel should handle ranges with excluded end.
31
+ if value.exclude_end?
32
+ [attribute.gteq(value.begin), attribute.lt(value.end)]
33
+ else
34
+ attribute.in(value)
35
+ end
36
+ else
37
+ attribute.eq(value)
38
+ end
39
+ end
40
+ end
41
+
42
+ predicates.flatten
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,218 @@
1
+ module ActiveRecord
2
+ module QueryMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method|
7
+ attr_accessor :"#{query_method}_values"
8
+
9
+ class_eval <<-CEVAL
10
+ def #{query_method}(*args)
11
+ new_relation = spawn
12
+ value = Array.wrap(args.flatten).reject {|x| x.blank? }
13
+ new_relation.#{query_method}_values += value if value.present?
14
+ new_relation
15
+ end
16
+ CEVAL
17
+ end
18
+
19
+ [:where, :having].each do |query_method|
20
+ class_eval <<-CEVAL
21
+ def #{query_method}(*args)
22
+ new_relation = spawn
23
+ value = build_where(*args)
24
+ new_relation.#{query_method}_values += [*value] if value.present?
25
+ new_relation
26
+ end
27
+ CEVAL
28
+ end
29
+
30
+ ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
31
+ attr_accessor :"#{query_method}_value"
32
+
33
+ class_eval <<-CEVAL
34
+ def #{query_method}(value = true)
35
+ new_relation = spawn
36
+ new_relation.#{query_method}_value = value
37
+ new_relation
38
+ end
39
+ CEVAL
40
+ end
41
+ end
42
+
43
+ def lock(locks = true)
44
+ relation = spawn
45
+ case locks
46
+ when String, TrueClass, NilClass
47
+ spawn.tap {|new_relation| new_relation.lock_value = locks || true }
48
+ else
49
+ spawn.tap {|new_relation| new_relation.lock_value = false }
50
+ end
51
+ end
52
+
53
+ def reverse_order
54
+ order_clause = arel.send(:order_clauses).join(', ')
55
+ relation = except(:order)
56
+
57
+ if order_clause.present?
58
+ relation.order(reverse_sql_order(order_clause))
59
+ else
60
+ relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC")
61
+ end
62
+ end
63
+
64
+ def arel
65
+ @arel ||= build_arel
66
+ end
67
+
68
+ def build_arel
69
+ arel = table
70
+
71
+ joined_associations = []
72
+ association_joins = []
73
+
74
+ joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
75
+
76
+ # Build association joins first
77
+ joins.each do |join|
78
+ association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
79
+ end
80
+
81
+ if association_joins.any?
82
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins.uniq, nil)
83
+ to_join = []
84
+
85
+ join_dependency.join_associations.each do |association|
86
+ if (association_relation = association.relation).is_a?(Array)
87
+ to_join << [association_relation.first, association.association_join.first]
88
+ to_join << [association_relation.last, association.association_join.last]
89
+ else
90
+ to_join << [association_relation, association.association_join]
91
+ end
92
+ end
93
+
94
+ to_join.each do |tj|
95
+ unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] }
96
+ joined_associations << tj
97
+ arel = arel.join(tj[0]).on(*tj[1])
98
+ end
99
+ end
100
+ end
101
+
102
+ joins.each do |join|
103
+ next if join.blank?
104
+
105
+ @implicit_readonly = true
106
+
107
+ case join
108
+ when Relation::JoinOperation
109
+ arel = arel.join(join.relation, join.join_class).on(*join.on)
110
+ when Hash, Array, Symbol
111
+ if array_of_strings?(join)
112
+ join_string = join.join(' ')
113
+ arel = arel.join(join_string)
114
+ end
115
+ else
116
+ arel = arel.join(join)
117
+ end
118
+ end
119
+
120
+ @where_values.uniq.each do |where|
121
+ next if where.blank?
122
+
123
+ case where
124
+ when Arel::SqlLiteral
125
+ arel = arel.where(where)
126
+ else
127
+ sql = where.is_a?(String) ? where : where.to_sql
128
+ arel = arel.where(Arel::SqlLiteral.new("(#{sql})"))
129
+ end
130
+ end
131
+
132
+ @having_values.uniq.each do |h|
133
+ arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
134
+ end
135
+
136
+ if defined?(@limit_value) && @limit_value.present?
137
+ arel = arel.take(@limit_value)
138
+ end
139
+
140
+ if defined?(@offset_value) && @offset_value.present?
141
+ arel = arel.skip(@offset_value)
142
+ end
143
+
144
+ @group_values.uniq.each do |g|
145
+ arel = arel.group(g) if g.present?
146
+ end
147
+
148
+ @order_values.uniq.each do |o|
149
+ arel = arel.order(Arel::SqlLiteral.new(o.to_s)) if o.present?
150
+ end
151
+
152
+ selects = @select_values.uniq
153
+
154
+ quoted_table_name = @klass.quoted_table_name
155
+
156
+ if selects.present?
157
+ selects.each do |s|
158
+ @implicit_readonly = false
159
+ arel = arel.project(s) if s.present?
160
+ end
161
+ else
162
+ arel = arel.project(quoted_table_name + '.*')
163
+ end
164
+
165
+ arel =
166
+ if defined?(@from_value) && @from_value.present?
167
+ arel.from(@from_value)
168
+ else
169
+ arel.from(quoted_table_name)
170
+ end
171
+
172
+ case @lock_value
173
+ when TrueClass
174
+ arel = arel.lock
175
+ when String
176
+ arel = arel.lock(@lock_value)
177
+ end
178
+
179
+ arel
180
+ end
181
+
182
+ def build_where(*args)
183
+ return if args.blank?
184
+
185
+ builder = PredicateBuilder.new(table.engine)
186
+
187
+ conditions = if [String, Array].include?(args.first.class)
188
+ @klass.send(:sanitize_sql, args.size > 1 ? args : args.first)
189
+ elsif args.first.is_a?(Hash)
190
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first)
191
+ builder.build_from_hash(attributes, table)
192
+ else
193
+ args.first
194
+ end
195
+
196
+ conditions
197
+ end
198
+
199
+ private
200
+
201
+ def reverse_sql_order(order_query)
202
+ order_query.to_s.split(/,/).each { |s|
203
+ if s.match(/\s(asc|ASC)$/)
204
+ s.gsub!(/\s(asc|ASC)$/, ' DESC')
205
+ elsif s.match(/\s(desc|DESC)$/)
206
+ s.gsub!(/\s(desc|DESC)$/, ' ASC')
207
+ else
208
+ s.concat(' DESC')
209
+ end
210
+ }.join(',')
211
+ end
212
+
213
+ def array_of_strings?(o)
214
+ o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
215
+ end
216
+
217
+ end
218
+ end