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
Ryuta Kamizono eb3740dcb0
Merge pull request #32498 from eugeneius/mutation_tracker_merge_changes
Prevent changes_to_save from mutating attributes
2018-04-10 13:18:34 +09: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.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