I'm using devise
and I followed this in order to setup three users (admin, seller, viewer). Each user has it's on model
, session_controller
, registration_conttroler
and views
folder with all the views associated to each user.
Now I'm trying to implement the pundit gem
in order to setup permissions in each controller
.
When trying to land on the localhost:3000/items
I'm getting the following error: unable to find policy of nil
Pundit::NotDefinedError in ItemsController#index
This is what I'm trying to do in the items_controller
:
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update, :destroy]
def index
authorize @item
@items = Item.all
end
def show
authorize @item
@comments = Comment.where(item_id: @item).order("created_at DESC")
@items = Item.find(params[:id])
end
def new
authorize @item
@item = Item.new
@categories = Category.order(:name)
end
def edit
authorize @item
@categories = Category.order(:name)
end
def create
authorize @item
@item = Item.new(item_params)
respond_to do |format|
if @item.save
format.html { redirect_to @item, notice: 'Item was successfully created.' }
format.json { render :show, status: :created, location: @item }
else
format.html { render :new }
format.json { render json: @item.errors, status: :unprocessable_entity }
end
end
end
def update
authorize @item
respond_to do |format|
if @item.update(item_params)
format.html { redirect_to @item, notice: 'Item was successfully updated.' }
format.json { render :show, status: :ok, location: @item }
else
format.html { render :edit }
format.json { render json: @item.errors, status: :unprocessable_entity }
end
end
end
def destroy
authorize @item
@item.destroy
respond_to do |format|
format.html { redirect_to items_url, notice: 'Item was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_item
@item = Item.find(params[:id])
end
end
application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery prepend: true
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def pundit_user
CurrentContext.new(current_seller, current_admin, current_viewer)
end
private
def user_not_authorized(exception)
policy_name = exception.policy.class.to_s.underscore
flash[:warning] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
redirect_to(request.referrer || root_path)
end
end
models/current_context.rb
class CurrentContext
attr_reader :seller, :admin, :viewer
def initialize(seller, admin, viewer)
@seller = seller
@admin = admin
@viewer = viewer
end
end
policies/application_policy.rb
class ApplicationPolicy
attr_reader :seller, :record, :admin, :viewer
def initialize(context, record)
raise Pundit::NotAuthorizedError, "must be logged in" unless context
@seller = context.seller
@admin = context.admin
@viewer = context.viewer
@record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :seller, :admin, :viewer, :scope
def initialize(context, scope)
@seller = context.seller
@admin = context.admin
@viewer = context.viewer
@scope = scope
end
def resolve
scope
end
end
end
policies/item_policy.rb
What I'm trying here is... the admin to have full access and the seller to create, edit, updated, delete only his own content.
class ItemPolicy < ApplicationPolicy
attr_reader :item
def initialize(user, item)
super(user, item)
@user = user
@item = record
end
def update?
@user.is_a?(Admin) || @item.try(:user) == @user
end
def index?
@user.is_a?(Admin) || @item.try(:user) == @user
end
def show?
@user.is_a?(Admin) || @item.try(:user) == @user
end
def create?
@user.is_a?(Admin) || @item.try(:user) == @user
end
def new?
@user.is_a?(Admin) || @item.try(:user) == @user
end
def edit?
@user.is_a?(Admin) || @item.try(:user) == @user
end
def destroy?
@user.is_a?(Admin) || @item.try(:user) == @user
end
end