activerecord 6.0.6.1 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1152 -779
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_record/aggregations.rb +5 -5
  6. data/lib/active_record/association_relation.rb +30 -12
  7. data/lib/active_record/associations/alias_tracker.rb +19 -15
  8. data/lib/active_record/associations/association.rb +49 -26
  9. data/lib/active_record/associations/association_scope.rb +18 -20
  10. data/lib/active_record/associations/belongs_to_association.rb +23 -10
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  12. data/lib/active_record/associations/builder/association.rb +32 -5
  13. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  16. data/lib/active_record/associations/builder/has_many.rb +6 -2
  17. data/lib/active_record/associations/builder/has_one.rb +11 -14
  18. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  19. data/lib/active_record/associations/collection_association.rb +32 -18
  20. data/lib/active_record/associations/collection_proxy.rb +12 -5
  21. data/lib/active_record/associations/foreign_association.rb +13 -0
  22. data/lib/active_record/associations/has_many_association.rb +24 -2
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  24. data/lib/active_record/associations/has_one_association.rb +15 -1
  25. data/lib/active_record/associations/join_dependency/join_association.rb +37 -21
  26. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +63 -49
  28. data/lib/active_record/associations/preloader/association.rb +14 -8
  29. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  30. data/lib/active_record/associations/preloader.rb +5 -3
  31. data/lib/active_record/associations/singular_association.rb +1 -1
  32. data/lib/active_record/associations.rb +118 -11
  33. data/lib/active_record/attribute_assignment.rb +10 -8
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  35. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  36. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  37. data/lib/active_record/attribute_methods/query.rb +3 -6
  38. data/lib/active_record/attribute_methods/read.rb +8 -11
  39. data/lib/active_record/attribute_methods/serialization.rb +11 -5
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  41. data/lib/active_record/attribute_methods/write.rb +12 -20
  42. data/lib/active_record/attribute_methods.rb +64 -54
  43. data/lib/active_record/attributes.rb +33 -8
  44. data/lib/active_record/autosave_association.rb +47 -30
  45. data/lib/active_record/base.rb +2 -14
  46. data/lib/active_record/callbacks.rb +152 -22
  47. data/lib/active_record/coders/yaml_column.rb +1 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +185 -134
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -23
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +114 -26
  56. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +92 -33
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +52 -76
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
  61. data/lib/active_record/connection_adapters/column.rb +15 -1
  62. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  63. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +24 -24
  65. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
  67. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
  68. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  69. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  70. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -4
  71. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  73. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  74. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  75. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +14 -53
  77. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  78. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  80. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  86. data/lib/active_record/connection_adapters/postgresql/quoting.rb +30 -4
  87. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  89. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  90. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  91. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -64
  92. data/lib/active_record/connection_adapters/schema_cache.rb +130 -15
  93. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  94. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +32 -5
  95. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  96. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  97. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +36 -3
  98. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
  99. data/lib/active_record/connection_adapters.rb +52 -0
  100. data/lib/active_record/connection_handling.rb +218 -71
  101. data/lib/active_record/core.rb +264 -63
  102. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
  103. data/lib/active_record/database_configurations/database_config.rb +52 -9
  104. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  105. data/lib/active_record/database_configurations/url_config.rb +15 -40
  106. data/lib/active_record/database_configurations.rb +125 -85
  107. data/lib/active_record/delegated_type.rb +209 -0
  108. data/lib/active_record/destroy_association_async_job.rb +36 -0
  109. data/lib/active_record/enum.rb +69 -34
  110. data/lib/active_record/errors.rb +47 -12
  111. data/lib/active_record/explain.rb +9 -4
  112. data/lib/active_record/explain_subscriber.rb +1 -1
  113. data/lib/active_record/fixture_set/file.rb +10 -17
  114. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  115. data/lib/active_record/fixture_set/render_context.rb +1 -1
  116. data/lib/active_record/fixture_set/table_row.rb +2 -2
  117. data/lib/active_record/fixtures.rb +58 -9
  118. data/lib/active_record/gem_version.rb +3 -3
  119. data/lib/active_record/inheritance.rb +40 -18
  120. data/lib/active_record/insert_all.rb +38 -5
  121. data/lib/active_record/integration.rb +3 -5
  122. data/lib/active_record/internal_metadata.rb +18 -7
  123. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  124. data/lib/active_record/locking/optimistic.rb +24 -17
  125. data/lib/active_record/locking/pessimistic.rb +6 -2
  126. data/lib/active_record/log_subscriber.rb +27 -8
  127. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  128. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  129. data/lib/active_record/middleware/database_selector.rb +4 -1
  130. data/lib/active_record/migration/command_recorder.rb +47 -27
  131. data/lib/active_record/migration/compatibility.rb +72 -18
  132. data/lib/active_record/migration.rb +114 -84
  133. data/lib/active_record/model_schema.rb +89 -14
  134. data/lib/active_record/nested_attributes.rb +2 -3
  135. data/lib/active_record/no_touching.rb +1 -1
  136. data/lib/active_record/persistence.rb +50 -45
  137. data/lib/active_record/query_cache.rb +15 -5
  138. data/lib/active_record/querying.rb +11 -6
  139. data/lib/active_record/railtie.rb +64 -44
  140. data/lib/active_record/railties/console_sandbox.rb +2 -4
  141. data/lib/active_record/railties/databases.rake +279 -101
  142. data/lib/active_record/readonly_attributes.rb +4 -0
  143. data/lib/active_record/reflection.rb +60 -44
  144. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  145. data/lib/active_record/relation/batches.rb +38 -31
  146. data/lib/active_record/relation/calculations.rb +104 -43
  147. data/lib/active_record/relation/finder_methods.rb +44 -14
  148. data/lib/active_record/relation/from_clause.rb +1 -1
  149. data/lib/active_record/relation/merger.rb +20 -23
  150. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  151. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  152. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  153. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  154. data/lib/active_record/relation/predicate_builder.rb +61 -38
  155. data/lib/active_record/relation/query_methods.rb +322 -196
  156. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  157. data/lib/active_record/relation/spawn_methods.rb +8 -7
  158. data/lib/active_record/relation/where_clause.rb +111 -61
  159. data/lib/active_record/relation.rb +100 -81
  160. data/lib/active_record/result.rb +41 -33
  161. data/lib/active_record/runtime_registry.rb +2 -2
  162. data/lib/active_record/sanitization.rb +6 -17
  163. data/lib/active_record/schema_dumper.rb +34 -4
  164. data/lib/active_record/schema_migration.rb +2 -8
  165. data/lib/active_record/scoping/default.rb +1 -3
  166. data/lib/active_record/scoping/named.rb +1 -17
  167. data/lib/active_record/secure_token.rb +16 -8
  168. data/lib/active_record/serialization.rb +5 -3
  169. data/lib/active_record/signed_id.rb +116 -0
  170. data/lib/active_record/statement_cache.rb +20 -4
  171. data/lib/active_record/store.rb +8 -3
  172. data/lib/active_record/suppressor.rb +2 -2
  173. data/lib/active_record/table_metadata.rb +42 -51
  174. data/lib/active_record/tasks/database_tasks.rb +140 -113
  175. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  176. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  177. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  178. data/lib/active_record/test_databases.rb +5 -4
  179. data/lib/active_record/test_fixtures.rb +79 -31
  180. data/lib/active_record/timestamp.rb +4 -6
  181. data/lib/active_record/touch_later.rb +21 -21
  182. data/lib/active_record/transactions.rb +19 -66
  183. data/lib/active_record/type/serialized.rb +6 -2
  184. data/lib/active_record/type.rb +8 -1
  185. data/lib/active_record/type_caster/connection.rb +0 -1
  186. data/lib/active_record/type_caster/map.rb +8 -5
  187. data/lib/active_record/validations/associated.rb +1 -1
  188. data/lib/active_record/validations/numericality.rb +35 -0
  189. data/lib/active_record/validations/uniqueness.rb +24 -4
  190. data/lib/active_record/validations.rb +1 -0
  191. data/lib/active_record.rb +7 -14
  192. data/lib/arel/attributes/attribute.rb +4 -0
  193. data/lib/arel/collectors/bind.rb +5 -0
  194. data/lib/arel/collectors/composite.rb +8 -0
  195. data/lib/arel/collectors/sql_string.rb +7 -0
  196. data/lib/arel/collectors/substitute_binds.rb +7 -0
  197. data/lib/arel/nodes/binary.rb +82 -8
  198. data/lib/arel/nodes/bind_param.rb +8 -0
  199. data/lib/arel/nodes/casted.rb +21 -9
  200. data/lib/arel/nodes/equality.rb +6 -9
  201. data/lib/arel/nodes/grouping.rb +3 -0
  202. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  203. data/lib/arel/nodes/in.rb +8 -1
  204. data/lib/arel/nodes/infix_operation.rb +13 -1
  205. data/lib/arel/nodes/join_source.rb +1 -1
  206. data/lib/arel/nodes/node.rb +7 -6
  207. data/lib/arel/nodes/ordering.rb +27 -0
  208. data/lib/arel/nodes/sql_literal.rb +3 -0
  209. data/lib/arel/nodes/table_alias.rb +7 -3
  210. data/lib/arel/nodes/unary.rb +0 -1
  211. data/lib/arel/nodes.rb +3 -1
  212. data/lib/arel/predications.rb +12 -18
  213. data/lib/arel/select_manager.rb +1 -2
  214. data/lib/arel/table.rb +13 -5
  215. data/lib/arel/visitors/dot.rb +14 -2
  216. data/lib/arel/visitors/mysql.rb +11 -1
  217. data/lib/arel/visitors/postgresql.rb +15 -4
  218. data/lib/arel/visitors/to_sql.rb +89 -78
  219. data/lib/arel/visitors.rb +0 -7
  220. data/lib/arel.rb +5 -13
  221. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  222. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  223. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  224. data/lib/rails/generators/active_record/migration.rb +6 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  226. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  227. metadata +25 -26
  228. data/lib/active_record/advisory_lock_base.rb +0 -18
  229. data/lib/active_record/attribute_decorators.rb +0 -88
  230. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  231. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  232. data/lib/active_record/define_callbacks.rb +0 -22
  233. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  234. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  235. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  236. data/lib/arel/attributes.rb +0 -22
  237. data/lib/arel/visitors/depth_first.rb +0 -203
  238. data/lib/arel/visitors/ibm_db.rb +0 -34
  239. data/lib/arel/visitors/informix.rb +0 -62
  240. data/lib/arel/visitors/mssql.rb +0 -156
  241. data/lib/arel/visitors/oracle.rb +0 -158
  242. data/lib/arel/visitors/oracle12.rb +0 -65
  243. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -3,8 +3,8 @@
