activerecord 4.2.11.3 → 5.0.7.2

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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1638 -1132
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record.rb +7 -2
  8. data/lib/active_record/aggregations.rb +34 -21
  9. data/lib/active_record/association_relation.rb +7 -4
  10. data/lib/active_record/associations.rb +347 -218
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +22 -10
  13. data/lib/active_record/associations/association_scope.rb +75 -104
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +13 -11
  22. data/lib/active_record/associations/collection_association.rb +85 -69
  23. data/lib/active_record/associations/collection_proxy.rb +104 -46
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +21 -78
  26. data/lib/active_record/associations/has_many_through_association.rb +6 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +38 -22
  29. data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +14 -4
  32. data/lib/active_record/associations/preloader/association.rb +52 -71
  33. data/lib/active_record/associations/preloader/collection_association.rb +0 -7
  34. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +0 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +36 -17
  38. data/lib/active_record/associations/singular_association.rb +13 -1
  39. data/lib/active_record/associations/through_association.rb +12 -4
  40. data/lib/active_record/attribute.rb +69 -19
  41. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  42. data/lib/active_record/attribute_assignment.rb +19 -140
  43. data/lib/active_record/attribute_decorators.rb +6 -5
  44. data/lib/active_record/attribute_methods.rb +69 -44
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  46. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  47. data/lib/active_record/attribute_methods/primary_key.rb +16 -3
  48. data/lib/active_record/attribute_methods/query.rb +2 -2
  49. data/lib/active_record/attribute_methods/read.rb +31 -59
  50. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  52. data/lib/active_record/attribute_methods/write.rb +13 -37
  53. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  54. data/lib/active_record/attribute_set.rb +32 -3
  55. data/lib/active_record/attribute_set/builder.rb +42 -16
  56. data/lib/active_record/attributes.rb +199 -81
  57. data/lib/active_record/autosave_association.rb +54 -17
  58. data/lib/active_record/base.rb +32 -23
  59. data/lib/active_record/callbacks.rb +39 -43
  60. data/lib/active_record/coders/json.rb +1 -1
  61. data/lib/active_record/coders/yaml_column.rb +20 -8
  62. data/lib/active_record/collection_cache_key.rb +50 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
  76. data/lib/active_record/connection_adapters/column.rb +28 -43
  77. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  78. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  79. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  80. data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
  81. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  82. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  87. data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
  88. data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
  89. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
  90. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
  93. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  99. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  102. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
  116. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  118. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  121. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
  122. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  123. data/lib/active_record/connection_handling.rb +37 -14
  124. data/lib/active_record/core.rb +92 -108
  125. data/lib/active_record/counter_cache.rb +13 -24
  126. data/lib/active_record/dynamic_matchers.rb +1 -20
  127. data/lib/active_record/enum.rb +116 -76
  128. data/lib/active_record/errors.rb +87 -48
  129. data/lib/active_record/explain.rb +20 -9
  130. data/lib/active_record/explain_registry.rb +1 -1
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +26 -5
  133. data/lib/active_record/fixtures.rb +77 -41
  134. data/lib/active_record/gem_version.rb +4 -4
  135. data/lib/active_record/inheritance.rb +32 -40
  136. data/lib/active_record/integration.rb +17 -14
  137. data/lib/active_record/internal_metadata.rb +56 -0
  138. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  139. data/lib/active_record/locale/en.yml +3 -2
  140. data/lib/active_record/locking/optimistic.rb +15 -15
  141. data/lib/active_record/locking/pessimistic.rb +1 -1
  142. data/lib/active_record/log_subscriber.rb +48 -24
  143. data/lib/active_record/migration.rb +362 -111
  144. data/lib/active_record/migration/command_recorder.rb +59 -18
  145. data/lib/active_record/migration/compatibility.rb +126 -0
  146. data/lib/active_record/model_schema.rb +270 -73
  147. data/lib/active_record/nested_attributes.rb +58 -29
  148. data/lib/active_record/no_touching.rb +4 -0
  149. data/lib/active_record/null_relation.rb +16 -8
  150. data/lib/active_record/persistence.rb +152 -90
  151. data/lib/active_record/query_cache.rb +18 -23
  152. data/lib/active_record/querying.rb +12 -11
  153. data/lib/active_record/railtie.rb +23 -16
  154. data/lib/active_record/railties/controller_runtime.rb +1 -1
  155. data/lib/active_record/railties/databases.rake +52 -41
  156. data/lib/active_record/readonly_attributes.rb +1 -1
  157. data/lib/active_record/reflection.rb +302 -115
  158. data/lib/active_record/relation.rb +187 -120
  159. data/lib/active_record/relation/batches.rb +141 -36
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  161. data/lib/active_record/relation/calculations.rb +92 -117
  162. data/lib/active_record/relation/delegation.rb +8 -20
  163. data/lib/active_record/relation/finder_methods.rb +173 -89
  164. data/lib/active_record/relation/from_clause.rb +32 -0
  165. data/lib/active_record/relation/merger.rb +16 -42
  166. data/lib/active_record/relation/predicate_builder.rb +120 -107
  167. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  168. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  169. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  170. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  171. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  173. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  174. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  175. data/lib/active_record/relation/query_attribute.rb +19 -0
  176. data/lib/active_record/relation/query_methods.rb +308 -244
  177. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  178. data/lib/active_record/relation/spawn_methods.rb +4 -7
  179. data/lib/active_record/relation/where_clause.rb +174 -0
  180. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  181. data/lib/active_record/result.rb +11 -4
  182. data/lib/active_record/runtime_registry.rb +1 -1
  183. data/lib/active_record/sanitization.rb +105 -66
  184. data/lib/active_record/schema.rb +26 -22
  185. data/lib/active_record/schema_dumper.rb +54 -37
  186. data/lib/active_record/schema_migration.rb +11 -14
  187. data/lib/active_record/scoping.rb +34 -16
  188. data/lib/active_record/scoping/default.rb +28 -10
  189. data/lib/active_record/scoping/named.rb +59 -26
  190. data/lib/active_record/secure_token.rb +38 -0
  191. data/lib/active_record/serialization.rb +3 -5
  192. data/lib/active_record/statement_cache.rb +17 -15
  193. data/lib/active_record/store.rb +8 -3
  194. data/lib/active_record/suppressor.rb +58 -0
  195. data/lib/active_record/table_metadata.rb +69 -0
  196. data/lib/active_record/tasks/database_tasks.rb +66 -49
  197. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  198. data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
  199. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  200. data/lib/active_record/timestamp.rb +20 -9
  201. data/lib/active_record/touch_later.rb +63 -0
  202. data/lib/active_record/transactions.rb +139 -57
  203. data/lib/active_record/type.rb +66 -17
  204. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  205. data/lib/active_record/type/date.rb +2 -45
  206. data/lib/active_record/type/date_time.rb +2 -49
  207. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  208. data/lib/active_record/type/internal/timezone.rb +15 -0
  209. data/lib/active_record/type/serialized.rb +15 -14
  210. data/lib/active_record/type/time.rb +10 -16
  211. data/lib/active_record/type/type_map.rb +4 -4
  212. data/lib/active_record/type_caster.rb +7 -0
  213. data/lib/active_record/type_caster/connection.rb +29 -0
  214. data/lib/active_record/type_caster/map.rb +19 -0
  215. data/lib/active_record/validations.rb +33 -32
  216. data/lib/active_record/validations/absence.rb +23 -0
  217. data/lib/active_record/validations/associated.rb +10 -3
  218. data/lib/active_record/validations/length.rb +24 -0
  219. data/lib/active_record/validations/presence.rb +11 -12
  220. data/lib/active_record/validations/uniqueness.rb +33 -33
  221. data/lib/rails/generators/active_record/migration.rb +15 -0
  222. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
  223. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  224. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
  226. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  227. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  228. metadata +58 -34
  229. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  230. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  231. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  232. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  233. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  234. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  235. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  236. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  237. data/lib/active_record/type/big_integer.rb +0 -13
  238. data/lib/active_record/type/binary.rb +0 -50
  239. data/lib/active_record/type/boolean.rb +0 -31
  240. data/lib/active_record/type/decimal.rb +0 -64
  241. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  242. data/lib/active_record/type/decorator.rb +0 -14
  243. data/lib/active_record/type/float.rb +0 -19
  244. data/lib/active_record/type/integer.rb +0 -59
  245. data/lib/active_record/type/mutable.rb +0 -16
  246. data/lib/active_record/type/numeric.rb +0 -36
  247. data/lib/active_record/type/string.rb +0 -40
  248. data/lib/active_record/type/text.rb +0 -11
  249. data/lib/active_record/type/time_value.rb +0 -38
  250. data/lib/active_record/type/unsigned_integer.rb +0 -15
  251. data/lib/active_record/type/value.rb +0 -110
