Skip to content
This repository has been archived by the owner on Aug 31, 2018. It is now read-only.
Bill Marquette edited this page Aug 27, 2014 · 3 revisions

Workflowable

Workflowable is a gem that allows easy creation of workflows in Ruby on Rails applications. Workflows can contain any number of stages and transitions, and can trigger customizable automated actions as states are triggered.

Installation and Setup

This section will walk through the steps for installing and setting up workflowable.

Installation

To your application's Gemfile, add:

gem 'workflowable'

Run the bundle command, install the migrations, and migrate your database:

bundle install
rake workflowable:install:migrations
rake db:migrate

Admin Interface

In order to create/modify workflows through the web interface, you'll need to add a entry to your routes.rb file:

mount Workflowable::Engine => "/workflowable", as: :workflowable

This will allow the workflowable interface to be accessed at /workflowable

Authorization

If you'd like to restrict access to the workflowable interface (recommended) one way you can do this is to add a constraint to your routes file. For example, if your user model has a "admin?" method, you could do the use the following instead of the example above:

admin_constraint = lambda do |request|
	request.env['warden'].authenticate? && request.env['warden'].user.admin?
end

constraints admin_constraint do
	mount Workflowable::Engine => "/workflowable", as: :workflowable
end 

Adding Workflow to a Model

Workflows need a model to attach to. For example, maybe you're associating a workflow with a Ticket model. In order to do this, add the following like to the model:

acts_as_workflowable

Workflowable Concepts

This section will give a brief overview of the concepts used by the Workflowable gem.

Workflow: A process, generally with multiple, ordered stages

Stage: A state within the process

Initial stage: The state the workflow starts in

Action: A function that gets run as part of adding a workflow to an item or moving between stages

Before action: An action that gets run when transitioning into a specific stage

After action: An action that gets run when transitioning out of a specific stage

Global action: An action that gets run when between any stage, including moving into the initial stage (i.e. when a result is first flagged)

Creating and Configuring a Workflow

This section will walkthrough the steps needed to create a basic workflow.

Create a New Workflow

  1. Go to the workflowable admin section (/workflowable)
  2. Click "New Workflow"
  3. Give your workflow a name

Adding Stages

We're going to create 4 stages for our workflow: "New", "Investigating", "Complete", and "Not an Issue". While still on the new workflow page (from Step 3 above).

  1. Click "Add a Stage"
  2. Enter "New" for the name of the stage
  3. Repeat for each Stage clicking "Add a Stage" and entering the stage's name.
  4. When all stages have been added, click "Create Workflow"

Configure Transitions

After you click "Create Workflow" you will be atken to a page to set up the allowed transitions between stages. We're going to create a simple workflow that starts with "New", can move to "Investigating", and can then move to either "Complete" or "Not an Issue". We'll also setup the Workflow to allow "Not an Issue" items to be transitioned back to "Investigating".

A workflow must have one initial stage. We'll set "New" as the initial stage.

  1. Use the "Initial Stage" dropdown to select "New".

Except the initial stage, each other stage need to be configure as a potential "next stage" or it won't be possible to transition to it. Let's setup our transitions as described above.

  1. Under "New" click "Add a next step"
  2. In the dropdown select "Investigating"
  3. Under "Investigating" click "Add a next step"
  4. In the dropdown select "Complete"
  5. Again under "Investigating" click "Add a next step"
  6. In the dropdown select "Not an Issue"
  7. Under "Not an Issue" click "Add a next step"
  8. In the dropdown select "Investigating"
  9. Click "Update Workflow"

At this point you have a fully functional workflow! On the view workflow page you should see a diagram showing the stages and transitions that you configured.

Advanced: Add Actions

You can optionally add actions to be run when transitions between stages.

So for example, imagine we're we want the following behavior:

  1. For all workflow transition (eg. "New" to "Investigating" to "Closed", "Investigating" to "Not an Issue", etc.) send out a notification. This will be a Global Action.
  2. When an item is moved into the "Investigating" stage, we want to choose an assignee. This will be a Before Action.
  3. When an item is moving out of the "Investigating" stage, we want the user to add a comment that specifies their findings. This will be an After Action.

In order to follow these steps, you will need to have added the three files discussed in the "Advanced: Develop and Add Actions" section above.

  1. From the workflow list page (/workflowable) click edit next to our workflow.
  2. To create the global workflow, under Global Actions click "Add global action"
  3. Give the action the name "Notification Action"
  4. In the position field input 1. This specifies the order that the global actions will be run (were there more than 1!)
  5. In the action dropdown select "Nofitication Action"

Two fields should appear: "Specify Value: message (required)" and "Default Value: message". The "Specify Value" field allows you to specify, from this interface, what value should be passed in. This allows will ensure the value you specify is alwasy passed as a parameter when the action is run.

The "Default Value" field allows you to specify a default value for the parameter, which may be overridden by the when the transition is run.

  1. In the "Specify Value" field, type "This is the notification!"

Next let's setup the Before Action that would allow choosing an assignee for the item before moving it into the "Investigating" stage.

  1. Under the Investgating stage, click "Add before action" under Before Actions
  2. Give the action the name "Assignee Action"
  3. In the position field input 1
  4. In the action dropdown select "Assignee Action"

For this action's parameter, let's set a default value:

  1. In the "Default Value" field, type "John"

This would cause the default assignee to be "John", but this could be overridden by the user.

Finally, let's setup the After Action that would force the user to add a comment when moving ending the "Investigating" stage.

  1. Under the Investgating stage, click "Add after action" under After Actions
  2. Give the action the name "Comment Action"
  3. In the position field input 1
  4. In the action dropdown select "Comment Action"

We want the user to specify their own comment, so we'll leave both the "Specify Value" and "Default Value" fields blank.

Click "Update Workflow" to save your changes.

Using a Workflow

Now that we've setup a Workflow, we can walk through how we can use it to track the state of a ticket. For this section, we'll walk through the methods that can be used. Developing apppropriate controllers and forms will be left up to you!

In your web application, make sure you workflowable model contains "acts_as_workflowable". This will provide helper methods we need to use the workflow.

  1. Open up the rails console

     rails c
    
  2. Create one of your workflowable objects. For example

     ticket = Ticket.create(summary=>"Test ticket")
    

Currently this ticket will not be associated with a workflow. Let's add it to our test workflow.

  1. Find the workflow you want to use:

     # Find the first workflow, which is the one you created. Note the id and initial_stage_id.
     Workflowable::Workflow.first	
     # We'll assume the following output: 
     # #<Workflowable::Workflow id: 1, name: "Test workflow", initial_stage_id: 1 ... >
    
  2. Set the workflow id that we want to use

     ticket.workflow_id = 1 # The id from above
    

Before we can save the ticket, we need to see if there are any required/optional parameters for the initial stage.

	ticket.next_step_options(1) # The "1" here is the initial stage id from above

This will return a hash with a list of parameters available/required for global, before, and after actions. This is an example of what you might see

{:global=>
	{"Notification Action"=>
		{"message"=>
  			{"required"=>true,
   				"type"=>:text,
   				"description"=>"The contents of the message",
		       	"value"=>"This is the notification"
		    }
		}
	},
 :before=>{},
 :after=>{}
 }

Here we have a hash, with the parameter requried for global, before, and after actions along with some metadata that helps us determine what to pass in. In this case, we can see that the Notification Action has a message parameter which is required. But, in the hash, there is a value set. This means the admin specified the value, so we don't have to do it now. If we tried to specify the value, we would not be allowed to override it.

If the value had been specified as a default value, it would look like this: {:global=> {"Notification Action"=> {"message"=> {"required"=>true, "type"=>:text, "description"=>"The contents of the message", "default"=>"This is the notification" } } }, :before=>{}, :after=>{} }

In this case, we could specify a value to override the default if we wanted.

Since we don't need to specify a value we can just save our ticket and it will perform our actions and set the stage to the initial stage:

ticket.save
# #<Ticket id: 1, stage_id: 1, workflow_id: 1, ... >

We can get more details if we want. For example let's see the details of the current stage.

ticket.stage
# #<Workflowable::Stage id: 1, name: "New", workflow_id: 1, created_at: "2014-08-18 04:20:39", updated_at: "2014-08-18 04:23:45">

Let's check what stages we can move to next.

