diff options
author | Sean Griffin <sean@thoughtbot.com> | 2014-12-22 14:55:58 -0700 |
---|---|---|
committer | Sean Griffin <sean@thoughtbot.com> | 2014-12-22 14:55:58 -0700 |
commit | 18ae0656f527e790bdc827fe68e3f2edd541b3a4 (patch) | |
tree | 954a7fd17c2f88a322f7f9b4efac0eeb3b1d76ba | |
parent | 849274316dc136135c50895e898e294904fec7a2 (diff) | |
download | rails-18ae0656f527e790bdc827fe68e3f2edd541b3a4.tar.gz rails-18ae0656f527e790bdc827fe68e3f2edd541b3a4.tar.bz2 rails-18ae0656f527e790bdc827fe68e3f2edd541b3a4.zip |
Don't calculate all in-place changes to determine if attribute_changed?
Calling `changed_attributes` will ultimately check if every mutable
attribute has changed in place. Since this gets called whenever an
attribute is assigned, it's extremely slow. Instead, we can avoid this
calculation until we actually need it.
Fixes #18029
-rw-r--r-- | activemodel/lib/active_model/dirty.rb | 6 | ||||
-rw-r--r-- | activerecord/lib/active_record/attribute_methods/dirty.rb | 4 | ||||
-rw-r--r-- | activerecord/test/cases/dirty_test.rb | 15 |
3 files changed, 24 insertions, 1 deletions
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 60af31cca7..337b61c55c 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -170,7 +170,7 @@ module ActiveModel # Handle <tt>*_changed?</tt> for +method_missing+. def attribute_changed?(attr, options = {}) #:nodoc: - result = changed_attributes.include?(attr) + result = changes_include?(attr) result &&= options[:to] == __send__(attr) if options.key?(:to) result &&= options[:from] == changed_attributes[attr] if options.key?(:from) result @@ -188,6 +188,10 @@ module ActiveModel private + def changes_include?(attr_name) + attributes_changed_by_setter.include?(attr_name) + end + # Removes current changes and makes them accessible through +previous_changes+. def changes_applied # :doc: @previously_changed = changes diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 033e71f7b9..d5702accaf 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -76,6 +76,10 @@ module ActiveRecord private + def changes_include?(attr_name) + super || attribute_changed_in_place?(attr_name) + end + def calculate_changes_from_defaults @changed_attributes = nil self.class.column_defaults.each do |attr, orig_value| diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index eb9b1a2d74..1eaff5e293 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -698,6 +698,21 @@ class DirtyTest < ActiveRecord::TestCase assert binary.changed? end + test "attribute_changed? doesn't compute in-place changes for unrelated attributes" do + test_type_class = Class.new(ActiveRecord::Type::Value) do + define_method(:changed_in_place?) do |*| + raise + end + end + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'people' + attribute :foo, test_type_class.new + end + + model = klass.new(first_name: "Jim") + assert model.first_name_changed? + end + private def with_partial_writes(klass, on = true) old = klass.partial_writes? |