Homepage

Chain of Responsibility Pattern in Ruby on Rails

In Ruby, chain of responsibility can help you with more effective optimizing of the legacy code. How? Read on to get a real-life project example, in which we used this method to upgrade the efficiency of code.

While we were working on a project we stumbled upon some legacy code that consists of huge ‘if else’ statements. In this post we’ll show you how we refactored it using the Chain of responsibility pattern.

What is the definition of the Chain of responsibility pattern?

Chain the receiving objects and pass the request along the chain until an object handles it.

– Krzysztof Kempinski, Chain of Responsibility Design Pattern in Ruby

It may be useful if we need to store some data that is not related to any model. Essentially, it’s an object oriented version of if … elsif … elsif … else … end that is much much easier to extend. Because of that we can’t use hashes. Processing objects, aka handlers, contain logic for handling exactly one case.

So where can we use it? For example we could define a chain for handling payments. One handler would take care of payment for one provider such as payU, credit card, bank transfer.

So why did we use it?

We were working with big and messy legacy code. There was a gigant if else statement in one of the services. In our case it was responsible for choosing shipping provider based on selected carrier and shipment. It was taking into account saved api keys for providers, which provider was enabled in the given store and some other stuff. There were a lot of different cases, every case was handled differently, each one of them consisted of about 50-line method.

Another reason

Services are usually used for everything in Rails, which may not always be an ideal solution. This is our old, but good post about basic design patterns. Services may be very helpful when we have a lot of processes to accomplish, but in our opinion they are overused. Also services should be responsible for only one thing, as deciding how to handle something and handling it are two completely different things

Our Idea?

Check which provider supports chosen carrier, then make sure that selected carrier is active for current provider. If not, continue checking.

Implementation

We have created main Chain object, which initializes all handlers and creates a “chain”.

tsx

It’s an interface for all future chains, it initializes handlers and assigns successor to each one of them. As you can see in the code below, successor is just a next handler in the chain.

We’ve also implemented BaseHandler

tsx

BaseHandler is an interface for all future handlers. In initializer, we provide a successor, which defines a next handler of the Chain, which will be executed if condition for the current handler is false (in this case, the carrier name is not passing).

BasicChain and BaseHandler provides pattern how every other Chain should look like. So, while creating a new chain, we need an array of handlers as it defines an order in chain and what handlers are involved in the chain.

tsx

As you can see in the example below, handler returns active provider when a certain condition is met. In any other case it passes all received data to the next handler in the chain.

tsx

Here’s an example of an implementation of a handler which selects FirstProvider if direct integration with this provider is enabled. If not, it selects unifaun if indirect integration enabled.

tsx

Conclusion

There is definitely a lot of room for improvement in our implementation but at the same time it’s a huge step forward compared to the original legacy code. Our solution also may not be perfect in this particular application, as there are not that many conditionals but it’s much easier to read and easier to extend in the future (for example when new providers will be added). Adding new Chains is now really easy, thanks to BasicChain and BaseHandler.

What other solutions would you suggest for our case and why? We’re always happy to learn new things!

Special thanks to the reviewer of this post: Jakub Flasinski.

Let’s Create a Great Website Together

We'll shape your web platform the way you win it!

More posts in this category

  • February 05, 2025 • 10 min read

    API-first CMS: What Options You Have (Web Dev Agency's Take)

    According to the 2024 State of the API Report by Postman, 74% of organizations are adopting API-first strategies. This statistic isn’t just impressive—it signals a major shift in how businesses operate. While API-first approaches have long been a staple in software development, they're now reshaping content management as well. More and more companies are realizing that traditional CMS platforms can't keep up with the demand for flexibility, speed, and seamless integrations.

    READ MORELearn more about api first cms
  • January 23, 2025 • 15 min read

    Best CMS for SaaS: Top Cloud-Based Solutions

    Choosing the right Content Management System (CMS) is a critical decision for your SaaS business. Your unique needs require solutions that are not only flexible, scalable, and user-friendly but also tailored to meet the demands of a fast-paced, customer-focused industry. A CMS should simplify your workflows and help you deliver personalized, high-quality digital experiences.

    READ MORELearn more about best cms for saas top cloud based solutions
  • December 12, 2024 • 10 min read

    We Picked the Best (Headless) CMS for Vue

    Picture a digital experience where content effortlessly flows across platforms, development is agile, and performance is unmatched. By combining the power of Vue.js, a progressive JavaScript framework, with a modern headless CMS, you can achieve all this and more.

    READ MORELearn more about headless cms for vue