Separate `dup` from `deep_dup` in the attributes hash

I'm looking to move towards a tree-like structure for dirty checking
that involves an attribute holding onto the attribute that it was
created from. This means that `changed?` can be fully encapsulated on
that object. Since the objects are immutable, in `changes_applied`, we
can simply perform a shallow dup, instead of a deep one.

I'm not sure if that will actually end up in a performance boost, but
I'd like to semantically separate these concepts regardless
This commit is contained in:
Sean Griffin 2015-09-28 16:17:08 -04:00
parent e950c4b4a5
commit fb03a9ab35
5 changed files with 36 additions and 6 deletions

View File

@ -47,7 +47,7 @@ module ActiveRecord
def initialize_dup(other) # :nodoc:
super
@mutation_tracker = AttributeMutationTracker.new(@attributes,
self.class._default_attributes.dup)
self.class._default_attributes.deep_dup)
end
def changes_applied

View File

@ -60,8 +60,14 @@ module ActiveRecord
super
end
def deep_dup
dup.tap do |copy|
copy.instance_variable_set(:@attributes, attributes.deep_dup)
end
end
def initialize_dup(_)
@attributes = attributes.deep_dup
@attributes = attributes.dup
super
end

View File

@ -47,8 +47,14 @@ module ActiveRecord
delegate_hash[key] = value
end
def deep_dup
dup.tap do |copy|
copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
end
end
def initialize_dup(_)
@delegate_hash = delegate_hash.transform_values(&:dup)
@delegate_hash = Hash[delegate_hash]
super
end

View File

@ -296,7 +296,7 @@ module ActiveRecord
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil)
@attributes = self.class._default_attributes.dup
@attributes = self.class._default_attributes.deep_dup
self.class.define_attribute_methods
init_internals
@ -366,7 +366,7 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
@attributes = @attributes.dup
@attributes = @attributes.deep_dup
@attributes.reset(self.class.primary_key)
_run_initialize_callbacks

View File

@ -29,7 +29,7 @@ module ActiveRecord
assert_equal :bar, attributes[:bar].name
end
test "duping creates a new hash and dups each attribute" do
test "duping creates a new hash, but does not dup the attributes" do
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
attributes = builder.build_from_database(foo: 1, bar: 'foo')
@ -41,6 +41,24 @@ module ActiveRecord
duped.write_from_database(:foo, 2)
duped[:bar].value << 'bar'
assert_equal 1, attributes[:foo].value
assert_equal 2, duped[:foo].value
assert_equal 'foobar', attributes[:bar].value
assert_equal 'foobar', duped[:bar].value
end
test "deep_duping creates a new hash and dups each attribute" do
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
attributes = builder.build_from_database(foo: 1, bar: 'foo')
# Ensure the type cast value is cached
attributes[:foo].value
attributes[:bar].value
duped = attributes.deep_dup
duped.write_from_database(:foo, 2)
duped[:bar].value << 'bar'
assert_equal 1, attributes[:foo].value
assert_equal 2, duped[:foo].value
assert_equal 'foo', attributes[:bar].value