activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,85 +1,29 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'active_support/core_ext/hash/indifferent_access'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/filters"
3
4
 
4
5
  module ActiveRecord
5
6
  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
7
+ ONE_AS_ONE = "1 AS one"
8
+
9
+ # 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]).
10
+ # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key
11
+ # is an integer, find by id coerces its arguments using +to_i+.
64
12
  #
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
13
+ # Person.find(1) # returns the object for ID = 1
14
+ # Person.find("1") # returns the object for ID = 1
15
+ # Person.find("31-sarah") # returns the object for ID = 31
16
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
17
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
18
+ # Person.find([1]) # returns an array for the object with ID = 1
19
+ # Person.where("administrator = 1").order("created_on DESC").find(1)
70
20
  #
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
21
+ # NOTE: The returned records are in the same order as the ids you provide.
22
+ # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
23
+ # method and provide an explicit ActiveRecord::QueryMethods#order option.
24
+ # But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound.
75
25
  #
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
26
+ # ==== Find with lock
83
27
  #
84
28
  # Example for find with a lock: Imagine two concurrent transactions:
85
29
  # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
@@ -92,311 +36,530 @@ module ActiveRecord
92
36
  # person.visits += 1
93
37
  # person.save!
94
38
  # end
39
+ #
40
+ # ==== Variations of #find
41
+ #
42
+ # Person.where(name: 'Spartacus', rating: 4)
43
+ # # returns a chainable list (which can be empty).
44
+ #
45
+ # Person.find_by(name: 'Spartacus', rating: 4)
46
+ # # returns the first item or nil.
47
+ #
48
+ # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
49
+ # # returns the first item or returns a new instance (requires you call .save to persist against the database).
50
+ #
51
+ # Person.find_or_create_by(name: 'Spartacus', rating: 4)
52
+ # # returns the first item or creates it and returns it.
53
+ #
54
+ # ==== Alternatives for #find
55
+ #
56
+ # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
57
+ # # returns a boolean indicating if any record with the given conditions exist.
58
+ #
59
+ # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3")
60
+ # # returns a chainable list of instances with only the mentioned fields.
61
+ #
62
+ # Person.where(name: 'Spartacus', rating: 4).ids
63
+ # # returns an Array of ids.
64
+ #
65
+ # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
66
+ # # returns an Array of the required fields.
95
67
  def find(*args)
96
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
68
+ return super if block_given?
69
+ find_with_ids(*args)
70
+ end
97
71
 
98
- options = args.extract_options!
72
+ # Finds the first record matching the specified conditions. There
73
+ # is no implied ordering so if order matters, you should specify it
74
+ # yourself.
75
+ #
76
+ # If no record is found, returns <tt>nil</tt>.
77
+ #
78
+ # Post.find_by name: 'Spartacus', rating: 4
79
+ # Post.find_by "published_at < ?", 2.weeks.ago
80
+ def find_by(arg, *args)
81
+ where(arg, *args).take
82
+ rescue ::RangeError
83
+ nil
84
+ end
99
85
 
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
86
+ # Like #find_by, except that if no record is found, raises
87
+ # an ActiveRecord::RecordNotFound error.
88
+ def find_by!(arg, *args)
89
+ where(arg, *args).take!
90
+ rescue ::RangeError
91
+ raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
92
+ @klass.name, @klass.primary_key)
110
93
  end
111
94
 
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
95
+ # Gives a record (or N records if a parameter is supplied) without any implied
96
+ # order. The order will depend on the database implementation.
97
+ # If an order is supplied it will be respected.
98
+ #
99
+ # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1
100
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
101
+ # Person.where(["name LIKE '%?'", name]).take
102
+ def take(limit = nil)
103
+ limit ? find_take_with_limit(limit) : find_take
104
+ end
105
+
106
+ # Same as #take but raises ActiveRecord::RecordNotFound if no record
107
+ # is found. Note that #take! accepts no arguments.
108
+ def take!
109
+ take || raise_record_not_found_exception!
110
+ end
111
+
112
+ # Find the first record (or first N records if a parameter is supplied).
113
+ # If no order is defined it will order by primary key.
114
+ #
115
+ # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1
116
+ # Person.where(["user_name = ?", user_name]).first
117
+ # Person.where(["user_name = :u", { u: user_name }]).first
118
+ # Person.order("created_on DESC").offset(5).first
119
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
120
+ #
121
+ def first(limit = nil)
122
+ if limit
123
+ find_nth_with_limit(0, limit)
121
124
  else
122
- find_first
125
+ find_nth 0
123
126
  end
124
127
  end
125
128
 
126
- # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
127
- # is found. Note that <tt>first!</tt> accepts no arguments.
129
+ # Same as #first but raises ActiveRecord::RecordNotFound if no record
130
+ # is found. Note that #first! accepts no arguments.
128
131
  def first!
129
- first or raise RecordNotFound
132
+ first || raise_record_not_found_exception!
130
133
  end
131
134
 
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
135
+ # Find the last record (or last N records if a parameter is supplied).
136
+ # If no order is defined it will order by primary key.
137
+ #
138
+ # Person.last # returns the last object fetched by SELECT * FROM people
139
+ # Person.where(["user_name = ?", user_name]).last
140
+ # Person.order("created_on DESC").offset(5).last
141
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
142
+ #
143
+ # Take note that in that last case, the results are sorted in ascending order:
144
+ #
145
+ # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
146
+ #
147
+ # and not:
148
+ #
149
+ # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
150
+ def last(limit = nil)
151
+ return find_last(limit) if loaded? || has_limit_or_offset?
152
+
153
+ result = ordered_relation.limit(limit)
154
+ result = result.reverse_order!
155
+
156
+ limit ? result.reverse : result.first
148
157
  end
149
158
 
150
- # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
151
- # is found. Note that <tt>last!</tt> accepts no arguments.
159
+ # Same as #last but raises ActiveRecord::RecordNotFound if no record
160
+ # is found. Note that #last! accepts no arguments.
152
161
  def last!
153
- last or raise RecordNotFound
162
+ last || raise_record_not_found_exception!
163
+ end
164
+
165
+ # Find the second record.
166
+ # If no order is defined it will order by primary key.
167
+ #
168
+ # Person.second # returns the second object fetched by SELECT * FROM people
169
+ # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
170
+ # Person.where(["user_name = :u", { u: user_name }]).second
171
+ def second
172
+ find_nth 1
173
+ end
174
+
175
+ # Same as #second but raises ActiveRecord::RecordNotFound if no record
176
+ # is found.
177
+ def second!
178
+ second || raise_record_not_found_exception!
154
179
  end
155
180
 
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
181
+ # Find the third record.
182
+ # If no order is defined it will order by primary key.
183
+ #
184
+ # Person.third # returns the third object fetched by SELECT * FROM people
185
+ # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
186
+ # Person.where(["user_name = :u", { u: user_name }]).third
187
+ def third
188
+ find_nth 2
189
+ end
190
+
191
+ # Same as #third but raises ActiveRecord::RecordNotFound if no record
192
+ # is found.
193
+ def third!
194
+ third || raise_record_not_found_exception!
195
+ end
196
+
197
+ # Find the fourth record.
198
+ # If no order is defined it will order by primary key.
199
+ #
200
+ # Person.fourth # returns the fourth object fetched by SELECT * FROM people
201
+ # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
202
+ # Person.where(["user_name = :u", { u: user_name }]).fourth
203
+ def fourth
204
+ find_nth 3
205
+ end
206
+
207
+ # Same as #fourth but raises ActiveRecord::RecordNotFound if no record
208
+ # is found.
209
+ def fourth!
210
+ fourth || raise_record_not_found_exception!
211
+ end
212
+
213
+ # Find the fifth record.
214
+ # If no order is defined it will order by primary key.
215
+ #
216
+ # Person.fifth # returns the fifth object fetched by SELECT * FROM people
217
+ # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
218
+ # Person.where(["user_name = :u", { u: user_name }]).fifth
219
+ def fifth
220
+ find_nth 4
221
+ end
222
+
223
+ # Same as #fifth but raises ActiveRecord::RecordNotFound if no record
224
+ # is found.
225
+ def fifth!
226
+ fifth || raise_record_not_found_exception!
227
+ end
228
+
229
+ # Find the forty-second record. Also known as accessing "the reddit".
230
+ # If no order is defined it will order by primary key.
231
+ #
232
+ # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
233
+ # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
234
+ # Person.where(["user_name = :u", { u: user_name }]).forty_two
235
+ def forty_two
236
+ find_nth 41
237
+ end
238
+
239
+ # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
240
+ # is found.
241
+ def forty_two!
242
+ forty_two || raise_record_not_found_exception!
243
+ end
244
+
245
+ # Find the third-to-last record.
246
+ # If no order is defined it will order by primary key.
247
+ #
248
+ # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
249
+ # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
250
+ # Person.where(["user_name = :u", { u: user_name }]).third_to_last
251
+ def third_to_last
252
+ find_nth_from_last 3
253
+ end
254
+
255
+ # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
256
+ # is found.
257
+ def third_to_last!
258
+ third_to_last || raise_record_not_found_exception!
259
+ end
260
+
261
+ # Find the second-to-last record.
262
+ # If no order is defined it will order by primary key.
263
+ #
264
+ # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
265
+ # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
266
+ # Person.where(["user_name = :u", { u: user_name }]).second_to_last
267
+ def second_to_last
268
+ find_nth_from_last 2
269
+ end
270
+
271
+ # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
272
+ # is found.
273
+ def second_to_last!
274
+ second_to_last || raise_record_not_found_exception!
160
275
  end
