Search code examples
ruby-on-railspundit

Getting boolean instead of record when authorizing with Pundit


According to the Pundit readme authorize should return the record, yet when I'm calling it I'm getting true.

authorize returns the object passed to it, so you can chain it like this:

Controller:

def show
  @user = authorize User.find(params[:id])
end

Gemfile:

gem 'rails', '~> 5.1.1'
gem 'devise', '~> 4.3'
gem 'pundit', '~> 1.1'

My controller:

class PostsController < ApplicationController
  skip_before_action :authenticate_user!, only: [:show, :index]
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  def show
    # just for debugging purposes
    raise "Post is a #{@post.class.name}!" unless @post.is_a? Post
  end

  def set_post
    # this should return an instance of post
    @post = authorize Post.find(params[:id])
  end
end

Policy:

class PostPolicy < ApplicationPolicy

  class Scope < Scope
    def resolve
      scope.all
    end
  end

  def show?
    true
  end

  # ...
end

Spec:

require 'rails_helper'
RSpec.describe "Posts", type: :request do
  subject { response }

  describe "GET /posts/:id" do
    let!(:post) { create(:post) }
    before { get post_path(post) }
    it { should be_successful }
  end
end

Failure message:

  4) Posts GET /posts/:id 
     Failure/Error: raise "Post is a #{@post.class.name}!" unless @post.is_a? Post

     RuntimeError:
       Post is a TrueClass!

While its pretty simple to remedy this by:

def set_post
  @post = Post.find(params[:id]).tap do |p|
    @post = Post.find(params[:id]).tap { |r| authorize r }
  end
end

Im very curious as to why its not working as stated by the readme. Is this a bug or am I just missing something?


Solution

  • Returning the record is apparently a change in the master that is not reflected in the 1.1 release.

    # Retrieves the policy for the given record, initializing it with the
    # record and user and finally throwing an error if the user is not
    # authorized to perform the given action.
    #
    # @param user [Object] the user that initiated the action
    # @param record [Object] the object we're checking permissions of
    # @param record [Symbol] the query method to check on the policy (e.g. `:show?`)
    # @raise [NotAuthorizedError] if the given query method returned false
    # @return [true] Always returns true
    def authorize(user, record, query)
      policy = policy!(user, record)
    
      unless policy.public_send(query)
        raise NotAuthorizedError, query: query, record: record, policy: policy
      end
    
      true
    end
    

    A workaround is:

    def authorize(record, query = nil)
      super
      record 
    end