diff options
author | Sean Griffin <sean@seantheprogrammer.com> | 2015-09-28 16:17:08 -0400 |
---|---|---|
committer | Sean Griffin <sean@seantheprogrammer.com> | 2015-09-28 16:26:50 -0400 |
commit | fb03a9ab35ed22e569ec9cef8a50ef72754b5dbe (patch) | |
tree | 6a3bd9b1b02e35f452c8158544a8748d7a6ec301 /activerecord | |
parent | e950c4b4a503d801ac141c143171dbffe758e6eb (diff) | |
download | rails-fb03a9ab35ed22e569ec9cef8a50ef72754b5dbe.tar.gz rails-fb03a9ab35ed22e569ec9cef8a50ef72754b5dbe.tar.bz2 rails-fb03a9ab35ed22e569ec9cef8a50ef72754b5dbe.zip |
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
Diffstat (limited to 'activerecord')
5 files changed, 36 insertions, 6 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 84b942d559..43c15841c2 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -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 diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index ee278388a4..026b3cf014 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -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 diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 0c730d313f..f974b7a876 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -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 diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index ffce2173ec..894d18b79e 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -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 diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb index 7524243270..5a0e463a48 100644 --- a/activerecord/test/cases/attribute_set_test.rb +++ b/activerecord/test/cases/attribute_set_test.rb @@ -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') @@ -43,6 +43,24 @@ module ActiveRecord 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 assert_equal 'foobar', duped[:bar].value end |