Git Product home page Git Product logo

workflow's Introduction

Workflow

workflow badge gpa coverage

Note: you can find documentation for specific workflow rubygem versions at http://rubygems.org/gems/workflow : select a version (optional, default is latest release), click "Documentation" link. When reading on github.com, the README refers to the upcoming release.

Workflow is a finite-state-machine-inspired API for modeling and interacting with what we tend to refer to as 'workflow'.

A lot of business modeling tends to involve workflow-like concepts, and the aim of this library is to make the expression of these concepts as clear as possible, using similar terminology as found in state machine theory.

So, a workflow has a state. It can only be in one state at a time. When a workflow changes state, we call that a transition. Transitions occur on an event, so events cause transitions to occur. Additionally, when an event fires, other arbitrary code can be executed, we call those actions. So any given state has a bunch of events, any event in a state causes a transition to another state and potentially causes code to be executed (an action). We can hook into states when they are entered, and exited from, and we can cause transitions to fail (guards), and we can hook in to every transition that occurs ever for whatever reason we can come up with.

Now, all that’s a mouthful, but we’ll demonstrate the API bit by bit with a real-ish world example.

Let’s say we’re modeling article submission from journalists. An article is written, then submitted. When it’s submitted, it’s awaiting review. Someone reviews the article, and then either accepts or rejects it. Here is the expression of this workflow using the API:

class Article
  include Workflow
  workflow do
    state :new do
      event :submit, :transitions_to => :awaiting_review
    end
    state :awaiting_review do
      event :review, :transitions_to => :being_reviewed
    end
    state :being_reviewed do
      event :accept, :transitions_to => :accepted
      event :reject, :transitions_to => :rejected
    end
    state :accepted
    state :rejected
  end
end

Nice, isn’t it!

Note: the first state in the definition (:new in the example, but you can name it as you wish) is used as the initial state - newly created objects start their life cycle in that state.

Let’s create an article instance and check in which state it is:

article = Article.new
article.accepted? # => false
article.new? # => true

You can also access the whole current_state object including the list of possible events and other meta information:

article.current_state
=> #<Workflow::State:0x7f1e3d6731f0 @events={
  :submit=>#<Workflow::Event:0x7f1e3d6730d8 @action=nil,
    @transitions_to=:awaiting_review, @name=:submit, @meta={}>},
  name:new, meta{}

You can also check, whether a state comes before or after another state (by the order they were defined):

article.current_state # => being_reviewed
article.current_state < :accepted # => true
article.current_state >= :accepted # => false
article.current_state.between? :awaiting_review, :rejected # => true

Now we can call the submit event, which transitions to the <tt>:awaiting_review</tt> state:

article.submit!
article.awaiting_review? # => true

Events are actually instance methods on a workflow, and depending on the state you’re in, you’ll have a different set of events used to transition to other states.

It is also easy to check, if a certain transition is possible from the current state . article.can_submit? checks if there is a :submit event (transition) defined for the current state.

gem install workflow

Important: If you’re interested in graphing your workflow state machine, you will also need to install the activesupport and ruby-graphviz gems.

Versions up to and including 1.0.0 are also available as a single file download - [lib/workflow.rb file](https://github.com/geekq/workflow/blob/v1.0.0/lib/workflow.rb).

After installation or downloading the library you can easily try out all the example code from this README in irb.

$ irb
require 'rubygems'
require 'workflow'

Now just copy and paste the source code from the beginning of this README file snippet by snippet and observe the output.

The best way is to use convention over configuration and to define a method with the same name as the event. Then it is automatically invoked when event is raised. For the Article workflow defined earlier it would be:

class Article
  def reject
    puts 'sending email to the author explaining the reason...'
  end
end

article.review!; article.reject! will cause state transition to being_reviewed state, persist the new state (if integrated with ActiveRecord), invoke this user defined reject method and finally persist the rejected state.

Note: on successful transition from one state to another the workflow gem immediately persists the new workflow state with update_column(), bypassing any ActiveRecord callbacks including updated_at update. This way it is possible to deal with the validation and to save the pending changes to a record at some later point instead of the moment when transition occurs.

You can also define event handler accepting/requiring additional arguments:

class Article
  def review(reviewer = '')
    puts "[#{reviewer}] is now reviewing the article"
  end
end

article2 = Article.new
article2.submit!
article2.review!('Homer Simpson') # => [Homer Simpson] is now reviewing the article

Alternative way is to use a block (only recommended for short event implementation without further code nesting):

event :review, :transitions_to => :being_reviewed do |reviewer|
  # store the reviewer
end

We’ve noticed, that mixing the list of events and states with the blocks invoked for particular transitions leads to a bumpy and poorly readable code due to a deep nesting. We tried (and dismissed) lambdas for this. Eventually we decided to invoke an optional user defined callback method with the same name as the event (convention over configuration) as explained before.

Note: Workflow 2.0 is a major refactoring for the worklow library. If your application suddenly breaks after the workflow 2.0 release, you’ve probably got your Gemfile wrong ;-). workflow uses semantic versioning. For highest compatibility please reference the desired major+minor version.

Note on ActiveRecord/Rails 4.*, 5.\* Support:

Since integration with ActiveRecord makes over 90% of the issues and maintenance effort, and also to allow for an independent (faster) release cycle for Rails support, starting with workflow version 2.0 in January 2019 the support for ActiveRecord (4.*, 5.\* and newer) has been extracted into a separate gem. Read at workflow-activerecord, how to include the right gem.

To use legacy built-in ActiveRecord 2.3 - 4.* support, reference Workflow 1.2 in your Gemfile:

gem 'workflow', '~> 1.2'

If you do not use a relational database and ActiveRecord, you can still integrate the workflow very easily. To implement persistence you just need to override load_workflow_state and persist_workflow_state(new_value) methods. Next section contains an example for using CouchDB, a document oriented database.

Tim Lossen implemented support for remodel / redis key-value store.

We are using the compact couchtiny library here. But the implementation would look similar for the popular couchrest library.

require 'couchtiny'
require 'couchtiny/document'
require 'workflow'

class User < CouchTiny::Document
  include Workflow
  workflow do
    state :submitted do
      event :activate_via_link, :transitions_to => :proved_email
    end
    state :proved_email
  end

  def load_workflow_state
    self[:workflow_state]
  end

  def persist_workflow_state(new_value)
    self[:workflow_state] = new_value
    save!
  end
end

Please also have a look at the full source code.

I get a lot of requests to integrate persistence support for different databases, object-relational adapters, column stores, document databases.

To enable highest possible quality, avoid too many dependencies and to avoid unneeded maintenance burden on the workflow core it is best to implement such support as a separate gem.

Only support for the ActiveRecord will remain for the foreseeable future. So Rails beginners can expect workflow to work with Rails out of the box. Other already included adapters stay for a while but should be extracted to separate gems.

If you want to implement support for your favorite ORM mapper or your favorite NoSQL database, you just need to implement a module which overrides the persistence methods load_workflow_state and persist_workflow_state. Example:

module Workflow
  module SuperCoolDb
    module InstanceMethods
      def load_workflow_state
        # Load and return the workflow_state from some storage.
        # You can use self.class.workflow_column configuration.
      end

      def persist_workflow_state(new_value)
        # save the new_value workflow state
      end
    end

    module ClassMethods
      # class methods of your adapter go here
    end

    def self.included(klass)
      klass.send :include, InstanceMethods
      klass.extend ClassMethods
    end
  end
end

