Skip to main content
Version: v3.0

Creating a custom role

This tutorial will walk you through adding a custom author role to your application.

Prerequisites

Ensure you have completed the Organization and Roles Tutorial

Data model

The data model for this tutorial is the same as the Organization and Roles Tutorial

Generate a new policy

docker compose run --rm backend "rails generate rhino:policy Author"
info

rails generate is a built in Rails command that will run a generator for you. In this case we are generating a new policy called "author". There are a number of generators available in Rails & Rhino. You can see a list of them by running rails generate without any arguments.

There will now be a new files, app/policies/author_policy.rb with the skeleton of a policy and test/policies/author_policy_test.rb with the skeleton of a policy test.

Edit the policy

app/policies/author_policy.rb
class AuthorPolicy < ::Rhino::ViewerPolicy
def check_author
return false unless auth_owner

# Check if the user is the author of the blog
record.class.where(id: record.id).joins(record.joins_for(:blog)).exists?("blogs.user_id": auth_owner.id)
end

# Can always create (for themselves)
def create?
authorize_action(true)
end

# Can only update if they are the author
def update?
authorize_action(check_author)
end

# Can only destroy if they are the author
def destroy?
authorize_action(check_author)
end

class Scope < ::Rhino::ViewerPolicy::Scope
end
end

Seed the database

Create an additional organization and role for testing.

db/seeds/development/organizations.rb
require_relative "users"

user = User.find_by(email: "test@example.com")
user_other = User.find_by(email: "other@example.com")

# Create sample organizations
org = ["Single User Org", "Multi User Org", "Viewer Org", "Editor Org", "Author Org"].map do |name|
Organization.find_or_create_by!(name:)
end

role_admin = Role.find_or_create_by!(name: "admin")
role_viewer = Role.find_or_create_by!(name: "viewer")
role_editor = Role.find_or_create_by!(name: "editor")
role_author = Role.find_or_create_by!(name: "author")

# The test@example.com user is the only user and admin of the "Single User Org" organization
UsersRole.find_or_create_by!(user:, organization: org[0], role: role_admin)

# The test@example.com and other@example.com are both users and admins of the "Multi User Org" organization
UsersRole.find_or_create_by!(user:, organization: org[1], role: role_admin)

# The test@example.com is a viewer and other@example.com is an admin of the "Viewer Org" organization
UsersRole.find_or_create_by!(user:, organization: org[2], role: role_viewer)
UsersRole.find_or_create_by!(user: user_other, organization: org[2], role: role_admin)

# The test@example.com is an editor and other@example.com is an admin of the "Editor Org" organization
UsersRole.find_or_create_by!(user:, organization: org[3], role: role_editor)
UsersRole.find_or_create_by!(user: user_other, organization: org[3], role: role_admin)

# The test@example.com is an author and other@example.com is an admin of the "Author Org" organization
UsersRole.create!(user: user, organization: org[4], role: role_author)
UsersRole.create!(user: user_other, organization: org[4], role: role_admin)
db/seeds/development/blogs.rb
require_relative "organizations"

user = User.find_by(email: "test@example.com")
user_other = User.find_by(email: "other@example.com")

# Function to generate sample blogs and blog posts
def generate_blogs(user, org)
5.times do
blog = Blog.create!(user_id: user.id, organization: org, title: FFaker::Book.unique.author, category_id: Category.ids.sample)
20.times do
BlogPost.create!(blog_id: blog.id, title: FFaker::Book.unique.title, body: FFaker::Book.unique.description, published: [true, false].sample)
end
end
end

# Only test@example.com authors blogs in the "Single User Org" organization
org = Organization.find_by(name: "Single User Org")
generate_blogs(user, org)

# Both users author blogs in the "Multi User Org" organization
org = Organization.find_by(name: "Multi User Org")
generate_blogs(user, org)
generate_blogs(user_other, org)

# Only other@example.com authors blogs in the "Viewer Org" organization
org = Organization.find_by(name: "Viewer Org")
generate_blogs(user_other, org)

# Only other@example.com authors blogs in the "Editor Org" organization
org = Organization.find_by(name: "Editor Org")
generate_blogs(user_other, org)

# Both test@example.com author blogs in the "Author Org" organization
org = Organization.find_by(name: "Author Org")
generate_blogs(user, org)
generate_blogs(user_other, org)

And add the seed data to the database

docker compose run --rm backend "rails db:seed"

Restart the server

docker compose restart backend

Try the Author Org

Login to the application with test@example.com and password and switch to the "Author Org" organization. Viewing the blogs where the author is test@example.com will allow editing and deleting:

While blogs where the author is not test@example.com will not: