From 8a811c83aa386e4ad26553c913d1a450d8a98d5f Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Thu, 24 Sep 2015 13:43:33 -0600 Subject: Encapsulate a lot of the logic from `Dirty` in an object In order to improve the performance of dirty checking, we're going to need to duplicate all of the `previous_` methods in Active Model. However, these methods are basically the same as their non-previous counterparts, but comparing `@original_attributes` to `@previous_original_attributes` instead of `@attributes` and `@original_attributes`. This will help reduce that duplication. --- .../lib/active_record/attribute_methods/dirty.rb | 33 ++++--------- .../active_record/attribute_mutation_tracker.rb | 55 ++++++++++++++++++++++ activerecord/lib/active_record/persistence.rb | 2 +- 3 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 activerecord/lib/active_record/attribute_mutation_tracker.rb (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index fd0a3cd313..a439683185 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/module/attribute_accessors' +require 'active_record/attribute_mutation_tracker' module ActiveRecord module AttributeMethods @@ -40,12 +41,13 @@ module ActiveRecord def init_internals super - @original_attributes = @attributes.dup + @mutation_tracker = AttributeMutationTracker.new(@attributes, @attributes.dup) end def initialize_dup(other) # :nodoc: super - @original_attributes = self.class._default_attributes.dup + @mutation_tracker = AttributeMutationTracker.new(@attributes, + self.class._default_attributes.dup) end def changes_applied @@ -77,7 +79,7 @@ module ActiveRecord if defined?(@cached_changed_attributes) @cached_changed_attributes else - calculate_changed_attributes.freeze + super.reverse_merge(@mutation_tracker.changed_values).freeze end end @@ -88,20 +90,17 @@ module ActiveRecord end def attribute_changed_in_place?(attr_name) - original_database_value = @original_attributes[attr_name].value_before_type_cast - @attributes[attr_name].changed_in_place_from?(original_database_value) + @mutation_tracker.changed_in_place?(attr_name) end private def changes_include?(attr_name) - attr_name = attr_name.to_s - super || attribute_modified?(attr_name) || attribute_changed_in_place?(attr_name) + super || @mutation_tracker.changed?(attr_name) end def clear_attribute_change(attr_name) - attr_name = attr_name.to_s - @original_attributes[attr_name] = @attributes[attr_name].dup + @mutation_tracker.forget_change(attr_name) end def _update_record(*) @@ -116,22 +115,8 @@ module ActiveRecord changed & self.class.column_names end - def attribute_modified?(attr_name) - @attributes[attr_name].changed_from?(@original_attributes.fetch_value(attr_name)) - end - def store_original_attributes - @original_attributes = @attributes.map do |attr| - attr.with_value_from_database(attr.value_for_database) - end - end - - def calculate_changed_attributes - attribute_names.each_with_object({}.with_indifferent_access) do |attr_name, result| - if changes_include?(attr_name) - result[attr_name] = @original_attributes.fetch_value(attr_name) - end - end + @mutation_tracker = @mutation_tracker.now_tracking(@attributes) end def cache_changed_attributes diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb new file mode 100644 index 0000000000..e4be5c5524 --- /dev/null +++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb @@ -0,0 +1,55 @@ +module ActiveRecord + class AttributeMutationTracker + def initialize(attributes, original_attributes) + @attributes = attributes + @original_attributes = original_attributes + end + + def changed_values + attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| + if changed?(attr_name) + result[attr_name] = original_attributes.fetch_value(attr_name) + end + end + end + + def changed?(attr_name) + attr_name = attr_name.to_s + modified?(attr_name) || changed_in_place?(attr_name) + end + + def changed_in_place?(attr_name) + original_database_value = original_attributes[attr_name].value_before_type_cast + attributes[attr_name].changed_in_place_from?(original_database_value) + end + + def forget_change(attr_name) + attr_name = attr_name.to_s + original_attributes[attr_name] = attributes[attr_name].dup + end + + def now_tracking(new_attributes) + AttributeMutationTracker.new(new_attributes, clean_copy_of(new_attributes)) + end + + protected + + attr_reader :attributes, :original_attributes + + private + + def attr_names + attributes.keys + end + + def modified?(attr_name) + attributes[attr_name].changed_from?(original_attributes.fetch_value(attr_name)) + end + + def clean_copy_of(attributes) + attributes.map do |attr| + attr.with_value_from_database(attr.value_for_database) + end + end + end +end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 1b7ee0bd38..7b53f6e5a0 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -211,7 +211,7 @@ module ActiveRecord def becomes(klass) became = klass.new became.instance_variable_set("@attributes", @attributes) - became.instance_variable_set("@original_attributes", @original_attributes) + became.instance_variable_set("@mutation_tracker", @mutation_tracker) became.instance_variable_set("@changed_attributes", attributes_changed_by_setter) became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@destroyed", destroyed?) -- cgit v1.2.3