activerecord 4.2.11.3 → 5.0.0.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 (246) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1281 -1204
  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/aggregations.rb +35 -24
  8. data/lib/active_record/association_relation.rb +3 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +11 -9
  11. data/lib/active_record/associations/association_scope.rb +73 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  13. data/lib/active_record/associations/builder/association.rb +28 -34
  14. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  15. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +11 -6
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  20. data/lib/active_record/associations/collection_association.rb +49 -41
  21. data/lib/active_record/associations/collection_proxy.rb +67 -27
  22. data/lib/active_record/associations/foreign_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_association.rb +20 -71
  24. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  25. data/lib/active_record/associations/has_one_association.rb +12 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
  27. data/lib/active_record/associations/join_dependency.rb +29 -19
  28. data/lib/active_record/associations/preloader/association.rb +46 -52
  29. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  30. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  31. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  32. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  33. data/lib/active_record/associations/preloader.rb +14 -4
  34. data/lib/active_record/associations/singular_association.rb +7 -1
  35. data/lib/active_record/associations/through_association.rb +11 -3
  36. data/lib/active_record/associations.rb +317 -209
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +68 -18
  39. data/lib/active_record/attribute_assignment.rb +19 -140
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  43. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  44. data/lib/active_record/attribute_methods/query.rb +2 -2
  45. data/lib/active_record/attribute_methods/read.rb +31 -59
  46. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  48. data/lib/active_record/attribute_methods/write.rb +13 -37
  49. data/lib/active_record/attribute_methods.rb +76 -47
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set/builder.rb +6 -4
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attributes.rb +199 -81
  54. data/lib/active_record/autosave_association.rb +49 -16
  55. data/lib/active_record/base.rb +32 -23
  56. data/lib/active_record/callbacks.rb +39 -43
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +20 -8
  59. data/lib/active_record/collection_cache_key.rb +40 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
  68. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
  73. data/lib/active_record/connection_adapters/column.rb +28 -43
  74. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  75. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  78. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  79. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  83. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +29 -166
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  87. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  100. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  102. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  105. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  106. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  107. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  108. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  109. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
  110. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  111. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  112. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  113. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  114. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  115. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  117. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +149 -192
  118. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  119. data/lib/active_record/connection_handling.rb +37 -14
  120. data/lib/active_record/core.rb +89 -107
  121. data/lib/active_record/counter_cache.rb +13 -24
  122. data/lib/active_record/dynamic_matchers.rb +1 -20
  123. data/lib/active_record/enum.rb +113 -76
  124. data/lib/active_record/errors.rb +87 -48
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +1 -1
  127. data/lib/active_record/fixture_set/file.rb +26 -5
  128. data/lib/active_record/fixtures.rb +76 -40
  129. data/lib/active_record/gem_version.rb +4 -4
  130. data/lib/active_record/inheritance.rb +32 -40
  131. data/lib/active_record/integration.rb +4 -4
  132. data/lib/active_record/internal_metadata.rb +56 -0
  133. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  134. data/lib/active_record/locale/en.yml +3 -2
  135. data/lib/active_record/locking/optimistic.rb +15 -15
  136. data/lib/active_record/locking/pessimistic.rb +1 -1
  137. data/lib/active_record/log_subscriber.rb +43 -21
  138. data/lib/active_record/migration/command_recorder.rb +59 -18
  139. data/lib/active_record/migration/compatibility.rb +126 -0
  140. data/lib/active_record/migration.rb +363 -133
  141. data/lib/active_record/model_schema.rb +129 -41
  142. data/lib/active_record/nested_attributes.rb +58 -29
  143. data/lib/active_record/null_relation.rb +16 -8
  144. data/lib/active_record/persistence.rb +121 -80
  145. data/lib/active_record/query_cache.rb +15 -18
  146. data/lib/active_record/querying.rb +10 -9
  147. data/lib/active_record/railtie.rb +23 -16
  148. data/lib/active_record/railties/controller_runtime.rb +1 -1
  149. data/lib/active_record/railties/databases.rake +69 -46
  150. data/lib/active_record/readonly_attributes.rb +1 -1
  151. data/lib/active_record/reflection.rb +282 -115
  152. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  153. data/lib/active_record/relation/batches.rb +139 -34
  154. data/lib/active_record/relation/calculations.rb +79 -108
  155. data/lib/active_record/relation/delegation.rb +7 -20
  156. data/lib/active_record/relation/finder_methods.rb +163 -81
  157. data/lib/active_record/relation/from_clause.rb +32 -0
  158. data/lib/active_record/relation/merger.rb +16 -42
  159. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  160. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  161. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  162. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  163. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  165. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  166. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  167. data/lib/active_record/relation/predicate_builder.rb +120 -107
  168. data/lib/active_record/relation/query_attribute.rb +19 -0
  169. data/lib/active_record/relation/query_methods.rb +308 -244
  170. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  171. data/lib/active_record/relation/spawn_methods.rb +4 -7
  172. data/lib/active_record/relation/where_clause.rb +174 -0
  173. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  174. data/lib/active_record/relation.rb +176 -116
  175. data/lib/active_record/result.rb +4 -3
  176. data/lib/active_record/runtime_registry.rb +1 -1
  177. data/lib/active_record/sanitization.rb +95 -66
  178. data/lib/active_record/schema.rb +26 -22
  179. data/lib/active_record/schema_dumper.rb +62 -38
  180. data/lib/active_record/schema_migration.rb +11 -14
  181. data/lib/active_record/scoping/default.rb +23 -9
  182. data/lib/active_record/scoping/named.rb +49 -28
  183. data/lib/active_record/scoping.rb +32 -15
  184. data/lib/active_record/secure_token.rb +38 -0
  185. data/lib/active_record/serialization.rb +2 -4
  186. data/lib/active_record/statement_cache.rb +16 -14
  187. data/lib/active_record/store.rb +8 -3
  188. data/lib/active_record/suppressor.rb +58 -0
  189. data/lib/active_record/table_metadata.rb +68 -0
  190. data/lib/active_record/tasks/database_tasks.rb +57 -43
  191. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  192. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  193. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  194. data/lib/active_record/timestamp.rb +20 -9
  195. data/lib/active_record/touch_later.rb +58 -0
  196. data/lib/active_record/transactions.rb +138 -56
  197. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  198. data/lib/active_record/type/date.rb +2 -45
  199. data/lib/active_record/type/date_time.rb +2 -49
  200. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  201. data/lib/active_record/type/internal/timezone.rb +15 -0
  202. data/lib/active_record/type/serialized.rb +15 -14
  203. data/lib/active_record/type/time.rb +10 -16
  204. data/lib/active_record/type/type_map.rb +4 -4
  205. data/lib/active_record/type.rb +66 -17
  206. data/lib/active_record/type_caster/connection.rb +29 -0
  207. data/lib/active_record/type_caster/map.rb +19 -0
  208. data/lib/active_record/type_caster.rb +7 -0
  209. data/lib/active_record/validations/absence.rb +23 -0
  210. data/lib/active_record/validations/associated.rb +10 -3
  211. data/lib/active_record/validations/length.rb +24 -0
  212. data/lib/active_record/validations/presence.rb +11 -12
  213. data/lib/active_record/validations/uniqueness.rb +30 -29
  214. data/lib/active_record/validations.rb +33 -32
  215. data/lib/active_record.rb +8 -4
  216. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  217. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  218. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  219. data/lib/rails/generators/active_record/migration.rb +7 -0
  220. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  221. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  222. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  223. metadata +60 -34
  224. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  225. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  226. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  227. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  228. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  229. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  231. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  232. data/lib/active_record/type/big_integer.rb +0 -13
  233. data/lib/active_record/type/binary.rb +0 -50
  234. data/lib/active_record/type/boolean.rb +0 -31
  235. data/lib/active_record/type/decimal.rb +0 -64
  236. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  237. data/lib/active_record/type/decorator.rb +0 -14
  238. data/lib/active_record/type/float.rb +0 -19
  239. data/lib/active_record/type/integer.rb +0 -59
  240. data/lib/active_record/type/mutable.rb +0 -16
  241. data/lib/active_record/type/numeric.rb +0 -36
  242. data/lib/active_record/type/string.rb +0 -40
  243. data/lib/active_record/type/text.rb +0 -11
  244. data/lib/active_record/type/time_value.rb +0 -38
  245. data/lib/active_record/type/unsigned_integer.rb +0 -15
  246. 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,9 @@ 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
