Class | StateMachine::Machine |
In: |
lib/state_machine/machine.rb
|
Parent: | Object |
Represents a state machine for a particular attribute. State machines consist of states, events and a set of transitions that define how the state changes after a particular event is fired.
A state machine will not know all of the possible states for an object unless they are referenced somewhere in the state machine definition. As a result, any unused states should be defined with the other_states or state helper.
When an action is configured for a state machine, it is invoked when an object transitions via an event. The success of the event becomes dependent on the success of the action. If the action is successful, then the transitioned state remains persisted. However, if the action fails (by returning false), the transitioned state will be rolled back.
For example,
class Vehicle attr_accessor :fail, :saving_state state_machine :initial => :parked, :action => :save do event :ignite do transition :parked => :idling end event :park do transition :idling => :parked end end def save @saving_state = state fail != true end end vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked"> vehicle.save # => true vehicle.saving_state # => "parked" # The state was "parked" was save was called # Successful event vehicle.ignite # => true vehicle.saving_state # => "idling" # The state was "idling" when save was called vehicle.state # => "idling" # Failed event vehicle.fail = true vehicle.park # => false vehicle.saving_state # => "parked" vehicle.state # => "idling"
As shown, even though the state is set prior to calling the save action on the object, it will be rolled back to the original state if the action fails. Note that this will also be the case if an exception is raised while calling the action.
In addition to the action being run as the result of an event, the action can also be used to run events itself. For example, using the above as an example:
vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked"> vehicle.state_event = 'ignite' vehicle.save # => true vehicle.state # => "idling" vehicle.state_event # => nil
As can be seen, the save action automatically invokes the event stored in the state_event attribute (:ignite in this case).
One important note about using this technique for running transitions is that if the class in which the state machine is defined also defines the action being invoked (and not a superclass), then it must manually run the StateMachine hook that checks for event attributes.
For example, in ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel, the default action (save) is already defined in a base class. As a result, when a state machine is defined in a model / resource, StateMachine can automatically hook into the save action.
On the other hand, the Vehicle class from above defined its own save method (and there is no save method in its superclass). As a result, it must be modified like so:
def save self.class.state_machines.transitions(self, :save).perform do @saving_state = state fail != true end end
This will add in the functionality for firing the event stored in the state_event attribute.
Callbacks are supported for hooking before and after every possible transition in the machine. Each callback is invoked in the order in which it was defined. See StateMachine::Machine#before_transition and StateMachine::Machine#after_transition for documentation on how to define new callbacks.
Note that callbacks only get executed within the context of an event. As a result, if a class has an initial state when it‘s created, any callbacks that would normally get executed when the object enters that state will not get triggered.
For example,
class Vehicle state_machine :initial => :parked do after_transition all => :parked do raise ArgumentError end ... end end vehicle = Vehicle.new # => #<Vehicle id: 1, state: "parked"> vehicle.save # => true (no exception raised)
If you need callbacks to get triggered when an object is created, this should be done by either:
Callbacks can be canceled by throwing :halt at any point during the callback. For example,
... throw :halt ...
If a before callback halts the chain, the associated transition and all later callbacks are canceled. If an after callback halts the chain, the later callbacks are canceled, but the transition is still successful.
These same rules apply to around callbacks with the exception that any around callback that doesn‘t yield will essentially result in :halt being thrown. Any code executed after the yield will behave in the same way as after callbacks.
Note that if a before callback fails and the bang version of an event was invoked, an exception will be raised instead of returning false. For example,
class Vehicle state_machine :initial => :parked do before_transition any => :idling, :do => lambda {|vehicle| throw :halt} ... end end vehicle = Vehicle.new vehicle.park # => false vehicle.park! # => StateMachine::InvalidTransition: Cannot transition state via :park from "idling"
Observers, in the sense of external classes and not Ruby‘s Observable mechanism, can hook into state machines as well. Such observers use the same callback api that‘s used internally.
Below are examples of defining observers for the following state machine:
class Vehicle state_machine do event :park do transition :idling => :parked end ... end ... end
Event/Transition behaviors:
class VehicleObserver def self.before_park(vehicle, transition) logger.info "#{vehicle} instructed to park... state is: #{transition.from}, state will be: #{transition.to}" end def self.after_park(vehicle, transition, result) logger.info "#{vehicle} instructed to park... state was: #{transition.from}, state is: #{transition.to}" end def self.before_transition(vehicle, transition) logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} is: #{transition.from}, #{transition.attribute} will be: #{transition.to}" end def self.after_transition(vehicle, transition) logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} was: #{transition.from}, #{transition.attribute} is: #{transition.to}" end def self.around_transition(vehicle, transition) logger.info Benchmark.measure { yield } end end Vehicle.state_machine do before_transition :on => :park, :do => VehicleObserver.method(:before_park) before_transition VehicleObserver.method(:before_transition) after_transition :on => :park, :do => VehicleObserver.method(:after_park) after_transition VehicleObserver.method(:after_transition) around_transition VehicleObserver.method(:around_transition) end
One common callback is to record transitions for all models in the system for auditing/debugging purposes. Below is an example of an observer that can easily automate this process for all models:
class StateMachineObserver def self.before_transition(object, transition) Audit.log_transition(object.attributes) end end [Vehicle, Switch, Project].each do |klass| klass.state_machines.each do |attribute, machine| machine.before_transition StateMachineObserver.method(:before_transition) end end
Additional observer-like behavior may be exposed by the various integrations available. See below for more information on integrations.
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.
By default, state machines are library-agnostic, meaning that they work on any Ruby class and have no external dependencies. However, there are certain libraries which expose additional behavior that can be taken advantage of by state machines.
This library is built to work out of the box with a few popular Ruby libraries that allow for additional behavior to provide a cleaner and smoother experience. This is especially the case for objects backed by a database that may allow for transactions, persistent storage, search/filters, callbacks, etc.
When a state machine is defined for classes using any of the above libraries, it will try to automatically determine the integration to use (Agnostic, ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, or Sequel) based on the class definition. To see how each integration affects the machine‘s behavior, refer to all constants defined under the StateMachine::Integrations namespace.
action | [R] | The action to invoke when an object transitions |
callbacks | [R] |
The callbacks to invoke before/after a transition is performed
Maps :before => callbacks and :after => callbacks |
default_messages | [RW] | |
events | [R] | The events that trigger transitions. These are sorted, by default, in the order in which they were defined. |
name | [R] | The name of the machine, used for scoping methods generated for the machine as a whole (not states or events) |
namespace | [R] | An identifier that forces all methods (including state predicates and event methods) to be generated with the value prefixed or suffixed, depending on the context. |
owner_class | [RW] | The class that the machine is defined in |
states | [R] |
A list of all of the states known to this state machine. This will pull states from
the following sources:
These are sorted, by default, in the order in which they were referenced. |
use_transactions | [R] | Whether the machine will use transactions when firing events |
Draws the state machines defined in the given classes using GraphViz. The given classes must be a comma-delimited string of class names.
Configuration options:
Attempts to find or create a state machine for the given class. For example,
StateMachine::Machine.find_or_create(Vehicle) StateMachine::Machine.find_or_create(Vehicle, :initial => :parked) StateMachine::Machine.find_or_create(Vehicle, :status) StateMachine::Machine.find_or_create(Vehicle, :status, :initial => :parked)
If a machine of the given name already exists in one of the class‘s superclasses, then a copy of that machine will be created and stored in the new owner class (the original will remain unchanged).
Creates a callback that will be invoked after a transition failures to be performed so long as the given requirements match the transition.
See before_transition for a description of the possible configurations for defining callbacks. Note however that you cannot define the state requirements in these callbacks. You may only define event requirements.
Failure callbacks get invoked whenever an event fails to execute. This can happen when no transition is available, a before callback halts execution, or the action associated with this machine fails to succeed. In any of these cases, any failure callback that matches the attempted transition will be run.
For example,
class Vehicle state_machine do after_failure do |vehicle, transition| logger.error "vehicle #{vehicle} failed to transition on #{transition.event}" end after_failure :on => :ignite, :do => :log_ignition_failure ... end end
Creates a callback that will be invoked after a transition is performed so long as the given requirements match the transition.
See before_transition for a description of the possible configurations for defining callbacks.
Creates a callback that will be invoked around a transition so long as the given requirements match the transition.
Around callbacks wrap transitions, executing code both before and after. These callbacks are defined in the exact same manner as before / after callbacks with the exception that the transition must be yielded to in order to finish running it.
If defining around callbacks using blocks, you must yield within the transition by directly calling the block (since yielding is not allowed within blocks).
For example,
class Vehicle state_machine do around_transition do |block| Benchmark.measure { block.call } end around_transition do |vehicle, block| logger.info "vehicle was #{state}..." block.call logger.info "...and is now #{state}" end around_transition do |vehicle, transition, block| logger.info "before #{transition.event}: #{vehicle.state}" block.call logger.info "after #{transition.event}: #{vehicle.state}" end end end
Notice that referencing the block is similar to doing so within an actual method definition in that it is always the last argument.
On the other hand, if you‘re defining around callbacks using method references, you can yield like normal:
class Vehicle state_machine do around_transition :benchmark ... end def benchmark Benchmark.measure { yield } end end
See before_transition for a description of the possible configurations for defining callbacks.
Creates a callback that will be invoked before a transition is performed so long as the given requirements match the transition.
Callbacks must be defined as either an argument, in the :do option, or as a block. For example,
class Vehicle state_machine do before_transition :set_alarm before_transition :set_alarm, all => :parked before_transition all => :parked, :do => :set_alarm before_transition all => :parked do |vehicle, transition| vehicle.set_alarm end ... end end
Notice that the first three callbacks are the same in terms of how the methods to invoke are defined. However, using the :do can provide for a more fluid DSL.
In addition, multiple callbacks can be defined like so:
class Vehicle state_machine do before_transition :set_alarm, :lock_doors, all => :parked before_transition all => :parked, :do => [:set_alarm, :lock_doors] before_transition :set_alarm do |vehicle, transition| vehicle.lock_doors end end end
Notice that the different ways of configuring methods can be mixed.
Callbacks can require that the machine be transitioning from and to specific states. These requirements use a Hash syntax to map beginning states to ending states. For example,
before_transition :parked => :idling, :idling => :first_gear, :do => :set_alarm
In this case, the set_alarm callback will only be called if the machine is transitioning from parked to idling or from idling to parked.
To help define state requirements, a set of helpers are available for slightly more complex matching:
See StateMachine::MatcherHelpers for more information.
Examples:
before_transition :parked => [:idling, :first_gear], :do => ... # Matches from parked to idling or first_gear before_transition all - [:parked, :idling] => :idling, :do => ... # Matches from every state except parked and idling to idling before_transition all => :parked, :do => ... # Matches all states to parked before_transition any => same, :do => ... # Matches every loopback
In addition to state requirements, an event requirement can be defined so that the callback is only invoked on specific events using the on option. This can also use the same matcher helpers as the state requirements.
Examples:
before_transition :on => :ignite, :do => ... # Matches only on ignite before_transition :on => all - :ignite, :do => ... # Matches on every event except ignite before_transition :parked => :idling, :on => :ignite, :do => ... # Matches from parked to idling on ignite
Requirements can also be defined using verbose options rather than the implicit Hash syntax and helper methods described above.
Configuration options:
Examples:
before_transition :from => :ignite, :to => :idling, :on => :park, :do => ... before_transition :except_from => :ignite, :except_to => :idling, :except_on => :park, :do => ...
In addition to the state/event requirements, a condition can also be defined to help determine whether the callback should be invoked.
Configuration options:
Examples:
before_transition :parked => :idling, :if => :moving?, :do => ... before_transition :on => :ignite, :unless => :seatbelt_on?, :do => ...
In addition to passing the object being transitioned, the actual transition describing the context (e.g. event, from, to) can be accessed as well. This additional argument is only passed if the callback allows for it.
For example,
class Vehicle # Only specifies one parameter (the object being transitioned) before_transition all => :parked do |vehicle| vehicle.set_alarm end # Specifies 2 parameters (object being transitioned and actual transition) before_transition all => :parked do |vehicle, transition| vehicle.set_alarm(transition) end end
Note that the object in the callback will only be passed in as an argument if callbacks are configured to not be bound to the object involved. This is the default and may change on a per-integration basis.
See StateMachine::Transition for more information about the attributes available on the transition.
Below is an example of a class with one state machine and various types of before transitions defined for it:
class Vehicle state_machine do # Before all transitions before_transition :update_dashboard # Before specific transition: before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt # With conditional callback: before_transition all => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on? # Using helpers: before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard ... end end
As can be seen, any number of transitions can be created using various combinations of configuration options.
Defines a new helper method in an instance or class scope with the given name. If the method is already defined in the scope, then this will not override it.
Example:
# Instance helper machine.define_helper(:instance, :state_name) do |machine, object| machine.states.match(object).name end # Class helper machine.define_helper(:class, :state_machine_name) do |machine, klass| "State" end
You can also define helpers using string evaluation like so:
# Instance helper machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 def state_name self.class.state_machine(:state).states.match(self).name end end_eval # Class helper machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1 def state_machine_name "State" end end_eval
Draws a directed graph of the machine for visualizing the various events, states, and their transitions.
This requires both the Ruby graphviz gem and the graphviz library be installed on the system.
Configuration options:
Defines one or more events for the machine and the transitions that can be performed when those events are run.
This method is also aliased as on for improved compatibility with using a domain-specific language.
Configuration options:
The following instance methods are generated when a new event is defined (the "park" event is used as an example):
With a namespace of "car", the above names map to the following methods:
The can_park? and park_transition helpers both take an optional set of requirements for determining what transitions are available for the current object. These requirements include:
event requires a block which allows you to define the possible transitions that can happen as a result of that event. For example,
event :park, :stop do transition :idling => :parked end event :first_gear do transition :parked => :first_gear, :if => :seatbelt_on? transition :parked => same # Allow to loopback if seatbelt is off end
See StateMachine::Event#transition for more information on the possible options that can be passed in.
Note that this block is executed within the context of the actual event object. As a result, you will not be able to reference any class methods on the model without referencing the class itself. For example,
class Vehicle def self.safe_states [:parked, :idling, :stalled] end state_machine do event :park do transition Vehicle.safe_states => :parked end end end
Additional arguments on event actions can be defined like so:
class Vehicle state_machine do event :park do ... end end def park(kind = :parallel, *args) take_deep_breath if kind == :parallel super end def take_deep_breath sleep 3 end end
Note that super is called instead of super(*args). This allows the entire arguments list to be accessed by transition callbacks through StateMachine::Transition#args like so:
after_transition :on => :park do |vehicle, transition| kind = *transition.args ... end
Remember that if the last argument is a boolean, it will be used as the run_action parameter to the event action. Using the park action example from above, you can might call it like so:
vehicle.park # => Uses default args and runs machine action vehicle.park(:parallel) # => Specifies the +kind+ argument and runs the machine action vehicle.park(:parallel, false) # => Specifies the +kind+ argument and *skips* the machine action
class Vehicle state_machine do # The park, stop, and halt events will all share the given transitions event :park, :stop, :halt do transition [:idling, :backing_up] => :parked end event :stop do transition :first_gear => :idling end event :ignite do transition :parked => :idling transition :idling => same # Allow ignite while still idling end end end
Gets the initial state of the machine for the given object. If a dynamic initial state was configured for this machine, then the object will be passed into the lambda block to help determine the actual state.
With a static initial state:
class Vehicle state_machine :initial => :parked do ... end end vehicle = Vehicle.new Vehicle.state_machine.initial_state(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=true>
With a dynamic initial state:
class Vehicle attr_accessor :force_idle state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked} do ... end end vehicle = Vehicle.new vehicle.force_idle = true Vehicle.state_machine.initial_state(vehicle) # => #<StateMachine::State name=:idling value="idling" initial=false> vehicle.force_idle = false Vehicle.state_machine.initial_state(vehicle) # => #<StateMachine::State name=:parked value="parked" initial=false>
Initializes the state on the given object. Initial values are only set if the machine‘s attribute hasn‘t been previously initialized.
Configuration options:
Generates a list of the possible transition sequences that can be run on the given object. These paths can reveal all of the possible states and events that can be encountered in the object‘s state machine based on the object‘s current state.
Configuration options:
Note that the object is never modified when the list of paths is generated.
class Vehicle state_machine :initial => :parked do event :ignite do transition :parked => :idling end event :shift_up do transition :idling => :first_gear, :first_gear => :second_gear end event :shift_down do transition :second_gear => :first_gear, :first_gear => :idling end end end vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked"> vehicle.state # => "parked" vehicle.state_paths # => [ # [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>, # #<StateMachine::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>, # #<StateMachine::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>, # #<StateMachine::Transition attribute=:state event=:shift_down from="second_gear" from_name=:second_gear to="first_gear" to_name=:first_gear>, # #<StateMachine::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>], # # [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>, # #<StateMachine::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>, # #<StateMachine::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>] # ] vehicle.state_paths(:from => :parked, :to => :second_gear) # => [ # [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>, # #<StateMachine::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>, # #<StateMachine::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>] # ]
In addition to getting the possible paths that can be accessed, you can also get summary information about the states / events that can be accessed at some point along one of the paths. For example:
# Get the list of states that can be accessed from the current state vehicle.state_paths.to_states # => [:idling, :first_gear, :second_gear] # Get the list of events that can be accessed from the current state vehicle.state_paths.events # => [:ignite, :shift_up, :shift_down]
Gets the current value stored in the given object‘s attribute.
For example,
class Vehicle state_machine :initial => :parked do ... end end vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked"> Vehicle.state_machine.read(vehicle, :state) # => "parked" # Equivalent to vehicle.state Vehicle.state_machine.read(vehicle, :event) # => nil # Equivalent to vehicle.state_event
Customizes the definition of one or more states in the machine.
Configuration options:
Whenever a state is automatically discovered in the state machine, its default value is assumed to be the stringified version of the name. For example,
class Vehicle state_machine :initial => :parked do event :ignite do transition :parked => :idling end end end
In the above state machine, there are two states automatically discovered: :parked and :idling. These states, by default, will store their stringified equivalents when an object moves into that state (e.g. "parked" / "idling").
For legacy systems or when tying state machines into existing frameworks, it‘s oftentimes necessary to need to store a different value for a state than the default. In order to continue taking advantage of an expressive state machine and helper methods, every defined state can be re-configured with a custom stored value. For example,
class Vehicle state_machine :initial => :parked do event :ignite do transition :parked => :idling end state :idling, :value => 'IDLING' state :parked, :value => 'PARKED end end
This is also useful if being used in association with a database and, instead of storing the state name in a column, you want to store the state‘s foreign key:
class VehicleState < ActiveRecord::Base end class Vehicle < ActiveRecord::Base state_machine :attribute => :state_id, :initial => :parked do event :ignite do transition :parked => :idling end states.each do |state| self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true) end end end
In the above example, each known state is configured to store it‘s associated database id in the state_id attribute. Also, notice that a lambda block is used to define the state‘s value. This is required in situations (like testing) where the model is loaded without any existing data (i.e. no VehicleState records available).
One caveat to the above example is to keep performance in mind. To avoid constant db hits for looking up the VehicleState ids, the value is cached by specifying the :cache option. Alternatively, a custom caching strategy can be used like so:
class VehicleState < ActiveRecord::Base cattr_accessor :cache_store self.cache_store = ActiveSupport::Cache::MemoryStore.new def self.find_by_name(name) cache_store.fetch(name) { find(:first, :conditions => {:name => name}) } end end
In addition to customizing states with other value types, lambda blocks can also be specified to allow for a state‘s value to be determined dynamically at runtime. For example,
class Vehicle state_machine :purchased_at, :initial => :available do event :purchase do transition all => :purchased end event :restock do transition all => :available end state :available, :value => nil state :purchased, :if => lambda {|value| !value.nil?}, :value => lambda {Time.now} end end
In the above definition, the :purchased state is customized with both a dynamic value and a value matcher.
When an object transitions to the purchased state, the value‘s lambda block will be called. This will get the current time and store it in the object‘s purchased_at attribute.
Note that the custom matcher is very important here. Since there‘s no way for the state machine to figure out an object‘s state when it‘s set to a runtime value, it must be explicitly defined. If the :if option were not configured for the state, then an ArgumentError exception would be raised at runtime, indicating that the state machine could not figure out what the current state of the object was.
Behaviors define a series of methods to mixin with objects when the current state matches the given one(s). This allows instance methods to behave a specific way depending on what the value of the object‘s state is.
For example,
class Vehicle attr_accessor :driver attr_accessor :passenger state_machine :initial => :parked do event :ignite do transition :parked => :idling end state :parked do def speed 0 end def rotate_driver driver = self.driver self.driver = passenger self.passenger = driver true end end state :idling, :first_gear do def speed 20 end def rotate_driver self.state = 'parked' rotate_driver end end other_states :backing_up end end
In the above example, there are two dynamic behaviors defined for the class:
Each of these behaviors are instance methods on the Vehicle class. However, which method actually gets invoked is based on the current state of the object. Using the above class as the example:
vehicle = Vehicle.new vehicle.driver = 'John' vehicle.passenger = 'Jane' # Behaviors in the "parked" state vehicle.state # => "parked" vehicle.speed # => 0 vehicle.rotate_driver # => true vehicle.driver # => "Jane" vehicle.passenger # => "John" vehicle.ignite # => true # Behaviors in the "idling" state vehicle.state # => "idling" vehicle.speed # => 20 vehicle.rotate_driver # => true vehicle.driver # => "John" vehicle.passenger # => "Jane"
As can be seen, both the speed and rotate_driver instance method implementations changed how they behave based on what the current state of the vehicle was.
If a specific behavior has not been defined for a state, then a NoMethodError exception will be raised, indicating that that method would not normally exist for an object with that state.
Using the example from before:
vehicle = Vehicle.new vehicle.state = 'backing_up' vehicle.speed # => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"
In addition to defining scopes for instance methods that are state-aware, the same can be done for certain types of class methods.
Some libraries have support for class-level methods that only run certain behaviors based on a conditions hash passed in. For example:
class Vehicle < ActiveRecord::Base state_machine do ... state :first_gear, :second_gear, :third_gear do validates_presence_of :speed validates_inclusion_of :speed, :in => 0..25, :if => :in_school_zone? end end end
In the above ActiveRecord model, two validations have been defined which will only run when the Vehicle object is in one of the three states: first_gear, second_gear, or +third_gear. Notice, also, that if/unless conditions can continue to be used.
This functionality is not library-specific and can work for any class-level method that is defined like so:
def validates_presence_of(attribute, options = {}) ... end
The minimum requirement is that the last argument in the method be an options hash which contains at least :if condition support.
Runs a transaction, rolling back any changes if the yielded block fails.
This is only applicable to integrations that involve databases. By default, this will not run any transactions since the changes aren‘t taking place within the context of a database.
Sets a new value in the given object‘s attribute.
For example,
class Vehicle state_machine :initial => :parked do ... end end vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked"> Vehicle.state_machine.write(vehicle, :state, 'idling') # => Equivalent to vehicle.state = 'idling' Vehicle.state_machine.write(vehicle, :event, 'park') # => Equivalent to vehicle.state_event = 'park' vehicle.state # => "idling" vehicle.event # => "park"
The method to hook into for triggering transitions when invoked. By default, this is the action configured for the machine.
Since the default hook technique relies on module inheritance, the action must be defined in an ancestor of the owner classs in order for it to be the action hook.
Creates a scope for finding objects with a particular value or values for the attribute.
By default, this is a no-op.
Creates a scope for finding objects without a particular value or values for the attribute.
By default, this is a no-op.
Determines whether action helpers should be defined for this machine. This is only true if there is an action configured and no other machines have process this same configuration already.
Hooks directly into actions by defining the same method in an included module. As a result, when the action gets invoked, any state events defined for the object will get run. Method visibility is preserved.
Adds helper methods for getting information about this state machine‘s events
Adds helper methods for interacting with the state machine, including for states, events, and transitions
Adds helper methods for accessing naming information about states and events on the owner class
Adds helper methods for getting information about this state machine‘s available transition paths
Defines the with/without scope helpers for this attribute. Both the singular and plural versions of the attribute are defined for each scope helper. A custom plural can be specified if it cannot be automatically determined by either calling pluralize on the attribute name or adding an "s" to the end of the name.
Adds predicate method to the owner class for determining the name of the current state
Determines whether any of the ancestors for this machine‘s owner class has the given method defined, even if it‘s private.
Pluralizes the given word using pluralize (if available) or simply adding an "s" to the end of the word