1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activemodel/lib/active_model/attribute_mutation_tracker.rb
Eugene Kenny b9e1c0c4d7 Avoid generating full changes hash on every save
`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.
2018-04-08 22:56:31 +01:00

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