This gem is a simple, declarative, role-based access control system for Rails that works great with devise!
SimonSays can be installed via your Gemfile.
SimonSays consists of two parts:
- A Roleable module mixin which provides a way to define roles on User models or on join through models.
- An Authorizer module mixin which provides a declarative API to controllers for finding and authorizing resources.
First, we need to define some roles on a model. Roles are stored as an integer and bitmasking is used to determine the roles assigned for given record. SimonSays provides a generator for creating a new migration for this required attribute:
rails g model User # if and only if this model does not yet exist rails g active_record:simon_says User rails db:migrate
Now we can define some roles in our User model. For example:
class User < ::Base include :: has_roles :add, :edit, :delete end # > User.new.roles # =>  # > u = User.create(roles: %i[add edit]) # => #<User ...> # > u.roles # => [:add, :edit] # > u.has_add? # => true # > u.has_delete? # => false # > u.update roles: %i[delete add edit] # > u.save # save record with roles_mask of 7
The attribute name can be customized by using the
:as option as seen
here in the Admin model:
class Admin < ::Base include :: has_roles :design, :support, :moderator, as: :access end # > Admin.new.access # =>  # > Admin.new(access: :support).access # => [:support]
Make sure to generate a migration using the correct attribute name if
:as is used. For example:
rails g active_record:simon_says Admin access
We can also use
has_roles to define roles on a join through model
which is used to associate a User with a resource.
class Permission < ActiveRecord::Base include SimonSays::Roleable belongs_to :user belongs_to :document has_roles :download, :edit, :delete, end # > Permission.new(roles: Permission::ROLES).roles # => [:download, :edit, :delete]
Roleable also creates two scopes that can be used to find records that
have a given set roles. Using the default attribute name, the two scopes
generated would be
with_all_roles. Both methods
accept one or more role symbols as its arguments. The first scope,
with_roles, will find any record with one or more the supplied roles.
The second scope,
with_all_roles will only find record that have all
of the supplied roles.
It is useful to note the various dynamically generated methods as well
ROLES constant, which is used in the Permission example. Take a
look at the
to see how methods and scopes are dynamically generated with
Authorizer concern provides several methods that can be used within
your controllers in a declarative manner.
Please note, certain assumptions are made with
upon the above User and Admin model examples,
Authorizer would assume
there is a
current_admin method. If these models
correspond to devise scopes this would be the case by default.
Additionally there would need to be an
authenticate_admin! methods, which devise provides as well.
Eventually, we would like to see better customization around the authentication aspects. This library is intended to solve the problem of authorization and access control. It is not an authentication library.
In general, the
Authorizer concern provides four core declarative methods
to be used in controllers. All of these methods accept the
:except options which end up being used in a
authenticate(scope, opts): Declarative convenience method to setup authenticatebefore_action`
find_resource(resource, opts): Declarative method to find a resource and assign it to an instance variable
authorize_resource(resource, *roles): Authorize resource for given roles
find_and_authorize(resource, *roles): Find a resource and then try authorize it for the given roles. If successful, the resource is assigned to an instance variable
When find resources, the
default_authorization_scope is used. It can
be customized on a per-controller basis. For example:
class ApplicationController < ActionController::Base include :: self. = :current_user end
To authorize resources against a given role, we use either
find_and_authorize. For example, consider this
DocumentsController which uses an authenticated
User resource and a
Permission through model:
class DocumentsController < ApplicationController authenticate :user :document, :edit, through: :permissions, only: [:edit, :update] :document, :delete, through: :permissions, only: :destroy end
This controller will find a Document resource and assign it to the
@document instance variable. For the
it'll require a permission with an
:edit role. For the
method, a permission with the
:delete role is required. Since the
:through option is used, a
@permission instance variable will also
find_resource method may raise an
authorize method may raise a
SimonSays::Authorizer::Denied exception if there is insufficient role
access. As a result, the
find_and_authorize method may raise either
We can also use a different authorization scope with the
find_and_authorize. For example:
class ReportsController < ApplicationController :admin, :support find_resource :report, from: :current_admin, except: [:index, :new, :create] end
Please refer to the
for more information on the various declarative methods provided by the
- Fork it ( https://github.com/SimplyBuilt/SimonSays/fork )
- Create your feature branch (
git checkout -b my-new-feature)
- Commit your changes (
git commit -am 'Add some feature')
- Push to the branch (
git push origin my-new-feature)
- Create a new Pull Request