Coder Social home page Coder Social logo

state_machine's Introduction

state_machine Build Status Dependency Status

state_machine adds support for creating state machines for attributes on any Ruby class.

Resources

API

Bugs

Development

Testing

Source

  • git://github.com/pluginaweek/state_machine.git

Mailing List

Description

State machines make it dead-simple to manage the behavior of a class. Too often, the state of an object is kept by creating multiple boolean attributes and deciding how to behave based on the values. This can become cumbersome and difficult to maintain when the complexity of your class starts to increase.

state_machine simplifies this design by introducing the various parts of a real state machine, including states, events, transitions, and callbacks. However, the api is designed to be so simple you don't even need to know what a state machine is :)

Some brief, high-level features include:

  • Defining state machines on any Ruby class
  • Multiple state machines on a single class
  • Namespaced state machines
  • before/after/around/failure transition hooks with explicit transition requirements
  • Integration with ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel
  • State predicates
  • State-driven instance / class behavior
  • State values of any data type
  • Dynamically-generated state values
  • Event parallelization
  • Attribute-based event transitions
  • Path analysis
  • Inheritance
  • Internationalization
  • GraphViz visualization creator
  • YARD integration (Ruby 1.9+ only)
  • Flexible machine syntax

Examples of the usage patterns for some of the above features are shown below. You can find much more detailed documentation in the actual API.

Usage

Example

Below is an example of many of the features offered by this plugin, including:

  • Initial states
  • Namespaced states
  • Transition callbacks
  • Conditional transitions
  • State-driven instance behavior
  • Customized state values
  • Parallel events
  • Path analysis

Class definition:

class Vehicle
  attr_accessor :seatbelt_on, :time_used, :auto_shop_busy
  
  state_machine :state, :initial => :parked do
    before_transition :parked => any - :parked, :do => :put_on_seatbelt
    
    after_transition :on => :crash, :do => :tow
    after_transition :on => :repair, :do => :fix
    after_transition any => :parked do |vehicle, transition|
      vehicle.seatbelt_on = false
    end
    
    after_failure :on => :ignite, :do => :log_start_failure
    
    around_transition do |vehicle, transition, block|
      start = Time.now
      block.call
      vehicle.time_used += Time.now - start
    end
    
    event :park do
      transition [:idling, :first_gear] => :parked
    end
    
    event :ignite do
      transition :stalled => same, :parked => :idling
    end
    
    event :idle do
      transition :first_gear => :idling
    end
    
    event :shift_up do
      transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
    end
    
    event :shift_down do
      transition :third_gear => :second_gear, :second_gear => :first_gear
    end
    
    event :crash do
      transition all - [:parked, :stalled] => :stalled, :if => lambda {|vehicle| !vehicle.passed_inspection?}
    end
    
    event :repair do
      # The first transition that matches the state and passes its conditions
      # will be used
      transition :stalled => :parked, :unless => :auto_shop_busy
      transition :stalled => same
    end
    
    state :parked do
      def speed
        0
      end
    end
    
    state :idling, :first_gear do
      def speed
        10
      end
    end
    
    state all - [:parked, :stalled, :idling] do
      def moving?
        true
      end
    end
    
    state :parked, :stalled, :idling do
      def moving?
        false
      end
    end
  end
  
  state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
    event :enable do
      transition all => :active
    end
    
    event :disable do
      transition all => :off
    end
    
    state :active, :value => 1
    state :off, :value => 0
  end
  
  def initialize
    @seatbelt_on = false
    @time_used = 0
    @auto_shop_busy = true
    super() # NOTE: This *must* be called, otherwise states won't get initialized
  end
  
  def put_on_seatbelt
    @seatbelt_on = true
  end
  
  def passed_inspection?
    false
  end
  
  def tow
    # tow the vehicle
  end
  
  def fix
    # get the vehicle fixed by a mechanic
  end
  
  def log_start_failure
    # log a failed attempt to start the vehicle
  end
end

Note the comment made on the initialize method in the class. In order for state machine attributes to be properly initialized, super() must be called. See StateMachine::MacroMethods for more information about this.

Using the above class as an example, you can interact with the state machine like so:

vehicle = Vehicle.new           # => #<Vehicle:0xb7cf4eac @state="parked", @seatbelt_on=false>
vehicle.state                   # => "parked"
vehicle.state_name              # => :parked
vehicle.human_state_name        # => "parked"
vehicle.parked?                 # => true
vehicle.can_ignite?             # => true
vehicle.ignite_transition       # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
vehicle.state_events            # => [:ignite]
vehicle.state_transitions       # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
vehicle.speed                   # => 0
vehicle.moving?                 # => false

vehicle.ignite                  # => true
vehicle.parked?                 # => false
vehicle.idling?                 # => true
vehicle.speed                   # => 10
vehicle                         # => #<Vehicle:0xb7cf4eac @state="idling", @seatbelt_on=true>

vehicle.shift_up                # => true
vehicle.speed                   # => 10
vehicle.moving?                 # => true
vehicle                         # => #<Vehicle:0xb7cf4eac @state="first_gear", @seatbelt_on=true>

# A generic event helper is available to fire without going through the event's instance method
vehicle.fire_state_event(:shift_up) # => true

# Call state-driven behavior that's undefined for the state raises a NoMethodError
vehicle.speed                   # => NoMethodError: super: no superclass method `speed' for #<Vehicle:0xb7cf4eac>
vehicle                         # => #<Vehicle:0xb7cf4eac @state="second_gear", @seatbelt_on=true>

# The bang (!) operator can raise exceptions if the event fails
vehicle.park!                   # => StateMachine::InvalidTransition: Cannot transition state via :park from :second_gear

# Generic state predicates can raise exceptions if the value does not exist
vehicle.state?(:parked)         # => false
vehicle.state?(:invalid)        # => IndexError: :invalid is an invalid name

# Namespaced machines have uniquely-generated methods
vehicle.alarm_state             # => 1
vehicle.alarm_state_name        # => :active

vehicle.can_disable_alarm?      # => true
vehicle.disable_alarm           # => true
vehicle.alarm_state             # => 0
vehicle.alarm_state_name        # => :off
vehicle.can_enable_alarm?       # => true

vehicle.alarm_off?              # => true
vehicle.alarm_active?           # => false

# Events can be fired in parallel
vehicle.fire_events(:shift_down, :enable_alarm) # => true
vehicle.state_name                              # => :first_gear
vehicle.alarm_state_name                        # => :active

vehicle.fire_events!(:ignite, :enable_alarm)    # => StateMachine::InvalidTransition: Cannot run events in parallel: ignite, enable_alarm

# Human-friendly names can be accessed for states/events
Vehicle.human_state_name(:first_gear)               # => "first gear"
Vehicle.human_alarm_state_name(:active)             # => "active"

Vehicle.human_state_event_name(:shift_down)         # => "shift down"
Vehicle.human_alarm_state_event_name(:enable)       # => "enable"

# States / events can also be references by the string version of their name
Vehicle.human_state_name('first_gear')              # => "first gear"
Vehicle.human_state_event_name('shift_down')        # => "shift down"

# Available transition paths can be analyzed for an object
vehicle.state_paths                                       # => [[#<StateMachine::Transition ...], [#<StateMachine::Transition ...], ...]
vehicle.state_paths.to_states                             # => [:parked, :idling, :first_gear, :stalled, :second_gear, :third_gear]
vehicle.state_paths.events                                # => [:park, :ignite, :shift_up, :idle, :crash, :repair, :shift_down]

# Find all paths that start and end on certain states
vehicle.state_paths(:from => :parked, :to => :first_gear) # => [[
                                                          #       #<StateMachine::Transition attribute=:state event=:ignite from="parked" ...>,
                                                          #       #<StateMachine::Transition attribute=:state event=:shift_up from="idling" ...>
                                                          #    ]]
# Skipping state_machine and writing to attributes directly
vehicle.state = "parked"
vehicle.state                   # => "parked"
vehicle.state_name              # => :parked

# *Note* that the following is not supported (see StateMachine::MacroMethods#state_machine):
# vehicle.state = :parked

Integrations

In addition to being able to define state machines on all Ruby classes, a set of out-of-the-box integrations are available for some of the more popular Ruby libraries. These integrations add library-specific behavior, allowing for state machines to work more tightly with the conventions defined by those libraries.

The integrations currently available include:

  • ActiveModel classes
  • ActiveRecord models
  • DataMapper resources
  • Mongoid models
  • MongoMapper models
  • Sequel models

A brief overview of these integrations is described below.

ActiveModel

The ActiveModel integration is useful for both standalone usage and for providing the base implementation for ORMs which implement the ActiveModel API. This integration adds support for validation errors, dirty attribute tracking, and observers. For example,

class Vehicle
  include ActiveModel::Dirty
  include ActiveModel::Validations
  include ActiveModel::Observing
  
  attr_accessor :state
  define_attribute_methods [:state]
  
  state_machine :initial => :parked do
    before_transition :parked => any - :parked, :do => :put_on_seatbelt
    after_transition any => :parked do |vehicle, transition|
      vehicle.seatbelt = 'off'
    end
    around_transition :benchmark
    
    event :ignite do
      transition :parked => :idling
    end
    
    state :first_gear, :second_gear do
      validates_presence_of :seatbelt_on
    end
  end
  
  def put_on_seatbelt
    ...
  end
  
  def benchmark
    ...
    yield
    ...
  end
end

class VehicleObserver < ActiveModel::Observer
  # Callback for :ignite event *before* the transition is performed
  def before_ignite(vehicle, transition)
    # log message
  end
  
  # Generic transition callback *after* the transition is performed
  def after_transition(vehicle, transition)
    Audit.log(vehicle, transition)
  end
  
  # Generic callback after the transition fails to perform
  def after_failure_to_transition(vehicle, transition)
    Audit.error(vehicle, transition)
  end
end

For more information about the various behaviors added for ActiveModel state machines and how to build new integrations that use ActiveModel, see StateMachine::Integrations::ActiveModel.

ActiveRecord

The ActiveRecord integration adds support for database transactions, automatically saving the record, named scopes, validation errors, and observers. For example,

class Vehicle < ActiveRecord::Base
  state_machine :initial => :parked do
    before_transition :parked => any - :parked, :do => :put_on_seatbelt
    after_transition any => :parked do |vehicle, transition|
      vehicle.seatbelt = 'off'
    end
    around_transition :benchmark
    
    event :ignite do
      transition :parked => :idling
    end
    
    state :first_gear, :second_gear do
      validates_presence_of :seatbelt_on
    end
  end
  
  def put_on_seatbelt
    ...
  end
  
  def benchmark
    ...
    yield
    ...
  end
