activerecord 3.2.19 → 5.0.0

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 (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,85 +1,26 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'active_support/core_ext/hash/indifferent_access'
1
+ require 'active_support/core_ext/string/filters'
3
2
 
4
3
  module ActiveRecord
5
4
  module FinderMethods
6
- # Find operates with four different retrieval approaches:
7
- #
8
- # * 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]).
9
- # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
10
- # * Find first - This will return the first 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(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
13
- # * Find last - This will return the last record matched by the options used. These options can either be specific
14
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
15
- # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
16
- # * Find all - This will return all the records matched by the options used.
17
- # If no records are found, an empty array is returned. Use
18
- # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
19
- #
20
- # All approaches accept an options hash as their last parameter.
21
- #
22
- # ==== Options
23
- #
24
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>["user_name = ?", username]</tt>,
25
- # or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
26
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
27
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
28
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a
29
- # <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
30
- # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
31
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
32
- # it would skip rows 0 through 4.
33
- # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
34
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an
35
- # <tt>INNER JOIN</tt> on the associated table(s),
36
- # or an array containing a mixture of both strings and named associations.
37
- # If the value is a string, then the records will be returned read-only since they will
38
- # have attributes that do not correspond to the table's columns.
39
- # Pass <tt>:readonly => false</tt> to override.
40
- # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
41
- # to already defined associations. See eager loading under Associations.
42
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you,
43
- # for example, want to do a join but not include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
44
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed
45
- # to an alternate table name (or even the name of a database view).
46
- # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
47
- # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
48
- # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
49
- #
50
- # ==== Examples
51
- #
52
- # # find by id
53
- # Person.find(1) # returns the object for ID = 1
54
- # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
55
- # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
56
- # Person.find([1]) # returns an array for the object with ID = 1
57
- # Person.where("administrator = 1").order("created_on DESC").find(1)
58
- #
59
- # Note that returned records may not be in the same order as the ids you
60
- # provide since database rows are unordered. Give an explicit <tt>:order</tt>
61
- # to ensure the results are sorted.
62
- #
63
- # ==== Examples
5
+ ONE_AS_ONE = '1 AS one'
6
+
7
+ # 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]).
8
+ # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key
9
+ # is an integer, find by id coerces its arguments using +to_i+.
64
10
  #
65
- # # find first
66
- # Person.first # returns the first object fetched by SELECT * FROM people
67
- # Person.where(["user_name = ?", user_name]).first
68
- # Person.where(["user_name = :u", { :u => user_name }]).first
69
- # Person.order("created_on DESC").offset(5).first
11
+ # Person.find(1) # returns the object for ID = 1
12
+ # Person.find("1") # returns the object for ID = 1
13
+ # Person.find("31-sarah") # returns the object for ID = 31
14
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
15
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
16
+ # Person.find([1]) # returns an array for the object with ID = 1
17
+ # Person.where("administrator = 1").order("created_on DESC").find(1)
70
18
  #
71
- # # find last
72
- # Person.last # returns the last object fetched by SELECT * FROM people
73
- # Person.where(["user_name = ?", user_name]).last
74
- # Person.order("created_on DESC").offset(5).last
19
+ # NOTE: The returned records may not be in the same order as the ids you
20
+ # provide since database rows are unordered. You'd need to provide an explicit QueryMethods#order
21
+ # option if you want the results are sorted.
75
22
  #
76
- # # find all
77
- # Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
78
- # Person.where(["category IN (?)", categories]).limit(50).all
79
- # Person.where({ :friends => ["Bob", "Steve", "Fred"] }).all
80
- # Person.offset(10).limit(10).all
81
- # Person.includes([:account, :friends]).all
82
- # Person.group("category").all
23
+ # ==== Find with lock
83
24
  #
84
25
  # Example for find with a lock: Imagine two concurrent transactions:
85
26
  # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
@@ -92,216 +33,416 @@ module ActiveRecord
92
33
  # person.visits += 1
93
34
  # person.save!
94
35
  # end
36
+ #
37
+ # ==== Variations of #find
38
+ #
39
+ # Person.where(name: 'Spartacus', rating: 4)
40
+ # # returns a chainable list (which can be empty).
41
+ #
42
+ # Person.find_by(name: 'Spartacus', rating: 4)
43
+ # # returns the first item or nil.
44
+ #
45
+ # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
46
+ # # returns the first item or returns a new instance (requires you call .save to persist against the database).
47
+ #
48
+ # Person.find_or_create_by(name: 'Spartacus', rating: 4)
49
+ # # returns the first item or creates it and returns it.
50
+ #
51
+ # ==== Alternatives for #find
52
+ #
53
+ # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
54
+ # # returns a boolean indicating if any record with the given conditions exist.
55
+ #
56
+ # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3")
57
+ # # returns a chainable list of instances with only the mentioned fields.
58
+ #
59
+ # Person.where(name: 'Spartacus', rating: 4).ids
60
+ # # returns an Array of ids.
61
+ #
62
+ # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
63
+ # # returns an Array of the required fields.
95
64
  def find(*args)
96
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
65
+ return super if block_given?
66
+ find_with_ids(*args)
67
+ end
97
68
 
98
- options = args.extract_options!
69
+ # Finds the first record matching the specified conditions. There
70
+ # is no implied ordering so if order matters, you should specify it
71
+ # yourself.
72
+ #
73
+ # If no record is found, returns <tt>nil</tt>.
74
+ #
75
+ # Post.find_by name: 'Spartacus', rating: 4
76
+ # Post.find_by "published_at < ?", 2.weeks.ago
77
+ def find_by(arg, *args)
78
+ where(arg, *args).take
79
+ rescue RangeError
80
+ nil
81
+ end
99
82
 
100
- if options.present?
101
- apply_finder_options(options).find(*args)
102
- else
103
- case args.first
104
- when :first, :last, :all
105
- send(args.first)
106
- else
107
- find_with_ids(*args)
108
- end
109
- end
83
+ # Like #find_by, except that if no record is found, raises
84
+ # an ActiveRecord::RecordNotFound error.
85
+ def find_by!(arg, *args)
86
+ where(arg, *args).take!
87
+ rescue RangeError
88
+ raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
89
+ @klass.name)
110
90
  end
