activerecord 6.0.0 → 6.1.0

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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +872 -582
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -13
  6. data/lib/active_record/aggregations.rb +1 -2
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations.rb +116 -13
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +49 -29
  11. data/lib/active_record/associations/association_scope.rb +17 -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 -3
  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 +25 -8
  22. data/lib/active_record/associations/collection_proxy.rb +14 -7
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -3
  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 +77 -42
  28. data/lib/active_record/associations/join_dependency/join_association.rb +36 -14
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +13 -8
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -9
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
  38. data/lib/active_record/attribute_methods/dirty.rb +3 -13
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -4
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -12
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -6
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +12 -21
  45. data/lib/active_record/attributes.rb +32 -8
  46. data/lib/active_record/autosave_association.rb +63 -44
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +153 -24
  49. data/lib/active_record/coders/yaml_column.rb +1 -2
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +86 -37
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +4 -9
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -52
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +263 -107
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +82 -35
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +74 -76
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
  77. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  93. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
  100. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  101. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
  102. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  103. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  104. data/lib/active_record/connection_adapters/postgresql_adapter.rb +81 -57
  105. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  106. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  107. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
  108. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
  109. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  110. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
  111. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -57
  112. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  113. data/lib/active_record/connection_handling.rb +211 -81
  114. data/lib/active_record/core.rb +237 -69
  115. data/lib/active_record/counter_cache.rb +4 -1
  116. data/lib/active_record/database_configurations.rb +124 -85
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  118. data/lib/active_record/database_configurations/database_config.rb +52 -9
  119. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  120. data/lib/active_record/database_configurations/url_config.rb +15 -41
  121. data/lib/active_record/delegated_type.rb +209 -0
  122. data/lib/active_record/destroy_association_async_job.rb +36 -0
  123. data/lib/active_record/dynamic_matchers.rb +2 -3
  124. data/lib/active_record/enum.rb +40 -16
  125. data/lib/active_record/errors.rb +47 -12
  126. data/lib/active_record/explain.rb +9 -5
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +10 -17
  129. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  130. data/lib/active_record/fixture_set/render_context.rb +1 -1
  131. data/lib/active_record/fixture_set/table_row.rb +2 -3
  132. data/lib/active_record/fixture_set/table_rows.rb +0 -1
  133. data/lib/active_record/fixtures.rb +54 -11
  134. data/lib/active_record/gem_version.rb +1 -1
  135. data/lib/active_record/inheritance.rb +40 -21
  136. data/lib/active_record/insert_all.rb +39 -10
  137. data/lib/active_record/integration.rb +3 -5
  138. data/lib/active_record/internal_metadata.rb +16 -7
  139. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  140. data/lib/active_record/locking/optimistic.rb +22 -17
  141. data/lib/active_record/locking/pessimistic.rb +6 -2
  142. data/lib/active_record/log_subscriber.rb +27 -9
  143. data/lib/active_record/middleware/database_selector.rb +4 -2
  144. data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
  145. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  146. data/lib/active_record/migration.rb +114 -84
  147. data/lib/active_record/migration/command_recorder.rb +53 -45
  148. data/lib/active_record/migration/compatibility.rb +70 -20
  149. data/lib/active_record/migration/join_table.rb +0 -1
  150. data/lib/active_record/model_schema.rb +120 -15
  151. data/lib/active_record/nested_attributes.rb +2 -5
  152. data/lib/active_record/no_touching.rb +1 -1
  153. data/lib/active_record/null_relation.rb +0 -1
  154. data/lib/active_record/persistence.rb +50 -46
  155. data/lib/active_record/query_cache.rb +15 -5
  156. data/lib/active_record/querying.rb +12 -7
  157. data/lib/active_record/railtie.rb +65 -45
  158. data/lib/active_record/railties/databases.rake +267 -93
  159. data/lib/active_record/readonly_attributes.rb +4 -0
  160. data/lib/active_record/reflection.rb +77 -63
  161. data/lib/active_record/relation.rb +108 -67
  162. data/lib/active_record/relation/batches.rb +38 -32
  163. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  164. data/lib/active_record/relation/calculations.rb +102 -45
  165. data/lib/active_record/relation/delegation.rb +9 -7
  166. data/lib/active_record/relation/finder_methods.rb +55 -17
  167. data/lib/active_record/relation/from_clause.rb +5 -1
  168. data/lib/active_record/relation/merger.rb +27 -26
  169. data/lib/active_record/relation/predicate_builder.rb +55 -35
  170. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  171. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  173. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  174. data/lib/active_record/relation/query_methods.rb +340 -180
  175. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  176. data/lib/active_record/relation/spawn_methods.rb +8 -8
  177. data/lib/active_record/relation/where_clause.rb +104 -58
  178. data/lib/active_record/result.rb +41 -34
  179. data/lib/active_record/runtime_registry.rb +2 -2
  180. data/lib/active_record/sanitization.rb +6 -17
  181. data/lib/active_record/schema_dumper.rb +34 -4
  182. data/lib/active_record/schema_migration.rb +2 -8
  183. data/lib/active_record/scoping.rb +0 -1
  184. data/lib/active_record/scoping/default.rb +0 -1
  185. data/lib/active_record/scoping/named.rb +7 -18
  186. data/lib/active_record/secure_token.rb +16 -8
  187. data/lib/active_record/serialization.rb +5 -3
  188. data/lib/active_record/signed_id.rb +116 -0
  189. data/lib/active_record/statement_cache.rb +20 -4
  190. data/lib/active_record/store.rb +3 -3
  191. data/lib/active_record/suppressor.rb +2 -2
  192. data/lib/active_record/table_metadata.rb +39 -36
  193. data/lib/active_record/tasks/database_tasks.rb +139 -113
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
  196. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
  197. data/lib/active_record/test_databases.rb +5 -4
  198. data/lib/active_record/test_fixtures.rb +38 -16
  199. data/lib/active_record/timestamp.rb +4 -7
  200. data/lib/active_record/touch_later.rb +20 -21
  201. data/lib/active_record/transactions.rb +22 -71
  202. data/lib/active_record/type.rb +8 -2
  203. data/lib/active_record/type/adapter_specific_registry.rb +2 -5
  204. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  205. data/lib/active_record/type/serialized.rb +6 -3
  206. data/lib/active_record/type/time.rb +10 -0
  207. data/lib/active_record/type/type_map.rb +0 -1
  208. data/lib/active_record/type/unsigned_integer.rb +0 -1
  209. data/lib/active_record/type_caster/connection.rb +0 -1
  210. data/lib/active_record/type_caster/map.rb +8 -5
  211. data/lib/active_record/validations.rb +3 -3
  212. data/lib/active_record/validations/associated.rb +1 -2
  213. data/lib/active_record/validations/numericality.rb +35 -0
  214. data/lib/active_record/validations/uniqueness.rb +24 -4
  215. data/lib/arel.rb +15 -12
  216. data/lib/arel/attributes/attribute.rb +4 -0
  217. data/lib/arel/collectors/bind.rb +5 -0
  218. data/lib/arel/collectors/composite.rb +8 -0
  219. data/lib/arel/collectors/sql_string.rb +7 -0
  220. data/lib/arel/collectors/substitute_binds.rb +7 -0
  221. data/lib/arel/nodes.rb +3 -1
  222. data/lib/arel/nodes/binary.rb +82 -8
  223. data/lib/arel/nodes/bind_param.rb +8 -0
  224. data/lib/arel/nodes/casted.rb +21 -9
  225. data/lib/arel/nodes/equality.rb +6 -9
  226. data/lib/arel/nodes/grouping.rb +3 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  228. data/lib/arel/nodes/in.rb +8 -1
  229. data/lib/arel/nodes/infix_operation.rb +13 -1
  230. data/lib/arel/nodes/join_source.rb +1 -1
  231. data/lib/arel/nodes/node.rb +7 -6
  232. data/lib/arel/nodes/ordering.rb +27 -0
  233. data/lib/arel/nodes/sql_literal.rb +3 -0
  234. data/lib/arel/nodes/table_alias.rb +7 -3
  235. data/lib/arel/nodes/unary.rb +0 -1
  236. data/lib/arel/predications.rb +17 -24
  237. data/lib/arel/select_manager.rb +1 -2
  238. data/lib/arel/table.rb +13 -5
  239. data/lib/arel/visitors.rb +0 -7
  240. data/lib/arel/visitors/dot.rb +14 -3
  241. data/lib/arel/visitors/mysql.rb +11 -1
  242. data/lib/arel/visitors/postgresql.rb +15 -5
  243. data/lib/arel/visitors/sqlite.rb +0 -1
  244. data/lib/arel/visitors/to_sql.rb +89 -79
  245. data/lib/arel/visitors/visitor.rb +0 -1
  246. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  247. data/lib/rails/generators/active_record/migration.rb +6 -2
  248. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  249. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  250. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  251. data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
  252. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  253. metadata +27 -24
  254. data/lib/active_record/attribute_decorators.rb +0 -90
  255. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  256. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  257. data/lib/active_record/define_callbacks.rb +0 -22
  258. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  259. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  260. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  261. data/lib/arel/attributes.rb +0 -22
  262. data/lib/arel/visitors/depth_first.rb +0 -204
  263. data/lib/arel/visitors/ibm_db.rb +0 -34
  264. data/lib/arel/visitors/informix.rb +0 -62
  265. data/lib/arel/visitors/mssql.rb +0 -157
  266. data/lib/arel/visitors/oracle.rb +0 -159
  267. data/lib/arel/visitors/oracle12.rb +0 -66
  268. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -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,13 +27,12 @@ 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)