@@ -1,15 +1,13 @@
1
- require 'set'
2
1
  require 'active_support/concern'
3
- require 'active_support/deprecation'
4
2
 
5
3
  module ActiveRecord
6
4
  module Delegation # :nodoc:
7
- module DelegateCache
8
- def relation_delegate_class(klass) # :nodoc:
5
+ module DelegateCache # :nodoc:
6
+ def relation_delegate_class(klass)
9
7
  @relation_delegate_cache[klass]
10
8
  end
11
9
 
12
- def initialize_relation_delegate_cache # :nodoc:
10
+ def initialize_relation_delegate_cache
13
11
  @relation_delegate_cache = cache = {}
14
12
  [
15
13
  ActiveRecord::Relation,
@@ -19,7 +17,7 @@ module ActiveRecord
19
17
  delegate = Class.new(klass) {
20
18
  include ClassSpecificRelation
21
19
  }
22
- const_set klass.name.gsub('::', '_'), delegate
20
+ const_set klass.name.gsub('::'.freeze, '_'.freeze), delegate
23
21
  cache[klass] = delegate
24
22
  end
25
23
  end
@@ -37,13 +35,10 @@ module ActiveRecord
37
35
  # may vary depending on the klass of a relation, so we create a subclass of Relation
38
36
  # for each different klass, and the delegations are compiled into that subclass only.
39
37
 
40
- BLACKLISTED_ARRAY_METHODS = [
41
- :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
42
- :shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
43
- :keep_if, :pop, :shift, :delete_at, :select!
44
- ].to_set # :nodoc:
45
-
46
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
38
+ delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
39
+ :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
40
+ :to_sentence, :to_formatted_s,
41
+ :shuffle, :split, :index, to: :records
47
42
 
48
43
  delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
49
44
  :connection, :columns_hash, :to => :klass
@@ -115,21 +110,14 @@ module ActiveRecord
115
110
 
116
111
  def respond_to?(method, include_private = false)
117
112
  super || @klass.respond_to?(method, include_private) ||
118
- array_delegable?(method) ||
119
113
  arel.respond_to?(method, include_private)
120
114
  end
121
115
 
122
116
  protected
123
117
 
124
- def array_delegable?(method)
125
- Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
126
- end
127
-
128
118
  def method_missing(method, *args, &block)
129
119
  if @klass.respond_to?(method)
130
120
  scoping { @klass.public_send(method, *args, &block) }
131
- elsif array_delegable?(method)
132
- to_a.public_send(method, *args, &block)
133
121
  elsif arel.respond_to?(method)
134
122
  arel.public_send(method, *args, &block)
135
123
  else
@@ -1,4 +1,3 @@
1
- require 'active_support/deprecation'
2
1
  require 'active_support/core_ext/string/filters'
3
2
 
4
3
  module ActiveRecord
@@ -6,7 +5,7 @@ module ActiveRecord
6
5
  ONE_AS_ONE = '1 AS one'
7
6
 
8
7
  # 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
8
+ # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key
10
9
  # is an integer, find by id coerces its arguments using +to_i+.
11
10
  #
12
11
  # Person.find(1) # returns the object for ID = 1
@@ -17,11 +16,9 @@ module ActiveRecord
17
16
  # Person.find([1]) # returns an array for the object with ID = 1
18
17
  # Person.where("administrator = 1").order("created_on DESC").find(1)
19
18
  #
20
- # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found.
21
- #
22
19
  # 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.
20
+ # provide since database rows are unordered. You will need to provide an explicit QueryMethods#order
21
+ # option if you want the results to be sorted.
25
22
  #
26
23
  # ==== Find with lock
27
24
  #
@@ -37,7 +34,7 @@ module ActiveRecord
37
34
  # person.save!
38
35
  # end
39
36
  #
40
- # ==== Variations of +find+
37
+ # ==== Variations of #find
41
38
  #
42
39
  # Person.where(name: 'Spartacus', rating: 4)
43
40
  # # returns a chainable list (which can be empty).
@@ -45,13 +42,13 @@ module ActiveRecord
45
42
  # Person.find_by(name: 'Spartacus', rating: 4)
46
43
  # # returns the first item or nil.
47
44
  #
48
- # Person.where(name: 'Spartacus', rating: 4).first_or_initialize
45
+ # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
49
46
  # # returns the first item or returns a new instance (requires you call .save to persist against the database).
50
47
  #
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.
48
+ # Person.find_or_create_by(name: 'Spartacus', rating: 4)
49
+ # # returns the first item or creates it and returns it.
53
50
  #
54
- # ==== Alternatives for +find+
51
+ # ==== Alternatives for #find
55
52
  #
56
53
  # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none)
57
54
  # # returns a boolean indicating if any record with the given conditions exist.
@@ -60,16 +57,13 @@ module ActiveRecord
60
57
  # # returns a chainable list of instances with only the mentioned fields.
61
58
  #
62
59
  # Person.where(name: 'Spartacus', rating: 4).ids
63
- # # returns an Array of ids, available since Rails 3.2.1.
60
+ # # returns an Array of ids.
64
61
  #
65
62
  # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
66
- # # returns an Array of the required fields, available since Rails 3.1.
63
+ # # returns an Array of the required fields.
67
64
  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
65
+ return super if block_given?
66
+ find_with_ids(*args)
73
67
  end
74
68
 
75
69
  # Finds the first record matching the specified conditions. There
@@ -80,18 +74,19 @@ module ActiveRecord
80
74
  #
81
75
  # Post.find_by name: 'Spartacus', rating: 4
82
76
  # Post.find_by "published_at < ?", 2.weeks.ago
83
- def find_by(*args)
84
- where(*args).take
77
+ def find_by(arg, *args)
78
+ where(arg, *args).take
85
79
  rescue RangeError
86
80
  nil
87
81
  end
88
82
 
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!
83
+ # Like #find_by, except that if no record is found, raises
84
+ # an ActiveRecord::RecordNotFound error.
85
+ def find_by!(arg, *args)
86
+ where(arg, *args).take!
93
87
  rescue RangeError
94
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
88
+ raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
89
+ @klass.name)
95
90
  end
