KatPadi's Point

Policy Object Adventures

A few months back, I wrote something about my service object adventures to handle extraction of a business logic from a Rails model or controller. Recently, I got introduced and experimented on the concept of policy object.

Policy objects are pretty much just like service objects. In fact, from what I understand, policy object is actually a service object. The only difference is that policy objects encapsulate operations that exclusively return boolean values. It should describe only whether the subject passed the policy or not.

I recreated my very first policy implementation and structure by creating an oversimplified example here https://github.com/katpadi/adventures_in_policies.

This is the base class for my policy:

class VotingPolicy
  attr_reader :voter, :policies, :args

  def initialize(voter, args = {})
    @voter = voter
    @args = args
  end

  def passed?
    policies = VotingPolicyFactory.build(voter, args: args)
    policies.each do |policy|
      return false unless policy.passed?
    end
    true
  end
end

This base class just loops through the result of the factory and checks if a “policy” is violated by the “voter”.

Here’s a simple factory implementation that builds an array of policy instances:

class VotingPolicyFactory
  class << self
    def build(voter, args: {})
      policies = []
      policies << common_policies
      policies << args[:custom_policies] if args[:custom_policies].present?

      # instantiate the policy classes
      policies.flatten.each_with_object([]) do |policy, instances|
        instances << policy.new(voter, args)
      end
    end

    def common_policies
      [
        Policies::AgeVotingPolicy,
        Policies::ResidencyVotingPolicy,
        Policies::DisqualificationVotingPolicy
      ]
    end
  end
end

Basically, the factory just creates the default policy objects needed.

Here are some subclasses for the base policy class:

module Policies
  class AgeVotingPolicy < VotingPolicy
    attr_reader :voter, :args

    def passed?
      voter.age >= 18
    end
  end
end
module Policies
  class ResidencyVotingPolicy < VotingPolicy
    attr_reader :voter, :args

    def passed?
      (Date.today - 1.year) >= voter.resident_since
    end
  end
end
module Policies
  class DisqualificationVotingPolicy < VotingPolicy
    attr_reader :voter, :args

    def passed?
      insane_or_incompetent?
    end

    private

    def insane_or_incompetent?
      # Randomly insane/incompetent
      [true, false].sample
    end
  end
end

After setting it up like this, the only call that I need to do in the interface is:

# Assuming we have a voter variable that has all the voter's attributes
# voter = Voter.new('Kat Padilla', 20)

# This is the only thing that I need to do!!!
VotingPolicy.new(voter).passed?

This call will already determine if the “voter” passed all the “policies” that I included in the factory.

As a bonus fun approach, a colleague told me that it’s also better if an optional or custom policy may be passed. In the future, for instance, a random policy is needed, they don’t have to dig deeper and edit the existing codes. They just have to add a policy object and pass it in the public method.

VotingPolicy.new(voter, custom_policies: Policies::AlwaysFalsePolicy).passed?

This call will loop through all the default policies plus the custom policy passed.

I’m not sure if this is the best way to structure it but so far, I’m conveniently exercising this approach.

So, that’s it for my policy object adventures!

1 comment for “Policy Object Adventures

  1. fan of popspadi
    November 2, 2015 at 11:31 am

    araykobeh, ang galing nyo poh!

Leave a Reply

Your email address will not be published. Required fields are marked *