The user of the adapter can use it then as:

class Article
  include Workflow
  include Workflow:SuperCoolDb
  workflow do
    state :submitted
    # ...
  end
end

I can then link to your implementation from this README. Please let me also know, if you need any interface beyond load_workflow_state and persist_workflow_state methods to implement an adapter for your favorite database.

You can easily reflect on workflow specification programmatically - for the whole class or for the current object. Examples:

article2.current_state.events # lists possible events from here
article2.current_state.events[:reject].transitions_to # => :rejected

Article.workflow_spec.states.keys
#=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]

Article.workflow_spec.state_names
#=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]

# list all events for all states
Article.workflow_spec.states.values.collect &:events

You can also store and later retrieve additional meta data for every state and every event:

class MyProcess
  include Workflow
  workflow do
    state :main, :meta => {:importance => 8}
    state :supplemental, :meta => {:importance => 1}
  end
end
puts MyProcess.workflow_spec.states[:supplemental].meta[:importance] # => 1

The workflow library itself uses this feature to tweak the graphical representation of the workflow. See below.

Conditions can be a "method name symbol" with a corresponding instance method, a proc or lambda which are added to events, like so:

state :off
  event :turn_on, :transition_to => :on,
                  :if => :sufficient_battery_level?

  event :turn_on, :transition_to => :low_battery,
                  :if => proc { |device| device.battery_level > 0 }
end

# corresponding instance method
def sufficient_battery_level?
  battery_level > 10
end

When calling a device.can_<fire_event>? check, or attempting a device.<event>!, each event is checked in turn:

  • With no :if check, proceed as usual.

  • If an :if check is present, proceed if it evaluates to true, or drop to the next event.

  • If you’ve run out of events to check (eg. battery_level == 0), then the transition isn’t possible.

We already had a look at the declaring callbacks for particular workflow events. If you would like to react to all transitions to/from the same state in the same way you can use the on_entry/on_exit hooks. You can either define it with a block inside the workflow definition or through naming convention, e.g. for the state :pending just define the method on_pending_exit(new_state, event, *args) somewhere in your class.

If you want to be informed about everything happening everywhere, e.g. for logging then you can use the universal on_transition hook:

workflow do
  state :one do
    event :increment, :transitions_to => :two
  end
  state :two
  on_transition do |from, to, triggering_event, *event_args|
    Log.info "#{from} -> #{to}"
  end
end

If you want to do custom exception handling internal to workflow, you can define an on_error hook in your workflow. For example:

workflow do
  state :first do
    event :forward, :transitions_to => :second
  end
  state :second

  on_error do |error, from, to, event, *args|
    Log.info "Exception(#{error.class}) on #{from} -> #{to}"
  end
end

If forward! results in an exception, on_error is invoked and the workflow stays in a 'first' state. This capability is particularly useful if your errors are transient and you want to queue up a job to retry in the future without affecting the existing workflow state.

