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 failedresult_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 failedresult_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.
Defined Under Namespace
Modules: Util
Constant Summary collapse
- MOD_VAR_NAME =
:@flows_result_module_for_do
- SingletonVarsSetup =
::Flows::Util::InheritableSingletonVars::IsolationStrategy.make_module( MOD_VAR_NAME => -> { Module.new } )
Instance Method Summary collapse
Instance Method Details
#do_notation(method_name) ⇒ Object
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 |