Share This Article
The strategy pattern in Ruby is one of the most common design patterns. It’s a very simple concept, yet it’s very powerful.
Basically, the strategy pattern is about encapsulating an algorithm in a class and making it interchangeable.
Sorting Algorithms As an Example
A sorting algorithm is a great example of something that can be implemented as a strategy.
You could have a Sorter
class with a sorting algorithm as a strategy. The algorithm could be interchangeable, meaning that you could use different algorithms for different situations.
Example:
class Sorter
def initialize(strategy)
@strategy = strategy
end
def sort(list)
@strategy.sort(list)
end
end
class QuickSort
def sort(list)
if list.length <= 1
list
else
pivot = list[list.length / 2]
list.delete_at(list.length / 2)
less_than = list.select { |n| n < pivot }
greater_than = list.select { |n| n > pivot }
sort(less_than) + [pivot] + sort(greater_than)
end
end
end
sorter = Sorter.new(QuickSort.new)
p sorter.sort([3,2,1])
# [1,2,3]
In this example, I’m using the quick sort algorithm.
But I could replace it with any other sorting algorithm without changing the Sorter
class.
The Sorter
class is completely decoupled from the sorting algorithm itself.
A Real-World Example
A common use case of the strategy pattern is when you have a class that accepts different strategies as a dependency.
Here’s a real-world example of this.
A few months ago, I worked on a project where I had to use the Stripe API to process payments.
To do that, I decided to use the Onfido API to verify the customer’s identity.
There are many ways to verify someone’s identity; you can use their social security number, driver’s license, or passport.
It depends on your application’s context and what data you have available.
I decided to implement this using the strategy pattern to easily add new verification strategies in the future needed.
The Onfido API
First, let’s look at the Onfido API.
Basically, you have to create a document resource, and then you can upload any type of document to that resource.
The Stripe API
With Stripe, you first create a customer resource and then you can create a card resource for that customer.
Verification Strategies
I decided to create a verification strategy for every type of document that I wanted to verify.
Example:
class SSNStrategy
def call(customer, options)
document = customer. stripe_document
document. upload(file: options[:file])
document. create_ssn_resource
end
end
class PassportStrategy
def call(customer, options)
document = customer. stripe_document
document. upload(file: options[:file])
document. create_passport_resource
end
end
class DriversLicenseStrategy
def call(customer, options)
document = customer. stripe_document
document. upload(file: options[:file])
document. create_drivers_license_resource
end
end
As you can see, these strategies are very similar.
They all start by uploading a file to the document resource and creating a specific resource (ssn, passport, drivers_license).
The only thing that changes is the name of the method being called at the end.
The VerificationService
Then I created a verification service that takes a strategy and an options hash as input.
The options hash is where I pass the file that needs to be uploaded.
class VerificationService
def initialize(strategy, options)
@strategy = strategy
@options = options
end
def call
@strategy. call(customer, @options)
end
end
The VerificationController
Finally, I created a verification controller that uses the VerificationService.
class VerificationController < ApplicationController
def create
strategy = SSNStrategy.new
service = VerificationService.new(strategy, verification_params)
service.call
render json: {}, status: :created
rescue Stripe::InvalidRequestError => e
render json: { error: e.message }, status: :unprocessable_entity
end
private
def verification_params
params.require(:file)
end
end
Adding a New Strategy
I could do that without changing the existing code if I ever needed to add a new strategy.
All I would have to do is create a new strategy & then pass that strategy to the verification service.
For example, if I wanted to add a new strategy for verifying by visa, I could do that like this.
class VerifiedByVisaStrategy
def call(customer, options)
document = customer. stripe_document
document. upload(file: options[:file])
document. create_verified_by_visa_resource
end
end
class VerificationController < ApplicationController
def create
strategy = VerifiedByVisaStrategy.new
service = VerificationService.new(strategy, verification_params)
service.call
render json: {}, status: :created
rescue Stripe::InvalidRequestError => e
render json: { error: e.message }, status: :unprocessable_entity
end
private
def verification_params
params.require(:file)
end
end
As you can see, the only thing changing is the strategy used.
The existing code doesn’t need to be changed at all.
Summary
This article taught you the strategy pattern and how to use it in ruby.
You’ve also seen a real-world example of how the strategy pattern can be used to decouple code and make it more flexible.
If you have any questions, feel free to leave a comment below.