Module: Flows::Result::Do

Includes:
Util::InheritableSingletonVars::IsolationStrategy.make_module( MOD_VAR_NAME => -> { Module.new } )
Defined in:
lib/flows/result/do.rb

Overview

Do-notation for Result Objects.

This functionality aims to simplify common control flow pattern: when you have to stop execution on a first failure and return this failure. Do Notation inspired by Do Notation in dry-rb and Haskell do keyword.

Sometimes you have to write something like this:

class Something
  include Flows::Result::Helpers

  def perform
    user_result = fetch_user
    return user_result if user_result.err?

    data_result = fetch_data
    return data_result if data_result.err?

    calculation_result = calculation(user_result.unwrap[:user], data_result.unwrap)
    return calculation_result if user_result.err?

    ok(data: calculation_result.unwrap[:some_field])
  end

  private

  def fetch_user
    # returns Ok or Err
  end

  def fetch_data
    # returns Ok or Err
  end

  def calculation(_user, _data)
    # returns Ok or Err
  end
end

The main idea of the code above is to stop method execution and return failed Result Object if one of the sub-operations is failed. At the moment of failure.

By using Do Notation feature you may rewrite it like this:

class SomethingWithDoNotation
  include Flows::Result::Helpers
  extend Flows::Result::Do # enable Do Notation

  do_notation(:perform) # changes behaviour of `yield` in this method
  def perform
    user = yield(fetch_user)[:user] # yield here returns array of one element
    data = yield fetch_data # yield here returns a Hash

    ok(
      data: yield(calculation(user, data))[:some_field]
    )
  end

  # private method definitions
end

do_notation(:perform) makes some wrapping here and allows you to use yield inside the perform method in a non standard way: to unpack results or instantly leave a method if a failed result was provided.

How to use it

First of all, you have to include Flows::Result::Do mixin into your class or module. It adds do_notation class method. do_notation accepts method name as an argument and changes behaviour of yield inside this method. By the way, when you are using do_notation you cannot pass a block to modified method anymore.

class MyClass
  extend Flows::Result::Do

  do_notation(:my_method_1)
  def my_method_1
    # some code
  end

  do_notation(:my_method_2)
  def my_method_2
    # some code
  end
end

yield in such methods is working by the following rules:

ok_result = Flows::Result::Ok.new(a: 1, b: 2)
err_result = Flows::Result::Err.new(x: 1, y: 2)

# the following three lines are equivalent
yield(ok_result)
ok_result.unwrap
{ a: 1, b: 2 }

# the following three lines are equivalent
yield(:a, :b, ok_result)
ok_result.unwrap.values_at(:a, :b)
[1, 2]

# the following three lines are equivalent
return err_result
yield(err_result)
yield(:x, :y, err_result)

As you may see, yield has two forms of usage:

  • yield(result_value) - returns unwrapped data Hash for successful results or, in case of failed result, stops method execution and returns failed result_value as a method result.
  • yield(*keys, result_value) - returns unwrapped data under provided keys as Array for successful results or, in case of failed result, stops method execution and returns failed result_value as a method result.

How it works

Under the hood Flows::Result::Do creates a module and prepends it to your class or module. Invoking do_notation(:method_name) adds special wrapper method to the prepended module. So, when you perform call to YourClassOrModule#method_name - you're executing wrapper in the prepended module.

Since:

  • 0.4.0

Defined Under Namespace

Modules: Util

Constant Summary collapse

MOD_VAR_NAME =

Since:

  • 0.4.0

:@flows_result_module_for_do
SingletonVarsSetup =

Since:

  • 0.4.0

::Flows::Util::InheritableSingletonVars::IsolationStrategy.make_module(
  MOD_VAR_NAME => -> { Module.new }
)

Instance Method Summary collapse

Instance Method Details

#do_notation(method_name) ⇒ Object

Since:

  • 0.4.0



165
166
167
168
169
# File 'lib/flows/result/do.rb', line 165

def do_notation(method_name)
  prepended_mod = Util.fetch_and_prepend_module(self)

  Util.define_wrapper(prepended_mod, method_name)
end