1

I have used the Pundit Gem before, but I've never tried doing what I'm trying to do now, and for some reason Pundit is not happy.

What I'm aiming to do, is to have a modal with the 'create' (Foo) form on my 'index'(Foos) page. Thus I need to instantiate an empty Foo object for the modal form to work.

The issue that I'm experiencing, is that Pundit throws an error when I submit the form remotely. The error is:

Pundit::NotDefinedError - unable to find policy of nil

I have tried to understand why this is happening but I've not been able to solve it yet.

Here is my foos_controller.rb#index:

...
def index
  @foo = Foo.new
  authorize @foo, :new?
  @foos = policy_scope(Foo)
end
...

I then have the following 'before_action' filter that runs for my other actions i.e. 'create'

...
before_action     :run_authorisation_check, except: [:index]
def run_authorisation_check
  authorize @foo
end
...

The policies that I'm using in foo_policy.rb:

....
def index?
  user.has_any_role? :super_admin
end

def create?
  user.has_any_role? :super_admin
end

def new?
  create?
end


def scope
  Pundit.policy_scope!(user, record.class)
end

class Scope
  attr_reader :user, :scope

  def initialize(user, scope)
    @user = user
    @scope = scope
  end

  def resolve
    if user.has_any_role? :super_admin
      scope.all
    end
  end
end
....

The error does not present itself until I submit the form. Could anybody familiar with Pundit please help guide me to understand what I'm doing incorrectly?

UPDATE

Full foos_controller.rb

class FoosController < ApplicationController


  def index
    @foo = Foo.new
    authorize @foo, :create?
    @foos = policy_scope(Foo)
  end


  def new
    @foo = Foo.new
  end


  def create
    @foo = Foo.new(foo_params)
    respond_to do |format|
      if @foo.save
        flash[:notice] = I18n.t("foo.flash.created")
        format.json { render json: @foo, status: :ok }
      else
        format.json { render json: @foo.errors, status: :unprocessable_entity }
      end
    end
  end

  private

    before_action     :run_authorisation_check, except: [:index]

    def foo_params
      params.fetch(:foo, {}).permit(:bar)
    end

    def run_authorisation_check
      authorize @foo
    end
end
Promise Preston
  • 24,334
  • 12
  • 145
  • 143
HermannHH
  • 1,732
  • 1
  • 27
  • 57

2 Answers2

0

Yeah, you're not setting the value of @foo, that's why you're getting the error unable to find policy of nil.

Most times, you would have something like this in your foos_controller.rb:

before_action :set_foo, only: [:show, :edit, :update, :destroy]
before_action     :run_authorisation_check, except: [:index]
...
private
  def set_foo
    @foo = Foo.find(params[:id])
  end

Let me know if that works

oreoluwa
  • 5,553
  • 2
  • 20
  • 27
  • How can I set @foo with a :new or :create method? This usually expects an empty instance....Or am I completely missing something? – HermannHH Aug 15 '16 at 10:02
0

I had this issue when working on a Rails 6 API only application with the Pundit gem.

I was running into the error below when I test my Pundit authorization for my controller actions:

Pundit::NotDefinedError - unable to find policy of nil

Here's how I solved:

Say I have a policy called SchoolPolicy:

class SchoolPolicy < ApplicationPolicy
  attr_reader :user, :school

  def initialize(user, school)
    @user = user
    @school = school
  end

  def index?
    user.admin?
  end

  def show?
    user.admin?
  end

  def new
    create?
  end

  def edit
    update?
  end

  def create
    user.admin?
  end

  def update?
    user.admin?
  end

  def destroy?
    user.admin?
  end
end

Then in my SchoolsController, I will have the following:

class Api::V1::SchoolsController < ApplicationController
  before_action :set_school, only: [:show, :update, :destroy]
  after_action :verify_authorized, except: :show

  # GET /schools
  def index
    @schools = School.all
    authorize @schools

    render json: SchoolSerializer.new(@schools).serializable_hash.to_json
  end

  # GET /schools/1
  def show

    render json: SchoolSerializer.new(@school).serializable_hash.to_json
  end

  # POST /schools
  def create
    @school = School.new(school_params)
    authorize @school

    if @school.save
      render json: SchoolSerializer.new(@school).serializable_hash.to_json, status: :created, location: api_v1_school_url(@school)
    else
      render json: @school.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /schools/1
  def update
    authorize @school

    if @school.update(school_params)
      render json: SchoolSerializer.new(@school).serializable_hash.to_json
    else
      render json: @school.errors, status: :unprocessable_entity
    end
  end

  # DELETE /schools/1
  def destroy
    authorize @school

    @school.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_school
      @school = School.find(params[:id])
    end

    # Only allow a trusted parameter "white list" through.
    def school_params
      params.require(:school).permit(:name, :alias, :code)
    end
end

Note:

  1. I used an after_action callback to call the verify_authorized method to enforce authorization for the controller actions
  2. I did not call the authorize method on the show action because it was skipped for authorization by me out of choice based on my design.
  3. The instance variables called by the authorize method corresponds to the instance variable of the controller actions being called. So for the index action it is @schools and for the create action it is @school and so on.

That's all.

I hope this helps

Promise Preston
  • 24,334
  • 12
  • 145
  • 143