end

class VehicleObserver < ActiveRecord::Observer
  # Callback for :ignite event *before* the transition is performed
  def before_ignite(vehicle, transition)
    # log message
  end
  
  # Generic transition callback *after* the transition is performed
  def after_transition(vehicle, transition)
    Audit.log(vehicle, transition)
  end
end

For more information about the various behaviors added for ActiveRecord state machines, see StateMachine::Integrations::ActiveRecord.

DataMapper

Like the ActiveRecord integration, the DataMapper integration adds support for database transactions, automatically saving the record, named scopes, Extlib-like callbacks, validation errors, and observers. For example,

class Vehicle
  include DataMapper::Resource
  
  property :id, Serial
  property :state, String
  
  state_machine :initial => :parked do
    before_transition :parked => any - :parked, :do => :put_on_seatbelt
    after_transition any => :parked do |transition|
      self.seatbelt = 'off' # self is the record
    end
    around_transition :benchmark
    
    event :ignite do
      transition :parked => :idling
    end
    
    state :first_gear, :second_gear do
      validates_presence_of :seatbelt_on
    end
  end
  
  def put_on_seatbelt
    ...
  end
  
  def benchmark
    ...
    yield
    ...
  end
end

class VehicleObserver
  include DataMapper::Observer
  
  observe Vehicle
  
  # Callback for :ignite event *before* the transition is performed
  before_transition :on => :ignite do |transition|
    # log message (self is the record)
  end
  
  # Generic transition callback *after* the transition is performed
  after_transition do |transition|
    Audit.log(self, transition) # self is the record
  end
  
  around_transition do |transition, block|
    # mark start time
    block.call
    # mark stop time
  end
  
  # Generic callback after the transition fails to perform
  after_transition_failure do |transition|
    Audit.log(self, transition) # self is the record
  end
end

Note that the DataMapper::Observer integration is optional and only available when the dm-observer library is installed.

For more information about the various behaviors added for DataMapper state machines, see StateMachine::Integrations::DataMapper.

Mongoid

The Mongoid integration adds support for automatically saving the record, basic scopes, validation errors, and observers. For example,

class Vehicle
  include Mongoid::Document
  
  state_machine :initial => :parked do
    before_transition :parked => any - :parked, :do => :put_on_seatbelt
    after_transition any => :parked do |vehicle, transition|
      vehicle.seatbelt = 'off' # self is the record
    end
    around_transition :benchmark
    
    event :ignite do
      transition :parked => :idling
    end
    
    state :first_gear, :second_gear do
      validates_presence_of :seatbelt_on
    end
  end
  
  def put_on_seatbelt
    ...
  end
  
  def benchmark
    ...
    yield
    ...
  end
end

class VehicleObserver < Mongoid::Observer
  # Callback for :ignite event *before* the transition is performed
  def before_ignite(vehicle, transition)
    # log message
  end
  
  # Generic transition callback *after* the transition is performed
  def after_transition(vehicle, transition)
    Audit.log(vehicle, transition)
  end
end

For more information about the various behaviors added for Mongoid state machines, see StateMachine::Integrations::Mongoid.

MongoMapper

The MongoMapper integration adds support for automatically saving the record, basic scopes, validation errors and callbacks. For example,

class Vehicle
  include MongoMapper::Document
  
  state_machine :initial => :parked do
    before_transition :parked => any - :parked, :do => :put_on_seatbelt
    after_transition any => :parked do |vehicle, transition|
      vehicle.seatbelt = 'off' # self is the record
    end
    around_transition :benchmark
    
    event :ignite do
      transition :parked => :idling
    end
    
    state :first_gear, :second_gear do
      validates_presence_of :seatbelt_on
    end
  end
  
  def put_on_seatbelt
    ...
  end
  
  def benchmark
    ...
    yield
    ...
  end
end

For more information about the various behaviors added for MongoMapper state machines, see StateMachine::Integrations::MongoMapper.

Sequel

Like the ActiveRecord integration, the Sequel integration adds support for database transactions, automatically saving the record, named scopes, validation errors and callbacks. For example,

class Vehicle < Sequel::Model
  plugin :validation_class_methods
  
  state_machine :initial => :parked do
    before_transition :parked => any - :parked, :do => :put_on_seatbelt
    after_transition any => :parked do |transition|
      self.seatbelt = 'off' # self is the record
    end
    around_transition :benchmark
    
    event :ignite do
      transition :parked => :idling
    end
    
    state :first_gear, :second_gear do
      validates_presence_of :seatbelt_on
    end
  end
  
  def put_on_seatbelt
    ...
  end
  
  def benchmark
    ...
    yield
    ...
  end
end

For more information about the various behaviors added for Sequel state machines, see StateMachine::Integrations::Sequel.

Additional Topics

Explicit vs. Implicit Event Transitions

Every event defined for a state machine generates an instance method on the class that allows the event to be explicitly triggered. Most of the examples in the state_machine documentation use this technique. However, with some types of integrations, like ActiveRecord, you can also implicitly fire events by setting a special attribute on the instance.

Suppose you're using the ActiveRecord integration and the following model is defined:

class Vehicle < ActiveRecord::Base
  state_machine :initial => :parked do
    event :ignite do
      transition :parked => :idling
    end
  end
end

To trigger the ignite event, you would typically call the Vehicle#ignite method like so:

vehicle = Vehicle.create    # => #<Vehicle id=1 state="parked">
vehicle.ignite              # => true
vehicle.state               # => "idling"

This is referred to as an explicit event transition. The same behavior can also be achieved implicitly by setting the state event attribute and invoking the action associated with the state machine. For example:

vehicle = Vehicle.create        # => #<Vehicle id=1 state="parked">
vehicle.state_event = "ignite"  # => "ignite"
vehicle.save                    # => true
vehicle.state                   # => "idling"
vehicle.state_event             # => nil

As you can see, the ignite event was automatically triggered when the save action was called. This is particularly useful if you want to allow users to drive the state transitions from a web API.

See each integration's API documentation for more information on the implicit approach.

Symbols vs. Strings

In all of the examples used throughout the documentation, you'll notice that states and events are almost always referenced as symbols. This isn't a requirement, but rather a suggested best practice.

You can very well define your state machine with Strings like so:

class Vehicle
  state_machine :initial => 'parked' do
    event 'ignite' do
      transition 'parked' => 'idling'
    end
    
    # ...
  end
end

You could even use numbers as your state / event names. The important thing to keep in mind is that the type being used for referencing states / events in your machine definition must be consistent. If you're using Symbols, then all states / events must use Symbols. Otherwise you'll encounter the following error:

class Vehicle
  state_machine do
    event :ignite do
      transition :parked => 'idling'
    end
  end
end

# => ArgumentError: "idling" state defined as String, :parked defined as Symbol; all states must be consistent

There is an exception to this rule. The consistency is only required within the definition itself. However, when the machine's helper methods are called with input from external sources, such as a web form, state_machine will map that input to a String / Symbol. For example:

class Vehicle
  state_machine :initial => :parked do
    event :ignite do
      transition :parked => :idling
    end
  end
end

v = Vehicle.new     # => #<Vehicle:0xb71da5f8 @state="parked">
v.state?('parked')  # => true
v.state?(:parked)   # => true

Note that none of this actually has to do with the type of the value that gets stored. By default, all state values are assumed to be string -- regardless of whether the state names are symbols or strings. If you want to store states as symbols instead you'll have to be explicit about it:

class Vehicle
  state_machine :initial => :parked do
    event :ignite do
      transition :parked => :idling
    end
    
    states.each do |state|
      self.state(state.name, :value => state.name.to_sym)
    end
  end
end

v = Vehicle.new     # => #<Vehicle:0xb71da5f8 @state=:parked>
v.state?('parked')  # => true
v.state?(:parked)   # => true

Syntax flexibility

Although state_machine introduces a simplified syntax, it still remains backwards compatible with previous versions and other state-related libraries by providing some flexibility around how transitions are defined. See below for an overview of these syntaxes.

Verbose syntax

In general, it's recommended that state machines use the implicit syntax for transitions. However, you can be a little more explicit and verbose about transitions by using the :from, :except_from, :to, and :except_to options.

For example, transitions and callbacks can be defined like so:

class Vehicle
  state_machine :initial => :parked do
    before_transition :from => :parked, :except_to => :parked, :do => :put_on_seatbelt
    after_transition :to => :parked do |transition|
      self.seatbelt = 'off' # self is the record
    end
    
    event :ignite do
      transition :from => :parked, :to => :idling
    end
  end
end

Transition context

Some flexibility is provided around the context in which transitions can be defined. In almost all examples throughout the documentation, transitions are defined within the context of an event. If you prefer to have state machines defined in the context of a state either out of preference or in order to easily migrate from a different library, you can do so as shown below:

class Vehicle
  state_machine :initial => :parked do
    ...
    
    state :parked do
      transition :to => :idling, :on => [:ignite, :shift_up], :if => :seatbelt_on?
      
      def speed
        0
      end
    end
    
    state :first_gear do
      transition :to => :second_gear, :on => :shift_up
      
      def speed
        10
      end
    end
    
    state :idling, :first_gear do
      transition :to => :parked, :on => :park
    end
  end
end

In the above example, there's no need to specify the from state for each transition since it's inferred from the context.

You can also define transitions completely outside the context of a particular state / event. This may be useful in cases where you're building a state machine from a data store instead of part of the class definition. See the example below:

class Vehicle
  state_machine :initial => :parked do
    ...
    
    transition :parked => :idling, :on => [:ignite, :shift_up]
    transition :first_gear => :second_gear, :second_gear => :third_gear, :on => :shift_up
    transition [:idling, :first_gear] => :parked, :on => :park
    transition [:idling, :first_gear] => :parked, :on => :park
    transition all - [:parked, :stalled] => :stalled, :unless => :auto_shop_busy?
  end
end

Notice that in these alternative syntaxes:

  • You can continue to configure :if and :unless conditions
  • You can continue to define from states (when in the machine context) using the all, any, and same helper methods

Static / Dynamic definitions

In most cases, the definition of a state machine is static. That is to say, the states, events and possible transitions are known ahead of time even though they may depend on data that's only known at runtime. For example, certain transitions may only be available depending on an attribute on that object it's being run on. All of the documentation in this library define static machines like so:

class Vehicle
  state_machine :state, :initial => :parked do
    event :park do
      transition [:idling, :first_gear] => :parked
    end
    
    ...
  end
end

