bumbleworks/bumbleworks

View on GitHub
lib/bumbleworks/task.rb

Summary

Maintainability
B
4 hrs
Test Coverage
require "bumbleworks/workitem_entity_storage"
require "bumbleworks/task/base"
require "bumbleworks/task/finder"

module Bumbleworks
  class Task
    include WorkitemEntityStorage
    include Support::WrapperComparison

    class AlreadyClaimed < StandardError; end
    class MissingWorkitem < StandardError; end
    class NotCompletable < StandardError; end
    class AvailabilityTimeout < StandardError; end
    class CompletionFailed < StandardError; end

    extend Forwardable
    delegate [:sid, :fei, :fields, :dispatched_at, :params, :participant_name, :wfid, :wf_name] => :@workitem
    attr_reader :nickname, :workitem
    alias_method :id, :sid

    def temporary_storage
      @temporary_storage ||= {}
    end

    class << self
      # @public
      # Autoload all task modules defined in files in the
      # tasks_directory.  The symbol for autoload comes from the
      # camelized version of the filename, so this method is dependent on
      # following that convention.  For example, file `chew_cud_task.rb`
      # should define `ChewCudTask`.
      #
      def autoload_all(options = {})
        if directory = options[:directory] || Bumbleworks.tasks_directory
          Bumbleworks::Support.all_files(directory, :camelize => true).each do |path, name|
            Object.autoload name.to_sym, path
          end
        end
      end

      def method_missing(method, *args, &block)
        if Finder.new.respond_to?(method)
          return Finder.new(self).send(method, *args, &block)
        end
        super
      end

      def find_by_id(sid)
        workitem = storage_participant[sid] if sid
        raise MissingWorkitem unless workitem
        new(workitem)
      rescue ArgumentError => e
        raise MissingWorkitem, e.message
      end

      def storage_participant
        Bumbleworks.dashboard.storage_participant
      end
    end

    def initialize(workitem)
      @workitem = workitem
      unless workitem && workitem.is_a?(::Ruote::Workitem)
        raise ArgumentError, "Not a valid workitem"
      end
      @nickname = params['task']
      extend_module
    end

    def reload
      @workitem = storage_participant[sid]
      self
    end

    # alias for fields[] (fields delegated to workitem)
    def [](key)
      fields[key]
    end

    # alias for fields[]= (fields delegated to workitem)
    def []=(key, value)
      fields[key] = value
    end

    def role
      participant_name
    end

    def extend_module
      extend Bumbleworks::Task::Base
      begin
        extend task_module if nickname
      rescue NameError
      end
    end

    def task_module
      return nil unless nickname
      klass_name = Bumbleworks::Support.camelize(nickname)
      klass = Bumbleworks::Support.constantize("#{klass_name}Task")
    end

    def call_before_hooks(action, *args)
      call_hooks(:before, action, *args)
    end

    def call_after_hooks(action, *args)
      call_hooks(:after, action, *args)
    end

    def with_hooks(action, metadata, options = {})
      call_before_hooks(action, metadata) unless options[:skip_callbacks]
      yield
      call_after_hooks(action, metadata) unless options[:skip_callbacks]
    end

    # update workitem with changes to fields & params
    def update(metadata = {}, options = {})
      with_hooks(:update, metadata, options) do
        update_workitem
        log(:update, metadata)
      end
    end

    # proceed workitem (saving changes to fields)
    def complete(metadata = {}, options = {})
      unless completable? || options.fetch(:force, false)
        raise NotCompletable.new(not_completable_error_message)
      end
      with_hooks(:update, metadata, options) do
        with_hooks(:complete, metadata, options) do
          proceed_workitem
          log(:complete, metadata)
        end
      end
    end

    # Token used to claim task, nil if not claimed
    def claimant
      params['claimant']
    end

    # Timestamp of last claim, nil if not currently claimed
    def claimed_at
      params['claimed_at']
    end

    # Claim task and assign token to claimant
    def claim(token, options = {})
      with_hooks(:claim, token, options) do
        set_claimant(token)
        log(:claim)
      end
    end

    # true if task is claimed
    def claimed?
      !claimant.nil?
    end

    # release claim on task.
    def release(options = {})
      current_claimant = claimant
      with_hooks(:release, current_claimant, options) do
        log(:release)
        set_claimant(nil)
      end
    end

    def on_dispatch
      log(:dispatch)
      call_after_hooks(:dispatch)
    end

    def log(action, metadata = {})
      Bumbleworks.logger.info({
        :actor => params['claimant'],
        :action => action,
        :target_type => 'Task',
        :target_id => id,
        :metadata => metadata.merge(:current_fields => fields)
      })
    end

    def to_s(options = {})
      titleize(options)
    end

    def titleize(options = {})
      displayify(:titleize, options)
    end

    def humanize(options = {})
      displayify(:humanize, options)
    end

  private
    def call_hooks(phase, action, *args)
      (Bumbleworks.observers + [self]).each do |observer|
        observer.send(:"#{phase}_#{action}", *args)
      end
    end

    def displayify(modifier, options = {})
      task_name = Bumbleworks::Support.send(modifier, nickname)

      if options[:entity] != false && !(entity_fields = entity_fields(modifier => true)).empty?
        "#{task_name}: #{entity_fields[:type]} #{entity_fields[:identifier]}"
      else
        task_name
      end
    end

    def storage_participant
      self.class.storage_participant
    end

    def update_workitem
      storage_participant.update(@workitem)
      reload
    end

    def proceed_workitem
      storage_participant.proceed(@workitem)
    end

    def set_claimant(token)
      if token && claimant && token != claimant
        raise AlreadyClaimed, "Already claimed by #{claimant}"
      end

      params['claimant'] = token
      params['claimed_at'] = token ? Time.now : nil
      update_workitem
    end
  end
end