161
276
 
162
277
  # 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:
278
+ # conditions given, or false otherwise. The argument can take six forms:
164
279
  #
165
280
  # * Integer - Finds the record with this primary key.
166
281
  # * String - Finds the record with a primary key corresponding to this
167
282
  # string (such as <tt>'5'</tt>).
168
283
  # * Array - Finds the record that matches these +find+-style conditions
169
- # (such as <tt>['color = ?', 'red']</tt>).
284
+ # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
170
285
  # * 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.
286
+ # (such as <tt>{name: 'David'}</tt>).
287
+ # * +false+ - Returns always +false+.
288
+ # * No args - Returns +false+ if the relation is empty, +true+ otherwise.
173
289
  #
174
- # For more information about specifying conditions as a Hash or Array,
290
+ # For more information about specifying conditions as a hash or array,
175
291
  # see the Conditions section in the introduction to ActiveRecord::Base.
176
292
  #
177
293
  # Note: You can't pass in a condition as a string (like <tt>name =
178
294
  # 'Jamie'</tt>), since it would be sanitized and then queried against
179
295
  # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
180
296
  #
181
- # ==== Examples
182
297
  # Person.exists?(5)
183
298
  # Person.exists?('5')
184
- # Person.exists?(:name => "David")
185
299
  # Person.exists?(['name LIKE ?', "%#{query}%"])
300
+ # Person.exists?(id: [1, 4, 8])
301
+ # Person.exists?(name: 'David')
302
+ # Person.exists?(false)
186
303
  # Person.exists?
187
- def exists?(id = false)
188
- id = id.id if ActiveRecord::Base === id
189
- return false if id.nil?
304
+ # Person.where(name: 'Spartacus', rating: 4).exists?
305
+ def exists?(conditions = :none)
306
+ if Base === conditions
307
+ raise ArgumentError, <<-MSG.squish
308
+ You are passing an instance of ActiveRecord::Base to `exists?`.
309
+ Please pass the id of the object by calling `.id`.
310
+ MSG
311
+ end
190
312
 
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)
313
+ return false if !conditions || limit_value == 0
194
314
 
195
- case id
196
- when Array, Hash
197
- relation = relation.where(id)
198
- else
199
- relation = relation.where(table[primary_key].eq(id)) if id
315
+ if eager_loading?
316
+ relation = apply_join_dependency(eager_loading: false)
317
+ return relation.exists?(conditions)
200
318
  end
201
319
 
202
- connection.select_value(relation, "#{name} Exists") ? true : false
203
- rescue ThrowResult
320
+ relation = construct_relation_for_exists(conditions)
321
+
322
+ skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists") } ? true : false
323
+ rescue ::RangeError
204
324
  false
205
325
  end
206
326
 
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
- []
327
+ # This method is called whenever no records are found with either a single
328
+ # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
329
+ #
330
+ # The error message is different depending on whether a single id or
331
+ # multiple ids are provided. If multiple ids are provided, then the number
332
+ # of results obtained should be provided in the +result_size+ argument and
333
+ # the expected number of results should be provided in the +expected_size+
334
+ # argument.
335
+ def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
336
+ conditions = arel.where_sql(@klass)
337
+ conditions = " [#{conditions}]" if conditions
338
+ name = @klass.name
339
+
340
+ if ids.nil?
341
+ error = "Couldn't find #{name}".dup
342
+ error << " with#{conditions}" if conditions
343
+ raise RecordNotFound.new(error, name, key)
344
+ elsif Array(ids).size == 1
345
+ error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
346
+ raise RecordNotFound.new(error, name, key, ids)
347
+ else
348
+ error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup
349
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
350
+ error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
351
+ raise RecordNotFound.new(error, name, key, ids)
352
+ end
216
353
  end
