README.md
# warhol [](https://codeclimate.com/github/mach-kernel/warhol)  [](https://codeclimate.com/github/mach-kernel/warhol/coverage) A better way to do [CanCanCan](https://github.com/CanCanCommunity/cancancan) [`Ability` classes](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities). Written in pure Ruby with `cancancan` as its only dependency. Designed to improve code quality in existing projects with CanCan. <img height="300px" src="https://raw.github.com/mach-kernel/warhol/master/splash.jpg" /> ## Getting Started ### Motivations CanCan's official documentation says that this is what your ability class should look like: ```rubyclass Ability include CanCan::Ability def initialize(user) user ||= User.new # guest user (not logged in) if user.admin? can :manage, :all else can :read, :all end endend``` For small applications, this is simple and clear. However, in practice, some of these `Ability` classes are 200+ LoC monoliths that encompass the enforcement of many different kinds of permission sets without any clear separation of responsibility. Using `Warhol::Ability` allows you to have an individual set of permissions for each role in your domain. ### Quick Start Specify a method on your domain object (for many, an `ActiveRecord` or `Mongoid` model, but any PORO is fine) returning an array of strings, one for each role. Warhol then takes those strings and matches them up to your defined ability classes, applying the access permissions you defined there. Matching is performed on the names of the `Warhol::Ability` subclass, excluding their namespaces. Here is a database-backed example inspired by the above snippet from CanCan's official docs: First, some quick configuration. In a Rails project, we suggest this is placed in an initializer: #### Initializer ```rubyrequire 'warhol' Warhol::Config.new do |warhol| # This is the method we invoke below warhol.role_accessor = :role_names # Exposes a basic attr_reader. More below. warhol.additional_accessors = ['user']end``` #### The Domain Object The domain object can look like this: ```rubyclass User < ActiveRecord::Base has_and_belongs_to_many :roles def role_names roles.pluck(:name) endend``` #### Definitions Some abilities: ```rubyclass Administrator < Warhol::Ability define_permissions do can :manage, :all # object is always included in scope can :other_condition, user_id: object.id # user via additional accessor can :thing_with_condition, user_id: user.id endend class Member < Warhol::Ability define_permissions do can :read, :all endend``` Now, we just check the role. People using Rails can just invoke `can?` from their controller instead of explicitly making a new `Ability` class: ```userputs user.role_names# => ['Member']puts Ability.new(user).can? :manage, @something# => false``` That's it! ### Config Options To configure Warhol, we create a new singleton configuration class. Every time you invoke `::new`, the instance is replaced with the one you most recently defined. ```rubyWarhol::Config.new do |warhol| warhol.option = valueend```| Key | Type | Description ||--------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|| ability_parent | Module | The parent object under which to define the `Ability` constant. || additional_accessors | Array[String] | By default, inside the `define_permissions` block you can access the object you are performing the check on by invoking `object`. For the example use case of applying user permissions, passing `%w(user)` may not be a bad idea. || role_accessor (REQUIRED) | Symbol | Accessor used to fetch roles from domain object. || role_proc | Proc | If you do not wish to define an accessor, you can pass a block with an arity of 1; the object you are performing the check against will be passed as an argument allowing you to either implement the logic here or delegate it to a service object. | ### Advanced Usage #### Map domain object to roles Some elect to not store role data on user objects, but rather pass users to a service object that provides its authorization level. You can do this with Warhol by providing it with a `proc`: ```rubyWarhol::Config.new do |warhol| warhol.role_proc = proc do |user| PermissionService.new(user).roles endend``` #### Override parent namespace of `Ability` If for some reason you would like to bind `Ability` somewhere other than `Object::Ability`, you can provide an alternate namespace. ```rubyWarhol::Config.new do |warhol| warhol.ability_parent = Foo::Bar::Bazend``` `Foo::Bar::Baz::Ability` will now be where it is placed. ## License MIT License Copyright (c) 2017 David Stancu Permission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE.