activerecord 7.2.3 → 8.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +391 -958
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -0
  5. data/lib/active_record/associations/association.rb +34 -10
  6. data/lib/active_record/associations/builder/association.rb +7 -6
  7. data/lib/active_record/associations/collection_association.rb +1 -1
  8. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  9. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  10. data/lib/active_record/associations/preloader/association.rb +2 -2
  11. data/lib/active_record/associations/singular_association.rb +8 -3
  12. data/lib/active_record/associations.rb +34 -4
  13. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  14. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  15. data/lib/active_record/attribute_methods/query.rb +34 -0
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
  17. data/lib/active_record/autosave_association.rb +69 -27
  18. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
  19. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  20. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -15
  22. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  23. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  25. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  26. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
  27. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +34 -7
  28. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  29. data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -43
  30. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
  31. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  32. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  33. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
  34. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  36. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  37. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
  38. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  39. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  40. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  41. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  42. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
  43. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +46 -96
  46. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  47. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
  48. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  49. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  50. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
  51. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
  52. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  53. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  54. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  55. data/lib/active_record/connection_adapters.rb +0 -56
  56. data/lib/active_record/connection_handling.rb +23 -1
  57. data/lib/active_record/core.rb +29 -14
  58. data/lib/active_record/database_configurations/database_config.rb +4 -0
  59. data/lib/active_record/database_configurations/hash_config.rb +16 -2
  60. data/lib/active_record/encryption/config.rb +3 -1
  61. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  62. data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
  63. data/lib/active_record/encryption/encryptor.rb +16 -8
  64. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  65. data/lib/active_record/encryption/scheme.rb +8 -1
  66. data/lib/active_record/enum.rb +9 -22
  67. data/lib/active_record/errors.rb +13 -5
  68. data/lib/active_record/fixtures.rb +0 -2
  69. data/lib/active_record/future_result.rb +13 -9
  70. data/lib/active_record/gem_version.rb +3 -3
  71. data/lib/active_record/insert_all.rb +1 -1
  72. data/lib/active_record/locking/optimistic.rb +1 -1
  73. data/lib/active_record/log_subscriber.rb +5 -11
  74. data/lib/active_record/migration/command_recorder.rb +31 -11
  75. data/lib/active_record/migration/compatibility.rb +5 -2
  76. data/lib/active_record/migration.rb +38 -42
  77. data/lib/active_record/model_schema.rb +3 -4
  78. data/lib/active_record/nested_attributes.rb +4 -6
  79. data/lib/active_record/persistence.rb +128 -130
  80. data/lib/active_record/query_logs.rb +102 -50
  81. data/lib/active_record/query_logs_formatter.rb +17 -28
  82. data/lib/active_record/querying.rb +8 -8
  83. data/lib/active_record/railtie.rb +2 -26
  84. data/lib/active_record/railties/databases.rake +11 -35
  85. data/lib/active_record/reflection.rb +18 -21
  86. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  87. data/lib/active_record/relation/batches.rb +132 -72
  88. data/lib/active_record/relation/calculations.rb +40 -39
  89. data/lib/active_record/relation/delegation.rb +25 -14
  90. data/lib/active_record/relation/finder_methods.rb +18 -18
  91. data/lib/active_record/relation/merger.rb +8 -8
  92. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  93. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  94. data/lib/active_record/relation/predicate_builder.rb +13 -0
  95. data/lib/active_record/relation/query_methods.rb +105 -61
  96. data/lib/active_record/relation/spawn_methods.rb +7 -7
  97. data/lib/active_record/relation.rb +79 -61
  98. data/lib/active_record/result.rb +66 -4
  99. data/lib/active_record/sanitization.rb +7 -6
  100. data/lib/active_record/schema_dumper.rb +5 -0
  101. data/lib/active_record/schema_migration.rb +2 -1
  102. data/lib/active_record/scoping/named.rb +5 -2
  103. data/lib/active_record/statement_cache.rb +14 -14
  104. data/lib/active_record/store.rb +7 -3
  105. data/lib/active_record/table_metadata.rb +1 -3
  106. data/lib/active_record/tasks/database_tasks.rb +69 -60
  107. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  108. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
  109. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  110. data/lib/active_record/test_databases.rb +1 -1
  111. data/lib/active_record/test_fixtures.rb +12 -0
  112. data/lib/active_record/token_for.rb +1 -1
  113. data/lib/active_record/transactions.rb +5 -6
  114. data/lib/active_record/validations/uniqueness.rb +8 -8
  115. data/lib/active_record.rb +21 -48
  116. data/lib/arel/collectors/bind.rb +2 -2
  117. data/lib/arel/collectors/sql_string.rb +1 -1
  118. data/lib/arel/collectors/substitute_binds.rb +2 -2
  119. data/lib/arel/nodes/binary.rb +1 -1
  120. data/lib/arel/nodes/node.rb +1 -1
  121. data/lib/arel/nodes/sql_literal.rb +1 -1
  122. data/lib/arel/table.rb +3 -7
  123. metadata +9 -10
  124. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -22,6 +22,9 @@ module ActiveRecord
