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.

Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +466 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +0 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +10 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +6 -2
  16. data/lib/active_record/associations/collection_proxy.rb +2 -2
  17. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  18. data/lib/active_record/associations/join_dependency.rb +14 -9
  19. data/lib/active_record/associations/join_dependency/join_association.rb +12 -3
  20. data/lib/active_record/associations/preloader.rb +13 -8
  21. data/lib/active_record/associations/preloader/association.rb +34 -30
  22. data/lib/active_record/associations/preloader/through_association.rb +48 -28
  23. data/lib/active_record/attribute_methods.rb +3 -53
  24. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  25. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  26. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  27. data/lib/active_record/attribute_methods/query.rb +2 -3
  28. data/lib/active_record/attribute_methods/read.rb +3 -9
  29. data/lib/active_record/attribute_methods/write.rb +6 -12
  30. data/lib/active_record/attributes.rb +13 -0
  31. data/lib/active_record/autosave_association.rb +21 -7
  32. data/lib/active_record/base.rb +0 -1
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -11
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +88 -61
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -4
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
  39. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  40. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +79 -22
  41. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
  42. data/lib/active_record/connection_adapters/abstract_adapter.rb +111 -33
  43. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +78 -73
  44. data/lib/active_record/connection_adapters/column.rb +17 -13
  45. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  46. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  49. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -5
  50. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
  51. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  52. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  53. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  54. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  55. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  56. data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
  57. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
  58. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  59. data/lib/active_record/connection_adapters/postgresql_adapter.rb +67 -26
  60. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  61. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  62. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  63. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  64. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  65. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -114
  66. data/lib/active_record/connection_handling.rb +31 -13
  67. data/lib/active_record/core.rb +23 -24
  68. data/lib/active_record/database_configurations.rb +73 -44
  69. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  70. data/lib/active_record/database_configurations/url_config.rb +12 -12
  71. data/lib/active_record/dynamic_matchers.rb +1 -1
  72. data/lib/active_record/enum.rb +15 -0
  73. data/lib/active_record/errors.rb +1 -1
  74. data/lib/active_record/fixtures.rb +11 -6
  75. data/lib/active_record/gem_version.rb +2 -2
  76. data/lib/active_record/insert_all.rb +179 -0
  77. data/lib/active_record/integration.rb +13 -1
  78. data/lib/active_record/internal_metadata.rb +5 -1
  79. data/lib/active_record/locking/optimistic.rb +3 -4
  80. data/lib/active_record/log_subscriber.rb +1 -1
  81. data/lib/active_record/middleware/database_selector.rb +3 -3
  82. data/lib/active_record/middleware/database_selector/resolver.rb +4 -6
  83. data/lib/active_record/migration.rb +62 -44
  84. data/lib/active_record/migration/command_recorder.rb +28 -14
  85. data/lib/active_record/migration/compatibility.rb +10 -0
  86. data/lib/active_record/model_schema.rb +3 -0
  87. data/lib/active_record/persistence.rb +206 -13
  88. data/lib/active_record/querying.rb +17 -12
  89. data/lib/active_record/railtie.rb +0 -1
  90. data/lib/active_record/railties/databases.rake +127 -25
  91. data/lib/active_record/reflection.rb +3 -3
  92. data/lib/active_record/relation.rb +99 -20
  93. data/lib/active_record/relation/calculations.rb +38 -40
  94. data/lib/active_record/relation/delegation.rb +22 -30
  95. data/lib/active_record/relation/finder_methods.rb +17 -12
  96. data/lib/active_record/relation/merger.rb +11 -16
  97. data/lib/active_record/relation/query_methods.rb +228 -76
  98. data/lib/active_record/relation/where_clause.rb +9 -5
  99. data/lib/active_record/sanitization.rb +33 -4
  100. data/lib/active_record/schema.rb +1 -1
  101. data/lib/active_record/schema_dumper.rb +10 -1
  102. data/lib/active_record/schema_migration.rb +1 -1
  103. data/lib/active_record/scoping/default.rb +6 -7
  104. data/lib/active_record/scoping/named.rb +3 -2
  105. data/lib/active_record/statement_cache.rb +2 -2
  106. data/lib/active_record/store.rb +48 -0
  107. data/lib/active_record/table_metadata.rb +9 -13
  108. data/lib/active_record/tasks/database_tasks.rb +109 -6
  109. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  110. data/lib/active_record/test_databases.rb +1 -16
  111. data/lib/active_record/test_fixtures.rb +1 -0
  112. data/lib/active_record/timestamp.rb +26 -16
  113. data/lib/active_record/touch_later.rb +4 -2
  114. data/lib/active_record/transactions.rb +56 -46
  115. data/lib/active_record/type_caster/connection.rb +16 -10
  116. data/lib/active_record/validations.rb +1 -0
  117. data/lib/active_record/validations/uniqueness.rb +3 -5
  118. data/lib/arel.rb +12 -5
  119. data/lib/arel/insert_manager.rb +3 -3
  120. data/lib/arel/nodes.rb +2 -1
  121. data/lib/arel/nodes/comment.rb +29 -0
  122. data/lib/arel/nodes/select_core.rb +16 -12
  123. data/lib/arel/nodes/unary.rb +1 -0
  124. data/lib/arel/nodes/values_list.rb +2 -17
  125. data/lib/arel/select_manager.rb +10 -10
  126. data/lib/arel/visitors/depth_first.rb +7 -2
  127. data/lib/arel/visitors/dot.rb +7 -2
  128. data/lib/arel/visitors/ibm_db.rb +13 -0
  129. data/lib/arel/visitors/informix.rb +6 -0
  130. data/lib/arel/visitors/mssql.rb +15 -1
  131. data/lib/arel/visitors/oracle12.rb +4 -5
  132. data/lib/arel/visitors/postgresql.rb +4 -10
  133. data/lib/arel/visitors/to_sql.rb +107 -131
  134. data/lib/arel/visitors/visitor.rb +9 -5
  135. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  136. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  137. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  138. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  139. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  140. metadata +16 -12
  141. data/lib/active_record/collection_cache_key.rb +0 -53
  142. 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 != table.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