3
3
  require "active_record/relation/from_clause"
4
4
  require "active_record/relation/query_attribute"
5
5
  require "active_record/relation/where_clause"
6
- require "active_record/relation/where_clause_factory"
7
6
  require "active_model/forbidden_attributes_protection"
7
+ require "active_support/core_ext/array/wrap"
8
8
 
9
9
  module ActiveRecord
10
10
  module QueryMethods
@@ -15,8 +15,6 @@ module ActiveRecord
15
15
  # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
16
16
  # In this case, #where must be chained with #not to return a new relation.
17
17
  class WhereChain
18
- include ActiveModel::ForbiddenAttributesProtection
19
-
20
18
  def initialize(scope)
21
19
  @scope = scope
22
20
  end
@@ -41,64 +39,70 @@ module ActiveRecord
41
39
  #
42
40
  # User.where.not(name: %w(Ko1 Nobu))
43
41
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
42
+ #
43
+ # User.where.not(name: "Jon", role: "admin")
44
+ # # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
44
45
  def not(opts, *rest)
45
- opts = sanitize_forbidden_attributes(opts)
46
+ where_clause = @scope.send(:build_where_clause, opts, rest)
46
47
 
47
- where_clause = @scope.send(:where_clause_factory).build(opts, rest)
48
-
49
- @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
50
-
51
- if not_behaves_as_nor?(opts)
52
- ActiveSupport::Deprecation.warn(<<~MSG.squish)
53
- NOT conditions will no longer behave as NOR in Rails 6.1.
54
- To continue using NOR conditions, NOT each condition individually
55
- (`#{
56
- opts.flat_map { |key, value|
57
- if value.is_a?(Hash) && value.size > 1
58
- value.map { |k, v| ".where.not(#{key.inspect} => { #{k.inspect} => ... })" }
59
- else
60
- ".where.not(#{key.inspect} => ...)"
61
- end
62
- }.join
63
- }`).
64
- MSG
65
- @scope.where_clause += where_clause.invert(:nor)
66
- else
67
- @scope.where_clause += where_clause.invert
68
- end
48
+ @scope.where_clause += where_clause.invert
69
49
 
