activerecord 3.2.22.5 → 4.2.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

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