activerecord 7.1.5.1 → 7.2.2.1
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 +645 -2329
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +14 -7
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +7 -1
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +59 -292
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods.rb +54 -63
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +12 -29
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -74
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +15 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +85 -37
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/enum.rb +18 -1
- data/lib/active_record/errors.rb +46 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +31 -68
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +37 -55
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +40 -43
- data/lib/active_record/reflection.rb +98 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +224 -58
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +496 -72
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +69 -41
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +86 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +70 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +148 -39
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +16 -10
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "mutex_m"
|
4
3
|
require "active_support/core_ext/module/delegation"
|
5
4
|
|
6
5
|
module ActiveRecord
|
@@ -67,23 +66,22 @@ module ActiveRecord
|
|
67
66
|
end
|
68
67
|
|
69
68
|
class GeneratedRelationMethods < Module # :nodoc:
|
70
|
-
|
69
|
+
MUTEX = Mutex.new
|
71
70
|
|
72
71
|
def generate_method(method)
|
73
|
-
synchronize do
|
72
|
+
MUTEX.synchronize do
|
74
73
|
return if method_defined?(method)
|
75
74
|
|
76
|
-
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) &&
|
75
|
+
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
|
77
76
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
78
77
|
def #{method}(...)
|
79
78
|
scoping { klass.#{method}(...) }
|
80
79
|
end
|
81
80
|
RUBY
|
82
81
|
else
|
83
|
-
define_method(method) do |*args, &block|
|
84
|
-
scoping { klass.public_send(method, *args, &block) }
|
82
|
+
define_method(method) do |*args, **kwargs, &block|
|
83
|
+
scoping { klass.public_send(method, *args, **kwargs, &block) }
|
85
84
|
end
|
86
|
-
ruby2_keywords(method)
|
87
85
|
end
|
88
86
|
end
|
89
87
|
end
|
@@ -102,7 +100,7 @@ module ActiveRecord
|
|
102
100
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
103
101
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
104
102
|
|
105
|
-
delegate :primary_key, :connection, :transaction, to: :klass
|
103
|
+
delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
|
106
104
|
|
107
105
|
module ClassSpecificRelation # :nodoc:
|
108
106
|
extend ActiveSupport::Concern
|
@@ -114,17 +112,16 @@ module ActiveRecord
|
|
114
112
|
end
|
115
113
|
|
116
114
|
private
|
117
|
-
def method_missing(method,
|
115
|
+
def method_missing(method, ...)
|
118
116
|
if @klass.respond_to?(method)
|
119
117
|
unless Delegation.uncacheable_methods.include?(method)
|
120
118
|
@klass.generate_relation_method(method)
|
121
119
|
end
|
122
|
-
scoping { @klass.public_send(method,
|
120
|
+
scoping { @klass.public_send(method, ...) }
|
123
121
|
else
|
124
122
|
super
|
125
123
|
end
|
126
124
|
end
|
127
|
-
ruby2_keywords(:method_missing)
|
128
125
|
end
|
129
126
|
|
130
127
|
module ClassMethods # :nodoc:
|
@@ -87,6 +87,14 @@ module ActiveRecord
|
|
87
87
|
#
|
88
88
|
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
|
89
89
|
# # returns an Array of the required fields.
|
90
|
+
#
|
91
|
+
# ==== Edge Cases
|
92
|
+
#
|
93
|
+
# Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
|
94
|
+
# Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
|
95
|
+
# Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
|
96
|
+
# Person.find([]) # returns an empty array if the argument is an empty array.
|
97
|
+
# Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
|
90
98
|
def find(*args)
|
91
99
|
return super if block_given?
|
92
100
|
find_with_ids(*args)
|
@@ -366,7 +374,11 @@ module ActiveRecord
|
|
366
374
|
relation = construct_relation_for_exists(conditions)
|
367
375
|
return false if relation.where_clause.contradiction?
|
368
376
|
|
369
|
-
skip_query_cache_if_necessary
|
377
|
+
skip_query_cache_if_necessary do
|
378
|
+
with_connection do |c|
|
379
|
+
c.select_rows(relation.arel, "#{name} Exists?").size == 1
|
380
|
+
end
|
381
|
+
end
|
370
382
|
end
|
371
383
|
|
372
384
|
# Returns true if the relation contains the given record or false otherwise.
|
@@ -459,7 +471,9 @@ module ActiveRecord
|
|
459
471
|
)
|
460
472
|
)
|
461
473
|
relation = skip_query_cache_if_necessary do
|
462
|
-
klass.
|
474
|
+
klass.with_connection do |c|
|
475
|
+
c.distinct_relation_for_primary_key(relation)
|
476
|
+
end
|
463
477
|
end
|
464
478
|
end
|
465
479
|
|
@@ -7,16 +7,15 @@ module ActiveRecord
|
|
7
7
|
class HashMerger # :nodoc:
|
8
8
|
attr_reader :relation, :hash
|
9
9
|
|
10
|
-
def initialize(relation, hash
|
10
|
+
def initialize(relation, hash)
|
11
11
|
hash.assert_valid_keys(*Relation::VALUE_METHODS)
|
12
12
|
|
13
13
|
@relation = relation
|
14
14
|
@hash = hash
|
15
|
-
@rewhere = rewhere
|
16
15
|
end
|
17
16
|
|
18
17
|
def merge
|
19
|
-
Merger.new(relation, other
|
18
|
+
Merger.new(relation, other).merge
|
20
19
|
end
|
21
20
|
|
22
21
|
# Applying values to a relation has some side effects. E.g.
|
@@ -44,11 +43,10 @@ module ActiveRecord
|
|
44
43
|
class Merger # :nodoc:
|
45
44
|
attr_reader :relation, :values, :other
|
46
45
|
|
47
|
-
def initialize(relation, other
|
46
|
+
def initialize(relation, other)
|
48
47
|
@relation = relation
|
49
48
|
@values = other.values
|
50
49
|
@other = other
|
51
|
-
@rewhere = rewhere
|
52
50
|
end
|
53
51
|
|
54
52
|
NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS -
|
@@ -178,7 +176,7 @@ module ActiveRecord
|
|
178
176
|
def merge_clauses
|
179
177
|
relation.from_clause = other.from_clause if replace_from_clause?
|
180
178
|
|
181
|
-
where_clause = relation.where_clause.merge(other.where_clause
|
179
|
+
where_clause = relation.where_clause.merge(other.where_clause)
|
182
180
|
relation.where_clause = where_clause unless where_clause.empty?
|
183
181
|
|
184
182
|
having_clause = relation.having_clause.merge(other.having_clause)
|
@@ -13,7 +13,7 @@ module ActiveRecord
|
|
13
13
|
return attribute.in([]) if value.empty?
|
14
14
|
|
15
15
|
values = value.map { |x| x.is_a?(Base) ? x.id : x }
|
16
|
-
nils = values.
|
16
|
+
nils = values.compact!
|
17
17
|
ranges = values.extract! { |v| v.is_a?(Range) }
|
18
18
|
|
19
19
|
values_predicate =
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
23
23
|
else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
if nils
|
27
27
|
values_predicate = values_predicate.or(attribute.eq(nil))
|
28
28
|
end
|
29
29
|
|
@@ -57,9 +57,15 @@ module ActiveRecord
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def convert_to_id(value)
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
if primary_key.is_a?(Array)
|
61
|
+
primary_key.map do |attribute|
|
62
|
+
if attribute == "id"
|
63
|
+
value.id_value
|
64
|
+
else
|
65
|
+
value.public_send(attribute)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
elsif value.respond_to?(primary_key)
|
63
69
|
value.public_send(primary_key)
|
64
70
|
else
|
65
71
|
value
|
@@ -28,9 +28,9 @@ module ActiveRecord
|
|
28
28
|
def self.references(attributes)
|
29
29
|
attributes.each_with_object([]) do |(key, value), result|
|
30
30
|
if value.is_a?(Hash)
|
31
|
-
result << Arel.sql(key)
|
31
|
+
result << Arel.sql(key, retryable: true)
|
32
32
|
elsif (idx = key.rindex("."))
|
33
|
-
result << Arel.sql(key[0, idx])
|
33
|
+
result << Arel.sql(key[0, idx], retryable: true)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -142,7 +142,7 @@ module ActiveRecord
|
|
142
142
|
queries.first
|
143
143
|
else
|
144
144
|
queries.map! { |query| query.reduce(&:and) }
|
145
|
-
queries =
|
145
|
+
queries = Arel::Nodes::Or.new(queries)
|
146
146
|
Arel::Nodes::Grouping.new(queries)
|
147
147
|
end
|
148
148
|
end
|
@@ -72,10 +72,26 @@ module ActiveRecord
|
|
72
72
|
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
73
73
|
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
74
74
|
# # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
|
75
|
+
#
|
76
|
+
# You can define join type in the scope and +associated+ will not use `JOIN` by default.
|
77
|
+
#
|
78
|
+
# Post.left_joins(:author).where.associated(:author)
|
79
|
+
# # SELECT "posts".* FROM "posts"
|
80
|
+
# # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
|
81
|
+
# # WHERE "authors"."id" IS NOT NULL
|
82
|
+
#
|
83
|
+
# Post.left_joins(:comments).where.associated(:author)
|
84
|
+
# # SELECT "posts".* FROM "posts"
|
85
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
86
|
+
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
87
|
+
# # WHERE "author"."id" IS NOT NULL
|
75
88
|
def associated(*associations)
|
76
89
|
associations.each do |association|
|
77
90
|
reflection = scope_association_reflection(association)
|
78
|
-
@scope.
|
91
|
+
unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
|
92
|
+
@scope.joins!(association)
|
93
|
+
end
|
94
|
+
|
79
95
|
if reflection.options[:class_name]
|
80
96
|
self.not(association => { reflection.association_primary_key => nil })
|
81
97
|
else
|
@@ -157,7 +173,7 @@ module ActiveRecord
|
|
157
173
|
end # end
|
158
174
|
|
159
175
|
def #{method_name}=(value) # def includes_values=(value)
|
160
|
-
|
176
|
+
assert_modifiable! # assert_modifiable!
|
161
177
|
@values[:#{name}] = value # @values[:includes] = value
|
162
178
|
end # end
|
163
179
|
CODE
|
@@ -419,6 +435,17 @@ module ActiveRecord
|
|
419
435
|
# # )
|
420
436
|
# # SELECT * FROM posts
|
421
437
|
#
|
438
|
+
# You can also pass an array of sub-queries to be joined in a +UNION ALL+.
|
439
|
+
#
|
440
|
+
# Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
|
441
|
+
# # => ActiveRecord::Relation
|
442
|
+
# # WITH posts_with_tags_or_comments AS (
|
443
|
+
# # (SELECT * FROM posts WHERE (tags_count > 0))
|
444
|
+
# # UNION ALL
|
445
|
+
# # (SELECT * FROM posts WHERE (comments_count > 0))
|
446
|
+
# # )
|
447
|
+
# # SELECT * FROM posts
|
448
|
+
#
|
422
449
|
# Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
|
423
450
|
#
|
424
451
|
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
|
@@ -457,6 +484,7 @@ module ActiveRecord
|
|
457
484
|
# .with(posts_with_comments: Post.where("comments_count > ?", 0))
|
458
485
|
# .with(posts_with_tags: Post.where("tags_count > ?", 0))
|
459
486
|
def with(*args)
|
487
|
+
raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
|
460
488
|
check_if_method_has_arguments!(__callee__, args)
|
461
489
|
spawn.with!(*args)
|
462
490
|
end
|
@@ -467,6 +495,30 @@ module ActiveRecord
|
|
467
495
|
self
|
468
496
|
end
|
469
497
|
|
498
|
+
# Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
499
|
+
#
|
500
|
+
# 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')])
|
501
|
+
# # => ActiveRecord::Relation
|
502
|
+
# # WITH post_and_replies AS (
|
503
|
+
# # (SELECT * FROM posts WHERE id = 42)
|
504
|
+
# # UNION ALL
|
505
|
+
# # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
|
506
|
+
# # )
|
507
|
+
# # SELECT * FROM posts
|
508
|
+
#
|
509
|
+
# See `#with` for more information.
|
510
|
+
def with_recursive(*args)
|
511
|
+
check_if_method_has_arguments!(__callee__, args)
|
512
|
+
spawn.with_recursive!(*args)
|
513
|
+
end
|
514
|
+
|
515
|
+
# Like #with_recursive but modifies the relation in place.
|
516
|
+
def with_recursive!(*args) # :nodoc:
|
517
|
+
self.with_values += args
|
518
|
+
@with_is_recursive = true
|
519
|
+
self
|
520
|
+
end
|
521
|
+
|
470
522
|
# Allows you to change a previously set select statement.
|
471
523
|
#
|
472
524
|
# Post.select(:title, :body)
|
@@ -606,7 +658,8 @@ module ActiveRecord
|
|
606
658
|
self
|
607
659
|
end
|
608
660
|
|
609
|
-
#
|
661
|
+
# Applies an <tt>ORDER BY</tt> clause based on a given +column+,
|
662
|
+
# ordered and filtered by a specific set of +values+.
|
610
663
|
#
|
611
664
|
# User.in_order_of(:id, [1, 5, 3])
|
612
665
|
# # SELECT "users".* FROM "users"
|
@@ -617,8 +670,34 @@ module ActiveRecord
|
|
617
670
|
# # WHEN "users"."id" = 3 THEN 3
|
618
671
|
# # END ASC
|
619
672
|
#
|
673
|
+
# +column+ can point to an enum column; the actual query generated may be different depending
|
674
|
+
# on the database adapter and the column definition.
|
675
|
+
#
|
676
|
+
# class Conversation < ActiveRecord::Base
|
677
|
+
# enum :status, [ :active, :archived ]
|
678
|
+
# end
|
679
|
+
#
|
680
|
+
# Conversation.in_order_of(:status, [:archived, :active])
|
681
|
+
# # SELECT "conversations".* FROM "conversations"
|
682
|
+
# # WHERE "conversations"."status" IN (1, 0)
|
683
|
+
# # ORDER BY CASE
|
684
|
+
# # WHEN "conversations"."status" = 1 THEN 1
|
685
|
+
# # WHEN "conversations"."status" = 0 THEN 2
|
686
|
+
# # END ASC
|
687
|
+
#
|
688
|
+
# +values+ can also include +nil+.
|
689
|
+
#
|
690
|
+
# Conversation.in_order_of(:status, [nil, :archived, :active])
|
691
|
+
# # SELECT "conversations".* FROM "conversations"
|
692
|
+
# # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
|
693
|
+
# # ORDER BY CASE
|
694
|
+
# # WHEN "conversations"."status" IS NULL THEN 1
|
695
|
+
# # WHEN "conversations"."status" = 1 THEN 2
|
696
|
+
# # WHEN "conversations"."status" = 0 THEN 3
|
697
|
+
# # END ASC
|
698
|
+
#
|
620
699
|
def in_order_of(column, values)
|
621
|
-
klass.disallow_raw_sql!([column], permit:
|
700
|
+
klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
|
622
701
|
return spawn.none! if values.empty?
|
623
702
|
|
624
703
|
references = column_references([column])
|
@@ -667,7 +746,7 @@ module ActiveRecord
|
|
667
746
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
668
747
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
669
748
|
:includes, :eager_load, :preload, :from, :readonly,
|
670
|
-
:having, :optimizer_hints])
|
749
|
+
:having, :optimizer_hints, :with])
|
671
750
|
|
672
751
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
673
752
|
# This is useful when passing around chains of relations and would like to
|
@@ -717,7 +796,7 @@ module ActiveRecord
|
|
717
796
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
718
797
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
719
798
|
end
|
720
|
-
|
799
|
+
assert_modifiable!
|
721
800
|
@values.delete(scope)
|
722
801
|
when Hash
|
723
802
|
scope.each do |key, target_value|
|
@@ -1082,7 +1161,7 @@ module ActiveRecord
|
|
1082
1161
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
1083
1162
|
end
|
1084
1163
|
|
1085
|
-
self.where_clause =
|
1164
|
+
self.where_clause = where_clause.or(other.where_clause)
|
1086
1165
|
self.having_clause = having_clause.or(other.having_clause)
|
1087
1166
|
self.references_values |= other.references_values
|
1088
1167
|
|
@@ -1453,6 +1532,9 @@ module ActiveRecord
|
|
1453
1532
|
# Post.excluding(post_one, post_two)
|
1454
1533
|
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
1455
1534
|
#
|
1535
|
+
# Post.excluding(Post.drafts)
|
1536
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
|
1537
|
+
#
|
1456
1538
|
# This can also be called on associations. As with the above example, either
|
1457
1539
|
# a single record of collection thereof may be specified:
|
1458
1540
|
#
|
@@ -1468,14 +1550,15 @@ module ActiveRecord
|
|
1468
1550
|
# is passed in) are not instances of the same model that the relation is
|
1469
1551
|
# scoping.
|
1470
1552
|
def excluding(*records)
|
1553
|
+
relations = records.extract! { |element| element.is_a?(Relation) }
|
1471
1554
|
records.flatten!(1)
|
1472
1555
|
records.compact!
|
1473
1556
|
|
1474
|
-
unless records.all?(klass)
|
1557
|
+
unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
|
1475
1558
|
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
|
1476
1559
|
end
|
1477
1560
|
|
1478
|
-
spawn.excluding!(records)
|
1561
|
+
spawn.excluding!(records + relations.flat_map(&:ids))
|
1479
1562
|
end
|
1480
1563
|
alias :without :excluding
|
1481
1564
|
|
@@ -1487,7 +1570,7 @@ module ActiveRecord
|
|
1487
1570
|
|
1488
1571
|
# Returns the Arel object associated with the relation.
|
1489
1572
|
def arel(aliases = nil) # :nodoc:
|
1490
|
-
@arel ||= build_arel(aliases)
|
1573
|
+
@arel ||= with_connection { |c| build_arel(c, aliases) }
|
1491
1574
|
end
|
1492
1575
|
|
1493
1576
|
def construct_join_dependency(associations, join_type) # :nodoc:
|
@@ -1508,9 +1591,21 @@ module ActiveRecord
|
|
1508
1591
|
def build_where_clause(opts, rest = []) # :nodoc:
|
1509
1592
|
opts = sanitize_forbidden_attributes(opts)
|
1510
1593
|
|
1594
|
+
if opts.is_a?(Array)
|
1595
|
+
opts, *rest = opts
|
1596
|
+
end
|
1597
|
+
|
1511
1598
|
case opts
|
1512
|
-
when String
|
1513
|
-
|
1599
|
+
when String
|
1600
|
+
if rest.empty?
|
1601
|
+
parts = [Arel.sql(opts)]
|
1602
|
+
elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
|
1603
|
+
parts = [build_named_bound_sql_literal(opts, rest.first)]
|
1604
|
+
elsif opts.include?("?")
|
1605
|
+
parts = [build_bound_sql_literal(opts, rest)]
|
1606
|
+
else
|
1607
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1608
|
+
end
|
1514
1609
|
when Hash
|
1515
1610
|
opts = opts.transform_keys do |key|
|
1516
1611
|
if key.is_a?(Array)
|
@@ -1541,11 +1636,71 @@ module ActiveRecord
|
|
1541
1636
|
self
|
1542
1637
|
end
|
1543
1638
|
|
1639
|
+
protected
|
1640
|
+
def arel_columns(columns)
|
1641
|
+
columns.flat_map do |field|
|
1642
|
+
case field
|
1643
|
+
when Symbol
|
1644
|
+
arel_column(field.to_s) do |attr_name|
|
1645
|
+
adapter_class.quote_table_name(attr_name)
|
1646
|
+
end
|
1647
|
+
when String
|
1648
|
+
arel_column(field, &:itself)
|
1649
|
+
when Proc
|
1650
|
+
field.call
|
1651
|
+
when Hash
|
1652
|
+
arel_columns_from_hash(field)
|
1653
|
+
else
|
1654
|
+
field
|
1655
|
+
end
|
1656
|
+
end
|
1657
|
+
end
|
1658
|
+
|
1544
1659
|
private
|
1545
1660
|
def async
|
1546
1661
|
spawn.async!
|
1547
1662
|
end
|
1548
1663
|
|
1664
|
+
def build_named_bound_sql_literal(statement, values)
|
1665
|
+
bound_values = values.transform_values do |value|
|
1666
|
+
if ActiveRecord::Relation === value
|
1667
|
+
Arel.sql(value.to_sql)
|
1668
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
1669
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
1670
|
+
values.empty? ? nil : values
|
1671
|
+
else
|
1672
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
1673
|
+
value
|
1674
|
+
end
|
1675
|
+
end
|
1676
|
+
|
1677
|
+
begin
|
1678
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
|
1679
|
+
rescue Arel::BindError => error
|
1680
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1681
|
+
end
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
def build_bound_sql_literal(statement, values)
|
1685
|
+
bound_values = values.map do |value|
|
1686
|
+
if ActiveRecord::Relation === value
|
1687
|
+
Arel.sql(value.to_sql)
|
1688
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
1689
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
1690
|
+
values.empty? ? nil : values
|
1691
|
+
else
|
1692
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
1693
|
+
value
|
1694
|
+
end
|
1695
|
+
end
|
1696
|
+
|
1697
|
+
begin
|
1698
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
|
1699
|
+
rescue Arel::BindError => error
|
1700
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1701
|
+
end
|
1702
|
+
end
|
1703
|
+
|
1549
1704
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1550
1705
|
each_join_dependencies do |join|
|
1551
1706
|
return join.base_klass if table_name == join.table_name
|
@@ -1570,12 +1725,11 @@ module ActiveRecord
|
|
1570
1725
|
)
|
1571
1726
|
end
|
1572
1727
|
|
1573
|
-
def
|
1574
|
-
raise
|
1575
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
1728
|
+
def assert_modifiable!
|
1729
|
+
raise UnmodifiableRelation if @loaded || @arel
|
1576
1730
|
end
|
1577
1731
|
|
1578
|
-
def build_arel(aliases = nil)
|
1732
|
+
def build_arel(connection, aliases = nil)
|
1579
1733
|
arel = Arel::SelectManager.new(table)
|
1580
1734
|
|
1581
1735
|
build_joins(arel.join_sources, aliases)
|
@@ -1747,20 +1901,37 @@ module ActiveRecord
|
|
1747
1901
|
build_with_value_from_hash(with_value)
|
1748
1902
|
end
|
1749
1903
|
|
1750
|
-
arel.with(with_statements)
|
1904
|
+
@with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
1751
1905
|
end
|
1752
1906
|
|
1753
1907
|
def build_with_value_from_hash(hash)
|
1754
1908
|
hash.map do |name, value|
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1909
|
+
Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
|
1910
|
+
end
|
1911
|
+
end
|
1912
|
+
|
1913
|
+
def build_with_expression_from_value(value, nested = false)
|
1914
|
+
case value
|
1915
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
1916
|
+
when ActiveRecord::Relation
|
1917
|
+
if nested
|
1918
|
+
value.arel.ast
|
1919
|
+
else
|
1920
|
+
value.arel
|
1921
|
+
end
|
1922
|
+
when Arel::SelectManager then value
|
1923
|
+
when Array
|
1924
|
+
return build_with_expression_from_value(value.first, false) if value.size == 1
|
1925
|
+
|
1926
|
+
parts = value.map do |query|
|
1927
|
+
build_with_expression_from_value(query, true)
|
1928
|
+
end
|
1929
|
+
|
1930
|
+
parts.reduce do |result, value|
|
1931
|
+
Arel::Nodes::UnionAll.new(result, value)
|
1932
|
+
end
|
1933
|
+
else
|
1934
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
1764
1935
|
end
|
1765
1936
|
end
|
1766
1937
|
|
@@ -1772,31 +1943,14 @@ module ActiveRecord
|
|
1772
1943
|
).join_sources.first
|
1773
1944
|
end
|
1774
1945
|
|
1775
|
-
def arel_columns(columns)
|
1776
|
-
columns.flat_map do |field|
|
1777
|
-
case field
|
1778
|
-
when Symbol
|
1779
|
-
arel_column(field.to_s) do |attr_name|
|
1780
|
-
connection.quote_table_name(attr_name)
|
1781
|
-
end
|
1782
|
-
when String
|
1783
|
-
arel_column(field, &:itself)
|
1784
|
-
when Proc
|
1785
|
-
field.call
|
1786
|
-
else
|
1787
|
-
field
|
1788
|
-
end
|
1789
|
-
end
|
1790
|
-
end
|
1791
|
-
|
1792
1946
|
def arel_column(field)
|
1793
1947
|
field = klass.attribute_aliases[field] || field
|
1794
1948
|
from = from_clause.name || from_clause.value
|
1795
1949
|
|
1796
1950
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1797
1951
|
table[field]
|
1798
|
-
elsif
|
1799
|
-
table,
|
1952
|
+
elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
|
1953
|
+
self.references_values |= [Arel.sql(table, retryable: true)]
|
1800
1954
|
predicate_builder.resolve_arel_attribute(table, column) do
|
1801
1955
|
lookup_table_klass_from_join_dependencies(table)
|
1802
1956
|
end
|
@@ -1807,7 +1961,7 @@ module ActiveRecord
|
|
1807
1961
|
|
1808
1962
|
def table_name_matches?(from)
|
1809
1963
|
table_name = Regexp.escape(table.name)
|
1810
|
-
quoted_table_name = Regexp.escape(
|
1964
|
+
quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
|
1811
1965
|
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1812
1966
|
end
|
1813
1967
|
|
@@ -1863,7 +2017,9 @@ module ActiveRecord
|
|
1863
2017
|
args.each do |arg|
|
1864
2018
|
next unless arg.is_a?(Hash)
|
1865
2019
|
arg.each do |_key, value|
|
1866
|
-
|
2020
|
+
if value.is_a?(Hash)
|
2021
|
+
validate_order_args([value])
|
2022
|
+
elsif VALID_DIRECTIONS.exclude?(value)
|
1867
2023
|
raise ArgumentError,
|
1868
2024
|
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
1869
2025
|
end
|
@@ -1871,10 +2027,14 @@ module ActiveRecord
|
|
1871
2027
|
end
|
1872
2028
|
end
|
1873
2029
|
|
2030
|
+
def flattened_args(args)
|
2031
|
+
args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
2032
|
+
end
|
2033
|
+
|
1874
2034
|
def preprocess_order_args(order_args)
|
1875
2035
|
@klass.disallow_raw_sql!(
|
1876
|
-
order_args
|
1877
|
-
permit:
|
2036
|
+
flattened_args(order_args),
|
2037
|
+
permit: model.adapter_class.column_name_with_order_matcher
|
1878
2038
|
)
|
1879
2039
|
|
1880
2040
|
validate_order_args(order_args)
|
@@ -1888,14 +2048,20 @@ module ActiveRecord
|
|
1888
2048
|
when Symbol
|
1889
2049
|
order_column(arg.to_s).asc
|
1890
2050
|
when Hash
|
1891
|
-
arg.map
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
2051
|
+
arg.map do |key, value|
|
2052
|
+
if value.is_a?(Hash)
|
2053
|
+
value.map do |field, dir|
|
2054
|
+
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
2055
|
+
end
|
1895
2056
|
else
|
1896
|
-
|
2057
|
+
case key
|
2058
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
2059
|
+
key.public_send(value.downcase)
|
2060
|
+
else
|
2061
|
+
order_column(key.to_s).public_send(value.downcase)
|
2062
|
+
end
|
1897
2063
|
end
|
1898
|
-
|
2064
|
+
end
|
1899
2065
|
else
|
1900
2066
|
arg
|
1901
2067
|
end
|
@@ -1942,7 +2108,7 @@ module ActiveRecord
|
|
1942
2108
|
if attr_name == "count" && !group_values.empty?
|
1943
2109
|
table[attr_name]
|
1944
2110
|
else
|
1945
|
-
Arel.sql(
|
2111
|
+
Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
|
1946
2112
|
end
|
1947
2113
|
end
|
1948
2114
|
end
|
@@ -2010,14 +2176,14 @@ module ActiveRecord
|
|
2010
2176
|
def process_select_args(fields)
|
2011
2177
|
fields.flat_map do |field|
|
2012
2178
|
if field.is_a?(Hash)
|
2013
|
-
|
2179
|
+
arel_columns_from_hash(field)
|
2014
2180
|
else
|
2015
2181
|
field
|
2016
2182
|
end
|
2017
2183
|
end
|
2018
2184
|
end
|
2019
2185
|
|
2020
|
-
def
|
2186
|
+
def arel_columns_from_hash(fields)
|
2021
2187
|
fields.flat_map do |key, columns_aliases|
|
2022
2188
|
case columns_aliases
|
2023
2189
|
when Hash
|