# frozen_string_literal: true module Spree module UserAddressBook extend ActiveSupport::Concern included do has_many :user_addresses, -> { active }, foreign_key: "user_id", class_name: "Spree::UserAddress" do def find_first_by_address_values(address_attrs) detect { |ua| ua.address == Spree::Address.new(address_attrs) } end def mark_default(user_address, address_type: :shipping) column_for_default = address_type == :shipping ? :default : :default_billing ActiveRecord::Base.transaction do (self - [user_address]).each do |address| # update_all would be nice, but it bypasses ActiveRecord callbacks if address.persisted? address.update!(column_for_default => false) else address.write_attribute(column_for_default, false) end end if user_address.persisted? user_address.update!(column_for_default => true, archived: false) else user_address.write_attribute(column_for_default, true) end end end end has_many :addresses, through: :user_addresses has_one :default_user_bill_address, ->{ default_billing }, class_name: 'Spree::UserAddress', foreign_key: 'user_id' has_one :bill_address, through: :default_user_bill_address, source: :address has_one :default_user_ship_address, ->{ default_shipping }, class_name: 'Spree::UserAddress', foreign_key: 'user_id' has_one :ship_address, through: :default_user_ship_address, source: :address accepts_nested_attributes_for :ship_address accepts_nested_attributes_for :bill_address end # saves address in address book # sets address to the default if automatic_default_address is set to true # if address is nil, does nothing and returns nil def ship_address=(address) if address save_in_address_book(address.attributes, Spree::Config.automatic_default_address) end end def ship_address_attributes=(attributes) self.ship_address = Spree::Address.immutable_merge(ship_address, attributes) end def bill_address=(address) if address save_in_address_book(address.attributes, Spree::Config.automatic_default_address, :billing) end end def bill_address_attributes=(attributes) self.bill_address = Spree::Address.immutable_merge(bill_address, attributes) end # saves order.ship_address and order.bill_address in address book # sets ship_address to the default if automatic_default_address is set to true # sets bill_address to the default if automatic_default_address is set to true and there is no ship_address # if one address is nil, does not save that address def persist_order_address(order) if order.ship_address address = save_in_address_book( order.ship_address.attributes, Spree::Config.automatic_default_address ) self.ship_address_id = address.id if address&.persisted? end if order.bill_address address = save_in_address_book( order.bill_address.attributes, Spree::Config.automatic_default_address, :billing ) self.bill_address_id = address.id if address&.persisted? end save! # In case the ship_address_id or bill_address_id was set end # Add an address to the user's list of saved addresses for future autofill # @param address_attributes HashWithIndifferentAccess of attributes that will be # treated as value equality to de-dup among existing Addresses # @param default set whether or not this address will show up from # #default_address or not def save_in_address_book(address_attributes, default = false, address_type = :shipping) return nil if address_attributes.blank? address_attributes = address_attributes.to_h.with_indifferent_access new_address = Spree::Address.factory(address_attributes) return new_address unless new_address.valid? first_one = user_addresses.empty? user_address = prepare_user_address(new_address) if address_attributes[:id].present? && new_address.id != address_attributes[:id] if ship_address&.id == address_attributes[:id].to_i user_addresses.mark_default(user_address, address_type: :shipping) end if bill_address&.id == address_attributes[:id].to_i user_addresses.mark_default(user_address, address_type: :billing) end remove_from_address_book(address_attributes[:id]) end user_addresses.mark_default(user_address, address_type:) if default || first_one if persisted? user_address.save! # If these associations have already been accessed, they will be # caching the existing values. # user_addresses need to be reset to get the new ordering based on any changes # {default_,}user_address needs to be reset as its result is likely to have changed. user_addresses.reset association(:default_user_ship_address).reset association(:ship_address).reset association(:default_user_bill_address).reset association(:bill_address).reset end user_address.address end def mark_default_ship_address(address) user_addresses.mark_default(user_addresses.find_by(address:)) end def mark_default_bill_address(address) user_addresses.mark_default(user_addresses.find_by(address:), address_type: :billing) end def remove_from_address_book(address_id) user_address = user_addresses.find_by(address_id:) if user_address remove_user_address_reference(address_id) user_address.update(archived: true, default: false) else false end end private def prepare_user_address(new_address) user_address = user_addresses.all_historical.find_first_by_address_values(new_address.attributes) user_address ||= user_addresses.build user_address.address = new_address user_address.archived = false user_address end def remove_user_address_reference(address_id) self.bill_address_id = bill_address_id == address_id.to_i ? nil : bill_address_id self.ship_address_id = ship_address_id == address_id.to_i ? nil : ship_address_id save if changed? end end end