96
91
 
97
92
  # Gives a record (or N records if a parameter is supplied) without any implied
@@ -105,10 +100,10 @@ module ActiveRecord
105
100
  limit ? limit(limit).to_a : find_take
106
101
  end
107
102
 
108
- # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
109
- # is found. Note that <tt>take!</tt> accepts no arguments.
103
+ # Same as #take but raises ActiveRecord::RecordNotFound if no record
104
+ # is found. Note that #take! accepts no arguments.
110
105
  def take!
111
- take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
106
+ take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
112
107
  end
113
108
 
114
109
  # Find the first record (or first N records if a parameter is supplied).
@@ -122,14 +117,14 @@ module ActiveRecord
122
117
  #
123
118
  def first(limit = nil)
124
119
  if limit
125
- find_nth_with_limit(offset_index, limit)
120
+ find_nth_with_limit_and_offset(0, limit, offset: offset_index)
126
121
  else
127
- find_nth(0, offset_index)
122
+ find_nth 0
128
123
  end
129
124
  end
130
125
 
131
- # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
132
- # is found. Note that <tt>first!</tt> accepts no arguments.
126
+ # Same as #first but raises ActiveRecord::RecordNotFound if no record
127
+ # is found. Note that #first! accepts no arguments.
133
128
  def first!
