activerecord 6.0.4.1 → 6.1.0.rc1

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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +767 -851
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +1 -1
  7. data/lib/active_record/association_relation.rb +22 -14
  8. data/lib/active_record/associations.rb +114 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +39 -27
  11. data/lib/active_record/associations/association_scope.rb +11 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  14. data/lib/active_record/associations/builder/association.rb +9 -3
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +19 -13
  22. data/lib/active_record/associations/collection_proxy.rb +12 -5
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -2
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +63 -49
  28. data/lib/active_record/associations/join_dependency/join_association.rb +29 -14
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/preloader.rb +5 -3
  31. data/lib/active_record/associations/preloader/association.rb +13 -5
  32. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/attribute_assignment.rb +10 -8
  35. data/lib/active_record/attribute_methods.rb +52 -48
  36. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  37. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  38. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  39. data/lib/active_record/attribute_methods/query.rb +3 -6
  40. data/lib/active_record/attribute_methods/read.rb +8 -11
  41. data/lib/active_record/attribute_methods/serialization.rb +4 -4
  42. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  43. data/lib/active_record/attribute_methods/write.rb +12 -20
  44. data/lib/active_record/attributes.rb +27 -7
  45. data/lib/active_record/autosave_association.rb +47 -30
  46. data/lib/active_record/base.rb +2 -14
  47. data/lib/active_record/callbacks.rb +32 -22
  48. data/lib/active_record/coders/yaml_column.rb +1 -1
  49. data/lib/active_record/connection_adapters.rb +50 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +180 -134
  51. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -7
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  55. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +110 -30
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +224 -85
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -24
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -70
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
  63. data/lib/active_record/connection_adapters/column.rb +15 -1
  64. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  65. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  66. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -24
  67. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +33 -6
  70. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  71. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  72. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +3 -3
  73. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  74. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  75. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  76. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +12 -53
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  81. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -10
  83. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  92. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  93. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  94. data/lib/active_record/connection_adapters/postgresql_adapter.rb +72 -55
  95. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  96. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  97. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +30 -5
  98. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  99. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  100. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +36 -3
  101. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
  102. data/lib/active_record/connection_handling.rb +210 -71
  103. data/lib/active_record/core.rb +215 -49
  104. data/lib/active_record/database_configurations.rb +124 -85
  105. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  106. data/lib/active_record/database_configurations/database_config.rb +52 -9
  107. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  108. data/lib/active_record/database_configurations/url_config.rb +15 -40
  109. data/lib/active_record/delegated_type.rb +209 -0
  110. data/lib/active_record/destroy_association_async_job.rb +36 -0
  111. data/lib/active_record/enum.rb +33 -23
  112. data/lib/active_record/errors.rb +47 -12
  113. data/lib/active_record/explain.rb +9 -4
  114. data/lib/active_record/explain_subscriber.rb +1 -1
  115. data/lib/active_record/fixture_set/file.rb +10 -17
  116. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  117. data/lib/active_record/fixture_set/render_context.rb +1 -1
  118. data/lib/active_record/fixture_set/table_row.rb +2 -2
  119. data/lib/active_record/fixtures.rb +54 -8
  120. data/lib/active_record/gem_version.rb +3 -3
  121. data/lib/active_record/inheritance.rb +40 -18
  122. data/lib/active_record/insert_all.rb +32 -5
  123. data/lib/active_record/integration.rb +3 -5
  124. data/lib/active_record/internal_metadata.rb +15 -4
  125. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  126. data/lib/active_record/locking/optimistic.rb +13 -16
  127. data/lib/active_record/locking/pessimistic.rb +6 -2
  128. data/lib/active_record/log_subscriber.rb +26 -8
  129. data/lib/active_record/middleware/database_selector.rb +4 -1
  130. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  131. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  132. data/lib/active_record/migration.rb +113 -83
  133. data/lib/active_record/migration/command_recorder.rb +47 -27
  134. data/lib/active_record/migration/compatibility.rb +67 -17
  135. data/lib/active_record/model_schema.rb +88 -42
  136. data/lib/active_record/nested_attributes.rb +2 -3
  137. data/lib/active_record/no_touching.rb +1 -1
  138. data/lib/active_record/persistence.rb +50 -45
  139. data/lib/active_record/query_cache.rb +15 -5
  140. data/lib/active_record/querying.rb +11 -6
  141. data/lib/active_record/railtie.rb +64 -44
  142. data/lib/active_record/railties/databases.rake +253 -98
  143. data/lib/active_record/readonly_attributes.rb +4 -0
  144. data/lib/active_record/reflection.rb +59 -44
  145. data/lib/active_record/relation.rb +90 -64
  146. data/lib/active_record/relation/batches.rb +38 -31
  147. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  148. data/lib/active_record/relation/calculations.rb +100 -43
  149. data/lib/active_record/relation/finder_methods.rb +44 -14
  150. data/lib/active_record/relation/from_clause.rb +1 -1
  151. data/lib/active_record/relation/merger.rb +20 -23
  152. data/lib/active_record/relation/predicate_builder.rb +57 -33
  153. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  154. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -2
  155. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  156. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  157. data/lib/active_record/relation/query_methods.rb +319 -196
  158. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  159. data/lib/active_record/relation/spawn_methods.rb +6 -5
  160. data/lib/active_record/relation/where_clause.rb +104 -57
  161. data/lib/active_record/result.rb +41 -33
  162. data/lib/active_record/runtime_registry.rb +2 -2
  163. data/lib/active_record/sanitization.rb +6 -17
  164. data/lib/active_record/schema_dumper.rb +34 -4
  165. data/lib/active_record/schema_migration.rb +0 -4
  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 +2 -2
  172. data/lib/active_record/suppressor.rb +2 -2
  173. data/lib/active_record/table_metadata.rb +36 -52
  174. data/lib/active_record/tasks/database_tasks.rb +139 -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 +36 -33
  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 +15 -64
  183. data/lib/active_record/type.rb +8 -1
  184. data/lib/active_record/type/serialized.rb +6 -2
  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.rb +1 -0
  188. data/lib/active_record/validations/associated.rb +1 -1
  189. data/lib/active_record/validations/numericality.rb +35 -0
  190. data/lib/active_record/validations/uniqueness.rb +24 -4
  191. data/lib/arel.rb +5 -13
  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.rb +3 -1
  198. data/lib/arel/nodes/binary.rb +82 -8
  199. data/lib/arel/nodes/bind_param.rb +8 -0
  200. data/lib/arel/nodes/casted.rb +21 -9
  201. data/lib/arel/nodes/equality.rb +6 -9
  202. data/lib/arel/nodes/grouping.rb +3 -0
  203. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  204. data/lib/arel/nodes/in.rb +8 -1
  205. data/lib/arel/nodes/infix_operation.rb +13 -1
  206. data/lib/arel/nodes/join_source.rb +1 -1
  207. data/lib/arel/nodes/node.rb +7 -6
  208. data/lib/arel/nodes/ordering.rb +27 -0
  209. data/lib/arel/nodes/sql_literal.rb +3 -0
  210. data/lib/arel/nodes/table_alias.rb +7 -3
  211. data/lib/arel/nodes/unary.rb +0 -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.rb +0 -7
  216. data/lib/arel/visitors/dot.rb +14 -2
  217. data/lib/arel/visitors/mysql.rb +11 -1
  218. data/lib/arel/visitors/postgresql.rb +15 -4
  219. data/lib/arel/visitors/to_sql.rb +89 -78
  220. data/lib/rails/generators/active_record/migration.rb +6 -1
  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/model/model_generator.rb +39 -2
  225. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  226. metadata +30 -31
  227. data/lib/active_record/advisory_lock_base.rb +0 -18
  228. data/lib/active_record/attribute_decorators.rb +0 -88
  229. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  230. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  231. data/lib/active_record/define_callbacks.rb +0 -22
  232. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  233. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  234. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  235. data/lib/arel/attributes.rb +0 -22
  236. data/lib/arel/visitors/depth_first.rb +0 -203
  237. data/lib/arel/visitors/ibm_db.rb +0 -34
  238. data/lib/arel/visitors/informix.rb +0 -62
  239. data/lib/arel/visitors/mssql.rb +0 -156
  240. data/lib/arel/visitors/oracle.rb +0 -158
  241. data/lib/arel/visitors/oracle12.rb +0 -65
  242. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  end
