activerecord 4.2.11 → 5.2.4.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 +4 -4
  2. data/CHANGELOG.md +580 -1626
  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 +263 -249
  8. data/lib/active_record/association_relation.rb +11 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +77 -43
  11. data/lib/active_record/associations/association_scope.rb +106 -133
  12. data/lib/active_record/associations/belongs_to_association.rb +52 -41
  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 +9 -22
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
  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 +139 -280
  22. data/lib/active_record/associations/collection_proxy.rb +231 -133
  23. data/lib/active_record/associations/foreign_association.rb +3 -1
  24. data/lib/active_record/associations/has_many_association.rb +34 -89
  25. data/lib/active_record/associations/has_many_through_association.rb +49 -76
  26. data/lib/active_record/associations/has_one_association.rb +38 -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 -87
  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 +133 -159
  32. data/lib/active_record/associations/preloader/association.rb +85 -120
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +81 -91
  35. data/lib/active_record/associations/singular_association.rb +27 -34
  36. data/lib/active_record/associations/through_association.rb +38 -18
  37. data/lib/active_record/associations.rb +1732 -1597
  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 +10 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -135
  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 +58 -36
  47. data/lib/active_record/attribute_methods/write.rb +30 -45
  48. data/lib/active_record/attribute_methods.rb +166 -109
  49. data/lib/active_record/attributes.rb +201 -82
  50. data/lib/active_record/autosave_association.rb +94 -36
  51. data/lib/active_record/base.rb +57 -44
  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 +24 -12
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -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 +570 -228
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -601
  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 +41 -180
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
  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 -58
  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 +4 -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 -22
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -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 +5 -7
  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 -5
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +55 -53
  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 +462 -284
  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 +432 -323
  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 -308
  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 +178 -198
  129. data/lib/active_record/counter_cache.rb +79 -36
  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 +135 -88
  133. data/lib/active_record/errors.rb +179 -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 +10 -5
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +188 -132
  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 +21 -3
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +88 -96
  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 +581 -282
  152. data/lib/active_record/model_schema.rb +290 -111
  153. data/lib/active_record/nested_attributes.rb +264 -222
  154. data/lib/active_record/no_touching.rb +7 -1
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +347 -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 +94 -32
  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 +149 -156
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +414 -267
  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 +256 -248
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +288 -239
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +86 -86
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
  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 +116 -119
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +448 -393
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +11 -13
  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 -340
  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 -16
  193. data/lib/active_record/scoping/default.rb +102 -85
  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 +134 -96
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
  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 +199 -124
  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 -45
  212. data/lib/active_record/type/date_time.rb +4 -49
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  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 +24 -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 +40 -41
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +34 -22
  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 -3
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
  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/model/templates/{module.rb → module.rb.tt} +0 -0
  243. data/lib/rails/generators/active_record.rb +7 -5
  244. metadata +72 -50
  245. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  246. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  247. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  248. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  249. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  250. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  251. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  252. data/lib/active_record/attribute.rb +0 -163
  253. data/lib/active_record/attribute_set/builder.rb +0 -106
  254. data/lib/active_record/attribute_set.rb +0 -81
  255. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  256. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  257. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  258. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  259. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  260. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  261. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  262. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  263. data/lib/active_record/type/big_integer.rb +0 -13
  264. data/lib/active_record/type/binary.rb +0 -50
  265. data/lib/active_record/type/boolean.rb +0 -31
  266. data/lib/active_record/type/decimal.rb +0 -64
  267. data/lib/active_record/type/decorator.rb +0 -14
  268. data/lib/active_record/type/float.rb +0 -19
  269. data/lib/active_record/type/integer.rb +0 -59
  270. data/lib/active_record/type/mutable.rb +0 -16
  271. data/lib/active_record/type/numeric.rb +0 -36
  272. data/lib/active_record/type/string.rb +0 -40
  273. data/lib/active_record/type/time_value.rb +0 -38
  274. data/lib/active_record/type/value.rb +0 -110
@@ -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,13 +100,13 @@ 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).
@@ -122,16 +120,16 @@ module ActiveRecord
122
120
  #
123
121
  def first(limit = nil)
124
122
  if limit
125
- find_nth_with_limit(offset_index, limit)
123
+ find_nth_with_limit(0, limit)
126
124
  else