134
129
  find_nth! 0
135
130
  end
@@ -150,21 +145,27 @@ module ActiveRecord
150
145
  #
151
146
  # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
152
147
  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
148
+ return find_last(limit) if loaded? || limit_value
149
+
150
+ result = limit(limit || 1)
151
+ result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
152
+ result = result.reverse_order!
153
+
154
+ limit ? result.reverse : result.first
155
+ rescue ActiveRecord::IrreversibleOrderError
156
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
157
+ Finding a last element by loading the relation when SQL ORDER
158
+ can not be reversed is deprecated.
159
+ Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
160
+ Please call `to_a.last` if you still want to load the relation.
161
+ WARNING
162
+ find_last(limit)
162
163
  end
163
164
 
164
- # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
165
- # is found. Note that <tt>last!</tt> accepts no arguments.
165
+ # Same as #last but raises ActiveRecord::RecordNotFound if no record
166
+ # is found. Note that #last! accepts no arguments.
166
167
  def last!
167
- last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
168
+ last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
168
169
  end
169
170
 
170
171
  # Find the second record.
@@ -174,10 +175,10 @@ module ActiveRecord
174
175
  # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
175
176
  # Person.where(["user_name = :u", { u: user_name }]).second
176
177
  def second
177
- find_nth(1, offset_index)
178
+ find_nth 1
178
179
  end
