Design Patterns

Chain Pattern in Ruby

Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.

Please refer refactoring.guru This post will give you code sample with Ruby.

Problem: imagine that we have a recruitment website which allow candidates to submit their CV whether applying a job or building their own profile. Our system will process their CV such as scanning skills in their CV, matching skills with current jobs in our database for finding the best matched jobs, approving the CV for those jobs in order to send it to our employers.

Now let’s create solution for this problem. Note that there are many ways to do same job. This post just shows how to implement chain design pattern in ruby.

What needs to be processed? That’s CV.

class CV
  attr_accessor :status, :name

  def initialize(status, name)
    @status = status
    @name   = name
  end
end

What are handlers?

class CVHandler
  include Printable
  attr_accessor :status, :next_handler

  def initialize(status)
    @status = status
  end

  def handle(cv)
    if can_handle?(cv.status)
      process(cv)
    elsif next_handler
      next_handler.handle(cv)
    else
      puts "Could not handle cv"
    end
  end

  def can_handle?(status)
    status <= self.status
  end

  def process(cv)
    raise NotImplementedError
  end
end

Now we apply template method pattern. Any new handler will extend above CVHandler class and implement process interface.

class CVUploadHandler < CVHandler
  def initialize
    super(1)
  end

  def process(cv)
    print(cv.name, 'upload')
  end
end

class CVScanHandler < CVHandler
  def initialize
    super(2)
  end

  def process(cv)
    print(cv.name, 'scan')
  end
end

class CVMatchingHandler < CVHandler
  def initialize
    super(3)
  end

  def process(cv)
    print(cv.name, 'match skills')
  end
end

class CVApproveHandler < CVHandler
  def initialize
    super(4)
  end

  def process(cv)
    print(cv.name, 'approve')
  end
end

To handle CV we do these steps: upload -> scan skills -> matching skills with jobs -> approve. Thus, we implement those handlers. Each handler handles a status of CV. We next build chain of handler like single linked list.

class CVHandlerChain
  def self.setup
    uploading = CVUploadHandler.new
    scanning  = CVScanHandler.new
    matching  = CVMatchingHandler.new
    approving = CVApproveHandler.new

    uploading.next_handler = scanning
    scanning.next_handler  = matching
    matching.next_handler  = approving

    uploading
  end
end

uploading is the first node. The CV instance will be traversed over all nodes.

The main function code:

cv_process = CVHandlerChain.setup

cv = CV.new(1, 'Will Nguyen')

(1..5).each do |i|
  cv.status = i
  cv_process.handle(cv)
end

Output:

Will Nguyen CVUploadHandler => upload
Will Nguyen CVScanHandler => scan
Will Nguyen CVMatchingHandler => match skills
Will Nguyen CVApproveHandler => approve

That is an example. In real case we can implement the state pattern. Everytime cv changed state, it automatically execute handler chain.

class CV
  attr_accessor :status, :name

  def initialize(status, name)
    @name       = name
    @cv_process = CVHandlerChain.setup

    set_status(status)
  end

  def set_status(status)
    @status = status
    @cv_process.handle(self)
  end
end

cv = CV.new(1, 'Will Nguyen')
(2..5).each { |status| cv.set_status(status) }

Same output.

Applying Dependency Injection, following Dependency Inversion in S.O.L.I.D. Why not?

def initialize(status, name, chain = CVHandlerChain)
    @name       = name
    @cv_process = chain.setup

    set_status(status)
end
0