If you want to halt the transition conditionally, you can just raise an exception in your [transition event handler](#transition_event_handler). There is a helper called halt!, which raises the Workflow::TransitionHalted exception. You can provide an additional halted_because parameter.

def reject(reason)
  halt! 'We do not reject articles unless the reason is important' \
    unless reason =~ /important/i
end

The traditional halt (without the exclamation mark) is still supported too. This just prevents the state change without raising an exception.

You can check halted? and halted_because values later.

The whole event sequence is as follows:

  • before_transition

  • event specific action

  • on_transition (if action did not halt)

  • on_exit

  • PERSIST WORKFLOW STATE, i.e. transition

  • on_entry

  • after_transition

You can generate a graphical representation of the workflow for a particular class for documentation purposes. Use Workflow::create_workflow_diagram(class) in your rake task like:

namespace :doc do
  desc "Generate a workflow graph for a model passed e.g. as 'MODEL=Order'."
  task :workflow => :environment do
    require 'workflow/draw'
    Workflow::Draw::workflow_diagram(ENV['MODEL'].constantize)
  end
end
  • gh-228 Support for Ruby 3 keyword args, provided by @agirling

  • retire Ruby 2.6 since it has reached end of live; please use workflow 2.x, if you still depend on that Ruby version

  • gh-229 Switch from travis CI to GihHub actions for continuous integration

  • extract persistence adapters, Rails/ActiveRecord integration is now a separate gem workflow-activerecord

sudo apt-get install graphviz # Linux
brew install graphviz # Mac OS
cd workflow
gem install bundler
bundle install
# run all the tests
bundle exec rake test

Author: Vladimir Dobriakov, https://infrastructure-as-code.de

Copyright (c) 2010-2022 Vladimir Dobriakov and Contributors

Copyright (c) 2008-2009 Vodafone

Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd

Based on the work of Ryan Allen and Scott Barron

Licensed under MIT license, see the MIT-LICENSE file.

workflow's People

Contributors

bilashsaha avatar bowsersenior avatar brandondrew avatar crafterm avatar damncabbage avatar danp avatar dche avatar dekart avatar dwbutler avatar geekq avatar hakon avatar iancanderson avatar indrekj avatar jamezilla avatar johndbritton avatar mattallen avatar meuble avatar mwilden avatar paulnsorensen avatar peck avatar plukevdh avatar rhec avatar robolson avatar ryan-allen avatar tanraya avatar tlossen avatar tmaier avatar tmarshall avatar virtualstaticvoid avatar voltechs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

workflow's Issues

Question: parallelisation state

since there is no forum, I use the issue tracker for asking a question.

I would like to know if the workflow fit with my requirement

I have n tasks to complete. A same task need to be parallelised and done by k non-experts , then to aggregated by a reviewer. I would like to design this kind of workflow though the use of this workflow gem as a potential help but I am not sure to understand how to design the parallelisation step.

To get more insights. I need to design a "task assigner" method so that when a non-experts wants a task he can choose on the available tasks that are still in the "parallelised" state ( less than k non-experts have completed the task)

nicolas

Feature request: Ability to compare workflow states

It would be great if we could test if a workflow is 'bigger/smaller or egal' than another according to the specification.

Example:

Workflow do
  state :one do
    event :next, :transitions_to => :two,
  end
  state :two do
    event :back, :transitions_to => :one,
    event :next, :transitions_to => :three,
  end
  state :three
end  

We could check:
my_object.current_state > Object.workflow_spec.states[:two]

Semantic

bigger ( state1 > state2): true if state2 cannot be reached (through any number of events/charges) from state1 BUT NOT the opposite (eg, :three > :one => true)

smaller ( state1 < state2): true if state2 can be reached from state1 BUT NOT the opposite (eg, :one > :three => true)

equal ( state1 == state2): true if state2 can be reached from state1 AND state1 can be reached from state2 (eg, :two == :one => true)

What do you think?

Operator to determine the relative state...

It would be very useful if there were operators able to respond about the overall state.
For example, having this:
class C
workflow do
state :a do
event :to_b, :transitions_to => :b
end
state :b do
event :to_c, :transitions_to => :c
end
state :c
end
...
end

And these instances:

inst_a.a? #=> true
inst_b.b? #=> true
inst_c.c? #=> true

It would be useful to have a method overcame?(state) like this:
inst_a.overcame?(:a) #=>false
inst_a.overcame?(:b) #=>false
inst_a.overcame?(:c) #=>false

inst_b.overcame?(:a) #=>true
inst_b.overcame?(:b) #=>false
inst_b.overcame?(:c) #=>false

inst_c.overcame?(:a) #=>true
inst_c.overcame?(:b) #=>true
inst_c.overcame?(:c) #=>false

and its counterpart to_reach?(state)
inst_a.to_reach?(:a) #=>false
inst_a.to_reach?(:b) #=>true
inst_a.to_reach?(:c) #=>true

inst_b.to_reach?(:a) #=>false
inst_b.to_reach?(:b) #=>false
inst_b.to_reach?(:c) #=>true

inst_c.to_reach?(:a) #=>false
inst_c.to_reach?(:b) #=>false
inst_c.to_reach?(:c) #=>false

of course this is a simple case in which these two methods would be deterministic since the diagram produced by the workflow is a tree and not a graph, but even just for those simple case, it would be useful to have these methods.
If the response cannot be determined, it could be thrown an exception.
These methods should be boolean, to be useful, not three-state, because the are intended to use with tree workflow...

Setting state on after_initialize instead of before_validation

Is there any problem with setting the initial state upon object instantiation? currently...

> d = MyModel.new
=> <MyModel....>
> d.state
=> nil
> d.valid?
=> false
> d.state
=> "created"

preferred way

> d = MyModel.new
=> <MyModel...>
> d.state
=> "created"

Is there a problem with calling write_initial_state as a callback for after_initialize?

Invoking halt! within a transaction destroys rollback action

If I use the helper halt! (with AR) within a event handler method (here 'load') where there is an transaction, halt! exception doesn't rollback the transaction leaving DB in a unsafe state.
In the following example, if I replace the halt! by raise, the rollback occurs properly.
Am I doing something wrong? or there is a bug somewhere...

def load
  Shipment.transaction do
    shipping_items.each do |si|
        si.prepare_for_shipment
        si.unit_price = 99.9          
        si.save!
    end
    # raise  # rollback properly
    halt! 'An error occured'   # raise exception but doesn't rollback properly
  end
end

PS: I checked the code but being new to Ruby, I'm not sure... maybe it's due to the exception type raised (TransitionHalted)?!?

on workflow 0.8.0, ree and rails 2.3.11, I added a new "first state", but the code in on_entry isn't being executed

I have the following workflow configuration:

# scheduled_change.rb
  workflow do
                    # "The initial state of a workflow is the first state defined"
    state :planning do
      event :advance, :transitions_to => :planning_approved
      event :skip_to_complete, :transitions_to => :completed

      on_entry do
                   # send a "scheduled_change has been created" email
        @subject = "A new Scheduled Change has been created. [not yet started]."
        @subject += " '" + name + "'"
        @subject += " planned on " + target_date.strftime('%m/%d/%Y')
        @subject += " at " + target_time + " "  + time_zone.name
        @subject += ". Expected duration: " + duration.to_s + " hours."
        @recipient = creator.email
        send_email(@subject, @recipient, "created")
                     # send a "scheduled_change needs your approval" email
        @subject = "A new Scheduled Change has been created NEEDING YOUR APPROVAL."
        @subject += " '" + name + "'"
        @subject += " planned on " + target_date.strftime('%m/%d/%Y')
        @subject += " at " + target_time + " "  + time_zone.name
        @subject += ". Expected duration: " + duration.to_s + " hours."
        @recipient = carrier.advocates.size > 0 ? carrier.advocates.map(&:email) : creator.email
        logger.info "Sending NEEDING YOUR APPROVAL to #{@recipient.inspect}"
        send_email(@subject, @recipient, "requesting_approval")
      end
    end

    state :planning_approved do
      event :advance, :transitions_to => :running

      on_entry do
                         # send a "scheduled_change approved" email
        @subject = "A new Scheduled Change has been approved."
        @subject += " '" + name + "'"
        @subject += " planned on " + target_date.strftime('%m/%d/%Y')
        @subject += " at " + target_time + " "  + time_zone.name
        @subject += ". Expected duration: " + duration.to_s + " hours."
        @recipient = carrier.advocates.size > 0 ? carrier.advocates.map(&:email) << creator.email : creator.email
        send_email(@subject, @recipient, "created")
      end
    end

    state :running do
      event :advance, :transitions_to => :completed

      on_entry do
                      # send a "scheduled_change has started" email
        @recipient = SCHEDULED_CHANGE_BROADCAST_ADDRESS_EOS
        @subject = "Scheduled Change #{name} has started"
        @subject += ". Expected duration: " + duration.to_s + " hours."
        send_email(@subject, @recipient, "status")
      end
    end

    state :completed do
      event :reset, :transitions_to => :planning

      on_entry do
                 # send a "scheduled_change has completed" email
        @recipient = SCHEDULED_CHANGE_BROADCAST_ADDRESS_EOS
        @subject = "Scheduled Change #{name} has completed"
        if scheduled_change_outcome
          @subject += ". Result: " + scheduled_change_outcome.name.upcase + "."
        end
        status="completed"
        send_email(@subject, @recipient, "status")
      end
    end

  end

  def send_email(_sub, _to, _type)
    case _type
      when "created"
        ScheduledChangeMailer::deliver_scheduled_change_created_mail(_to, _sub, id)
      when "requesting_approval"
        ScheduledChangeMailer::deliver_scheduled_change_requesting_approval_mail(_to, _sub, id)
      when "status"
        ScheduledChangeMailer::deliver_scheduled_change_state_mail(_to, _sub, id)
    end
  end

But the code in :planning on_entry is no being executed. Can you please look over and see if you can tell me why?

Thanks! I appreciate your plugin!

Jason

does anyone see dynamically formulated workflows fly at all?

Back on 1.2 and 2.0.3 I used AASM and fellows - lately I've been trying out state_machine - but then I googled 'dynamic state machine' and Mountain View 'coughed up' this gem!

Pitch

I'd like to allow users to setting up workflows themselves, selecting from a bunch of available events/transistions, and in the proces I18n'ifying the body of the work :)

One use case would be several groups of users processing workflows on the same codebase - but with totally different workflow use cases (multi-site)

One other use case would be users adjusting workflows to changes to business policies - like: we at Corp XYZ are now 'green' and thus our SourcingOrders should not print before they are emailed/faxed to our suppliers - unless the Order Total exceeds 1,000,000...

UI design

I'm working off of a generic abstract_action_base.rb model which gets inherited by models like Project, Product, Item, Partner, etc. and then (as STI models) like Partner being inherited by models like Customer, Supplier, Bank, etc

Ideally I'd like my abstract_action_base to provide the workflow_states as a Hash

{ sourcing: :requested, selling: :ordered, stock: :backlogged }

and all state_questions to work like Product.find(419).sourcing_requested? and persisted to ActiveRecord (AR) in a quasi-searchable fashion - something along the lines of:

workflow_states = '----1R--2O--3B----'

which would allow quick searches on workflow states like:

Product.where{ workflow_states.like_all [ '-1R-','-3B-'] }.where( "workflow_states like '%-2O-% " )

Workflow transitions

Workflow transitions would - again ideally - be persisted to AR as something like:

t.string :transition_label  # what to label the transition and use as key for I18n.translations
t.text :guard               # code to eval true/false guarding the transition on an instance
t.text :from_to             # hash of transition like { drafted: { requested: :some_condition_attribute_or_method, archived: :other_condition_or_method }, requested: {} }
                                    # persisted as serialized object

Epilogue

Am I totally loosing it here? Or does you (the reader) see dynamically formulated workflows fly at all?

"Cannot update on a new record object" in created! method

After creating new record I get the following error

"can not update on a new record object"

My versions of ruby and rails:
ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-linux]
Rails 3.2.12