22
22
  end
23
23
 
24
24
  module DelegateCache # :nodoc:
25
+ @delegate_base_methods = true
26
+ singleton_class.attr_accessor :delegate_base_methods
27
+
25
28
  def relation_delegate_class(klass)
26
29
  @relation_delegate_cache[klass]
27
30
  end
@@ -75,12 +78,12 @@ module ActiveRecord
75
78
  if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
76
79
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
77
80
  def #{method}(...)
78
- scoping { klass.#{method}(...) }
81
+ scoping { model.#{method}(...) }
79
82
  end
80
83
  RUBY
81
84
  else
82
85
  define_method(method) do |*args, **kwargs, &block|
83
- scoping { klass.public_send(method, *args, **kwargs, &block) }
86
+ scoping { model.public_send(method, *args, **kwargs, &block) }
84
87
  end
85
88
  end
86
89
  end
@@ -92,15 +95,15 @@ module ActiveRecord
92
95
 
93
96
  # This module creates compiled delegation methods dynamically at runtime, which makes
94
97
  # subsequent calls to that method faster by avoiding method_missing. The delegations
95
- # may vary depending on the klass of a relation, so we create a subclass of Relation
96
- # for each different klass, and the delegations are compiled into that subclass only.
98
+ # may vary depending on the model of a relation, so we create a subclass of Relation
99
+ # for each different model, and the delegations are compiled into that subclass only.
97
100
 
98
101
  delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
99
102
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
100
103
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
101
104
  :shuffle, :split, :slice, :index, :rindex, to: :records
102
105
 
103
- delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
106
+ delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
104
107
 
105
108
  module ClassSpecificRelation # :nodoc:
106
109
  extend ActiveSupport::Concern
@@ -113,11 +116,19 @@ module ActiveRecord
113
116
 
114
117
  private
115
118
  def method_missing(method, ...)
116
- if @klass.respond_to?(method)
117
- unless Delegation.uncacheable_methods.include?(method)
118
- @klass.generate_relation_method(method)
119
+ if model.respond_to?(method)
120
+ if !DelegateCache.delegate_base_methods && Base.respond_to?(method)
121
+ # A common mistake in Active Record's own code is to call `ActiveRecord::Base`
122
+ # class methods on Association. It works because it's automatically delegated, but
123
+ # can introduce subtle bugs because it sets the global scope.
124
+ # We can't deprecate this behavior because gems might depend on it, however we
125
+ # can ban it from Active Record's own test suite to avoid regressions.
126
+ raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods"
127
+ elsif !Delegation.uncacheable_methods.include?(method)
128
+ model.generate_relation_method(method)
119
129
  end
120
- scoping { @klass.public_send(method, ...) }
130
+
131
+ scoping { model.public_send(method, ...) }
121
132
  else
122
133
  super
123
134
  end
@@ -125,19 +136,19 @@ module ActiveRecord
125
136
  end
126
137
 
127
138
  module ClassMethods # :nodoc:
128
- def create(klass, *args, **kwargs)
129
- relation_class_for(klass).new(klass, *args, **kwargs)
139
+ def create(model, ...)
140
+ relation_class_for(model).new(model, ...)
130
141
  end
131
142
 
132
143
  private
133
- def relation_class_for(klass)
134
- klass.relation_delegate_class(self)
144
+ def relation_class_for(model)
145
+ model.relation_delegate_class(self)
135
146
  end
136
147
  end
137
148
 
138
149
  private
139
150
  def respond_to_missing?(method, _)
140
- super || @klass.respond_to?(method)
151
+ super || model.respond_to?(method)
141
152
  end
142
153
  end
143
154
  end
@@ -145,10 +145,10 @@ module ActiveRecord
145
145
 
146
146
  if found.nil?
147
147
  raise_record_not_found_exception!
148
- elsif undesired.present?
149
- raise ActiveRecord::SoleRecordExceeded.new(self)
150
- else
148
+ elsif undesired.nil?
151
149
  found
150
+ else
151
+ raise ActiveRecord::SoleRecordExceeded.new(model)
152
152
  end
153
153
  end
154
154
 
@@ -376,7 +376,7 @@ module ActiveRecord
376
376
 
377
377
  skip_query_cache_if_necessary do
378
378
  with_connection do |c|
379
- c.select_rows(relation.arel, "#{name} Exists?").size == 1
379
+ c.select_rows(relation.arel, "#{model.name} Exists?").size == 1
380
380
  end
381
381
  end
382
382
  end
@@ -389,7 +389,7 @@ module ActiveRecord
389
389
  def include?(record)
390
390
  # The existing implementation relies on receiving an Active Record instance as the input parameter named record.
391
391
  # Any non-Active Record object passed to this implementation is guaranteed to return `false`.
392
- return false unless record.is_a?(klass)
392
+ return false unless record.is_a?(model)
393
393
 
394
394
  if loaded? || offset_value || limit_value || having_clause.any?
395
395
  records.include?(record)
@@ -415,9 +415,9 @@ module ActiveRecord
415
415
  # the expected number of results should be provided in the +expected_size+
416
416
  # argument.
417
417
  def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
418
- conditions = " [#{arel.where_sql(klass)}]" unless where_clause.empty?
418
+ conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty?
419
419
 
420
- name = @klass.name
420
+ name = model.name
421
421
 
422
422
  if ids.nil?
423
423
  error = +"Couldn't find #{name}"
@@ -472,7 +472,7 @@ module ActiveRecord
472
472
  )
473
473
  )