However, there may be cases where the definition of a state machine is dynamic. This means that you don't know the possible states or events for a machine until runtime. For example, you may allow users in your application to manage the state machine of a project or task in your system. This means that the list of transitions (and their associated states / events) could be stored externally, such as in a database. In a case like this, you can define dynamically-generated state machines like so:

class Vehicle
  attr_accessor :state
  
  # Make sure the machine gets initialized so the initial state gets set properly
  def initialize(*)
    super
    machine
  end
  
  # Replace this with an external source (like a db)
  def transitions
    [
      {:parked => :idling, :on => :ignite},
      {:idling => :first_gear, :first_gear => :second_gear, :on => :shift_up}
      # ...
    ]
  end
  
  # Create a state machine for this vehicle instance dynamically based on the
  # transitions defined from the source above
  def machine
    vehicle = self
    @machine ||= Machine.new(vehicle, :initial => :parked, :action => :save) do
      vehicle.transitions.each {|attrs| transition(attrs)}
    end
  end
  
  def save
    # Save the state change...
    true
  end
end

# Generic class for building machines
class Machine
  def self.new(object, *args, &block)
    machine_class = Class.new
    machine = machine_class.state_machine(*args, &block)
    attribute = machine.attribute
    action = machine.action
    
    # Delegate attributes
    machine_class.class_eval do
      define_method(:definition) { machine }
      define_method(attribute) { object.send(attribute) }
      define_method("#{attribute}=") {|value| object.send("#{attribute}=", value) }
      define_method(action) { object.send(action) } if action
    end
    
    machine_class.new
  end
end

vehicle = Vehicle.new                   # => #<Vehicle:0xb708412c @state="parked" ...>
vehicle.state                           # => "parked"
vehicle.machine.ignite                  # => true
vehicle.machine.state                   # => "idling
vehicle.state                           # => "idling"
vehicle.machine.state_transitions       # => [#<StateMachine::Transition ...>]
vehicle.machine.definition.states.keys  # => :first_gear, :second_gear, :parked, :idling

As you can see, state_machine provides enough flexibility for you to be able to create new machine definitions on the fly based on an external source of transitions.

Core Extensions

By default, state_machine extends the Ruby core with a state_machine method on Class. All other parts of the library are confined within the StateMachine namespace. While this isn't wholly necessary, it also doesn't have any performance impact and makes it truly feel like an extension to the language. This is very similar to the way that you'll find yaml, json, or other libraries adding a simple method to all objects just by loading the library.

However, if you'd like to avoid having state_machine add this extension to the Ruby core, you can do so like so:

require 'state_machine/core'

class Vehicle
  extend StateMachine::MacroMethods
  
  state_machine do
    # ...
  end
end

If you're using a gem loader like Bundler, you can explicitly indicate which file to load:

# In Gemfile
...
gem 'state_machine', :require => 'state_machine/core'

Tools

Generating graphs

This library comes with built-in support for generating di-graphs based on the events, states, and transitions defined for a state machine using GraphViz. This requires that both the ruby-graphviz gem and graphviz library be installed on the system.

Examples

To generate a graph for a specific file / class:

rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle

To save files to a specific path:

rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files

To customize the image format / orientation:

rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg ORIENTATION=landscape

See http://rdoc.info/github/glejeune/Ruby-Graphviz/Constants for the list of supported image formats. If resolution is an issue, the svg format may offer better results.

To generate multiple state machine graphs:

rake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car

To use human state / event names:

rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle HUMAN_NAMES=true

Note that this will generate a different file for every state machine defined in the class. The generated files will use an output filename of the format #{class_name}_#{machine_name}.#{format}.

For examples of actual images generated using this task, see those under the examples folder.

Interactive graphs

Jean Bovet's Visual Automata Simulator is a great tool for "simulating, visualizing and transforming finite state automata and Turing Machines". It can help in the creation of states and events for your models. It is cross-platform, written in Java.

Generating documentation

If you use YARD to generate documentation for your projects, state_machine can be enabled to generate API docs for auto-generated methods from each state machine definition as well as providing embedded visualizations.

See the generated API documentation under the examples folder to see what the output looks like.

To enable the YARD integration, you'll need to add state_machine to the list of YARD's plugins by editing the global YARD config:

~/.yard/config:

load_plugins: true
autoload_plugins:
  - state_machine

Once enabled, simply generate your documentation like you normally do.

Note that this only works for Ruby 1.9+.

Web Frameworks

Ruby on Rails

Integrating state_machine into your Ruby on Rails application is straightforward and provides a few additional features specific to the framework. To get started, following the steps below.

1. Install the gem

If using Rails 2.x:

# In config/environment.rb
...
Rails::Initializer.run do |config|
  ...
  config.gem 'state_machine', :version => '~> 1.0'
  ...
end

If using Rails 3.x or up:

# In Gemfile
...
gem 'state_machine'
gem 'ruby-graphviz', :require => 'graphviz' # Optional: only required for graphing

As usual, run bundle install to load the gems.

2. Create a model

Create a model with a field to store the state, along with other any other fields your application requires:

$ rails generate model Vehicle state:string
$ rake db:migrate

3. Configure the state machine

Add the state machine to your model. Following the examples above, app/models/vehicle.rb might become:

class Vehicle < ActiveRecord::Base
  state_machine :initial => :parked do
    before_transition :parked => any - :parked, :do => :put_on_seatbelt
    ...
  end
end

Rake tasks

There is a special integration Rake task for generating state machines for classes used in a Ruby on Rails application. This task will load the application environment, meaning that it's unnecessary to specify the actual file to load.

For example,

rake state_machine:draw CLASS=Vehicle

If you are using this library as a gem in Rails 2.x, the following must be added to the end of your application's Rakefile in order for the above task to work:

require 'tasks/state_machine'

Merb

Rake tasks

Like Ruby on Rails, there is a special integration Rake task for generating state machines for classes used in a Merb application. This task will load the application environment, meaning that it's unnecessary to specify the actual files to load.

For example,

rake state_machine:draw CLASS=Vehicle

Testing

To run the core test suite (does not test any of the integrations):

bundle install
bundle exec rake test

To run integration tests:

bundle install
rake appraisal:install
rake appraisal:test

You can also test a specific version:

rake appraisal:active_model-3.0.0 test
rake appraisal:active_record-2.0.0 test
rake appraisal:data_mapper-0.9.4 test
rake appraisal:mongoid-2.0.0 test
rake appraisal:mongo_mapper-0.5.5 test
rake appraisal:sequel-2.8.0 test

Caveats

The following caveats should be noted when using state_machine:

  • Overridden event methods won't get invoked when using attribute-based event transitions
  • DataMapper: Attribute-based event transitions are disabled when using dm-validations 0.9.4 - 0.9.6
  • DataMapper: Transitions cannot persist states when run from after :create / :save callbacks
  • JRuby / Rubinius: around_transition callbacks in ORM integrations won't work on JRuby since it doesn't support continuations
  • Factory Girl: Dynamic initial states don't work because of the way factory_girl builds objects. You can work around this in a few ways:
    1. Use a default state that is common across all objects and rely on events to determine the actual initial state for your object.
    2. Assuming you're not using state-driven behavior on initialization, you can re-initialize states after the fact:
# Re-initialize in FactoryGirl
FactoryGirl.define do
  factory :vehicle do
    after_build {|user| user.send(:initialize_state_machines, :dynamic => :force)}
  end
end

# Alternatively re-initialize in your model
class Vehicle < ActiveRecord::Base
  ...
  before_validation :on => :create {|user| user.send(:initialize_state_machines, :dynamic => :force)}
end

Dependencies

Ruby versions officially supported and tested:

  • Ruby (MRI) 1.8.6+
  • JRuby (1.8, 1.9)
  • Rubinius (1.8, 1.9)

ORM versions officially supported and tested:

If graphing state machine:

state_machine's People

Contributors

amatsuda avatar bdimcheff avatar chinasaur avatar darkhelmet avatar durran avatar jashmenn avatar joelind avatar laserlemon avatar michaelklishin avatar mixr avatar morgoth avatar nathanl avatar nblumoe avatar obrie avatar rdpoor avatar sanemat avatar shanghaichris avatar stefanpenner 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  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

state_machine's Issues

Conflicts with no-peeping-toms plugin

I have come across some serious issues with using the no-peeping-toms plugin along with the state machine plugin. If you have any state machine configured in any loaded file, then the no-peeping system breaks, and also the specs fail. I am working on a solution to this but wanted to note it on both repositories. Perhaps someone else may come up with a fix first.

Missing i18n/internationalization and/or documentation

Paul Strong opened this issue

The state_machine README says that state_machine supports internationalization, but I cannot find any further explanation of how this works. After going through the API I cannot find anyway of doing internationalization of state names short of creating a i18n module from scratch.

Has this feature been implemented yet? I’m sorry if I just overlooked something in the api or docs.

Any pointers or response would be greatly appreciated.

original LH ticket

This ticket has 0 attachment(s).

State events don't call event methods

Aaron Pfeifer opened this issue

When you use a state event and you’ve overridden the event method, the method won’t get called. For example,

class Vehicle < ActiveRecord::Base
  state_machine :initial => :parked do
    event :ignite do
      transition :parked => :idling
    end
  end

  def ignite(*args)
    raise ArgumentError # Never gets called
  end
end

v = Vehicle.new(:state_event => &rsquo;ignite&rsquo;)
v.save

This transitions the object, but never calls #ignite which is unexpected.

original LH ticket

This ticket has 1 attachment(s).

extensions.rb syntax error in ruby 1.8.6

tsechingho opened this issue

I found the fix [06e8e84]:http://github.com/pluginaweek/state_machine/commit/06e8e845b583fc229ee023dd63abc390e266472e has a syntax error in ruby 1.8.6.

everything works fine in my development box: ruby 1.8.7 (2008-08-11 patchlevel 72) [powerpc-darwin8]

but I got an error in my production box: ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-linux]

@@@
vendor/plugins/state_machine/lib/state_machine/extensions.rb:39: syntax error, unexpected ’,’, expecting ’|’ (SyntaxError)
...e_method(:initialize) {|*args, &block| initialize_with_state...
^
@@@

So I change the line 39 of extensions.rb from

@@@ ruby
define_method(:initialize) {|_args, &block| initialize_with_state_machine(_args, &block) }
@@@

to

@@@ ruby
define_method(:initialize) {|args, block| initialize_with_state_machine(*args, &block) }
@@@

, and it works in both boxes.

original LH ticket

This ticket has 0 attachment(s).

before callbacks run twice during save

Aaron Pfeifer opened this issue

When using event attributes (i.e. the RESTful means of running events), the before callbacks get run twice:

  1. Immediately before validation
  2. Immediately before save

before callbacks should always be called only once during the process.

original LH ticket

This ticket has 0 attachment(s).

Broken with DataMapper edge

Daniel Neighman opened this issue

Hi,

I updated datamapper to the edge version today for an unrelated reason, and it turns out this breaks state_machine.

I get an exception raised when trying to create a new record, because the state machine is trying to set a property when datamapper has not yet initialized. DataMapper is moving to a state machine to track the dirtiness of an object and the state machine is not yet set when trying to set the property in this method:

https://gist.github.com/08453296ebd63a3bd8dd

In order to use a datamapper object, it’s required that super be called before trying to use any datamapper behavior. If you absolutely have to setup the state machine first, then I think you’d have to talk to dkubb about how this could be dealt with.

I’m sorry I can’t provide more information. This is still a bit in flux if the initialize_state_machines call is required before super.

Cheers
Daniel

original LH ticket

This ticket has 0 attachment(s).

Problem with parent-child relations in tests

Dennis Theisen opened this issue

  1. Ok, I have a Blog which has Articles which has Comments (all ActiveRecord).

  2. The StateMachine is used in the comments (for moderation/trash/spam states).

  3. When running the following BlogTest, I get a strange exception (at least for me it’s strange):

    @@@ ruby
    assert Blog.create(:title => ’hey’)
    @@@

This is the exception:

@NoMethodError: undefined method `create’ for PluginAWeek::StateMachine::Event:Class
method log in event.rb at line 31
method send in association_collection.rb at line 259
method method_missing in association_collection.rb at line 259
method with_scope in base.rb at line 1841@

The statemachine isn’t used in the Blog at all, only in comments which is a second class child. I also tried to install the @plugin_test_helper@ (which is only needed for running the StateMachineTests themselves anyway, I guess)

Do you have any tip, what I am doing wrong? Or is this really a bug?

original LH ticket

This ticket has 0 attachment(s).

railite_name is depracated

Wojciech Wnętrzak opened this issue

On the rails master branch railtie_name is deprecated. When environment is loaded I get:
DEPRECATION WARNING: railtie_name is deprecated and has no effect. (called from class:Railtie at ~/.rvm/gems/ruby-1.9.1-p378/gems/state_machine-0.9.0/lib/state_machine/initializers/rails.rb:2)

original LH ticket

This ticket has 0 attachment(s).

Loopback transitions don't appear to update Rails' record

onitony opened this issue

It seems that if a State Machine instance performs a transition that loops back into it’s previous state, the record is not saved (because no fields have been modified?). The desired behaviour though, is for updated_at to update, to note the time the transition took place (same as it happens in non-loopback transitions).

original LH ticket

This ticket has 1 attachment(s).

Adding StateMachine to a rails3 project makes 'rails generate' unhappy

Sven Riedel opened this issue

Environment: Rails3 beta3, Ruby 1.9.2

Added "gem ’statemachine’" to my Gemfile, ran "bundle install --relock". Running "rails generate" will then result in:

/home/sr/.bundle/ruby/1.9.1/gems/statemachine-1.0.0/lib/statemachine/action_invokation.rb:58:in `instance_exec&rsquo;: wrong number of arguments (1 for 0) (ArgumentError)

Tracing the instance_exec calls shows two, with the error generated on the second call:
Sending __instance_exec_82285100_85565950 with #MyApp::Application:0xa396f0c
Sending __instance_exec_82285100_85768070 with #MyApp::Application:0xa396f0c

original LH ticket

This ticket has 0 attachment(s).

Transition to different states depending on success of an action

Philippe Creux opened this issue

Hey,

I’m looking for a good practice to transition to two different states depending on the success or failure of an event?

For example, I want the following:

BalanceTransfer.create
 => <BalanceTransfer, @state => :new >

# on success
BalanceTransfer.create.perform_transfer
 => <BalanceTransfer, @state => :completed >

# on failure
BalanceTransfer.create.perform_transfer
 => <BalanceTransfer, @state => :error >

So far, I’ve done that using a method ’perform_transfer’:

def perform_transfer
  return unless self.state == :new
  # perform the transfer...
  if success
    complete!
  else
    error!
  end
end

It looks like re-inventing the wheel and it’s quite ugly. Am I missing a feature built it state_machine? Is there any good pratice for doing such a thing?

Thanks!

original LH ticket

This ticket has 0 attachment(s).

ActiveRecord::VERSION

germaninthetown opened this issue

Before all, thank you Aaron for this great gem. It’s amazing.

However I got an error while using it in my rails 2.1 application (yes, I know, it’s old).

$ rails -v
Rails 2.1.0

$ ruby -v
ruby 1.8.6 (2008-08-08 patchlevel 286) [i686-linux]

I have a model Cart

class Cart < ActiveRecord::Base

...

state_machine :fullfilment_status, :initial => :incomplete do
state :waiting_for_cutout_processing, :value => "Waiting for Manual Cutout Processing"
state :processing, :value => "Cutouts being Processed"
state :waiting_for_customer_approval, :value => "Waiting for Customer Approval"
state :ready_to_print, :value => "Ready to Print"
state :being_printed, :value => "Being Printed"
state :shipped, :value => "Shipped"
state :cancelled, :value => "Cancelled"
state :incomplete, :value => "incomplete"

state :reclamation,                   :value => "Reclamation - being reprinted"

event :purchase do               
  transition :incomplete => :waiting_for_cutout_processing     
end

event :wait_for_approval do
  transition :waiting_for_cutout_processing => :waiting_for_customer_approval
end

event :process do 
  transition :waiting_for_cutout_processing => :processing, :waiting_for_customer_approval => :processing
end

event :cancel do 
  transition all => :cancelled
end

event :ship do
  transition :processed => :shipped
end

event :print do
  transition :ready_to_print => :processing 
end

event :reclaim do
  transition :shipped => :being_printed
end

end

...
end

but when I got to the ./script/console I got the error:

=> Cart.last
NameError: uninitialized constant ActiveRecord::VERSION
from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:278:in load_missing_constant&rsquo; from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:467:inconst_missing’
from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/integrations/active_record.rb:468:in define_scope&rsquo; from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/integrations/active_record.rb:446:increate_with_scope’
from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/machine.rb:1523:in send&rsquo; from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/machine.rb:1523:indefine_scopes’
from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/machine.rb:1520:in each&rsquo; from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/machine.rb:1520:indefine_scopes’
from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/machine.rb:1519:in each&rsquo; from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/machine.rb:1519:indefine_scopes’
from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/machine.rb:445:in initialize&rsquo; from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/machine.rb:332:innew’
from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine/machine.rb:332:in find_or_create&rsquo; from /opt/ruby-enterprise-1.8.6-20090201/lib/ruby/gems/1.8/gems/state_machine-0.9.0/lib/state_machine.rb:378:instate_machine’
from /home/german/projects/ltlprints/app/models/cart.rb:11
from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:215:in load_without_new_constant_marking&rsquo; from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:215:inload_file’
from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:354:in new_constants_in&rsquo; from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:214:inload_file’
from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:95:in require_or_load&rsquo; from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:260:inload_missing_constant’
from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:467:in const_missing&rsquo; from /home/german/.gem/ruby/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:479:inconst_missing’
from (irb):1>>

I tried to find the error and finally got the code working by adding the following line

require ’active_record/version’

to the method self.extended (line 325) in /state_machine-0.9.0/lib/state_machine/integrations/active_record.rb file.

I’m not quite sure is this error appearing for me only, or I just did something wrong elsewhere in the code, but I think it would be great, if you’ll add this line to your code so other developers wouldn’t notice this error in future. How do you think?

Thanks,
Dmitrii Samoilov.

original LH ticket

This ticket has 0 attachment(s).

Rails 3

dan morris opened this issue

Any idea why i’m getting this error?

undefined method `state_machine’

I’ve installed the 0.9.2 gem in a Rails beta 3 app.

I’ve tried a few things and got a different problem before where transitioning wouldn’t work at all.

Many thanks!

original LH ticket

This ticket has 0 attachment(s).

(before|after)_transition doesn't work when using state_event

qoobaa opened this issue

I’ve got following user model:

@@@ ruby
class User < ActiveRecord::Base
state_machine do
event :event_one do
transition nil => :state_one
end

after_transition :on => :event_one, :do => :something

end

def something
throw :something
end
end
@@@

When I create model with state_event, it doesn’t call "something" method. When I use transaction method (user.event_one) everything works fine.

@@@ ruby
user = User.new(:state_event => "event_one")
user.save

=> true

user.state_one?

=> true

user = User.new
user.event_one

ArgumentError: uncaught throw :something...

@@@

original LH ticket

This ticket has 1 attachment(s).

Error in API Documentation

Jon opened this issue

I noticed an error in the API that may confuse some people.

@@@ ruby
state_machine :state, :initial => ’idling’ do
end
@@@

Yet, the example shows the initial state to be ’parked’.

@@@ ruby
vehicle = Vehicle.create # => #<Vehicle id: 1, seatbelt_on: false, state: "parked">
@@@

Thanks for the plugin!

original LH ticket

This ticket has 0 attachment(s).

state_machine only works for new records

Tom opened this issue

I can’t seem to get the state_machine gem to work on existing records (it works correctly on new records).

Here’s my model:

class Comment < ActiveRecord::Base
  state_machine :state, :initial => :pending do
    event :publish do
      transition all => :published
    end
  end
end

and here’s an IRB session that demonstrates the issue (I did ActiveRecord::Base.logger = Logger.new(STDOUT) to make it easier to read):

>> c = Comment.new
=> #<Comment id: nil, song_id: nil, author: nil, body: nil, created_at: nil, updated_at: nil, state: "pending">
>> c.state
=> "pending"
>> c.publish
  Comment Create (0.6ms)   INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES(&rsquo;2009-11-02 02:44:37&rsquo;, NULL, NULL, NULL, &rsquo;2009-11-02 02:44:37&rsquo;, &rsquo;published&rsquo;)
=> true
>> Comment.last.state
  Comment Load (0.4ms)   SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "published"
>> c = Comment.create
  Comment Create (0.5ms)   INSERT INTO "comments" ("updated_at", "body", "author", "song_id", "created_at", "state") VALUES(&rsquo;2009-11-02 02:44:47&rsquo;, NULL, NULL, NULL, &rsquo;2009-11-02 02:44:47&rsquo;, &rsquo;pending&rsquo;)
=> #<Comment id: 4, song_id: nil, author: nil, body: nil, created_at: "2009-11-02 02:44:47", updated_at: "2009-11-02 02:44:47", state: "pending">
>> c.publish
=> true
>> c.save
=> true
>> Comment.last.state
  Comment Load (0.4ms)   SELECT * FROM "comments" ORDER BY comments.id DESC LIMIT 1
=> "pending"

I.e., everything works fine when I publish an unsaved comment, but when I try to publish a comment that’s already saved, nothing happens.

Would really appreciate some help here -- I’m literally pulling my hair out (of the left side of my face)!

original LH ticket

This ticket has 0 attachment(s).

before_filters now invoked on event

Karim Helal opened this issue

Hi all,

I have a model that relies on state_machine to manage its different states. One particular event requires a before_transition as it needs to build a join table fore doing the transition. Unfortunately it doesn’t work.

class DocumentSet < ActiveRecord::Base

  state_machine :state, :initial => :draft do
    # Callbacks
    before_transition :on=>:submit, :do=>:populate_join_table

    # States
    state :draft
    state :submitted

    # Events
    event :submit do transition :draft=>:submitted, :if=>:can_submit? end
  end

def populate_join_table
   puts &rsquo;::::::::: INSIDE POPULATE_JOIN_TABLE&rsquo;
end

def can_submit?    
  raise "Document Set has no Document(s)" if self.document_versions.blank?
  true
end

Now when I do DocumentSet.submit, it actually never goes into the populate_join_table as it evaluates the can_submit? as false.
What am I missing?

Thx!

original LH ticket

This ticket has 0 attachment(s).

The interpolation key format might depend on I18n not ActiveRecord's version number

Jérémy Lecour opened this issue

Hi,

I’ve cloned state_machine and launched the test suite for ActiveRecord.
I have a Rails 2.3.x setup but the latest I18n gem.

The test suite gives me some warning about the interpolation key format, saying it’s deprecated.

I’ve looked in the source and it appears that you check the major version number for ActiveRecord to see if it’s Rails 2 or 3.
In fact I think that you should check for the version of the I18n gem instead, since it seems that it’s responsible for this API change.

I’m sorry not to provide any patch.

Thanks for state_machine. I’ve not yet really used it but it seems to be a very good piece of code ;-)

original LH ticket

This ticket has 0 attachment(s).

transition without save?

Aaron Gibralter opened this issue

I’ve encountered a few cases where I might want to call a transition without immediately saving the record.

For instance, on line 166 of user.rb (http://gist.github.com/16815) in one of my before_save callbacks, I want to transition to an "unverified" email. I don’t want to save in my before_save callback though... Also on lines 105 and 106 of user.rb, I want to make a few transitions at once. It seems wasteful to make 3 save calls in a row.

Anyway, does anyone have thoughts on this commit which adds the ability to call any event without saving? I do it by allowing events to be called with a "false" argument.

http://github.com/agibralter/state_machine/commit/c588b743763921ab9d60999cc2878d93544db492

P.S. I did encounter a few bugs in my UserObserver code as a result of this changeset... As you can see in user_observer.rb (http://gist.github.com/16815), it is possible for my after_unverify callback to get called before the user has been saved... as a result user doesn’t have an id yet. I don’t think this is a fault of my commit though, more a fault of my callback design.

original LH ticket

This ticket has 0 attachment(s).

"normal" membership state generator

Aaron Gibralter opened this issue

Have you ever thought of creating a standardized collection of state_machines that represents best practices for user-group membership related states?

I have a pretty complicated set of state_machines that represents a user being invited to a group (based roughly on, say, facebook’s group invite system):

@@@
state_machine :state_user, :initial => :pending, :namespace => :by_user do

before_transition :to => :accepted do |membership|
membership.activate(false)
true
end

before_transition :to => :suspended do |membership|
membership.deactivate(false)
true
end

before_transition :from => :suspended do |membership|
membership.activate(false)
true
end

event :request do
transition :to => :requested, :from => :pending, :if => lambda { |membership| membership.by_admin_pending? }
transition :to => :accepted, :from => [:pending, :rejected, :suspended], :if => lambda { |membership| membership.by_admin_requested? }
transition :to => :requested, :from => :suspended, :if => lambda { |membership| membership.by_admin_accepted? }
end

event :cancel do
transition :to => :pending, :from => :requested, :if => lambda { |membership| membership.by_admin_pending? }
end

event :reject do
transition :to => :rejected, :from => :pending, :if => lambda { |membership| membership.by_admin_requested? }
end

Can suspend if you are active

Last admin cannot suspend his or herself

event :suspend do
transition :to => :suspended, :from => [:accepted, :requested], :if => lambda { |membership| !membership.admin? || membership.group.admins.size > 1 }
end
end

state_machine :state_admin, :initial => :pending, :namespace => :by_admin do

before_transition :to => :accepted do |membership|
membership.activate(false)
true
end

before_transition :to => :suspended do |membership|
membership.deactivate(false)
true
end

before_transition :from => :suspended do |membership|
membership.activate(false)
true
end

event :pre_accept do
transition :to => :accepted, :from => :pending
end

event :request do
transition :to => :requested, :from => :pending, :if => lambda { |membership| membership.by_user_pending? }
transition :to => :accepted, :from => [:pending, :rejected, :suspended], :if => lambda { |membership| membership.by_user_requested? }
transition :to => :requested, :from => :suspended, :if => lambda { |membership| membership.by_user_accepted? }
end

event :cancel do
transition :to => :pending, :from => :requested, :if => lambda { |membership| membership.by_user_pending? }
end

event :reject do
transition :to => :rejected, :from => :pending, :if => lambda { |membership| membership.by_user_requested? }
end

Cannot suspend other admins

event :suspend do
transition :to => :suspended, :from => [:accepted, :requested], :if => lambda { |membership| !membership.admin? }
end
end
@@@

This leads to the following full/combined states:
@@@

full states:

- nothing

- member

- requested_by_user

- requested_by_admin

- rejected_by_user

- rejected_by_admin

- suspended_by_user

- suspended_by_admin

def full_state
case [state_user, state_admin]
when %w(pending pending)
:nothing
when %w(pending requested)
:requested_by_admin
when %w(requested pending)
:requested_by_user
when %w(requested accepted)
:member
when %w(requested rejected)
:rejected_by_admin
when %w(requested suspended)
:suspended_by_admin
when %w(accepted requested)
:member
when %w(accepted suspended)
:suspended_by_admin
when %w(rejected requested)
:rejected_by_user
when %w(suspended requested)
:suspended_by_user
when %w(suspended accepted)
:suspended_by_user
else
raise UnanticipatedState
end
end
@@@

This is /really/ hackish and cumbersome though... any thoughts?

I just thought it might help in terms of making state_machine the ubiquitous solution for ruby state handling by providing a plug-n-play solution for web app developers.

Thanks for the shout-out in the blog post btw (http://www.pluginaweek.org/2009/03/08/state_machine-one-machine-to-rule-them-all/)!

original LH ticket

This ticket has 0 attachment(s).

Double arrows when drawing a state machine.

Nicolas Blanco opened this issue

Hello,

when drawing the following state_machine for a User class, I obtain many ’double’ arrows.
See the drawing included in this ticket.

@@@
state_machine :state, :initial => :pending do
event :subscribe do
transition :pending => :subscribing
end

event :wait_validation do
  transition [:subscribing, :refused] => :waiting_validation
end

event :activate do
  transition :waiting_validation => :active
end

event :delete do
  transition all - :deleted => :deleted
end

event :refuse do
  transition :waiting_validation => :refused
end

event :abort do
  transition :subscribing => :aborted
end

event :ban do
  transition :active => :banned
end

end
@@@

original LH ticket

This ticket has 4 attachment(s).

ActiveRecord 2.3.2 Test Failures

Patrick Klingemann opened this issue

I am having trouble getting my state machine to set the initial state, it was always coming up nil in my staging environment. It works fine in production and development. So I decided to run the tests on state_machine and I’m receiving failures. Is this a configuration issue on my server? Below are the failures:

  1. Failure:
    test_should_allow_chaining_scopes(ActiveRecordTest::MachineTest)
    [./test/unit/integrations/active_record_test.rb:142:in test_should_allow_chaining_scopes&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <[#<#Class:0x2b7eaef45e40 id: 2, state: nil>]> expected but was
    <[]>.

  2. Failure:
    test_should_only_include_records_with_state_in_singular_with_scope(ActiveRecordTest::MachineTest)
    [./test/unit/integrations/active_record_test.rb:101:in test_should_only_include_records_with_state_in_singular_with_scope&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <[#<#Class:0x2b7eaf11bf80 id: 1, state: nil>]> expected but was
    <[]>.

  3. Failure:
    test_should_only_include_records_with_states_in_plural_with_scope(ActiveRecordTest::MachineTest)
    [./test/unit/integrations/active_record_test.rb:112:in test_should_only_include_records_with_states_in_plural_with_scope&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <[#<#Class:0x2b7eaf0c27f0 id: 1, state: nil>,
    #<#Class:0x2b7eaf0c27f0 id: 2, state: nil>]> expected but was
    <[]>.

  4. Failure:
    test_should_only_include_records_without_state_in_singular_without_scope(ActiveRecordTest::MachineTest)
    [./test/unit/integrations/active_record_test.rb:123:in test_should_only_include_records_without_state_in_singular_without_scope&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <[#<#Class:0x2b7eaf03b098 id: 1, state: nil>]> expected but was
    <[]>.

  5. Failure:
    test_should_only_include_records_without_states_in_plural_without_scope(ActiveRecordTest::MachineTest)
    [./test/unit/integrations/active_record_test.rb:135:in test_should_only_include_records_without_states_in_plural_without_scope&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <[#<#Class:0x2b7eaefef710 id: 1, state: nil>,
    #<#Class:0x2b7eaefef710 id: 2, state: nil>]> expected but was
    <[]>.

  6. Failure:
    test_should_use_machine_default(ActiveRecordTest::MachineWithColumnDefaultTest)
    [./test/unit/integrations/active_record_test.rb:369:in test_should_use_machine_default&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    <"idling">.

  7. Failure:
    test_should_return_true_for_predicate_if_matches_current_value(ActiveRecordTest::MachineWithColumnStateAttributeTest)
    [./test/unit/integrations/active_record_test.rb:415:in test_should_return_true_for_predicate_if_matches_current_value&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    is not true.

  8. Failure:
    test_should_test_for_existence_on_predicate_without_parameters(ActiveRecordTest::MachineWithColumnStateAttributeTest)
    [./test/unit/integrations/active_record_test.rb:404:in test_should_test_for_existence_on_predicate_without_parameters&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    is not true.

  9. Failure:
    test_should_not_set_initial_state_after_already_initialized(ActiveRecordTest::MachineWithDynamicInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:351:in test_should_not_set_initial_state_after_already_initialized&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  10. Failure:
    test_should_set_attributes_prior_to_after_initialize_hook(ActiveRecordTest::MachineWithDynamicInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:333:in test_should_set_attributes_prior_to_after_initialize_hook&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  11. Failure:
    test_should_set_initial_state_after_setting_attributes(ActiveRecordTest::MachineWithDynamicInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:346:in test_should_set_initial_state_after_setting_attributes&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"nil"> expected but was
    .

  12. Failure:
    test_should_set_initial_state_on_created_object(ActiveRecordTest::MachineWithDynamicInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:308:in test_should_set_initial_state_on_created_object&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  13. Failure:
    test_should_still_set_attributes(ActiveRecordTest::MachineWithDynamicInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:313:in test_should_still_set_attributes&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <1> expected but was
    .

  14. Failure:
    test_should_not_change_current_state(ActiveRecordTest::MachineWithFailedActionTest)
    [./test/unit/integrations/active_record_test.rb:806:in test_should_not_change_current_state&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  15. Failure:
    test_should_not_change_current_state(ActiveRecordTest::MachineWithFailedBeforeCallbacksTest)
    [./test/unit/integrations/active_record_test.rb:763:in test_should_not_change_current_state&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  16. Failure:
    test_should_allow_different_initial_state_when_dynamic(ActiveRecordTest::MachineWithInitializedStateTest)
    [./test/unit/integrations/active_record_test.rb:584:in test_should_allow_different_initial_state_when_dynamic&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"idling"> expected but was
    .

  17. Failure:
    test_should_allow_different_initial_state_when_static(ActiveRecordTest::MachineWithInitializedStateTest)
    [./test/unit/integrations/active_record_test.rb:578:in test_should_allow_different_initial_state_when_static&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"idling"> expected but was
    .

  18. Failure:
    test_should_use_default_state_if_protected(ActiveRecordTest::MachineWithInitializedStateTest)
    [./test/unit/integrations/active_record_test.rb:593:in test_should_use_default_state_if_protected&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  19. Error:
    test_should_include_state_in_changed_attributes(ActiveRecordTest::MachineWithLoopbackTest):
    NoMethodError: undefined method delete&rsquo; for nil:NilClass ./test/unit/integrations/active_record_test.rb:729:intest_should_include_state_in_changed_attributes’
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in __send__&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:inrun’

  20. Failure:
    test_should_return_true_for_predicate_if_matches_current_value(ActiveRecordTest::MachineWithNonColumnStateAttributeDefinedTest)
    [./test/unit/integrations/active_record_test.rb:474:in test_should_return_true_for_predicate_if_matches_current_value&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    is not true.

  21. Failure:
    test_should_set_initial_state_on_created_object(ActiveRecordTest::MachineWithNonColumnStateAttributeDefinedTest)
    [./test/unit/integrations/active_record_test.rb:482:in test_should_set_initial_state_on_created_object&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  22. Failure:
    test_should_only_include_records_with_subclass_states_in_with_scope(ActiveRecordTest::MachineWithOwnerSubclassTest)
    [./test/unit/integrations/active_record_test.rb:515:in test_should_only_include_records_with_subclass_states_in_with_scope&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <[#<#Class:0x2b7eaec67840 id: 1, state: nil>,
    #<#Class:0x2b7eaec67840 id: 2, state: nil>]> expected but was
    <[]>.

  23. Failure:
    test_should_only_include_records_without_subclass_states_in_without_scope(ActiveRecordTest::MachineWithOwnerSubclassTest)
    [./test/unit/integrations/active_record_test.rb:523:in test_should_only_include_records_without_subclass_states_in_without_scope&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <[#<#Class:0x2b7eaf20cb88 id: 1, state: nil>,
    #<#Class:0x2b7eaf20cb88 id: 2, state: nil>]> expected but was
    <[]>.

  24. Failure:
    test_should_be_invalid_if_validation_fails_within_state_scope(ActiveRecordTest::MachineWithStateDrivenValidationsTest)
    [./test/unit/integrations/active_record_test.rb:869:in test_should_be_invalid_if_validation_fails_within_state_scope&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    is not true.

  25. Failure:
    test_should_not_set_initial_state_after_already_initialized(ActiveRecordTest::MachineWithStaticInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:289:in test_should_not_set_initial_state_after_already_initialized&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  26. Failure:
    test_should_set_attributes_prior_to_after_initialize_hook(ActiveRecordTest::MachineWithStaticInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:271:in test_should_set_attributes_prior_to_after_initialize_hook&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  27. Failure:
    test_should_set_initial_state_before_setting_attributes(ActiveRecordTest::MachineWithStaticInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:284:in test_should_set_initial_state_before_setting_attributes&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  28. Failure:
    test_should_set_initial_state_on_created_object(ActiveRecordTest::MachineWithStaticInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:241:in test_should_set_initial_state_on_created_object&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  29. Failure:
    test_should_set_initial_state_with_nil_attributes(ActiveRecordTest::MachineWithStaticInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:246:in test_should_set_initial_state_with_nil_attributes&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <"parked"> expected but was
    .

  30. Failure:
    test_should_still_set_attributes(ActiveRecordTest::MachineWithStaticInitialStateTest)
    [./test/unit/integrations/active_record_test.rb:251:in test_should_still_set_attributes&rsquo; /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:insend
    /usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/testing/setup_and_teardown.rb:62:in `run’]:
    <1> expected but was
    .