179
180
 
180
- # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
181
+ # Same as #second but raises ActiveRecord::RecordNotFound if no record
181
182
  # is found.
182
183
  def second!
183
184
  find_nth! 1
@@ -190,10 +191,10 @@ module ActiveRecord
190
191
  # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
191
192
  # Person.where(["user_name = :u", { u: user_name }]).third
192
193
  def third
193
- find_nth(2, offset_index)
194
+ find_nth 2
194
195
  end
195
196
 
196
- # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
197
+ # Same as #third but raises ActiveRecord::RecordNotFound if no record
197
198
  # is found.
198
199
  def third!
199
200
  find_nth! 2
@@ -206,10 +207,10 @@ module ActiveRecord
206
207
  # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
207
208
  # Person.where(["user_name = :u", { u: user_name }]).fourth
208
209
  def fourth
209
- find_nth(3, offset_index)
210
+ find_nth 3
210
211
  end
211
212
 
212
- # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
213
+ # Same as #fourth but raises ActiveRecord::RecordNotFound if no record
213
214
  # is found.
214
215
  def fourth!
215
216
  find_nth! 3
@@ -222,10 +223,10 @@ module ActiveRecord
222
223
  # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
223
224
  # Person.where(["user_name = :u", { u: user_name }]).fifth
224
225
  def fifth
225
- find_nth(4, offset_index)
226
+ find_nth 4
226
227
  end
227
228
 
228
- # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
229
+ # Same as #fifth but raises ActiveRecord::RecordNotFound if no record
229
230
  # is found.
230
231
  def fifth!
231
232
  find_nth! 4
@@ -238,17 +239,49 @@ module ActiveRecord
238
239
  # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
239
240
  # Person.where(["user_name = :u", { u: user_name }]).forty_two
240
241
  def forty_two
241
- find_nth(41, offset_index)
242
+ find_nth 41
242
243
  end
243
244
 
244
- # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
245
+ # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
245
246
  # is found.
246
247
  def forty_two!
247
248
  find_nth! 41
248
249
  end
249
250
 
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:
251
+ # Find the third-to-last record.
252
+ # If no order is defined it will order by primary key.
253
+ #
254
+ # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
255
+ # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
256
+ # Person.where(["user_name = :u", { u: user_name }]).third_to_last
257
+ def third_to_last
258
+ find_nth_from_last 3
259
+ end
260
+
261
+ # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
262
+ # is found.
263
+ def third_to_last!
264
+ find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
265
+ end
266
+
267
+ # Find the second-to-last record.
268
+ # If no order is defined it will order by primary key.
269
+ #
270
+ # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
271
+ # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
272
+ # Person.where(["user_name = :u", { u: user_name }]).second_to_last
273
+ def second_to_last
274
+ find_nth_from_last 2
275
+ end
276
+
277
+ # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
278
+ # is found.
279
+ def second_to_last!
280
+ find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
281
+ end
282
+
283
+ # Returns true if a record exists in the table that matches the +id+ or
284
+ # conditions given, or false otherwise. The argument can take six forms:
252
285
  #
