mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
b9e1c0c4d7
`changed_attribute_names_to_save` is called in `keys_for_partial_write`, which is called on every save when partial writes are enabled. We can avoid generating the full changes hash by asking the mutation tracker for just the names of the changed attributes. At minimum this saves one array allocation per attribute, but will also avoid calling `Attribute#original_value` which is expensive for serialized attributes.
119 lines
2.5 KiB
Ruby
119 lines
2.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "active_support/core_ext/hash/indifferent_access"
|
|
|
|
module ActiveModel
|
|
class AttributeMutationTracker # :nodoc:
|
|
OPTION_NOT_GIVEN = Object.new
|
|
|
|
def initialize(attributes)
|
|
@attributes = attributes
|
|
@forced_changes = Set.new
|
|
end
|
|
|
|
def changed_attribute_names
|
|
attr_names.select { |attr_name| changed?(attr_name) }
|
|
end
|
|
|
|
def changed_values
|
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
|
if changed?(attr_name)
|
|
result[attr_name] = attributes[attr_name].original_value
|
|
end
|
|
end
|
|
end
|
|
|
|
def changes
|
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
|
change = change_to_attribute(attr_name)
|
|
if change
|
|
result[attr_name] = change
|
|
end
|
|
end
|
|
end
|
|
|
|
def change_to_attribute(attr_name)
|
|
attr_name = attr_name.to_s
|
|
if changed?(attr_name)
|
|
[attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
|
|
end
|
|
end
|
|
|
|
def any_changes?
|
|
attr_names.any? { |attr| changed?(attr) }
|
|
end
|
|
|
|
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
|
|
attr_name = attr_name.to_s
|
|
forced_changes.include?(attr_name) ||
|
|
attributes[attr_name].changed? &&
|
|
(OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) &&
|
|
(OPTION_NOT_GIVEN == to || attributes[attr_name].value == to)
|
|
end
|
|
|
|
def changed_in_place?(attr_name)
|
|
attributes[attr_name.to_s].changed_in_place?
|
|
end
|
|
|
|
def forget_change(attr_name)
|
|
attr_name = attr_name.to_s
|
|
attributes[attr_name] = attributes[attr_name].forgetting_assignment
|
|
forced_changes.delete(attr_name)
|
|
end
|
|
|
|
def original_value(attr_name)
|
|
attributes[attr_name.to_s].original_value
|
|
end
|
|
|
|
def force_change(attr_name)
|
|
forced_changes << attr_name.to_s
|
|
end
|
|
|
|
private
|
|
attr_reader :attributes, :forced_changes
|
|
|
|
def attr_names
|
|
attributes.keys
|
|
end
|
|
end
|
|
|
|
class NullMutationTracker # :nodoc:
|
|
include Singleton
|
|
|
|
def changed_attribute_names(*)
|
|
[]
|
|
end
|
|
|
|
def changed_values(*)
|
|
{}
|
|
end
|
|
|
|
def changes(*)
|
|
{}
|
|
end
|
|
|
|
def change_to_attribute(attr_name)
|
|
end
|
|
|
|
def any_changes?(*)
|
|
false
|
|
end
|
|
|
|
def changed?(*)
|
|
false
|
|
end
|
|
|
|
def changed_in_place?(*)
|
|
false
|
|
end
|
|
|
|
def forget_change(*)
|
|
end
|
|
|
|
def original_value(*)
|
|
end
|
|
|
|
def force_change(*)
|
|
end
|
|
end
|
|
end
|