217
354
 
218
- def construct_join_dependency_for_association_find
219
- including = (@eager_load_values + @includes_values).uniq
220
- ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
221
- end
355
+ private
222
356
 
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
357
+ def offset_index
358
+ offset_value || 0
359
+ end
229
360
 
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)
233
- end
361
+ def construct_relation_for_exists(conditions)
362
+ if distinct_value && offset_value
363
+ relation = except(:order).limit!(1)
364
+ else
365
+ relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
366
+ end
367
+
368
+ case conditions
369
+ when Array, Hash
370
+ relation.where!(conditions) unless conditions.empty?
371
+ else
372
+ relation.where!(primary_key => conditions) unless conditions == :none
373
+ end
374
+
375
+ relation
376
+ end
234
377
 
235
- def apply_join_dependency(relation, join_dependency)
236
- join_dependency.join_associations.each do |association|
237
- relation = association.join_relation(relation)
378
+ def construct_join_dependency
379
+ including = eager_load_values + includes_values
380
+ ActiveRecord::Associations::JoinDependency.new(
381
+ klass, table, including
382
+ )
238
383
  end
239
384
 
240
- limitable_reflections = using_limitable_reflections?(join_dependency.reflections)
385
+ def apply_join_dependency(eager_loading: group_values.empty?)
386
+ join_dependency = construct_join_dependency
387
+ relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
241
388
 
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)
389
+ if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
390
+ if has_limit_or_offset?
391
+ limited_ids = limited_ids_for(relation)
392
+ limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
393
+ end
394
+ relation.limit_value = relation.offset_value = nil
395
+ end
396
+
397
+ if block_given?
398
+ yield relation, join_dependency
399
+ else
400
+ relation
401
+ end
245
402
  end
246
403
 
247
- relation = relation.except(:limit, :offset) unless limitable_reflections
404
+ def limited_ids_for(relation)
405
+ values = @klass.connection.columns_for_distinct(
406
+ connection.column_name_from_arel_node(arel_attribute(primary_key)),
407
+ relation.order_values
408
+ )
248
409
 
249
- relation
250
- end
410
+ relation = relation.except(:select).select(values).distinct!
251
411
 
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)
412
+ id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") }
413
+ id_rows.map { |row| row[primary_key] }
414
+ end
255
415
 
256
- relation = relation.dup.select(values)
257
- relation.uniq_value = nil
416
+ def using_limitable_reflections?(reflections)
417
+ reflections.none?(&:collection?)
418
+ end
258
419
 
259
- id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
260
- ids_array = id_rows.map {|row| row[primary_key]}
420
+ def find_with_ids(*ids)
421
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
261
422
 
262
- ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
263
- end
423
+ expects_array = ids.first.kind_of?(Array)
424
+ return [] if expects_array && ids.first.empty?
264
425
 
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)
426
+ ids = ids.flatten.compact.uniq
268
427
 
269
- if match.bang? && result.nil?
270
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
271
- else
272
- yield(result) if block_given?
273
- result
274
- end
275
- end
428
+ model_name = @klass.name
276
429
 
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
430
+ case ids.size
431
+ when 0
432
+ error_message = "Couldn't find #{model_name} without an ID"
433
+ raise RecordNotFound.new(error_message, model_name, primary_key)
434
+ when 1
435
+ result = find_one(ids.first)
436
+ expects_array ? [ result ] : result
283
437
  else
284
- unprotected_attributes_for_create[attributes[i]] = args[i]
438
+ find_some(ids)
285
439
  end
440
+ rescue ::RangeError
441
+ error_message = "Couldn't find #{model_name} with an out of range ID"
442
+ raise RecordNotFound.new(error_message, model_name, primary_key, ids)
286
443
  end
287
444
 
288
- conditions = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes).symbolize_keys
445
+ def find_one(id)
446
+ if ActiveRecord::Base === id
447
+ raise ArgumentError, <<-MSG.squish
448
+ You are passing an instance of ActiveRecord::Base to `find`.
449
+ Please pass the id of the object by calling `.id`.
450
+ MSG
451
+ end
289
452
 
290
- record = where(conditions).first
453
+ relation = where(primary_key => id)
454
+ record = relation.take
291
455
 
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?
456
+ raise_record_not_found_exception!(id, 0, 1) unless record
457
+
458
+ record
298
459
  end
