activerecord 4.2.0 → 5.2.8.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

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