474
474
  relation = skip_query_cache_if_necessary do
475
- klass.with_connection do |c|
475
+ model.with_connection do |c|
476
476
  c.distinct_relation_for_primary_key(relation)
477
477
  end
478
478
  end
@@ -490,9 +490,9 @@ module ActiveRecord
490
490
  end
491
491
 
492
492
  def find_with_ids(*ids)
493
- raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
493
+ raise UnknownPrimaryKey.new(model) if primary_key.nil?
494
494
 
495
- expects_array = if klass.composite_primary_key?
495
+ expects_array = if model.composite_primary_key?
496
496
  ids.first.first.is_a?(Array)
497
497
  else
498
498
  ids.first.is_a?(Array)
@@ -504,7 +504,7 @@ module ActiveRecord
504
504
 
505
505
  ids = ids.compact.uniq
506
506
 
507
- model_name = @klass.name
507
+ model_name = model.name
508
508
 
509
509
  case ids.size
510
510
  when 0
@@ -526,7 +526,7 @@ module ActiveRecord
526
526
  MSG
527
527
  end
528
528
 
529
- relation = if klass.composite_primary_key?
529
+ relation = if model.composite_primary_key?
530
530
  where(primary_key.zip(id).to_h)
531
531
  else
532
532
  where(primary_key => id)
