Hi - Very new to RoR. I have looked around at different strategies
for handling shipping and billing addresses for an e-commerce
applications. What I am trying to do is to have a user registration
form where the user need to also fill out the default shipping
address. Ultimately, I would like for the users to be able to add
additional shipping address and billing addresses.
I am sort of going down the path outlined by ryanb's here and using
Single Table Inheritance where the shipping address and billing
address inherits from address class. Is this the best approach?
ryanb suggested something along the lines below:
Customer
has_many :addresses
has_many :orders
Address
belongs_to :customer
Order
belongs_to :customer
belongs_to :billing_address #...
belongs_to :shipping_address #...
Is this an instance where I need to use both STI and polymorphic
association?
No I don't think you need either. You have a case where an Order can
have two different addresses, but they are both always Addresses.
I haven't created the order.rb model yet, but ryanb did suggest making
sure that...
"The billing_address_id column would go in the orders table. Same
with the shipping."
Is this the only table in the database that would contain this
column? Should they also exist in the address table or is this
handled by Rails through the type column?
I don't think that they need to be in the address table, in this case.
Let's see.
Here's my current code but I'm certain I'm missing some key concepts
here. I am providing some of the code below. Any help would be
greatly appreciated.
users_controller.rb
************************************************
class UsersController < ApplicationController
user.rb model:
************************************************
class User < ActiveRecord::Base
acts_as_authentic
has_many :addresses
accepts_nested_attributes_for :addresses, :allow_destroy => true
def address_attributes=(address_attributes)
address_attributes.each do |attributes|
addresses.build(attributes)
end
end
How do I make the default registration address be the default
shipping_address? I am using address here but should it be
shipping_address? Should I use a hidden field to set the type and
modify the create section of the code?
This isn't really an issue with the user model. User's don't have a
shipping address, they just have one or more addresses. Now they
might have one of those addresses be the one they normal want orders
shipped to.
I'll get to setting the defaults for a order at the end.
************************************************
addresses_controller.rb
************************************************
class AddressesController < ApplicationController
# GET /addresses
# GET /addresses.xml
def index
@addresses = Address.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @addresses }
end
end
# GET /addresses/1
# GET /addresses/1.xml
def show
@address = Address.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @address }
end
end
# GET /addresses/new
# GET /addresses/new.xml
def new
@address = Address.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @address }
end
end
# GET /addresses/1/edit
def edit
@address = Address.find(params[:id])
end
# POST /addresses
# POST /addresses.xml
def create
@address = Address.new(params[:address])
respond_to do |format|
if @address.save
flash[:notice] = 'Address was successfully created.'
format.html { redirect_to(@address) }
format.xml { render :xml => @address, :status
=> :created, :location => @address }
else
format.html { render :action => "new" }
format.xml { render :xml => @address.errors, :status
=> :unprocessable_entity }
end
end
end
# PUT /addresses/1
# PUT /addresses/1.xml
def update
@address = Address.find(params[:id])
respond_to do |format|
if @address.update_attributes(params[:address])
flash[:notice] = 'Address was successfully updated.'
format.html { redirect_to(@address) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @address.errors, :status
=> :unprocessable_entity }
end
end
end
# DELETE /addresses/1
# DELETE /addresses/1.xml
def destroy
@address = Address.find(params[:id])
@address.destroy
respond_to do |format|
format.html { redirect_to(addresses_url) }
format.xml { head :ok }
end
end
end
************************************************
The main comment I've got about this, is that if every address belongs
to a user, then the address controller should be scoped to a user, and
instead of Address.find, Address.new, Address.create you should find
instantiate and create addresses using the addresses association of
user.
address.rb model
************************************************
class Address < ActiveRecord::Base
belongs_to :user
end
************************************************
billing_address.rb
************************************************
class BillingAddress < Address
end
************************************************
shipping_address.rb
************************************************
class ShippingAddress < Address
end
************************************************
You don't need subclasses unless there is a behavioral difference
between billing and shipping addresses. I doubt that there is, they
are both addresses, it's just that they are playing different roles.
If you change the Order model slightly
Order
belongs_to :customer
belongs_to :billing_address, :class_name => 'Address'
belongs_to :shipping_address, :class_name => 'Address'
Then you can use the Address class for both roles.
Now having gotten here, you could also model the notion of a customer
having a preferred shipping address (and even a preferred billing
address) in a similar way
User
...
belongs_to :preferred_billing_address, :class_name => "Address"
belongs_to :preferred_shipping_address, :class_name => "Address"
It might seem odd to have a User 'belonging to' two particular
addresses, but you can alternatively thing of belongs_to as
has_a_pointer_to, and has_many as has_many_pointing_to_me.
Now about the order. I think that there are at least two ways of
dealing with the billing and shipping addresses:
1) in the OrdersController
def new
@order = current_user.orders.build(
:billing_address =>
current_user.preferred_billing_address,
:shipping_address =>
current_user.preferred_shipping_address
)
#...
end
2) in the OrderModel
class Order < ActiveRecord::Base
belongs_to :customer
belongs_to :billing_address, :class_name => 'Address'
belongs_to :shipping_address, :class_name => 'Address'
def after_initialize
if new_record?
self.billing_address =
customer.preferred_billing_address unless billing_address
self.shipping_address =
customer.preferred_shipping_address unless shipping_address
end
end
end
The first approach is probably more conventional.
HTH