Gemfile
gem 'pundit', '~> 0.2.1'
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
...
app/policies/application_policy.rb
class ApplicationPolicy < Struct.new(:user, :record)
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
end
app/policies/book_policy.rb
class BookPolicy < ApplicationPolicy
def create?
record.new_record?
end
def new?
create? end
def show?
record.published? || user == record.user || user.is?(:admin)
end
end
app/controllers/books_controller.rb
class BooksController < ApplicationController
before_action :set_book, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:show]
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
# GET /books/1
def show
authorize(@book)
end
# GET /books/new
def new
@book = Book.new
authorize(@book)
end
# POST /books
def create
@book = current_user.books.build(book_params)
authorize(@book)
if @book.save
redirect_to @book, notice: 'Your book was successfully created.'
else
render action: 'new'
end
end
private
def set_book
@book = Book.find(params[:id])
end
def book_params
params.require(:book).permit(:title, :description)
end
end
test/factories/factories.rb
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "email#{n}@x.com" }
password '12345678'
password_confirmation '12345678'
end
factory :book do
title 'xx'
user
end
end
As per the Minitest
docs I tried < Minitest::Test
, but got gems/minitest-4.7.5/lib/minitest/unit.rb:19:in 'const_missing': uninitialized constant MiniTest::Test (NameError)
which led me to discover that the docs in master are for Minitest 5. So I hunted down the Minitest docs before the version 5 commit and found that we should be subclassing MiniTest::Unit::TestCase
.
test/policies/book_policy_test.rb
require 'test_helper'
class BookPolicyTest < Minitest::Test
...
end
test/policies/book_policy_test.rb
require 'test_helper'
class BookPolicyTest < Minitest::Unit::TestCase
def test_new
user = FactoryGirl.create(:user)
book_policy = BookPolicy.new(user, Book.new)
assert book_policy.new?
end
def test_create
book = FactoryGirl.create(:book)
book_policy = BookPolicy.new(book.user, book)
assert !book_policy.create?
end
end
test/policies/book_policy_test.rb
require 'test_helper'
class BookPolicyTest < Minitest::Unit::TestCase
def test_new
user = FactoryGirl.create(:user)
assert permit(user, Book.new, :new)
end
def test_create
book = FactoryGirl.create(:book)
assert !permit(book.user, book, :create)
end
private
def permit(current_user, record, action)
self.class.to_s.gsub(/Test/, '').constantize.new(current_user, record).public_send("#{action.to_s}?")
end
end
test/test_helper.rb
class PolicyTest < Minitest::Unit::TestCase
def permit(current_user, record, action)
self.class.to_s.gsub(/Test/, '').constantize.new(current_user, record).public_send("#{action.to_s}?")
end
end
test/policies/book_policy_test.rb
require 'test_helper'
class BookPolicyTest < PolicyTest
def test_new
user = FactoryGirl.create(:user)
assert permit(user, Book.new, :new)
end
def test_create
book = FactoryGirl.create(:book)
assert !permit(book.user, book, :create)
end
end
test/test_helper.rb
class PolicyTest < Minitest::Unit::TestCase
def permit(current_user, record, action)
self.class.to_s.gsub(/Test/, '').constantize.new(current_user, record).public_send("#{action.to_s}?")
end
def forbid(current_user, record, action)
!permit(current_user, record, action)
end
end
test/policies/book_policy_test.rb
require 'test_helper'
class BookPolicyTest < PolicyTest
def test_new
user = FactoryGirl.create(:user)
assert permit(user, Book.new, :new)
end
def test_create
book = FactoryGirl.create(:book)
assert forbid(book.user, book, :create)
end
end
require 'test_helper'
class BookPolicyTest < PolicyTest
def test_new
assert permit(User.new, Book.new, :new)
book = FactoryGirl.create(:book)
assert forbid(book.user, book, :new)
end
def test_create
assert permit(User.new, Book.new, :create)
book = FactoryGirl.create(:book)
assert forbid(book.user, book, :create)
end
def test_show
# a stranger should be able to see a published book
stranger = FactoryGirl.build(:user)
book = FactoryGirl.create(:book, published: true)
refute_equal stranger, book.user
assert permit(stranger, book, :show)
# but not if it's NOT published
book.published = false
assert forbid(stranger, book, :show)
# but the book owner still should
assert permit(book.user, book, :show)
# and so should the admin
admin = FactoryGirl.build(:admin)
assert permit(admin, book, :show)
end
end
I created a gem for testing pundit with minitest called policy-assertions. Here's what your test looks like.
class ArticlePolicyTest < PolicyAssertions::Test
def test_index_and_show
assert_permit nil, Article
end
def test_new_and_create
assert_permit users(:staff), Article
end
def test_destroy
refute_permit users(:regular), articles(:instructions)
end
end
Pundit now provides Pundit#authorize
(https://github.com/elabs/pundit/pull/227).
So user664833's permit method can be updated as below:
def permit(current_context, record, action)
Pundit.authorize(current_context, record, action)
rescue Pundit::NotAuthorizedError
false
end
Building on user664833's answer I use the following to test Pundit's policy_scope:
def permit_index(user, record)
(record.class.to_s + 'Policy::Scope').constantize.new(user, record.class).resolve.include?(record)
end
For example:
assert permit_index(@book.user, @book)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With