mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
eb3740dcb0
Prevent changes_to_save from mutating 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.merge!(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
|