activerecord 6.0.0.beta3 → 6.0.2.rc2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +466 -9
- data/README.rdoc +3 -1
- data/lib/active_record.rb +0 -1
- data/lib/active_record/association_relation.rb +15 -6
- data/lib/active_record/associations.rb +4 -3
- data/lib/active_record/associations/association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +6 -2
- data/lib/active_record/associations/collection_proxy.rb +2 -2
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency.rb +14 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +12 -3
- data/lib/active_record/associations/preloader.rb +13 -8
- data/lib/active_record/associations/preloader/association.rb +34 -30
- data/lib/active_record/associations/preloader/through_association.rb +48 -28
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +21 -7
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -11
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +88 -61
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +79 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
- data/lib/active_record/connection_adapters/abstract_adapter.rb +111 -33
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +78 -73
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +67 -26
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -114
- data/lib/active_record/connection_handling.rb +31 -13
- data/lib/active_record/core.rb +23 -24
- data/lib/active_record/database_configurations.rb +73 -44
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +12 -12
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +1 -1
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector.rb +3 -3
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -6
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +10 -0
- data/lib/active_record/model_schema.rb +3 -0
- data/lib/active_record/persistence.rb +206 -13
- data/lib/active_record/querying.rb +17 -12
- data/lib/active_record/railtie.rb +0 -1
- data/lib/active_record/railties/databases.rake +127 -25
- data/lib/active_record/reflection.rb +3 -3
- data/lib/active_record/relation.rb +99 -20
- data/lib/active_record/relation/calculations.rb +38 -40
- data/lib/active_record/relation/delegation.rb +22 -30
- data/lib/active_record/relation/finder_methods.rb +17 -12
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_methods.rb +228 -76
- data/lib/active_record/relation/where_clause.rb +9 -5
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +6 -7
- data/lib/active_record/scoping/named.rb +3 -2
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +1 -0
- data/lib/active_record/timestamp.rb +26 -16
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +56 -46
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/uniqueness.rb +3 -5
- data/lib/arel.rb +12 -5
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +16 -12
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -37,15 +37,14 @@ module ActiveRecord
|
|
37
37
|
nodes = arel.constraints.first
|
38
38
|
|
39
39
|
others = nodes.children.extract! do |node|
|
40
|
-
Arel.fetch_attribute(node) { |attr| attr.relation.name
|
40
|
+
!Arel.fetch_attribute(node) { |attr| attr.relation.name == table.name }
|
41
41
|
end
|
42
42
|
|
43
43
|
joins << table.create_join(table, table.create_on(nodes), join_type)
|
44
44
|
|
45
45
|
unless others.empty?
|
46
46
|
joins.concat arel.join_sources
|
47
|
-
|
48
|
-
right.expr.children.concat(others)
|
47
|
+
append_constraints(joins.last, others)
|
49
48
|
end
|
50
49
|
|
51
50
|
# The current table in this iteration becomes the foreign table in the next
|
@@ -65,6 +64,16 @@ module ActiveRecord
|
|
65
64
|
|
66
65
|
@readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
|
67
66
|
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def append_constraints(join, constraints)
|
70
|
+
if join.is_a?(Arel::Nodes::StringJoin)
|
71
|
+
join_string = table.create_and(constraints.unshift(join.left))
|
72
|
+
join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
|
73
|
+
else
|
74
|
+
join.right.expr.children.concat(constraints)
|
75
|
+
end
|
76
|
+
end
|
68
77
|
end
|
69
78
|
end
|
70
79
|
end
|
@@ -112,7 +112,7 @@ module ActiveRecord
|
|
112
112
|
association.flat_map { |parent, child|
|
113
113
|
grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
|
114
114
|
loaders = preloaders_for_reflection(reflection, reflection_records, scope)
|
115
|
-
recs = loaders.flat_map(&:preloaded_records)
|
115
|
+
recs = loaders.flat_map(&:preloaded_records).uniq
|
116
116
|
child_polymorphic_parent = reflection && reflection.options[:polymorphic]
|
117
117
|
loaders.concat Array.wrap(child).flat_map { |assoc|
|
118
118
|
preloaders_on assoc, recs, scope, child_polymorphic_parent
|
@@ -143,16 +143,13 @@ module ActiveRecord
|
|
143
143
|
|
144
144
|
def preloaders_for_reflection(reflection, records, scope)
|
145
145
|
records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
|
146
|
-
|
147
|
-
loader.run self
|
148
|
-
loader
|
146
|
+
preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope).run
|
149
147
|
end
|
150
148
|
end
|
151
149
|
|
152
150
|
def grouped_records(association, records, polymorphic_parent)
|
153
151
|
h = {}
|
154
152
|
records.each do |record|
|
155
|
-
next unless record
|
156
153
|
reflection = record.class._reflect_on_association(association)
|
157
154
|
next if polymorphic_parent && !reflection || !record.association(association).klass
|
158
155
|
(h[reflection] ||= []) << record
|
@@ -166,10 +163,18 @@ module ActiveRecord
|
|
166
163
|
@reflection = reflection
|
167
164
|
end
|
168
165
|
|
169
|
-
def run
|
166
|
+
def run
|
167
|
+
self
|
168
|
+
end
|
170
169
|
|
171
170
|
def preloaded_records
|
172
|
-
|
171
|
+
@preloaded_records ||= records_by_owner.flat_map(&:last)
|
172
|
+
end
|
173
|
+
|
174
|
+
def records_by_owner
|
175
|
+
@records_by_owner ||= owners.each_with_object({}) do |owner, result|
|
176
|
+
result[owner] = Array(owner.association(reflection.name).target)
|
177
|
+
end
|
173
178
|
end
|
174
179
|
|
175
180
|
private
|
@@ -180,7 +185,7 @@ module ActiveRecord
|
|
180
185
|
# and attach it to a relation. The class returned implements a `run` method
|
181
186
|
# that accepts a preloader.
|
182
187
|
def preloader_for(reflection, owners)
|
183
|
-
if owners.
|
188
|
+
if owners.all? { |o| o.association(reflection.name).loaded? }
|
184
189
|
return AlreadyLoaded
|
185
190
|
end
|
186
191
|
reflection.check_preloadable!
|
@@ -4,29 +4,43 @@ module ActiveRecord
|
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
6
|
class Association #:nodoc:
|
7
|
-
attr_reader :preloaded_records
|
8
|
-
|
9
7
|
def initialize(klass, owners, reflection, preload_scope)
|
10
8
|
@klass = klass
|
11
9
|
@owners = owners
|
12
10
|
@reflection = reflection
|
13
11
|
@preload_scope = preload_scope
|
14
12
|
@model = owners.first && owners.first.class
|
15
|
-
@preloaded_records = []
|
16
13
|
end
|
17
14
|
|
18
|
-
def run
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
def run
|
16
|
+
if !preload_scope || preload_scope.empty_scope?
|
17
|
+
owners.each do |owner|
|
18
|
+
associate_records_to_owner(owner, records_by_owner[owner] || [])
|
19
|
+
end
|
20
|
+
else
|
21
|
+
# Custom preload scope is used and
|
22
|
+
# the association can not be marked as loaded
|
23
|
+
# Loading into a Hash instead
|
24
|
+
records_by_owner
|
23
25
|
end
|
26
|
+
self
|
27
|
+
end
|
24
28
|
|
25
|
-
|
26
|
-
|
29
|
+
def records_by_owner
|
30
|
+
# owners can be duplicated when a relation has a collection association join
|
31
|
+
# #compare_by_identity makes such owners different hash keys
|
32
|
+
@records_by_owner ||= preloaded_records.each_with_object({}.compare_by_identity) do |record, result|
|
33
|
+
owners_by_key[convert_key(record[association_key_name])].each do |owner|
|
34
|
+
(result[owner] ||= []) << record
|
35
|
+
end
|
27
36
|
end
|
28
37
|
end
|
29
38
|
|
39
|
+
def preloaded_records
|
40
|
+
return @preloaded_records if defined?(@preloaded_records)
|
41
|
+
@preloaded_records = owner_keys.empty? ? [] : records_for(owner_keys)
|
42
|
+
end
|
43
|
+
|
30
44
|
private
|
31
45
|
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
32
46
|
|
@@ -54,13 +68,10 @@ module ActiveRecord
|
|
54
68
|
end
|
55
69
|
|
56
70
|
def owners_by_key
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
h[key] = owner if key
|
61
|
-
end
|
71
|
+
@owners_by_key ||= owners.each_with_object({}) do |owner, result|
|
72
|
+
key = convert_key(owner[owner_key_name])
|
73
|
+
(result[key] ||= []) << owner if key
|
62
74
|
end
|
63
|
-
@owners_by_key
|
64
75
|
end
|
65
76
|
|
66
77
|
def key_conversion_required?
|
@@ -87,23 +98,16 @@ module ActiveRecord
|
|
87
98
|
@model.type_for_attribute(owner_key_name).type
|
88
99
|
end
|
89
100
|
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
98
|
-
@preloaded_records.group_by do |record|
|
99
|
-
convert_key(record[association_key_name])
|
101
|
+
def records_for(ids)
|
102
|
+
scope.where(association_key_name => ids).load do |record|
|
103
|
+
# Processing only the first owner
|
104
|
+
# because the record is modified but not an owner
|
105
|
+
owner = owners_by_key[convert_key(record[association_key_name])].first
|
106
|
+
association = owner.association(reflection.name)
|
107
|
+
association.set_inverse_instance(record)
|
100
108
|
end
|
101
109
|
end
|
102
110
|
|
103
|
-
def records_for(ids, &block)
|
104
|
-
scope.where(association_key_name => ids).load(&block)
|
105
|
-
end
|
106
|
-
|
107
111
|
def scope
|
108
112
|
@scope ||= build_scope
|
109
113
|
end
|
@@ -4,41 +4,57 @@ module ActiveRecord
|
|
4
4
|
module Associations
|
5
5
|
class Preloader
|
6
6
|
class ThroughAssociation < Association # :nodoc:
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
7
|
+
PRELOADER = ActiveRecord::Associations::Preloader.new
|
8
|
+
|
9
|
+
def initialize(*)
|
10
|
+
super
|
11
|
+
@already_loaded = owners.first.association(through_reflection.name).loaded?
|
12
|
+
end
|
13
|
+
|
14
|
+
def preloaded_records
|
15
|
+
@preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
|
16
|
+
end
|
17
|
+
|
18
|
+
def records_by_owner
|
19
|
+
return @records_by_owner if defined?(@records_by_owner)
|
20
|
+
source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge)
|
21
|
+
through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge)
|
22
|
+
|
23
|
+
@records_by_owner = owners.each_with_object({}) do |owner, result|
|
24
|
+
through_records = through_records_by_owner[owner] || []
|
25
|
+
|
26
|
+
if @already_loaded
|
18
27
|
if source_type = reflection.options[:source_type]
|
19
28
|
through_records = through_records.select do |record|
|
20
29
|
record[reflection.foreign_type] == source_type
|
21
30
|
end
|
22
31
|
end
|
23
|
-
else
|
24
|
-
owner.association(through_reflection.name).reset if through_scope
|
25
|
-
end
|
26
|
-
result = through_records.flat_map do |record|
|
27
|
-
record.association(source_reflection.name).target
|
28
32
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
associate_records_to_owner(owner, result)
|
33
|
-
end
|
34
|
-
unless scope.empty_scope?
|
35
|
-
middle_records.each do |owner|
|
36
|
-
owner.association(source_reflection.name).reset
|
33
|
+
|
34
|
+
records = through_records.flat_map do |record|
|
35
|
+
source_records_by_owner[record]
|
37
36
|
end
|
37
|
+
|
38
|
+
records.compact!
|
39
|
+
records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
|
40
|
+
records.uniq! if scope.distinct_value
|
41
|
+
result[owner] = records
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
41
45
|
private
|
46
|
+
def source_preloaders
|
47
|
+
@source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope)
|
48
|
+
end
|
49
|
+
|
50
|
+
def middle_records
|
51
|
+
through_preloaders.flat_map(&:preloaded_records)
|
52
|
+
end
|
53
|
+
|
54
|
+
def through_preloaders
|
55
|
+
@through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope)
|
56
|
+
end
|
57
|
+
|
42
58
|
def through_reflection
|
43
59
|
reflection.through_reflection
|
44
60
|
end
|
@@ -48,8 +64,8 @@ module ActiveRecord
|
|
48
64
|
end
|
49
65
|
|
50
66
|
def preload_index
|
51
|
-
@preload_index ||=
|
52
|
-
result[
|
67
|
+
@preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
|
68
|
+
result[record] = index
|
53
69
|
end
|
54
70
|
end
|
55
71
|
|
@@ -57,11 +73,15 @@ module ActiveRecord
|
|
57
73
|
scope = through_reflection.klass.unscoped
|
58
74
|
options = reflection.options
|
59
75
|
|
76
|
+
values = reflection_scope.values
|
77
|
+
if annotations = values[:annotate]
|
78
|
+
scope.annotate!(*annotations)
|
79
|
+
end
|
80
|
+
|
60
81
|
if options[:source_type]
|
61
82
|
scope.where! reflection.foreign_type => options[:source_type]
|
62
83
|
elsif !reflection_scope.where_clause.empty?
|
63
84
|
scope.where_clause = reflection_scope.where_clause
|
64
|
-
values = reflection_scope.values
|
65
85
|
|
66
86
|
if includes = values[:includes]
|
67
87
|
scope.includes!(source_reflection.name => includes)
|
@@ -88,7 +108,7 @@ module ActiveRecord
|
|
88
108
|
end
|
89
109
|
end
|
90
110
|
|
91
|
-
scope
|
111
|
+
scope
|
92
112
|
end
|
93
113
|
end
|
94
114
|
end
|
@@ -35,7 +35,8 @@ module ActiveRecord
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def initialize_generated_modules # :nodoc:
|
38
|
-
@generated_attribute_methods = GeneratedAttributeMethods.new
|
38
|
+
@generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
|
39
|
+
private_constant :GeneratedAttributeMethods
|
39
40
|
@attribute_methods_generated = false
|
40
41
|
include @generated_attribute_methods
|
41
42
|
|
@@ -158,57 +159,6 @@ module ActiveRecord
|
|
158
159
|
end
|
159
160
|
end
|
160
161
|
|
161
|
-
# Regexp for column names (with or without a table name prefix). Matches
|
162
|
-
# the following:
|
163
|
-
# "#{table_name}.#{column_name}"
|
164
|
-
# "#{column_name}"
|
165
|
-
COLUMN_NAME = /\A(?:\w+\.)?\w+\z/i
|
166
|
-
|
167
|
-
# Regexp for column names with order (with or without a table name
|
168
|
-
# prefix, with or without various order modifiers). Matches the following:
|
169
|
-
# "#{table_name}.#{column_name}"
|
170
|
-
# "#{table_name}.#{column_name} #{direction}"
|
171
|
-
# "#{table_name}.#{column_name} #{direction} NULLS FIRST"
|
172
|
-
# "#{table_name}.#{column_name} NULLS LAST"
|
173
|
-
# "#{column_name}"
|
174
|
-
# "#{column_name} #{direction}"
|
175
|
-
# "#{column_name} #{direction} NULLS FIRST"
|
176
|
-
# "#{column_name} NULLS LAST"
|
177
|
-
COLUMN_NAME_WITH_ORDER = /
|
178
|
-
\A
|
179
|
-
(?:\w+\.)?
|
180
|
-
\w+
|
181
|
-
(?:\s+asc|\s+desc)?
|
182
|
-
(?:\s+nulls\s+(?:first|last))?
|
183
|
-
\z
|
184
|
-
/ix
|
185
|
-
|
186
|
-
def disallow_raw_sql!(args, permit: COLUMN_NAME) # :nodoc:
|
187
|
-
unexpected = args.reject do |arg|
|
188
|
-
Arel.arel_node?(arg) ||
|
189
|
-
arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
|
190
|
-
end
|
191
|
-
|
192
|
-
return if unexpected.none?
|
193
|
-
|
194
|
-
if allow_unsafe_raw_sql == :deprecated
|
195
|
-
ActiveSupport::Deprecation.warn(
|
196
|
-
"Dangerous query method (method whose arguments are used as raw " \
|
197
|
-
"SQL) called with non-attribute argument(s): " \
|
198
|
-
"#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
|
199
|
-
"arguments will be disallowed in Rails 6.0. This method should " \
|
200
|
-
"not be called with user-provided values, such as request " \
|
201
|
-
"parameters or model attributes. Known-safe values can be passed " \
|
202
|
-
"by wrapping them in Arel.sql()."
|
203
|
-
)
|
204
|
-
else
|
205
|
-
raise(ActiveRecord::UnknownAttributeReference,
|
206
|
-
"Query method called with non-attribute argument(s): " +
|
207
|
-
unexpected.map(&:inspect).join(", ")
|
208
|
-
)
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
162
|
# Returns true if the given attribute exists, otherwise false.
|
213
163
|
#
|
214
164
|
# class Person < ActiveRecord::Base
|
@@ -464,7 +414,7 @@ module ActiveRecord
|
|
464
414
|
end
|
465
415
|
|
466
416
|
def pk_attribute?(name)
|
467
|
-
name ==
|
417
|
+
name == @primary_key
|
468
418
|
end
|
469
419
|
end
|
470
420
|
end
|
@@ -46,6 +46,7 @@ module ActiveRecord
|
|
46
46
|
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
47
47
|
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
|
48
48
|
def read_attribute_before_type_cast(attr_name)
|
49
|
+
sync_with_transaction_state if @transaction_state&.finalized?
|
49
50
|
@attributes[attr_name.to_s].value_before_type_cast
|
50
51
|
end
|
51
52
|
|
@@ -60,17 +61,19 @@ module ActiveRecord
|
|
60
61
|
# task.attributes_before_type_cast
|
61
62
|
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
|
62
63
|
def attributes_before_type_cast
|
64
|
+
sync_with_transaction_state if @transaction_state&.finalized?
|
63
65
|
@attributes.values_before_type_cast
|
64
66
|
end
|
65
67
|
|
66
68
|
private
|
67
69
|
|
68
|
-
#
|
70
|
+
# Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
|
69
71
|
def attribute_before_type_cast(attribute_name)
|
70
72
|
read_attribute_before_type_cast(attribute_name)
|
71
73
|
end
|
72
74
|
|
73
75
|
def attribute_came_from_user?(attribute_name)
|
76
|
+
sync_with_transaction_state if @transaction_state&.finalized?
|
74
77
|
@attributes[attribute_name].came_from_user?
|
75
78
|
end
|
76
79
|
end
|
@@ -29,9 +29,7 @@ module ActiveRecord
|
|
29
29
|
# <tt>reload</tt> the record and clears changed attributes.
|
30
30
|
def reload(*)
|
31
31
|
super.tap do
|
32
|
-
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
33
32
|
@mutations_before_last_save = nil
|
34
|
-
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
35
33
|
@mutations_from_database = nil
|
36
34
|
end
|
37
35
|
end
|
@@ -51,7 +49,7 @@ module ActiveRecord
|
|
51
49
|
# +to+ When passed, this method will return false unless the value was
|
52
50
|
# changed to the given value
|
53
51
|
def saved_change_to_attribute?(attr_name, **options)
|
54
|
-
mutations_before_last_save.changed?(attr_name,
|
52
|
+
mutations_before_last_save.changed?(attr_name.to_s, options)
|
55
53
|
end
|
56
54
|
|
57
55
|
# Returns the change to an attribute during the last save. If the
|
@@ -63,7 +61,7 @@ module ActiveRecord
|
|
63
61
|
# invoked as +saved_change_to_name+ instead of
|
64
62
|
# <tt>saved_change_to_attribute("name")</tt>.
|
65
63
|
def saved_change_to_attribute(attr_name)
|
66
|
-
mutations_before_last_save.change_to_attribute(attr_name)
|
64
|
+
mutations_before_last_save.change_to_attribute(attr_name.to_s)
|
67
65
|
end
|
68
66
|
|
69
67
|
# Returns the original value of an attribute before the last save.
|
@@ -73,7 +71,7 @@ module ActiveRecord
|
|
73
71
|
# invoked as +name_before_last_save+ instead of
|
74
72
|
# <tt>attribute_before_last_save("name")</tt>.
|
75
73
|
def attribute_before_last_save(attr_name)
|
76
|
-
mutations_before_last_save.original_value(attr_name)
|
74
|
+
mutations_before_last_save.original_value(attr_name.to_s)
|
77
75
|
end
|
78
76
|
|
79
77
|
# Did the last call to +save+ have any changes to change?
|
@@ -101,7 +99,7 @@ module ActiveRecord
|
|
101
99
|
# +to+ When passed, this method will return false unless the value will be
|
102
100
|
# changed to the given value
|
103
101
|
def will_save_change_to_attribute?(attr_name, **options)
|
104
|
-
mutations_from_database.changed?(attr_name,
|
102
|
+
mutations_from_database.changed?(attr_name.to_s, options)
|
105
103
|
end
|
106
104
|
|
107
105
|
# Returns the change to an attribute that will be persisted during the
|
@@ -115,7 +113,7 @@ module ActiveRecord
|
|
115
113
|
# If the attribute will change, the result will be an array containing the
|
116
114
|
# original value and the new value about to be saved.
|
117
115
|
def attribute_change_to_be_saved(attr_name)
|
118
|
-
mutations_from_database.change_to_attribute(attr_name)
|
116
|
+
mutations_from_database.change_to_attribute(attr_name.to_s)
|
119
117
|
end
|
120
118
|
|
121
119
|
# Returns the value of an attribute in the database, as opposed to the
|
@@ -127,7 +125,7 @@ module ActiveRecord
|
|
127
125
|
# saved. It can be invoked as +name_in_database+ instead of
|
128
126
|
# <tt>attribute_in_database("name")</tt>.
|
129
127
|
def attribute_in_database(attr_name)
|
130
|
-
mutations_from_database.original_value(attr_name)
|
128
|
+
mutations_from_database.original_value(attr_name.to_s)
|
131
129
|
end
|
132
130
|
|
133
131
|
# Will the next call to +save+ have any changes to persist?
|
@@ -158,16 +156,51 @@ module ActiveRecord
|
|
158
156
|
end
|
159
157
|
|
160
158
|
private
|
159
|
+
def mutations_from_database
|
160
|
+
sync_with_transaction_state if @transaction_state&.finalized?
|
161
|
+
super
|
162
|
+
end
|
163
|
+
|
164
|
+
def mutations_before_last_save
|
165
|
+
sync_with_transaction_state if @transaction_state&.finalized?
|
166
|
+
super
|
167
|
+
end
|
168
|
+
|
161
169
|
def write_attribute_without_type_cast(attr_name, value)
|
162
|
-
|
163
|
-
|
164
|
-
name = self.class.attribute_alias(name)
|
165
|
-
end
|
166
|
-
result = super(name, value)
|
167
|
-
clear_attribute_change(name)
|
170
|
+
result = super
|
171
|
+
clear_attribute_change(attr_name)
|
168
172
|
result
|
169
173
|
end
|
170
174
|
|
175
|
+
def _touch_row(attribute_names, time)
|
176
|
+
@_touch_attr_names = Set.new(attribute_names)
|
177
|
+
|
178
|
+
affected_rows = super
|
179
|
+
|
180
|
+
if @_skip_dirty_tracking ||= false
|
181
|
+
clear_attribute_changes(@_touch_attr_names)
|
182
|
+
return affected_rows
|
183
|
+
end
|
184
|
+
|
185
|
+
changes = {}
|
186
|
+
@attributes.keys.each do |attr_name|
|
187
|
+
next if @_touch_attr_names.include?(attr_name)
|
188
|
+
|
189
|
+
if attribute_changed?(attr_name)
|
190
|
+
changes[attr_name] = _read_attribute(attr_name)
|
191
|
+
_write_attribute(attr_name, attribute_was(attr_name))
|
192
|
+
clear_attribute_change(attr_name)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
changes_applied
|
197
|
+
changes.each { |attr_name, value| _write_attribute(attr_name, value) }
|
198
|
+
|
199
|
+
affected_rows
|
200
|
+
ensure
|
201
|
+
@_touch_attr_names, @_skip_dirty_tracking = nil, nil
|
202
|
+
end
|
203
|
+
|
171
204
|
def _update_record(attribute_names = attribute_names_for_partial_writes)
|
172
205
|
affected_rows = super
|
173
206
|
changes_applied
|