Skip to content

Commit

Permalink
Merge pull request #366 from bonobos/address_book
Browse files Browse the repository at this point in the history
new AddressBookController
  • Loading branch information
magnusvk committed Sep 21, 2015
2 parents 6d84190 + eb8955c commit 779801e
Show file tree
Hide file tree
Showing 9 changed files with 340 additions and 34 deletions.
38 changes: 38 additions & 0 deletions api/app/controllers/spree/api/address_books_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module Spree
module Api
class AddressBooksController < Spree::Api::BaseController
# Note: the AddressBook is the resource to think about here, not individual addresses

def show
render_address_book
end

def update
address_params = address_book_params
default_flag = address_params.delete(:default)
address = current_api_user.save_in_address_book(address_params, default_flag)
if address.valid?
render_address_book
else
invalid_resource!(address)
end
end

def destroy
current_api_user.remove_from_address_book(params[:address_id])
render_address_book
end

private

def render_address_book
@user_addresses = current_api_user.user_addresses
render :show, status: :ok
end

def address_book_params
params.require(:address_book).permit(permitted_address_book_attributes)
end
end
end
end
4 changes: 4 additions & 0 deletions api/app/views/spree/api/address_books/show.v1.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
collection @user_addresses
node do |user_address|
partial("spree/api/addresses/show", object: user_address.address).merge(default: user_address.default)
end
2 changes: 2 additions & 0 deletions api/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@
end
end

resource :address_book, only: [:show, :update, :destroy]

get '/config/money', to: 'config#money'
get '/config', to: 'config#show'
put '/classifications', to: 'classifications#update', as: :classifications
Expand Down
60 changes: 60 additions & 0 deletions api/spec/controllers/spree/api/address_books_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'spec_helper'

module Spree
describe Api::AddressBooksController, :type => :controller do
render_views

context "unauthorized user" do
it "get 401 on /show" do
api_get :show
expect(response.status).to eq 401
end

it "get 401 on /update" do
api_put :update
expect(response.status).to eq 401
end

it "get 401 on /destroy" do
api_delete :destroy, address_id: 1
expect(response.status).to eq 401
end
end

context "authorized user with addresses" do
let(:address1) { create(:address) }
let(:address2) { create(:address, firstname: "Different") }

before do
stub_authentication!
current_api_user.save_in_address_book(address1.attributes, true)
current_api_user.save_in_address_book(address2.attributes, false)
end

it "gets their address book" do
api_get :show
expect(json_response.length).to eq 2
end

it "the first one is default" do
api_get :show
first, second = *json_response
expect(first["default"]).to be true
expect(second["default"]).to be false
end

it "can remove an address" do
api_delete :destroy, address_id: address1.id
expect(json_response.length).to eq 1
end

it "can update an address" do
updated_params = address2.attributes
updated_params[:firstname] = "Johnny"
updated_params[:default] = true
api_put :update, address_book: updated_params
expect(json_response.first["firstname"]).to eq "Johnny"
end
end
end
end
89 changes: 62 additions & 27 deletions core/app/models/concerns/spree/user_address_book.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,85 +3,120 @@ module UserAddressBook
extend ActiveSupport::Concern

included do
has_many :user_addresses, foreign_key: "user_id", class_name: "Spree::UserAddress" 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 == Address.new(address_attrs) }
end

# @note this method enforces one-and-only-one default address per user
# @note this method enforces only one default address per user
def mark_default(user_address)
# the checks of persisted? allow us to build a User and associate Addresses at once
ActiveRecord::Base.transaction do
(self - [user_address]).each do |ua| #update_all would be nice, but it bypasses ActiveRecord callbacks
ua.persisted? ? ua.update!(default: false) : ua.default = false
end
user_address.persisted? ? user_address.update!(default: true) : user_address.default = true
user_address.persisted? ? user_address.update!(default: true, archived: false) : user_address.default = true
end
reset
end
end

has_many :addresses, through: :user_addresses

has_one :default_user_address, -> { where default: true}, foreign_key: "user_id", class_name: 'Spree::UserAddress'
has_one :default_address, through: :default_user_address, source: :address

# bill_address is only minimally used now, but we can't get rid of it without a major version release
belongs_to :bill_address, class_name: 'Spree::Address'

def ship_address
default_address
def bill_address=(address)
# stow a copy in our address book too
address = save_in_address_book(address.attributes) if address
super(address)
end

def ship_address=(address)
# TODO default = true for now to preserve existing behavior until MyAccount UI created
def bill_address_attributes=(attributes)
self.bill_address = Address.immutable_merge(bill_address, attributes)
end

def default_address
user_addresses.default.first.try(:address)
end

def default_address=(address)
save_in_address_book(address.attributes, true) if address
end

