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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +391 -958
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -15
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +34 -7
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +46 -96
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +23 -1
- data/lib/active_record/core.rb +29 -14
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
- data/lib/active_record/encryption/encryptor.rb +16 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +9 -22
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +13 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +31 -11
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +38 -42
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +102 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +2 -26
- data/lib/active_record/railties/databases.rake +11 -35
- data/lib/active_record/reflection.rb +18 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +40 -39
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +105 -61
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation.rb +79 -61
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/statement_cache.rb +14 -14
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +5 -6
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +21 -48
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/table.rb +3 -7
- metadata +9 -10
- 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 {
|
|
81
|
+
scoping { model.#{method}(...) }
|
|
79
82
|
end
|
|
80
83
|
RUBY
|
|
81
84
|
else
|
|
82
85
|
define_method(method) do |*args, **kwargs, &block|
|
|
83
|
-
scoping {
|
|
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
|
|
96
|
-
# for each different
|
|
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, :
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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(
|
|
129
|
-
relation_class_for(
|
|
139
|
+
def create(model, ...)
|
|
140
|
+
relation_class_for(model).new(model, ...)
|
|
130
141
|
end
|
|
131
142
|
|
|
132
143
|
private
|
|
133
|
-
def relation_class_for(
|
|
134
|
-
|
|
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 ||
|
|
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.
|
|
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?(
|
|
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(
|
|
418
|
+
conditions = " [#{arel.where_sql(model)}]" unless where_clause.empty?
|
|
419
419
|
|
|
420
|
-
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
|
-
|
|
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(
|
|
493
|
+
raise UnknownPrimaryKey.new(model) if primary_key.nil?
|
|
494
494
|
|
|
495
|
-
expects_array = if
|
|
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 =
|
|
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
|
|
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|
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
104
|
-
r.class_name == other.
|
|
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.
|
|
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.
|
|
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.
|
|
188
|
+
relation.model.base_class == other.model.base_class
|
|
189
189
|
end
|
|
190
190
|
end
|
|
191
191
|
end
|
|
@@ -9,10 +9,11 @@ module ActiveRecord
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
if value.select_values.empty?
|
|
12
|
-
|
|
13
|
-
|
|
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[
|
|
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 =>
|
|
97
|
+
self.not(association => association_conditions)
|
|
97
98
|
else
|
|
98
|
-
self.not(reflection.table_name =>
|
|
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 =>
|
|
130
|
+
@scope.where!(association => association_conditions)
|
|
129
131
|
else
|
|
130
|
-
@scope.where!(reflection.table_name =>
|
|
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
|
-
|
|
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 `#{
|
|
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
|
-
|
|
702
|
-
|
|
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
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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?(
|
|
1560
|
-
raise ArgumentError, "You must only pass a single or collection of #{
|
|
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
|
-
|
|
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 = [
|
|
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|
|
|
1634
|
+
key.map { |k| model.attribute_aliases[k.to_s] || k.to_s }
|
|
1615
1635
|
else
|
|
1616
1636
|
key = key.to_s
|
|
1617
|
-
|
|
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
|
|
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 ==
|
|
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
|
|
1891
|
-
arel.project(*
|
|
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
|
|
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[
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1954
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2192
|
-
|
|
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
|
-
|
|
2246
|
+
arel_column_with_table(table_name, column)
|
|
2202
2247
|
end
|
|
2203
2248
|
when String, Symbol
|
|
2204
|
-
arel_column(key
|
|
2205
|
-
|
|
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
|