24
24
 
25
25
  def self.empty
26
- @empty ||= new(nil, nil)
26
+ @empty ||= new(nil, nil).freeze
27
27
  end
28
28
  end
29
29
  end
@@ -7,15 +7,16 @@ module ActiveRecord
7
7
  class HashMerger # :nodoc:
8
8
  attr_reader :relation, :hash
9
9
 
10
- def initialize(relation, hash)
10
+ def initialize(relation, hash, rewhere = nil)
11
11
  hash.assert_valid_keys(*Relation::VALUE_METHODS)
12
12
 
13
13
  @relation = relation
14
14
  @hash = hash
15
+ @rewhere = rewhere
15
16
  end
16
17
 
17
- def merge #:nodoc:
18
- Merger.new(relation, other).merge
18
+ def merge
19
+ Merger.new(relation, other, @rewhere).merge
19
20
  end
20
21
 
21
22
  # Applying values to a relation has some side effects. E.g.
@@ -28,19 +29,14 @@ module ActiveRecord
28
29
  table: relation.table,
29
30
  predicate_builder: relation.predicate_builder
30
31
  )
31
- hash.each { |k, v|
32
- if k == :joins
33
- if Hash === v
34
- other.joins!(v)
35
- else
36
- other.joins!(*v)
37
- end
38
- elsif k == :select
39
- other._select!(v)
32
+ hash.each do |k, v|
33
+ k = :_select if k == :select
34
+ if Array === v
35
+ other.public_send("#{k}!", *v)
40
36
  else
41
- other.send("#{k}!", v)
37
+ other.public_send("#{k}!", v)
42
38
  end
43
- }
39
+ end
44
40
  other