111
91
 
112
- # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
113
- # same arguments to this method as you can to <tt>find(:first)</tt>.
114
- def first(*args)
115
- if args.any?
116
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
117
- limit(*args).to_a
118
- else
119
- apply_finder_options(args.first).first
120
- end
92
+ # Gives a record (or N records if a parameter is supplied) without any implied
93
+ # order. The order will depend on the database implementation.
94
+ # If an order is supplied it will be respected.
95
+ #
96
+ # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
97
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
98
+ # Person.where(["name LIKE '%?'", name]).take
99
+ def take(limit = nil)
100
+ limit ? limit(limit).to_a : find_take
101
+ end
102
+
103
+ # Same as #take but raises ActiveRecord::RecordNotFound if no record
104
+ # is found. Note that #take! accepts no arguments.
105
+ def take!
106
+ take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
107
+ end
108
+
109
+ # Find the first record (or first N records if a parameter is supplied).
110
+ # If no order is defined it will order by primary key.
111
+ #
112
+ # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
113
+ # Person.where(["user_name = ?", user_name]).first
114
+ # Person.where(["user_name = :u", { u: user_name }]).first
115
+ # Person.order("created_on DESC").offset(5).first
116
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
117
+ #
118
+ def first(limit = nil)
119
+ if limit
120
+ find_nth_with_limit_and_offset(0, limit, offset: offset_index)
121
121
  else
122
- find_first
122
+ find_nth 0
123
123
  end
124
124
  end
125
125
 
126
- # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
127
- # is found. Note that <tt>first!</tt> accepts no arguments.
126
+ # Same as #first but raises ActiveRecord::RecordNotFound if no record
127
+ # is found. Note that #first! accepts no arguments.
128
128
  def first!
129
- first or raise RecordNotFound
130
- end
131
-
132
- # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
133
- # same arguments to this method as you can to <tt>find(:last)</tt>.
134
- def last(*args)
135
- if args.any?
136
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
137
- if order_values.empty? && primary_key
138
- order("#{quoted_table_name}.#{quoted_primary_key} DESC").limit(*args).reverse
139
- else
140
- to_a.last(*args)
141
- end
142
- else
143
- apply_finder_options(args.first).last
144
- end
145
- else
146
- find_last
147
- end
129
+ find_nth! 0
148
130
  end
