Code Ruby like you build Lego
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2003533/Lego_Clip_Art_5798.jpg)
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.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2003592/lego-plan.jpg)
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.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2003539/Lego_dimensions.svg.png)
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.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2003629/lego-detail.png)
Example
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
Overengineering?
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.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2003692/lego_workmen_harddrive.jpg)
Assemble Service Objects?
class ProcessOrder < Struct.new(:order)
class RollbackTriggerError < StandardError; end
def call
ActiveRecord::Base.transaction do
update_stock = UpdateStock.new(order).call
raise_error unless update_stock.success?
complete_order = CompleteOrder.new(order).call
raise_error unless complete_order.success?
prepare_for_shipment = PrepareForShipment.new(order).call
raise_error unless prepare_for_shipment.success?
end
rescue RollbackTriggerError
@success = false
ensure
self
end
def success?; @success != false; end
def raise_error; raise RollbackTriggerError; end
end
this feels like
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2004860/lego_random.jpg)
Our Goal
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2004861/lego_bricks.jpg)
Process
UpdateStock
CompleteOrder
PrepareForShipment
How to elegantly assemble?
Unix has pipes
Elixir has pipes and streams
What Ruby has to offer?
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2003614/lego-bricks.jpg)
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.
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2003706/ship7.jpg)
Assemble with Waterfall
class ProcessOrder < Struct.new(:order)
include Waterfall
class RollbackTriggerError < StandardError; end
def call
ActiveRecord::Base.transaction do
self
.chain { UpdateStock.new(order) }
.chain { CompleteOrder.new(order) }
.chain { PrepareForShipment.new(order) }
.on_dam { raise RollbackTriggerError }
end
rescue RollbackTriggerError
self
end
end
# 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 outflow.foo and outflow.bar
}
.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!
![](https://s3.amazonaws.com/media-p.slid.es/uploads/187318/images/2003758/the-lego-movie-awesome-e1392309318427.png)
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
- 9,189