34
- case value
35
- when Base
36
- value._read_attribute(primary_key)
34
+ if value.respond_to?(:id)
35
+ value.id
37
36
  else
38
37
  value
39
38
  end
@@ -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,53 +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
+ @scope.where_clause += where_clause.invert
48
49
 
49
- @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
50
+ @scope
51
+ end
50
52
 
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 conditions manually
55
- (`#{ opts.keys.map { |key| ".where.not(#{key.inspect} => ...)" }.join }`).
56
- MSG
57
- @scope.where_clause += where_clause.invert(:nor)
58
- else
59
- @scope.where_clause += where_clause.invert
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)
60
77
  end
61
78
 
62
79
  @scope
63
80
  end
64
-
65
- private
66
- def not_behaves_as_nor?(opts)
67
- opts.is_a?(Hash) && opts.size > 1
68
- end
69
81
  end
70
82
 
71
83
  FROZEN_EMPTY_ARRAY = [].freeze
72
84
  FROZEN_EMPTY_HASH = {}.freeze
73
85
 
74
86
  Relation::VALUE_METHODS.each do |name|
75
- method_name = \
87
+ method_name, default =
76
88
  case name
77
- when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
78
- when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
79
- 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"]
80
95
  end
96
+
81
97
  class_eval <<-CODE, __FILE__, __LINE__ + 1
82
- def #{method_name} # def includes_values
83
- default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes]
84
- @values.fetch(:#{name}, default) # @values.fetch(:includes, default)
85
- end # end
86
-
87
- def #{method_name}=(value) # def includes_values=(value)
88
- assert_mutability! # assert_mutability!
89
- @values[:#{name}] = value # @values[:includes] = value
90
- 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
91
106
  CODE
92
107
  end
93
108
 
@@ -138,9 +153,6 @@ module ActiveRecord
138
153
  end
139
154
 
140
155
  def includes!(*args) # :nodoc:
141
- args.reject!(&:blank?)
142
- args.flatten!
143
-
144
156
  self.includes_values |= args
145
157
  self
146
158
  end
@@ -157,7 +169,7 @@ module ActiveRecord
157
169
  end
158
170
 
159
171
  def eager_load!(*args) # :nodoc:
160
- self.eager_load_values += args
172
+ self.eager_load_values |= args
161
173
  self
162
174
  end
163
175
 
@@ -171,7 +183,7 @@ module ActiveRecord
171
183
  end
172
184
 
173
185
  def preload!(*args) # :nodoc:
174
- self.preload_values += args
186
+ self.preload_values |= args
175
187
  self
176
188
  end
177
189
 
@@ -204,9 +216,6 @@ module ActiveRecord
204
216
  end
205
217
 
206
218
  def references!(*table_names) # :nodoc:
207
- table_names.flatten!
208
- table_names.map!(&:to_s)
209
-
210
219
  self.references_values |= table_names
211
220
  self
212
221
  end
@@ -260,14 +269,12 @@ module ActiveRecord
260
269
  return super()
261
270
  end
262
271
 
263
- 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.")
264
273
  spawn._select!(*fields)
265
274
  end
266
275
 
267
276
  def _select!(*fields) # :nodoc:
268
- fields.reject!(&:blank?)
269
- fields.flatten!
270
- self.select_values += fields
277
+ self.select_values |= fields
271
278
  self
272
279
  end
273
280
 
@@ -318,8 +325,6 @@ module ActiveRecord
318
325
  end
319
326
 
320
327
  def group!(*args) # :nodoc:
321
- args.flatten!
322
-
323
328
  self.group_values += args
324
329
  self
325
330
  end
@@ -344,15 +349,16 @@ module ActiveRecord
344
349
  # User.order('name DESC, email')
345
350
  # # SELECT "users".* FROM "users" ORDER BY name DESC, email
346
351
  def order(*args)
347
- check_if_method_has_arguments!(:order, args)
352
+ check_if_method_has_arguments!(:order, args) do
353
+ sanitize_order_arguments(args)
354
+ end
348
355
  spawn.order!(*args)
349
356
  end
350
357
 
351
358
  # Same as #order but operates on relation in-place instead of copying.
352
359
  def order!(*args) # :nodoc:
353
- preprocess_order_args(args)
354
-
355
- self.order_values += args
360
+ preprocess_order_args(args) unless args.empty?
361
+ self.order_values |= args
356
362
  self
357
363
  end
358
364
 
@@ -366,14 +372,16 @@ module ActiveRecord
366
372
  #
367
373
  # generates a query with 'ORDER BY id ASC, name ASC'.
368
374
  def reorder(*args)
369
- 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
370
378
  spawn.reorder!(*args)
371
379
  end
372
380
 
373
381
  # Same as #reorder but operates on relation in-place instead of copying.
374
382
  def reorder!(*args) # :nodoc:
375
383
  preprocess_order_args(args) unless args.all?(&:blank?)
376
-
384
+ args.uniq!
377
385
  self.reordering_value = true
378
386
  self.order_values = args
379
387
  self
@@ -422,7 +430,6 @@ module ActiveRecord
422
430
  end
423
431
 
424
432
  def unscope!(*args) # :nodoc:
425
- args.flatten!
426
433
  self.unscope_values += args
427
434
 
428
435
  args.each do |scope|
@@ -433,14 +440,14 @@ module ActiveRecord
433
440
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
434
441
  end
435
442
  assert_mutability!
436
- @values[scope] = DEFAULT_VALUES[scope]
443
+ @values.delete(scope)
437
444
  when Hash
438
445
  scope.each do |key, target_value|
439
446
  if key != :where
440
447
  raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
441
448
  end
442
449
 
443
- target_values = Array(target_value).map(&:to_s)
450
+ target_values = resolve_arel_attributes(Array.wrap(target_value))
444
451
  self.where_clause = where_clause.except(*target_values)
445
452
  end
446
453
  else
@@ -473,8 +480,7 @@ module ActiveRecord
473
480
  # # SELECT "users".*
474
481
  # # FROM "users"
475
482
  # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
476
- # # INNER JOIN "comments" "comments_posts"
477
- # # ON "comments_posts"."post_id" = "posts"."id"
483
+ # # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
478
484
  #
479
485
  # You can use strings in order to customize your joins:
480
486
  #
@@ -486,9 +492,7 @@ module ActiveRecord
486
492
  end
487
493
 
488
494
  def joins!(*args) # :nodoc:
489
- args.compact!
490
- args.flatten!
491
- self.joins_values += args
495
+ self.joins_values |= args
492
496
  self
493
497
  end
494
498
 
@@ -504,9 +508,7 @@ module ActiveRecord
504
508
  alias :left_joins :left_outer_joins
505
509
 
506
510
  def left_outer_joins!(*args) # :nodoc:
507
- args.compact!
508
- args.flatten!
509
- self.left_outer_joins_values += args
511
+ self.left_outer_joins_values |= args
510
512
  self
511
513
  end
512
514
 
@@ -629,20 +631,18 @@ module ActiveRecord
629
631
  #
630
632
  # If the condition is any blank-ish object, then #where is a no-op and returns
631
633
  # the current relation.
632
- def where(opts = :chain, *rest)
633
- if :chain == opts
634
+ def where(*args)
635
+ if args.empty?
634
636
  WhereChain.new(spawn)
635
- elsif opts.blank?
637
+ elsif args.length == 1 && args.first.blank?
636
638
  self
637
639
  else
638
- spawn.where!(opts, *rest)
640
+ spawn.where!(*args)
639
641
  end
640
642
  end
641
643
 
642
644
  def where!(opts, *rest) # :nodoc:
643
- opts = sanitize_forbidden_attributes(opts)
644
- references!(PredicateBuilder.references(opts)) if Hash === opts
645
- self.where_clause += where_clause_factory.build(opts, rest)
645
+ self.where_clause += build_where_clause(opts, rest)
646
646
  self
647
647
  end
648
648
 
@@ -660,7 +660,44 @@ module ActiveRecord
660
660
  # This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
661
661
  # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
662
662
  def rewhere(conditions)
663
- 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
664
701
  end
665
702
 
666
703
  # Returns a new relation, which is the logical union of this relation and the one passed as an
@@ -668,21 +705,21 @@ module ActiveRecord
668
705
  #
669
706
  # The two relations must be structurally compatible: they must be scoping the same model, and
670
707
  # they must differ only by #where (if no #group has been defined) or #having (if a #group is
671
- # present). Neither relation may have a #limit, #offset, or #distinct set.
708
+ # present).
672
709
  #
673
710
  # Post.where("id = 1").or(Post.where("author_id = 3"))
674
711
  # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
675
712
  #
676
713
  def or(other)
677
- unless other.is_a? Relation
714
+ if other.is_a?(Relation)
715
+ spawn.or!(other)
716
+ else
678
717
  raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
679
718
  end
680
-
681
- spawn.or!(other)
682
719
  end
683
720
 
684
721
  def or!(other) # :nodoc:
685
- incompatible_values = structurally_incompatible_values_for_or(other)
722
+ incompatible_values = structurally_incompatible_values_for(other)
686
723
 
687
724
  unless incompatible_values.empty?
688
725
  raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
@@ -690,7 +727,7 @@ module ActiveRecord
690
727
 
691
728
  self.where_clause = self.where_clause.or(other.where_clause)
692
729
  self.having_clause = having_clause.or(other.having_clause)
693
- self.references_values += other.references_values
730
+ self.references_values |= other.references_values
694
731
 
695
732
  self
696
733
  end
@@ -704,10 +741,7 @@ module ActiveRecord
704
741
  end
705
742
 
706
743
  def having!(opts, *rest) # :nodoc:
707
- opts = sanitize_forbidden_attributes(opts)
708
- references!(PredicateBuilder.references(opts)) if Hash === opts
709
-
710
- self.having_clause += having_clause_factory.build(opts, rest)
744
+ self.having_clause += build_having_clause(opts, rest)
711
745
  self
712
746
  end
713
747
 
@@ -809,6 +843,21 @@ module ActiveRecord
809
843
  self
810
844
  end
811
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
+
812
861
  # Sets attributes to be used when creating new records from a
813
862
  # relation object.
814
863
  #
@@ -950,8 +999,6 @@ module ActiveRecord
950
999
  end
951
1000
 
952
1001
  def optimizer_hints!(*args) # :nodoc:
953
- args.flatten!
954
-
955
1002
  self.optimizer_hints_values |= args
956
1003
  self
957
1004
  end
@@ -964,8 +1011,7 @@ module ActiveRecord
964
1011
  end
965
1012
 
966
1013
  def reverse_order! # :nodoc:
967
- orders = order_values.uniq
968
- orders.reject!(&:blank?)
1014
+ orders = order_values.compact_blank
969
1015
  self.order_values = reverse_sql_order(orders)
970
1016
  self
971
1017
  end
@@ -1000,6 +1046,14 @@ module ActiveRecord
1000
1046
  self
1001
1047
  end
1002
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
+
1003
1057
  # Returns the Arel object associated with the relation.
1004
1058
  def arel(aliases = nil) # :nodoc:
1005
1059
  @arel ||= build_arel(aliases)
@@ -1020,7 +1074,57 @@ module ActiveRecord
1020
1074
  end
1021
1075
  end
1022
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
+
1023
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
+
1024
1128
  def assert_mutability!
1025
1129
  raise ImmutableRelation if @loaded
1026
1130
  raise ImmutableRelation if defined?(@arel) && @arel
@@ -1029,45 +1133,44 @@ module ActiveRecord
1029
1133
  def build_arel(aliases)
1030
1134
  arel = Arel::SelectManager.new(table)
1031
1135
 
1032
- if !joins_values.empty?
1033
- build_joins(arel, joins_values.flatten, aliases)
1034
- elsif !left_outer_joins_values.empty?
1035
- build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
1036
- end
1136
+ build_joins(arel.join_sources, aliases)
1037
1137
 
1038
1138
  arel.where(where_clause.ast) unless where_clause.empty?
1039
1139
  arel.having(having_clause.ast) unless having_clause.empty?
1040
- if limit_value
1041
- limit_attribute = ActiveModel::Attribute.with_cast_value(
1042
- "LIMIT",
1043
- connection.sanitize_limit(limit_value),
1044
- Type.default_value,
1045
- )
1046
- arel.take(Arel::Nodes::BindParam.new(limit_attribute))
1047
- end
1048
- if offset_value
1049
- offset_attribute = ActiveModel::Attribute.with_cast_value(
1050
- "OFFSET",
1051
- offset_value.to_i,
1052
- Type.default_value,
1053
- )
1054
- arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
1055
- end
1056
- 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?
1057
1143
 
1058
1144
  build_order(arel)
1059
-
1060
1145
  build_select(arel)
1061
1146
 
1062
1147
  arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1063
1148
  arel.distinct(distinct_value)
1064
1149
  arel.from(build_from) unless from_clause.empty?
1065
1150
  arel.lock(lock_value) if lock_value
1066
- 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
1067
1165
 
1068
1166
  arel
1069
1167
  end
1070
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
+
1071
1174
  def build_from
1072
1175
  opts = from_clause.value
1073
1176
  name = from_clause.name
@@ -1083,87 +1186,101 @@ module ActiveRecord
1083
1186
  end
1084
1187
  end
1085
1188
 
1086
- def valid_association_list(associations)
1189
+ def select_association_list(associations, stashed_joins = nil)
1190
+ result = []
1087
1191
  associations.each do |association|
1088
1192
  case association
1089
1193
  when Hash, Symbol, Array
1090
- # valid
1194
+ result << association
1195
+ when ActiveRecord::Associations::JoinDependency
1196
+ stashed_joins&.<< association
1091
1197
  else
1092
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1198
+ yield association if block_given?
1093
1199
  end
1094
1200
  end
1201
+ result
1095
1202
  end
1096
1203
 
1097
- def build_left_outer_joins(manager, outer_joins, aliases)
1098
- buckets = Hash.new { |h, k| h[k] = [] }
1099
- buckets[:association_join] = valid_association_list(outer_joins)
1100
- build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
1204
+ class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
1101
1205
  end
1102
1206
 
1103
- def build_joins(manager, joins, aliases)
1207
+ def build_join_buckets
1104
1208
  buckets = Hash.new { |h, k| h[k] = [] }
1105
1209
 
1106
1210
  unless left_outer_joins_values.empty?
1107
- left_joins = valid_association_list(left_outer_joins_values.flatten)
1108
- buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1109
- end
1211
+ stashed_left_joins = []
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
1110
1215
 
1111
- joins.map! do |join|
1112
- if join.is_a?(String)
1113
- table.create_string_join(Arel.sql(join.strip)) unless join.blank?
1216
+ if joins_values.empty?
1217
+ buckets[:association_join] = left_joins
1218
+ buckets[:stashed_join] = stashed_left_joins
1219
+ return buckets, Arel::Nodes::OuterJoin
1114
1220
  else
1115
- join
1221
+ stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1116
1222
  end
1117
- end.delete_if(&:blank?).uniq!
1223
+ end
1224
+
1225
+ joins = joins_values.dup
1226
+ if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1227
+ stashed_eager_load = joins.pop if joins.last.base_klass == klass
1228
+ end
1229
+
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
1118
1233
 
1119
1234
  while joins.first.is_a?(Arel::Nodes::Join)
1120
1235
  join_node = joins.shift
1121
- if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty?
1236
+ if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
1122
1237
  buckets[:join_node] << join_node
1123
1238
  else
1124
1239
  buckets[:leading_join] << join_node
1125
1240
  end
1126
1241
  end
1127
1242
 
1128
- joins.each do |join|
1129
- case join
1130
- when Hash, Symbol, Array
1131
- buckets[:association_join] << join
1132
- when ActiveRecord::Associations::JoinDependency
1133
- buckets[:stashed_join] << join
1134
- 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)
1135
1245
  buckets[:join_node] << join
1136
1246
  else
1137
1247
  raise "unknown class: %s" % join.class.name
1138
1248
  end
1139
1249
  end
1140
1250
 
1141
- build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
1251
+ buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
1252
+ buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
1253
+
1254
+ return buckets, Arel::Nodes::InnerJoin
1142
1255
  end
1143
1256
 
1144
- 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
+
1145
1262
  association_joins = buckets[:association_join]
1146
1263
  stashed_joins = buckets[:stashed_join]
1147
1264
  leading_joins = buckets[:leading_join]
1148
1265
  join_nodes = buckets[:join_node]
1149
1266
 
1150
- join_sources = manager.join_sources
1151
1267
  join_sources.concat(leading_joins) unless leading_joins.empty?
1152
1268
 
1153
1269
  unless association_joins.empty? && stashed_joins.empty?
1154
1270
  alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1155
1271
  join_dependency = construct_join_dependency(association_joins, join_type)
1156
- 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))
1157
1273
  end