+ :shuffle, :split, to: :records
47
41
 
48
42
  delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
49
43
  :connection, :columns_hash, :to => :klass
@@ -115,21 +109,14 @@ module ActiveRecord
115
109
 
116
110
  def respond_to?(method, include_private = false)
117
111
  super || @klass.respond_to?(method, include_private) ||
118
- array_delegable?(method) ||
119
112
  arel.respond_to?(method, include_private)
120
113
  end
121
114
 
122
115
  protected
123
116
 
124
- def array_delegable?(method)
125
- Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method)
126
- end
127
-
128
117
  def method_missing(method, *args, &block)
129
118
  if @klass.respond_to?(method)
130
119
  scoping { @klass.public_send(method, *args, &block) }
131
- elsif array_delegable?(method)
132
- to_a.public_send(method, *args, &block)
133
120
  elsif arel.respond_to?(method)
134
121
  arel.public_send(method, *args, &block)
135
122
  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,10 +16,8 @@ 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>
20
+ # provide since database rows are unordered. You'd need to provide an explicit QueryMethods#order
24
21
  # option if you want the results are sorted.
25
22
  #
26
23
  # ==== Find with lock
@@ -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,11 +332,11 @@ 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
@@ -311,7 +344,7 @@ module ActiveRecord
311
344
  # the expected number of results should be provided in the +expected_size+