253
286
  # * Integer - Finds the record with this primary key.
254
287
  # * String - Finds the record with a primary key corresponding to this
@@ -261,7 +294,7 @@ module ActiveRecord
261
294
  # * No args - Returns +false+ if the table is empty, +true+ otherwise.
262
295
  #
263
296
  # For more information about specifying conditions as a hash or array,
264
- # see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
297
+ # see the Conditions section in the introduction to ActiveRecord::Base.
265
298
  #
266
299
  # Note: You can't pass in a condition as a string (like <tt>name =
267
300
  # 'Jamie'</tt>), since it would be sanitized and then queried against
@@ -279,7 +312,7 @@ module ActiveRecord
279
312
  conditions = conditions.id
280
313
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
281
314
  You are passing an instance of ActiveRecord::Base to `exists?`.
282
- Please pass the id of the object by calling `.id`
315
+ Please pass the id of the object by calling `.id`.
283
316
  MSG
284
317
  end
285
318
 
@@ -299,29 +332,31 @@ module ActiveRecord
299
332
  end
300
333
  end
301
334
 
302
- connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false
335
+ connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
303
336
  end
304
337
 
305
338
  # This method is called whenever no records are found with either a single
306
- # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
339
+ # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
307
340
  #
308
341
  # The error message is different depending on whether a single id or
309
342
  # multiple ids are provided. If multiple ids are provided, then the number
310
343
  # of results obtained should be provided in the +result_size+ argument and
311
344
  # the expected number of results should be provided in the +expected_size+
312
345
  # argument.
313
- def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
314
- conditions = arel.where_sql
346
+ def raise_record_not_found_exception!(ids, result_size, expected_size, key = primary_key) #:nodoc:
347
+ conditions = arel.where_sql(@klass.arel_engine)
315
348
  conditions = " [#{conditions}]" if conditions
349
+ name = @klass.name
316
350
 
317
351
  if Array(ids).size == 1
318
- error = "Couldn't find #{@klass.name} with '#{primary_key}'=#{ids}#{conditions}"
352
+ error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
353
+ raise RecordNotFound.new(error, name, key, ids)
319
354
  else
320
- error = "Couldn't find all #{@klass.name.pluralize} with '#{primary_key}': "
355
+ error = "Couldn't find all #{name.pluralize} with '#{key}': "
321
356
  error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
322
- end
323
357
 
324
- raise RecordNotFound, error
358
+ raise RecordNotFound, error
359
+ end
325
360
  end
326
361
 
327
362
  private
@@ -353,7 +388,7 @@ module ActiveRecord
353
388
  []
354
389
  else
355
390
  arel = relation.arel
356
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
391
+ rows = connection.select_all(arel, 'SQL', relation.bound_attributes)
357
392
  join_dependency.instantiate(rows, aliases)
358
393
  end
359
394
  end
@@ -385,7 +420,7 @@ module ActiveRecord
385
420
  else
386
421
  if relation.limit_value
387
422
  limited_ids = limited_ids_for(relation)
388
- limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
423
+ limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
389
424
  end
390
425
  relation.except(:limit, :offset)
391
426
  end
@@ -398,12 +433,12 @@ module ActiveRecord
398
433
  relation = relation.except(:select).select(values).distinct!
399
434
  arel = relation.arel
400
435
 
401
- id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
436
+ id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
402
437
  id_rows.map {|row| row[primary_key]}
403
438
  end
404
439
 
405
440
  def using_limitable_reflections?(reflections)
406
- reflections.none? { |r| r.collection? }
441
+ reflections.none?(&:collection?)
407
442
  end
408
443
 
409
444
  protected
@@ -434,7 +469,7 @@ module ActiveRecord
434
469
  id = id.id