My logs:

activerecord (3.2.12) lib/active_record/persistence.rb:195:in update_column' workflow (1.0.0) lib/workflow.rb:348:inpersist_workflow_state'
workflow (1.0.0) lib/workflow.rb:214:in process_event!' workflow (1.0.0) lib/workflow.rb:154:inblock (4 levels) in workflow'
vendor/engines/ads/app/controllers/ads/postings_controller.rb:46:in create' actionpack (3.2.12) lib/action_controller/metal/implicit_render.rb:4:insend_action'
actionpack (3.2.12) lib/abstract_controller/base.rb:167:in process_action' actionpack (3.2.12) lib/action_controller/metal/rendering.rb:10:inprocess_action'
actionpack (3.2.12) lib/abstract_controller/callbacks.rb:18:in block in process_action' activesupport (3.2.12) lib/active_support/callbacks.rb:459:inblock in _run__725368758838839659__process_action__1844081299291344106__callbacks'
activesupport (3.2.12) lib/active_support/callbacks.rb:215:in block in _conditional_callback_around_830' activesupport (3.2.12) lib/active_support/callbacks.rb:326:inaround'
activesupport (3.2.12) lib/active_support/callbacks.rb:310:in _callback_around_785' activesupport (3.2.12) lib/active_support/callbacks.rb:214:in_conditional_callback_around_830'
activesupport (3.2.12) lib/active_support/callbacks.rb:447:in _run__725368758838839659__process_action__1844081299291344106__callbacks' activesupport (3.2.12) lib/active_support/callbacks.rb:405:in__run_callback'
activesupport (3.2.12) lib/active_support/callbacks.rb:385:in _run_process_action_callbacks' activesupport (3.2.12) lib/active_support/callbacks.rb:81:inrun_callbacks'
actionpack (3.2.12) lib/abstract_controller/callbacks.rb:17:in process_action' actionpack (3.2.12) lib/action_controller/metal/rescue.rb:29:inprocess_action'
actionpack (3.2.12) lib/action_controller/metal/instrumentation.rb:30:in block in process_action' activesupport (3.2.12) lib/active_support/notifications.rb:123:inblock in instrument'
activesupport (3.2.12) lib/active_support/notifications/instrumenter.rb:20:in instrument' activesupport (3.2.12) lib/active_support/notifications.rb:123:ininstrument'
actionpack (3.2.12) lib/action_controller/metal/instrumentation.rb:29:in process_action' actionpack (3.2.12) lib/action_controller/metal/params_wrapper.rb:207:inprocess_action'
activerecord (3.2.12) lib/active_record/railties/controller_runtime.rb:18:in process_action' actionpack (3.2.12) lib/abstract_controller/base.rb:121:inprocess'
actionpack (3.2.12) lib/abstract_controller/rendering.rb:45:in process' actionpack (3.2.12) lib/action_controller/metal.rb:203:indispatch'
actionpack (3.2.12) lib/action_controller/metal/rack_delegation.rb:14:in dispatch' actionpack (3.2.12) lib/action_controller/metal.rb:246:inblock in action'
actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:73:in call' actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:73:indispatch'
actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:36:in call' journey (1.0.4) lib/journey/router.rb:68:inblock in call'
journey (1.0.4) lib/journey/router.rb:56:in each' journey (1.0.4) lib/journey/router.rb:56:incall'
actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:601:in call' railties (3.2.12) lib/rails/engine.rb:479:incall'
railties (3.2.12) lib/rails/railtie/configurable.rb:30:in method_missing' journey (1.0.4) lib/journey/router.rb:68:inblock in call'
journey (1.0.4) lib/journey/router.rb:56:in each' journey (1.0.4) lib/journey/router.rb:56:incall'
actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:601:in call' warden (1.2.1) lib/warden/manager.rb:35:inblock in call'
warden (1.2.1) lib/warden/manager.rb:34:in catch' warden (1.2.1) lib/warden/manager.rb:34:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/best_standards_support.rb:17:in call' rack (1.4.5) lib/rack/etag.rb:23:incall'
rack (1.4.5) lib/rack/conditionalget.rb:35:in call' actionpack (3.2.12) lib/action_dispatch/middleware/head.rb:14:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/params_parser.rb:21:in call' actionpack (3.2.12) lib/action_dispatch/middleware/flash.rb:242:incall'
rack (1.4.5) lib/rack/session/abstract/id.rb:210:in context' rack (1.4.5) lib/rack/session/abstract/id.rb:205:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/cookies.rb:341:in call' activerecord (3.2.12) lib/active_record/query_cache.rb:64:incall'
activerecord (3.2.12) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in call' actionpack (3.2.12) lib/action_dispatch/middleware/callbacks.rb:28:inblock in call'
activesupport (3.2.12) lib/active_support/callbacks.rb:405:in _run__4503270357500974277__call__1321220018304606217__callbacks' activesupport (3.2.12) lib/active_support/callbacks.rb:405:in__run_callback'
activesupport (3.2.12) lib/active_support/callbacks.rb:385:in _run_call_callbacks' activesupport (3.2.12) lib/active_support/callbacks.rb:81:inrun_callbacks'
actionpack (3.2.12) lib/action_dispatch/middleware/callbacks.rb:27:in call' actionpack (3.2.12) lib/action_dispatch/middleware/reloader.rb:65:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/remote_ip.rb:31:in call' actionpack (3.2.12) lib/action_dispatch/middleware/debug_exceptions.rb:16:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/show_exceptions.rb:56:in call' railties (3.2.12) lib/rails/rack/logger.rb:32:incall_app'
railties (3.2.12) lib/rails/rack/logger.rb:18:in call' actionpack (3.2.12) lib/action_dispatch/middleware/request_id.rb:22:incall'
rack (1.4.5) lib/rack/methodoverride.rb:21:in call' rack (1.4.5) lib/rack/runtime.rb:17:incall'
activesupport (3.2.12) lib/active_support/cache/strategy/local_cache.rb:72:in call' rack (1.4.5) lib/rack/lock.rb:15:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/static.rb:62:in call' rack-cache (1.2) lib/rack/cache/context.rb:136:inforward'
rack-cache (1.2) lib/rack/cache/context.rb:143:in pass' rack-cache (1.2) lib/rack/cache/context.rb:155:ininvalidate'
rack-cache (1.2) lib/rack/cache/context.rb:71:in call!' rack-cache (1.2) lib/rack/cache/context.rb:51:incall'
railties (3.2.12) lib/rails/engine.rb:479:in call' railties (3.2.12) lib/rails/application.rb:223:incall'
rack (1.4.5) lib/rack/content_length.rb:14:in call' railties (3.2.12) lib/rails/rack/log_tailer.rb:17:incall'
rack (1.4.5) lib/rack/handler/webrick.rb:59:in service' /home/aslyusarchuk/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/webrick/httpserver.rb:138:inservice'
/home/aslyusarchuk/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/webrick/httpserver.rb:94:in run' /home/aslyusarchuk/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/webrick/server.rb:295:inblock in start_thread'

minor typos in application using workflow - better error messages?

Hi - beginner error here... but an uninformative diagnostic error message. I accidentally typed "transition_to" instead of "transitions_to". Which provokes a message about an undefined method "to_sym" for nil.Class. Not very obvious that there's a missing required parameter...
Can I suggest a one liner enhancement to the initialize function?