312
345
  # argument.
313
346
  def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
314
- conditions = arel.where_sql
347
+ conditions = arel.where_sql(@klass.arel_engine)
315
348
  conditions = " [#{conditions}]" if conditions
316
349
 
317
350
  if Array(ids).size == 1
@@ -353,7 +386,7 @@ module ActiveRecord
353
386
  []
354
387
  else
355
388
  arel = relation.arel
356
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
389
+ rows = connection.select_all(arel, 'SQL', relation.bound_attributes)
357
390
  join_dependency.instantiate(rows, aliases)
358
391
  end
359
392
  end
@@ -385,7 +418,7 @@ module ActiveRecord
385
418
  else
386
419
  if relation.limit_value
387
420
  limited_ids = limited_ids_for(relation)
388
- limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids))
421
+ limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
389
422
  end
390
423
  relation.except(:limit, :offset)
391
424
  end
@@ -398,12 +431,12 @@ module ActiveRecord
398
431
  relation = relation.except(:select).select(values).distinct!
399
432
  arel = relation.arel
400
433
 
401
- id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
434
+ id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes)
402
435
  id_rows.map {|row| row[primary_key]}
403
436
  end
404
437
 
405
438
  def using_limitable_reflections?(reflections)
406
- reflections.none? { |r| r.collection? }
439
+ reflections.none?(&:collection?)
407
440
  end
408
441
 
409
442
  protected
@@ -434,7 +467,7 @@ module ActiveRecord
434
467
  id = id.id
435
468
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
436
469
  You are passing an instance of ActiveRecord::Base to `find`.
437
- Please pass the id of the object by calling `.id`
470
+ Please pass the id of the object by calling `.id`.
438
471
  MSG
439
472
  end
440
473
 
@@ -447,6 +480,8 @@ module ActiveRecord
447
480
  end
448
481
 
449
482
  def find_some(ids)
483
+ return find_some_ordered(ids) unless order_values.present?
484
+
450
485
  result = where(primary_key => ids).to_a
451
486
 
452
487
  expected_size =
@@ -468,49 +503,96 @@ module ActiveRecord
468
503
  end