127
- find_nth(0, offset_index)
125
+ find_nth 0
128
126
  end
129
127
  end
130
128
 
131
- # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
132
- # 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.
133
131
  def first!
134
- find_nth! 0
132
+ first || raise_record_not_found_exception!
135
133
  end
136
134
 
137
135
  # Find the last record (or last N records if a parameter is supplied).
@@ -150,21 +148,18 @@ module ActiveRecord
150
148
  #
151
149
  # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
152
150
  def last(limit = nil)
153
- if limit
154
- if order_values.empty? && primary_key
155
- order(arel_table[primary_key].desc).limit(limit).reverse
156
- else
157
- to_a.last(limit)
158
- end
159
- else
160
- find_last
161
- 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
162
157
  end
163
158
 
164
- # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
165
- # 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.
166
161
  def last!
167
- last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
162
+ last || raise_record_not_found_exception!
168
163
  end
169
164
 
170
165
  # Find the second record.
@@ -174,13 +169,13 @@ module ActiveRecord
174
169
  # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
175
170
  # Person.where(["user_name = :u", { u: user_name }]).second
176
171
  def second
177
- find_nth(1, offset_index)
172
+ find_nth 1
178
173
  end
179
174
 
180
- # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
175
+ # Same as #second but raises ActiveRecord::RecordNotFound if no record
181
176
  # is found.
182
177
  def second!
183
- find_nth! 1
178
+ second || raise_record_not_found_exception!
184
179
  end
185
180
 
186
181
  # Find the third record.
@@ -190,13 +185,13 @@ module ActiveRecord
190
185
  # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
191
186
  # Person.where(["user_name = :u", { u: user_name }]).third
192
187
  def third
193
- find_nth(2, offset_index)
188
+ find_nth 2
194
189
  end
195
190
 
196
- # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
191
+ # Same as #third but raises ActiveRecord::RecordNotFound if no record
197
192
  # is found.
198
193
  def third!
199
- find_nth! 2
194
+ third || raise_record_not_found_exception!
200
195
  end
201
196
 
202
197
  # Find the fourth record.
@@ -206,13 +201,13 @@ module ActiveRecord
206
201
  # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
207
202
  # Person.where(["user_name = :u", { u: user_name }]).fourth
208
203
  def fourth
209
- find_nth(3, offset_index)
204
+ find_nth 3
210
205
  end
211
206
 
212
- # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
207
+ # Same as #fourth but raises ActiveRecord::RecordNotFound if no record
213
208
  # is found.
214
209
  def fourth!
215
- find_nth! 3
210
+ fourth || raise_record_not_found_exception!
216
211
  end
217
212
 
218
213
  # Find the fifth record.
@@ -222,13 +217,13 @@ module ActiveRecord
222
217
  # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
223
218
  # Person.where(["user_name = :u", { u: user_name }]).fifth
224
219
  def fifth
225
- find_nth(4, offset_index)
220
+ find_nth 4
226
221
  end
227
222
 
228
- # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
223
+ # Same as #fifth but raises ActiveRecord::RecordNotFound if no record
229
224
  # is found.
230
225
  def fifth!
231
- find_nth! 4
226
+ fifth || raise_record_not_found_exception!
232
227
  end
233
228
 
234
229
  # Find the forty-second record. Also known as accessing "the reddit".
@@ -238,17 +233,49 @@ module ActiveRecord
238
233
  # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
239
234
  # Person.where(["user_name = :u", { u: user_name }]).forty_two
240
235
  def forty_two
241
- find_nth(41, offset_index)
236
+ find_nth 41
242
237
  end
243
238
 
244
- # 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
245
240
  # is found.
246
241
  def forty_two!
247
- find_nth! 41
242
+ forty_two || raise_record_not_found_exception!
248
243
  end
