I have just started using Pundit for authorization in my current project along with the pundit-matchers gem.
So far it seems to generally be working for me but I have a problem in my tests.
I have generally tried to follow the examples in the pundit-matchers readme and the Thunderbolt labs blog (http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/).
This is my policy file;
#app/policies/procedure_policy.rb
class ProcedurePolicy
attr_reader :user, :procedure
def initialize(user, procedure)
@user = user
@procedure = procedure
end
def index?
user.admin?
end
end
And this is my policy_spec file
require 'rails_helper'
describe ProcedurePolicy do
subject {described_class.new(user, procedure)}
let(:procedure) {FactoryGirl.create(:procedure)}
context "for a guest" do
let(:user) {nil}
it {is_expected.not_to permit_action(:index)}
end
context "for a non-admin user" do
let(:user) {FactoryGirl.create(:user)}
it {is_expected.not_to permit_action(:index)}
end
context "for an admin user" do
let(:user) {FactoryGirl.create(:admin_user)}
it {is_expected.to permit_action(:index)}
end
end
2 of my 3 tests pass; The "for a non-admin user"
and "for an admin user"
ones. The "for a guest"
test fails with
NoMethodError:
undefined method `admin?' for nil:NilClass
Now I understand why. I'm passing nil
to the #index?
method of my ProcedurePolicy class which will not have an #admin?
method. But all of the example specs I have found online do exactly this. What am I not seeing.
Apologies if I'm missing something really obvious. I've been away from coding for a couple of years.
The "being a visitor" context is for testing authorisation attempts when there is no user currently authenticated in the system. Since no user is logged in, no user record/object exists - the absence of a user object is nil
. This is why both the Thunderbolt Labs and pundit-matchers examples use nil to represent a visitor. The nil
object does not have an admin?
method, causing the error.
To correctly handle nil/guest users in your policy, check that the user object is present before checking if the user is an admin, either by checking the user object directly or using the present?
method in Rails, e.g.
def index?
user.present? && user.admin?
end
or just:
def index?
user && user.admin?
end
You'll find that authentication systems such as Devise return nil
from the current_user
method when no user is signed in.