original LH ticket

This ticket has 0 attachment(s).

StateMachine::InvalidTransition raised with valid event for current state

David North opened this issue

I have a Page model record with state ’published’,

@page.state_events => [:draft, :unpublish]

But when I call draft! or unpublish! StateMachine::InvalidTransition is raised. (@page.draft returns false)

"Cannot transition state via :draft from :published"

/event.rb:248:in add_actions&rsquo; /machine.rb:512:incall’
/machine.rb:512:in `draft!’

Version 0.8, Rails 2.3.4

It looks like draft/published is returning false because of my model being invalid so I guess the problem is that this isn’t an appropriate error, its really that it failed to save the change in state, not that the transition was invalid.

Thanks

original LH ticket

This ticket has 0 attachment(s).

State-driven instance behavior for setters is bugged

Kane opened this issue

when defineing a method like

@@@ ruby
state_machine :state, :initial => :applicating do
state :applicating do
def fame_buy_in=(value)
write_attribute(:fame_buy_in, value)
end
end
end
@@@

an ArgumentError is thrown while trying to create a new object with .new(:fame_buy_in=>12)

ArgumentError: nil is not a known state value
E:/DEV/ruby/lib/ruby/gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/state_collection.rb:80:in match!&rsquo; E:/DEV/ruby/lib/ruby/gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/state.rb:170:infame_buy_in=’
E:/DEV/ruby/lib/ruby/gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/extensions.rb:30:in `initialize

