Policy Objects
The Policy Objects design pattern is similar to Service Objects, but is responsible for read operations while Service Objects are responsible for write operations. Policy Objects encapsulate complex business rules and can easily be replaced by other Policy Objects with different rules. For example, we can check if a guest user is able to retrieve certain resources using a guest Policy Object. If the user is an admin, we can easily change this guest Policy Object to an admin Policy Object that contains admin rules.
Example
Before a user creates a project, the Controller checks whether the current user is a manager, whether they have permission to create a project, whether the number of current user projects is under the maximum, and checks the presence of blocks on the creation of projects in Redis key/value storage. In this case, we can make the Model and Controller skinny by moving this logic to a policy object.
Copyclass CreateProjectPolicy def initialize(user, redis_client) @user = user @redis_client = redis_client end def allowed? @user.manager? && below_project_limit && !project_creation_blocked end private def below_project_limit @user.projects.count < Project.max_count end def project_creation_blocked @redis_client.get('projects_creation_blocked') == '1' end end
Copyclass ProjectsController < ApplicationController def create if policy.allowed? @project = Project.create!(project_params) render json: @project, status: :created else head :unauthorized end end private def project_params params.require(:project).permit(:name, :description) end def policy CreateProjectPolicy.new(current_user, redis) end def redis Redis.current end end
Copyclass User < ActiveRecord::Base enum role: [:manager, :employee, :guest] end
The result is a clean Controller and Model. The policy object encapsulates the permission check logic, and all external dependencies are injected from the Controller into the policy object. All classes do their own work and no one else’s.