Repository: rails/rails
File: activemodel/lib/active_model/attribute_mutation_tracker.rb (Ruby)

Code before:
require "active_support/core_ext/object/duplicable"

module ActiveModel
  class AttributeMutationTracker # :nodoc:
    OPTION_NOT_GIVEN = Object.new.freeze

    def initialize(attributes)
      @attributes = attributes
    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] = original_value(attr_name)
        end
      end
    end

    def changes
      attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
        if change = change_to_attribute(attr_name)
          result.raw_store(attr_name, change)
        end
      end
    end

    def change_to_attribute(attr_name)
      if changed?(attr_name)
        [original_value(attr_name), 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)
      attribute_changed?(attr_name) &&
        (OPTION_NOT_GIVEN == from || original_value(attr_name) == type_cast(attr_name, from)) &&
        (OPTION_NOT_GIVEN == to || fetch_value(attr_name) == type_cast(attr_name, to))
    end

    def changed_in_place?(attr_name)
      attributes[attr_name].changed_in_place?
    end

    def forget_change(attr_name)

Review comment (suggestion):
`changed_values` suffers from the same problem, so the same fix should be applied to it.

Code after:
require "active_support/core_ext/object/duplicable"

module ActiveModel
  class AttributeMutationTracker # :nodoc:
    OPTION_NOT_GIVEN = Object.new.freeze

    def initialize(attributes)
      @attributes = attributes
    end

    def changed_attribute_names
      attr_names.select { |attr_name| changed?(attr_name) }
    end

    def changed_values
      attr_names.each_with_object(ActiveSupport::HashWithIndifferentAccess.new) do |attr_name, result|
        if changed?(attr_name)
          result.store(attr_name, original_value(attr_name), convert_value: false)
        end
      end
    end

    def changes
      attr_names.each_with_object(ActiveSupport::HashWithIndifferentAccess.new) do |attr_name, result|
        if change = change_to_attribute(attr_name)
          result.store(attr_name, change, convert_value: false)
        end
      end
    end

    def change_to_attribute(attr_name)
      if changed?(attr_name)
        [original_value(attr_name), 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)
      attribute_changed?(attr_name) &&
        (OPTION_NOT_GIVEN == from || original_value(attr_name) == type_cast(attr_name, from)) &&
        (OPTION_NOT_GIVEN == to || fetch_value(attr_name) == type_cast(attr_name, to))
    end

    def changed_in_place?(attr_name)
      attributes[attr_name].changed_in_place?
    end

    def forget_change(attr_name)