mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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:
parent
e950c4b4a5
commit
fb03a9ab35
5 changed files with 36 additions and 6 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue