Search code examples
ruby-on-railsrubypundit

How to solve NoMethodError with Pundit


I don't know if I'm doing something wrong here but it seems like.

I use Pundit for authorization and I have set up a few models with it now.

Ive got a Category model which can only be created by admins. Also I don't want users to see the show/edit/destroy views either. I just want it to be accessed by admins. So far so good.

Will add some code below:

category_policy.rb

class CategoryPolicy < ApplicationPolicy
  def index?
    user.admin?
  end

  def create?
    user.admin?
  end

  def show?
    user.admin?
  end

  def new?
    user.admin?
  end

  def update?
    return true if user.admin?
  end

  def destroy?
    return true if user.admin?
  end
end

categories_controller.rb

class CategoriesController < ApplicationController
  layout 'scaffold'

  before_action :set_category, only: %i[show edit update destroy]

  # GET /categories
  def index
    @category = Category.all
    authorize @category
  end

  # GET /categories/1
  def show
    @category = Category.find(params[:id])

    authorize @category
  end

  # GET /categories/new
  def new
    @category = Category.new
    authorize @category
  end

  # GET /categories/1/edit
  def edit
    authorize @category
  end

  # POST /categories
  def create
    @category = Category.new(category_params)
    authorize @category
    if @category.save
      redirect_to @category, notice: 'Category was successfully created.'
    else
      render :new
    end
  end

  # PATCH/PUT /categories/1
  def update
    authorize @category
    if @category.update(category_params)
      redirect_to @category, notice: 'Category was successfully updated.'
    else
      render :edit
    end
  end

  # DELETE /categories/1
  def destroy
    authorize @category
    @category.destroy
    redirect_to categories_url, notice: 'Category was successfully destroyed.'
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_category
    @category = Category.find(params[:id])
  end

  # Only allow a trusted parameter "white list" through.
  def category_params
    params.require(:category).permit(:name)
  end
end

application_policy.rb

class ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    false
  end

  def create?
    create?
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope.all
    end
  end
end

Ive got Pundit included in my ApplicationController and rescue_from Pundit::NotAuthorizedError, with: :forbidden this set up there as well.

The authorization itself works, if I'm logged in with an admin account I have access to /categories/*. If I'm logged out I get the following: NoMethodError at /categories undefined methodadmin?' for nil:NilClass` While writing the question I think I found the problem- I guess Pundit looks for a User that is nil because I'm not logged in. What would the correct approach of solving this issue look like?

Best regards


Solution

  • The most common approach is to redirect users from pages that are not accessible by not logged in users. Just add a before action in your controller:

    class CategoriesController < ApplicationController
      before_action :redirect_if_not_logged_in
    
      <...>
    
      private
    
      def redirect_if_not_logged_in
        redirect_to :home unless current_user
      end
    end
    

    (I assume here that you have current_user method which returns user instance or nil. Please change :home to wherever you want to redirect users)