require 'workflow'
module Smash
module CloudPowers
# The WorkflowFactory module provides you with a custom, embeddable workflow
# for classes. To do this, it takes a description of the workflow, in
# the form of a Hash and injects it into the class' Singleton/Eigan
# class. It uses the +workflow+ gem to handle the workflow stuff for now
# but after MVP, this will be a roll-our-own implementation, to cut down on
# all dependencies possible.
module WorkflowFactory
# This is the method responsible for injecting the workflow on the instance
# it is called on. After this method is called on an instance of some class,
# you can then access any of the described states as methods on that instance.
#
# Parameters
# * description +Hash+ - Describe the workflow you want to use. The format
# follows the actual Workflow Specification(s) from the gem that you would
# normally write onto the class like normal.
#
# * Example +description+ +Hash+
# * * this description would give you a workflow that starts in the +new+
# state and when the #build!() method was called on the object
# that has this workflow, the state would transition into the +building+
# state. The workflow would then listen for the run!() method
# call, which would progress the state to the +in_progress+ state. Next,
# the workflow would listen for the post_results!() method
# call. When someone or something calls it, the state will progress to
# the final state, +done+, from which no more workflow stuff will happen.
#
# description = {
# workflow: {
# states: [
# { new: { event: :build, transitions_to: :building } },
# { building: { event: :run, transitions_to: :in_progress } },
# { in_progress: { event: :post_results, transitions_to: :done } },
# { done: nil }
# ]
# }
# }
#
# Returns
# * +nil+
#
# Example
# * build the workflow using the description from above
# class Job
# # code code code...
# def insert_my_workflow(description)
# class << build_workflow(description)
# end
# end
#
# Which would yield this workflow, from the Workflow gem
#
# class Job
# # all the commented lines below are what `WorkflowFactory#inject_workflow()`
# # did for you. These lines don't need to actually be in your class.
#
# # include Workflow
# #
# # workflow do
# # state :new do
# # event :build, :transitions_to => :building
# # end
# # state :building do
# # event :run, :transitions_to => :in_progress
# # end
# # state :in_progress do
# # event :post_results, :transitions_to => :done
# # end
# # state :done
# # end
# end
#
# job = Job.new
# # => #
# job.done?
# # => NoMethodError
# job.insert_workflow(description)
# # => nil
# job.done?
# # => false
# job.current_state
# # => :building
#
# Notes
# * See description_to_s()
# * TODO: There has got to be a better way, so if any of you have suggestions...
# The fact that the eval gets evaluated and invoked in the workflow gem
# is of little comfort, despite how nice the gem is. Long story short,
# be comfortable with what you're doing.
# * see the workflow gem docs and question me if you want some nice ways
# to really use this module. {workflow homepage}[https://github.com/geekq/workflow]
def inject_workflow(description)
workflow_spec_string = description_to_s(description)
begin
self.class.class_eval(workflow_spec_string)
define_singleton_method(:has_workflow?) { true }
rescue Exception => e
define_singleton_method(:has_workflow?) { !!(puts e.backtrace) }
end
end
# Takes a description and turns it into a string that would describe the
# workflow you want to insert.
#
# Parameters
# * +description+ +Hash+ - of the format
# {
# workflow: {
# states: [
# { state_name: { event: :event_name, transitions_to: :transition_to_name } },
# { state_name: nil }
# ]
# }
# }
#
# Returns
# +String+
#
# Example
# # given the description seen in inject_workflow()
# puts description_to_s(description)
# # =>
# workflow do
# state :new do
# event :build, :transitions_to => :building
# end
# state :building do
# event :run, :transitions_to => :in_progress
# end
# state :in_progress do
# event :post_results, :transitions_to => :done
# end
# state :done
# end
# Notes
# * See inject_workflow()
def description_to_s(description)
description_string_builder = ['include Workflow', 'workflow do']
description[:workflow][:states].each do |state|
state.map do |name, state_description|
if state_description.nil? # if this is a final state...
description_string_builder << "state :#{name}"
else # because it is not a final state, add event information too.
description_string_builder.concat([
"state :#{name} do",
"event :#{state_description[:event]}, transitions_to: :#{state_description[:transitions_to]}",
"end"
])
end
end
end
description_string_builder << "end\n"
description_string_builder.join("\n")
end
end
end
end