Moulin Rouge

Moulin Rouge offers a custom and easy DSL to manage your authorizations and groups of access with CanCan. The main feature is the ability to split your authorizations in many ruby files, that are automatically pushed to CanCan authorization system. It is also decoupled from any role system giving you flexibility.

There are a bunch of examples bellow to show you how to get started and explaining all features.

Installation

Add the gem to your Gemfile:

gem 'moulin_rouge'

And install it

bundle install

Run the generator to install the roles and permissions structure:

rails g moulin_rouge:install

Generate a permission file:

rails g moulin_rouge:auth <name>

Add your permissions to newly created file:

class UserAuthorization < MoulinRouge::Authorization
  role :name do
    can :read, :something
  end
end

By default your current_user method should respond to is?, but you can change this method to match your role system at the configuration.

Usage

First of all, you have to accept that the role registering belongs to the ruby code. Organizing this ruby files it's a really important issue to any large application.

When you run:

  bundle g moulin_rouge:install

This will create the following folder structure:

  root
  | app/
  | | authorizations/
  | config/
  | | initalizers
  | | | moulin_rouge.rb

Defining roles

All your authorization files will be stored inside the app/authorizations folder. Just create a ruby file inside and the definitions will be automatically defined.

class UserAuthorization < MoulinRouge::Authorization
  role :superuser do
    can :manage, :all
  end

  role :editors do
    can :manage, Articles
  end

  role :authors do
    can :manage, Article do |article|
      article.user_id == current_user.id
    end
  end
end

Note that the can method is the same for defining abilities in CanCan, and will be passed as they are on the Ability class. See Defining Abilities for more information on what you can do.

Also, the others CanCan methods are avaliable (cannot, can?, cannot?) and will act like expected.

Groups

A group is an easy way to organize your authorization. All groups with the same name, will have their abilities and authorizations nested together.

The group will delegate all abilities defined inside of it, to their childrens, so any role or group will have the same abilities defined in the parent. Also the group is not an avaliable role, they only serve has namespaces.

class UserAuthorization < MoulinRouge::Authorization
  group :marketing do
    can :read, Dashboard

    role :manager do
      can :manage, Proposal
    end

    role :salesman do
      can :manage, Proposal, :user_id => current_user.id
    end
  end
end

To avoid name conflicts, whenever you have a nested roles or groups, their name will be prefixed with the parent name separeted by a _ underscore just like they were namespaced.

Following the example above, will generate two roles:

MoulinRouge::Authorization.roles
# => [:marketing_manager, :marketing_salesman]
# => :marketing_manager   => can :read, Dashboard, can :manage, Proposal
# => :marketing_salesman  => can :read, Dashboard, can :manage, Proposal, :user_id => current_user.id

Nested roles

When you have nested rules, they will act has namespaces, no ability will be shared unless is explicited with the include method.

class UserAuthorization < MoulinRouge::Authorization
  role :marketing do
    can :manage, Proposal

    role :salesman do
      can :read, Proposal
    end
  end
end

Following the example above, this will generate two roles with the abilities:

MoulinRouge::Authorization.roles
# => [:marketing, :marketing_salesman]
# => :marketing            => can :manage, Proposal
# => :marketing_salesman   => can :read, Proposal

Extending

If you want extend the abilities from a role to another, MoulinRouge let you include them automatically. All the abilities from the target will be appended to the caller.

Only roles can be included, and if you provide a name that is not defined, a RoleNotFound will be raised. Notice by the example bellow, that you should provide the full name of the role in order to find them.

class UserAuthorization < MoulinRouge::Authorization
  group :marketing do
    role :admin do
      can :do, :something
    end
  end

  role :super do
    include :marketing_admin
  end
end

Configuration

MoulinRouge.configure do |config|
  # Cache authorizations
  config.cache = Rails.env.production?
  # Path for search the authorizations files
  config.path = 'app/authorizations/**/*.rb'
  # Method name that will send to the user to test if the role is assigned to him
  config.test_method = :is?
  # Your user model
  config.model = User
  # The method name that will access the current user information
  config.model_instance = :current_user
end

Goodies

This session is in experimental development and will be part of the next release!

For those who does not like the load_and_authorize_resource method from CanCan, here is provided a cleaner and more flexible solution through ActionController::Responder, the MoulinRouge::CanCan::Responder bellow there are instruction to activate them.

Create the file lib/application_responder.rb with the following:

require 'moulin_rouge/cancan/responder'

class ApplicationResponder < ActionController::Responder
  include MoulinRouge::CanCan::Responder
end

And on your application_controller.rb just add the responder:

require 'application_responder'

class ApplicationController < ActionController::Base
  protect_from_forgery
  self.responder = ApplicationResponder
  ...
end

More about the Responder class:

Testing

This gem uses the RSpec-2 lib for BDD testing, with the help of Guard to autotest. For development just execute the following line:

bundle exec guard

And it will perform all tests, and reload every time you implement something new. Also you can follow the test coverage by simplecov.

Thanks

A little of history

Mouling Rouge is a cabaret in Paris best know as the birthplace on modern CanCan second to Wikipedia.

Copyrights

Copyrights 2012 Edson Hilios edson (at) hilios (dot) com (dot) br

This software is licensed under MIT-LICENSE