149
131
 
150
- # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
151
- # is found. Note that <tt>last!</tt> accepts no arguments.
132
+ # Find the last record (or last N records if a parameter is supplied).
133
+ # If no order is defined it will order by primary key.
134
+ #
135
+ # Person.last # returns the last object fetched by SELECT * FROM people
136
+ # Person.where(["user_name = ?", user_name]).last
137
+ # Person.order("created_on DESC").offset(5).last
138
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
139
+ #
140
+ # Take note that in that last case, the results are sorted in ascending order:
141
+ #
142
+ # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
143
+ #
144
+ # and not:
145
+ #
146
+ # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
147
+ def last(limit = nil)
148
+ return find_last(limit) if loaded? || limit_value
149
+
150
+ result = limit(limit || 1)
151
+ result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
152
+ result = result.reverse_order!
153
+
154
+ limit ? result.reverse : result.first
155
+ rescue ActiveRecord::IrreversibleOrderError
156
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
157
+ Finding a last element by loading the relation when SQL ORDER
158
+ can not be reversed is deprecated.
159
+ Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
160
+ Please call `to_a.last` if you still want to load the relation.
161
+ WARNING
162
+ find_last(limit)
163
+ end
164
+
165
+ # Same as #last but raises ActiveRecord::RecordNotFound if no record
166
+ # is found. Note that #last! accepts no arguments.
152
167
  def last!
153
- last or raise RecordNotFound
168
+ last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
169
+ end
170
+
171
+ # Find the second record.
172
+ # If no order is defined it will order by primary key.
173
+ #
174
+ # Person.second # returns the second object fetched by SELECT * FROM people
175
+ # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
176
+ # Person.where(["user_name = :u", { u: user_name }]).second
177
+ def second
178
+ find_nth 1
179
+ end
180
+
181
+ # Same as #second but raises ActiveRecord::RecordNotFound if no record
182
+ # is found.
183
+ def second!
184
+ find_nth! 1
185
+ end
186
+
187
+ # Find the third record.
188
+ # If no order is defined it will order by primary key.
189
+ #
190
+ # Person.third # returns the third object fetched by SELECT * FROM people
191
+ # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
192
+ # Person.where(["user_name = :u", { u: user_name }]).third
193
+ def third
194
+ find_nth 2
195
+ end
196
+
197
+ # Same as #third but raises ActiveRecord::RecordNotFound if no record
198
+ # is found.
199
+ def third!
200
+ find_nth! 2
201
+ end
202
+
203
+ # Find the fourth record.
204
+ # If no order is defined it will order by primary key.
205
+ #
206
+ # Person.fourth # returns the fourth object fetched by SELECT * FROM people
207
+ # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
208
+ # Person.where(["user_name = :u", { u: user_name }]).fourth
209
+ def fourth
210
+ find_nth 3
211
+ end
212
+
213
+ # Same as #fourth but raises ActiveRecord::RecordNotFound if no record
214
+ # is found.
215
+ def fourth!
216
+ find_nth! 3
217
+ end
218
+
219
+ # Find the fifth record.
220
+ # If no order is defined it will order by primary key.
221
+ #
222
+ # Person.fifth # returns the fifth object fetched by SELECT * FROM people
223
+ # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
224
+ # Person.where(["user_name = :u", { u: user_name }]).fifth
225
+ def fifth
226
+ find_nth 4
227
+ end
228
+
229
+ # Same as #fifth but raises ActiveRecord::RecordNotFound if no record
230
+ # is found.
231
+ def fifth!
232
+ find_nth! 4
233
+ end
234
+
235
+ # Find the forty-second record. Also known as accessing "the reddit".
236
+ # If no order is defined it will order by primary key.
237
+ #
238
+ # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
239
+ # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
240
+ # Person.where(["user_name = :u", { u: user_name }]).forty_two
241
+ def forty_two
242
+ find_nth 41
154
243
  end
155
244
 
156
- # A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
157
- # same arguments to this method as you can to <tt>find(:all)</tt>.
158
- def all(*args)
159
- args.any? ? apply_finder_options(args.first).to_a : to_a
245
+ # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
246
+ # is found.
247
+ def forty_two!
248
+ find_nth! 41
249
+ end
250
+
251
+ # Find the third-to-last record.
252
+ # If no order is defined it will order by primary key.
253
+ #
254
+ # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
255
+ # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
256
+ # Person.where(["user_name = :u", { u: user_name }]).third_to_last
257
+ def third_to_last
258
+ find_nth_from_last 3
259
+ end
260
+
261
+ # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
262
+ # is found.
263
+ def third_to_last!
264
+ find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
265
+ end
266
+
267
+ # Find the second-to-last record.
268
+ # If no order is defined it will order by primary key.
269
+ #
270
+ # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
271
+ # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
272
+ # Person.where(["user_name = :u", { u: user_name }]).second_to_last
273
+ def second_to_last
274
+ find_nth_from_last 2
275
+ end
276
+
277
+ # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
278
+ # is found.
279
+ def second_to_last!
280
+ find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
160
281
  end
161
282
 
162
283
  # Returns true if a record exists in the table that matches the +id+ or
163
- # conditions given, or false otherwise. The argument can take five forms:
284
+ # conditions given, or false otherwise. The argument can take six forms:
164
285
  #
165
286
  # * Integer - Finds the record with this primary key.
166
287
  # * String - Finds the record with a primary key corresponding to this
167
288
  # string (such as <tt>'5'</tt>).
168
289
  # * Array - Finds the record that matches these +find+-style conditions
169
- # (such as <tt>['color = ?', 'red']</tt>).
290
+ # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
170
291
  # * Hash - Finds the record that matches these +find+-style conditions
171
- # (such as <tt>{:color => 'red'}</tt>).
172
- # * No args - Returns false if the table is empty, true otherwise.
292
+ # (such as <tt>{name: 'David'}</tt>).
293
+ # * +false+ - Returns always +false+.
294
+ # * No args - Returns +false+ if the table is empty, +true+ otherwise.
173
295
  #
174
- # For more information about specifying conditions as a Hash or Array,
296
+ # For more information about specifying conditions as a hash or array,
175
297
  # see the Conditions section in the introduction to ActiveRecord::Base.
176
298
  #
177
299
  # Note: You can't pass in a condition as a string (like <tt>name =
178
300
  # 'Jamie'</tt>), since it would be sanitized and then queried against
179
301
  # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
180
302
  #
181
- # ==== Examples
182
303
  # Person.exists?(5)
183
304
  # Person.exists?('5')
184
- # Person.exists?(:name => "David")
185
305
  # Person.exists?(['name LIKE ?', "%#{query}%"])
306
+ # Person.exists?(id: [1, 4, 8])
307
+ # Person.exists?(name: 'David')
308
+ # Person.exists?(false)
186
309
  # Person.exists?
187
- def exists?(id = false)
188
- id = id.id if ActiveRecord::Base === id
189
- return false if id.nil?
310
+ def exists?(conditions = :none)
311
+ if Base === conditions
312
+ conditions = conditions.id
313
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
314
+ You are passing an instance of ActiveRecord::Base to `exists?`.
315
+ Please pass the id of the object by calling `.id`.
316
+ MSG
317
+ end
318
+
319
+ return false if !conditions
320
+
321
+ relation = apply_join_dependency(self, construct_join_dependency)
322
+ return false if ActiveRecord::NullRelation === relation
190
323
 
191
- join_dependency = construct_join_dependency_for_association_find
192
- relation = construct_relation_for_association_find(join_dependency)
193
- relation = relation.except(:select, :order).select("1 AS one").limit(1)
324
+ relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
194
325
 
195
- case id
326
+ case conditions
196
327
  when Array, Hash
197
- relation = relation.where(id)
328
+ relation = relation.where(conditions)
198
329
  else
199
- relation = relation.where(table[primary_key].eq(id)) if id
330
+ unless conditions == :none
331
+ relation = relation.where(primary_key => conditions)
332
+ end
200
333
  end
201
334
 