@@ -574,7 +574,7 @@ module ActiveRecord
574
574
  result = relation.records
575
575
 
576
576
  if result.size == ids.size
577
- result.in_order_of(:id, ids.map { |id| @klass.type_for_attribute(primary_key).cast(id) })
577
+ result.in_order_of(:id, ids.map { |id| model.type_for_attribute(primary_key).cast(id) })
578
578
  else
579
579
  raise_record_not_found_exception!(ids, result.size, ids.size)
580
580
  end
@@ -639,7 +639,7 @@ module ActiveRecord
639
639
  end
640
640
 
641
641
  def ordered_relation
642
- if order_values.empty? && (implicit_order_column || !query_constraints_list.nil? || primary_key)
642
+ if order_values.empty? && (model.implicit_order_column || !model.query_constraints_list.nil? || primary_key)
643
643
  order(_order_columns.map { |column| table[column].asc })
644
644
  else
645
645
  self
@@ -649,11 +649,11 @@ module ActiveRecord
649
649
  def _order_columns
650
650
  oc = []
651
651
 
652
- oc << implicit_order_column if implicit_order_column
653
- oc << query_constraints_list if query_constraints_list
652
+ oc << model.implicit_order_column if model.implicit_order_column
653
+ oc << model.query_constraints_list if model.query_constraints_list
654
654
 
655
- if primary_key && query_constraints_list.nil?
656
- oc << primary_key
655
+ if model.primary_key && model.query_constraints_list.nil?
656
+ oc << model.primary_key
657
657
  end
658
658
 
659
659
  oc.flatten.uniq.compact
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
  # the values.
25
25
  def other
26
26
  other = Relation.create(
27
- relation.klass,
27
+ relation.model,
28
28
  table: relation.table,
29
29
  predicate_builder: relation.predicate_builder
30
30
  )
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  def merge_select_values
85
85
  return if other.select_values.empty?
86
86
 
87
- if other.klass == relation.klass
87
+ if other.model == relation.model
88
88
  relation.select_values |= other.select_values
89
89
  else
90
90
  relation.select_values |= other.instance_eval do
@@ -96,12 +96,12 @@ module ActiveRecord
96
96
  def merge_preloads
97
97
  return if other.preload_values.empty? && other.includes_values.empty?
98
98
 
99
- if other.klass == relation.klass
99
+ if other.model == relation.model
100
100
  relation.preload_values |= other.preload_values unless other.preload_values.empty?
101
101
  relation.includes_values |= other.includes_values unless other.includes_values.empty?
102
102
  else
103
- reflection = relation.klass.reflect_on_all_associations.find do |r|
104
- r.class_name == other.klass.name
103
+ reflection = relation.model.reflect_on_all_associations.find do |r|
104
+ r.class_name == other.model.name
105
105
  end || return
106
106
 
107
107
  unless other.preload_values.empty?
@@ -117,7 +117,7 @@ module ActiveRecord
117
117
  def merge_joins
118
118
  return if other.joins_values.empty?
119
119
 
120
- if other.klass == relation.klass
120
+ if other.model == relation.model
121
121
  relation.joins_values |= other.joins_values
122
122
  else
123
123
  associations, others = other.joins_values.partition do |join|
@@ -136,7 +136,7 @@ module ActiveRecord
136
136
  def merge_outer_joins
137
137
  return if other.left_outer_joins_values.empty?
138
138
 
139
- if other.klass == relation.klass
139
+ if other.model == relation.model
140
140
  relation.left_outer_joins_values |= other.left_outer_joins_values
141
141
  else
142
142
  associations, others = other.left_outer_joins_values.partition do |join|
@@ -185,7 +185,7 @@ module ActiveRecord
185
185
 
186
186
  def replace_from_clause?
187
187
  relation.from_clause.empty? && !other.from_clause.empty? &&