1158
1274
 
1159
1275
  join_sources.concat(join_nodes) unless join_nodes.empty?
1276
+ join_sources
1160
1277
  end
1161
1278
 
1162
1279
  def build_select(arel)
1163
1280
  if select_values.any?
1164
- arel.project(*arel_columns(select_values.uniq))
1281
+ arel.project(*arel_columns(select_values))
1165
1282
  elsif klass.ignored_columns.any?
1166
- arel.project(*klass.column_names.map { |field| arel_attribute(field) })
1283
+ arel.project(*klass.column_names.map { |field| table[field] })
1167
1284
  else
1168
1285
  arel.project(table[Arel.star])
1169
1286
  end
@@ -1191,19 +1308,26 @@ module ActiveRecord
1191
1308
  from = from_clause.name || from_clause.value
1192
1309
 
1193
1310
  if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1194
- 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
1195
1317
  else
1196
1318
  yield field
1197
1319
  end
1198
1320
  end
1199
1321
 
1200
1322
  def table_name_matches?(from)
1201
- /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1323
+ table_name = Regexp.escape(table.name)
1324
+ quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
1325
+ /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1202
1326
  end
1203
1327
 
1204
1328
  def reverse_sql_order(order_query)
1205
1329
  if order_query.empty?
1206
- return [arel_attribute(primary_key).desc] if primary_key
1330
+ return [table[primary_key].desc] if primary_key
1207
1331
  raise IrreversibleOrderError,
