Main page background image

Ruby Template Design Pattern


Ihor T.
RoR Developer

Template Design Pattern

If your code always wants to do the same thing except for a particular operation, and that operation sometimes wants to do this and sometimes wants to do that, then you probably need a template method.

To understand the template method and when we can use it, imagine that our first Ruby project is to write a program that obtains an access token from an external token generator API. The mini-program will receive a token we can use later in our program to access APIs or secure resources to get the information we need.

class Token
  def access_token
    response = Net::HTTP.post(url, body, headers)
    json = JSON.parse(response.body)
    json['access_token']
  end

  def url
    URI('https://token-generator-endpoint')
  end

  def body
    {
      # body for the token
    }
  end

  def headers
    { Authorization: '...' }
  end
end

token = Token.new.access_token

So far, so good. It does exactly what we need.

But a new request comes in, and we need different tokens (provider, system, patient). Each of them was assigned a specific set of permissions. For example, with a system token, we can access one resource with the provider, a different one, and so on.

class Token
  def initialize(token_type)
    @token_type = token_type
  end

  def access_token
    response = Net::HTTP.post(url, body, headers)
    json = JSON.parse(response.body)
    json['access_token']
  end

  def body
    case @token_type
    when :patient
      {
        # body for the patient
      }
    when :provider
      {
        # body for the provider
      }
    when :system
      {
        # body for the system
      }
    end
  end

  def url
    URI('https://token-endpoint')
  end

  def headers
    { Authorization: '...' }
  end
end

provider_token = Token.new(:provider).access_token
system_token = Token.new(:system).access_token
patient_token = Token.new(:patient).access_token

The new version might work, but the body looks a little messy, and more importantly, we’ve mixed up code that changes with code that stays the same. Thus, we violate one of the principles of good design.

Separate things that can change

I want to show how we can separate things that remain the same from those that may change. Let’s define an abstract base class with methods we don’t expect to be changed and leave the details to subclasses.

class Token
  def access_token
    response = Net::HTTP.post(url, body, headers)
    json = JSON.parse(response.body)
    json['access_token']
  end

  def body
    raise 'Called abstract method: body'
  end

  def url
    URI('https://token-endpoint')
  end

  def headers
    { Authorization: '...' }
  end
end

class Provider < Token
  def body
    {
      # body for the provider
    }
  end
end

class System < Token
  def body
    {
      # body for the system
    }
  end
end

class Patient < Token
  def body
    {
      # body for the patient
    }
  end
end

provider_token = Provider.new.access_token
system_token = System.new.access_token
patient_token = Patient.new.access_token

Summary

By having inheritance, we create classes related to each other by a common implementation. Every time we change the superclass, there is a good chance that we can change the behavior of the subclasses. If your goal is to build a system where one change doesn’t cause changes to cascade throughout the code, then we probably shouldn’t rely too much on inheritance.

Follow my other articles to see what else we can use to build robust systems.