0.01
A long-lived project that still receives updates
Assign multiple roles to any User or other ActiveRecord object. Select only the appropriate objects based on intelligent, chainable ActiveRecord::Relation finder methods.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

Effective Roles

Assign multiple roles to any User or other ActiveRecord object. Select only the appropriate objects based on intelligent, chainable ActiveRecord::Relation finder methods.

Implements multi-role authorization based on an integer roles_mask field

Includes a mixin for adding authentication for any model.

SQL Finders for returning an ActiveRecord::Relation with all permitted records.

Getting Started

Add to Gemfile:

gem 'effective_roles'

Run the bundle command to install it:

bundle install

Install the configuration file:

rails generate effective_roles:install

The generator will install an initializer which describes all configuration options.

Usage

Add the mixin to an existing model:

class Post
  acts_as_role_restricted                  # Singular role, use radio buttons
  acts_as_role_restricted multiple: true   # Multiple roles. use check boxes
end

Then create a migration to add the :roles_mask column to the model.

rails generate migration add_roles_to_post roles_mask:integer

which will create a migration something like

class AddRolesToPost < ActiveRecord::Migration
  def change
    add_column :posts, :roles_mask, :integer
  end
end

Usage

Defining Roles

All roles are defined in the config/effective_roles.rb initializer. The roles are defined once and may be applied to any acts_as_role_restricted model in the application.

Model

Assign roles:

post.roles = [:admin, :superadmin]
post.save

See if an object has been assigned a specific role:

post.is_role_restricted?
=> true

post.is?(:admin)
=> true

post.is_any?(:editor, :superadmin)
=> true

post.roles
=> [:admin, :superadmin]

Compare against another acts_as_role_restricted object:

post = Post.new()
post.roles = :admin

user = User.new()
user.roles = nil

post.roles_permit?(user)
=> false  # Post requires the :admin role, but User has no admin role
post.roles = :superadmin
user.roles = :admin

post.roles_permit?(user)
=> false  # User does not have the superadmin role
post.roles = :admin
user.roles = [:superadmin, :admin]

post.roles_permit?(user)
=> true  # User has required :admin role

Finder Methods

Find all objects that have been assigned a specific role (or roles). Will not return posts that have no assigned roles (roles_mask = 0 or NULL)

Post.with_role(:admin, :superadmin)   # Can pass as an array if you want
Post.with_role(current_user.roles)

Find all objects that are appropriate for a specific role. Will return posts that have no assigned roles

Post.for_role(:admin)
Post.for_role(current_user.roles)

These are both ActiveRecord::Relations, so you can chain them with other methods like normal.

Assignable Roles

Specifies which roles can be assigned to a resource by a specific user.

See the initializers/effective_roles.rb for more information.

  config.assignable_roles = {
    :superadmin => [:superadmin, :admin, :member], # Superadmins may assign any resource any role
    :admin => [:admin, :member],                   # Admins may only assign the :admin or :member role
    :member => []                                  # Members may not assign any roles
  }

When using assignable roles, you must assign the acts_as_role_restricted resource a current_user when saving changes to the roles or roles mask:

You can do this in one of three ways:

  1. Setting resource.current_user = current_user in your controller update action directly.
  2. Add before_action -> { @thing.current_user = current_user } to your ApplicationController
  3. Using Effective::CrudController to do this automatically.

This restriction is only applied when running within the rails server. Not on rails console or db:seeds.

Form Helper

If you pass current_user (or any acts_as_role_restricted object) into these helpers, only the assignable_roles will be displayed.

effective_form_with

Depending on your acts_as_role_restricted multiple: value:

effective_form_with(model: @user) do |f|
  = f.checks :roles, EffectiveRoles.roles_collection(f.object, current_user)
effective_form_with(model: @user) do |f|
  = f.radios :roles, EffectiveRoles.roles_collection(f.object, current_user)

simple_form

simple_form_for @user do |f|
  = f.input :roles, collection: EffectiveRoles.roles_collection(f.object, current_user), as: :check_boxes
simple_form_for @user do |f|
  = f.input :roles, collection: EffectiveRoles.roles_collection(f.object, current_user), as: :radio_buttons

Strong Parameters

Make your controller aware of the passed parameters:

def permitted_params
  params.require(:base_object).permit(EffectiveRoles.permitted_params)
end

The actual permitted parameters are:

roles: []

Summary

Use the effective_roles_summary view helper to output a list of roles and descriptions.

effective_roles_summary(user) # any acts_as_role_restricted object
effective_roles_summary(post)

Summary table

Use the effective_roles_summary_table view helper to output a table of the actual permission levels for each role and ActiveRecord object combination.

You can customize the helper function with the following keys: roles, only, except, plus and additionally

effective_roles_summary_table(roles: [:admin, :superadmin], only: [Post, Event])
effective_roles_summary_table(except: [Post, User])
effective_roles_summary_table(plus: [Reports::PostReport]) # Add a non ActiveRecord object to the output, sorted with the other model names
effective_roles_summary_table(additionally: [Reports::PostReport]) # Add a non ActiveRecord object to the output, after the other models
effective_roles_summary_table(plus: {post_report: :export}) # A custom permission based on a symbol

You can override the effective_roles_authorization_label(klass) method for better control of the label display.

Bitmask Implementation

The underlying role information for any acts_as_role_restricted ActiveRecord object is stored in that object's roles_mask field.

roles_mask is an integer, in which each power of 2 represents the presence or absense of a role.

If we have the following roles defined:

EffectiveRoles.setup do |config|
  config.roles = [:superadmin, :admin, :betauser, :member]
end

Then the following will hold true:

user = User.new()

user.roles
=> []

user.roles_mask
=> 0

user.roles = [:superadmin]
user.roles_mask
=> 1

user.roles = [:admin]
user.roles_mask
=> 2

user.roles = [:betauser]
user.roles_mask
=> 4

user.roles = [:member]
user.roles_mask
=> 8

As well:

user.roles = [:superadmin, :admin]
user.roles_mask
=> 3

user.roles = [:superadmin, :betauser]
user.roles_mask
=> 5

user.roles = [:admin, :member]
user.roles_mask
=> 10

user.roles = [:superadmin, :admin, :betauser, :member]
user.roles_mask
=> 15

Keep in mind, when using this gem you should never be working directly with the roles_mask field.

All roles are get/set through the roles and roles= methods.

License

MIT License. Copyright Code and Effect Inc.

Credits

This model implements the https://github.com/ryanb/cancan/wiki/Role-Based-Authorization multi role based authorization based on the roles_mask field

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Bonus points for test coverage
  6. Create new Pull Request