ticket.stage.next_steps
# [#<Workflowable::Stage id: 2, name: "Investigating", workflow_id: 1, created_at: "2014-08-18 04:20:39", updated_at: "2014-08-18 04:20:39">]

So it looks like we are able to move from the current stage, "New", to "Investigating". This should sound familiar since it is how we setup our Workflow earlier!

Let's see what options we would need to use to move to the "Investigating" stage.

ticket.next_step_options(2)

You should get back a results like this:

{:global=>
	{"Notification Action"=>
		{"message"=>
  			{"required"=>true,
   				"type"=>:text,
   				"description"=>"The contents of the message",
   				"value"=>"This is the notification"
   			}
   		}
   	},
 :before=>
	{"Assignee action"=>
		{"assignee"=>
  			{"required"=>true,
   				"type"=>:text,
   				"description"=>"This is the assignee",
   				"default"=>"John"
   			}
   		}
   	},
:after=>{}
}

Here we can see there is a global action (same as before) with an already specified parameter, as well as a before action that needs and an assignee with a default option provided. Let's override this value and assigne the ticket to "Cindy" instead. First let's confirm that we need to pass in this parameter. We don't need to run this here, but it will confirm that there are no options that we MUST specify if it returns nil:

ticket.validate_actions(2)
# nil

Let's run this again, but this type let's override the default value of "John" with "Cindy". We'll pass a hash as the second parameter, in the exact same format as before with a value key specified in the assignee hash:

ticket.validate_actions(2, {before: {"Before action notification" =>{message: { value: "Cindy"}}}} )
# nil

Look like this is ok. As you can see, we have the same hash format as the next_step_options method return. We removed all the extra key/values, but we could have passed them as well and they would have been ignored.

Since everything looks good, let's move to the next stage with these options.

ticket.set_stage(2, {before: {"Before action notification" =>{message: { value: "Cindy"}}}} )
# true
ticket
# #<ResultFlag id: 1, stage_id: 2, workflow_id: 1, ...
ticket.stage
# #<Workflowable::Stage id: 2, name: "Investigating" ...

Looks good! We've moved to the investigating stage!

Let's apply this one more time to move the ticket to the "Complete"" status.

ticket.stage.next_steps
#  [#<Workflowable::Stage id: 3, name: "Complete", ...>,
#   #<Workflowable::Stage id: 4, name: "Not an Issue" ...>]
ticket.next_step_options(3)
#{:global=>
#  {"Notification Action"=>
#  {"message"=>
#  {"required"=>true,
#   "type"=>:text,
#   "description"=>"The contents of the message",
#   "value"=>"This is the notification"}}},
#	:before=>{},
#	 :after=>
#	{"Comment after action"=>
#	{"comment"=>
#  	{"required"=>true,
#   	"type"=>:text,
#   	"description"=>"The contents of the message"}}}}

Here we notce that a "comment" parameter is required but not specified. Let's see what happens if we try to change stages without specifying that option.

ticket.set_stage(3)
# [{"Comment after action"=>["comment is required\n"]}]

We got a message indicating that we missed the required parameter. We could have also seen this if we ran the validation_actions method:

ticket.validate_actions(3)
# [{"Comment after action"=>["comment is required\n"]}]

Instead of nil, we get an array of error messages.

Ok, now we know what we need to pass in. Let's complete this ticket for real:

ticket.set_stage(3, {after: {"Comment after action" =>{comment: { value: "Complete!"}}}} )
# true
ticket
# #<ResultFlag id: 1, stage_id: 3, workflow_id: 1, ...
ticket.stage
# #<Workflowable::Stage id: 2, name: "Complete" ...

As you can see, workflowable provides all the information you need to build a workflow and move between stages with custom actions at each step. All you have left to do is build the appropriate forms and controls to allow navigating the workflow!

Workflowable API

By adding "acts_as_workflowable" to a class, Workflowable will add in several methods that can be used to set the stage, determine next steps, determine required options, validate options, etc. This section will discuss the methods available and their use.

acts_as_workflowable Model Methods

This section will disucss the methods inherited by models that include "acts_as_workflowable"

stage

This will return the Workflowable::Stage object for the object's current stage.

Parameters

None

Return Value

The object's current stage (Workflowable::Stage)

