Policy Objects

Posted Over 7 years ago. Visible to the public.

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.

class 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
class 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
class 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.

Alexander M
Last edit
Over 7 years ago
Alexander M
Posted by Alexander M to Ruby and RoR knowledge base (2016-11-23 13:46)