202
- connection.select_value(relation, "#{name} Exists") ? true : false
203
- rescue ThrowResult
204
- false
335
+ connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
205
336
  end
206
337
 
207
- protected
208
-
209
- def find_with_associations
210
- join_dependency = construct_join_dependency_for_association_find
211
- relation = construct_relation_for_association_find(join_dependency)
212
- rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
213
- join_dependency.instantiate(rows)
214
- rescue ThrowResult
215
- []
216
- end
338
+ # This method is called whenever no records are found with either a single
339
+ # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
340
+ #
341
+ # The error message is different depending on whether a single id or
342
+ # multiple ids are provided. If multiple ids are provided, then the number
343
+ # of results obtained should be provided in the +result_size+ argument and
344
+ # the expected number of results should be provided in the +expected_size+
345
+ # argument.
346
+ def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
347
+ conditions = arel.where_sql(@klass.arel_engine)
348
+ conditions = " [#{conditions}]" if conditions
349
+
350
+ if Array(ids).size == 1
351
+ error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}"
352
+ else
353
+ error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': "
354
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
355
+ end
217
356
 
218
- def construct_join_dependency_for_association_find
219
- including = (@eager_load_values + @includes_values).uniq
220
- ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
357
+ raise RecordNotFound, error
221
358
  end
222
359
 
223
- def construct_relation_for_association_calculations
224
- including = (@eager_load_values + @includes_values).uniq
225
- join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
226
- relation = except(:includes, :eager_load, :preload)
227
- apply_join_dependency(relation, join_dependency)
228
- end
360
+ private
229
361
 
230
- def construct_relation_for_association_find(join_dependency)
231
- relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
232
- apply_join_dependency(relation, join_dependency)
362
+ def offset_index
363
+ offset_value || 0
233
364
  end
234
365
 
235
- def apply_join_dependency(relation, join_dependency)
236
- join_dependency.join_associations.each do |association|
237
- relation = association.join_relation(relation)
238
- end
239
-
240
- limitable_reflections = using_limitable_reflections?(join_dependency.reflections)
241
-
242
- if !limitable_reflections && relation.limit_value
243
- limited_id_condition = construct_limited_ids_condition(relation.except(:select))
244
- relation = relation.where(limited_id_condition)
366
+ def find_with_associations
367
+ # NOTE: the JoinDependency constructed here needs to know about
368
+ # any joins already present in `self`, so pass them in
369
+ #
370
+ # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
371
+ # incorrect SQL is generated. In that case, the join dependency for
372
+ # SpecialCategorizations is constructed without knowledge of the
373
+ # preexisting join in joins_values to categorizations (by way of
374
+ # the `has_many :through` for categories).
375
+ #
376
+ join_dependency = construct_join_dependency(joins_values)
377
+
378
+ aliases = join_dependency.aliases
379
+ relation = select aliases.columns
380
+ relation = apply_join_dependency(relation, join_dependency)
381
+
382
+ if block_given?
383
+ yield relation
384
+ else
385
+ if ActiveRecord::NullRelation === relation
386
+ []
387
+ else
388
+ arel = relation.arel
389
+ rows = connection.select_all(arel, 'SQL', relation.bound_attributes)
390
+ join_dependency.instantiate(rows, aliases)
391
+ end
245
392
  end
246
-
247
- relation = relation.except(:limit, :offset) unless limitable_reflections
248
-
249
- relation
250
393
  end
251
394
 
252
- def construct_limited_ids_condition(relation)
253
- orders = relation.order_values.map { |val| val.presence }.compact
254
- values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
255
-
256
- relation = relation.dup.select(values)
257
- relation.uniq_value = nil
258
-
259
- id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
260
- ids_array = id_rows.map {|row| row[primary_key]}
261
-
262
- ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
395
+ def construct_join_dependency(joins = [])
396
+ including = eager_load_values + includes_values
397
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
263
398
  end
264
399
 
265
- def find_by_attributes(match, attributes, *args)
266
- conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
267
- result = where(conditions).send(match.finder)
268
-
269
- if match.bang? && result.nil?
270
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
400
+ def construct_relation_for_association_calculations
401
+ from = arel.froms.first
402
+ if Arel::Table === from
403
+ apply_join_dependency(self, construct_join_dependency(joins_values))
271
404
  else