249
244
 
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:
245
+ # Find the third-to-last record.
246
+ # If no order is defined it will order by primary key.
247
+ #
248
+ # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
249
+ # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
250
+ # Person.where(["user_name = :u", { u: user_name }]).third_to_last
251
+ def third_to_last
252
+ find_nth_from_last 3
253
+ end
254
+
255
+ # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
256
+ # is found.
257
+ def third_to_last!
258
+ third_to_last || raise_record_not_found_exception!
259
+ end
260
+
261
+ # Find the second-to-last record.
262
+ # If no order is defined it will order by primary key.
263
+ #
264
+ # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
265
+ # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
266
+ # Person.where(["user_name = :u", { u: user_name }]).second_to_last
267
+ def second_to_last
268
+ find_nth_from_last 2
269
+ end
270
+
271
+ # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
272
+ # is found.
273
+ def second_to_last!
274
+ second_to_last || raise_record_not_found_exception!
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:
252
279
  #
253
280
  # * Integer - Finds the record with this primary key.
254
281
  # * String - Finds the record with a primary key corresponding to this
@@ -258,10 +285,10 @@ module ActiveRecord
258
285
  # * Hash - Finds the record that matches these +find+-style conditions
259
286
  # (such as <tt>{name: 'David'}</tt>).
260
287
  # * +false+ - Returns always +false+.
261
- # * No args - Returns +false+ if the table is empty, +true+ otherwise.
288
+ # * No args - Returns +false+ if the relation is empty, +true+ otherwise.
262
289
  #
263
290
  # For more information about specifying conditions as a hash or array,
264
- # see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
291
+ # see the Conditions section in the introduction to ActiveRecord::Base.
265
292
  #
266
293
  # Note: You can't pass in a condition as a string (like <tt>name =
267
294
  # 'Jamie'</tt>), since it would be sanitized and then queried against
@@ -274,243 +301,265 @@ module ActiveRecord
274
301
  # Person.exists?(name: 'David')
275
302
  # Person.exists?(false)
276
303
  # Person.exists?
304
+ # Person.where(name: 'Spartacus', rating: 4).exists?
277
305
  def exists?(conditions = :none)
278
306
  if Base === conditions
279
- conditions = conditions.id
280
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
307
+ raise ArgumentError, <<-MSG.squish
281
308
  You are passing an instance of ActiveRecord::Base to `exists?`.
282
- Please pass the id of the object by calling `.id`
309
+ Please pass the id of the object by calling `.id`.
283
310
  MSG
284
311
  end
285
312
 
286
- return false if !conditions
287
-
288
- relation = apply_join_dependency(self, construct_join_dependency)
289
- return false if ActiveRecord::NullRelation === relation
313
+ return false if !conditions || limit_value == 0
290
314
 
291
- relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
292
-
293
- case conditions
294
- when Array, Hash
295
- relation = relation.where(conditions)
296
- else
297
- unless conditions == :none
298
- relation = relation.where(primary_key => conditions)
299
- end
315
+ if eager_loading?
316
+ relation = apply_join_dependency(eager_loading: false)
317
+ return relation.exists?(conditions)
300
318
  end
301
319
 
302
- 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
303
325
  end
304
326
 
305
327
  # This method is called whenever no records are found with either a single
306
- # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
328
+ # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
307
329
  #
308
330
  # The error message is different depending on whether a single id or
309
331
  # multiple ids are provided. If multiple ids are provided, then the number
310
332
  # of results obtained should be provided in the +result_size+ argument and
311
333
  # the expected number of results should be provided in the +expected_size+
312
334
  # argument.
313
- def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
314
- 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)
315
337
  conditions = " [#{conditions}]" if conditions
316
-
317
- if Array(ids).size == 1
318
- 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)
319
347
  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})"
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)
322
352
  end
323
-
324
- raise RecordNotFound, error
325
353
  end
326
354
 
327
355
  private
328
356
 
329
- def offset_index
330
- offset_value || 0
331
- end
357
+ def offset_index
358
+ offset_value || 0
359
+ end
332
360
 
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
- []
361
+ def construct_relation_for_exists(conditions)
362
+ if distinct_value && offset_value
363
+ relation = except(:order).limit!(1)
354
364
  else
355
- arel = relation.arel
356
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
357
- join_dependency.instantiate(rows, aliases)
365
+ relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
358
366
  end
359
- end
360
- end
361
367
 
362
- def construct_join_dependency(joins = [])
363
- including = eager_load_values + includes_values
364
- ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
365
- 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
366
374
 
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))
371
- else
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))
375
+ relation
376
376
  end
377
- end
378
377
 