188
- relation.klass.base_class == other.klass.base_class
188
+ relation.model.base_class == other.model.base_class
189
189
  end
190
190
  end
191
191
  end
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
  if value.is_a?(Base)
38
38
  value.class
39
39
  elsif value.is_a?(Relation)
40
- value.klass
40
+ value.model
41
41
  end
42
42
  end
43
43
 
@@ -9,10 +9,11 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  if value.select_values.empty?
12
- if value.klass.composite_primary_key?
13
- raise ArgumentError, "Cannot map composite primary key #{value.klass.primary_key} to #{attribute.name}"
12
+ model = value.model
13
+ if model.composite_primary_key?
14
+ raise ArgumentError, "Cannot map composite primary key #{model.primary_key} to #{attribute.name}"
14
15
  else
15
- value = value.select(value.table[value.klass.primary_key])
16
+ value = value.select(value.table[model.primary_key])
16
17
  end
17
18
  end
18
19
 
@@ -72,11 +72,24 @@ module ActiveRecord
72
72
  table.associated_table(table_name, &block).arel_table[column_name]
73
73
  end
74
74
 
75
+ def with(table)
76
+ other = dup
77
+ other.table = table
78
+ other
79
+ end
80
+
75
81
  protected
82
+ attr_writer :table
83
+
76
84
  def expand_from_hash(attributes, &block)
77
85
  return ["1=0"] if attributes.empty?
78
86
 
79
87
  attributes.flat_map do |key, value|
88
+ if key.is_a?(Array) && key.size == 1
89
+ key = key.first
90
+ value = value.flatten
91
+ end
92
+
80
93
  if key.is_a?(Array)
81
94
  queries = Array(value).map do |ids_set|
82
95
  raise ArgumentError, "Expected corresponding value for #{key} to be an Array" unless ids_set.is_a?(Array)
@@ -92,10 +92,11 @@ module ActiveRecord
92
92
  @scope.joins!(association)
93
93
  end
94
94
 
95
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
95
96
  if reflection.options[:class_name]
96
- self.not(association => { reflection.association_primary_key => nil })
97
+ self.not(association => association_conditions)
97
98
  else
98
- self.not(reflection.table_name => { reflection.association_primary_key => nil })
99
+ self.not(reflection.table_name => association_conditions)
99
100
  end
100
101
  end
101
102
 
@@ -124,10 +125,11 @@ module ActiveRecord
124
125
  associations.each do |association|
125
126
  reflection = scope_association_reflection(association)
126
127
  @scope.left_outer_joins!(association)
128
+ association_conditions = Array(reflection.association_primary_key).index_with(nil)
127
129
  if reflection.options[:class_name]
128
- @scope.where!(association => { reflection.association_primary_key => nil })
130
+ @scope.where!(association => association_conditions)
129
131
  else
130
- @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
132
+ @scope.where!(reflection.table_name => association_conditions)
131
133
  end
132
134
  end
133
135
 
@@ -136,9 +138,10 @@ module ActiveRecord
136
138
 
137
139
  private
138
140
  def scope_association_reflection(association)
139
- reflection = @scope.klass._reflect_on_association(association)
141
+ model = @scope.model
142
+ reflection = model._reflect_on_association(association)
140
143
  unless reflection