45
41
  end
46
42
  end
@@ -48,10 +44,11 @@ module ActiveRecord
48
44
  class Merger # :nodoc:
49
45
  attr_reader :relation, :values, :other
50
46
 
51
- def initialize(relation, other)
47
+ def initialize(relation, other, rewhere = nil)
52
48
  @relation = relation
53
49
  @values = other.values
54
50
  @other = other
51
+ @rewhere = rewhere
55
52
  end
56
53
 
57
54
  NORMAL_VALUES = Relation::VALUE_METHODS -
@@ -73,7 +70,7 @@ module ActiveRecord
73
70
  if name == :select
74
71
  relation._select!(*value)
75
72
  else
76
- relation.send("#{name}!", *value)
73
+ relation.public_send("#{name}!", *value)
77
74
  end
78
75
  end
79
76
  end
@@ -93,8 +90,8 @@ module ActiveRecord
93
90
  return if other.preload_values.empty? && other.includes_values.empty?
94
91
 
95
92
  if other.klass == relation.klass
96
- relation.preload!(*other.preload_values) unless other.preload_values.empty?
97
- relation.includes!(other.includes_values) unless other.includes_values.empty?
93
+ relation.preload_values |= other.preload_values unless other.preload_values.empty?
94
+ relation.includes_values |= other.includes_values unless other.includes_values.empty?
98
95
  else
99
96
  reflection = relation.klass.reflect_on_all_associations.find do |r|
100
97
  r.class_name == other.klass.name
@@ -111,10 +108,10 @@ module ActiveRecord
111
108
  end
112
109
 
113
110
  def merge_joins
114
- return if other.joins_values.blank?
111
+ return if other.joins_values.empty?
115
112
 
116
113
  if other.klass == relation.klass
117
- relation.joins!(*other.joins_values)
114
+ relation.joins_values |= other.joins_values
118
115
  else
119
116
  associations, others = other.joins_values.partition do |join|
120
117
  case join
@@ -130,10 +127,10 @@ module ActiveRecord
130
127
  end
131
128
 
132
129
  def merge_outer_joins
133
- return if other.left_outer_joins_values.blank?
130
+ return if other.left_outer_joins_values.empty?
134
131
 
135
132
  if other.klass == relation.klass
136
- relation.left_outer_joins!(*other.left_outer_joins_values)
133
+ relation.left_outer_joins_values |= other.left_outer_joins_values
137
134
  else
138
135
  associations, others = other.left_outer_joins_values.partition do |join|
139
136
  case join
@@ -172,7 +169,7 @@ module ActiveRecord
172
169
  def merge_clauses
173
170
  relation.from_clause = other.from_clause if replace_from_clause?
174
171
 
175
- where_clause = relation.where_clause.merge(other.where_clause)
172
+ where_clause = relation.where_clause.merge(other.where_clause, @rewhere)
176
173
  relation.where_clause = where_clause unless where_clause.empty?
177
174
 
178
175
  having_clause = relation.having_clause.merge(other.having_clause)
@@ -2,34 +2,41 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class PredicateBuilder # :nodoc:
5
- delegate :resolve_column_aliases, to: :table
5
+ require "active_record/relation/predicate_builder/array_handler"
6
+ require "active_record/relation/predicate_builder/basic_object_handler"
7
+ require "active_record/relation/predicate_builder/range_handler"
8
+ require "active_record/relation/predicate_builder/relation_handler"
9
+ require "active_record/relation/predicate_builder/association_query_value"
10
+ require "active_record/relation/predicate_builder/polymorphic_array_value"
11
+
12
+ # No-op BaseHandler to work Mashal.load(File.read("legacy_relation.dump")).
13
+ # TODO: Remove the constant alias once Rails 6.1 has released.
14
+ BaseHandler = BasicObjectHandler
6
15
 
7
16
  def initialize(table)
8
17
  @table = table
9
18
  @handlers = []
10
19
 
11
20
  register_handler(BasicObject, BasicObjectHandler.new(self))
12
- register_handler(Base, BaseHandler.new(self))
13
21
  register_handler(Range, RangeHandler.new(self))
14
22
  register_handler(Relation, RelationHandler.new)
15
23
  register_handler(Array, ArrayHandler.new(self))
16
24
  register_handler(Set, ArrayHandler.new(self))
17
25
  end
18
26
 
19
- def build_from_hash(attributes)
27
+ def build_from_hash(attributes, &block)
20
28
  attributes = convert_dot_notation_to_hash(attributes)
21
- expand_from_hash(attributes)
29
+ expand_from_hash(attributes, &block)
22
30
  end
23
31
 
24
32
  def self.references(attributes)
25
- attributes.map do |key, value|
33
+ attributes.each_with_object([]) do |(key, value), result|
26
34
  if value.is_a?(Hash)
27
- key
28
- else
29
- key = key.to_s
30
- key.split(".").first if key.include?(".")
35
+ result << key
36
+ elsif key.include?(".")
37
+ result << key.split(".").first
31
38
  end
