Enough has been written already about service objects in Rails. They help us to keep our controllers skinny, to separate business logic and to test more easily.
We can identify two types of service objects: - “Fire and forget” ones. We execute them, but we don’t care about their return value or if they failed. For instance: a service object which sends a welcome email to a new user. - Service with an outcome. We want to known if they succeeded or not, their return value and if there was any error.
The latter were giving me headaches. Should they return a hash object? If so, which keys should I use to maintain consistency between all projects? Should they return a struct object instead?
That’s why we tried to standardise our service objects with this small gem: https://github.com/welaika/simpleserviceobject
Let’s see an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class LendMoney < SimpleServiceObject::Base
def call(lender, borrower, amount)
if lender.balance >= amount
lender.withdrawal(amount)
borrower.deposit(amount)
else
errors.push("#{lender.name} has not enough money to borrow!")
end
generate_transaction_id
end
def generate_transaction_id
SecureRandom.uuid
end
end
Let’s suppose Alice balance is only € 200.
1
2
3
4
5
result = LendMoney.call(alice, bob, 500.00)
result.success? # => false, because `#errors` contains errors
result.failure? # => true
result.errors # => ["Alice has not enough money to borrow!"]
result.value = "2d931510-d99f-494a-8c67-87feb05e1594" # It's the return value of `#call`
Now let’s suppose Alice balance is € 700.
1
2
3
4
5
result = LendMoney.call(alice, bob, 500.00)
result.success? # => true, because `#errors` does not contain errors
result.failure? # => false
result.errors # => []
result.value = "bad85eb9-0713-4da7-8d36-07a8e4b00eab" # It's the return value of `#call`
This simple interface is enough for our typical service object.
simple_service_object
gem is so small that you can copy/paste the code in your project and change it to suit your needs.
If you are interested in a fully fledged gem with arguments validation, callbacks and service objects composition, then take a look at active_interaction gem.