<commit_before># frozen_string_literal: true

module Spree
  # `Spree::Address` provides the foundational ActiveRecord model for recording and
  # validating address information for `Spree::Order`, `Spree::Shipment`,
  # `Spree::UserAddress`, and `Spree::Carton`.
  #
  class Address < Spree::Base
    extend ActiveModel::ForbiddenAttributesProtection

    belongs_to :country, class_name: "Spree::Country", optional: true
    belongs_to :state, class_name: "Spree::State", optional: true

    validates :firstname, :address1, :city, :country_id, presence: true
    validates :zipcode, presence: true, if: :require_zipcode?
    validates :phone, presence: true, if: :require_phone?

    validate :state_validate
    validate :validate_state_matches_country

    alias_attribute :first_name, :firstname
    alias_attribute :last_name, :lastname

    DB_ONLY_ATTRS = %w(id updated_at created_at)
    TAXATION_ATTRS = %w(state_id country_id zipcode)

    self.whitelisted_ransackable_attributes = %w[firstname lastname]

    scope :with_values, ->(attributes) do
      where(value_attributes(attributes))
    end

    # @return [Address] an address with default attributes
    def self.build_default(*args, &block)
      where(country: Spree::Country.default).build(*args, &block)
    end

    # @return [Address] an equal address already in the database or a newly created one
    def self.factory(attributes)
      full_attributes = value_attributes(column_defaults, new(attributes).attributes)
      find_or_initialize_by(full_attributes)
    end

    # @return [Address] address from existing address plus new_attributes as diff
    # @note, this may return existing_address if there are no changes to value equality
    def self.immutable_merge(existing_address, new_attributes)
      # Ensure new_attributes is a sanitized hash
      new_attributes = sanitize_for_mass_assignment(new_attributes)

      return factory(new_attributes) if existing_address.nil?

      merged_attributes = value_attributes(existing_address.attributes, new_attributes)
      new_address = factory(merged_attributes)
      if existing_address == new_address
        existing_address
      else
        new_address
      end
    end

    # @return [Hash] hash of attributes contributing to value equality with optional merge
    def self.value_attributes(base_attributes, merge_attributes = nil)
      # dup because we may modify firstname/lastname.
      base = base_attributes.dup

      base.stringify_keys!

      if merge_attributes
        base.merge!(merge_attributes.stringify_keys)
      end

      # TODO: Deprecate these aliased attributes
      base['firstname'] = base.delete('first_name') if base.key?('first_name')
      base['lastname'] = base.delete('last_name') if base.key?('last_name')

      base.except!(*DB_ONLY_ATTRS)
    end

    # @return [Hash] hash of attributes contributing to value equality
    def value_attributes
      self.class.value_attributes(attributes)
    end
<commit_msg>Refactor Spree::Address value_attributes<commit_after># frozen_string_literal: true

module Spree
  # `Spree::Address` provides the foundational ActiveRecord model for recording and
  # validating address information for `Spree::Order`, `Spree::Shipment`,
  # `Spree::UserAddress`, and `Spree::Carton`.
  #
  class Address < Spree::Base
    extend ActiveModel::ForbiddenAttributesProtection

    belongs_to :country, class_name: "Spree::Country", optional: true
    belongs_to :state, class_name: "Spree::State", optional: true

    validates :firstname, :address1, :city, :country_id, presence: true
    validates :zipcode, presence: true, if: :require_zipcode?
    validates :phone, presence: true, if: :require_phone?

    validate :state_validate
    validate :validate_state_matches_country

    alias_attribute :first_name, :firstname
    alias_attribute :last_name, :lastname

    DB_ONLY_ATTRS = %w(id updated_at created_at)
    TAXATION_ATTRS = %w(state_id country_id zipcode)

    self.whitelisted_ransackable_attributes = %w[firstname lastname]

    scope :with_values, ->(attributes) do
      where(value_attributes(attributes))
    end

    # @return [Address] an address with default attributes
    def self.build_default(*args, &block)
      where(country: Spree::Country.default).build(*args, &block)
    end

    # @return [Address] an equal address already in the database or a newly created one
    def self.factory(attributes)
      full_attributes = value_attributes(column_defaults, new(attributes).attributes)
      find_or_initialize_by(full_attributes)
    end

    # @return [Address] address from existing address plus new_attributes as diff
    # @note, this may return existing_address if there are no changes to value equality
    def self.immutable_merge(existing_address, new_attributes)
      # Ensure new_attributes is a sanitized hash
      new_attributes = sanitize_for_mass_assignment(new_attributes)

      return factory(new_attributes) if existing_address.nil?

      merged_attributes = value_attributes(existing_address.attributes, new_attributes)
      new_address = factory(merged_attributes)
      if existing_address == new_address
        existing_address
      else
        new_address
      end
    end

    # @return [Hash] hash of attributes contributing to value equality with optional merge
    def self.value_attributes(base_attributes, merge_attributes = {})
      base = base_attributes.stringify_keys.merge(merge_attributes.stringify_keys)

      # TODO: Deprecate these aliased attributes
      base['firstname'] = base['first_name'] if base.key?('first_name')
      base['lastname'] = base['last_name'] if base.key?('last_name')

      excluded_attributes = DB_ONLY_ATTRS + %w(first_name last_name)

      base.except(*excluded_attributes)
    end

    # @return [Hash] hash of attributes contributing to value equality
    def value_attributes
      self.class.value_attributes(attributes)
    end

{'fraction_non_alphanumeric': 0.08233910267181986, 'fraction_numerical': 0.0003360779700890607, 'mean_word_length': 4.207349081364829, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1342062', 'n_tokens_mistral': 1689, 'n_tokens_neox': 1661, 'n_words': 452}