379
- def apply_join_dependency(relation, join_dependency)
380
- relation = relation.except(:includes, :eager_load, :preload)
381
- relation = relation.joins join_dependency
378
+ def construct_join_dependency
379
+ including = eager_load_values + includes_values
380
+ ActiveRecord::Associations::JoinDependency.new(
381
+ klass, table, including
382
+ )
383
+ end
382
384
 
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))
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)
388
+
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
389
401
  end
390
- relation.except(:limit, :offset)
391
402
  end
392
- end
393
403
 
394
- def limited_ids_for(relation)
395
- values = @klass.connection.columns_for_distinct(
396
- "#{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
+ )
397
409
 
398
- relation = relation.except(:select).select(values).distinct!
399
- arel = relation.arel
410
+ relation = relation.except(:select).select(values).distinct!
400
411
 
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
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
404
415
 
405
- def using_limitable_reflections?(reflections)
406
- reflections.none? { |r| r.collection? }
407
- end
416
+ def using_limitable_reflections?(reflections)
417
+ reflections.none?(&:collection?)
418
+ end
408
419
 
409
- protected
420
+ def find_with_ids(*ids)
421
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
410
422
 
411
- def find_with_ids(*ids)
412
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
423
+ expects_array = ids.first.kind_of?(Array)
424
+ return [] if expects_array && ids.first.empty?
413
425
 
414
- expects_array = ids.first.kind_of?(Array)
415
- return ids.first if expects_array && ids.first.empty?
426
+ ids = ids.flatten.compact.uniq
416
427
 
417
- ids = ids.flatten.compact.uniq
428
+ model_name = @klass.name
418
429
 
419
- case ids.size
420
- when 0
421
- raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
422
- when 1
423
- result = find_one(ids.first)
424
- expects_array ? [ result ] : result
425
- else
426
- 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)
427
443
  end
428
- rescue RangeError
429
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
430
- end
431
444
 
432
- def find_one(id)
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
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
439
459
  end
440
460
 
441
- relation = where(primary_key => id)
442
- record = relation.take
461
+ def find_some(ids)
462
+ return find_some_ordered(ids) unless order_values.present?
443
463
 
444
- raise_record_not_found_exception!(id, 0, 1) unless record
464
+ result = where(primary_key => ids).to_a
445
465
 
446
- record
447
- end
466
+ expected_size =
467
+ if limit_value && ids.size > limit_value
468
+ limit_value
469
+ else
470
+ ids.size
471
+ end
448
472
 
449
- def find_some(ids)
450
- 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
451
477
 
452
- expected_size =
453
- if limit_value && ids.size > limit_value
454
- limit_value
478
+ if result.size == expected_size
479
+ result
455
480
  else
456
- ids.size
481
+ raise_record_not_found_exception!(ids, result.size, expected_size)
457
482
  end
483
+ end
484
+
485
+ def find_some_ordered(ids)
486
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
458
487
 
459
- # 11 ids with limit 3, offset 9 should give 2 results.
460
- if offset_value && (ids.size - offset_value < expected_size)
461
- expected_size = ids.size - offset_value
488
+ result = except(:limit, :offset).where(primary_key => ids).records
489
+
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
462
498
  end
463
499
 
464
- if result.size == expected_size
465
- result
466
- else
467
- 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
468
506
  end
469
- end
470
507
 
471
- def find_take
472
- if loaded?
473
- @records.first
474
- else
475
- @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
476
514
  end
477
- end
478
515
 
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
516
+ def find_nth(index)
517
+ @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
485
518
  end
486
- end
487
519
 
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
520
+ def find_nth_with_limit(index, limit)
521
+ if loaded?
522
+ records[index, limit] || []
523
+ else
524
+ relation = ordered_relation
491
525
 
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
526
+ if limit_value
527
+ limit = [limit_value - index, limit].min
528
+ end
498
529
 
499
- relation = relation.offset(offset) unless offset.zero?
500
- relation.limit(limit).to_a
501
- 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
502
538
 
503
- def find_last
504
- if loaded?
505
- @records.last
506
- else
507
- @last ||=
508
- if limit_value
509
- 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]
510
547
  else
511
- reverse_order.limit(1).to_a.first
548
+ relation.last(index)[-index]
512
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
513
563
  end
514
- end
515
564
  end
516
565
  end