32
- end.compact
39
+ end
33
40
  end
34
41
 
35
42
  # Define how a class is converted to Arel nodes when passed to +where+.
@@ -47,27 +54,37 @@ module ActiveRecord
47
54
  @handlers.unshift([klass, handler])
48
55
  end
49
56
 
50
- def build(attribute, value)
51
- if table.type(attribute.name).force_equality?(value)
57
+ def [](attr_name, value, operator = nil)
58
+ build(table.arel_table[attr_name], value, operator)
59
+ end
60
+
61
+ def build(attribute, value, operator = nil)
62
+ value = value.id if value.is_a?(Base)
63
+ if operator ||= table.type(attribute.name).force_equality?(value) && :eq
52
64
  bind = build_bind_attribute(attribute.name, value)
53
- attribute.eq(bind)
65
+ attribute.public_send(operator, bind)
54
66
  else
55
67
  handler_for(value).call(attribute, value)
56
68
  end
57
69
  end
58
70
 
59
71
  def build_bind_attribute(column_name, value)
60
- attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
72
+ attr = Relation::QueryAttribute.new(column_name, value, table.type(column_name))
61
73
  Arel::Nodes::BindParam.new(attr)
62
74
  end
63
75
 
76
+ def resolve_arel_attribute(table_name, column_name, &block)
77
+ table.associated_table(table_name, &block).arel_table[column_name]
78
+ end
79
+
64
80
  protected
65
- def expand_from_hash(attributes)
81
+ def expand_from_hash(attributes, &block)
66
82
  return ["1=0"] if attributes.empty?
67
83
 
68
84
  attributes.flat_map do |key, value|
69
85
  if value.is_a?(Hash) && !table.has_column?(key)
70
- table.associated_predicate_builder(key).expand_from_hash(value)
86
+ table.associated_table(key, &block)
87
+ .predicate_builder.expand_from_hash(value.stringify_keys)
71
88
  elsif table.associated_with?(key)
72
89
  # Find the foreign key when using queries such as:
73
90
  # Post.where(author: author)
@@ -81,13 +98,18 @@ module ActiveRecord
81
98
  value = [value] unless value.is_a?(Array)
82
99
  klass = PolymorphicArrayValue
83
100
  end
101
+ elsif associated_table.through_association?
102
+ next associated_table.predicate_builder.expand_from_hash(
103
+ associated_table.join_foreign_key => value
104
+ )
84
105
  end
85
106
 
86
107
  klass ||= AssociationQueryValue
87
- queries = klass.new(associated_table, value).queries.map do |query|
88
- expand_from_hash(query).reduce(&:and)
108
+ queries = klass.new(associated_table, value).queries.map! do |query|
109
+ expand_from_hash(query)
89
110
  end
90
- queries.reduce(&:or)
111
+
112
+ grouping_queries(queries)
91
113
  elsif table.aggregated_with?(key)
92
114
  mapping = table.reflect_on_aggregation(key).mapping
93
115
  values = value.nil? ? [nil] : Array.wrap(value)
@@ -96,17 +118,18 @@ module ActiveRecord
96
118
  values = values.map do |object|
97
119
  object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
98
120
  end
99
- build(table.arel_attribute(column_name), values)
121
+ self[column_name, values]
100
122
  else
101
123
  queries = values.map do |object|
102
124
  mapping.map do |field_attr, aggregate_attr|
103
- build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
104
- end.reduce(&:and)
125
+ self[field_attr, object.try!(aggregate_attr)]
126
+ end
105
127
  end
106
- queries.reduce(&:or)
128
+
129
+ grouping_queries(queries)
107
130
  end
108
131
  else
109
- build(table.arel_attribute(key), value)
132
+ self[key, value]
110
133
  end
111
134
  end
112
135
  end
@@ -114,6 +137,16 @@ module ActiveRecord
114
137
  private
115
138
  attr_reader :table
116
139
 
140
+ def grouping_queries(queries)
141
+ if queries.one?
142
+ queries.first
143
+ else
144
+ queries.map! { |query| query.reduce(&:and) }
145
+ queries = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) }
146
+ Arel::Nodes::Grouping.new(queries)
147
+ end
148
+ end
149
+
117
150
  def convert_dot_notation_to_hash(attributes)
118
151
  dot_notation = attributes.select do |k, v|
119
152
  k.include?(".") && !v.is_a?(Hash)
@@ -135,12 +168,3 @@ module ActiveRecord
135
168
  end
136
169
  end
137
170
  end
138
-
139
- require "active_record/relation/predicate_builder/array_handler"
140
- require "active_record/relation/predicate_builder/base_handler"
141
- require "active_record/relation/predicate_builder/basic_object_handler"
142
- require "active_record/relation/predicate_builder/range_handler"
143
- require "active_record/relation/predicate_builder/relation_handler"
144
-
145
- require "active_record/relation/predicate_builder/association_query_value"
146
- require "active_record/relation/predicate_builder/polymorphic_array_value"
@@ -20,20 +20,19 @@ module ActiveRecord
20
20
  case values.length