435
470
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
436
471
  You are passing an instance of ActiveRecord::Base to `find`.
437
- Please pass the id of the object by calling `.id`
472
+ Please pass the id of the object by calling `.id`.
438
473
  MSG
439
474
  end
440
475
 
@@ -447,6 +482,8 @@ module ActiveRecord
447
482
  end
448
483
 
449
484
  def find_some(ids)
485
+ return find_some_ordered(ids) unless order_values.present?
486
+
450
487
  result = where(primary_key => ids).to_a
451
488
 
452
489
  expected_size =
@@ -468,49 +505,96 @@ module ActiveRecord
468
505
  end
469
506
  end
470
507
 
508
+ def find_some_ordered(ids)
509
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
510
+
511
+ result = except(:limit, :offset).where(primary_key => ids).records
512
+
513
+ if result.size == ids.size
514
+ pk_type = @klass.type_for_attribute(primary_key)
515
+
516
+ records_by_id = result.index_by(&:id)
517
+ ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
518
+ else
519
+ raise_record_not_found_exception!(ids, result.size, ids.size)
520
+ end
521
+ end
522
+
471
523
  def find_take
472
524
  if loaded?
473
- @records.first
525
+ records.first
474
526
  else
475
- @take ||= limit(1).to_a.first
527
+ @take ||= limit(1).records.first
476
528
  end
477
529
  end
478
530
 
479
- def find_nth(index, offset)
531
+ def find_nth(index, offset = nil)
532
+ # TODO: once the offset argument is removed we rely on offset_index
533
+ # within find_nth_with_limit, rather than pass it in via
534
+ # find_nth_with_limit_and_offset
535
+ if offset
536
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
537
+ Passing an offset argument to find_nth is deprecated,
538
+ please use Relation#offset instead.
539
+ MSG
540
+ end
480
541
  if loaded?
481
- @records[index]
542
+ records[index]
482
543
  else
483
- offset += index
484
- @offsets[offset] ||= find_nth_with_limit(offset, 1).first
544
+ offset ||= offset_index
545
+ @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
485
546
  end
486
547
  end
487
548
 
488
549
  def find_nth!(index)
489
- find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
550
+ find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
490
551
  end
491
552
 
492
- def find_nth_with_limit(offset, limit)
553
+ def find_nth_with_limit(index, limit)
554
+ # TODO: once the offset argument is removed from find_nth,
555
+ # find_nth_with_limit_and_offset can be merged into this method
493
556
  relation = if order_values.empty? && primary_key
494
- order(arel_table[primary_key].asc)
557
+ order(arel_attribute(primary_key).asc)
495
558
  else
496
559
  self
497
560
  end
498
561
 
499
- relation = relation.offset(offset) unless offset.zero?
562
+ relation = relation.offset(index) unless index.zero?
500
563
  relation.limit(limit).to_a
501
564
  end
502
565
 
503
- def find_last
566
+ def find_nth_from_last(index)
504
567
  if loaded?
505
- @records.last
568
+ records[-index]
506
569
  else
507
- @last ||=
508
- if limit_value
509
- to_a.last
510
- else
511
- reverse_order.limit(1).to_a.first
512
- end
570
+ relation = if order_values.empty? && primary_key
571
+ order(arel_attribute(primary_key).asc)
572
+ else
573
+ self
574
+ end
575
+
576
+ relation.to_a[-index]
577
+ # TODO: can be made more performant on large result sets by
578
+ # for instance, last(index)[-index] (which would require
579
+ # refactoring the last(n) finder method to make test suite pass),
580
+ # or by using a combination of reverse_order, limit, and offset,
581
+ # e.g., reverse_order.offset(index-1).first
513
582
  end
514
583
  end
584
+
585
+ private
586
+
587
+ def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
588
+ if loaded?
589
+ records[index, limit]
590
+ else
591
+ index += offset
592
+ find_nth_with_limit(index, limit)
593
+ end
594
+ end
595
+
596
+ def find_last(limit)
597
+ limit ? records.last(limit) : records.last
598
+ end
515
599
  end
516
600
  end