- right = joins.last.right
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
- loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
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(preloader); end
166
+ def run
167
+ self
168
+ end
170
169
 
171
170
  def preloaded_records
172
- owners.flat_map { |owner| owner.association(reflection.name).target }
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.first.association(reflection.name).loaded?
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(preloader)
19
- records = load_records do |record|
20
- owner = owners_by_key[convert_key(record[association_key_name])]
21
- association = owner.association(reflection.name)
22
- association.set_inverse_instance(record)
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
- owners.each do |owner|
26
- associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
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
- unless defined?(@owners_by_key)
58
- @owners_by_key = owners.each_with_object({}) do |owner, h|
59
- key = convert_key(owner[owner_key_name])
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 load_records(&block)
91
- return {} if owner_keys.empty?
92
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
93
- # Make several smaller queries if necessary or make one query if the adapter supports it
94
- slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
95
- @preloaded_records = slices.flat_map do |slice|
96
- records_for(slice, &block)
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
- def run(preloader)
8
- already_loaded = owners.first.association(through_reflection.name).loaded?
9
- through_scope = through_scope()
10
- through_preloaders = preloader.preload(owners, through_reflection.name, through_scope)
11
- middle_records = through_preloaders.flat_map(&:preloaded_records)
12
- preloaders = preloader.preload(middle_records, source_reflection.name, scope)
13
- @preloaded_records = preloaders.flat_map(&:preloaded_records)
14
-
15
- owners.each do |owner|
16
- through_records = Array(owner.association(through_reflection.name).target)
17
- if already_loaded
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
- result.compact!
30
- result.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
31
- result.uniq! if scope.distinct_value
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 ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
52
- result[id] = index
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 unless scope.empty_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 == self.class.primary_key
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
- # Handle *_before_type_cast for method_missing.
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, **options)
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, **options)
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
- name = attr_name.to_s
163
- if self.class.attribute_alias?(name)
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