21
21
  when 0 then NullPredicate
22
22
  when 1 then predicate_builder.build(attribute, values.first)
23
- else
24
- values.map! do |v|
25
- predicate_builder.build_bind_attribute(attribute.name, v)
26
- end
27
- values.empty? ? NullPredicate : attribute.in(values)
23
+ else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
28
24
  end
29
25
 
30
26
  unless nils.empty?
31
- values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
27
+ values_predicate = values_predicate.or(attribute.eq(nil))
32
28
  end
33
29
 
34
- array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
35
- array_predicates.unshift(values_predicate)
36
- array_predicates.inject(&:or)
30
+ if ranges.empty?
31
+ values_predicate
32
+ else
33
+ array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
34
+ array_predicates.inject(values_predicate, &:or)
35
+ end
37
36
  end
38
37
 
39
38
  private
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def queries
12
- [associated_table.association_join_foreign_key.to_s => ids]
12
+ [associated_table.join_foreign_key => ids]
13
13
  end
14
14
 
15
15
  private
@@ -27,7 +27,7 @@ module ActiveRecord
27
27
  end
28
28
 
29
29
  def primary_key
30
- associated_table.association_join_primary_key
30
+ associated_table.join_primary_key
31
31
  end
32
32
 
33
33
  def convert_to_id(value)
@@ -11,8 +11,8 @@ module ActiveRecord
11
11
  def queries
12
12
  type_to_ids_mapping.map do |type, ids|
13
13
  {
14
- associated_table.association_foreign_type.to_s => type,
15
- associated_table.association_foreign_key.to_s => ids
14
+ associated_table.join_foreign_type => type,
15
+ associated_table.join_foreign_key => ids
16
16
  }
17
17
  end
18
18
  end
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  end
29
29
 
30
30
  def primary_key(value)
31
- associated_table.association_join_primary_key(klass(value))
31
+ associated_table.join_primary_key(klass(value))
32
32
  end
33
33
 
34
34
  def klass(value)
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  if value.select_values.empty?
12
- value = value.select(value.arel_attribute(value.klass.primary_key))
12
+ value = value.select(value.table[value.klass.primary_key])
13
13
  end
14
14
 
15
15
  attribute.in(value.arel)
@@ -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
@@ -1011,6 +1046,14 @@ module ActiveRecord
1011
1046
  self
1012
1047
  end
1013
1048
 
1049
+ # Deduplicate multiple values.
1050
+ def uniq!(name)
1051
+ if values = @values[name]
1052
+ values.uniq! if values.is_a?(Array) && !values.empty?
1053
+ end
1054
+ self
1055
+ end
1056
+
1014
1057
  # Returns the Arel object associated with the relation.
1015
1058
  def arel(aliases = nil) # :nodoc:
1016
1059
  @arel ||= build_arel(aliases)
@@ -1031,7 +1074,57 @@ module ActiveRecord
1031
1074
  end
1032
1075
  end
1033
1076
 
1077
+ def build_where_clause(opts, rest = []) # :nodoc:
1078
+ opts = sanitize_forbidden_attributes(opts)
1079
+
1080
+ case opts
1081
+ when String, Array
1082
+ parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1083
+ when Hash
1084
+ opts = opts.stringify_keys
1085
+ references = PredicateBuilder.references(opts)
1086
+ self.references_values |= references unless references.empty?
1087
+
1088
+ parts = predicate_builder.build_from_hash(opts) do |table_name|
1089
+ lookup_reflection_from_join_dependencies(table_name)
1090
+ end
1091
+ when Arel::Nodes::Node
1092
+ parts = [opts]
1093
+ else
1094
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
1095
+ end
1096
+
1097
+ Relation::WhereClause.new(parts)
1098
+ end
1099
+ alias :build_having_clause :build_where_clause
1100
+
1034
1101
  private
1102
+ def lookup_reflection_from_join_dependencies(table_name)
1103
+ each_join_dependencies do |join|
1104
+ return join.reflection if table_name == join.table_name
1105
+ end
1106
+ nil
1107
+ end
1108
+
1109
+ def each_join_dependencies(join_dependencies = build_join_dependencies)
1110
+ join_dependencies.each do |join_dependency|
1111
+ join_dependency.each do |join|
1112
+ yield join
1113
+ end
1114
+ end
1115
+ end
1116
+
1117
+ def build_join_dependencies
1118
+ associations = joins_values | left_outer_joins_values
1119
+ associations |= eager_load_values unless eager_load_values.empty?
1120
+ associations |= includes_values unless includes_values.empty?
1121
+
1122
+ join_dependencies = []
1123
+ join_dependencies.unshift construct_join_dependency(
1124
+ select_association_list(associations, join_dependencies), nil
1125
+ )
1126
+ end
1127
+
1035
1128
  def assert_mutability!
1036
1129
  raise ImmutableRelation if @loaded
1037
1130
  raise ImmutableRelation if defined?(@arel) && @arel
@@ -1040,45 +1133,44 @@ module ActiveRecord
1040
1133
  def build_arel(aliases)