469
504
  end
470
505
 
506
+ def find_some_ordered(ids)
507
+ ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
508
+
509
+ result = except(:limit, :offset).where(primary_key => ids).records
510
+
511
+ if result.size == ids.size
512
+ pk_type = @klass.type_for_attribute(primary_key)
513
+
514
+ records_by_id = result.index_by(&:id)
515
+ ids.map { |id| records_by_id.fetch(pk_type.cast(id)) }
516
+ else
517
+ raise_record_not_found_exception!(ids, result.size, ids.size)
518
+ end
519
+ end
520
+
471
521
  def find_take
472
522
  if loaded?
473
523
  @records.first
474
524
  else
475
- @take ||= limit(1).to_a.first
525
+ @take ||= limit(1).records.first
476
526
  end
477
527
  end
478
528
 
479
- def find_nth(index, offset)
529
+ def find_nth(index, offset = nil)
530
+ # TODO: once the offset argument is removed we rely on offset_index
531
+ # within find_nth_with_limit, rather than pass it in via
532
+ # find_nth_with_limit_and_offset
533
+ if offset
534
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
535
+ Passing an offset argument to find_nth is deprecated,
536
+ please use Relation#offset instead.
537
+ MSG
538
+ end
480
539
  if loaded?
481
540
  @records[index]
482
541
  else
483
- offset += index
484
- @offsets[offset] ||= find_nth_with_limit(offset, 1).first
542
+ offset ||= offset_index
543
+ @offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
485
544
  end
486
545
  end
487
546
 
488
547
  def find_nth!(index)
489
- find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]")
548
+ find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
490
549
  end
491
550
 
492
- def find_nth_with_limit(offset, limit)
551
+ def find_nth_with_limit(index, limit)
552
+ # TODO: once the offset argument is removed from find_nth,
553
+ # find_nth_with_limit_and_offset can be merged into this method
493
554
  relation = if order_values.empty? && primary_key
494
- order(arel_table[primary_key].asc)
555
+ order(arel_attribute(primary_key).asc)
495
556
  else
496
557
  self
497
558
  end
498
559
 
499
- relation = relation.offset(offset) unless offset.zero?
560
+ relation = relation.offset(index) unless index.zero?
500
561
  relation.limit(limit).to_a
501
562
  end
502
563
 
503
- def find_last
564
+ def find_nth_from_last(index)
504
565
  if loaded?
505
- @records.last
566
+ @records[-index]
506
567
  else
507
- @last ||=
508
- if limit_value
509
- to_a.last
510
- else
511
- reverse_order.limit(1).to_a.first
512
- end
568
+ relation = if order_values.empty? && primary_key
569
+ order(arel_attribute(primary_key).asc)
570
+ else
571
+ self
572
+ end
573
+
574
+ relation.to_a[-index]
575
+ # TODO: can be made more performant on large result sets by
576
+ # for instance, last(index)[-index] (which would require
577
+ # refactoring the last(n) finder method to make test suite pass),
578
+ # or by using a combination of reverse_order, limit, and offset,
579
+ # e.g., reverse_order.offset(index-1).first
513
580
  end
514
581
  end
582
+
583
+ private
584
+
585
+ def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
586
+ if loaded?
587
+ @records[index, limit]
588
+ else
589
+ index += offset
590
+ find_nth_with_limit(index, limit)
591
+ end
592
+ end
593
+
594
+ def find_last(limit)
595
+ limit ? records.last(limit) : records.last
596
+ end
515
597
  end
516
598
  end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ class FromClause # :nodoc:
4
+ attr_reader :value, :name
5
+
6
+ def initialize(value, name)
7
+ @value = value
8
+ @name = name
9
+ end
10
+
11
+ def binds
12
+ if value.is_a?(Relation)
13
+ value.bound_attributes
14
+ else
15
+ []
16
+ end
17
+ end
18
+
19
+ def merge(other)
20
+ self
21
+ end
22
+
23
+ def empty?
24
+ value.nil?
25
+ end
26
+
27
+ def self.empty
28
+ @empty ||= new(nil, nil)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,4 @@
1
1
  require 'active_support/core_ext/hash/keys'
