module Startback::Support::OperationRunner

Support module for high-level architectural components that execute operations as part of their logic, see e.g. Web::Api.

This module contributes a `run` instance method that allows binding an operation with a world, and executing it while supporting around runners.

Example:

class HighLevelComponent
  include Startback::Support::OperationRunner

  def some_method
    # Runs the operation passed after some binding
    run SomeOperation.new
  end

protected

  # Overriden to inject some extra world
  def operation_world(op)
    super(op).merge({ hello: "world" })
  end

  # Execute this around op
  around_run do |op, then_block|
    puts "About to run #{op.inspect}"
    then_block.call
  end

  # SomeClass#call will be called with the operation
  # as first parameter and a block as continuation
  around_run SomeClass.new

end

Public Class Methods

included(by) click to toggle source

When included by a class/module, install the DSL methods

# File lib/startback/support/operation_runner.rb, line 94
def self.included(by)
  by.extend(ClassMethods)
end

Public Instance Methods

run(operation) click to toggle source

Runs `operation`, taking care of binding it and executing hooks.

This method is NOT intended to be overriden. Use hooks and `operation_world` to impact default behavior.

# File lib/startback/support/operation_runner.rb, line 103
def run(operation)
  op_world = operation_world(operation)
  op_bound = operation.bind(op_world)
  _run_befores(op_bound)
  r = _run_with_arounds(op_bound, self.class.send(:arounds))
  _run_afters(op_bound)
  r
end

Protected Instance Methods

operation_world(op) click to toggle source

Returns the world to use to bind an operation.

The default implementation returns an empty hash. This is intended to be overriden by classes including this module.

# File lib/startback/support/operation_runner.rb, line 118
def operation_world(op)
  {}
end

Private Instance Methods

_run_afters(op_bound) click to toggle source
# File lib/startback/support/operation_runner.rb, line 144
def _run_afters(op_bound)
  op_bound.after_call if op_bound.respond_to?(:after_call, true)
end
_run_befores(op_bound) click to toggle source
# File lib/startback/support/operation_runner.rb, line 124
def _run_befores(op_bound)
  op_bound.before_call if op_bound.respond_to?(:before_call, true)
end
_run_with_arounds(operation, arounds = []) click to toggle source
# File lib/startback/support/operation_runner.rb, line 128
def _run_with_arounds(operation, arounds = [])
  if arounds.empty?
    operation.call
  else
    arounder, iexec = arounds.first
    after_first = ->() {
      _run_with_arounds(operation, arounds[1..-1])
    }
    if iexec
      self.instance_exec(operation, after_first, &arounder)
    else
      arounder.call(self, operation, &after_first)
    end
  end
end