original LH ticket

This ticket has 0 attachment(s).

rake tasks not seen in Rails (if using as gem)

Matt Scilipoti opened this issue

I think ticket #11 corrected this issue if you are using it as a plugin, but not as a gem.

As far as I know, when using it as a gem, we must add entries to our application’s Rakefile. This also required some changes to file organization in state_machine.

I have moved the files and updated the README in http://github.com/mattscilipoti/state_machine/commit/79994017a41922914d42bf5d4202107d93502aee

original LH ticket

This ticket has 0 attachment(s).

require qualified name for state_event=?

Aaron Gibralter opened this issue

So it seems like the following code:
@@@
state_machine :state_news, :initial => :unsubscribed, :namespace => :news do
event :toggle do
transition :unsubscribed => :subscribed
transition :subscribed => :unsubscribed
end
end

state_machine :state_standing, :initial => :neutral, :namespace => :standing do
event :toggle do
transition :neutral => :suspended
transition :suspended => :neutral
end
end
@@@
requires the following:
@@@
@user.state_news_event = ’toggle_news’
@user.state_standing_event = ’toggle_standing’
@@@
Should the following be allowed since the first way seems a bit redundant?
@@@
@user.state_news_event = ’toggle’
@user.state_standing_event = ’toggle’
@@@

original LH ticket

This ticket has 0 attachment(s).

problem with transition guards?

Aaron Gibralter opened this issue

(commit 883805e)

I was going through and (finally) updating an old model to use the symbol form of states instead of string form and I noticed one issue. I’m not sure if it’s because I’m declaring my events incorrectly, but here it is...

This is my User’s password state_machine:
@@@
state_machine :state_password, :initial => :known, :namespace => :password do

before_transition :to => :forgotten do |user, transition|
  user.multipurpose_code = user.new_multipurpose_code
end

before_transition :to => :known do |user, transition|
  user.multipurpose_code = nil if user.email_verified?
end

event :forgets do
  transition :to => :forgotten, :from => %w(known forgotten)
end

event :remembers do
  transition :to => :known, :from => :forgotten
end

end
@@@

I’m having trouble with @user.remembers_password and I’ve traced it back to event.rb:
@@@
def transition_for(object)
from = machine.states.match(object).name

  guards.each do |guard|
    if match = guard.match(object, :from => from)
      # Guard allows for the transition to occur
      to = match[:to].values.empty? ? from : match[:to].values.first

      return Transition.new(object, machine, name, from, to)
    end
  end

  # No transition matched
  nil
end

@@@

(from = machine.states.match(object).name) is returning ’forgotten’ not :forgotten... as a result, guard.match(object, :from => from) is returning nil; however, if I ruby-debug and break on that line and do guard.match(object, :from => from.to_sym) it recognizes it...

Any thoughts? Am I doing something wrong?

Thanks!
-Aaron

original LH ticket

This ticket has 0 attachment(s).

unpredictable results for "?" methods for multiple state_machines

Aaron Gibralter opened this issue

Does state_machine define methods to test the current state? The github README examples seem to show that vehicle.parked? will say whether or not the vehicle is parked. I’ve been getting kind of unpredictable behavior with my models with multiple state_machines. For some of them the "?" methods work and others don’t:

@@@

class User ...

state_machine :state_password, :initial => ’known’ do

... events for ’forgotten’ and ’known’ passwords

end

state_machine :state_email, :initial => ’unverified’ do

... events for ’unverified’ and ’verified’ email addresses

end

u.known? #=> NoMethodError: undefined method `known?’ for #User:0x3e51a48
@@@

What about creating a method which takes the name of the state_machine as such:

@@@
user.state_password?(’known’) #=> true

or...

user.state_password_known? #=> true

user.state_email?(’verified’) #=> true
user.state_email?(’unverified’) #=> false
@@@