def initialize(name, transitions_to, meta = {}, &action)
  raise "missing 'transitions_to' from workflow for '#{name}'" if transitions_to == nil 
  @name, @transitions_to, @meta, @action = name, transitions_to.to_sym, meta, action
end

Call on_*_entry before saving model

Could you modify workflow to call on_*_entry callbacks before saving ActiveRecord models? That way the callbacks can modify object state without having to call save() a second time, improving usability and performance.

"Cannot update on a new record object" in created! method

After creating new record I get the following error

"can not update on a new record object"

My versions of ruby and rails:
ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-linux]
Rails 3.2.12

My logs:

activerecord (3.2.12) lib/active_record/persistence.rb:195:in update_column'
workflow (1.0.0) lib/workflow.rb:348:inpersist_workflow_state'
workflow (1.0.0) lib/workflow.rb:214:in process_event!'
workflow (1.0.0) lib/workflow.rb:154:inblock (4 levels) in workflow'
vendor/engines/ads/app/controllers/ads/postings_controller.rb:46:in create'
actionpack (3.2.12) lib/action_controller/metal/implicit_render.rb:4:insend_action'
actionpack (3.2.12) lib/abstract_controller/base.rb:167:in process_action'
actionpack (3.2.12) lib/action_controller/metal/rendering.rb:10:inprocess_action'
actionpack (3.2.12) lib/abstract_controller/callbacks.rb:18:in block in process_action'
activesupport (3.2.12) lib/active_support/callbacks.rb:459:inblock in run_725368758838839659__process_action__1844081299291344106__callbacks'
activesupport (3.2.12) lib/active_support/callbacks.rb:215:in block in _conditional_callback_around_830'
activesupport (3.2.12) lib/active_support/callbacks.rb:326:inaround'
activesupport (3.2.12) lib/active_support/callbacks.rb:310:in _callback_around_785'
activesupport (3.2.12) lib/active_support/callbacks.rb:214:in_conditional_callback_around_830'
activesupport (3.2.12) lib/active_support/callbacks.rb:447:in _run__725368758838839659__process_action__1844081299291344106__callbacks'
activesupport (3.2.12) lib/active_support/callbacks.rb:405:in__run_callback'
activesupport (3.2.12) lib/active_support/callbacks.rb:385:in _run_process_action_callbacks'
activesupport (3.2.12) lib/active_support/callbacks.rb:81:inrun_callbacks'
actionpack (3.2.12) lib/abstract_controller/callbacks.rb:17:in process_action'
actionpack (3.2.12) lib/action_controller/metal/rescue.rb:29:inprocess_action'
actionpack (3.2.12) lib/action_controller/metal/instrumentation.rb:30:in block in process_action'
activesupport (3.2.12) lib/active_support/notifications.rb:123:inblock in instrument'
activesupport (3.2.12) lib/active_support/notifications/instrumenter.rb:20:in instrument'
activesupport (3.2.12) lib/active_support/notifications.rb:123:ininstrument'
actionpack (3.2.12) lib/action_controller/metal/instrumentation.rb:29:in process_action'
actionpack (3.2.12) lib/action_controller/metal/params_wrapper.rb:207:inprocess_action'
activerecord (3.2.12) lib/active_record/railties/controller_runtime.rb:18:in process_action'
actionpack (3.2.12) lib/abstract_controller/base.rb:121:inprocess'
actionpack (3.2.12) lib/abstract_controller/rendering.rb:45:in process'
actionpack (3.2.12) lib/action_controller/metal.rb:203:indispatch'
actionpack (3.2.12) lib/action_controller/metal/rack_delegation.rb:14:in dispatch'
actionpack (3.2.12) lib/action_controller/metal.rb:246:inblock in action'
actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:73:in call'
actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:73:indispatch'
actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:36:in call'
journey (1.0.4) lib/journey/router.rb:68:inblock in call'
journey (1.0.4) lib/journey/router.rb:56:in each'
journey (1.0.4) lib/journey/router.rb:56:incall'
actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:601:in call'
railties (3.2.12) lib/rails/engine.rb:479:incall'
railties (3.2.12) lib/rails/railtie/configurable.rb:30:in method_missing'
journey (1.0.4) lib/journey/router.rb:68:inblock in call'
journey (1.0.4) lib/journey/router.rb:56:in each'
journey (1.0.4) lib/journey/router.rb:56:incall'
actionpack (3.2.12) lib/action_dispatch/routing/route_set.rb:601:in call'
warden (1.2.1) lib/warden/manager.rb:35:inblock in call'
warden (1.2.1) lib/warden/manager.rb:34:in catch'
warden (1.2.1) lib/warden/manager.rb:34:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/best_standards_support.rb:17:in call'
rack (1.4.5) lib/rack/etag.rb:23:incall'
rack (1.4.5) lib/rack/conditionalget.rb:35:in call'
actionpack (3.2.12) lib/action_dispatch/middleware/head.rb:14:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/params_parser.rb:21:in call'
actionpack (3.2.12) lib/action_dispatch/middleware/flash.rb:242:incall'
rack (1.4.5) lib/rack/session/abstract/id.rb:210:in context'
rack (1.4.5) lib/rack/session/abstract/id.rb:205:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/cookies.rb:341:in call'
activerecord (3.2.12) lib/active_record/query_cache.rb:64:incall'
activerecord (3.2.12) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in call'
actionpack (3.2.12) lib/action_dispatch/middleware/callbacks.rb:28:inblock in call'
activesupport (3.2.12) lib/active_support/callbacks.rb:405:in _run__4503270357500974277__call__1321220018304606217__callbacks'
activesupport (3.2.12) lib/active_support/callbacks.rb:405:in__run_callback'
activesupport (3.2.12) lib/active_support/callbacks.rb:385:in _run_call_callbacks'
activesupport (3.2.12) lib/active_support/callbacks.rb:81:inrun_callbacks'
actionpack (3.2.12) lib/action_dispatch/middleware/callbacks.rb:27:in call'
actionpack (3.2.12) lib/action_dispatch/middleware/reloader.rb:65:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/remote_ip.rb:31:in call'
actionpack (3.2.12) lib/action_dispatch/middleware/debug_exceptions.rb:16:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/show_exceptions.rb:56:in call'
railties (3.2.12) lib/rails/rack/logger.rb:32:incall_app'
railties (3.2.12) lib/rails/rack/logger.rb:18:in call'
actionpack (3.2.12) lib/action_dispatch/middleware/request_id.rb:22:incall'
rack (1.4.5) lib/rack/methodoverride.rb:21:in call'
rack (1.4.5) lib/rack/runtime.rb:17:incall'
activesupport (3.2.12) lib/active_support/cache/strategy/local_cache.rb:72:in call'
rack (1.4.5) lib/rack/lock.rb:15:incall'
actionpack (3.2.12) lib/action_dispatch/middleware/static.rb:62:in call'
rack-cache (1.2) lib/rack/cache/context.rb:136:inforward'
rack-cache (1.2) lib/rack/cache/context.rb:143:in pass'
rack-cache (1.2) lib/rack/cache/context.rb:155:ininvalidate'
rack-cache (1.2) lib/rack/cache/context.rb:71:in call!'
rack-cache (1.2) lib/rack/cache/context.rb:51:incall'
railties (3.2.12) lib/rails/engine.rb:479:in call'
railties (3.2.12) lib/rails/application.rb:223:incall'
rack (1.4.5) lib/rack/content_length.rb:14:in call'
railties (3.2.12) lib/rails/rack/log_tailer.rb:17:incall'
rack (1.4.5) lib/rack/handler/webrick.rb:59:in service'
/home/aslyusarchuk/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/webrick/httpserver.rb:138:inservice'
/home/aslyusarchuk/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/webrick/httpserver.rb:94:in run'
/home/aslyusarchuk/.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/webrick/server.rb:295:inblock in start_thread'

