E-commerce Tips 1.0 Apr 01
I've been looking through many business's code over the past couple years. It appears many very smart developers make the same mistakes over and over again. I continually see some obvious and not-so-obvious mistakes being made. My plan is to start documenting and blogging about the issues I see. So today My goal is to start this series with...
Saving Addresses
Saving an address in an application sounds so easy most people don't put much thought into it. You just need a basic CRUD app right? You guessed it... WRONG!
Lets talk about the easy section of the CRUD actions first, CREATE. Create is almost as simple as it sounds. Add all the validations that you need and you're done.
Next up... DELETE. I'm skipping UPDATE for now. Update is the most complex so I'll leave that for last. If you do this:
## NEVER DO THIS
@address.destroy
you better get ready for 404 errors and nil object errors. Instead you better have code that looks like this:
@address.inactivate!
class Address
def inactivate!
self.active = false
save!
end
end
and in your User's model you better have something like this:
class User
has_many :active_addresses, :class_name => 'Address',
:conditions => ['addresses.active = ?', true]
end
or you could do this:
class Address
def self.active
where(['addresses.active = ?', true])
end
end
What does this buy you? It should be pretty straight forward but now we are not deleting Addresses in the DB. So when a user looks at an old Order an address object isn't nil.
Now comes the difficult beast... UPDATE. The reason this is difficult it that you actually shouldn't ever update an address. Instead you should be creating a new address and inactivating the old address.
In this process validations need to be fooled around with a bit to work the way the end users should see them. So you should be doing something like this:
def update
@address = current_user.addresses.
new(params[:address])
respond_to do |format|
if @address.save
old_shopping_address = current_user.addresses.
find(params[:id])
old_shopping_address.inactivate!
format.html {
redirect_to(address_url(@address),
:notice => 'Updated successfully' ) }
else
@form_address = current_user.addresses.
find(params[:id])
@form_address.attributes = params[:address]
format.html { render :action => "edit" }
end
end
end
Now to make this work all pretty in your view your form needs to use "@address" for the error messages. Then use "@form_address" for the form. Like this:
<% if @address.errors.any? %>
<div id="error_explanation">
<%## You might want more Verbiage %>
<ul>
<% @address.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_for(@form_address,
:url => address_path(@form_address)) do |f| %>
<%= render :partial => 'form', :locals => {:f => f} %>
<% end %>
Well I guess I left truly the easiest for last... READ. Just make sure when you display the person's current account information that you only display active addresses.
@user.active_addresses
# or
Address.active.for_user(user)
One more thing. You might think you can create a new inactive record for each order you create (or an address with a specific address_type). I assure you this is a bad idea for several reason.
- You will generate a lot of records.
- You have many records in your DB that are basically duplicates
- When you are trying to look through a users information it gets very difficult figure out what may have changed and when it changed.
- It hurts performance
That's it for this post. CreditCards, CartItems, Cart vs Order, OrderItems and Returns are all on my to-do list. If I get really motivated this might turn into a book.
Thanks for reading!