original LH ticket

This ticket has 0 attachment(s).

callbacks and validations

qoobaa opened this issue

@@@ ruby
state_machine :initial => :first do
event :boom
transition :first => :second
end

state :second
validate :something # validation fails
end

after_transition :boom, :do => lambda { puts "BOOM!!!" }
@@@

result:

@@@

object_with_state_machine.boom!
BOOM!!!
StateMachine::InvalidTransition: Cannot transition state via :boom from :first
@@@

I think it’s not expected behavior, it causes a lot of problems in my app.

original LH ticket

This ticket has 0 attachment(s).

state_event and record marshaling

qoobaa opened this issue

There’s a problem with using state_event and record serialization:

@@@ ruby
class User < ActiveRecord::Base
state_machine do
event :verify do
transition nil => :verified
end

after_transition :on => :verify do |user|
  user.to_yaml
end

end
end

User.create(:state_event => "verify")

TypeError: can’t dump anonymous class Class

@@@

It’s caused because to_yaml is serializing @state_event_transition instance variable (it contains Proc objects). The same problem exists when using Marshal.dump. I’m using serialization to deliver e-mail messages in background.

original LH ticket

This ticket has 0 attachment(s).

How do callbacks on initial states work?

Aaron Pfeifer opened this issue

As commented on in the blog: event of creation of an object with :initial=>:newly_created state — is it a transition?

I tried:

@@@
before_transition all => :newly_created , :do => :alarm
after_transition all => :newly_created , :do => :alarm
@@@

None was triggered.

Does it mean that initial state has no callbacks? (Workaround would be calling :alarm in object initialize.)

original LH ticket

This ticket has 0 attachment(s).

rake tasks don't show up in Rails

Pete Forde opened this issue

I noticed that there’s no tasks folder in state_machine. The way rake works (as I understand it) is that any rake tasks in files with a .rake extension in a tasks folder will show up in the available tasks for a project.

In my local checkout I:

mkdir tasks
cp Rakefile tasks/Rakefile.rake

And that works. I’m sure there’s a best practice I’m probably missing, but I hope this helps.

original LH ticket

This ticket has 0 attachment(s).

pluralize issue

Pete Forde opened this issue

Hey Aaron,

I’m sorry that what I’m about to say isn’t tested or particularly demonstrable, but in certain places in my app I seem to run into situations (under 2.2.2) where for some reason state_machine explodes and the whole mongrel instance needs to be restarted.

Here’s an example:

class User < ActiveRecord::Base
  state_machine :initial => :pending do
    event :activate do
      transition :from => :pending, :to => :active
    end
    event :reactivate do
      transition :from => :disabled, :to => :active
    end
    event :disable do
      transition :from => :active, :to => :disabled
    end
  end
end

In this situation where the problem occurs, it bombs with a really long error stack:

wrong number of arguments (0 for 2)

vendor/plugins/state_machine/lib/state_machine/machine.rb:1020:in `pluralize’
vendor/plugins/state_machine/lib/state_machine/machine.rb:1020:in `define_scopes’
vendor/plugins/state_machine/lib/state_machine/machine.rb:265:in `initialize’
vendor/plugins/state_machine/lib/state_machine/machine.rb:171:in `new’
vendor/plugins/state_machine/lib/state_machine/machine.rb:171:in `find_or_create’
vendor/plugins/state_machine/lib/state_machine.rb:309:in `state_machine’
app/models/user.rb:27

Just to explain, line 27 of the User model is where I call state_machine.

After testing, I concluded that if on machine.rb line 1020 attribute is a Symbol, for some reason responds_to? returns true and it blows up.

Strangely, if I modify the call like so:

attributes.pluralize(2, ’girl’) # => plural is girls

Anyhow, I tweaked my local plugin source so it looked more like this:

plural = custom_plural || (attribute.respond_to?(:pluralize) ? attribute.to_s.pluralize : "#{attribute}s")

And it works!

For some reason, sometimes attribute is a Symbol, which doesn’t have a pluralize method. That said, I have no idea why attribute.respond_to? returns true in this case.

Hope this helps.

original LH ticket

This ticket has 0 attachment(s).

problems with SM cols in migrations

Aaron Gibralter opened this issue

Ok, so I can’t really figure out what’s going on here. For some reason it seems like the columns associated with state_machine in my migrations are always being set blank.

Here is a link to part of my migration and two of my models:

https://gist.github.com/d079cdfedb66dff9361f

After the migration, the every row in the contextualizations table has state_status_admin and state_status_user blank (even though they are NOT NULL columns!)...

However, when I go into script/console and issue a very similar command to what is in my migration, it works just fine! At first I thought it was because I was running a bunch of migrations at once and forgot reset_column_information... but adding that didn’t do the trick.

Any thoughts??

Many thanks in advance!

original LH ticket

This ticket has 0 attachment(s).

problems running graphviz

Pete Forde opened this issue

I’ve installed the graphviz darwin package, and the graphviz-ruby gem.

I created a very simple class to test the state_machine:draw:rails functionality, and this is what I get:

pete ~/quotesys (edge)$ rake state_machine:draw:rails CLASS=Booking
(in /Users/pete/quotesys)
  SQL (0.4ms)   SET SQL_AUTO_IS_NULL=0
  SQL (0.6ms)   SHOW TABLES
  SQL (0.2ms)   SELECT version FROM schema_migrations
  Global Columns (3.5ms)   SHOW FIELDS FROM `globals`
  SQL (0.1ms)   BEGIN
  SQL (0.2ms)   SELECT count(*) AS count_all FROM `globals` 
  SQL (0.1ms)   ROLLBACK
Warning: node ’released’, graph ’G’ size too small for label
Warning: node ’production’, graph ’G’ size too small for label
Warning: node ’installation’, graph ’G’ size too small for label
Warning: node ’installed’, graph ’G’ size too small for label
Warning: node ’never_booked’, graph ’G’ size too small for label
Warning: node ’never_produced’, graph ’G’ size too small for label
Warning: node ’never_installed’, graph ’G’ size too small for label
Warning: node ’never_removed’, graph ’G’ size too small for label
dyld: lazy symbol binding failed: Symbol not found: _pixman_image_create_bits
  Referenced from: /usr/local/lib/graphviz/libgvplugin_pango.5.dylib
  Expected in: flat namespace

dyld: Symbol not found: _pixman_image_create_bits
  Referenced from: /usr/local/lib/graphviz/libgvplugin_pango.5.dylib
  Expected in: flat namespace

original LH ticket

This ticket has 5 attachment(s).

state_events and namespace ?

Thomas Darde opened this issue

I think the state_events helper is not generated when using multiple state_machines and the namespace feature. (only the main namespace is available)

original LH ticket

This ticket has 0 attachment(s).

Confusing InvalidTransition error - this should work

Jonas Grimfelt opened this issue

My ActiveRecord model with migrations OK, and payment_state-value ’unconfirmed’ and handling_state-value ’unconfirmed’ in DB:

@@@ ruby
class Order < ActiveRecord::Base

state_machine :handling_state, :initial => :unconfirmed, :namespace => :handling do
state :unconfirmed
state :new
state :open
state :shipped
state :canceled

event :confirm do
  transition :unconfirmed => :new
end

end

state_machine :payment_state, :initial => :unconfirmed, :namespace => :payment do
state :unconfirmed
state :pending
state :charged
state :refunded

event :confirm do
  transition :unconfirmed => :pending
end

end
end
@@@

Calls:

@@@ ruby
@order = Order.new
@order.confirm_handling! # ...so far so good, but now:
@order.confirm_payment! # BANG! =(
@@@

Error:

@@@
StateMachine::InvalidTransition: Cannot transition payment_state via :confirm from :unconfirmed
from /opt/local/lib/ruby/gems/1.8/gems/state_machine-0.9.2/lib/state_machine/event.rb:248:in add_actions&rsquo; from /opt/local/lib/ruby/gems/1.8/gems/state_machine-0.9.2/lib/state_machine/machine.rb:537:incall’
from /opt/local/lib/ruby/gems/1.8/gems/state_machine-0.9.2/lib/state_machine/machine.rb:537:in `confirm_payment!’
from (irb):39
from :0
@@@

Calling order.confirm_payment! fails no matter what order I call them in. I’m stuck. =/

original LH ticket

This ticket has 0 attachment(s).

can_event? does not check validations ...

rbialek (at gmail) opened this issue

The can_event? method returns true even though validations are failing! See the example:

class Att < ActiveRecord::Base
state_machine :status do
state :one do
validate :validation1
end
state :two do
end

event :goto0 do; transition any => :zero ; end
event :goto1 do; transition :zero => :one ; end
event :goto2 do; transition :one => :two ; end

end
def validation1
errors.add_to_base "validation 1 failing"
end
end

a=Att.new :status => "zero"
a.can_goto0? => true
a.can_goto1? => true
a.can_goto2? => false
goto0 and 1 return true because status transition is allowed!
Apparently they do not check the validations so the transition itself fails.

a.goto1 => false
a.errors => @errors={"base"=>["validation 1 failing"]}

Is it intended behavior? Imho the can_goto1? method should return false because we can not do goto1.
Can you fix it or add an example to readme of how to check if the transition is possible with respect to validation?.
Thank you for the great plugin!
++Robert

original LH ticket

This ticket has 0 attachment(s).

bug in namespaced model callbacks

Aaron Gibralter opened this issue

It seems like AR observer callbacks are broken with namespaces... I’ve tracked the problem down to:

lib/state_machine/integrations/active_record.rb:177
@@@
def after_initialize
# Observer callbacks never halt the chain; result is ignored
callbacks[:before] << Callback.new {|object, transition| notify(:before, object, transition)}
callbacks[:after] << Callback.new {|object, transition, result| notify(:after, object, transition)}
end
@@@
lib/state_machine/integrations/active_record.rb:251
@@@
def notify(type, object, transition)
["#{type}_#{transition.event}", "#{type}_transition"].each do |method|
object.class.class_eval do
@observer_peers.dup.each do |observer|
observer.send(method, object, transition) if observer.respond_to?(method)
end if defined?(@observer_peers)
end
end

      true
    end

@@@

I’m thinking the AR observer should follow the same namespace naming conventions:
@@@
class SomeObserver < ActiveRecord::Observer
def [before/after][event_name][namespace]
# ...
end
end
@@@

So the after_initialize method will have to pass the namespace... I’ll take a stab at the solution.

original LH ticket

This ticket has 0 attachment(s).

Callback after transaction end?

Brian Langenfeld opened this issue

Excellent job on this, Aaron.

I’m interested in logging transition results to a log table -- not just successes -- so after_transition isn’t getting the job done. As expected, the log entry gets wiped out upon rollback. I could just turn off transactions for the entire transition, but I’d rather not.

Any plans to include a callback to be executed after a transition transaction ends? I’ve hacked state_machine a bit to tag transitions with the encountered Exception, which is then available to a new after_transaction callback (so you can know if/how the transaction failed)... but maybe you already had something cooking.

If not, let me know... I’d be happy to post a patch, if this sounds like something that fits the direction you want to take with state_machine. Alternatively, if I’m missing something obvious (like an option somewhere), feel free to point it out. I may just be blind.

original LH ticket

This ticket has 0 attachment(s).

warning: undefining `initialize'