Example
ticket.stage # Assume ticket is an instance of an acts_as_workflowable model
# #<Workflowable::Stage id: 2, name: "Investigating", workflow_id: 1,

stage.next_steps

By retrieving the object's current stage we can then call next_steps to determine which stages the object could move into next.

Parameters

None

Return Value

An array with possible next stages (ArrayWorkflowable::Stage)

Example
ticket.stage.next_steps # Assume ticket is an instance of an acts_as_workflowable model
# [#<Workflowable::Stage id: 3, name: "Complete"...>, #<Workflowable::Stage id: 3, name: "Not an Issue" ...>, ...]

workflow

This will return the Workflowable::Workflow object for the object's workflow.

Parameters

None

Return Value

The object's associated workflow (Workflowable::Workflow)

Example
ticket.worklow # Assume ticket is an instance of an acts_as_workflowable model
# #<Workflowable::Workflow id: 2, name: "Task"

workflow.initial_stage

By retrieving the object's workflow we can then get the initial_stage to determine the stages the object should start in. This is necessary if the initial stage may require user specified parameters, but otherwise it not really necesary. (See the Initial Stage Requires User-Specified Options section below)

Parameters

None

Return Value

The stage the object will start on (Workflowable::Stage)

Example
ticket.workflow.initial_stage # Assume ticket is an instance of an acts_as_workflowable model
# [#<Workflowable::Stage id: 1, name: "New"...>

next_step_options

Returns an options hash to indicate which options are available and/or required to change to the indicated stage.

Parameters

next_step (integer) The id of the next stage to move to

options (options hash, optional) The options the user has specified so far

user (devise user object, optional) The user making the change

Return Value

The options for the next stage (Options hash. See Passing Options below)

Example
ticket.next_step_options(3, {}, current_user) # Assume ticket is an instance of an acts_as_workflowable model
# { :global=>{}, :before=>{}, :after=>{}} # No options available

validate_actions

Validates whether, with the provided options we are able to move to the next stage. This provides a mechanism for checking the options before actually changes stages. This is useful if options may be dynamic. For example, imagine an action will create a ticket in an externally ticketing system. If the "Project" we're creating the ticket in changes (because the user changes that value), the options may be different. By using validate actions we can check whether the options are acceptable and, if not, present the user an error.

Parameters

next_step (integer) The id of the next stage to move to

options (options hash, optional) The options the user has specified so far

user (devise user object, optional) The user making the change

Return Value

Array of error messages from the actions.

key: Name of the action with invalid parameters

value: The message provided by the action

Example
ticket.validate_actions(3, {}, current_user) # Assume ticket is an instance of an acts_as_workflowable model
# [{"Notification Action"=>["message is required\n"]}]

set_stage

Move the object into the specified stage.

Parameters

next_step (integer) The id of the next stage to move to

options (options hash, optional) The options the user has specified

user (devise user object, optional) The user making the change

Return Value

On error, an array of error messages from the actions. On success "true".

key: Name of the action with invalid parameters

value: The message provided by the action

Example
# Assume ticket is an instance of an acts_as_workflowable model

# Example 1: Fail because of missing required options
ticket.set_stage(3, {}, current_user)
# [{"Notification Action"=>["message is required\n"]}]

# Example 2: Success
ticket.set_stage(3, {before: {"Notification Action" =>{message: { value: "Hello"}}}}, current_user) 
# true

acts_as_workflowable Action Options Format

In order to allow maximum flexibility, acts_as_workflowable allows Actions to define a schema of options that can/must be passed into the action as part of transitioning to a stage. This section will discuss the format of this data. Your application will need to be able to recognize that options are required (using the methods provided and discussed above) and prompt or send the appropriate values.

Defining Options

Definind options occurs in the implementation of the action. In other words, when an action is developed it will define a set of options that are available and/or required. This is done by creating a class function (self.option) which will return a properly formatted hash.

The options hash has the following attributes:

key: (symbol) A key used to identify the option value. Each option must have a unique key

value: (hash) This will contain information about the option

value[:description] (string) A description of the option

value[:required] (boolean) Is the option required

value[:default] (Any type) A hardcoded default value (can be overridden by workflow admin or user if allowed)

value[:type] (symbol) A type of value that should be passed in the option

