Module: Teckel::Chain

Defined in:
lib/teckel/chain.rb

Overview

Railway style execution of multiple Operations.

  • Runs multiple Operations (steps) in order.

  • The output of an earlier step is passed as input to the next step.

  • Any failure will stop the execution chain (none of the later steps is called).

  • All Operations (steps) must behave like Teckel::Operation::Results and return a result object like Teckel::Result

  • A failure response is wrapped into a Teckel::Chain::StepFailure giving additional information about which step failed

Examples:

Defining a simple Chain with three steps

class CreateUser
  include ::Teckel::Operation::Results

  input  Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer.optional)
  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.safe
      success!(user)
    else
      fail!(message: "Could not safe User", errors: user.errors)
    end
  end
end

class LogUser
  include ::Teckel::Operation::Results

  input Types.Instance(User)
  output input

  def call(usr)
    Logger.new(File::NULL).info("User #{usr.name} created")
    usr # we need to return the correct output type
  end
end

class AddFriend
  class << self
    # Don't actually do this! It's not safe and for generating the failure sample only.
    attr_accessor :fail_befriend
  end

  include ::Teckel::Operation::Results

  input Types.Instance(User)
  output Types::Hash.schema(user: Types.Instance(User), friend: Types.Instance(User))
  error  Types::Hash.schema(message: Types::String)

  def call(user)
    if self.class.fail_befriend
      fail!(message: "Did not find a friend.")
    else
      { user: user, friend: User.new(name: "A friend", age: 42) }
    end
  end
end

class MyChain
  include Teckel::Chain

  step :create, CreateUser
  step :log, LogUser
  step :befriend, AddFriend
end

result = MyChain.call(name: "Bob", age: 23)
result.is_a?(Teckel::Result)          #=> true
result.success[:user].is_a?(User)    #=> true
result.success[:friend].is_a?(User)  #=> true

AddFriend.fail_befriend = true
failure_result = MyChain.call(name: "Bob", age: 23)
failure_result.is_a?(Teckel::Chain::StepFailure) #=> true

# additional step information
failure_result.step_name                        #=> :befriend
failure_result.step                             #=> AddFriend

# otherwise behaves just like a normal +Result+
failure_result.failure?                         #=> true
failure_result.failure                          #=> {message: "Did not find a friend."}

See Also:

Defined Under Namespace

Modules: ClassMethods, InstanceMethods Classes: StepFailure

Class Method Summary collapse

Class Method Details

.included(receiver) ⇒ Object



177
178
179
180
181
182
183
184
# File 'lib/teckel/chain.rb', line 177

def self.included(receiver)
  receiver.extend         ClassMethods
  receiver.send :include, InstanceMethods

  receiver.class_eval do
    @steps = []
  end
end