Skip to content

Result objects

Build-in

class CreateUser
  include Teckel::Operation

  # Shortcut for
  # result Teckel::Operation::Result
  result!

  input  Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
  output Types.Instance(User)
  error  Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))

  def call(input)
    user = User.new(name: input[:name], age: input[:age])
    if user.save
      success!(user)
    else
      fail!(message: "Could not save User", errors: user.errors)
    end
  end
end

A success call:

result = CreateUser.call(name: "Bob", age: 23)
result
#=> #<Teckel::Operation::Result:<...>>

result.successful?
#=> true

result.failure?
#=> false

result.success
#=> #<User:<...> @age=23, @name="Bob">

A failure call:

result = CreateUser.call(name: "Bob", age: 10)
result
#=> #<Teckel::Operation::Result:<...>>

result.successful?
#=> false

result.failure?
#=> true

result.failure
#=> {:errors=>[{:age=>"underage"}], :message=>"Could not save User"}

result.success do |value|
  # do something with the error value
  puts value[:message]
  # return something useful
  value[:errors]
end
  Could not save User
#=> [{:age=>"underage"}]

Custom

You can use your own result object. If you plan using your operation in a Chain, the should implement the interface defined in Teckel::Result.

require 'time'

class MyResult
  include Teckel::Result
  def initialize(value, success, opts = {})
    @value, @success, @opts = value, (!!success).freeze, opts
  end

  # implementing Teckel::Result
  attr_reader :value

  # implementing Teckel::Result
  def successful?
    @success
  end

  def at
    @opts[:at]
  end
end

class CreateUserOtherResult
  include Teckel::Operation

  result MyResult
  result_constructor ->(value, success) { MyResult.new(value, success, at: Time.now) }

  input  Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
  output Types.Instance(User)
  error  Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))

  def call(input)
    user = User.new(name: input[:name], age: input[:age])
    if user.save
      success!(user)
    else
      fail!(message: "Could not save User", errors: user.errors)
    end
  end
  finalize!
end

result = CreateUserOtherResult.call(name: "Bob", age: 23)
result
#=> #<MyResult:<...>
  @opts={:at=><time>}, 
  @success=true,
  @value=#<User:<...>>

result.at
#=> <time>

result.successful?
#=> true

result.failure?
#=> false

result.value
#=> #<User:<...> @age=23, @name="Bob">

Dry-Monads

require 'dry/monads'

# we need some glue code to make them work with Chains
class DryResult
  include Teckel::Result
  include Dry::Monads[:result]

  def initialize(value, success)
    @value, @success = value, (!!success).freeze
  end

  # implementing Teckel::Result
  attr_reader :value

  # implementing Teckel::Result
  def successful?
    @success
  end

  def to_monad
    @success ? Success(value) : Failure(value)
  end
end

class CreateUserDry
  include Teckel::Operation

  result DryResult
  result_constructor :new

  input  Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
  output Types.Instance(User)
  error  Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))

  def call(input)
    user = User.new(name: input[:name], age: input[:age])
    if user.save
      success!(user)
    else
      fail!(message: "Could not save User", errors: user.errors)
    end
  end
  finalize!
end

result = CreateUserDry.call(name: "Bob", age: 23)
result.to_monad
#=> Success(#<User:<...> @name="Bob", @age=23>)

Last update: 2021-09-28 15:32:53