272
- yield(result) if block_given?
273
- result
405
+ # FIXME: as far as I can tell, `from` will always be an Arel::Table.
406
+ # There are no tests that test this branch, but presumably it's
407
+ # possible for `from` to be a list?
408
+ apply_join_dependency(self, construct_join_dependency(from))
274
409
  end
275
410
  end
276
411
 
277
- def find_or_instantiator_by_attributes(match, attributes, *args)
278
- options = args.size > 1 && args.last(2).all?{ |a| a.is_a?(Hash) } ? args.extract_options! : {}
279
- protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
280
- args.each_with_index do |arg, i|
281
- if arg.is_a?(Hash)
282
- protected_attributes_for_create = args[i].with_indifferent_access
283
- else
284
- unprotected_attributes_for_create[attributes[i]] = args[i]
412
+ def apply_join_dependency(relation, join_dependency)
413
+ relation = relation.except(:includes, :eager_load, :preload)
414
+ relation = relation.joins join_dependency
415
+
416
+ if using_limitable_reflections?(join_dependency.reflections)
417
+ relation
418
+ else
419
+ if relation.limit_value
420
+ limited_ids = limited_ids_for(relation)
421
+ limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
285
422
  end
423
+ relation.except(:limit, :offset)
286
424
  end
425
+ end
287
426
 