1208
1332
  "Relation has no current order and table has no primary key to be used as default order"
1209
1333
  end
@@ -1214,6 +1338,8 @@ module ActiveRecord
1214
1338
  o.desc
1215
1339
  when Arel::Nodes::Ordering
1216
1340
  o.reverse
1341
+ when Arel::Nodes::NodeExpression
1342
+ o.desc
1217
1343
  when String
1218
1344
  if does_not_support_reverse?(o)
1219
1345
  raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
@@ -1240,9 +1366,7 @@ module ActiveRecord
1240
1366
  end
1241
1367
 
1242
1368
  def build_order(arel)
1243
- orders = order_values.uniq
1244
- orders.reject!(&:blank?)
1245
-
1369
+ orders = order_values.compact_blank
1246
1370
  arel.order(*orders) unless orders.empty?
1247
1371
  end
1248
1372
 
@@ -1262,12 +1386,6 @@ module ActiveRecord
1262
1386
  end
1263
1387
 
1264
1388
  def preprocess_order_args(order_args)
1265
- order_args.reject!(&:blank?)
1266
- order_args.map! do |arg|
1267
- klass.sanitize_sql_for_order(arg)
1268
- end
1269
- order_args.flatten!
1270
-
1271
1389
  @klass.disallow_raw_sql!(
1272
1390
  order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1273
1391
  permit: connection.column_name_with_order_matcher
@@ -1275,9 +1393,8 @@ module ActiveRecord
1275
1393
 
1276
1394
  validate_order_args(order_args)
1277
1395
 
1278
- references = order_args.grep(String)
1279
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1280
- references!(references) if references.any?
1396
+ references = column_references(order_args)
1397
+ self.references_values |= references unless references.empty?
1281
1398
 
1282
1399
  # if a symbol is given we prepend the quoted table name
1283
1400
  order_args.map! do |arg|
@@ -1288,9 +1405,9 @@ module ActiveRecord
1288
1405
  arg.map { |field, dir|
1289
1406
  case field
1290
1407
  when Arel::Nodes::SqlLiteral
1291
- field.send(dir.downcase)
1408
+ field.public_send(dir.downcase)
1292
1409
  else
1293
- order_column(field.to_s).send(dir.downcase)
1410
+ order_column(field.to_s).public_send(dir.downcase)
1294
1411
  end
1295
1412
  }
1296
1413
  else
@@ -1299,16 +1416,54 @@ module ActiveRecord
1299
1416
  end.flatten!
1300
1417
  end
1301
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
+
1302
1433
  def order_column(field)
1303
1434
  arel_column(field) do |attr_name|
1304
1435
  if attr_name == "count" && !group_values.empty?
1305
- arel_attribute(attr_name)
1436
+ table[attr_name]
1306
1437
  else
1307
1438
  Arel.sql(connection.quote_table_name(attr_name))
1308
1439
  end
1309
1440
  end
1310
1441
  end
1311
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
+
1312
1467
  # Checks to make sure that the arguments are not blank. Note that if some
1313
1468
  # blank-like object were initially passed into the query method, then this
1314
1469
  # method will not raise an error.
@@ -1325,35 +1480,40 @@ module ActiveRecord
1325
1480
  # check_if_method_has_arguments!("references", args)
1326
1481
  # ...
1327
1482
  # end
1328
- def check_if_method_has_arguments!(method_name, args)
1483
+ def check_if_method_has_arguments!(method_name, args, message = nil)
1329
1484
  if args.blank?
1330
- 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!
1331
1491
  end
1332
1492
  end
1333
1493
 
1334
- STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
1335
- 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)
1336
1500
  values = other.values
1337
- STRUCTURAL_OR_METHODS.reject do |method|
1338
- default = DEFAULT_VALUES[method]
1339
- @values.fetch(method, default) == values.fetch(method, default)
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
1340
1509
  end
1341
1510
  end
1511
+ end
1342
1512
 
1343
- def where_clause_factory
1344
- @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
1345
- end
1346
- alias having_clause_factory where_clause_factory
1347
-
1348
- DEFAULT_VALUES = {
1349
- create_with: FROZEN_EMPTY_HASH,
1350
- where: Relation::WhereClause.empty,
1351
- having: Relation::WhereClause.empty,
1352
- from: Relation::FromClause.empty
1353
- }
1354
-
1355
- Relation::MULTI_VALUE_METHODS.each do |value|
1356
- DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
1357
- 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
1358
1518
  end
1359
1519
  end