Fat models and large actions have always been a problem for Rails developers, service objects came to help us but sometimes it feels like we just move the bad and hard to follow the code from controllers to plain ruby objects. But Waterfall gem seems to solve the problem. Let’s check it out.


A waterfall is a new approach to service objects. The idea behind it is being functional: you can pass blocks and other waterfall objects one to another chaining them easily. If any of them fails (gets ‘dammed’ as it’s called here), the whole waterfall fails and can be rolled back by transactions or manually using the #reverse flow method.

The average waterfall service looks like this:

The best thing about this approach is that it allows us to create some basic service objects and combine them the way we want to. As for the controller, it looks like this:

You can add as many #chain methods as you want of course, but we try to keep it simple for controllers.

Case 1 — Nested attributes and around callbacks

Let’s take a look at the example. What we wanted to do is to get rid of the nested attributes and update passengers of a booking (enquiry) separately from the booking itself. We also had to do some tracking before any updates and right after them. Finally, we needed the tracking to be optional.

If we didn’t have to to get rid of the nested attributes, it would be easy just to make some around_update callback for a booking and use nested attributes. But that gives us a hard time trying to get an older state of the object because the around_update callback would assign attributes right away. Finally, we would have to think of the way to skip the callback in some cases and there’s currently no clean solution for this in Rails. And all this has to be done for the sake of a simple tracking, isn’t that overcomplicated already?

So we decided to make separate service objects for each step and chain them all together. The update action now looks like this:

and the UpdateEnquiry service:

As you can see, we can easily use the #chain method inside the blocks as well. Tracking became much easier – now we just pass an enquiry we want to track and some block to update it or its associations. It’s like an around_update callback but it’s playing by our rules and it’s up to us whether we are going to use it. Here’s the code for TrackEnquiryUpdate:

We’ve decided to use Struct for service objects which have no initialize logic, that makes them much slimmer. Notice the #reverse_flow method that allows us to implement a custom rollback in case the waterfall is dammed.

19 Ruby on Rails Gems which Can Amaze

I’ll cover it out in the next example.

Case 2 — Payments and rollbacks

Payments. We all been there: sometimes API returns an error after creating a payment in a database, other times we enroll a payment but fail to save it to a database. Waterfall helps to ditch these issues as well.

How to Choose a Payment Platform for Your Project: PayPal, Stripe, Braintree

In controller we have this:

and the Enroll payment waterfall:

Notice how we separate API calls and database transactions. Having them in separate waterfalls allows us to apply custom rollbacks for them. with_transaction block will take care of database rollbacks, for API rollback we’ll have to take a look at the ChargeStripe waterfall:

The #reverse_flow is our custom rollback and it will only be executed if the current waterfall finished its job and the parent waterfall got dammed later. So in case we get a database error or the GetStripeBalanceTransaction waterfall will be dammed, the ChargeStripe waterfall will refund the payment made earlier.


The waterfall is a great new way to implement service objects and clear out your controllers and models from the code that doesn’t belong there. Now services look like a clear sequence of actions, not just some kind of a rubbish dump for all your scary code 🙂

How useful was this post?

Click on a star to rate it!

Average rating 4.8 / 5. Vote count: 16

No votes so far! Be the first to rate this post.

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?


CMO at Rubyroid Labs

Write A Comment

https://ftp.scala-sbt.org slot gacor https://rumblr.zedge.net game slot online Slot88 https://olympics.cybintsolutions.com slot deposit pulsa tanpa potongan