June 30, 2011
by firebait
0 comments
I recently got the opportunity to integrate the Devise gem with Cancan for authentication and authorization.
These gems are a real time saver and one really good thing about these is the ability to customize them especially Cancan. Cancan doesn’t enforce a model on you or a data structure for it to work, you can define the role table as you will and plug it in to your app.
But this blog is not intended to be a blog about authorization and authentication is more about using metaprogramming as a tool for generating methods from table records.
In my particular case I used a Roles table with a name field, pretty standard. My first instinct was to loop thru the records in the Roles table and generate a method called “is_role_name?”. This was my implementation.
Role.all.each do |role|
define_method("is_#{role.name.downcase}?") do
!roles.find_by_name(role).nil?
end
end
This worked really well as I could query a user and just ask for user.is_admin? and that would be it. The problem happened when I wanted to test it, because the database get’s deleted, all of the seed data for the default roles get deleted as well. If I tried to create them after the fact, the User model was already loaded so the methods never get created. Even worst if I went and drop the database and tried to create it, it would blow up saying that there is no Roles table when it’s loading the User model (there is a clever way around this, just put this piece of code after the Roles.all.each loop and the methods won’t get created if there is no Roles table: if ActiveRecord::Base.connection.tables.include?('roles'). So, no good.
I had to go back to the traditional way of querying roles and use user.has_role?(admin) which if fine if you want to keep thing simple. But my goal was to call the roles using the role name inside the method so method_missing to the rescue.
def has_role?(role)
!roles.find_by_name(role).nil?
end
def method_missing(method, *args)
return has_role?($1.to_s, *args) if method =~ /^is_+(.*)\?/
super
end
This way you can still user the format user.is_admin? to call your roles and not have the issue of creating methods with seed data or table records.
Hope this helps.