Rails Tip: Always Scope Your Finders

Written by on Apr 10 2008

It is easy to open a security hole in your Rails application. Fortunately, by scoping your finders, it is also easy to write your code without opening it. Here is an example. Let’s say you have a expense tracking application and the url is /expenses/151. Obviously this calls the expenses controller with a params[:id] = 151.

#bad: def show @expense = Expenses.find(params[:id]) end

#good: #@user is the logged on user. def show @expense = @user.expenses.find(params[:id]) end

The scoped finders actually add the proper where clause to the sql. It happens automatically. Without scoping the expense finder, anyone can see anyone else’s data. Generally you will want to set this up as a before filter. This also works for nested routes. Let’s say the url is /invoices/25/line_items/87:

class LineItemsController < ApplicationController before_filter :setup #snip many lines

protected def setup @invoice = @user.invoices.find(params[:invoiceid]) unless params[:invoiceid].blank? @lineitem = @invoice.blank? ? @user.lineitems.find(params[:id]) : @invoices.line_items.find(params[:id]) end end

You don’t have to use the @user variable. In Less Accounting we use sub-domains for each business. Since each business may have several users, all the controllers are scoped around the @business variable, which is determined by the sub-domain of the url. The @business variable itself is scoped by the @user variable.

Meet
Steven

Hi I'm Steven,

I wrote the article you're reading... I lead the developers, write music, used to race motorcycles, and help clients find the right features to build on their product.

Get Blog Updates