activerecord 3.1.10 → 4.2.11

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 (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  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 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  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 -102
  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 +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  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 +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  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 +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  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 +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  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 +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  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 +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  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 +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  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 +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  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 +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  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 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  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 +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,89 +1,33 @@
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
86
- # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
30
+ # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
87
31
  # transaction has to wait until the first is finished; we get the
88
32
  # expected <tt>person.visits == 4</tt>.
89
33
  #
@@ -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? && reorder_value.nil?
138
- order("#{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,153 +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
154
216
  end
155
217
 
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
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)
226
+ end
227
+
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
- 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
189
285
 
190
- id = id.id if ActiveRecord::Base === id
286
+ return false if !conditions
191
287
 
192
- join_dependency = construct_join_dependency_for_association_find
193
- relation = construct_relation_for_association_find(join_dependency)
194
- relation = relation.except(:select).select("1").limit(1)
288
+ relation = apply_join_dependency(self, construct_join_dependency)
289
+ return false if ActiveRecord::NullRelation === relation
195
290
 
196
- case id
291
+ relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
292
+
293
+ case conditions
197
294
  when Array, Hash
198
- relation = relation.where(id)
295
+ relation = relation.where(conditions)
199
296
  else
200
- relation = relation.where(table[primary_key].eq(id)) if id
297
+ unless conditions == :none
298
+ relation = relation.where(primary_key => conditions)
299
+ end
201
300
  end
202
301
 
203
- connection.select_value(relation) ? true : false
302
+ connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
204
303
  end
205
304
 
206
- protected
207
-
208
- def find_with_associations
209
- join_dependency = construct_join_dependency_for_association_find
210
- relation = construct_relation_for_association_find(join_dependency)
211
- rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
212
- join_dependency.instantiate(rows)
213
- rescue ThrowResult
214
- []
215
- 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
216
323
 
217
- def construct_join_dependency_for_association_find
218
- including = (@eager_load_values + @includes_values).uniq
219
- ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
324
+ raise RecordNotFound, error
220
325
  end
221
326
 
222
- def construct_relation_for_association_calculations
223
- including = (@eager_load_values + @includes_values).uniq
224
- join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
225
- relation = except(:includes, :eager_load, :preload)
226
- apply_join_dependency(relation, join_dependency)
227
- end
327
+ private
228
328
 
229
- def construct_relation_for_association_find(join_dependency)
230
- relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
231
- apply_join_dependency(relation, join_dependency)
329
+ def offset_index
330
+ offset_value || 0
232
331
  end
233
332
 
234
- def apply_join_dependency(relation, join_dependency)
235
- join_dependency.join_associations.each do |association|
236
- relation = association.join_relation(relation)
237
- end
238
-
239
- limitable_reflections = using_limitable_reflections?(join_dependency.reflections)
240
-
241
- if !limitable_reflections && relation.limit_value
242
- limited_id_condition = construct_limited_ids_condition(relation.except(:select))
243
- 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
244
359
  end
245
-
246
- relation = relation.except(:limit, :offset) unless limitable_reflections
247
-
248
- relation
249
360
  end
250
361
 
251
- def construct_limited_ids_condition(relation)
252
- orders = relation.order_values.map { |val| val.presence }.compact
253
- values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
254
-
255
- relation = relation.dup
256
-
257
- ids_array = relation.select(values).collect {|row| row[primary_key]}
258
- 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)
259
365
  end
260
366
 
261
- def find_by_attributes(match, attributes, *args)
262
- conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
263
- result = where(conditions).send(match.finder)
264
-
265
- if match.bang? && result.blank?
266
- 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))
267
371
  else
268
- 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))
269
376
  end
270
377
  end
271
378
 
272
- def find_or_instantiator_by_attributes(match, attributes, *args)
273
- options = args.size > 1 && args.last(2).all?{ |a| a.is_a?(Hash) } ? args.extract_options! : {}
274
- protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
275
- args.each_with_index do |arg, i|
276
- if arg.is_a?(Hash)
277
- protected_attributes_for_create = args[i].with_indifferent_access
278
- else
279
- 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))
280
389
  end