2
- require "set"
3
2
 
4
3
  module ActiveRecord
5
4
  class Relation
@@ -22,7 +21,7 @@ module ActiveRecord
22
21
  # build a relation to merge in rather than directly merging
23
22
  # the values.
24
23
  def other
25
- other = Relation.create(relation.klass, relation.table)
24
+ other = Relation.create(relation.klass, relation.table, relation.predicate_builder)
26
25
  hash.each { |k, v|
27
26
  if k == :joins
28
27
  if Hash === v
@@ -49,10 +48,9 @@ module ActiveRecord
49
48
  @other = other
50
49
  end
51
50
 
52
- NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
53
- Relation::MULTI_VALUE_METHODS -
54
- [:includes, :preload, :joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
55
-
51
+ NORMAL_VALUES = Relation::VALUE_METHODS -
52
+ Relation::CLAUSE_METHODS -
53
+ [:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
56
54
 
57
55
  def normal_values
58
56
  NORMAL_VALUES
@@ -76,6 +74,7 @@ module ActiveRecord
76
74
 
77
75
  merge_multi_values
78
76
  merge_single_values
77
+ merge_clauses
79
78
  merge_preloads
80
79
  merge_joins
81
80
 
@@ -130,20 +129,6 @@ module ActiveRecord
130
129
  end
131
130
 
132
131
  def merge_multi_values
133
- lhs_wheres = relation.where_values
134
- rhs_wheres = other.where_values
135
-
136
- lhs_binds = relation.bind_values
137
- rhs_binds = other.bind_values
138
-
139
- removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
140
-
141
- where_values = kept + rhs_wheres
142
- bind_values = filter_binds(lhs_binds, removed) + rhs_binds
143
-
144
- relation.where_values = where_values
145
- relation.bind_values = bind_values
146
-
147
132
  if other.reordering_value
148
133
  # override any order specified in the original relation
149
134
  relation.reorder! other.order_values
@@ -156,36 +141,25 @@ module ActiveRecord
156
141
  end
157
142
 
158
143
  def merge_single_values
159
- relation.from_value = other.from_value unless relation.from_value
160
- relation.lock_value = other.lock_value unless relation.lock_value
144
+ if relation.from_clause.empty?
145
+ relation.from_clause = other.from_clause
146
+ end
147
+ relation.lock_value ||= other.lock_value
161
148
 
162
149
  unless other.create_with_value.blank?
163
150
  relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
164
151
  end
165
152
  end
166
153
 
167
- def filter_binds(lhs_binds, removed_wheres)
168
- return lhs_binds if removed_wheres.empty?
169
-
170
- set = Set.new removed_wheres.map { |x| x.left.name.to_s }
171
- lhs_binds.dup.delete_if { |col,_| set.include? col.name }
154
+ CLAUSE_METHOD_NAMES = CLAUSE_METHODS.map do |name|
155
+ ["#{name}_clause", "#{name}_clause="]
172
156
  end
173
157
 
174
- # Remove equalities from the existing relation with a LHS which is
175
- # present in the relation being merged in.
176
- # returns [things_to_remove, things_to_keep]
177
- def partition_overwrites(lhs_wheres, rhs_wheres)
178
- if lhs_wheres.empty? || rhs_wheres.empty?
179
- return [[], lhs_wheres]
180
- end
181
-
182
- nodes = rhs_wheres.find_all do |w|
183
- w.respond_to?(:operator) && w.operator == :==
184
- end
185
- seen = Set.new(nodes) { |node| node.left }
186
-
187
- lhs_wheres.partition do |w|
188
- w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
158
+ def merge_clauses
159
+ CLAUSE_METHOD_NAMES.each do |(reader, writer)|
160
+ clause = relation.send(reader)
161
+ other_clause = other.send(reader)
162
+ relation.send(writer, clause.merge(other_clause))
189
163
  end
190
164
  end
191
165
  end