70
50
  @scope
71
51
  end
72
52
 
73
- private
74
- def not_behaves_as_nor?(opts)
75
- return false unless opts.is_a?(Hash)
76
-
77
- opts.any? { |k, v| v.is_a?(Hash) && v.size > 1 } ||
78
- opts.size > 1
53
+ # Returns a new relation with left outer joins and where clause to identify
54
+ # missing relations.
55
+ #
56
+ # For example, posts that are missing a related author:
57
+ #
58
+ # Post.where.missing(:author)
59
+ # # SELECT "posts".* FROM "posts"
60
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
61
+ # # WHERE "authors"."id" IS NULL
62
+ #
63
+ # Additionally, multiple relations can be combined. This will return posts
64
+ # that are missing both an author and any comments:
65
+ #
66
+ # Post.where.missing(:author, :comments)
67
+ # # SELECT "posts".* FROM "posts"
68
+ # # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
69
+ # # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
70
+ # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
71
+ def missing(*args)
72
+ args.each do |arg|
73
+ reflection = @scope.klass._reflect_on_association(arg)
74
+ opts = { reflection.table_name => { reflection.association_primary_key => nil } }
75
+ @scope.left_outer_joins!(arg)
76
+ @scope.where!(opts)
79
77
  end
78
+
79
+ @scope
80
+ end
80
81
  end
81
82
 
82
83
  FROZEN_EMPTY_ARRAY = [].freeze
83
84
  FROZEN_EMPTY_HASH = {}.freeze
84
85
 
85
86
  Relation::VALUE_METHODS.each do |name|
86
- method_name = \
87
+ method_name, default =
87
88
  case name