1041
1134
  arel = Arel::SelectManager.new(table)
1042
1135
 
1043
- if !joins_values.empty?
1044
- build_joins(arel, joins_values.flatten, aliases)
1045
- elsif !left_outer_joins_values.empty?
1046
- build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
1047
- end
1136
+ build_joins(arel.join_sources, aliases)
1048
1137
 
1049
1138
  arel.where(where_clause.ast) unless where_clause.empty?
1050
1139
  arel.having(having_clause.ast) unless having_clause.empty?
1051
- if limit_value
1052
- limit_attribute = ActiveModel::Attribute.with_cast_value(
1053
- "LIMIT",
1054
- connection.sanitize_limit(limit_value),
1055
- Type.default_value,
1056
- )
1057
- arel.take(Arel::Nodes::BindParam.new(limit_attribute))
1058
- end
1059
- if offset_value
1060
- offset_attribute = ActiveModel::Attribute.with_cast_value(
1061
- "OFFSET",
1062
- offset_value.to_i,
1063
- Type.default_value,
1064
- )
1065
- arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
1066
- end
1067
- arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
1140
+ arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
1141
+ arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
1142
+ arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
1068
1143
 
1069
1144
  build_order(arel)
1070
-
1071
1145
  build_select(arel)
1072
1146
 
1073
1147
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1074
1148
  arel.distinct(distinct_value)
1075
1149
  arel.from(build_from) unless from_clause.empty?
1076
1150
  arel.lock(lock_value) if lock_value