value[:choices] (array of hashes with format {id: , value: }, optional)

Note: workflowable supports three types:

  • :boolean
  • :choice
  • Any thing else

The value for type used impacts how the options will appear in the admin interface. Boolean options will use a checkbox, choice options will use a dropdown (with choices pulled from the :choices value), and anything else will result in a text box being displayed.

Other values for type can be used, however in the workflowable admin interface, a text box will be used for input. For any values that a user need to specify at the time that the stage is being changed, your application will need to determine how to gather that input. In these cases other type values could be used to present a different input field to the user. For example, maybe you'd want to use a :date format to indicate to your application to show a date picker.

Example

Below is an exampe of how an action's implementation may define an options hash. This could either be hardcoded into the implementation file, or could be dynamically generated. This hash would specify 3 parameters:

  • :recipients would be a required text parameter

  • :priority would be an optional choice parameter with three choices (High, Medium or Low)

  • :delayed_send would be a required boolean parameter

      OPTIONS = {
      	:recipients => {
            :required=>true,
      	  :type=>:text,
            :description=>"The recipients of the notification"
          },
      	:priority => {
            :required=>false,
      	  :type=>:choice,
      	  :choices=>[{id: 1, value: "High"}, {id: 2, value: "Medium"},{id:3, value: "Low"} ]
            :description=>"The priority of the notification"
      	},
      	:delayed_send => {
            :required=>true,
            :type=>:boolean,
            :default=>false,
            :description=>"Should we delay sending the notification"
      	}
        }
    

Passing Options

When transition an object between stages with actions that allow or require parameters, your application will need to pass the parameters in so Workflowable can send them to the actions that need them. Parameters need to be passed in a specific hash format. This format is documented here.

Determining available options

It is possible to determine what options are available by calling the next_step_options method on the workflowable object (See next_step_options above). This will return an options hash that you can use to determine which options are available. This hash will have three keys:

:global The value of this key will contain hashes for all the global actions (if they have options)

:before The value of this key will contain hashes for all the before actions (if they have options)

:after The value of this key will contain hashes for all the after actions (if they have options)

Each of these three keys will will have values that are hashes. These hashes will be configured as follows:

:key The name of the action (as configured in the workflowable admin page)

:value The hash passed back from the action's options method (Should be in the format discussed in Defining Options above)

Example

Here is an example of a hash that could be returned from the next_step_options method.

