Results
Motivation behind results
Usually, the service exposes a sole public method, let it be call
for this case.
class Service
def call
# ...
end
end
Since Ruby is a dynamic language, call
may return any type of value.
Sometimes it is advantageous, for things like Duck Typing, Abstract Factories, Null Objects, etc.
But occasionally it may also become problematic.
For example, you can not implement a unified way to check whether the value was calculated correctly (as it was intended by its author).
value = service.call
if value.any? # When an array.
if value.cover?(0...100) # Range.
if value.match(/^[0-9]*$/) # Regexp.
if value.dig(:user, :address) # Hash.
if value.nozero? # Integer....
Someone may consider Object#blank? and its opposite Object#present? from ActiveSupport as an attempt, but they detect truthiness in the Rails sense.
Also it is common to forget to verify if the returned value is actually what you need:
hash = service.call
hash.merge(other_hash)
# Expected a merge, but it raises since hash is nil...
Convenient Service suggests utilizing Result objects as a way to overcome the problems listed above.
Practically speaking, to return results from all services.
To express this idea, let's create a different public method instead of call
and name it result
.
class Service
def result
# ...
end
end
Now, we have the following invocation:
result = service.result
if result.success?
result.data
else
result.message
end
So what is a Result? Its benefits?
Result is a data structure that satisfies the following properties:
-
Firstly, it has a consolidated way to detect its state (
success?
,failure?
,error?
, and their counterparts withnot
likenot_success?
). -
Secondly, when the result is successful, you can be 100% sure that its data is correct, no additional verifications are needed (the notion of correctness is defined by the service author).
-
Moveover, results induce you to always check them, before accessing their attributes, otherwise an exception is raised.
-
Also, when something goes wrong, the result contains a description of the reason (
result.message
), which simplifies finding the source of the problem.