添加了对在任何 Ruby 类上的属性创建状态机的支持
state_machine adds support for creating state machines for attributes on any Ruby class.
API
Bugs
Development
Testing
Source
Mailing List
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:
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.
Below is an example of many of the features offered by this plugin, including:
Class definition:
…
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:
…
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:
A brief overview of these integrations is described below.
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,
…
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.
The ActiveRecord integration adds support for database transactions, automatically saving the record, named scopes, validation errors, and observers. For example,
…
For more information about the various behaviors added for ActiveRecord state
machines, see StateMachine::Integrations::ActiveRecord.
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,
…
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.
The Mongoid integration adds support for automatically saving the record, basic scopes, validation errors, and observers. For example,
…
For more information about the various behaviors added for Mongoid state
machines, see StateMachine::Integrations::Mongoid.
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.
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.
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.
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
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.
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
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
Клитор
FrozenError when calling `.new` on STI model with state_machine in Rails 6.1
StandardError: GraphViz not installed or dot not in PATH. Install GraphViz or use the 'path' option
around_validation error for Rails4.1.0beta
How to get list of event eligible events?
State already defined
Error: Different defined default values
https://www.rubydoc.info/github/pluginaweek/state_machine/master/StateMachine/Machine#on-instance_method
Machine class yardoc page gives error: Invalid namespace object: StateMachine::Machine#owner_class