1
0
Fork 0
mirror of https://github.com/varvet/pundit.git synced 2022-11-09 12:30:11 -05:00

Add controller helpers which facilitate update and build patterns

This commit is contained in:
Jonas Nicklas 2015-03-27 15:54:09 +01:00
parent 1bfe952d09
commit 8dedc55bf8
5 changed files with 129 additions and 5 deletions

View file

@ -536,6 +536,49 @@ class PostsController < ApplicationController
end
```
## Controller helpers
Pundit provides controller helpers which take the permitted attributes pattern to its
logical conclusion. We can write an update action like this:
```ruby
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def update
@post = Post.find(params[:id])
if update(@post)
redirect_to @post
else
render :edit
end
end
end
```
Not only does this correctly retrieve the permitted attributes and assign them,
it also makes sure authorization is performed both before *and* after we set
these attributes. For example, an admin might be able to assign everyone a
particular task, but a regular user can only assign themselves, not someone
else. This kind of scenario requires the permissions to be checked twice, both
before (they can't edit tasks assigned to someone else) and after (they can't
assign a task to someone other than themselves).
Similarly there is a helper for building a new record:
```ruby
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def update
@post = build(Post)
if @post.save
redirect_to @post
else
render :edit
end
end
end
```
## RSpec
### Policy Specs

View file

@ -121,6 +121,20 @@ module Pundit
params.require(name).permit(policy(record).permitted_attributes)
end
def build(klass, attributes = {})
record = klass.build(attributes)
record.assign_attributes(permitted_attributes(record))
authorize(record)
record
end
def update(record, attributes = {})
authorize(record)
record.assign_attributes(attributes.merge(permitted_attributes(record)))
authorize(record)
record.save
end
def policies
@_pundit_policies ||= {}
end

View file

@ -2,8 +2,8 @@ require "spec_helper"
describe PostPolicy do
let(:user) { double }
let(:own_post) { double(user: user) }
let(:other_post) { double(user: double) }
let(:own_post) { double(user: user, votes: 0) }
let(:other_post) { double(user: double, votes: 0) }
subject { described_class }
permissions :update?, :show? do

View file

@ -1,8 +1,8 @@
require "spec_helper"
describe Pundit do
let(:user) { double }
let(:post) { Post.new(user) }
let(:user) { double("user") }
let(:post) { Post.new(user: user) }
let(:comment) { Comment.new }
let(:article) { Article.new }
let(:controller) { Controller.new(user, { :action => 'update' }) }
@ -315,4 +315,49 @@ describe Pundit do
expect(Controller.new(double, params).permitted_attributes(post)).to eq({ 'votes' => 5 })
end
end
describe "#build" do
let(:params) { ActionController::Parameters.new({ action: 'update', post: { title: 'Hello', votes: 5, admin: true } }) }
let(:controller) { Controller.new(user, params) }
it "builds a record of the given type with permitted attributes" do
post = controller.build(Post, user: user)
expect(post).to be_an_instance_of(Post)
expect(post.user).to eq(user)
expect(post.attributes["title"]).to eq('Hello')
expect(post.attributes.has_key?("admin")).to eq(false)
end
it "denies authorization if the given record is not authorized to perform the action" do
expect { controller.build(Post, user: double("other")) }.to raise_error(Pundit::NotAuthorizedError)
end
end
describe "#update" do
let(:params) { ActionController::Parameters.new({ action: 'update', post: { title: 'Hello', votes: 5, admin: true } }) }
let(:controller) { Controller.new(user, params) }
it "builds a record of the given type with permitted attributes" do
post = Post.new(title: 'Monkey', user: user)
expect(post).to receive(:save).and_return(:done)
expect(controller.update(post, "body" => "test")).to eq(:done)
expect(post.user).to eq(user)
expect(post.attributes['title']).to eq('Hello')
expect(post.attributes['body']).to eq('test')
end
it "denies authorization if the given record is not authorized to perform the action" do
post = Post.new(title: 'Monkey', user: double("other"))
expect(post).not_to receive(:save)
expect { controller.update(post, user: user) }.to raise_error(Pundit::NotAuthorizedError)
end
it "denies authorization if the given record is not authorized after attributes are assigned" do
post = Post.new(title: 'Monkey', user: user)
expect(post).not_to receive(:save)
expect { controller.update(post, user: double("other")) }.to raise_error(Pundit::NotAuthorizedError)
end
end
end

View file

@ -27,7 +27,7 @@ end
class PostPolicy < Struct.new(:user, :post)
def update?
post.user == user
post.user == user and post.votes < 10
end
def destroy?
false
@ -43,15 +43,37 @@ class PostPolicy < Struct.new(:user, :post)
end
end
end
class PostPolicy::Scope < Struct.new(:user, :scope)
def resolve
scope.published
end
end
class Post < Struct.new(:user)
attr_reader :attributes
singleton_class.send :alias_method, :build, :new
def self.published
:published
end
def initialize(attributes = {})
@attributes = attributes
end
def user
@attributes[:user]
end
def votes
@attributes[:votes].to_i
end
def assign_attributes(attributes)
@attributes.merge!(attributes)
end
def to_s; "Post"; end
def inspect; "#<Post>"; end
end