One of the greatest features in Rails 2.3 is the Rails Metal piece. It’s part of the goodness that came out of the Rails/Merb merge.
Metal provides a layer of functionality that is executed before or in the place of your rails application. The common term for this type of software is middleware. There is a terrific presentation that was given at Mountain West RubyConf 2009. You can actually view the video of it here.
Here’s a simple example of how to use metal to require authentication before accessing pages on the site.
First, in your rails application’s root directory, generate some metal:
script/generate metal authenticationIf you look in your app folder, you’ll see a new folder called metal. Inside that folder, you’ll see a file called authentication.rb.
cd app/metal ls
Open authentication.rb and look inside:
vim authentication.rbYou’ll see contents like this:
1 2 3 4 5 6 7 8 9 10 11 12 | # Allow the metal piece to run in isolation require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails) class Authentication def self.call(env) if env["PATH_INFO"] =~ /^\/authentication/ [200, {"Content-Type" => "text/html"}, ["Hello, World!"]] else [404, {"Content-Type" => "text/html"}, ["Not Found"]] end end end |
The call method is what is executed before your rails application. In this case, if the user goes to http://yourrailsapp.com/authentication, it’ll return the text “Hello World!” Isn’t rails great!
It is, but “Hello World!” isn’t very impressive. So, let’s make it do something interesting. Let’s assume that your rails app is authenticated when there’s a user_id variable in your session. So, let’s access the session, and check if there’s a user_id in it.
First, let’s initialize the session variable.
1 2 3 | def initialize(env) @session = env['rack.session'] end |
Then we’ll update our call method to check the session.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails) class Authentication def initialize(env) @session = env['rack.session'] @current_user = User.find_by_id(@session["user_id"]) if @session["user_id"] end def self.call(env) # Create a new instance, which initializes our session and current user and processes the call. Authentication.new(env).process_call end def process_call if @current_user # A 400 status (Not Found) passes the call on to the next piece in the stack, # which can be another metal piece, or your rails application. [404, {"Content-Type" => "text/html"}, ["Not Found!"]] else # Redirects to login if there is no logged in user [302, {'Location'=> '/login' }, []] end end end |
If you implement this metal, you’ll find a problem. If you’re not logged in, you’ll get caught in a loop where you’re being redirected login over and over. To fix this, you’ll have to add an exception for the login page.
1 2 3 4 5 6 7 8 9 10 | def process_call if @current_user || env["PATH_INFO"] == "/login" # A 400 status (Not Found) passes the call on to the next piece in the stack, # which can be another metal piece, or your rails application. [404, {"Content-Type" => "text/html"}, ["Not Found!"]] else # Redirects to login if there is no logged in user [302, {'Location'=> '/login' }, []] end end |
I could go on, but I’ll leave it up to you to figure out how to only protect certain paths or to add role based permissions to your users. In any case, I’m certain you can see the power of Rails Metal. Overall, it allows you to implement simple functions and allow them to execute extremely quickly without loading all of the extra stuff that Ruby on Rails provides.







1 Comment »
chris
May 2, 2009
Very interesting!
“… I could go on, but I’ll leave it up to you to figure out how to only protect certain paths or to add role based permissions to your users. …”
Please “go on” … authentication is something I would like to get right.
thanks
Leave a comment