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?
Related posts:
- Techy Treasures #4: What’s inside a dollar function? The $ function is a common feature of all of...
- How To Develop a jQuery Plugin Creating a jQuery plugin is easier than you might think....







Very good point. Any user input should always be sanity checked.
I would, however, note that one should not be taking actions other than retrieving information using Http GET requests. That should be completely handled using POST. The main reason for this is things like bots can have an interesting effect on things. There was a situation once where google started deleting people’s profile because the delete command was something like “userprofile.php?id=NN&action=delete”
July 2nd, 2006 at 11:38 pm
See my note in the last paragraph ;)
An example of using the verify macro:
July 3rd, 2006 at 10:17 am
I forgot to mention there’s also the ScopedAccess and MeantimeFilter plugins which help limiting the scope of database fetches. Only problem with using plugins though is you’re not guaranteed they won’t break on future versions of Rails, and the technique I described is useful elsewhere.
July 3rd, 2006 at 12:32 pm
What if the order’s lineitem hasn’t been saved and is only in memory. will the find still work?
July 3rd, 2006 at 1:20 pm
Stevenwulf: Nope, the find is only for database fetches. If you were storing it in memory (in the session for example) then you’d probably be referencing it by position in the line_items array, rather than the record’s id.
For this app I’m creating the order in the database with a state of ‘unfinished’. I store the session id with the order, so when I delete old sessions from the database I also delete their unfinished orders.
July 3rd, 2006 at 1:36 pm