288
- conditions = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes).symbolize_keys
427
+ def limited_ids_for(relation)
428
+ values = @klass.connection.columns_for_distinct(
429
+ "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
289
430
 
290
- record = where(conditions).first
431
+ relation = relation.except(:select).select(values).distinct!
432
+ arel = relation.arel
291
433
 
292
- unless record
293
- record = @klass.new(protected_attributes_for_create, options) do |r|
294
- r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
295
- end
296
- yield(record) if block_given?
297
- record.send(match.save_method) if match.save_record?
298
- end
434
+ id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
435
+ id_rows.map {|row| row[primary_key]}
436
+ end
299
437
 
300
- record
438
+ def using_limitable_reflections?(reflections)
439
+ reflections.none?(&:collection?)
301
440
  end
302
441
 
442
+ protected
443
+
303
444
  def find_with_ids(*ids)
304
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
445
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
305
446
 
306
447
  expects_array = ids.first.kind_of?(Array)
307
448
  return ids.first if expects_array && ids.first.empty?
@@ -317,86 +458,141 @@ module ActiveRecord
317
458
  else
318
459
  find_some(ids)
319
460
  end
461
+ rescue RangeError
462
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
320
463
  end
321
464
 
322
465
  def find_one(id)
323
- id = id.id if ActiveRecord::Base === id
324
-
325
- if IdentityMap.enabled? && where_values.blank? &&
326
- limit_value.blank? && order_values.blank? &&
327
- includes_values.blank? && preload_values.blank? &&
328
- readonly_value.nil? && joins_values.blank? &&
329
- !@klass.locking_enabled? &&
330
- record = IdentityMap.get(@klass, id)
331
- return record
466
+ if ActiveRecord::Base === id
467
+ id = id.id
468
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
469
+ You are passing an instance of ActiveRecord::Base to `find`.
470
+ Please pass the id of the object by calling `.id`.
471
+ MSG
332
472
  end
333
473
 
334
- column = columns_hash[primary_key]
474
+ relation = where(primary_key => id)
475
+ record = relation.take
335
476
 
336
- substitute = connection.substitute_at(column, @bind_values.length)
337
- relation = where(table[primary_key].eq(substitute))
338
- relation.bind_values = [[column, id]]
339
- record = relation.first
340
-
341
- unless record
342
- conditions = arel.where_sql
343
- conditions = " [#{conditions}]" if conditions
344
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
345
- end
477
+ raise_record_not_found_exception!(id, 0, 1) unless record
346
478
 
347
479
  record
348
480
  end
349
481
 
350
482
  def find_some(ids)
351
- result = where(table[primary_key].in(ids)).all
483
+ return find_some_ordered(ids) unless order_values.present?
484
+
485
+ result = where(primary_key => ids).to_a
352
486
 
353
487
  expected_size =
354
- if @limit_value && ids.size > @limit_value
355
- @limit_value
488
+ if limit_value && ids.size > limit_value
489
+ limit_value
356
490
  else
357
491
  ids.size
358
492
  end
359
493
 
360
494
  # 11 ids with limit 3, offset 9 should give 2 results.
361
- if @offset_value && (ids.size - @offset_value < expected_size)
362
- expected_size = ids.size - @offset_value
495
+ if offset_value && (ids.size - offset_value < expected_size)
496
+ expected_size = ids.size - offset_value
363
497
  end
364
498
 
365
499
  if result.size == expected_size
366
500
  result
367
501
  else
368
- conditions = arel.where_sql
369
- conditions = " [#{conditions}]" if conditions
502
+ raise_record_not_found_exception!(ids, result.size, expected_size)
503
+ end
504
+ end
505
+
506
+ def find_some_ordered(ids)
507
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
370
508
 
371
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
372
- error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
373
- raise RecordNotFound, error
509
+ result = except(:limit, :offset).where(primary_key => ids).records
510
+
511
+ if result.size == ids.size
512
+ pk_type = @klass.type_for_attribute(primary_key)
513
+
514
+ records_by_id = result.index_by(&:id)
515
+ ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
516
+ else
517
+ raise_record_not_found_exception!(ids, result.size, ids.size)
374
518
  end
375
519
  end
376
520
 
377
- def find_first
521
+ def find_take
378
522
  if loaded?
379
523
  @records.first
380
524
  else
381
- @first ||= limit(1).to_a[0]
525
+ @take ||= limit(1).records.first
382
526
  end
383
527
  end
384
528
 
385
- def find_last
529
+ def find_nth(index, offset = nil)
530
+ # TODO: once the offset argument is removed we rely on offset_index
531
+ # within find_nth_with_limit, rather than pass it in via
532
+ # find_nth_with_limit_and_offset
533
+ if offset
534
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
535
+ Passing an offset argument to find_nth is deprecated,
536
+ please use Relation#offset instead.
537
+ MSG
538
+ end
386
539
  if loaded?
387
- @records.last
540
+ @records[index]
388
541
  else
389
- @last ||=
390
- if offset_value || limit_value
391
- to_a.last
392
- else
393
- reverse_order.limit(1).to_a[0]
394
- end
542
+ offset ||= offset_index
543
+ @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
395
544
  end
396
545
  end
397
546
 
398
- def using_limitable_reflections?(reflections)
399
- reflections.none? { |r| r.collection? }
547
+ def find_nth!(index)
548
+ find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
549
+ end
550
+
551
+ def find_nth_with_limit(index, limit)
552
+ # TODO: once the offset argument is removed from find_nth,
553
+ # find_nth_with_limit_and_offset can be merged into this method
554
+ relation = if order_values.empty? && primary_key
555
+ order(arel_attribute(primary_key).asc)
556
+ else
557
+ self
558
+ end
559
+
560
+ relation = relation.offset(index) unless index.zero?
561
+ relation.limit(limit).to_a
562
+ end
563
+
564
+ def find_nth_from_last(index)
565
+ if loaded?
566
+ @records[-index]
567
+ else
568
+ relation = if order_values.empty? && primary_key
569
+ order(arel_attribute(primary_key).asc)
570
+ else
571
+ self
572
+ end
573
+
574
+ relation.to_a[-index]
575
+ # TODO: can be made more performant on large result sets by
576
+ # for instance, last(index)[-index] (which would require
577
+ # refactoring the last(n) finder method to make test suite pass),
578
+ # or by using a combination of reverse_order, limit, and offset,
579
+ # e.g., reverse_order.offset(index-1).first
580
+ end
581
+ end
582
+
583
+ private
584
+
585
+ def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
586
+ if loaded?
587
+ @records[index, limit]
588
+ else
589
+ index += offset
590
+ find_nth_with_limit(index, limit)
591
+ end
592
+ end
593
+
594
+ def find_last(limit)
595
+ limit ? records.last(limit) : records.last
400
596
  end
401
597
  end
402
598
  end