88
- when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
89
- when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
90
- when *Relation::CLAUSE_METHODS then "#{name}_clause"
89
+ when *Relation::MULTI_VALUE_METHODS
90
+ ["#{name}_values", "FROZEN_EMPTY_ARRAY"]
91
+ when *Relation::SINGLE_VALUE_METHODS
92
+ ["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
93
+ when *Relation::CLAUSE_METHODS
94
+ ["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
91
95
  end
96
+
92
97
  class_eval <<-CODE, __FILE__, __LINE__ + 1
93
- def #{method_name} # def includes_values
94
- default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes]
95
- @values.fetch(:#{name}, default) # @values.fetch(:includes, default)
96
- end # end
97
-
98
- def #{method_name}=(value) # def includes_values=(value)
99
- assert_mutability! # assert_mutability!
100
- @values[:#{name}] = value # @values[:includes] = value
101
- end # end
98
+ def #{method_name} # def includes_values
99
+ @values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
100
+ end # end
101
+
102
+ def #{method_name}=(value) # def includes_values=(value)
103
+ assert_mutability! # assert_mutability!
104
+ @values[:#{name}] = value # @values[:includes] = value
105
+ end # end
102
106
  CODE
103
107
  end
104
108
 
@@ -149,9 +153,6 @@ module ActiveRecord
149
153
  end
150
154
 
151
155
  def includes!(*args) # :nodoc:
152
- args.reject!(&:blank?)
153
- args.flatten!
154
-
155
156
  self.includes_values |= args
156
157
  self
157
158
  end
@@ -215,9 +216,6 @@ module ActiveRecord
215
216
  end
216
217
 
217
218
  def references!(*table_names) # :nodoc:
218
- table_names.flatten!
219
- table_names.map!(&:to_s)
220
-
221
219
  self.references_values |= table_names
222
220
  self
223
221
  end
@@ -271,14 +269,12 @@ module ActiveRecord
271
269
  return super()
272
270
  end
273
271
 
274
- raise ArgumentError, "Call `select' with at least one field" if fields.empty?
272
+ check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
275
273
  spawn._select!(*fields)
276
274
  end
277
275
 
278
276
  def _select!(*fields) # :nodoc:
279
- fields.reject!(&:blank?)
280
- fields.flatten!
281
- self.select_values += fields
277
+ self.select_values |= fields
282
278
  self
283
279
  end
284
280
 
@@ -329,8 +325,6 @@ module ActiveRecord
329
325
  end
330
326
 
331
327
  def group!(*args) # :nodoc:
332
- args.flatten!
333
-
334
328
  self.group_values += args
335
329
  self
336
330
  end
@@ -355,15 +349,16 @@ module ActiveRecord
355
349
  # User.order('name DESC, email')
356
350
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
357
351
  def order(*args)
358
- check_if_method_has_arguments!(:order, args)
352
+ check_if_method_has_arguments!(:order, args) do
353
+ sanitize_order_arguments(args)
354
+ end
359
355
  spawn.order!(*args)
360
356
  end
361
357
 
362
358
  # Same as #order but operates on relation in-place instead of copying.
363
359
  def order!(*args) # :nodoc:
364
- preprocess_order_args(args)
365
-
366
- self.order_values += args
360
+ preprocess_order_args(args) unless args.empty?
361
+ self.order_values |= args
367
362
  self
368
363
  end
369
364
 
@@ -377,14 +372,16 @@ module ActiveRecord
377
372
  #
378
373
  # generates a query with 'ORDER BY id ASC, name ASC'.
379
374
  def reorder(*args)
380
- check_if_method_has_arguments!(:reorder, args)
375
+ check_if_method_has_arguments!(:reorder, args) do
376
+ sanitize_order_arguments(args) unless args.all?(&:blank?)
377
+ end
381
378
  spawn.reorder!(*args)
382
379
  end
383
380
 
384
381
  # Same as #reorder but operates on relation in-place instead of copying.
385
382
  def reorder!(*args) # :nodoc:
386
383
  preprocess_order_args(args) unless args.all?(&:blank?)
387
-
384
+ args.uniq!
388
385
  self.reordering_value = true
389
386
  self.order_values = args
390
387
  self
@@ -433,7 +430,6 @@ module ActiveRecord
433
430
  end
434
431
 
435
432
  def unscope!(*args) # :nodoc:
436
- args.flatten!
437
433
  self.unscope_values += args
438
434
 
439
435
  args.each do |scope|
@@ -444,14 +440,14 @@ module ActiveRecord
444
440
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
445
441
  end
446
442
  assert_mutability!
447
- @values[scope] = DEFAULT_VALUES[scope]
443
+ @values.delete(scope)
448
444
  when Hash
449
445
  scope.each do |key, target_value|
450
446
  if key != :where
451
447
  raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
452
448
  end
453
449
 
454
- target_values = Array(target_value).map(&:to_s)
450
+ target_values = resolve_arel_attributes(Array.wrap(target_value))
455
451
  self.where_clause = where_clause.except(*target_values)
456
452
  end
457
453
  else
@@ -484,8 +480,7 @@ module ActiveRecord
484
480
  # # SELECT "users".*
485
481
  # # FROM "users"
486
482
  # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
487
- # # INNER JOIN "comments" "comments_posts"
488
- # # ON "comments_posts"."post_id" = "posts"."id"
483
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
489
484
  #
490
485
  # You can use strings in order to customize your joins:
491
486
  #
@@ -497,8 +492,6 @@ module ActiveRecord
497
492
  end
498
493
 
499
494
  def joins!(*args) # :nodoc:
500
- args.compact!
501
- args.flatten!
502
495
  self.joins_values |= args
503
496
  self
504
497
  end
@@ -515,8 +508,6 @@ module ActiveRecord
515
508
  alias :left_joins :left_outer_joins
516
509
 
517
510
  def left_outer_joins!(*args) # :nodoc:
518
- args.compact!
519
- args.flatten!
520
511
  self.left_outer_joins_values |= args
521
512
  self
522
513
  end
@@ -640,20 +631,18 @@ module ActiveRecord
640
631
  #
641
632
  # If the condition is any blank-ish object, then #where is a no-op and returns
642
633
  # the current relation.
643
- def where(opts = :chain, *rest)
644
- if :chain == opts
634
+ def where(*args)
635
+ if args.empty?
645
636
  WhereChain.new(spawn)
646
- elsif opts.blank?
637
+ elsif args.length == 1 && args.first.blank?
647
638
  self
648
639
  else
649
- spawn.where!(opts, *rest)
640
+ spawn.where!(*args)
650
641
  end
651
642
  end
652
643
 
653
644
  def where!(opts, *rest) # :nodoc:
654
- opts = sanitize_forbidden_attributes(opts)
655
- references!(PredicateBuilder.references(opts)) if Hash === opts
656
- self.where_clause += where_clause_factory.build(opts, rest)
645
+ self.where_clause += build_where_clause(opts, rest)
657
646
  self
658
647
  end
659
648
 
@@ -671,7 +660,44 @@ module ActiveRecord
671
660
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
672
661
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
673
662
  def rewhere(conditions)
674
- unscope(where: conditions.keys).where(conditions)
663
+ scope = spawn
664
+ where_clause = scope.build_where_clause(conditions)
665
+
666
+ scope.unscope!(where: where_clause.extract_attributes)
667
+ scope.where_clause += where_clause
668
+ scope
669
+ end
670
+
671
+ # Returns a new relation, which is the logical intersection of this relation and the one passed
672
+ # as an argument.
673
+ #
674
+ # The two relations must be structurally compatible: they must be scoping the same model, and
675
+ # they must differ only by #where (if no #group has been defined) or #having (if a #group is
676
+ # present).
677
+ #
678
+ # Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
679
+ # # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
680
+ #
681
+ def and(other)
682
+ if other.is_a?(Relation)
683
+ spawn.and!(other)
684
+ else
685
+ raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
686
+ end
687
+ end
688
+
689
+ def and!(other) # :nodoc:
690
+ incompatible_values = structurally_incompatible_values_for(other)
691
+
692
+ unless incompatible_values.empty?
693
+ raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
694
+ end
695
+
696
+ self.where_clause |= other.where_clause
697
+ self.having_clause |= other.having_clause
698
+ self.references_values |= other.references_values
699
+
700
+ self
675
701
  end
676
702
 
677
703
  # Returns a new relation, which is the logical union of this relation and the one passed as an
@@ -679,21 +705,21 @@ module ActiveRecord
679
705
  #
680
706
  # The two relations must be structurally compatible: they must be scoping the same model, and
681
707
  # they must differ only by #where (if no #group has been defined) or #having (if a #group is
682
- # present). Neither relation may have a #limit, #offset, or #distinct set.
708
+ # present).
683
709
  #
684
710
  # Post.where("id = 1").or(Post.where("author_id = 3"))
685
711
  # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
686
712
  #
687
713
  def or(other)
688
- unless other.is_a? Relation
714
+ if other.is_a?(Relation)
715
+ spawn.or!(other)
716
+ else
689
717
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
690
718
  end
691
-
692
- spawn.or!(other)
693
719
  end
694
720
 
695
721
  def or!(other) # :nodoc:
696
- incompatible_values = structurally_incompatible_values_for_or(other)
722
+ incompatible_values = structurally_incompatible_values_for(other)
697
723
 
698
724
  unless incompatible_values.empty?
699
725
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
@@ -701,7 +727,7 @@ module ActiveRecord
701
727
 
702
728
  self.where_clause = self.where_clause.or(other.where_clause)
703
729
  self.having_clause = having_clause.or(other.having_clause)
704
- self.references_values += other.references_values
730
+ self.references_values |= other.references_values
705
731
 
706
732
  self
707
733
  end
@@ -715,10 +741,7 @@ module ActiveRecord
715
741
  end
716
742
 
717
743
  def having!(opts, *rest) # :nodoc:
718
- opts = sanitize_forbidden_attributes(opts)
719
- references!(PredicateBuilder.references(opts)) if Hash === opts
720
-
721
- self.having_clause += having_clause_factory.build(opts, rest)
744
+ self.having_clause += build_having_clause(opts, rest)
722
745
  self
723
746
  end
724
747
 
@@ -820,6 +843,21 @@ module ActiveRecord
820
843
  self
821
844
  end
822
845
 
846
+ # Sets the returned relation to strict_loading mode. This will raise an error
847
+ # if the record tries to lazily load an association.
848
+ #
849
+ # user = User.strict_loading.first
850
+ # user.comments.to_a
851
+ # => ActiveRecord::StrictLoadingViolationError
852
+ def strict_loading(value = true)
853
+ spawn.strict_loading!(value)
854
+ end
855
+
856
+ def strict_loading!(value = true) # :nodoc:
857
+ self.strict_loading_value = value
858
+ self
859
+ end
860
+
823
861
  # Sets attributes to be used when creating new records from a
824
862
  # relation object.
825
863
  #
@@ -961,8 +999,6 @@ module ActiveRecord
961
999
  end
962
1000
 
963
1001
  def optimizer_hints!(*args) # :nodoc:
964
- args.flatten!
965
-
966
1002
  self.optimizer_hints_values |= args
967
1003
  self
968
1004
  end
@@ -975,8 +1011,7 @@ module ActiveRecord
975
1011
  end
976
1012
 
977
1013
  def reverse_order! # :nodoc:
978
- orders = order_values.uniq
979
- orders.reject!(&:blank?)
1014
+ orders = order_values.compact_blank
980
1015
  self.order_values = reverse_sql_order(orders)
981
1016
  self
982
1017
  end
@@ -1013,6 +1048,14 @@ module ActiveRecord
1013
1048
  self
1014
1049
  end
1015
1050
 
1051
+ # Deduplicate multiple values.
1052
+ def uniq!(name)
1053
+ if values = @values[name]
1054
+ values.uniq! if values.is_a?(Array) && !values.empty?
1055
+ end
1056
+ self
1057
+ end
1058
+
1016
1059
  # Returns the Arel object associated with the relation.
1017
1060
  def arel(aliases = nil) # :nodoc:
1018
1061
  @arel ||= build_arel(aliases)
@@ -1033,54 +1076,106 @@ module ActiveRecord
1033
1076
  end
1034
1077
  end
1035
1078
 
1079
+ def build_where_clause(opts, rest = []) # :nodoc:
1080
+ opts = sanitize_forbidden_attributes(opts)
1081
+
1082
+ case opts
1083
+ when String, Array
1084
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1085
+ when Hash
1086
+ opts = opts.transform_keys do |key|
1087
+ key = key.to_s
1088
+ klass.attribute_aliases[key] || key
1089
+ end
1090
+ references = PredicateBuilder.references(opts)
1091
+ self.references_values |= references unless references.empty?
1092
+
1093
+ parts = predicate_builder.build_from_hash(opts) do |table_name|
1094
+ lookup_table_klass_from_join_dependencies(table_name)
1095
+ end
1096
+ when Arel::Nodes::Node
1097
+ parts = [opts]
1098
+ else
1099
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
1100
+ end
1101
+
1102
+ Relation::WhereClause.new(parts)
1103
+ end
1104
+ alias :build_having_clause :build_where_clause
1105
+
1036
1106
  private
1107
+ def lookup_table_klass_from_join_dependencies(table_name)
1108
+ each_join_dependencies do |join|
1109
+ return join.base_klass if table_name == join.table_name
1110
+ end
1111
+ nil
1112
+ end
1113
+
1114
+ def each_join_dependencies(join_dependencies = build_join_dependencies)
1115
+ join_dependencies.each do |join_dependency|
1116
+ join_dependency.each do |join|
1117
+ yield join
1118
+ end
1119
+ end
1120
+ end
1121
+
1122
+ def build_join_dependencies
1123
+ associations = joins_values | left_outer_joins_values
1124
+ associations |= eager_load_values unless eager_load_values.empty?
1125
+ associations |= includes_values unless includes_values.empty?
1126
+
1127
+ join_dependencies = []
1128
+ join_dependencies.unshift construct_join_dependency(
1129
+ select_association_list(associations, join_dependencies), nil
1130
+ )
1131
+ end
1132
+
1037
1133
  def assert_mutability!
1038
1134
  raise ImmutableRelation if @loaded
1039
1135
  raise ImmutableRelation if defined?(@arel) && @arel
1040
1136
  end
1041
1137
 
1042
- def build_arel(aliases)
1138
+ def build_arel(aliases = nil)
1043
1139
  arel = Arel::SelectManager.new(table)
1044
1140
 
1045
- if !joins_values.empty?
1046
- build_joins(arel, joins_values.flatten, aliases)
1047
- elsif !left_outer_joins_values.empty?
1048
- build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
1049
- end
1141
+ build_joins(arel.join_sources, aliases)
1050
1142
 
1051
1143
  arel.where(where_clause.ast) unless where_clause.empty?
1052
1144
  arel.having(having_clause.ast) unless having_clause.empty?
1053
- if limit_value
1054
- limit_attribute = ActiveModel::Attribute.with_cast_value(
1055
- "LIMIT",
1056
- connection.sanitize_limit(limit_value),
1057
- Type.default_value,
1058
- )
1059
- arel.take(Arel::Nodes::BindParam.new(limit_attribute))
1060
- end
1061
- if offset_value
1062
- offset_attribute = ActiveModel::Attribute.with_cast_value(
1063
- "OFFSET",
1064
- offset_value.to_i,
1065
- Type.default_value,
1066
- )
1067
- arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
1068
- end
1069
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1145
+ arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1146
+ arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1147
+ arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1070
1148
 
1071
1149
  build_order(arel)
1072
-
1073
1150
  build_select(arel)
1074
1151
 
1075
1152
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1076
1153
  arel.distinct(distinct_value)
1077
1154
  arel.from(build_from) unless from_clause.empty?
1078
1155
  arel.lock(lock_value) if lock_value
1079
- arel.comment(*annotate_values) unless annotate_values.empty?
1156
+
1157
+ unless annotate_values.empty?
1158
+ annotates = annotate_values
1159
+ annotates = annotates.uniq if annotates.size > 1
1160
+ unless annotates == annotate_values
1161
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
1162
+ Duplicated query annotations are no longer shown in queries in Rails 7.0.
1163
+ To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations
1164
+ (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
1165
+ MSG
1166
+ annotates = annotate_values
1167
+ end
1168
+ arel.comment(*annotates)
1169
+ end
1080
1170
 
1081
1171
  arel
1082
1172
  end
1083
1173
 
1174
+ def build_cast_value(name, value)
1175
+ cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1176
+ Arel::Nodes::BindParam.new(cast_value)
1177
+ end
1178
+
1084
1179
  def build_from
1085
1180
  opts = from_clause.value
1086
1181
  name = from_clause.name
@@ -1105,47 +1200,41 @@ module ActiveRecord
1105
1200
  when ActiveRecord::Associations::JoinDependency
1106
1201
  stashed_joins&.<< association
1107
1202
  else
1108
- yield if block_given?
1203
+ yield association if block_given?
1109
1204
  end
1110
1205
  end
1111
1206
  result
1112
1207
  end
1113
1208
 
1114
- def valid_association_list(associations, stashed_joins)
1115
- select_association_list(associations, stashed_joins) do
1116
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1117
- end
1118
- end
1119
-
1120
- def build_left_outer_joins(manager, outer_joins, aliases)
1121
- buckets = Hash.new { |h, k| h[k] = [] }
1122
- buckets[:association_join] = valid_association_list(outer_joins, buckets[:stashed_join])
1123
- build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1124
- end
1125
-
1126
1209
  class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1127
1210
  end
1128
1211
 
1129
- def build_joins(manager, joins, aliases)
1212
+ def build_join_buckets
1130
1213
  buckets = Hash.new { |h, k| h[k] = [] }
1131
1214
 
1132
1215
  unless left_outer_joins_values.empty?
1133
1216
  stashed_left_joins = []
1134
- left_joins = valid_association_list(left_outer_joins_values.flatten, stashed_left_joins)
1135
- stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1217
+ left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1218
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1219
+ end
1220
+
1221
+ if joins_values.empty?
1222
+ buckets[:association_join] = left_joins
1223
+ buckets[:stashed_join] = stashed_left_joins
1224
+ return buckets, Arel::Nodes::OuterJoin
1225
+ else
1226
+ stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1227
+ end
1136
1228
  end
1137
1229
 
1230
+ joins = joins_values.dup
1138
1231
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1139
1232
  stashed_eager_load = joins.pop if joins.last.base_klass == klass
1140
1233
  end
1141
1234
 
1142
- joins.map! do |join|
1143
- if join.is_a?(String)
1144
- table.create_string_join(Arel.sql(join.strip)) unless join.blank?
1145
- else
1146
- join
1147
- end
1148
- end.delete_if(&:blank?).uniq!
1235
+ joins.each_with_index do |join, i|
1236
+ joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
1237
+ end
1149
1238
 
1150
1239
  while joins.first.is_a?(Arel::Nodes::Join)
1151
1240
  join_node = joins.shift
@@ -1156,13 +1245,8 @@ module ActiveRecord
1156
1245
  end
1157
1246
  end
1158
1247
 
1159
- joins.each do |join|
1160
- case join
1161
- when Hash, Symbol, Array
1162
- buckets[:association_join] << join
1163
- when ActiveRecord::Associations::JoinDependency
1164
- buckets[:stashed_join] << join
1165
- when Arel::Nodes::Join
1248
+ buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1249
+ if join.is_a?(Arel::Nodes::Join)
1166
1250
  buckets[:join_node] << join
1167
1251
  else
1168
1252
  raise "unknown class: %s" % join.class.name
@@ -1172,32 +1256,36 @@ module ActiveRecord
1172
1256
  buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1173
1257
  buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
1174
1258
 
1175
- build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1259
+ return buckets, Arel::Nodes::InnerJoin
1176
1260
  end
1177
1261
 
1178
- def build_join_query(manager, buckets, join_type, aliases)
1262
+ def build_joins(join_sources, aliases = nil)
1263
+ return join_sources if joins_values.empty? && left_outer_joins_values.empty?
1264
+
1265
+ buckets, join_type = build_join_buckets
1266
+
1179
1267
  association_joins = buckets[:association_join]
1180
1268
  stashed_joins = buckets[:stashed_join]
1181
1269
  leading_joins = buckets[:leading_join]
1182
1270
  join_nodes = buckets[:join_node]
1183
1271
 
1184
- join_sources = manager.join_sources
1185
1272
  join_sources.concat(leading_joins) unless leading_joins.empty?
1186
1273
 
1187
1274
  unless association_joins.empty? && stashed_joins.empty?
1188
1275
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1189
1276
  join_dependency = construct_join_dependency(association_joins, join_type)
1190
- join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
1277
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1191
1278
  end
1192
1279
 
1193
1280
  join_sources.concat(join_nodes) unless join_nodes.empty?
1281
+ join_sources
1194
1282
  end
1195
1283
 
1196
1284
  def build_select(arel)
1197
1285
  if select_values.any?
1198
- arel.project(*arel_columns(select_values.uniq))
1286
+ arel.project(*arel_columns(select_values))
1199
1287
  elsif klass.ignored_columns.any?
1200
- arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1288
+ arel.project(*klass.column_names.map { |field| table[field] })
1201
1289
  else
1202
1290
  arel.project(table[Arel.star])
1203
1291
  end
@@ -1225,7 +1313,12 @@ module ActiveRecord
1225
1313
  from = from_clause.name || from_clause.value
1226
1314
 
1227
1315
  if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1228
- arel_attribute(field)
1316
+ table[field]
1317
+ elsif field.match?(/\A\w+\.\w+\z/)
1318
+ table, column = field.split(".")
1319
+ predicate_builder.resolve_arel_attribute(table, column) do
1320
+ lookup_table_klass_from_join_dependencies(table)
1321
+ end
1229
1322
  else
1230
1323
  yield field
1231
1324
  end
@@ -1239,7 +1332,7 @@ module ActiveRecord
1239
1332
 
1240
1333
  def reverse_sql_order(order_query)
1241
1334
  if order_query.empty?
1242
- return [arel_attribute(primary_key).desc] if primary_key
1335
+ return [table[primary_key].desc] if primary_key
1243
1336
  raise IrreversibleOrderError,
1244
1337
  "Relation has no current order and table has no primary key to be used as default order"
1245
1338
  end
@@ -1250,6 +1343,8 @@ module ActiveRecord
1250
1343
  o.desc
1251
1344
  when Arel::Nodes::Ordering
1252
1345
  o.reverse
1346
+ when Arel::Nodes::NodeExpression
1347
+ o.desc
1253
1348
  when String
1254
1349
  if does_not_support_reverse?(o)
1255
1350
  raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
@@ -1276,9 +1371,7 @@ module ActiveRecord
1276
1371
  end
1277
1372
 
1278
1373
  def build_order(arel)
1279
- orders = order_values.uniq
1280
- orders.reject!(&:blank?)
1281
-
1374
+ orders = order_values.compact_blank
1282
1375
  arel.order(*orders) unless orders.empty?
1283
1376
  end
1284
1377
 
@@ -1298,12 +1391,6 @@ module ActiveRecord
1298
1391
  end
1299
1392
 
1300
1393
  def preprocess_order_args(order_args)
1301
- order_args.reject!(&:blank?)
1302
- order_args.map! do |arg|
1303
- klass.sanitize_sql_for_order(arg)
1304
- end
1305
- order_args.flatten!
1306
-
1307
1394
  @klass.disallow_raw_sql!(
1308
1395
  order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1309
1396
  permit: connection.column_name_with_order_matcher
@@ -1311,9 +1398,8 @@ module ActiveRecord
1311
1398
 
1312
1399
  validate_order_args(order_args)
1313
1400
 
1314
- references = order_args.grep(String)
1315
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1316
- references!(references) if references.any?
1401
+ references = column_references(order_args)
1402
+ self.references_values |= references unless references.empty?
1317
1403
 
1318
1404
  # if a symbol is given we prepend the quoted table name
1319
1405
  order_args.map! do |arg|
@@ -1324,9 +1410,9 @@ module ActiveRecord
1324
1410
  arg.map { |field, dir|
1325
1411
  case field
1326
1412
  when Arel::Nodes::SqlLiteral
1327
- field.send(dir.downcase)
1413
+ field.public_send(dir.downcase)
1328
1414
  else
1329
- order_column(field.to_s).send(dir.downcase)
1415
+ order_column(field.to_s).public_send(dir.downcase)
1330
1416
  end
1331
1417
  }
1332
1418
  else
@@ -1335,16 +1421,54 @@ module ActiveRecord
1335
1421
  end.flatten!
1336
1422
  end
1337
1423
 
1424
+ def sanitize_order_arguments(order_args)
1425
+ order_args.map! do |arg|
1426
+ klass.sanitize_sql_for_order(arg)
1427
+ end
1428
+ order_args.flatten!
1429
+ order_args.compact_blank!
1430
+ end
1431
+
1432
+ def column_references(order_args)
1433
+ references = order_args.grep(String)
1434
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1435
+ references
1436
+ end
1437
+
1338
1438
  def order_column(field)
1339
1439
  arel_column(field) do |attr_name|
1340
1440
  if attr_name == "count" && !group_values.empty?
1341
- arel_attribute(attr_name)
1441
+ table[attr_name]
1342
1442
  else
1343
1443
  Arel.sql(connection.quote_table_name(attr_name))
1344
1444
  end
1345
1445
  end
1346
1446
  end
1347
1447
 
1448
+ def resolve_arel_attributes(attrs)
1449
+ attrs.flat_map do |attr|
1450
+ case attr
1451
+ when Arel::Predications
1452
+ attr
1453
+ when Hash
1454
+ attr.flat_map do |table, columns|
1455
+ table = table.to_s
1456
+ Array(columns).map do |column|
1457
+ predicate_builder.resolve_arel_attribute(table, column)
1458
+ end
1459
+ end
1460
+ else
1461
+ attr = attr.to_s
1462
+ if attr.include?(".")
1463
+ table, column = attr.split(".", 2)
1464
+ predicate_builder.resolve_arel_attribute(table, column)
1465
+ else
1466
+ attr
1467
+ end
1468
+ end
1469
+ end
1470
+ end
1471
+
1348
1472
  # Checks to make sure that the arguments are not blank. Note that if some
1349
1473
  # blank-like object were initially passed into the query method, then this
1350
1474
  # method will not raise an error.
@@ -1361,38 +1485,40 @@ module ActiveRecord
1361
1485
  # check_if_method_has_arguments!("references", args)
1362
1486
  # ...
1363
1487
  # end
1364
- def check_if_method_has_arguments!(method_name, args)
1488
+ def check_if_method_has_arguments!(method_name, args, message = nil)
1365
1489
  if args.blank?
1366
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1490
+ raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1491
+ elsif block_given?
1492
+ yield args
1493
+ else
1494
+ args.flatten!
1495
+ args.compact_blank!
1367
1496
  end
1368
1497
  end
1369
1498
 
1370
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
1371
- def structurally_incompatible_values_for_or(other)
1499
+ STRUCTURAL_VALUE_METHODS = (
1500
+ Relation::VALUE_METHODS -
1501
+ [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
1502
+ ).freeze # :nodoc:
1503
+
1504
+ def structurally_incompatible_values_for(other)
1372
1505
  values = other.values
1373
- STRUCTURAL_OR_METHODS.reject do |method|
1374
- default = DEFAULT_VALUES[method]
1375
- v1, v2 = @values.fetch(method, default), values.fetch(method, default)
1376
- v1 = v1.uniq if v1.is_a?(Array)
1377
- v2 = v2.uniq if v2.is_a?(Array)
1506
+ STRUCTURAL_VALUE_METHODS.reject do |method|
1507
+ v1, v2 = @values[method], values[method]
1508
+ if v1.is_a?(Array)
1509
+ next true unless v2.is_a?(Array)
1510
+ v1 = v1.uniq
1511
+ v2 = v2.uniq
1512
+ end
1378
1513
  v1 == v2
1379
1514
  end
1380
1515
  end
1516
+ end
1381
1517
 
1382
- def where_clause_factory
1383
- @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1384
- end
1385
- alias having_clause_factory where_clause_factory
1386
-
1387
- DEFAULT_VALUES = {
1388
- create_with: FROZEN_EMPTY_HASH,
1389
- where: Relation::WhereClause.empty,
1390
- having: Relation::WhereClause.empty,
1391
- from: Relation::FromClause.empty
1392
- }
1393
-
1394
- Relation::MULTI_VALUE_METHODS.each do |value|
1395
- DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
1396
- end
1518
+ class Relation # :nodoc:
1519
+ # No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
1520
+ # TODO: Remove the class once Rails 6.1 has released.
1521
+ class WhereClauseFactory # :nodoc:
1522
+ end
1397
1523
  end
1398
1524
  end