299
460
 
300
- record
301
- end
461
+ def find_some(ids)
462
+ return find_some_ordered(ids) unless order_values.present?
302
463
 
303
- def find_with_ids(*ids)
304
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
464
+ result = where(primary_key => ids).to_a
305
465
 
306
- expects_array = ids.first.kind_of?(Array)
307
- return ids.first if expects_array && ids.first.empty?
466
+ expected_size =
467
+ if limit_value && ids.size > limit_value
468
+ limit_value
469
+ else
470
+ ids.size
471
+ end
308
472
 
309
- ids = ids.flatten.compact.uniq
473
+ # 11 ids with limit 3, offset 9 should give 2 results.
474
+ if offset_value && (ids.size - offset_value < expected_size)
475
+ expected_size = ids.size - offset_value
476
+ end
310
477
 
311
- case ids.size
312
- when 0
313
- raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
314
- when 1
315
- result = find_one(ids.first)
316
- expects_array ? [ result ] : result
317
- else
318
- find_some(ids)
478
+ if result.size == expected_size
479
+ result
480
+ else
481
+ raise_record_not_found_exception!(ids, result.size, expected_size)
482
+ end
319
483
  end
320
- end
321
-
322
- def find_one(id)
323
- id = id.id if ActiveRecord::Base === id
324
484
 
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
332
- end
485
+ def find_some_ordered(ids)
486
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
333
487
 
334
- column = columns_hash[primary_key]
488
+ result = except(:limit, :offset).where(primary_key => ids).records
335
489
 
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
490
+ if result.size == ids.size
491
+ pk_type = @klass.type_for_attribute(primary_key)
340
492
 
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}"
493
+ records_by_id = result.index_by(&:id)
494
+ ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
495
+ else
496
+ raise_record_not_found_exception!(ids, result.size, ids.size)
497
+ end
345
498
  end
346
499
 
347
- record
348
- end
349
-
350
- def find_some(ids)
351
- result = where(table[primary_key].in(ids)).all
500
+ def find_take
501
+ if loaded?
502
+ records.first
503
+ else
504
+ @take ||= limit(1).records.first
505
+ end
506
+ end
352
507
 
353
- expected_size =
354
- if @limit_value && ids.size > @limit_value
355
- @limit_value
508
+ def find_take_with_limit(limit)
509
+ if loaded?
510
+ records.take(limit)
356
511
  else
357
- ids.size
512
+ limit(limit).to_a
358
513
  end
514
+ end
359
515
 
360
- # 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
516
+ def find_nth(index)
517
+ @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
363
518
  end
364
519
 
365
- if result.size == expected_size
366
- result
367
- else
368
- conditions = arel.where_sql
369
- conditions = " [#{conditions}]" if conditions
520
+ def find_nth_with_limit(index, limit)
521
+ if loaded?
522
+ records[index, limit] || []
523
+ else
524
+ relation = ordered_relation
370
525
 
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
374
- end
375
- end
526
+ if limit_value
527
+ limit = [limit_value - index, limit].min
528
+ end
376
529
 
377
- def find_first
378
- if loaded?
379
- @records.first
380
- else
381
- @first ||= limit(1).to_a[0]
530
+ if limit > 0
531
+ relation = relation.offset(offset_index + index) unless index.zero?
532
+ relation.limit(limit).to_a
533
+ else
534
+ []
535
+ end
536
+ end
382
537
  end
383
- end
384
538
 
385
- def find_last
386
- if loaded?
387
- @records.last
388
- else
389
- @last ||=
390
- if offset_value || limit_value
391
- to_a.last
539
+ def find_nth_from_last(index)
540
+ if loaded?
541
+ records[-index]
542
+ else
543
+ relation = ordered_relation
544
+
545
+ if equal?(relation) || has_limit_or_offset?
546
+ relation.records[-index]
392
547
  else
393
- reverse_order.limit(1).to_a[0]
548
+ relation.last(index)[-index]
394
549
  end
550
+ end
395
551
  end
396
- end
397
552
 
398
- def using_limitable_reflections?(reflections)
399
- reflections.none? { |r| r.collection? }
400
- end
553
+ def find_last(limit)
554
+ limit ? records.last(limit) : records.last
555
+ end
556
+
557
+ def ordered_relation
558
+ if order_values.empty? && primary_key
559
+ order(arel_attribute(primary_key).asc)
560
+ else
561
+ self
562
+ end
563
+ end
401
564
  end
402
565
  end