NameError: undefined local variable or method `c' for #<Article>, Ruby 1.9.1

I have Ruby 1.9.1
I commented four lines of code (it begins at the line 180), as you recommended.
Need some help with this:

$ irb
irb(main):001:0> require 'rubygems'
=> false
irb(main):002:0> require 'workflow'
=> true
irb(main):003:0> class Article
irb(main):004:1> include Workflow
irb(main):005:1> workflow do
irb(main):006:2* state :new do
irb(main):007:3* event :submit, :transitions_to => :awaiting_review
irb(main):008:3> end
irb(main):009:2> state :awaiting_review do
irb(main):010:3* event :review, :transitions_to => :being_reviewed
irb(main):011:3> end
irb(main):012:2> state :being_reviewed do
irb(main):013:3* event :accept, :transitions_to => :accepted
irb(main):014:3> event :reject, :transitions_to => :rejected
irb(main):015:3> end
irb(main):016:2> state :accepted
irb(main):017:2> state :rejected
irb(main):018:2> end
irb(main):019:1> end
=> [#<Workflow::State:0x000000023bc040 @name=:new, @events={:submit=>#<Workflow::Event:0x000000023bbe10 @name=:submit, @transitions_to=:awaiting_review, @meta={}, @action=nil>}, @meta={}>, #<Workflow::State:0x000000023bbc88 @name=:awaiting_review, @events={:review=>#<Workflow::Event:0x000000023bba58 @name=:review, @transitions_to=:being_reviewed, @meta={}, @action=nil>}, @meta={}>, #<Workflow::State:0x000000023bb908 @name=:being_reviewed, @events={:accept=>#<Workflow::Event:0x000000023bb6d8 @name=:accept, @transitions_to=:accepted, @meta={}, @action=nil>, :reject=>#<Workflow::Event:0x000000023bb5c0 @name=:reject, @transitions_to=:rejected, @meta={}, @action=nil>}, @meta={}>, #<Workflow::State:0x000000023bb4a8 @name=:accepted, @events={}, @meta={}>, #<Workflow::State:0x000000023bb208 @name=:rejected, @events={}, @meta={}>]
irb(main):020:0> article = Article.new
=> #Article:0x000000023b0ce0
irb(main):021:0> article.accepted?
NameError: undefined local variable or method c' for #<Article:0x000000023b0ce0> from /usr/lib/ruby/gems/1.9.1/gems/workflow-0.6.0/lib/workflow.rb:188:inspec'
from /usr/lib/ruby/gems/1.9.1/gems/workflow-0.6.0/lib/workflow.rb:135:in current_state' from /usr/lib/ruby/gems/1.9.1/gems/workflow-0.6.0/lib/workflow.rb:115:inblock (3 levels) in workflow'
from (irb):21
from /usr/bin/irb:12:in `

'

I also tried to use Workflow in Rails project, through script/console, but it shows me the same error.
Sorry, if I misunderstood something.

Transition saves without validation

Perhaps this is intended but it's definitely not something I want in my app. Any state transition saves the object without validation because it uses update_attribute. This is true even if the object is not yet persisted.

If for example in your controller you have:

@user = User.new
@user.email = "someinvalidemail"
@user.registered!
if @user.save
..

The user will be saved with the invalid email. You will have invalid data the bug will be hard to find. This could manifest in some terribly more subtle ways if you've modified an object and then call a transition method. I prefer validation be performed, as sometimes validation could depend on state, so I'm trying out this monkey patch:

module Workflow
  module ActiveRecordInstanceMethods
    def persist_workflow_state(new_value)
      send "#{self.class.workflow_column}=".to_sym, new_value
      save!
    end
  end
end

It's probably better to return false and halt in the caller however. I was going to hack up a new version but the gemspec is not checked in and I'm not all that familiar with publishing gems, and this makes it hard to develop directly on a path with bundler.

Discussion of how best to do state transition validations

I have a requirement to do state-based validations. This keeps all of the form code tidy, since it can use the standard ActiveRecord error discovery.

I've been looking at the southdesign modification to do state-based validations, which is a good start, but for my purposes there are a few shortcomings:

1 - It always throws an exception - I can see why, because new_state! is already the "!" operator which would normally raise an exception in ActiveRecord, but in the workflow nomenclature it does not raise an exception under normal circumstances. I think it is preferable that halted? or valid? return false in the normal case (where, for validation purposes, invalid input should be considered a normal case).

