aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/attribute_methods
diff options
context:
space:
mode:
authorRyuta Kamizono <kamipo@gmail.com>2018-05-19 11:46:03 +0900
committerRyuta Kamizono <kamipo@gmail.com>2019-04-16 12:30:45 +0900
commit63ff495bdf90e0ab20114a49db5cffe3cb9ef2fd (patch)
tree33d5e390a602ef7c77a2c675865b573802fdd35b /activerecord/lib/active_record/attribute_methods
parent20b94af9eb9305d19a343f72f0afb18eb49e2de7 (diff)
downloadrails-63ff495bdf90e0ab20114a49db5cffe3cb9ef2fd.tar.gz
rails-63ff495bdf90e0ab20114a49db5cffe3cb9ef2fd.tar.bz2
rails-63ff495bdf90e0ab20114a49db5cffe3cb9ef2fd.zip
Fix dirty tracking after rollback.
Currently the rollback only restores primary key value, `new_record?`, `destroyed?`, and `frozen?`. Since the `save` clears current dirty attribute states, retrying save after rollback will causes no change saved if partial writes is enabled (by default). This makes `remember_transaction_record_state` remembers original values then restores dirty attribute states after rollback. Fixes #15018. Fixes #30167. Fixes #33868. Fixes #33443. Closes #33444. Closes #34504.
Diffstat (limited to 'activerecord/lib/active_record/attribute_methods')
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb9
5 files changed, 17 insertions, 16 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index dc239ff9ea..affcf2a4db 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -46,6 +46,7 @@ module ActiveRecord
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
+ sync_with_transaction_state
@attributes[attr_name.to_s].value_before_type_cast
end
@@ -60,6 +61,7 @@ module ActiveRecord
# task.attributes_before_type_cast
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
def attributes_before_type_cast
+ sync_with_transaction_state
@attributes.values_before_type_cast
end
@@ -71,6 +73,7 @@ module ActiveRecord
end
def attribute_came_from_user?(attribute_name)
+ sync_with_transaction_state
@attributes[attribute_name].came_from_user?
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 68ac8475b0..942fe48635 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -156,6 +156,16 @@ module ActiveRecord
end
private
+ def mutations_from_database
+ sync_with_transaction_state
+ super
+ end
+
+ def mutations_before_last_save
+ sync_with_transaction_state
+ super
+ end
+
def write_attribute_without_type_cast(attr_name, value)
name = attr_name.to_s
if self.class.attribute_alias?(name)
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 6af5346fa7..feaef72a30 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -16,39 +16,33 @@ module ActiveRecord
# Returns the primary key column's value.
def id
- sync_with_transaction_state
primary_key = self.class.primary_key
_read_attribute(primary_key) if primary_key
end
# Sets the primary key column's value.
def id=(value)
- sync_with_transaction_state
primary_key = self.class.primary_key
_write_attribute(primary_key, value) if primary_key
end
# Queries the primary key column's value.
def id?
- sync_with_transaction_state
query_attribute(self.class.primary_key)
end
# Returns the primary key column's value before type cast.
def id_before_type_cast
- sync_with_transaction_state
read_attribute_before_type_cast(self.class.primary_key)
end
# Returns the primary key column's previous value.
def id_was
- sync_with_transaction_state
attribute_was(self.class.primary_key)
end
# Returns the primary key column's value from the database.
def id_in_database
- sync_with_transaction_state
attribute_in_database(self.class.primary_key)
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index ffac5313ad..84b1ec2fea 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -9,14 +9,11 @@ module ActiveRecord
private
def define_method_attribute(name)
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
-
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
generated_attribute_methods, name
) do |temp_method_name, attr_name_expr|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{temp_method_name}
- #{sync_with_transaction_state}
name = #{attr_name_expr}
_read_attribute(name) { |n| missing_attribute(n, caller) }
end
@@ -36,13 +33,13 @@ module ActiveRecord
primary_key = self.class.primary_key
name = primary_key if name == "id" && primary_key
- sync_with_transaction_state if name == primary_key
_read_attribute(name, &block)
end
# This method exists to avoid the expensive primary_key check internally, without
# breaking compatibility with the read_attribute API
def _read_attribute(attr_name, &block) # :nodoc
+ sync_with_transaction_state
@attributes.fetch_value(attr_name.to_s, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index d5ba2f42cb..d1cfe43bb2 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -13,15 +13,12 @@ module ActiveRecord
private
def define_method_attribute=(name)
- sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
-
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
generated_attribute_methods, name, writer: true,
) do |temp_method_name, attr_name_expr|
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{temp_method_name}(value)
name = #{attr_name_expr}
- #{sync_with_transaction_state}
_write_attribute(name, value)
end
RUBY
@@ -40,21 +37,21 @@ module ActiveRecord
primary_key = self.class.primary_key
name = primary_key if name == "id" && primary_key
- sync_with_transaction_state if name == primary_key
_write_attribute(name, value)
end
# This method exists to avoid the expensive primary_key check internally, without
# breaking compatibility with the write_attribute API
def _write_attribute(attr_name, value) # :nodoc:
+ sync_with_transaction_state
@attributes.write_from_user(attr_name.to_s, value)
value
end
private
def write_attribute_without_type_cast(attr_name, value)
- name = attr_name.to_s
- @attributes.write_cast_value(name, value)
+ sync_with_transaction_state
+ @attributes.write_cast_value(attr_name.to_s, value)
value
end