141
- raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
144
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{model.name}`.")
142
145
  end
143
146
  reflection
144
147
  end
@@ -254,6 +257,10 @@ module ActiveRecord
254
257
  self
255
258
  end
256
259
 
260
+ def all # :nodoc:
261
+ spawn
262
+ end
263
+
257
264
  # Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
258
265
  # Performs a single query joining all specified associations. For example:
259
266
  #
@@ -500,7 +507,7 @@ module ActiveRecord
500
507
  #
501
508
  # Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
502
509
  # # => ActiveRecord::Relation
503
- # # WITH post_and_replies AS (
510
+ # # WITH RECURSIVE post_and_replies AS (
504
511
  # # (SELECT * FROM posts WHERE id = 42)
505
512
  # # UNION ALL
506
513
  # # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
@@ -698,26 +705,39 @@ module ActiveRecord
698
705
  # # WHEN "conversations"."status" = 0 THEN 3
699
706
  # # END ASC
700
707
  #
701
- def in_order_of(column, values)
702
- klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
708
+ # +filter+ can be set to +false+ to include all results instead of only the ones specified in +values+.
709
+ #
710
+ # Conversation.in_order_of(:status, [:archived, :active], filter: false)
711
+ # # SELECT "conversations".* FROM "conversations"
712
+ # # ORDER BY CASE
713
+ # # WHEN "conversations"."status" = 1 THEN 1
714
+ # # WHEN "conversations"."status" = 0 THEN 2
715
+ # # ELSE 3
716
+ # # END ASC
717
+ def in_order_of(column, values, filter: true)
718
+ model.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
703
719
  return spawn.none! if values.empty?
704
720
 
705
721
  references = column_references([column])
706
722
  self.references_values |= references unless references.empty?
707
723
 
708
- values = values.map { |value| type_caster.type_cast_for_database(column, value) }
724
+ values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
709
725
  arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
710
726
 
711
- where_clause =
712
- if values.include?(nil)
713
- arel_column.in(values.compact).or(arel_column.eq(nil))
714
- else
715
- arel_column.in(values)
716
- end
727
+ scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))
717
728
 
718
- spawn
719
- .order!(build_case_for_value_position(arel_column, values))
720
- .where!(where_clause)
729
+ if filter
730
+ where_clause =
731
+ if values.include?(nil)
732
+ arel_column.in(values.compact).or(arel_column.eq(nil))
733
+ else
734
+ arel_column.in(values)
735
+ end
736
+
737
+ scope = scope.where!(where_clause)
738
+ end
739
+
740
+ scope
721
741
  end
722
742
 
723
743
  # Replaces any existing order defined on the relation with the specified order.
@@ -1556,8 +1576,8 @@ module ActiveRecord
1556
1576
  records.flatten!(1)
1557
1577
  records.compact!
1558
1578
 
1559
- unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
1560
- raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
1579
+ unless records.all?(model) && relations.all? { |relation| relation.model == model }
1580
+ raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
1561
1581
  end
1562
1582
 
1563
1583
  spawn.excluding!(records + relations.flat_map(&:ids))
@@ -1577,7 +1597,7 @@ module ActiveRecord
1577
1597
 
1578
1598
  def construct_join_dependency(associations, join_type) # :nodoc:
1579
1599
  ActiveRecord::Associations::JoinDependency.new(
1580
- klass, table, associations, join_type
1600
+ model, table, associations, join_type
1581
1601
  )
1582
1602
  end
1583
1603
 
@@ -1606,15 +1626,15 @@ module ActiveRecord
1606
1626
  elsif opts.include?("?")
1607
1627
  parts = [build_bound_sql_literal(opts, rest)]
1608
1628
  else
1609
- parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1629
+ parts = [model.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
1610
1630
  end
1611
1631
  when Hash
1612
1632
  opts = opts.transform_keys do |key|
1613
1633
  if key.is_a?(Array)
1614
- key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
1634
+ key.map { |k| model.attribute_aliases[k.to_s] || k.to_s }
1615
1635
  else
1616
1636
  key = key.to_s
1617
- klass.attribute_aliases[key] || key
1637
+ model.attribute_aliases[key] || key
1618
1638
  end
1619
1639
  end
1620
1640
  references = PredicateBuilder.references(opts)
@@ -1642,12 +1662,8 @@ module ActiveRecord
1642
1662
  def arel_columns(columns)
1643
1663
  columns.flat_map do |field|
1644
1664
  case field
1645
- when Symbol
1646
- arel_column(field.to_s) do |attr_name|
1647
- adapter_class.quote_table_name(attr_name)
1648
- end
1649
- when String
1650
- arel_column(field, &:itself)
1665
+ when Symbol, String
1666
+ arel_column(field)
1651
1667
  when Proc
1652
1668
  field.call
1653
1669
  when Hash
@@ -1830,7 +1846,7 @@ module ActiveRecord
1830
1846
 
1831
1847
  joins = joins_values.dup
1832
1848
  if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1833
- stashed_eager_load = joins.pop if joins.last.base_klass == klass
1849
+ stashed_eager_load = joins.pop if joins.last.base_klass == model
1834
1850
  end
1835
1851
 
1836
1852
  joins.each_with_index do |join, i|
@@ -1887,8 +1903,8 @@ module ActiveRecord
1887
1903
  def build_select(arel)
1888
1904
  if select_values.any?
1889
1905
  arel.project(*arel_columns(select_values))
1890
- elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
1891
- arel.project(*klass.column_names.map { |field| table[field] })
1906
+ elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements
1907
+ arel.project(*model.column_names.map { |field| table[field] })
1892
1908
  else
1893
1909
  arel.project(table[Arel.star])
1894
1910
  end
@@ -1912,7 +1928,8 @@ module ActiveRecord
1912
1928
 
1913
1929
  def build_with_expression_from_value(value, nested = false)
1914
1930
  case value
1915
- when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
1931
+ when Arel::Nodes::SqlLiteral, Arel::Nodes::BoundSqlLiteral
1932
+ Arel::Nodes::Grouping.new(value)
1916
1933
  when ActiveRecord::Relation
1917
1934
  if nested
1918
1935
  value.arel.ast
@@ -1939,29 +1956,60 @@ module ActiveRecord
1939
1956
  with_table = Arel::Table.new(name)
1940
1957
 
1941
1958
  table.join(with_table, kind).on(
1942
- with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
1959
+ with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key])
1943
1960
  ).join_sources.first
1944
1961
  end
1945
1962
 
1963
+ def arel_columns_from_hash(fields)
1964
+ fields.flat_map do |table_name, columns|
1965
+ table_name = table_name.name if table_name.is_a?(Symbol)
1966
+ case columns
1967
+ when Symbol, String
1968
+ arel_column_with_table(table_name, columns)
1969
+ when Array
1970
+ columns.map do |column|
1971
+ arel_column_with_table(table_name, column)
1972
+ end
1973
+ else
1974
+ raise TypeError, "Expected Symbol, String or Array, got: #{columns.class}"
1975
+ end
1976
+ end
1977
+ end
1978
+
1979
+ def arel_column_with_table(table_name, column_name)
1980
+ self.references_values |= [Arel.sql(table_name, retryable: true)]
1981
+
1982
+ if column_name.is_a?(Symbol) || !column_name.match?(/\W/)
1983
+ predicate_builder.resolve_arel_attribute(table_name, column_name) do
1984
+ lookup_table_klass_from_join_dependencies(table_name)
1985
+ end
1986
+ else
1987
+ Arel.sql("#{model.adapter_class.quote_table_name(table_name)}.#{column_name}")
1988
+ end
1989
+ end
1990
+
1946
1991
  def arel_column(field)
1947
- field = klass.attribute_aliases[field] || field
1992
+ field = field.name if is_symbol = field.is_a?(Symbol)
1993
+
1994
+ field = model.attribute_aliases[field] || field.to_s
1948
1995
  from = from_clause.name || from_clause.value
1949
1996
 
1950
- if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1997
+ if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
1951
1998
  table[field]
1952
1999
  elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
1953
- self.references_values |= [Arel.sql(table, retryable: true)]
1954
- predicate_builder.resolve_arel_attribute(table, column) do
1955
- lookup_table_klass_from_join_dependencies(table)
1956
- end
1957
- else
2000
+ arel_column_with_table(table, column)
2001
+ elsif block_given?
1958
2002
  yield field
2003
+ elsif Arel.arel_node?(field)
2004
+ field
2005
+ else
2006
+ Arel.sql(is_symbol ? model.adapter_class.quote_table_name(field) : field)
1959
2007
  end
1960
2008
  end
1961
2009
 
1962
2010
  def table_name_matches?(from)
1963
2011
  table_name = Regexp.escape(table.name)
1964
- quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
2012
+ quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name))
1965
2013
  /(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
1966
2014
  end
1967
2015
 
@@ -2032,7 +2080,7 @@ module ActiveRecord
2032
2080
  end
2033
2081
 
2034
2082
  def preprocess_order_args(order_args)
2035
- @klass.disallow_raw_sql!(
2083
+ model.disallow_raw_sql!(
2036
2084
  flattened_args(order_args),
2037
2085
  permit: model.adapter_class.column_name_with_order_matcher
2038
2086
  )
@@ -2070,7 +2118,7 @@ module ActiveRecord
2070
2118
 
2071
2119
  def sanitize_order_arguments(order_args)
2072
2120
  order_args.map! do |arg|
2073
- klass.sanitize_sql_for_order(arg)
2121
+ model.sanitize_sql_for_order(arg)
2074
2122
  end
2075
2123
  end
2076
2124
 
@@ -2108,17 +2156,18 @@ module ActiveRecord
2108
2156
  if attr_name == "count" && !group_values.empty?
2109
2157
  table[attr_name]
2110
2158
  else
2111
- Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
2159
+ Arel.sql(model.adapter_class.quote_table_name(attr_name), retryable: true)
2112
2160
  end
2113
2161
  end
2114
2162
  end
2115
2163
 
2116
- def build_case_for_value_position(column, values)
2164
+ def build_case_for_value_position(column, values, filter: true)
2117
2165
  node = Arel::Nodes::Case.new
2118
2166
  values.each.with_index(1) do |value, order|
2119
2167
  node.when(column.eq(value)).then(order)
2120
2168
  end
2121
2169
 
2170
+ node = node.else(values.length + 1) unless filter
2122
2171
  Arel::Nodes::Ascending.new(node)
2123
2172
  end
2124
2173
 
@@ -2176,34 +2225,29 @@ module ActiveRecord
2176
2225
  def process_select_args(fields)
2177
2226
  fields.flat_map do |field|
2178
2227
  if field.is_a?(Hash)
2179
- arel_columns_from_hash(field)
2228
+ arel_column_aliases_from_hash(field)
2180
2229
  else
2181
2230
  field
2182
2231
  end
2183
2232
  end
2184
2233
  end
2185
2234
 
2186
- def arel_columns_from_hash(fields)
2235
+ def arel_column_aliases_from_hash(fields)
2187
2236
  fields.flat_map do |key, columns_aliases|
2237
+ table_name = key.is_a?(Symbol) ? key.name : key
2188
2238
  case columns_aliases
2189
2239
  when Hash
2190
2240
  columns_aliases.map do |column, column_alias|
2191
- if values[:joins]&.include?(key)
2192
- references = PredicateBuilder.references({ key.to_s => fields[key] })
2193
- self.references_values |= references unless references.empty?
2194
- end
2195
- arel_column("#{key}.#{column}") do
2196
- predicate_builder.resolve_arel_attribute(key.to_s, column)
2197
- end.as(column_alias.to_s)
2241
+ arel_column_with_table(table_name, column)
2242
+ .as(model.adapter_class.quote_column_name(column_alias.to_s))
2198
2243
  end
2199
2244
  when Array
2200
2245
  columns_aliases.map do |column|
2201
- arel_column("#{key}.#{column}", &:itself)
2246
+ arel_column_with_table(table_name, column)
2202
2247
  end
2203
2248
  when String, Symbol
2204
- arel_column(key.to_s) do
2205
- predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
2206
- end.as(columns_aliases.to_s)
2249
+ arel_column(key)
2250
+ .as(model.adapter_class.quote_column_name(columns_aliases.to_s))
2207
2251
  end
2208
2252
  end
2209
2253
  end