2 - It does not take advantage of the new ActiveModel Validators (http://api.rubyonrails.org/classes/ActiveModel/Validations.html). This causes a deprecation warning in Rails 3, but also I think it doesn't capture the existing validation classes. No reason to be writing more of them!

So I think this is all easily managed, but my questions for discussion are:
1: Is there a reasonable syntax for indicating when to not throw/throw an exception for validation? I am thinking new_state! and new_state!(true), respectively, but this seems slightly kludgy. Comments?

2: Is there a good syntax for defining validations so they can follow the Rails3 validator syntax:
# validates :name, :presence => {:if => :foo?}, :length => {:maximum => 200, :minimum => 100}

For consistency, I am inclined to use:

event :accept, :transitions_to => :accepted do
  validates :title, :presence
end

But I see in the README "that mixing the list of events and states with the blocks invoked for particular transitions leads to a bumpy and poorly readable code due to a deep nesting" so I am conflicted about the right way to handle this. Advice/comments?

Document the initial state

The README does a great job with describing Workflow but one important detail appears to be missing. How to define which state is the initial state for the object. The example of article = Article.new; article.new? => true implies that if the state is named with :new, that is the initial state. Could this be stated explicitly in the docs, and what happens if the workflow does not define a state called :new?

Previous step

Looking through the code, I don't see any way to reference the last step. For example, I've got a branched workflow, and I'd like to be able to call reject at a step and have it go back to the previous step, which might be one of two possible things. I'm thinking that the best way to handle this might be to add another column called previous_workflow_state, and then update this using the on_transition hook. Sound reasonable?

Thanks!

Recommendation for undoing an event..

Hi, I'm wondering what is the most appropriate way for handling 'undo' using workflow, for example using your 'article' illustration:

from 'being_reviewed' we can either accept or reject an article. If we choose to accept but we want to revert back to 'being_reviewed' is it better to?

1 give's each state an extra event for reverting, ex:

    state :new do
      event :submit, :transitions_to => :awaiting_review
    end
    state :awaiting_review do
      event :review, :transitions_to => :being_reviewed
      event :revert_new, :transitions_to => :new
    end
    state :being_reviewed do
      event :accept, :transitions_to => :accepted
      event :reject, :transitions_to => :rejected
      event :revert_awaiting_review, :transitions_to => :awaiting_review
    end
    state :accepted do
      event :revert_being_reviewed, :transitions_to => :being_reviewed
    end

2 make use of external gem for handling undo, such as papertrail
3 some other way?

Access to the current_event object in event method?

If you have a workflow

workflow do
    state :open do
      event :archive, :transitions_to => :completed
      event :order, :transitions_to => :ordered
    end
    state :ordered do
      event :archive, :transitions_to => :completed
    end
    state :completed
end

def archive
     #object event?
end

My question is can I have access of the object 'event' in the archive method? I would like to check the origin state from which the event was fired (ordered or open) and possibly the destination state and the event meta info.

Using strings instead of symbols

If I try to use strings instead of symbols for defining the events Workflow doesn't work properly.

e.g.

state 'new' do
    event 'publish', ...
end

Then

object.can_publish? 

always returns false where it should return true. When using symbols this works fine.

However using string is useful for validation:

validates :state, :presence => true, :inclusion => STATUSES

where STATUSES is the array of possible valid states

When the object is first created the state is a symbol so validation passes. Then when the saved and retrieved back again state is a string so it doesn't validate anymore.

I would be good to have full support for using strings as well as symbols.

Event triggers state update but not invokes method

Using workflow in an ActiveRecord model, when an event is raised the workflow state is updated, however the method model (with the same name) is not invoked.

I use RVM with ruby-1.9.2-p320 and rails 3.2.12.

My model :

class Assembly < ActiveRecord::Base
include Workflow
...
workflow do
state :fresh_aln_assembly do
event :submit_aln_transcripts_on_dataset, :transitions_to => :aln_transcripts_on_dataset
end
state :aln_transcripts_on_dataset do
event :aln_transcripts_on_dataset_check, :transitions_to => :aln_transcripts_on_dataset_checking
end
etc...
...
def aln_transcripts_on_dataset
do some stuff
end
...

ActiveRecord Transactions

What I would like is for the event transition and the transition event handler to be executed within the same ActiveRecord transaction to guarantee that the state is properly represented.

I'm using Workflow along with ActiveRecord in an order processing application. I have an event, "confirm", that relieves available inventory and changes the order state from "Processing" to "Shipped". I know if there is a problem confirming inventory I can use halt! to prevent the state from updating to "Shipped". But, what I am concerned of is the rare case in which the error occurs in updating the state itself. If this were to happen, the inventory would confirm but the state would remain "Processing". Then, the same order would be confirmed again the next time the rake task runs which would double-relieve inventory.

I know I could try and catch the exception and try and reverse the effects, but things can start to get complicated when doing that. Is there currently a solution to run the entire event sequence in an ActiveRecord transaction?

Thank you for your help, and btw thanks for this gem... it's almost exactly what I was looking for.

Workflow::create_workflow_diagram problems

Hello,
I've detected a few issues with Workflow::create_workflow_diagram and since I'm not experienced enough (yet) to do the fork/merge thing I will write them here, I hope you can incorporate them into the gem:

Docs

  1. Workflow::create_workflow_diagram expects a class, not a instance as stated in the doc:
    Workflow::create_workflow_diagram(Order) instead of Workflow::create_workflow_diagram(Order.new)
  2. To be able to use it with rake, the rake task must get access to the environment and not on its own like in the doc:
    task :workflow => :environment do instead of task :workflow do
  3. Lastly, it would be good to indicate that the dot library might be downloaded bundled with a viewer at http://www.graphviz.org/

Code

  1. When the dir path for the generated files has a space in it, it doesn't work, either Ruby (with the ) or dot (without the ), at least on MacOS X 10.5. It would be good change to the following line escaping the spaces at least on UNIXes:
    dot -Tpdf -o#{fname.gsub(/ /,'\ ')}.pdf #{fname.gsub(/ /,'\ ')}.dot

I hope it helps,
Gam

Conflicting method and event names

In a Rails application of mine a model has a state :created with an event :open, :transition_to => :opened declaration.
A method of the model triggers the state change calling open!. That worked well with the 0.8.1 gem but doesn't work anymore with the 0.8.6 one. Instead it ends up calling the open method of open-uri, which happens to have been included somewhere by the framework and has a different number of arguments and returns an error which I saw in the browser.

An easy workaround would be to change the state name to something else but I set "0.8.1" in the Gemfile to be sure I have no other problems I still didn't find out. Furthermore, what if the next version of another gem "steals" one or more of my state names? (inadvertently, obviously, I don't think this is an attack vector). Devising perfectly safe state names is impossible.

The ideal solution is that the Workflow state prevails over any method name. That should make sense because a locally defined workflow should be expected to be in the local scope. Calling a far away method probably violates the least surprise principle.

Allow providing an instantiated Specification to the "workflow" method in lieu of a block