{
	:global => {
		"Notification Action" => {
			:message => {
				:required=>true,
				:type=>:text,
				:default=>"Alert!"
				:description=>"The message of the notification"
		}
	},
	:before => {
		"Before Stage Action" => {
			:foo => {
				:required=>false,
				:type=>:boolean,
				:value=>false,
				:description=>"The foo parameter"
		},
		"Before Stage Action 2" => {
			:bar =>  {
				:required=>false,
				:type=>:boolean,
				:value=>true,
				:user_specified=>true,	
				:description=>"The bar value"
			}
		}
	},
	:after => {
		"After Stage Action" => {}
	}
}

This hash would represent:

  • One global action called "Notification Action" with one text parameter (:message), with a default value of "Alert!"
  • Two before actions called "Before Stage Action" and "Before Stage Action 2", both with one boolean parameter (:foo and :bar)
    • The :foo parameter in "Before Stage Action" has a value specified. Here the admin specified the value meaning the user will not be able to override it (and therefore you would not need to pass a value for it). It is include for informational purposes only.
    • The :bar parameter in "Before Sta ge Action 2" has a value specified. Here the user specified the value. This is evident based on the user_specified parameter being set to true. The value is included here so that the interface could default the value to what the user previously provided, but also knows the user can change the value.
  • One after action with no parameters.

The next section discusses how you would pass these parameters to changes stages.

Sending options to workflowable

Once you know which options are available, you'll need to collect the appropriate values from the user (or fill them in if the application can determine them). Options are passed in essentially the same format. The only difference being that your application only needs to specify value, and not the rest of the metadata.

The following functions accept the options hash in this format:

  • next_step_options
  • validate_actions
  • set_stage
Example
# Find the options for the next stage (3)
ticket.next_step_options(3) # Assume ticket is an instance of an acts_as_workflowable model
# Result: one global action with one required parameter
# {:global => {
#		"Notification Action" => {
#			:message => {
#				:required=>true,
#				:type=>:text,
#				:default=>"Alert!"
#				:description=>"The message of the notification"
#		}
#	},
#	:before => { },
#	:after => { }
#	}

# Create an options hash to pass in--this could normally be generated from user-supplied input
options = {:global =>
	{ "Notification Action" => {
		:message=>{
			:value=>"This is the real message"
			}
		}
	}
}

# Ensure the options are valid
ticket.validate_options(3, options, current_user)
# nil  # No errors

# Set the stage
ticket.set_stage(3, options, current_user)
# true
Initial Stage Requires User-Specified Options

Creating a new workflowable object in a situation where the initial stage required the user to specify options requires a special procedure. Since you can't call set_stage on an object that hasn't been created, there is a special virtual attributes added to the model called :workflow_options and :current_user. These can be used to pass the options and user values to workflowable when creating a new object.

Note: this is only needed when creating a workflowable object where the initial stage requires user-specified options.

####### Example

# Create a ticket with will use workflow with id of (1)
ticket = Ticket.new(:workflow_id => 1)
#<Ticket id: nil, ...>

ticket.workflow.initial_stage
# 1

ticket.next_step_options(1) # Get the options for the next stage
# Result: one global action with one required parameter
# {:global => {
#		"Notification Action" => {
#			:message => {
#				:required=>true,
#				:type=>:text,
#				:default=>"Alert!"
#				:description=>"The message of the notification"
#		}
#	},
#	:before => { },
#	:after => { }
#	}

# Create an options hash to pass in--this could normally be generated from user-supplied input
options = {:global =>
	{ "Notification Action" => {
		:message=>{
			:value=>"This is the real message"
			}
		}
	}
}

# Ensure the options are valid
ticket.validate_options(3, options, current_user)
# nil  # No errors

# Set the options
ticket.workflow_options = options
# Set the user
ticket.current_user = user
# Save the ticket
ticket.save
# true

Extending Workflowable

This section will discuss extending workflowable.

Advanced: Develop and Add Actions

Actions are code modules that can be run when moving items between Workflow stages. Actions can be developed to do just about anything--add a comment to a ticket, make API calls to an external system, send out notification emails, etc. There are three types of transition actions:

  • Global actions--Run during every transition
  • Before actions--Run before moving into a specific stage
  • After actions--Run when moving out of a specific stage

All actions are developed the same way: extending Workflowable::Actions::Action and placing the Ruby file in the lib/workflowable/actions folder.

For the purpose of this walkthrough, we will add three actions that we can select and use in the interface (even though they don't actually do anything!)

Create three files:

lib/workflowable/actions/comment_action.rb
lib/workflowable/actions/notification_action.rb
lib/workflowable/actions/assignee_action.rb

For the comment_action.rb file use the following code:

class Workflowable::Actions::CommentAction < Workflowable::Actions::Action

	# This is a name for the action and is how you will identify the action in the web application
	NAME="Comment Action"
	
	# This is a hash of options that can/must be passed moving between stages that will run this action
	# The keys are used to identify the parameters
	# The value for each key specifies options including a description that can be accessed and shown in
	# the web interface, the type of parameter (this can be used to customize forms shown to the user), and	 	# whether the parameter is required in order to transitions to the next stage.
	OPTIONS = {
		:message => {
		:required=>true,
		:type=>:text,
		:description=>"The contents of the message"
		}
	}

	# This function will be run when transitions between stages. This function doesn't do anything
	# except print "Commenting!", but it could do other things!
	def run
		puts "Commenting!"
	end
end

For the notification_action.rb file use the following code:

class Workflowable::Actions::NotificationAction < Workflowable::Actions::Action

	NAME="Notification Action"
	OPTIONS = {
		:message => {
		:required=>true,
		:type=>:text,
		:description=>"The contents of the notification"
		}
	}

	def run
		puts "Notifying!"
	end
end

For the assignee_action.rb file use the following code:

class Workflowable::Actions::AssigneeAction < Workflowable::Actions::Action

	NAME="Assignee Action"
	OPTIONS = {
		:message => {
		:required=>true,
		:type=>:text,
		:description=>"Name of the assignee"
		}
	}

	def run
		puts "Assigning!"
	end
end

Don't forget to restart the application!

Once these files are created, we'll be able to use them in our Workflows. We'll walkthrough the steps to do this in the Advanced: Add Actions section below.

Developing an Action

This section will discuss how to develop a new action that can be used when transitioning between Workflow stages. New actions are ruby classes. They should subclass Workflowable::Actions::Action and be placed in your application's the lib/workflowable/actions folder.

Often, an action will only need to implement one function (run) and set two variables (NAME, OPTIONS). There are, however, other methods used in the base class that, for special cases can be overridden (initialize, self.options, self.validate_options). These methods and variables will be discussed below.

Action Variables
NAME Variable

This value should be defined in the action class and will be used to reference the action from the workflowable admin interface.

####### Example

NAME="Comment Action"
OPTIONS Variable

By default, this value will be used to determine what options are available and if the user is missing and required options. This value should be a hash conforming to the format discussed in Defining Options above.

####### Example

OPTIONS = {
    	:recipients => {
	      :required=>true,
    	  :type=>:text,
	      :description=>"The recipients of the notification"
	    },
    	:priority => {
	      :required=>false,
    	  :type=>:choice,
    	  :choices=>[{id: 1, value: "High"}, {id: 2, value: "Medium"},{id:3, value: "Low"} ]
	      :description=>"The priority of the notification"
    	},
    	:delayed_send => {
	      :required=>true,
	      :type=>:boolean,
	      :default=>false,
	      :description=>"Should we delay sending the notification"
    	}
	  }
Action Methods

There are 4 methods that can be implemented/overloaded in action classes. These will be discussed here.

initialize

The default initializer will use the validate_options method to merge in options passed from the user. If validate_options returns error messages, these will be returned. The function also sets the following values, which can be accessed from the run function.

Note the default initializer will map the passed in values to instance variables which can be accessed from other functions (such as run). For example, @options will contain the passed in options merged with the default/admin specified values, @workflow will contain the workflow object, @object will contain the "acts_as_workflowable" object, etc.

####### Parameters

options (option hash, as discussed in Defining Options section above) The options for the action. Note only the options for the specific action being created/run should be passed in.

workflow (Workflowable::Workflow) The workflow instance for the object being actioned

object (Any, generally ActiveRecord) The "acts_as_workflowable" object being actioned

current_stage (Workflowable::Stage) The stage the object is currently in

next_stage (Workflowable::Stage) The stage the object is moving to

user (Any) The user object that has been passed (generally current_user)

####### Return Value

Array of error messages if any, otherwise nil

self.options

The default self.options function returns the value OPTIONS global. In certain circumstances it may be useful to override this function. (For example, if the options are dynamic based on previously specified options or conditions).

####### Parameters

options (option hash, as discussed in Defining Options section above) The options for the action. Note only the options for the specific action being created/run should be passed in.

workflow (Workflowable::Workflow) The workflow instance for the object being actioned

object (Any, generally ActiveRecord) The "acts_as_workflowable" object being actioned

current_stage (Workflowable::Stage) The stage the object is currently in

next_stage (Workflowable::Stage) The stage the object is moving to

user (Any) The user object that has been passed (generally current_user)

####### Return Value

Option hash, as discussed in Defining Options section above

validate_options

The default validate options function will validate that:

  • All required options are specified
  • No unknown options are specified

If either of these conditions are not met, an array of errors will be returned. This function can be overridden if you'd like to do more strenuous validation of the option values (checking length, type, etc.) or if new options may be added based on previous selections.

####### Parameters

None

####### Return

Array of error messages (Array)

run

The run function performs the action and is called when the stage is being set. This function should only be called after options have been validated and Workflowable has determined that the transition can occur.

####### Parameters

None

####### Return Value

None

Sample Action

Below is a simple action that accepts a single parameter (message) and simply prints the message out to the console.

class Workflowable::Actions::AssigneeAction < Workflowable::Actions::Action

	NAME="Print Action"
	OPTIONS = {
		:message => {
		:required=>true,
		:type=>:text,
		:description=>"Message to print"
		}
	}

	def run
		puts "#{@options[:message]}"
	end
end