def ship_address_attributes=(attributes)
def default_address_attributes=(attributes)
# see "Nested Attributes Examples" section of http:https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
# this #{fieldname}_attributes= method works with fields_for in the views
# even without declaring accepts_nested_attributes_for
self.ship_address = Address.immutable_merge(ship_address, attributes)
self.default_address = Address.immutable_merge(default_address, attributes)
end

def bill_address=(address)
# stow a copy in our address book too
address = save_in_address_book(address.attributes) if address
super(address)
end
alias_method :ship_address, :default_address
alias_method :ship_address_attributes=, :default_address_attributes=

def bill_address_attributes=(attributes)
self.bill_address = Address.immutable_merge(bill_address, attributes)
def ship_address=(address)
be_default = Spree::Config.automatic_default_address
save_in_address_book(address.attributes, be_default) if address
end

def persist_order_address(order)
#TODO the 'true' there needs to change once we have MyAccount UI
save_in_address_book(order.ship_address.attributes, true) if order.ship_address
save_in_address_book(order.bill_address.attributes, order.ship_address.nil?) if order.bill_address
if Spree::Config.automatic_default_address
save_in_address_book(order.ship_address.attributes, true) if order.ship_address
save_in_address_book(order.bill_address.attributes, order.ship_address.nil?) if order.bill_address
else
save_in_address_book(order.ship_address.attributes) if order.ship_address
save_in_address_book(order.bill_address.attributes) if order.bill_address
end
end

# Add an address to the user's list of saved addresses for future autofill
# @param address_attributes Hash of attributes that will be
# @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)
return nil unless address_attributes.present?
user_address = user_addresses.find_first_by_address_values(address_attributes)
return user_address.address if user_address && (!default || user_address.default)

new_address = Address.factory(address_attributes)
return new_address unless new_address.valid?

first_one = user_addresses.empty?
user_address ||= user_addresses.build(address: Address.factory(address_attributes))

if address_attributes[:id].present? && new_address.id != address_attributes[:id]
remove_from_address_book(address_attributes["id"])
end

user_address = prepare_user_address(new_address)
user_addresses.mark_default(user_address) if (default || first_one)
save! if persisted?
user_address.save! if persisted?

user_address.address
end

def mark_default_address(address)
user_addresses.mark_default(user_addresses.find_by(address: address))
end

def remove_from_address_book(address_id)
user_address = user_addresses.find_by(address_id: address_id)
if user_address
user_address.update_attributes(archived: true, default: false)
user_addresses.reset
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
end
end
end
8 changes: 8 additions & 0 deletions core/app/models/spree/app_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,14 @@ class AppConfiguration < Preferences::Configuration
# @return [Boolean] Creates a new allocation anytime {StoreCredit#credit} is called
preference :credit_to_new_allocation, :boolean, default: false

# @!attribute [rw] automatic_default_address
# The default value of true preserves existing backwards compatible feature of
# treating the most recently used address in checkout as the user's default address.
# Setting to false means that the user should manage their own default via some
# custom UI that uses AddressBookController.
# @return [Boolean] Whether use of an address in checkout marks it as user's default
preference :automatic_default_address, :boolean, default: true

# searcher_class allows spree extension writers to provide their own Search class
attr_writer :searcher_class
def searcher_class
Expand Down
8 changes: 7 additions & 1 deletion core/app/models/spree/user_address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ class UserAddress < Spree::Base
belongs_to :address, class_name: "Spree::Address"

validates_uniqueness_of :address_id, scope: :user_id
validates_uniqueness_of :user_id, conditions: -> { where(default: true) }, message: :default_address_exists, if: :default?
validates_uniqueness_of :user_id, conditions: -> { active.default }, message: :default_address_exists, if: :default?

scope :with_address_values, ->(address_attributes) do
joins(:address).merge(
Spree::Address.with_values(address_attributes)
)
end

scope :all_historical, -> { unscope(where: :archived) }
scope :default, -> { where(default: true) }
scope :active, -> { where(archived: false) }

default_scope -> { order([default: :desc, updated_at: :desc]) }
end
end
3 changes: 3 additions & 0 deletions core/lib/spree/permitted_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Spree
module PermittedAttributes
ATTRIBUTES = [
:address_attributes,
:address_book_attributes,
:checkout_attributes,
:credit_card_update_attributes,
:customer_return_attributes,
Expand Down Expand Up @@ -41,6 +42,8 @@ module PermittedAttributes
state: [:name, :abbr]
]

@@address_book_attributes = address_attributes + [:default]

@@checkout_attributes = [
:coupon_code, :email, :shipping_method_id, :special_instructions, :use_billing
]
Expand Down
Loading

0 comments on commit 779801e

Please sign in to comment.