390
+ relation.except(:limit, :offset)
281
391
  end
392
+ end
282
393
 
283
- 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)
284
397
 
285
- record = where(conditions).first
398
+ relation = relation.except(:select).select(values).distinct!
399
+ arel = relation.arel
286
400
 
287
- unless record
288
- record = @klass.new(protected_attributes_for_create, options) do |r|
289
- r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
290
- end
291
- yield(record) if block_given?
292
- record.save if match.instantiator == :create
293
- 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
294
404
 
295
- record
405
+ def using_limitable_reflections?(reflections)
406
+ reflections.none? { |r| r.collection? }
296
407
  end
297
408
 
409
+ protected
410
+
298
411
  def find_with_ids(*ids)
299
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
412
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
300
413
 
301
414
  expects_array = ids.first.kind_of?(Array)
302
415
  return ids.first if expects_array && ids.first.empty?
@@ -312,86 +425,92 @@ module ActiveRecord
312
425
  else
313
426
  find_some(ids)
314
427
  end
428
+ rescue RangeError
429
+ raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
315
430
  end
316
431
 
317
432
  def find_one(id)
318
- id = id.id if ActiveRecord::Base === id
319
-
320
- if IdentityMap.enabled? && where_values.blank? &&
321
- limit_value.blank? && order_values.blank? &&
322
- includes_values.blank? && preload_values.blank? &&
323
- readonly_value.nil? && joins_values.blank? &&
324
- !@klass.locking_enabled? &&
325
- record = IdentityMap.get(@klass, id)
326
- 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
327
439
  end
328
440
 
329
- column = columns_hash[primary_key]
330
-
331
- substitute = connection.substitute_at(column, @bind_values.length)
332
- relation = where(table[primary_key].eq(substitute))
333
- relation.bind_values = [[column, id]]
334
- record = relation.first
441
+ relation = where(primary_key => id)
442
+ record = relation.take
335
443
 
336
- unless record
337
- conditions = arel.where_sql
338
- conditions = " [#{conditions}]" if conditions
339
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
340
- end
444
+ raise_record_not_found_exception!(id, 0, 1) unless record
341
445
 
342
446
  record
343
447
  end
344
448
 
345
449
  def find_some(ids)
346
- result = where(table[primary_key].in(ids)).all
450
+ result = where(primary_key => ids).to_a
347
451
 
348
452
  expected_size =
349
- if @limit_value && ids.size > @limit_value
350
- @limit_value
453
+ if limit_value && ids.size > limit_value
454
+ limit_value
351
455
  else
352
456
  ids.size
353
457
  end
354
458
 
355
459
  # 11 ids with limit 3, offset 9 should give 2 results.
356
- if @offset_value && (ids.size - @offset_value < expected_size)
357
- expected_size = ids.size - @offset_value
460
+ if offset_value && (ids.size - offset_value < expected_size)
461
+ expected_size = ids.size - offset_value
358
462
  end
359
463
 
360
464
  if result.size == expected_size
361
465
  result
362
466
  else
363
- conditions = arel.where_sql
364
- conditions = " [#{conditions}]" if conditions
365
-
366
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
367
- error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
368
- raise RecordNotFound, error
467
+ raise_record_not_found_exception!(ids, result.size, expected_size)
369
468
  end
370
469
  end
371
470
 
372
- def find_first
471
+ def find_take
373
472
  if loaded?
374
473
  @records.first
375
474
  else
376
- @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
377
485
  end
378
486
  end
379
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
+
380
503
  def find_last
381
504
  if loaded?
382
505
  @records.last
383
506
  else
384
507
  @last ||=
385
- if offset_value || limit_value
508
+ if limit_value
386
509
  to_a.last
387
510
  else
388
- reverse_order.limit(1).to_a[0]
511
+ reverse_order.limit(1).to_a.first
389
512
  end
390
513
  end
391
514
  end
392
-
393
- def using_limitable_reflections?(reflections)
394
- reflections.none? { |r| r.collection? }
395
- end
396
515
  end
397
516
  end