devsigner / result_command

A SuperSimple Chainable Command like Monad/Result

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Functionnal world with Result::Command

What is Result::Command?

Command partern ? Like SimpleCommand

not only

It's an object with very simple API based on its state like SimpleCommand:

  • .success?
  • .failure?
o = MyCommand.call(:a)

o.success? # => true
o.failure? # => false
o.result # => :a

But It's container, like Monad (Result)?!

Using Result::Command as an atomiq object. We can easily implement Monad Result with Result::Command and used it like a container:

Success[:a]
# => #<Success @content=:a, @called=true, @result=:a>

Success[:a].success? # => true
Success[:a].failure? # => false
Success[:a].result # => :a

Failure[:b]
# => #<Failure @content=:b, @called=true, @result=nil>

Failure[:b].success? # => false
Failure[:b].failure? # => true
Failure[:b].result # => :b

Result::Failure[42].with_errors(
  Result::Errors.build(
    source: 'test',
    details: { base: ['something went wrong'] }
  )
)
# => #<Result::Failure 
#        @content=42, 
#        @called=true, 
#        @errors=#<Result::Errors @source="test", @errors={ base: ['something went wrong'] }>, 
#        @result=42>

Better, Faster, Stronger with Chainable!

Chainable add some chainable methods;

  • then (or alias |)
Result::Params[:h] | MyCommande | MySecondCommande
=> #<MaSecondCommande @params="h", @called=true, @result="hello">
Result::Params[:h] | MyCommande | MyFaildCommand | MySecondCommande
=> #<MyFaildCommand @content="h", @called=true, @result="h">

with lambda

Result::Params[:hello].
  then(->(input) { Result::Success[input.to_s] }).
  then(->(input) { Result::Success[input + ' world'] })
=> #<Result::Success @content="hello world", @called=true, @result="hello world">
  Result::Params[{ user: { first_name: 'John', last_name: 'Dow' } }]
    .then(
      lambda { |input|
        response = MyClientApi.post('/users', payload: input)

        if response.status == :ok
          Result::Success[response.body]
        else
          Result::Failure[input].with_errors(
            Result::Errors.build(source: self, errors: response.body)
          )
        end
      }
    )
    .then(CreateUserLocaly)

Command

wrap result after running call method

cmd = MyCommand.call(args)

cmd.success? # => true | false
cmd.failure? # => true | false
Callback

use block and callback

  • on_success yield Errors instance
  • on_failure yield result
MyCommand.call(args) do |cmd|
  cmd.on_success do |result|
    # do something
  end

  cmd.on_failure do |errors|
    # or do something else
  end
end

because chain of commands return Command instance, we can use callback :)

when_succeed = ->(result) { # do something with result }
when_fail = ->(errors) { # do something with error }

(Result::Params[:h] | MyCommande | MyFaildCommand | MySecondCommande).
  on_failure(&when_fail).
  on_success(&when_succeed)

or wrap by an other command

class WrapCommands
  prepend Result::Command

  def initialize(input)
    @input = input
  end

  def call
    Params[@input].
      then(MyCommande).
      then(MyFaildCommand).
      then(MySecondCommande).
      on_failure(&when_failed).
      on_success(&when_succeed)
  end

  def when_succeed(result)
    # do something with result 
  end

  def when_failed(errors)
    # do something with error
    errors.merge(errors)
  end
end

About

A SuperSimple Chainable Command like Monad/Result

License:MIT License


Languages

Language:Ruby 100.0%