Antun’s Blog

Notes on OpenLaszlo, LZX and Rich Internet Application Development

Antun’s Blog header image 2

Passing Arguments to before_filter in a Rails Controller

July 15th, 2008 · 7 Comments

I’ve been working with Ruby on Rails a lot recently. It’s a great platform for creating both old-fashioned web applications as well as services for RIAs. I especially like the fact that Rails encourages you to think about the right way to do something, before writing any code. For all you know, that complex thing you wanted to add could involve nothing more than one shell command and a tweaked line of Ruby code.

There was one seemingly ubiquitous thing that I had to think about for long time however, so I’m posing it here for posterity: How do you pass arguments to an ActionController method from a filter, while also using filter conditions?

It seems like such a common problem. You buy the Agile Web Development with Rails book, like everybody else, read half the first chapter, then start writing your own application. At some point you need to add user authentication to your application. If you were working with any other language/framework, this would have taken waaayyyy longer than it did to copy-and-paste the authentication example in chapter 11. Here’s the example I’m referring to:

my_app/app/controllers/application.rb
class ApplicationController < ActionController::Base
session :session_key = "_my_session_id"
private
def authorize
unless User.find_by_id(session[:user_id])
flash[:notice] = "Please log in"
redirect_to(:controller => "login", :action => "login")
end
end

This method gets called automatically by specifying so on a filter in the individual controller, e.g.

my_app/app/controllers/item_controller.rb
class ItemController < ApplicationController
before_filter :authorize, :except => [:show, :search]

# ... various methods ...
end

Then, several features later, you realize that you need to have different levels of user (administrator, moderator, gnat, etc.).  The authorize method now needs to take an argument of what type of user is required.  You would have to specify this when you write the filter (in item_controller.rb), but you need to retain the filter conditions (:except). This took me a long time to find. The reference page on filters didn’t help.

Filtes can pass arguments to the methods they call, but the syntax is not obvious:

  • The authorize method you call must be public.
  • Your filter conditions have to be the first argument to the filter.
  • Instead of referencing the authorize method, you will need to call it, but you must do so from a code block, and that must be the last argument to the filter. Note the do … end in the example below.
  •  You can pass as many arguments as you like to the method inside this code block, since you’re actually calling it here.
  • To call the method, you need to define a block variable inside your code block (it can be named anything you like). This will be a reference to the controller. Note the two pipes in the example below.

So here’s the controller class with filter:

my_app/app/controllers/item_controller.rb
class ItemController < ApplicationController
before_filter :except => [:show, :search] do |controller| controller.authorize({"required_user_level" => "administrator"})
end
# ... various methods ...
end

Don’t forget to add an argument to your method declaration in the ActionController, and to make it public:

my_app/app/controllers/application.rb
class ApplicationController < ActionController::Base
session :session_key = "_my_session_id"

def authorize(vars)
puts "Required User Level is " + vars["required_user_level"]
unless User.find_by_id(session[:user_id])
flash[:notice] = "Please log in"
redirect_to(:controller => "login", :action => "login")
end
end

Tags: Rails

7 responses so far ↓

  • 1 Doug Scott // Aug 8, 2008 at 4:11 pm

    Very timely. Does seem to move you forward. On the other hand, the public authenticate method is now accessible as an action on the controller. So kinda a trade-off.

    Even the restful auth guys don’t have a good way around this.

  • 2 Doug Scott // Aug 8, 2008 at 5:30 pm

    Couple of clarifications:

    “Very timely” – i.e. I had just run into this problem myself.

    “public authenticate method” – I meant “authorize”, like in your example.

    Wouldn’t protected also work? In your example “controller” is an ItemController and a subclass of ApplicationController, where you’ve defined your authorize method. So I would expect that the child could call a protected method on instance of the parent class.

    I’m going to try that anyway … no better way to learn to walk than by falling down.

  • 3 antun // Aug 8, 2008 at 10:40 pm

    Hey Doug,

    I tried making the authorize method both protected and private, but that results in a NoMethodError.

    I’m not sure why exactly – it didn’t make sense to me either. But in my install it definitely only works when the authorize method is public.

    -Antun

  • 4 Glenn // Aug 27, 2008 at 9:20 am

    Almost perfect! I’ll definitely be hacking around with this shortly to see if I can find a way to prevent exposing my internal methods

  • 5 Bookmarks about Rails // Oct 29, 2008 at 2:58 pm

    [...] – bookmarked by 4 members originally found by arunsimha on 2008-10-10 Passing Arguments to before_filter in a Rails Controller http://www.antunkarlovac.com/blog/?p=81 – bookmarked by 5 members originally found by amzacatalin [...]

  • 6 jano // Sep 23, 2009 at 1:43 am

    I found following 2 ways how to avoid exposing your filter method and still pass arguments to your filter method:

    method 1:

    before_filter do |c|
    c.class.module_eval do
    private
    def custom_filter
    authorize(args)
    end
    end
    end
    before_filter :custom_filter

    method 2:
    before_filter do |c|
    c.send(:authorize, args)
    end

  • 7 makuchaku // Jun 23, 2010 at 10:58 am

    @ jano, the second way works perfectly!
    Thanks…

Leave a Comment

Need to post code in a comment? Read this first!