Code Ruby like you build Lego

There is a version with refactoring example here

What do I mean?

Every Lego is an assembly of bricks.


Every piece of software is an assembly of parts.

Ruby Bricks?

The Service Object pattern lets you define classes which represent the parts of your business logic.


By convention each Service Object is named out of a verb: it's an action.


Service Objects are bricks.

What is a service object?

It's a simple Ruby class.


It must have a precise, action name (LaunchProject, CreateContact)


It's scope is usually limited: specialised object, reusable and easy to test.



You want to process an order.

Steps are:

- remove line items from stock

- complete order

- prepare for shipment


=> Service Objects represent business logic


Service Objects are:

- UpdateStock

- CompleteOrder

- PrepareForShipment


You might wonder what is the point to create an object like CompleteOrder.


This object has something models lack: Context.


No more (conditional) callbacks.

All logic gathered in its own (relevant!) box.

No more fat models nor controllers.

Assemble Service Objects?

class ProcessOrder <

  class RollbackTriggerError < StandardError; end

  def call
    ActiveRecord::Base.transaction do 
      update_stock =
      raise_error unless update_stock.success?
      complete_order =
      raise_error unless complete_order.success?
      prepare_for_shipment =
      raise_error unless prepare_for_shipment.success?
  rescue RollbackTriggerError
    @success = false

  def success?; @success != false; end

  def raise_error; raise RollbackTriggerError; end

this feels like

Our Goal





How to elegantly assemble?

Unix has pipes

Elixir has pipes and streams


What  Ruby has to offer?

Assemble with Ruby

I've created Waterfall, a dedicated gem, because I needed this feature.


It keeps on chaining service objects when everything goes well.


It stops the assembly if something goes wrong.

Assemble with Waterfall

class ProcessOrder <

  include Waterfall

  class RollbackTriggerError < StandardError; end

  def call
    ActiveRecord::Base.transaction do 
        .chain  {   }
        .chain  { }
        .chain  { }
        .on_dam { raise RollbackTriggerError    }
  rescue RollbackTriggerError

# we assume all services include Waterfall

Chaining API

.chain(:foo) { 
  # add `foo` to local outflow, which value will 
  # be what the block returns

.chain(bar: :baz) {
  # block returns a waterfall which exposes `baz`
  # and we rename it `bar` in the local outflow

.chain { |outflow| 
  # outflow is a mere OpenStruct
  # in our example, we could use and

.when_falsy { some_expression }
  .dam { 
    # you can dam with anything you want, but let it be explanatory

.when_truthy { some_other_expression }
  .dam {}

.on_dam {|error_pool|
  # the error pool contains the value returned by a `dam` in the chain
  # or even by any dam from the chained waterfalls

Want to know more?

Check the github repository.


Contains full api details, examples of code, examples of how to spec.


Ping me @apneadiving

Happy Building!

code ruby like you build lego

By Benjamin Roth

code ruby like you build lego

Split your app based on business logic bricks and assemble them

  • 8,930