It would be nice if the "workflow" method accepted a Specification OR a block, or possibly both. I am wrapping your state machine DSL with a proxy to enable my own DSL more specific to my problem domain. To do that, I wanted to have a WorkflowProxy that would intercept my own methods and convert them to method calls to your DSL, which is the Specification object. To do this I instantiate a Specification and my proxy uses method missing to pass method calls through, but can define custom methods to. In any case, I had to fork the project to allow this to work. I would submit a pull request, but I suspect my implementation may not be the right ultimate solution. (http://github.com/PlasticLizard/workflow)

Question: Isolating workflows to module

Great job on this -- it is working really well for me. But to my question / issue: I am trying to externalize my workflows to my lib so I can re-use them. For example, a lot of my models use a common state of new, pending, active, etc. So what I am trying to do is define a couple of common workflows that a class can mix in. In my lib, I have myco/workflows/life_cycle, for example. In that module, I have a constant for the possible states (array) and the "workflow do..." logic. But, I can't seem to get everything to pick up my method. Any suggestions or tips? I've tried doing it as instance methods, class methods, and both, and I tried creating a method to wrap the "workflow do..." in then invoking that in my class def, but no luck. FYI, when I move the logic back in to my class, it works fine. Any thoughts are appreciated, and I'm happy to contribute my library of what I think are practical, common workflows.

Change state on halt

I can't found any way to easily change state on a halt

class Model < AR::Base
  include Workflow 

  workflow do
    state :new do
      event :process,  :transitions_to => :ready
    end
    state :ready
    state :fail
  end

  def process
    if process_works_whell?
       :ok
    else
      change_state :to => :fail
  end
end

Is any way to do this?

Maybe an option as:
event :process, :transitions_to => :ready, :on_halt => :fail

Or more advanced:
event :process, :transitions_to => [:ready, :fail]
and in the code
halt(:to => :fail)

Or may i doing something wrong?

How to change the state manually?

I need to change the state of an object manually without triggering the state machine and any hooks. e.g. model.current_state = :closed

This is because I need to set up an initial state that can be any of the possible states.

Is this possible? How?

Thanks

Does not work with Ruby 1.9

ruby -e "puts RUBY_VERSION; require 'workflow'"

1.9.1
/usr/local/lib/ruby/gems/1.9.1/gems/workflow-0.6.0/lib/workflow.rb: /usr/local/lib/ruby/gems/1.9.1/gems/workflow-0.6.0/lib/workflow.rb:182: Invalid return (SyntaxError)

Name space pollution

class Registration < ActiveRecord::Base

has_many :reservations, dependent: :destroy

has_many :events, through: :reservations, source: :event # Not work with workflow.

has_many :events, through: :reservations, class_name: '::Event' # Works with Workflow

include Workflow

end

class Reservation < ActiveRecord::Base

belongs_to :registration

belongs_to :event

end

class Event < ActiveRecord::Base

has_many :reservations, dependent: :destroy

has_many :registrations, :through => :reservations

end

reg = Registration.find_by_id(267); reg.events

NoMethodError: undefined method quoted_table_name' for Workflow::Event:Class from /usr/local/lib/ruby/gems/1.9.1/gems/activerecord-2.3.8/lib/active_record/reflection.rb:187:inquoted_table_name'
from /usr/local/lib/ruby/gems/1.9.1/gems/activerecord-2.3.8/lib/active_record/associations/has_many_through_association.rb:142:in construct_select' from /usr/local/lib/ruby/gems/1.9.1/gems/activerecord-2.3.8/lib/active_record/associations/has_many_through_association.rb:79:infind_target'
from /usr/local/lib/ruby/gems/1.9.1/gems/activerecord-2.3.8/lib/active_record/associations/association_collection.rb:354:in load_target' from /usr/local/lib/ruby/gems/1.9.1/gems/activerecord-2.3.8/lib/active_record/associations/association_proxy.rb:140:ininspect'
from /usr/local/bin/irb:12:in `

'

Using "class_name: '::Event'" works around this bug.

Defining global transitions?

I'm wondering if it makes sense to have a way of defining a global transition that applies to all states? For example, if we have a long process with a lot of steps, it would be good to declare a global 'stop' transition that will go to :cancelled state.
I see a potential issue as we could then transit from :cancelled to :cancelled which is not good.

Anyone has a better idea?

Inheritance

I have some classes which are inherited, however the initial state on the parent class is not the default state on the child class.

This should be configurable or default.

class Animal < AR::Base
  include Workflow

  workflow do

    state :conceived do
      event :birth, :transition_to => :born
    end

    state :born do

    end
  end
end

class Cat < Animal
  include Workflow

  workflow do

    state :upset do
      event :scratch, :transition_to => :hiding
    end

    state :hiding do

    end
  end
end

As you can see, my Cat should still start at :conceived, in fact all classes derived from Animal should.

Obviously this is a very simplified example, to illustrate a point!

undefined method `find' for Workflow::Event:Class

Hi,

I have the following classes on my system:


class Event < ActiveRecord::Base

  has_many :event_approval_requests

  belongs_to :user

  include Workflow  
  workflow do
    state :pre_registered do
      event :submit, :transitions_to => :awaiting_approval
    end
    
    state :awaiting_approval do
      event :approve, :transitions_to => :approved
      event :reject, :transitions_to => :rejected
    end

    state :approved do
      event :submit, :transitions_to => :awaiting_approval
    end

    state :rejected do
      event :submit, :transitions_to => :awaiting_approval
    end
  end

 ...

end
class EventApprovalRequest < ActiveRecord::Base
  belongs_to :user
  belongs_to :department
  belongs_to :event

  include Workflow  

  workflow do
    state :pending do
      event :approve, :transitions_to => :approved
      event :reject, :transitions_to => :rejected
    end

    state :approved
    state :rejected
  end

  ...

end

Note that the two classes have workflow definitions and one have a relationship with the other. In a view, when I try to access the event from the event_approval_request like below:


<%=h event_approval_request.event.title %>

I got the following error: NoMethodError in Event_approval_requests#index undefined method `find' for Workflow::Event:Class

this error doesn't occur if I access a non-workflow class, like below:


<%=h event_approval_request.user.first_name %>

I'm using JRuby 1.5.1, Linux Fedora 11, Rails 2.3.8 and workflow 0.6.0.

Could anyone help me? Is it really an issue?

Thanks in advance,
Gustavo

Updating other object's states from a event transition method

Hello,
I'm having an interesting problem:
I have a request order with many sales. I want to transit the ro (request order) to 'completed' once the sum of all the sales' items equal to the ro items.
So each time I transit a sale to 'completed', I want to invoke a transition ('sell_partially') on the ro to control if all requested items have been sold.
My issue is:
Since I'm STILL in the sale's transition to 'completed', this very sale is NOT selected when the ro method iterating over all the related sales to control if the ro should be transited to 'completed' therefore it will wrongly assess that the ro is not yet completed and the ro will never be marked as 'completed' even though it is...
I could call it from the sales controller after having transited the sale but I feel it's not its business and this logic should belong to ro...
Anybody has a similar problem or suggestion?
Thanks in advance!

Gam

PS: Is there a way of nicely add this scopes to all the classes that uses Workflow:

makes all the named scoped for each state! [don't use :new as a state, it doesn't work with this]

workflow_spec.states.keys.each do |state|
named_scope state, :conditions => ['workflow_state = ?', state.to_s]
end

Diagram generation fails for models in modules

We have an Orders::RetailerOrder model and create_workflow_diagram tries to put the dot file in a sub folder + the dot file is invalid.

I was able to fix the problem by modifying the workflow_name :

workflow_name = "#{klass.name.tableize}_workflow".gsub(/\//, '')

workflow_state persistence for multiple_workflows example

bowsersenior reported:

The per-object workflow has some issues, even on ruby 1.8.7!

Let me explain with reference to the working example:
http://github.com/geekq/workflow/blob/master/test/multiple_workflows_test.rb

The Booking class is an ActiveRecord model, but in fact, the ActiveRecordInstanceMethods are never loaded. The workflow_state is persisted only in a variable. I am not sure why this is happening, but it seems to be related to the use of metaclass.

Now on Ruby 1.9.2, where the bug referenced above has been fixed, the test fails, because ActiveRecordInstanceMethods ARE loaded, and no class method name workflow_column is found.

I fiddled around with this for a while trying to find a solution, but couldn't get anywhere. It seems like there should be a straightforward solution...

Transitions not working with ActiveRecord

Hi there,

I have ActiveRecord 3.2.10 with ruby 1.9.2-p290 and trying to get this working with workflow gem version 0.8.6.

Without active record, the transitions work fine, as soon as I add in ActiveRecord to the mix, it only created the initial object and records the initial state in the DB correctly, but any transition (whether it's a new instance or trying to resume it from the DB) fails. No error message provided, the object state stays in the original one.

I even tried to run your Booking class example (https://github.com/geekq/workflow/blob/master/test/multiple_workflows_test.rb) and that didn't work either.

Could you look into this please and/or advise what am I doing wrong?

Thanks and regards.
Máté

Multiple simultaneous workflows

I like this workflow module, however I'm concerned that it locks me into one state machine per model. The multiple workflow demo appears to be for alternate workflows, not parallel work flows. It's quite possible I would want to add a second unrelated workflow to a model and as far as I can tell this is not possible.

Would love to see:

workflow :billing_state do ...

workflow :activity_state do ...

Not a great example, but basically I like the syntax of this module but may end up using another (such as https://github.com/pluginaweek/state_machine) because this seems like an unnecessary restriction. I haven't looked at the code to see how feasible this would be.

Correct way to check object.halted? from controller?

I have a conditional halt in my model

def approve
  halt! 'The invoice must be linked to a project' unless self.project
end

And in the controller I want to write something like below, so that it doesn't show the standard Workflow::TransitionHalted error page. (It doesn't work because @received_invoice.approve! is always called) I know about the halted? method but was wondering what the recommended way was to implement this in the controller.

def approve
  @received_invoice = ReceivedInvoice.find(params[:id])
  if @received_invoice.approve!
    redirect_to received_invoice_url(@received_invoice), notice: 'Invoice approved'
  else
    redirect_to received_invoice_url(@received_invoice), notice: @received_invoice.halted_because
  end
end

Persistence without AR

Is there some trick for non-active record persistence that I am missing?

I am using mongomapper and have added the :workflow_state attribute. I have also tried hooking on_transition and persist_workflow_state to preform a save manually, which actually does persist the state value but workflow does not seem to read that value on load of a record.

thanks,
chris

Steps

Hi,

We'd like to use workflow to create a wizard. Is there any way to get the current step and the total number of steps in the workflow?

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.