1077
- arel.comment(*annotate_values) unless annotate_values.empty?
1151
+
1152
+ unless annotate_values.empty?
1153
+ annotates = annotate_values
1154
+ annotates = annotates.uniq if annotates.size > 1
1155
+ unless annotates == annotate_values
1156
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
1157
+ Duplicated query annotations are no longer shown in queries in Rails 6.2.
1158
+ To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
1159
+ (`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
1160
+ MSG
1161
+ annotates = annotate_values
1162
+ end
1163
+ arel.comment(*annotates)
1164
+ end
1078
1165
 
1079
1166
  arel
1080
1167
  end
1081
1168
 
1169
+ def build_cast_value(name, value)
1170
+ cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
1171
+ Arel::Nodes::BindParam.new(cast_value)
1172
+ end
1173
+
1082
1174
  def build_from
1083
1175
  opts = from_clause.value
1084
1176
  name = from_clause.name
@@ -1103,47 +1195,41 @@ module ActiveRecord
1103
1195
  when ActiveRecord::Associations::JoinDependency
1104
1196
  stashed_joins&.<< association
1105
1197
  else
1106
- yield if block_given?
1198
+ yield association if block_given?
1107
1199
  end
1108
1200
  end
1109
1201
  result
1110
1202
  end
1111
1203
 
1112
- def valid_association_list(associations, stashed_joins)
1113
- select_association_list(associations, stashed_joins) do
1114
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1115
- end
1116
- end
1117
-
1118
- def build_left_outer_joins(manager, outer_joins, aliases)
1119
- buckets = Hash.new { |h, k| h[k] = [] }
1120
- buckets[:association_join] = valid_association_list(outer_joins, buckets[:stashed_join])
1121
- build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1122
- end
1123
-
1124
1204
  class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1125
1205
  end
1126
1206
 
1127
- def build_joins(manager, joins, aliases)
1207
+ def build_join_buckets
1128
1208
  buckets = Hash.new { |h, k| h[k] = [] }
1129
1209
 
1130
1210
  unless left_outer_joins_values.empty?
1131
1211
  stashed_left_joins = []
1132
- left_joins = valid_association_list(left_outer_joins_values.flatten, stashed_left_joins)
1133
- stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1212
+ left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
1213
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1214
+ end
1215
+
1216
+ if joins_values.empty?
1217
+ buckets[:association_join] = left_joins
1218
+ buckets[:stashed_join] = stashed_left_joins
1219
+ return buckets, Arel::Nodes::OuterJoin
1220
+ else
1221
+ stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1222
+ end
1134
1223
  end
1135
1224
 
1225
+ joins = joins_values.dup
1136
1226
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1137
1227
  stashed_eager_load = joins.pop if joins.last.base_klass == klass
1138
1228
  end
1139
1229
 
1140
- joins.map! do |join|
1141
- if join.is_a?(String)
1142
- table.create_string_join(Arel.sql(join.strip)) unless join.blank?
1143
- else
1144
- join
1145
- end
1146
- end.delete_if(&:blank?).uniq!
1230
+ joins.each_with_index do |join, i|
1231
+ joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
1232
+ end
1147
1233
 
1148
1234
  while joins.first.is_a?(Arel::Nodes::Join)
1149
1235
  join_node = joins.shift
@@ -1154,13 +1240,8 @@ module ActiveRecord
1154
1240
  end
1155
1241
  end
1156
1242
 
1157
- joins.each do |join|
1158
- case join
1159
- when Hash, Symbol, Array
1160
- buckets[:association_join] << join
1161
- when ActiveRecord::Associations::JoinDependency
1162
- buckets[:stashed_join] << join
1163
- when Arel::Nodes::Join
1243
+ buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
1244
+ if join.is_a?(Arel::Nodes::Join)
1164
1245
  buckets[:join_node] << join
1165
1246
  else
1166
1247
  raise "unknown class: %s" % join.class.name
@@ -1170,32 +1251,36 @@ module ActiveRecord
1170
1251
  buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1171
1252
  buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
1172
1253
 
1173
- build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1254
+ return buckets, Arel::Nodes::InnerJoin
1174
1255
  end
1175
1256
 
1176
- def build_join_query(manager, buckets, join_type, aliases)
1257
+ def build_joins(join_sources, aliases = nil)
1258
+ return join_sources if joins_values.empty? && left_outer_joins_values.empty?
1259
+
1260
+ buckets, join_type = build_join_buckets
1261
+
1177
1262
  association_joins = buckets[:association_join]
1178
1263
  stashed_joins = buckets[:stashed_join]
1179
1264
  leading_joins = buckets[:leading_join]
1180
1265
  join_nodes = buckets[:join_node]
1181
1266
 
1182
- join_sources = manager.join_sources
1183
1267
  join_sources.concat(leading_joins) unless leading_joins.empty?
1184
1268
 
1185
1269
  unless association_joins.empty? && stashed_joins.empty?
1186
1270
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1187
1271
  join_dependency = construct_join_dependency(association_joins, join_type)
1188
- join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
1272
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
1189
1273
  end
1190
1274
 
1191
1275
  join_sources.concat(join_nodes) unless join_nodes.empty?
1276
+ join_sources
1192
1277
  end
1193
1278
 
1194
1279
  def build_select(arel)
1195
1280
  if select_values.any?
1196
- arel.project(*arel_columns(select_values.uniq))
1281
+ arel.project(*arel_columns(select_values))
1197
1282
  elsif klass.ignored_columns.any?
1198
- arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1283
+ arel.project(*klass.column_names.map { |field| table[field] })
1199
1284
  else
1200
1285
  arel.project(table[Arel.star])
1201
1286
  end
@@ -1223,7 +1308,12 @@ module ActiveRecord
1223
1308
  from = from_clause.name || from_clause.value
1224
1309
 
1225
1310
  if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1226
- arel_attribute(field)
1311
+ table[field]
1312
+ elsif field.match?(/\A\w+\.\w+\z/)
1313
+ table, column = field.split(".")
1314
+ predicate_builder.resolve_arel_attribute(table, column) do
1315
+ lookup_reflection_from_join_dependencies(table)
1316
+ end
1227
1317
  else
1228
1318
  yield field
1229
1319
  end
@@ -1237,7 +1327,7 @@ module ActiveRecord
1237
1327
 
1238
1328
  def reverse_sql_order(order_query)
1239
1329
  if order_query.empty?
1240
- return [arel_attribute(primary_key).desc] if primary_key
1330
+ return [table[primary_key].desc] if primary_key
1241
1331
  raise IrreversibleOrderError,
1242
1332
  "Relation has no current order and table has no primary key to be used as default order"
1243
1333
  end
@@ -1248,6 +1338,8 @@ module ActiveRecord
1248
1338
  o.desc
1249
1339
  when Arel::Nodes::Ordering
1250
1340
  o.reverse
1341
+ when Arel::Nodes::NodeExpression
1342
+ o.desc
1251
1343
  when String
1252
1344
  if does_not_support_reverse?(o)
1253
1345
  raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
@@ -1274,9 +1366,7 @@ module ActiveRecord
1274
1366
  end
1275
1367
 
1276
1368
  def build_order(arel)
1277
- orders = order_values.uniq
1278
- orders.reject!(&:blank?)
1279
-
1369
+ orders = order_values.compact_blank
1280
1370
  arel.order(*orders) unless orders.empty?
1281
1371
  end
1282
1372
 
@@ -1296,12 +1386,6 @@ module ActiveRecord
1296
1386
  end
1297
1387
 
1298
1388
  def preprocess_order_args(order_args)
1299
- order_args.reject!(&:blank?)
1300
- order_args.map! do |arg|
1301
- klass.sanitize_sql_for_order(arg)
1302
- end
1303
- order_args.flatten!
1304
-
1305
1389
  @klass.disallow_raw_sql!(
1306
1390
  order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1307
1391
  permit: connection.column_name_with_order_matcher
@@ -1309,9 +1393,8 @@ module ActiveRecord
1309
1393
 
1310
1394
  validate_order_args(order_args)
1311
1395
 
1312
- references = order_args.grep(String)
1313
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1314
- references!(references) if references.any?
1396
+ references = column_references(order_args)
1397
+ self.references_values |= references unless references.empty?
1315
1398
 
1316
1399
  # if a symbol is given we prepend the quoted table name
1317
1400
  order_args.map! do |arg|
@@ -1322,9 +1405,9 @@ module ActiveRecord
1322
1405
  arg.map { |field, dir|
1323
1406
  case field
1324
1407
  when Arel::Nodes::SqlLiteral
1325
- field.send(dir.downcase)
1408
+ field.public_send(dir.downcase)
1326
1409
  else
1327
- order_column(field.to_s).send(dir.downcase)
1410
+ order_column(field.to_s).public_send(dir.downcase)
1328
1411
  end
1329
1412
  }
1330
1413
  else
@@ -1333,16 +1416,54 @@ module ActiveRecord
1333
1416
  end.flatten!
1334
1417
  end
1335
1418
 
1419
+ def sanitize_order_arguments(order_args)
1420
+ order_args.map! do |arg|
1421
+ klass.sanitize_sql_for_order(arg)
1422
+ end
1423
+ order_args.flatten!
1424
+ order_args.compact_blank!
1425
+ end
1426
+
1427
+ def column_references(order_args)
1428
+ references = order_args.grep(String)
1429
+ references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1430
+ references
1431
+ end
1432
+
1336
1433
  def order_column(field)
1337
1434
  arel_column(field) do |attr_name|
1338
1435
  if attr_name == "count" && !group_values.empty?
1339
- arel_attribute(attr_name)
1436
+ table[attr_name]
1340
1437
  else
1341
1438
  Arel.sql(connection.quote_table_name(attr_name))
1342
1439
  end
1343
1440
  end
1344
1441
  end
1345
1442
 
1443
+ def resolve_arel_attributes(attrs)
1444
+ attrs.flat_map do |attr|
1445
+ case attr
1446
+ when Arel::Predications
1447
+ attr
1448
+ when Hash
1449
+ attr.flat_map do |table, columns|
1450
+ table = table.to_s
1451
+ Array(columns).map do |column|
1452
+ predicate_builder.resolve_arel_attribute(table, column)
1453
+ end
1454
+ end
1455
+ else
1456
+ attr = attr.to_s
1457
+ if attr.include?(".")
1458
+ table, column = attr.split(".", 2)
1459
+ predicate_builder.resolve_arel_attribute(table, column)
1460
+ else
1461
+ attr
1462
+ end
1463
+ end
1464
+ end
1465
+ end
1466
+
1346
1467
  # Checks to make sure that the arguments are not blank. Note that if some
1347
1468
  # blank-like object were initially passed into the query method, then this
1348
1469
  # method will not raise an error.
@@ -1359,38 +1480,40 @@ module ActiveRecord
1359
1480
  # check_if_method_has_arguments!("references", args)
1360
1481
  # ...
1361
1482
  # end
1362
- def check_if_method_has_arguments!(method_name, args)
1483
+ def check_if_method_has_arguments!(method_name, args, message = nil)
1363
1484
  if args.blank?
1364
- raise ArgumentError, "The method .#{method_name}() must contain arguments."
1485
+ raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
1486
+ elsif block_given?
1487
+ yield args
1488
+ else
1489
+ args.flatten!
1490
+ args.compact_blank!
1365
1491
  end
1366
1492
  end
1367
1493
 
1368
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
1369
- def structurally_incompatible_values_for_or(other)
1494
+ STRUCTURAL_VALUE_METHODS = (
1495
+ Relation::VALUE_METHODS -
1496
+ [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
1497
+ ).freeze # :nodoc:
1498
+
1499
+ def structurally_incompatible_values_for(other)
1370
1500
  values = other.values
1371
- STRUCTURAL_OR_METHODS.reject do |method|
1372
- default = DEFAULT_VALUES[method]
1373
- v1, v2 = @values.fetch(method, default), values.fetch(method, default)
1374
- v1 = v1.uniq if v1.is_a?(Array)
1375
- v2 = v2.uniq if v2.is_a?(Array)
1376
- v1 == v2
1501
+ STRUCTURAL_VALUE_METHODS.reject do |method|
1502
+ v1, v2 = @values[method], values[method]
1503
+ if v1.is_a?(Array)
1504
+ next true unless v2.is_a?(Array)
1505
+ v1 = v1.uniq
1506
+ v2 = v2.uniq
1507
+ end
1508
+ v1 == v2 || (!v1 || v1.empty?) && (!v2 || v2.empty?)
1377
1509
  end
1378
1510
  end
1511
+ end
1379
1512
 
1380
- def where_clause_factory
1381
- @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1382
- end
1383
- alias having_clause_factory where_clause_factory
1384
-
1385
- DEFAULT_VALUES = {
1386
- create_with: FROZEN_EMPTY_HASH,
1387
- where: Relation::WhereClause.empty,
1388
- having: Relation::WhereClause.empty,
1389
- from: Relation::FromClause.empty
1390
- }
1391
-
1392
- Relation::MULTI_VALUE_METHODS.each do |value|
1393
- DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
1394
- end
1513
+ class Relation # :nodoc:
1514
+ # No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
1515
+ # TODO: Remove the class once Rails 6.1 has released.
1516
+ class WhereClauseFactory # :nodoc:
1517
+ end
1395
1518
  end
1396
1519
  end