Using AR::Associations to limit find() scope
In an ecommerce application I’m currently building there’s a URL for destroying a line item in the user’s cart:
http://127.0.0.1:3000/cart/line-item/16/destroy
Like all good web coding monkeys should, I’m going to need to check that the LineItem with id 16 is actually owned by the user before destroying it. Sounds obvious? Well scarily there are plenty of web applications I’ve seen that don’t do this check, often just assuming database ids passed in via GET or POST are valid.
An initial way to code up the destroy
action of the LineItemsController
could be:
def destroy
fetch_order_from_session # sets up the @order object
@line_item = LineItem.find(params[:id])
if @order.line_items.include?(@line_item)
@line_item.destroy
end
end
another way could be:
def destroy
fetch_order_from_session
@line_item = LineItem.find(:first, :conditions => ['id = ? AND order_id = ?', params[:id], @order.id])
@line_item.destroy
end
These both work, but aren’t exactly pretty. One of the premises of ActiveRecord associations is that if you’ve defined the relationship between two model objects you shouldn’t need to repeat yourself by coding foreign key searches by hand.
A lesser known (and poorly documented) trick is to use the association’s find
method:
def destroy
fetch_order_from_session
@line_item = @order.line_items.find(params[:id])
@line_item.destroy
end
The association’s find
method works exactly as a regular find, but the SQL select is automatically scoped to the foreign key of the parent. In this case "order_id = #{@order.id}"
is added to the SQL SELECT clause. You don’t have to stop with simple finds either, you can perform all your regular find trickery:
@order.line_items.find(:all, :conditions => ["quantity >= ?", 0])
This technique is also useful if you have a user object that’s created by a before_filter
. Rather than having to add user_id = @current_user.id
to every find, just perform the find directly through an association of the @current_user
object. If you’re new to using associations I suggest checking out a previous blog post of mine: Abusing err.. Using AR::Associations.
As a side note, don’t forget to ensure it’s a POST request using ActionController’s verify macro. You wouldn’t want those frenzied GET
pigeons messing up your lovely database would you?