- Read Tutorial
- Watch Guide Video
In this guide, we are going to implement a custom policy to start building out our app's permission structure.
To start, let's create a file called post_policy.rb
in app/policies
.
In this file, we want our PostPolicy
class to inherit from ApplicationPolicy
. By default the ApplicationPolicy
has defined a number of methods. To begin our PostPolicy
will override the update
method.
Before we build this feature, if you open the application_policy.rb
file you'll see two variables at the top called user
and record
. You'll have access to these variables in the inherited classes that we'll create for each site component that we want to add authorization to. User
refers to the current user that is attempting to access the site component and record
refers to the current record that is being edited, which in this case, is the post.
Now, in post_policy.rb
we want to define a rule that states that only the user who created a record to edit it. As for admins, we want them to be able to approve or reject a post.
First we are going to check if the owner of a record is the current user.
# app/policies/post_policy.rb def update? record.user_id == user.id end
Next, open post_controller.rb
, and update the edit
and update
methods.
# app/controllers/posts_controller.rb def edit authorize @post end def update authorize @post if @post.update(post_params) redirect_to @post, notice: 'Your post was edited successfully' else render :edit end end
This will authorize the record, and allow the user that created the record to edit it.
Let's see how it all works in the browser. After creating a new record and clicking on the edit
link, I get an error.
This is because I didn't include pundit
in our application controller. In order for helper methods like authorize
to work, we need to include Pundit
in our application so that all of the controllers have access to the methods. Let's open our application_controller.rb
and update it to include a call to the Pundit
module:
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base include Pundit protect_from_forgery with: :exception before_action :authenticate_user! end
Now, if you go back to the browser and try to edit the post the page should open properly. You can edit and make changes to the record.
Next, if I try to edit an existing record that was created by a different user, the browser throws an error:
That's not the behavior that we're looking for since we are preventing an admin user from accessing a post. We want admin users to be able to access the page and edit the status. To do that, let's update the code in our post_policy.rb
file.
# app/policies/post_policy.rb def update? record.user_id == user.id || user.type == 'AdminUser' end
In this code, we are allowing admin users as well as the owner of the record to update a record using an or
statement.
If you open up the browser you'll see that it's working properly.
Let's check this functionality with a regular user. Everything is fine.
Before we close this out let's perform a small refactor, I don't like to hard code "AdminUser" into the query. Let's reuse the code we implemented earlier in our admin dashboard's application_controller.rb
file.
First let's create a method where we can list all of the potential admin types in an array and place it in the ApplicationPolicy
parent class (this way we can use this list in any other policies, eventually we'll want to create a single place that lists out potential admin types for the entire application).
# app/policies/application_policy.rb def admin_types ['AdminUser'] end
Now let's update the update?
method in the PostPolicy
:
# app/policies/post_policy.rb class PostPolicy < ApplicationPolicy def update? record.user_id == user.id || admin_types.include?(user.type) end end
If we check this in the browser, it works!