aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean Griffin <sean@thoughtbot.com>2014-12-22 14:55:58 -0700
committerSean Griffin <sean@thoughtbot.com>2014-12-22 14:55:58 -0700
commit18ae0656f527e790bdc827fe68e3f2edd541b3a4 (patch)
tree954a7fd17c2f88a322f7f9b4efac0eeb3b1d76ba
parent849274316dc136135c50895e898e294904fec7a2 (diff)
downloadrails-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.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb4
-rw-r--r--activerecord/test/cases/dirty_test.rb15
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?