tsechingho opened this issue

yesterday i check out the new version state machine for my previous project using old version state machine and rails 2.2.2. I encounter two situation which can be produced many times.

(1) /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:410: warning: undefining `initialize’ may cause serious problem

I get this waring whether in my previous project or a brand new rails project (rails 2.2.2). The code is as simple as:

@@@ ruby
class User < ActiveRecord::Base
state_machine :initial => ’passive’ do
event :activate do
transition :to => ’active’, :from => ’pending’
end
end
end
@@@

(2) And I can not migrate from the blank database(schema not yet have) in my previous project change to new version state machine. But brand new rails project can do this. I know this maybe is my own problem, but anyone has any suggestion?

@@@
rake db:migrate --trace
rake aborted!
Could not find table ’users’

** Invoke db:migrate (first_time)
** Invoke environment (first_time)
** Execute environment
rake aborted!
Could not find table ’users’
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:29:in table_structure&rsquo; /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/core_ext/object/misc.rb:39:inreturning’
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:28:in table_structure&rsquo; /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/connection_adapters/sqlite_adapter.rb:189:incolumns’
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:1220:in columns&rsquo; /opt/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/base.rb:1228:incolumns_hash’
/opt/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/attribute_methods.rb:71:in define_attribute_methods&rsquo; /Users/tsechingho/Desktop/Facebook/trunk/vendor/plugins/state_machine/lib/state_machine/integrations/active_record.rb:194:indefine_attribute_accessor’
/Users/tsechingho/Desktop/Facebook/trunk/vendor/plugins/state_machine/lib/state_machine/machine.rb:244:in initialize&rsquo; /Users/tsechingho/Desktop/Facebook/trunk/vendor/plugins/state_machine/lib/state_machine/machine.rb:184:innew’
/Users/tsechingho/Desktop/Facebook/trunk/vendor/plugins/state_machine/lib/state_machine/machine.rb:184:in find_or_create&rsquo; /Users/tsechingho/Desktop/Facebook/trunk/vendor/plugins/state_machine/lib/state_machine.rb:129:instate_machine’
/Users/tsechingho/Desktop/Facebook/trunk/lib/authorization/statable_roles.rb:69:in included&rsquo; /Users/tsechingho/Desktop/Facebook/trunk/lib/authorization/statable_roles.rb:66:inclass_eval’
/Users/tsechingho/Desktop/Facebook/trunk/lib/authorization/statable_roles.rb:66:in included&rsquo; /Users/tsechingho/Desktop/Facebook/trunk/app/models/user.rb:8:ininclude’
/Users/tsechingho/Desktop/Facebook/trunk/app/models/user.rb:8
@@@

original LH ticket

This ticket has 0 attachment(s).

Exception when changing state of an existing record with a nil state

state_machine is a great plugin but I've found a problem.

When you add a state_machine to an existing ActiveRecord model and load an existing model, the state of the model is therefore nil, state_machine raises an exception when you call an event.

>> u = User.last
=> #
>> u.state
=> nil
>> u.activate!
NoMethodError: You have a nil object when you didn't expect it!
The error occurred while evaluating nil.name
    from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:166:in `transition_for'
    from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:190:in `fire'
    from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:242:in `add_actions'
    from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/machine.rb:512:in `call'
    from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/machine.rb:512:in `activate'
    from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:247:in `send'
    from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:247:in `add_actions'
    from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/machine.rb:512:in `call'
    from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/machine.rb:512:in `activate!'
    from (irb):3

define_state_transitions

Aaron Gibralter opened this issue

Ok so here’s what I’ve got working... I added this to the StateMachine::Machine class:
@@@
def define_state_transitions(*states)
states_hash = {}
if states.last.is_a?(Hash)
states.last.each { |state, message| states_hash[state.to_sym] = message }
else
states.each { |state| states_hash[state.to_sym] = state.to_sym }
end

  event_names = {}
  loops = []
  states_hash.keys.each do |state_from|
    event_names[state_from] = {}
    states_hash.keys.each do |state_to|
      event = event("#{state_from}__to__#{state_to}") do
        transition :to => state_to, :from => state_from
      end
      event_names[state_from][state_to] = event.qualified_name
      loops << event.qualified_name if state_to == state_from
    end
  end

  event_attr_name = self.namespace ? :"#{self.namespace}_state_transition" : :state_transition

  current_attribute = self.attribute

  @instance_helper_module.class_eval do
    define_method(event_attr_name) do
      current_state = self.send(current_attribute).to_sym
      states_hash.keys.include?(current_state) ? event_names[current_state][current_state] : nil
    end
  end

  @instance_helper_module.class_eval do
    define_method("#{event_attr_name}s") do
      current_state = self.send(current_attribute).to_sym
      states_hash.keys.include?(current_state) ? states_hash.keys.collect { |state| [states_hash[state], event_names[current_state][state]] } : []
    end
  end

  owner_class.class_eval do
    validate do |model|
      model.errors.add(event_attr_name, "invalid selection") if model.instance_variable_get("@#{event_attr_name}") == :invalid
    end
  end

  @instance_helper_module.class_eval do
    define_method("#{event_attr_name}=") do |event_name|
      current_state = self.send(current_attribute).to_sym
      event_name = event_name.to_sym
      unless event_names[current_state].values.include?(event_name) && (loops.include?(event_name) || self.send(event_name, false))
        instance_variable_set("@#{event_attr_name}", :invalid)
      end
    end
  end
end

@@@

This is clearly a really bloated method and could probably be abstracted into a separate class. Also, the validation need to be in the integration because it is AR specific.

Anyway...

What this allows you to do is something like this:
@@@
class Post < ActiveRecord::Base
state_machine :state_visibility, :initial => :public, :namespace => :visible do

define_state_transitions :public => &rsquo;make public&rsquo;, :private => &rsquo;for your eyes only&rsquo;

end
end

@post = Post.new

@post.state_visibility #=> :public

@post.visible_state_transitions = [[’make public’, :public__to__public_visible], [’for your eyes only’, :public__to__private_visible]]

for custom labels on the select menu...

form_for @post do |f|

f.select :visible_state_transition, @post.visible_state_transitions

end

@post.update_attributes(:visible_state_transition => ’public__to__public_visible’) # essentially a no-op
@post.valid? #=> true
@post.visible_public? #=> true
@post.visible_private? #=> false

@post.update_attributes(:visible_state_transition => ’public__to__private_visible’)
@post.valid? #=> true
@post.visible_public? #=> false
@post.visible_private? #=> true

@post.visible_state_transitions = [[’make public’, :private__to__public_visible], [’for your eyes only’, :private__to__private_visible]]

@post.update_attributes(:visible_state_transition => ’private__to__private_visible’) # essentially a no-op
@post.valid? #=> true
@post.visible_public? #=> false
@post.visible_private? #=> true

@post.update_attributes(:visible_state_transition => ’public__to__public_visible’)
@post.valid? #=> false! invalid transition because the ’start’ position is private
@post.visible_public? #=> false
@post.visible_private? #=> true
@@@

Any thoughts?

original LH ticket

This ticket has 1 attachment(s).

Exception when changing state of an existing record with a nil state

Nicolas Blanco opened this issue

state_machine is a great plugin but I’ve found a problem.

When you add a state_machine to an existing ActiveRecord model and load an existing model, the state of the model is therefore nil, state_machine raises an exception when you call an event.

@@@

u = User.last
=> #
u.state
=> nil
u.activate!
NoMethodError: You have a nil object when you didn’t expect it!
The error occurred while evaluating nil.name
from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:166:in transition_for&rsquo; from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:190:infire’
from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:242:in add_actions&rsquo; from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/machine.rb:512:incall’
from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/machine.rb:512:in activate&rsquo; from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:247:insend’
from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/event.rb:247:in add_actions&rsquo; from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/machine.rb:512:incall’
from /Library/Ruby/Gems/1.8/gems/pluginaweek-state_machine-0.7.6/lib/state_machine/machine.rb:512:in `activate!’
from (irb):3
@@@

original LH ticket

This ticket has 0 attachment(s).

Enhancement: Option to validate when leaving a state, rather than when entering

Patrick Klingemann opened this issue

Aaron,

Thanks for your great work on this plugin/gem, it is quite useful to me. I am building a workflow form with state machine in which the form that is displayed is based on the state of a state machine. The issue I’m having is that I need to validate certain fields based on the state that the state machine is in currently, and state_machine’s behavior validates fields based on the state to which the state machine is transitioning.

I am wondering if this has been considered as an option, also I would be glad to help develop an option to accomplish this. You may a have a strong opinion against this, but otherwise, could you provide me some guidelines on how I might tackle this issue? I am using ActiveRecord with Rails 2.3.2.

Once again, thanks for your contributions to the community.

Patrick Klingemann

original LH ticket

This ticket has 0 attachment(s).

State machine overriding internalisation settings

Colin Gemmell opened this issue

Hi there

I was using state machine and was trying to modify the error messages to be more useful to our users. As per the documentation I added this to the en.yml file

en:
activerecord:
errors:
messages:
invalid: "you request is invalid"
invalid_event: "please choose an option below"
invalid_transition: "that option is not available here"

However this made no change to the output. On further investigation I found that state-machines defaults where being added last to the I18n.load_path causing them to overwrite my customisations.

removing the line in integrations/active_record.rb fixed the problem as defaults where not being added

I18n.load_path << locale unless I18n.load_path.include?(locale)

Cheers
Colin G

original LH ticket

This ticket has 0 attachment(s).

railite_name is depracated

Wojciech Wnętrzak opened this issue

On the rails master branch railtie_name is deprecated. When environment is loaded I get:
DEPRECATION WARNING: railtie_name is deprecated and has no effect. (called from class:Railtie at ~/.rvm/gems/ruby-1.9.1-p378/gems/state_machine-0.9.0/lib/state_machine/initializers/rails.rb:2)

original LH ticket

This ticket has 0 attachment(s).

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.