state_machine
Class Methods
The following class methods will be automatically generated by the state machine based on the name of the machine. Any existing methods will not be overwritten.
human_state_name(state)
Gets the humanized value for the given state. This may be generated by internationalization libraries if supported by the integration.
human_state_event_name(event)
Gets the humanized value for the given event. This may be generated by internationalization libraries if supported by the integration.
For example,
class Vehicle
state_machine :state, :initial => :parked do
event :ignite do
transition :parked => :idling
end
event :shift_up do
transition :idling => :first_gear
end
end
end
Vehicle.human_state_name(:parked) # => "parked"
Vehicle.human_state_name(:first_gear) # => "first gear"
Vehicle.human_state_event_name(:park) # => "park"
Vehicle.human_state_event_name(:shift_up) # => "shift up"
Instance Methods
The following instance methods will be automatically generated by the state machine based on the name of the machine. Any existing methods will not be overwritten.
state
Gets the current value for the attribute
state=(value)
Sets the current value for the attribute
state?(name)
Checks the given state name against the current state. If the name is not a known state, then an ArgumentError is raised.
state_name
Gets the name of the state for the current value
human_state_name
Gets the human-readable name of the state for the current value
state_events(requirements = {})
Gets the list of events that can be fired on the current object's state (uses the unqualified event names)
state_transitions(requirements = {})
Gets the list of transitions that can be made on the current object's state
state_paths(requirements = {})
Gets the list of sequences of transitions that can be run from the current object's state
fire_state_event(name, *args)
Fires an arbitrary event with the given argument list. This is essentially the same as calling the actual event method itself.
The state_events
, state_transitions
, and state_paths
helpers all take an optional set of
requirements for determining what's available for the current object. These requirements include:
:from
One or more states to transition from. If none are specified, then this will be the object's current state.
:to
One or more states to transition to. If none are specified, then this will match any to state.
:on
One or more events to transition on. If none are specified, then this will match any event.
:guard
Whether to guard transitions with the if/unless conditionals defined for each one. Default is true.
For example,
class Vehicle
state_machine :state, :initial => :parked do
event :ignite do
transition :parked => :idling
end
event :park do
transition :idling => :parked
end
end
end
vehicle = Vehicle.new
vehicle.state # => "parked"
vehicle.state_name # => :parked
vehicle.human_state_name # => "parked"
vehicle.state?(:parked) # => true
# Changing state
vehicle.state = 'idling'
vehicle.state # => "idling"
vehicle.state_name # => :idling
vehicle.state?(:parked) # => false
# Getting current event / transition availability
vehicle.state_events # => [:park]
vehicle.park # => true
vehicle.state_events # => [:ignite]
vehicle.state_events(:from => :idling) # => [:park]
vehicle.state_events(:to => :parked) # => []
vehicle.state_transitions # => [#<StateMachines::Transition attribute=:state
# event=:ignite from="parked" from_name=:parked
# to="idling" to_name=:idling>]
vehicle.ignite # => true
vehicle.state_transitions # => [#<StateMachines::Transition attribute=:state
# event=:park from="idling" from_name=:idling
# to="parked" to_name=:parked>]
vehicle.state_transitions(:on => :ignite) # => []
# Getting current path availability
vehicle.state_paths # => [
# [#<StateMachines::Transition attribute=:state
# event=:park from="idling" from_name=:idling
# to="parked" to_name=:parked>,
# #<StateMachines::Transition attribute=:state
# event=:ignite from="parked" from_name=:parked
# to="idling" to_name=:idling>]
# ]
vehicle.state_paths(:guard => false) # =>
# [#<StateMachines::Transition attribute=:state
# event=:park from="idling" from_name=:idling
# to="parked" to_name=:parked>,
# #<StateMachines::Transition attribute=:state
# event=:ignite from="parked" from_name=:parked
# to="idling" to_name=:idling>]
# ]
# Fire arbitrary events
vehicle.fire_state_event(:park) # => true
Overriding instance / class methods
Hooking in behavior to the generated instance / class methods from the state machine, events, and
states is very simple because of the way these methods are generated on the class. Using the class's
ancestors, the original generated method can be referred to via super
. For example:
class Vehicle
state_machine do
event :park do
...
end
end
def park(*args)
logger.info "..."
super
end
end
In the above example, the park
instance method that's generated on the Vehicle class (by the
associated event) is overridden with custom behavior. Once this behavior is complete, the original
method from the state machine is invoked by simply calling super
.
The same technique can be used for state
, state_name
, and all other instance and class methods
on the Vehicle class.
Method conflicts
By default statemachine does not redefine methods that exist on superclasses (_including Object) or any modules (including Kernel) that were included before it was defined. This is in order to ensure that existing behavior on the class is not broken by the inclusion of state_machine.
If a conflicting method is detected, state_machine will generate a warning. For example, consider the following class:
class Vehicle
state_machine do
event :open do
...
end
end
end
In the above class, an event named "open" is defined for its state machine. However, "open" is already defined as an instance method in Ruby's Kernel module that gets included in every Object. As a result, state_machine will generate the following warning:
Instance method "open" is already defined in Object, use generic helper instead or set
StateMachines::Machine.ignore_method_conflicts = true.
Even though you may not be using Kernel's implementation of the "open" instance method, state_machine isn't aware of this and, as a result, stays safe and just skips redefining the method.
As with almost all helpers methods defined by state_machine in your class, there are generic methods available for working around this method conflict. In the example above, you can invoke the "open" event like so:
vehicle = Vehicle.new # => #<Vehicle:0xb72686b4 @state=nil>
vehicle.fire_events(:open) # => true
# This will not work
vehicle.open # => NoMethodError: private method `open' called for
#<Vehicle:0xb72686b4 @state=nil>
If you want to take on the risk of overriding existing methods and just ignore method conflicts altogether, you can do so by setting the following configuration:
StateMachines::Machine.ignore_method_conflicts = true
This will allow you to define events like "open" as described above and still generate the "open" instance helper method. For example:
StateMachines::Machine.ignore_method_conflicts = true
class Vehicle
state_machine do
event :open do
...
end
end
vehicle = Vehicle.new # => #<Vehicle:0xb72686b4 @state=nil>
vehicle.open # => true
By default, state_machine helps prevent you from making mistakes and accidentally overriding methods
that you didn't intend to. Once you understand this and what the consequences are, setting the
ignore_method_conflicts
option is a perfectly reasonable workaround.