Search code examples
ruby-on-railspundit

Pundit - Policies are not recognised


I am implementing pundit and wish to restrict the user#edit and user#update actions to only the current_user

def edit
  @user = current_user
  authorize(@user)
end

def update
  @user = current_user
  authorise(@user)
  if @user.update_attributes(user_params)
    flash[:success] = "Profile updated"
    redirect_to edit_user_path
  else
    render 'edit'
  end
end

The following is my attempted policy which (a) does not work and (b) is illogical.

class UserPolicy

  attr_reader :user, :user

  def initialise(user, user)
    @user = user
  end

  def update?
    true
  end

  alias_method :edit?, :update?

end

I have now updated my UserPolicy as per below. I have set the actions to false for testing as everything was being authorised:

class UserPolicy < ApplicationPolicy

  def new?
    create?
  end

  def create?
    false
  end

  def edit?
    update?
  end

  def update?
    false
    #user.id == record.id
  end

end

However my policies are not recognised. Upon further reading I added the following to my ApplicationController:

after_filter :verify_authorized, except: :index
after_filter :verify_policy_scoped, only: :index

When I now navigate to my user#edit action I receive:

Pundit::AuthorizationNotPerformedError

Solution

  • First, make sure you have...

    your-app/app/controllers/application_controller.rb

    class ApplicationController < ActionController::Base
      include Pundit
    end
    

    your-app/app/policies/application_policy.rb with default permissions for common actions.

    class ApplicationPolicy
      attr_reader :user, :record
    
      def initialize(user, record)
        @user = user
        @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 :user, :scope
    
        def initialize(user, scope)
          @user = user
          @scope = scope
        end
    
        def resolve
          scope
        end
      end
    

    Then, in your UserPolicy

    your-app/app/policies/section_policy.rb

    class UserPolicy < ApplicationPolicy
      def edit?
        user.id == record.id
      end
    
      def update?
        edit?
      end
    end
    

    So, by default, user will be your current user and record will be the @user defined on edit and update actions.

    You don't need to call authorize method explicitly. Pundit knows what to do with your @user attribute. So, your controller should be:

    def edit
      user
    end
    
    def update
      if user.update_attributes(user_params)
        flash[:success] = "Profile updated"
        redirect_to edit_user_path
      else
        render 'edit'
      end
    end
    
    private
    
    def user
      @user ||= User.find(params[:id])
    end
    

    you must know if you don't have a current_user method, yo will need to define a pundit_user in your application controller.