08 Oct, 2007

Published at 05:01AM

Tagged with method_missing, programming, rails, reference, slate, and tips

This post has 4 comments

Handling user roles in Rails

Here’s a nice way to deal with user roles.

1
2
3
4
5
6
7
8
9
10
# application.rb
def require_role(role, options = {})
  before_filter(options) do
    logged_in? && current_user.send("#{role.to_s}?") ? true : access_denied
  end
end

# any controller
require_role :admin,  :only => [:destroy]
require_role :author, :only => [:show, :edit, :create]

Of course, there’d need to be admin? and author? methods on the user object. This could be handled in a number of ways, two of which are:

1
2
3
4
5
6
7
8
9
10
11
# user.rb

# 1) dynamically define each role using define_method
%w{admin author guest}.each do |role|
  define_method("#{role}?") { self.role == role } unless method_defined? "#{role}?"
end

# 2) use method_missing to catch the undefined method
def method_missing(method)
  self.role == method.to_s.gsub(/?/, '')
end

This code is untested and was realized by something I read this evening—just wanted to make note of it…

Comments

Chris Monday, 08 Oct, 2007 Posted at 06:39AM

In slate, I created a Permissions module that does role-based authentication in a similar fashion. The permission system is based on a set of predefined roles: writer, publisher, and administrator. Here’s the basic idea:

1
2
3
4
5
6
class SomeController < ApplicationController
  permissions do
    publisher :publish, :unpublish
    administrator :destroy
  end
end

Note that the three roles “build” on each other, such that adminstrator will have access to all actions that require the publisher role automatically.

When the permissions method is called, a before filter is created that ensures the current user has the proper role. If that fails, a permission denied error is rendered. The module also defines has_permission? to perform checks for permission to other controller/action pairs from within a controller or view.

Ryan Monday, 08 Oct, 2007 Posted at 12:11PM

Sweet… sounds pretty organized. I’m curious what the permissions implementation looks like. I imagine each of the administrator and publisher methods are what turn into before filters?

1
2
# publisher :publish, :unpublish
before_filter :only => [:publish, :unpublish] { current_user.roles.include?(publisher) ? true : raise NoPermission }

Well I guess you’re probably merging the check somehow to consider publisher and admin in the same filter? Anyway… just curious.

This legacy .NET system we have lingering around has about 10 roles or so (that build upon one another), and a few of them pass off to another system entirely. It’s ugly and horrible to work with. Roles are always a pain, especially when (like you mentioned) they build off of each other, which is typical a lot of the time.

Admittedly, though, I enjoy dealing with those pains in Rails. It’s always nice when you can turn an annoying mess into something awesome.

Chris Monday, 08 Oct, 2007 Posted at 12:41PM

The permissions method passes it’s block to a builder class which has methods publisher and administrator, and all those methods do is add a new permission entry to a hash. The actual before filter is like this:

1
2
3
def ensure_permission
  has_permission? :halt => true
end

The has_permission? method looks up entries in the permission hash. The key to making this all work easy is that the roles are assigned numerical values, with writer = 0, publisher = 1, and administrator = 2. So, checking the permission entry is like “permission <= current_role” essentially.

I can send you the .rb file sometime if you ever want to take a closer look.

Ryan Monday, 08 Oct, 2007 Posted at 01:49PM

Ahh… that’s a clever way to handle the “building” of permissions. Sometimes I’ll browse through random plugin code just to (try and) figure it out… I’m sure I can learn just as much from your stuff… so, send away, I’d love to take a look at it. Thanks!

Do you have something to say about this post?
Protected by Defensio Textile Formatting Tips

or

Ryan Heath | Site Management A Ruby on Rails production.

This site is a Formed Function. Formed Function LLC | @formedfunction | Get in Touch