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
This commit is contained in:
Sean Griffin 2014-12-22 14:55:58 -07:00
parent 849274316d
commit 18ae0656f5
3 changed files with 24 additions and 1 deletions

View File

@ -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

View File

@ -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|

View File

@ -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?