Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
$ gem install mongoid_ability
The permissions are defined by a
Lock that applies to a
Subject and defines access for its owner –
User and/or its
Lock class can be any class that include
MongoidAbility::Lock. There should be only one such class in an application.
class MyLock include Mongoid::Document include :: :owner, polymorphic: true end
This class defines a permission itself using the following fields:
:subject_type, type: String
:subject_id, type: Moped::BSON::ObjectId
:action, type: Symbol, default: :read
:outcome, type: Boolean, default: false
These fields define what subject (respectively subject type, when referring to a class) the lock applies to, which action it is defined for (for example
:read), and whether the outcome is positive or negative.
All subjects (classes which permissions you want to control) will include the
Each action and its default outcome needs to be defined using the
class MySubject include Mongoid::Document include :: default_lock MyLock, :read, true default_lock MyLock, :update, false end
The subject classes can be subclassed. Subclasses inherit the default locks (unless they override them), the resulting outcome being correctly calculated bottom-up the superclass chain.
Additionally the locks can be converted to Mongoid criteria:
Ability class supports two levels of inheritance (for example User and its Roles). The locks can be either embedded (via
.embeds_many) or associated (via
.has_many). Make sure to include the
as: :owner option.
class MyUser include Mongoid::Document include :: :locks, class_name: 'MyLock', as: :owner has_and_belongs_to_many :roles, class_name: 'MyRole' # override if your relation is named differently def self.locks_relation_name :locks end # override if your relation is named differently def self.inherit_from_relation_name :roles end end
class MyRole include Mongoid::Document include :: :locks, class_name: 'MyLock', as: :owner has_and_belongs_to_many :users, class_name: 'MyUser' end
Both users and roles can be further subclassed.
The owner also gains the
#cannot? methods, that are delegate to the user's ability. It is then easy to perform permission checks per user:
current_user.can?(:read, resource, ) other_user.can?(:read, ResourceClass, )
Ability can be easily obtained as:
The ability object is fully cache-able, which means it is possible to save some precious time on every request (instead of always converting the Lock documents to CanCan rules):
class ActionController::Base def current_ability @current_ability ||= Rails.cache.fetch([current_user.cache_key, 'ability'].join('/')) do ::.(current_user) end.tap do |ability| ability.owner ||= current_user end end end
And on the owner:
def ability @ability ||= Rails.cache.fetch([cache_key, 'ability'].join('/')) do ::.(self) end.tap do |ability| ability.owner ||= self end end
Of course this assumes the user's
cache_key updates when any of its locks (or locks stored on its roles) change.
Note the owner has to be assigned after fetching the ability from cache.
To be able to check permissions on decorated objects (for example via the Draper gem) subclass the Ability class as follows:
class MyAbility < :: def can?(action, subject, *extra_args) while subject.is_a?(Draper::Decorator) subject = subject.model end super(action, subject, *extra_args) end end
:current_ability defined by CanCanCan will be automatically overriden by the
Ability class provided by this gem.
- Setup subject classes and their default locks.
- Define permissions using lock objects embedded (or associated to) either in user or role.
- Use standard CanCanCan helpers (
#cannot?) to authorize the current user.
- Fork it ( https://github.com/tomasc/mongoid_ability/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