Project

ii_policy

0.0
The project is in a healthy, maintained state
A base policy to support management of authorization logic
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

 Project Readme

IIPolicy

A base policy to support management of authorization logic.

This gem is inspired by pundit specs.

Dependencies

  • ruby 2.3+
  • activesupport 5.0+

Installation

Add this line to your application's Gemfile:

gem 'ii_policy'

Then execute:

$ bundle

Usage

Prepare model:

class Item < ActiveRecord::Base
end

Prepare controller with current_user and call authorize:

class ItemsController < ActionController::Base
  def index
    @policy = authorize(ItemPolicy)
    @items = Item.all
  end

  def show
    @item = Item.find(params[:id])
    @policy = authorize(@item)
  end

  def current_user
    User.find(session[:login_user_id])
  end
end

Create policy that has methods corresponding with actions of controller:

class ItemPolicy < IIPolicy::Base
  def index?
    @user.admin?
  end

  def show?
    @user.admin? && @item.status != 'deleted'
  end
end

Controller

authorize lookups policy and calls it's method corresponding with current action. authorize takes following arguments:

# no argument (policy class is looked up using the name of controller class)
authorize

# instance (policy class is looked up using the name of instance's class)
authorize(@item)

# policy class
authorize(ItemPolicy)

# with extra context as second argument
authorize(@item, something: 'something')

Context is set to { user: current_user } in the controller by default. You can set other context you want by overriding policy_context:

class ItemsController < ActionController::Base
  def policy_context
    super.merge(something: 'something')
  end
end

When current user is not authoized, IIPolicy::AuthorizationError is raised. You can catch the error and render a special page using rescue_from:

class ItemsController < ActionController::Base
  rescue_from IIPolicy::AuthorizationError, with: -> { ... }
end

You can also create policy instance by yourself and check authorization using allowed method as follows:

# policy class
policy(ItemPolicy).allowed(:index?)

# instance
policy(@item).allowed(:index?)

Policy

Policy has following attributes:

class ItemPolicy < IIPolicy::Base
  def index?
    puts "user: #{@user}"
    puts "item: #{@item}"
    puts "context: #{@context}"
  end
end

policy = ItemPolicy.new(user: User.find(1), item: Item.find(1), something: 'something')
policy.allowed(:index?)
#=> user: #<User: ...>
#   item: #<Item: ...>
#   context: #<IIPolicy::Context user=..., item=..., something="something">

You can call another policy method in the same context:

class ItemPolicy < IIPolicy::Base
  def another_show?
    allowed(:show?)
  end
end

You can use policy for another instance by using policy:

class ItemPolicy < IIPolicy::Base
  def another_show?
    policy(@context.another_item).allowed(:show?)
  end
end

Callbacks

Following callbacks are available:

  • before_call
  • around_call
  • after_call

For example:

class ItemPolicy < IIPolicy::Base
  before_call do
    @something = @context.something
  end

  def index?
    @something == 'something'
  end
end

Coactors

You can define multiple coactors by using coact as follows:

# shared policy
class SharedPolicy < IIPolicy::Base
  def show?
    @user.admin?
  end
end

# base policy
class ItemPolicy < IIPolicy::Base
  coact SharedPolicy

  def show?
    @item.status != 'deleted'
  end
end

policy = ItemPolicy.new(user: User.find(1), item: Item.find(1))
policy.allowed(:show?)
#=> true

In this example, policy.allowed(:show?) is evaluated by SharedPolicy#show? && ItemPolicy#show?.

See coactive for more coact examples:

Lookup for policy

authorize and policy lookups policy class if the first argument of them is not a policy class. So the name of policy class should be composed of the base name of model or controller. For example:

class ItemPolicy < IIPolicy::Base
end

class Item
end

class ItemsController < ActionController::Base
end

IIPolicy::Base.lookup(Item)
#=> ItemPolicy

IIPolicy::Base.lookup(Item.new)
#=> ItemPolicy

IIPolicy::Base.lookup(ItemsController)
#=> ItemPolicy

Note that superclass of model or controller is also looked up until policy is found.

class ItemPolicy < IIPolicy::Base
end

class Item
end

class InheritedItem < Item
end

IIPolicy::Base.lookup(InheritedItem)
#=> ItemPolicy

IIPolicy::Base.lookup(InheritedItem.new)
#=> ItemPolicy

Logging

Policy supports instrumentation hook supplied by ActiveSupport::Notifications. You can enable log subscriber as follows:

IIPolicy::LogSubscriber.attach_to :ii_policy

This subscriber will write logs in debug mode as the following example:

Calling ItemPolicy#index? with #<IIPolicy::Context ...>
...
Called ItemPolicy#index? and return true (Duration: 0.1ms, Allocations: 9)

Contributing

Bug reports and pull requests are welcome at https://github.com/kanety/ii_policy.

License

The gem is available as open source under the terms of the MIT License.