Skip to main content

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....
note

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 with not like not_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.