class Doable::Job

The Job class is responsible for describing the process of running some set of steps. It utilizes a very specific DSL for defining what steps need executing, along with their order. It can also describe how to recover when things break and provides hooks and triggers to make more flexible scripts for varying environments.

Attributes

handlers[R]
hooks[R]
steps[R]
threads[R]

Public Class Methods

new() { |self| ... } click to toggle source

Yields itself to allow the syntax seen in the plan class method.

# File lib/doable/job.rb, line 24
def initialize
  @hooks    = {}
  @steps    = []
  @handlers = {}
  @threads  = []
  yield self
end
plan(&block) click to toggle source

Allows sequential definition of job steps. @example usage

job = Doable::Job.plan do |j|
  j.before  { log "Starting my awesome job" }
  j.step    { # do some stuff here }
  j.attempt { # try to do some other stuff here }
  j.after   { log "Looks like we're all set" }
end
# File lib/doable/job.rb, line 19
def self.plan(&block)
  self.new(&block)
end

Public Instance Methods

after(options = {}, &block) click to toggle source

Registers an action to be performed after normal execution completes @param options [Hash] @param block [Proc] @return [Boolean]

# File lib/doable/job.rb, line 64
def after(options = {}, &block)
  on(:after, options, &block)
end
attempt(options = {}, &block) click to toggle source

Add a step to the queue, but first wrap it in a begin..rescue WARNING! Exception handlers are __not__ used with these steps, as they never actually raise exceptions @param options [Hash] @param block [Proc] @return [Step] the step just create by this method

# File lib/doable/job.rb, line 73
def attempt(options = {}, &block)
  @steps << Step.new(self, options) do
    begin
      self.instance_exec(&block)
    rescue SkipStep => e
      raise e # We'll rescue this somewhere higher up the stack
    rescue => e
      log "Ignoring Exception in attempted step: #{colorize("#{e.class}: (#{e.message})", :red)}"
    end
  end
  @steps.last # return the last step (the one we just defined)
end
background(options = {}, &block) click to toggle source

Allow running steps in the background @param options [Hash] @param block [Proc] @return [Step]

# File lib/doable/job.rb, line 90
def background(options = {}, &block)
  @steps << Step.new(self, options) do
    @threads << Thread.new { self.instance_exec(&block) }
  end
  @steps.last # return the last step (the one we just defined)
end
before(options = {}, &block) click to toggle source

Registers an action to be performed before normal step execution @param options [Hash] @param block [Proc] @return [Step]

# File lib/doable/job.rb, line 56
def before(options = {}, &block)
  on(:before, options, &block)
end
context() click to toggle source

Returns the binding context of the Job @return [Binding]

# File lib/doable/job.rb, line 115
def context
  binding
end
handle(exception, &block) click to toggle source

Register a handler for named exception @param exception [String,StandardError] Exception to register handler for @param block [Proc]

# File lib/doable/job.rb, line 122
def handle(exception, &block)
  @handlers[exception] = Step.new(self, &block)
end
multitasking?() click to toggle source

Check if background steps are running @return [Boolean]

# File lib/doable/job.rb, line 99
def multitasking?
  return @threads.collect {|t| t if t.alive? }.compact.empty? ? false : true
end
on(hook, options = {}, &block) click to toggle source

Registers a hook action to be performed when the hook is triggered @param hook [Symbol] Name of the hook to register the action with @param options [Hash] @param block [Proc] @return [Step]

# File lib/doable/job.rb, line 37
def on(hook, options = {}, &block)
  @hooks[hook] ||= []
  @hooks[hook] << Step.new(self, options, &block)
  @hooks[hook].last # return the last step (the one we just defined)
end
rollback!() click to toggle source

Trigger a rollback of the entire Job, based on calls to rollback!() on each eligible Step

# File lib/doable/job.rb, line 104
def rollback!
  log "Rolling Back...", :warn
  @hooks[:after].reverse.each {|s| s.rollback! if s.rollbackable? }  if @hooks.has_key?(:after)
  @steps.reverse.each {|s| s.rollback! if s.rollbackable? }
  @hooks[:before].reverse.each {|s| s.rollback! if s.rollbackable? } if @hooks.has_key?(:before)
  log "Rollback complete!", :warn
  raise RolledBack
end
run() click to toggle source

Here we actually trigger the execution of a Job

# File lib/doable/job.rb, line 171
def run
  #merge_config FILE_CONFIG
  #merge_config CLI_CONFIG
  ## Run our defaults Proc to merge in any default configs
  #@defaults.call(@@config)

  # before hooks
  trigger(:before)
  
  # Actual installer steps
  @steps.each_with_index do |step, index|
    begin
      step.call
    rescue SkipStep => e
      step.skip
      log e.message, :warn
    rescue => e
      if @handlers[e.message]
        log "Handling #{e.class}: (#{e.message})", :warn
        @handlers[e.message].call(e, step)
        step.handled
      elsif @handlers[e.class]
        log "Handling #{e.class}: (#{e.message})", :warn
        @handlers[e.class].call(e, step)
        step.handled
      else
        # Check the ancestry of the exception to see if any lower level Exception classes are caught
        e.class.ancestors[1..-4].each do |ancestor|
          if @handlers[ancestor]
            log "Handling #{e.class}: (#{e.message}) via handler for #{ancestor}", :warn
            @handlers[ancestor].call(e, step)
            step.handled
          end # if @handlers[ancestor]
        end
        
        unless step.successful?
          message = "\n\nUnhandled Exception in #{colorize("steps[#{index}]", :yellow)}: #{colorize("#{e.class}: (#{e.message})", :red)}\n\n"
          #if @config.auto_rollback
          #  log message
          #  rollback!
          #else
            raise message
          #end
        end # unless
      end # if @handlers...
    end # rescue
  end # @steps.each_with_index
  
  # after hooks
  trigger(:after)
  
  # bring together all background threads
  unless @threads.empty?
    log "Cleaning up background tasks..."
    @threads.each do |t|
      begin
        t.join
      rescue => e
        # We don't really need to do anything here,
        # we've already handled or died from aborted Threads
      end
    end
  end

  log "All Job steps completed successfully!", :success   # This should only happen if everything goes well
end
step(options = {}, &block) click to toggle source

Adds a step to the queue @param options [Hash] @param block [Proc] @return [Step]

# File lib/doable/job.rb, line 47
def step(options = {}, &block)
  @steps << Step.new(self, options, &block)
  @steps.last # return the last step (the one we just defined)
end
trigger(hook) click to toggle source

This triggers a block associated with a hook @param hook [Symbol] Hook to trigger @return [Boolean] returns true no exceptions are encounter during enumeration of hook steps.

# File lib/doable/job.rb, line 129
def trigger(hook)
  @hooks[hook].each_with_index do |step, index|
    begin
      step.call
    rescue SkipStep => e
      step.skip
      log e.message, :warn
    rescue => e
      if @handlers[e.message]
        log "Handling #{e.class}: (#{e.message})", :warn
        @handlers[e.message].call(e, step)
        step.handled unless step.status == :skipped       # Don't mark the step as "handled" if it was skipped
      elsif @handlers[e.class]
        log "Handling #{e.class}: (#{e.message})", :warn
        @handlers[e.class].call(e, step)
        step.handled unless step.status == :skipped       # Don't mark the step as "handled" if it was skipped
      else
        # Check the ancestry of the exception to see if any lower level Exception classes are caught
        e.class.ancestors[1..-4].each do |ancestor|
          if @handlers[ancestor]
            log "Handling #{e.class}: (#{e.message}) via handler for #{ancestor}", :warn
            @handlers[ancestor].call(e, step)
            step.handled unless step.status == :skipped   # Don't mark the step as "handled" if it was skipped
          end # if @@handlers[ancestor]
        end
        
        unless step.successful?
          message = "\n\nUnhandled Exception in #{colorize("hooks##{hook}[#{index}]", :yellow)}: #{colorize("#{e.class}: (#{e.message})", :red)}\n\n"
          #if @config.auto_rollback
          #  log message
          #  rollback!
          #else
            raise message
          #end
        end # unless
      end
    end # begin()
  end if @hooks[hook] # each_with_index()
  return true
end