[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7823":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":16,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":16,"starSnapshotCount":16,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},7823,"state_machine","pluginaweek\u002Fstate_machine","pluginaweek","Adds support for creating state machines for attributes on any Ruby class","http:\u002F\u002Fwww.pluginaweek.org",null,"Ruby",3722,519,74,59,0,60.15,"MIT License",false,"master",true,[],"2026-06-12 04:00:35","# state_machine [![Build Status](https:\u002F\u002Fsecure.travis-ci.org\u002Fpluginaweek\u002Fstate_machine.png \"Build Status\")](http:\u002F\u002Ftravis-ci.org\u002Fpluginaweek\u002Fstate_machine) [![Dependency Status](https:\u002F\u002Fgemnasium.com\u002Fpluginaweek\u002Fstate_machine.png \"Dependency Status\")](https:\u002F\u002Fgemnasium.com\u002Fpluginaweek\u002Fstate_machine)\n\n*state_machine* adds support for creating state machines for attributes on any\nRuby class.\n\n## Resources\n\nAPI\n\n* http:\u002F\u002Frdoc.info\u002Fgithub\u002Fpluginaweek\u002Fstate_machine\u002Fmaster\u002Fframes\n\nBugs\n\n* http:\u002F\u002Fgithub.com\u002Fpluginaweek\u002Fstate_machine\u002Fissues\n\nDevelopment\n\n* http:\u002F\u002Fgithub.com\u002Fpluginaweek\u002Fstate_machine\n\nTesting\n\n* http:\u002F\u002Ftravis-ci.org\u002Fpluginaweek\u002Fstate_machine\n\nSource\n\n* git:\u002F\u002Fgithub.com\u002Fpluginaweek\u002Fstate_machine.git\n\nMailing List\n\n* http:\u002F\u002Fgroups.google.com\u002Fgroup\u002Fpluginaweek-talk\n\n## Description\n\nState machines make it dead-simple to manage the behavior of a class.  Too often,\nthe state of an object is kept by creating multiple boolean attributes and\ndeciding how to behave based on the values.  This can become cumbersome and\ndifficult to maintain when the complexity of your class starts to increase.\n\n*state_machine* simplifies this design by introducing the various parts of a real\nstate machine, including states, events, transitions, and callbacks.  However,\nthe api is designed to be so simple you don't even need to know what a\nstate machine is :)\n\nSome brief, high-level features include:\n\n* Defining state machines on any Ruby class\n* Multiple state machines on a single class\n* Namespaced state machines\n* before\u002Fafter\u002Faround\u002Ffailure transition hooks with explicit transition requirements\n* Integration with ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel\n* State predicates\n* State-driven instance \u002F class behavior\n* State values of any data type\n* Dynamically-generated state values\n* Event parallelization\n* Attribute-based event transitions\n* Path analysis\n* Inheritance\n* Internationalization\n* GraphViz visualization creator\n* YARD integration (Ruby 1.9+ only)\n* Flexible machine syntax\n\nExamples of the usage patterns for some of the above features are shown below.\nYou can find much more detailed documentation in the actual API.\n\n## Usage\n\n### Example\n\nBelow is an example of many of the features offered by this plugin, including:\n\n* Initial states\n* Namespaced states\n* Transition callbacks\n* Conditional transitions\n* State-driven instance behavior\n* Customized state values\n* Parallel events\n* Path analysis\n\nClass definition:\n\n```ruby\nclass Vehicle\n  attr_accessor :seatbelt_on, :time_used, :auto_shop_busy\n  \n  state_machine :state, :initial => :parked do\n    before_transition :parked => any - :parked, :do => :put_on_seatbelt\n    \n    after_transition :on => :crash, :do => :tow\n    after_transition :on => :repair, :do => :fix\n    after_transition any => :parked do |vehicle, transition|\n      vehicle.seatbelt_on = false\n    end\n    \n    after_failure :on => :ignite, :do => :log_start_failure\n    \n    around_transition do |vehicle, transition, block|\n      start = Time.now\n      block.call\n      vehicle.time_used += Time.now - start\n    end\n    \n    event :park do\n      transition [:idling, :first_gear] => :parked\n    end\n    \n    event :ignite do\n      transition :stalled => same, :parked => :idling\n    end\n    \n    event :idle do\n      transition :first_gear => :idling\n    end\n    \n    event :shift_up do\n      transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear\n    end\n    \n    event :shift_down do\n      transition :third_gear => :second_gear, :second_gear => :first_gear\n    end\n    \n    event :crash do\n      transition all - [:parked, :stalled] => :stalled, :if => lambda {|vehicle| !vehicle.passed_inspection?}\n    end\n    \n    event :repair do\n      # The first transition that matches the state and passes its conditions\n      # will be used\n      transition :stalled => :parked, :unless => :auto_shop_busy\n      transition :stalled => same\n    end\n    \n    state :parked do\n      def speed\n        0\n      end\n    end\n    \n    state :idling, :first_gear do\n      def speed\n        10\n      end\n    end\n    \n    state all - [:parked, :stalled, :idling] do\n      def moving?\n        true\n      end\n    end\n    \n    state :parked, :stalled, :idling do\n      def moving?\n        false\n      end\n    end\n  end\n  \n  state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do\n    event :enable do\n      transition all => :active\n    end\n    \n    event :disable do\n      transition all => :off\n    end\n    \n    state :active, :value => 1\n    state :off, :value => 0\n  end\n  \n  def initialize\n    @seatbelt_on = false\n    @time_used = 0\n    @auto_shop_busy = true\n    super() # NOTE: This *must* be called, otherwise states won't get initialized\n  end\n  \n  def put_on_seatbelt\n    @seatbelt_on = true\n  end\n  \n  def passed_inspection?\n    false\n  end\n  \n  def tow\n    # tow the vehicle\n  end\n  \n  def fix\n    # get the vehicle fixed by a mechanic\n  end\n  \n  def log_start_failure\n    # log a failed attempt to start the vehicle\n  end\nend\n```\n\n**Note** the comment made on the `initialize` method in the class.  In order for\nstate machine attributes to be properly initialized, `super()` must be called.\nSee `StateMachine::MacroMethods` for more information about this.\n\nUsing the above class as an example, you can interact with the state machine\nlike so:\n\n```ruby\nvehicle = Vehicle.new           # => #\u003CVehicle:0xb7cf4eac @state=\"parked\", @seatbelt_on=false>\nvehicle.state                   # => \"parked\"\nvehicle.state_name              # => :parked\nvehicle.human_state_name        # => \"parked\"\nvehicle.parked?                 # => true\nvehicle.can_ignite?             # => true\nvehicle.ignite_transition       # => #\u003CStateMachine::Transition attribute=:state event=:ignite from=\"parked\" from_name=:parked to=\"idling\" to_name=:idling>\nvehicle.state_events            # => [:ignite]\nvehicle.state_transitions       # => [#\u003CStateMachine::Transition attribute=:state event=:ignite from=\"parked\" from_name=:parked to=\"idling\" to_name=:idling>]\nvehicle.speed                   # => 0\nvehicle.moving?                 # => false\n\nvehicle.ignite                  # => true\nvehicle.parked?                 # => false\nvehicle.idling?                 # => true\nvehicle.speed                   # => 10\nvehicle                         # => #\u003CVehicle:0xb7cf4eac @state=\"idling\", @seatbelt_on=true>\n\nvehicle.shift_up                # => true\nvehicle.speed                   # => 10\nvehicle.moving?                 # => true\nvehicle                         # => #\u003CVehicle:0xb7cf4eac @state=\"first_gear\", @seatbelt_on=true>\n\n# A generic event helper is available to fire without going through the event's instance method\nvehicle.fire_state_event(:shift_up) # => true\n\n# Call state-driven behavior that's undefined for the state raises a NoMethodError\nvehicle.speed                   # => NoMethodError: super: no superclass method `speed' for #\u003CVehicle:0xb7cf4eac>\nvehicle                         # => #\u003CVehicle:0xb7cf4eac @state=\"second_gear\", @seatbelt_on=true>\n\n# The bang (!) operator can raise exceptions if the event fails\nvehicle.park!                   # => StateMachine::InvalidTransition: Cannot transition state via :park from :second_gear\n\n# Generic state predicates can raise exceptions if the value does not exist\nvehicle.state?(:parked)         # => false\nvehicle.state?(:invalid)        # => IndexError: :invalid is an invalid name\n\n# Namespaced machines have uniquely-generated methods\nvehicle.alarm_state             # => 1\nvehicle.alarm_state_name        # => :active\n\nvehicle.can_disable_alarm?      # => true\nvehicle.disable_alarm           # => true\nvehicle.alarm_state             # => 0\nvehicle.alarm_state_name        # => :off\nvehicle.can_enable_alarm?       # => true\n\nvehicle.alarm_off?              # => true\nvehicle.alarm_active?           # => false\n\n# Events can be fired in parallel\nvehicle.fire_events(:shift_down, :enable_alarm) # => true\nvehicle.state_name                              # => :first_gear\nvehicle.alarm_state_name                        # => :active\n\nvehicle.fire_events!(:ignite, :enable_alarm)    # => StateMachine::InvalidTransition: Cannot run events in parallel: ignite, enable_alarm\n\n# Human-friendly names can be accessed for states\u002Fevents\nVehicle.human_state_name(:first_gear)               # => \"first gear\"\nVehicle.human_alarm_state_name(:active)             # => \"active\"\n\nVehicle.human_state_event_name(:shift_down)         # => \"shift down\"\nVehicle.human_alarm_state_event_name(:enable)       # => \"enable\"\n\n# States \u002F events can also be references by the string version of their name\nVehicle.human_state_name('first_gear')              # => \"first gear\"\nVehicle.human_state_event_name('shift_down')        # => \"shift down\"\n\n# Available transition paths can be analyzed for an object\nvehicle.state_paths                                       # => [[#\u003CStateMachine::Transition ...], [#\u003CStateMachine::Transition ...], ...]\nvehicle.state_paths.to_states                             # => [:parked, :idling, :first_gear, :stalled, :second_gear, :third_gear]\nvehicle.state_paths.events                                # => [:park, :ignite, :shift_up, :idle, :crash, :repair, :shift_down]\n\n# Find all paths that start and end on certain states\nvehicle.state_paths(:from => :parked, :to => :first_gear) # => [[\n                                                          #       #\u003CStateMachine::Transition attribute=:state event=:ignite from=\"parked\" ...>,\n                                                          #       #\u003CStateMachine::Transition attribute=:state event=:shift_up from=\"idling\" ...>\n                                                          #    ]]\n# Skipping state_machine and writing to attributes directly\nvehicle.state = \"parked\"\nvehicle.state                   # => \"parked\"\nvehicle.state_name              # => :parked\n\n# *Note* that the following is not supported (see StateMachine::MacroMethods#state_machine):\n# vehicle.state = :parked\n```\n\n## Integrations\n\nIn addition to being able to define state machines on all Ruby classes, a set of\nout-of-the-box integrations are available for some of the more popular Ruby\nlibraries.  These integrations add library-specific behavior, allowing for state\nmachines to work more tightly with the conventions defined by those libraries.\n\nThe integrations currently available include:\n\n* ActiveModel classes\n* ActiveRecord models\n* DataMapper resources\n* Mongoid models\n* MongoMapper models\n* Sequel models\n\nA brief overview of these integrations is described below.\n\n### ActiveModel\n\nThe ActiveModel integration is useful for both standalone usage and for providing\nthe base implementation for ORMs which implement the ActiveModel API.  This\nintegration adds support for validation errors, dirty attribute tracking, and\nobservers.  For example,\n\n```ruby\nclass Vehicle\n  include ActiveModel::Dirty\n  include ActiveModel::Validations\n  include ActiveModel::Observing\n  \n  attr_accessor :state\n  define_attribute_methods [:state]\n  \n  state_machine :initial => :parked do\n    before_transition :parked => any - :parked, :do => :put_on_seatbelt\n    after_transition any => :parked do |vehicle, transition|\n      vehicle.seatbelt = 'off'\n    end\n    around_transition :benchmark\n    \n    event :ignite do\n      transition :parked => :idling\n    end\n    \n    state :first_gear, :second_gear do\n      validates_presence_of :seatbelt_on\n    end\n  end\n  \n  def put_on_seatbelt\n    ...\n  end\n  \n  def benchmark\n    ...\n    yield\n    ...\n  end\nend\n\nclass VehicleObserver \u003C ActiveModel::Observer\n  # Callback for :ignite event *before* the transition is performed\n  def before_ignite(vehicle, transition)\n    # log message\n  end\n  \n  # Generic transition callback *after* the transition is performed\n  def after_transition(vehicle, transition)\n    Audit.log(vehicle, transition)\n  end\n  \n  # Generic callback after the transition fails to perform\n  def after_failure_to_transition(vehicle, transition)\n    Audit.error(vehicle, transition)\n  end\nend\n```\n\nFor more information about the various behaviors added for ActiveModel state\nmachines and how to build new integrations that use ActiveModel, see\n`StateMachine::Integrations::ActiveModel`.\n\n### ActiveRecord\n\nThe ActiveRecord integration adds support for database transactions, automatically\nsaving the record, named scopes, validation errors, and observers.  For example,\n\n```ruby\nclass Vehicle \u003C ActiveRecord::Base\n  state_machine :initial => :parked do\n    before_transition :parked => any - :parked, :do => :put_on_seatbelt\n    after_transition any => :parked do |vehicle, transition|\n      vehicle.seatbelt = 'off'\n    end\n    around_transition :benchmark\n    \n    event :ignite do\n      transition :parked => :idling\n    end\n    \n    state :first_gear, :second_gear do\n      validates_presence_of :seatbelt_on\n    end\n  end\n  \n  def put_on_seatbelt\n    ...\n  end\n  \n  def benchmark\n    ...\n    yield\n    ...\n  end\nend\n\nclass VehicleObserver \u003C ActiveRecord::Observer\n  # Callback for :ignite event *before* the transition is performed\n  def before_ignite(vehicle, transition)\n    # log message\n  end\n  \n  # Generic transition callback *after* the transition is performed\n  def after_transition(vehicle, transition)\n    Audit.log(vehicle, transition)\n  end\nend\n```\n\nFor more information about the various behaviors added for ActiveRecord state\nmachines, see `StateMachine::Integrations::ActiveRecord`.\n\n### DataMapper\n\nLike the ActiveRecord integration, the DataMapper integration adds support for\ndatabase transactions, automatically saving the record, named scopes, Extlib-like\ncallbacks, validation errors, and observers.  For example,\n\n```ruby\nclass Vehicle\n  include DataMapper::Resource\n  \n  property :id, Serial\n  property :state, String\n  \n  state_machine :initial => :parked do\n    before_transition :parked => any - :parked, :do => :put_on_seatbelt\n    after_transition any => :parked do |transition|\n      self.seatbelt = 'off' # self is the record\n    end\n    around_transition :benchmark\n    \n    event :ignite do\n      transition :parked => :idling\n    end\n    \n    state :first_gear, :second_gear do\n      validates_presence_of :seatbelt_on\n    end\n  end\n  \n  def put_on_seatbelt\n    ...\n  end\n  \n  def benchmark\n    ...\n    yield\n    ...\n  end\nend\n\nclass VehicleObserver\n  include DataMapper::Observer\n  \n  observe Vehicle\n  \n  # Callback for :ignite event *before* the transition is performed\n  before_transition :on => :ignite do |transition|\n    # log message (self is the record)\n  end\n  \n  # Generic transition callback *after* the transition is performed\n  after_transition do |transition|\n    Audit.log(self, transition) # self is the record\n  end\n  \n  around_transition do |transition, block|\n    # mark start time\n    block.call\n    # mark stop time\n  end\n  \n  # Generic callback after the transition fails to perform\n  after_transition_failure do |transition|\n    Audit.log(self, transition) # self is the record\n  end\nend\n```\n\n**Note** that the DataMapper::Observer integration is optional and only available\nwhen the dm-observer library is installed.\n\nFor more information about the various behaviors added for DataMapper state\nmachines, see `StateMachine::Integrations::DataMapper`.\n\n### Mongoid\n\nThe Mongoid integration adds support for automatically saving the record,\nbasic scopes, validation errors, and observers.  For example,\n\n```ruby\nclass Vehicle\n  include Mongoid::Document\n  \n  state_machine :initial => :parked do\n    before_transition :parked => any - :parked, :do => :put_on_seatbelt\n    after_transition any => :parked do |vehicle, transition|\n      vehicle.seatbelt = 'off' # self is the record\n    end\n    around_transition :benchmark\n    \n    event :ignite do\n      transition :parked => :idling\n    end\n    \n    state :first_gear, :second_gear do\n      validates_presence_of :seatbelt_on\n    end\n  end\n  \n  def put_on_seatbelt\n    ...\n  end\n  \n  def benchmark\n    ...\n    yield\n    ...\n  end\nend\n\nclass VehicleObserver \u003C Mongoid::Observer\n  # Callback for :ignite event *before* the transition is performed\n  def before_ignite(vehicle, transition)\n    # log message\n  end\n  \n  # Generic transition callback *after* the transition is performed\n  def after_transition(vehicle, transition)\n    Audit.log(vehicle, transition)\n  end\nend\n```\n\nFor more information about the various behaviors added for Mongoid state\nmachines, see `StateMachine::Integrations::Mongoid`.\n\n### MongoMapper\n\nThe MongoMapper integration adds support for automatically saving the record,\nbasic scopes, validation errors and callbacks.  For example,\n\n```ruby\nclass Vehicle\n  include MongoMapper::Document\n  \n  state_machine :initial => :parked do\n    before_transition :parked => any - :parked, :do => :put_on_seatbelt\n    after_transition any => :parked do |vehicle, transition|\n      vehicle.seatbelt = 'off' # self is the record\n    end\n    around_transition :benchmark\n    \n    event :ignite do\n      transition :parked => :idling\n    end\n    \n    state :first_gear, :second_gear do\n      validates_presence_of :seatbelt_on\n    end\n  end\n  \n  def put_on_seatbelt\n    ...\n  end\n  \n  def benchmark\n    ...\n    yield\n    ...\n  end\nend\n```\n\nFor more information about the various behaviors added for MongoMapper state\nmachines, see `StateMachine::Integrations::MongoMapper`.\n\n### Sequel\n\nLike the ActiveRecord integration, the Sequel integration adds support for\ndatabase transactions, automatically saving the record, named scopes, validation\nerrors and callbacks.  For example,\n\n```ruby\nclass Vehicle \u003C Sequel::Model\n  plugin :validation_class_methods\n  \n  state_machine :initial => :parked do\n    before_transition :parked => any - :parked, :do => :put_on_seatbelt\n    after_transition any => :parked do |transition|\n      self.seatbelt = 'off' # self is the record\n    end\n    around_transition :benchmark\n    \n    event :ignite do\n      transition :parked => :idling\n    end\n    \n    state :first_gear, :second_gear do\n      validates_presence_of :seatbelt_on\n    end\n  end\n  \n  def put_on_seatbelt\n    ...\n  end\n  \n  def benchmark\n    ...\n    yield\n    ...\n  end\nend\n```\n\nFor more information about the various behaviors added for Sequel state\nmachines, see `StateMachine::Integrations::Sequel`.\n\n## Additional Topics\n\n### Explicit vs. Implicit Event Transitions\n\nEvery event defined for a state machine generates an instance method on the\nclass that allows the event to be explicitly triggered.  Most of the examples in\nthe state_machine documentation use this technique.  However, with some types of\nintegrations, like ActiveRecord, you can also *implicitly* fire events by\nsetting a special attribute on the instance.\n\nSuppose you're using the ActiveRecord integration and the following model is\ndefined:\n\n```ruby\nclass Vehicle \u003C ActiveRecord::Base\n  state_machine :initial => :parked do\n    event :ignite do\n      transition :parked => :idling\n    end\n  end\nend\n```\n\nTo trigger the `ignite` event, you would typically call the `Vehicle#ignite`\nmethod like so:\n\n```ruby\nvehicle = Vehicle.create    # => #\u003CVehicle id=1 state=\"parked\">\nvehicle.ignite              # => true\nvehicle.state               # => \"idling\"\n```\n\nThis is referred to as an *explicit* event transition.  The same behavior can\nalso be achieved *implicitly* by setting the state event attribute and invoking\nthe action associated with the state machine.  For example:\n\n```ruby\nvehicle = Vehicle.create        # => #\u003CVehicle id=1 state=\"parked\">\nvehicle.state_event = \"ignite\"  # => \"ignite\"\nvehicle.save                    # => true\nvehicle.state                   # => \"idling\"\nvehicle.state_event             # => nil\n```\n\nAs you can see, the `ignite` event was automatically triggered when the `save`\naction was called.  This is particularly useful if you want to allow users to\ndrive the state transitions from a web API.\n\nSee each integration's API documentation for more information on the implicit\napproach.\n\n### Symbols vs. Strings\n\nIn all of the examples used throughout the documentation, you'll notice that\nstates and events are almost always referenced as symbols.  This isn't a\nrequirement, but rather a suggested best practice.\n\nYou can very well define your state machine with Strings like so:\n\n```ruby\nclass Vehicle\n  state_machine :initial => 'parked' do\n    event 'ignite' do\n      transition 'parked' => 'idling'\n    end\n    \n    # ...\n  end\nend\n```\n\nYou could even use numbers as your state \u002F event names.  The **important** thing\nto keep in mind is that the type being used for referencing states \u002F events in\nyour machine definition must be **consistent**.  If you're using Symbols, then\nall states \u002F events must use Symbols.  Otherwise you'll encounter the following\nerror:\n\n```ruby\nclass Vehicle\n  state_machine do\n    event :ignite do\n      transition :parked => 'idling'\n    end\n  end\nend\n\n# => ArgumentError: \"idling\" state defined as String, :parked defined as Symbol; all states must be consistent\n```\n\nThere **is** an exception to this rule.  The consistency is only required within\nthe definition itself.  However, when the machine's helper methods are called\nwith input from external sources, such as a web form, state_machine will map\nthat input to a String \u002F Symbol.  For example:\n\n```ruby\nclass Vehicle\n  state_machine :initial => :parked do\n    event :ignite do\n      transition :parked => :idling\n    end\n  end\nend\n\nv = Vehicle.new     # => #\u003CVehicle:0xb71da5f8 @state=\"parked\">\nv.state?('parked')  # => true\nv.state?(:parked)   # => true\n```\n\n**Note** that none of this actually has to do with the type of the value that\ngets stored.  By default, all state values are assumed to be string -- regardless\nof whether the state names are symbols or strings.  If you want to store states\nas symbols instead you'll have to be explicit about it:\n\n```ruby\nclass Vehicle\n  state_machine :initial => :parked do\n    event :ignite do\n      transition :parked => :idling\n    end\n    \n    states.each do |state|\n      self.state(state.name, :value => state.name.to_sym)\n    end\n  end\nend\n\nv = Vehicle.new     # => #\u003CVehicle:0xb71da5f8 @state=:parked>\nv.state?('parked')  # => true\nv.state?(:parked)   # => true\n```\n\n### Syntax flexibility\n\nAlthough state_machine introduces a simplified syntax, it still remains\nbackwards compatible with previous versions and other state-related libraries by\nproviding some flexibility around how transitions are defined.  See below for an\noverview of these syntaxes.\n\n#### Verbose syntax\n\nIn general, it's recommended that state machines use the implicit syntax for\ntransitions.  However, you can be a little more explicit and verbose about\ntransitions by using the `:from`, `:except_from`, `:to`,\nand `:except_to` options.\n\nFor example, transitions and callbacks can be defined like so:\n\n```ruby\nclass Vehicle\n  state_machine :initial => :parked do\n    before_transition :from => :parked, :except_to => :parked, :do => :put_on_seatbelt\n    after_transition :to => :parked do |transition|\n      self.seatbelt = 'off' # self is the record\n    end\n    \n    event :ignite do\n      transition :from => :parked, :to => :idling\n    end\n  end\nend\n```\n\n#### Transition context\n\nSome flexibility is provided around the context in which transitions can be\ndefined.  In almost all examples throughout the documentation, transitions are\ndefined within the context of an event.  If you prefer to have state machines\ndefined in the context of a **state** either out of preference or in order to\neasily migrate from a different library, you can do so as shown below:\n\n```ruby\nclass Vehicle\n  state_machine :initial => :parked do\n    ...\n    \n    state :parked do\n      transition :to => :idling, :on => [:ignite, :shift_up], :if => :seatbelt_on?\n      \n      def speed\n        0\n      end\n    end\n    \n    state :first_gear do\n      transition :to => :second_gear, :on => :shift_up\n      \n      def speed\n        10\n      end\n    end\n    \n    state :idling, :first_gear do\n      transition :to => :parked, :on => :park\n    end\n  end\nend\n```\n\nIn the above example, there's no need to specify the `from` state for each\ntransition since it's inferred from the context.\n\nYou can also define transitions completely outside the context of a particular\nstate \u002F event.  This may be useful in cases where you're building a state\nmachine from a data store instead of part of the class definition.  See the\nexample below:\n\n```ruby\nclass Vehicle\n  state_machine :initial => :parked do\n    ...\n    \n    transition :parked => :idling, :on => [:ignite, :shift_up]\n    transition :first_gear => :second_gear, :second_gear => :third_gear, :on => :shift_up\n    transition [:idling, :first_gear] => :parked, :on => :park\n    transition [:idling, :first_gear] => :parked, :on => :park\n    transition all - [:parked, :stalled] => :stalled, :unless => :auto_shop_busy?\n  end\nend\n```\n\nNotice that in these alternative syntaxes:\n\n* You can continue to configure `:if` and `:unless` conditions\n* You can continue to define `from` states (when in the machine context) using\nthe `all`, `any`, and `same` helper methods\n\n### Static \u002F Dynamic definitions\n\nIn most cases, the definition of a state machine is **static**.  That is to say,\nthe states, events and possible transitions are known ahead of time even though\nthey may depend on data that's only known at runtime.  For example, certain\ntransitions may only be available depending on an attribute on that object it's\nbeing run on.  All of the documentation in this library define static machines\nlike so:\n\n```ruby\nclass Vehicle\n  state_machine :state, :initial => :parked do\n    event :park do\n      transition [:idling, :first_gear] => :parked\n    end\n    \n    ...\n  end\nend\n```\n\nHowever, there may be cases where the definition of a state machine is **dynamic**.\nThis means that you don't know the possible states or events for a machine until\nruntime.  For example, you may allow users in your application to manage the\nstate machine of a project or task in your system.  This means that the list of\ntransitions (and their associated states \u002F events) could be stored externally,\nsuch as in a database.  In a case like this, you can define dynamically-generated\nstate machines like so:\n\n```ruby\nclass Vehicle\n  attr_accessor :state\n  \n  # Make sure the machine gets initialized so the initial state gets set properly\n  def initialize(*)\n    super\n    machine\n  end\n  \n  # Replace this with an external source (like a db)\n  def transitions\n    [\n      {:parked => :idling, :on => :ignite},\n      {:idling => :first_gear, :first_gear => :second_gear, :on => :shift_up}\n      # ...\n    ]\n  end\n  \n  # Create a state machine for this vehicle instance dynamically based on the\n  # transitions defined from the source above\n  def machine\n    vehicle = self\n    @machine ||= Machine.new(vehicle, :initial => :parked, :action => :save) do\n      vehicle.transitions.each {|attrs| transition(attrs)}\n    end\n  end\n  \n  def save\n    # Save the state change...\n    true\n  end\nend\n\n# Generic class for building machines\nclass Machine\n  def self.new(object, *args, &block)\n    machine_class = Class.new\n    machine = machine_class.state_machine(*args, &block)\n    attribute = machine.attribute\n    action = machine.action\n    \n    # Delegate attributes\n    machine_class.class_eval do\n      define_method(:definition) { machine }\n      define_method(attribute) { object.send(attribute) }\n      define_method(\"#{attribute}=\") {|value| object.send(\"#{attribute}=\", value) }\n      define_method(action) { object.send(action) } if action\n    end\n    \n    machine_class.new\n  end\nend\n\nvehicle = Vehicle.new                   # => #\u003CVehicle:0xb708412c @state=\"parked\" ...>\nvehicle.state                           # => \"parked\"\nvehicle.machine.ignite                  # => true\nvehicle.machine.state                   # => \"idling\nvehicle.state                           # => \"idling\"\nvehicle.machine.state_transitions       # => [#\u003CStateMachine::Transition ...>]\nvehicle.machine.definition.states.keys  # => :first_gear, :second_gear, :parked, :idling\n```\n\nAs you can see, state_machine provides enough flexibility for you to be able\nto create new machine definitions on the fly based on an external source of\ntransitions.\n\n### Core Extensions\n\nBy default, state_machine extends the Ruby core with a `state_machine` method on\n`Class`.  All other parts of the library are confined within the `StateMachine`\nnamespace.  While this isn't wholly necessary, it also doesn't have any performance\nimpact and makes it truly feel like an extension to the language.  This is very\nsimilar to the way that you'll find `yaml`, `json`, or other libraries adding a\nsimple method to all objects just by loading the library.\n\nHowever, if you'd like to avoid having state_machine add this extension to the\nRuby core, you can do so like so:\n\n```ruby\nrequire 'state_machine\u002Fcore'\n\nclass Vehicle\n  extend StateMachine::MacroMethods\n  \n  state_machine do\n    # ...\n  end\nend\n```\n\nIf you're using a gem loader like Bundler, you can explicitly indicate which\nfile to load:\n\n```ruby\n# In Gemfile\n...\ngem 'state_machine', :require => 'state_machine\u002Fcore'\n```\n\n## Tools\n\n### Generating graphs\n\nThis library comes with built-in support for generating di-graphs based on the\nevents, states, and transitions defined for a state machine using [GraphViz](http:\u002F\u002Fwww.graphviz.org).\nThis requires that both the `ruby-graphviz` gem and graphviz library be\ninstalled on the system.\n\n#### Examples\n\nTo generate a graph for a specific file \u002F class:\n\n```bash\nrake state_machine:draw FILE=vehicle.rb CLASS=Vehicle\n```\n\nTo save files to a specific path:\n\n```bash\nrake state_machine:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files\n```\n\nTo customize the image format \u002F orientation:\n\n```bash\nrake state_machine:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg ORIENTATION=landscape\n```\n\nSee http:\u002F\u002Frdoc.info\u002Fgithub\u002Fglejeune\u002FRuby-Graphviz\u002FConstants for the list of\nsupported image formats.  If resolution is an issue, the svg format may offer\nbetter results.\n\nTo generate multiple state machine graphs:\n\n```bash\nrake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car\n```\n\nTo use human state \u002F event names:\n\n```bash\nrake state_machine:draw FILE=vehicle.rb CLASS=Vehicle HUMAN_NAMES=true\n```\n\n**Note** that this will generate a different file for every state machine defined\nin the class.  The generated files will use an output filename of the format\n`#{class_name}_#{machine_name}.#{format}`.\n\nFor examples of actual images generated using this task, see those under the\nexamples folder.\n\n### Interactive graphs\n\nJean Bovet's [Visual Automata Simulator](http:\u002F\u002Fwww.cs.usfca.edu\u002F~jbovet\u002Fvas.html)\nis a great tool for \"simulating, visualizing and transforming finite state\nautomata and Turing Machines\".  It can help in the creation of states and events\nfor your models.  It is cross-platform, written in Java.\n\n### Generating documentation\n\nIf you use YARD to generate documentation for your projects, state_machine can\nbe enabled to generate API docs for auto-generated methods from each state machine\ndefinition as well as providing embedded visualizations.\n\nSee the generated API documentation under the examples folder to see what the\noutput looks like.\n\nTo enable the YARD integration, you'll need to add state_machine to the list of\nYARD's plugins by editing the global YARD config:\n\n~\u002F.yard\u002Fconfig:\n\n```yaml\nload_plugins: true\nautoload_plugins:\n  - state_machine\n```\n\nOnce enabled, simply generate your documentation like you normally do.\n\n*Note* that this only works for Ruby 1.9+.\n\n## Web Frameworks\n\n### Ruby on Rails\n\nIntegrating state_machine into your Ruby on Rails application is straightforward\nand provides a few additional features specific to the framework. To get\nstarted, following the steps below.\n\n#### 1. Install the gem\n\nIf using Rails 2.x:\n\n```ruby\n# In config\u002Fenvironment.rb\n...\nRails::Initializer.run do |config|\n  ...\n  config.gem 'state_machine', :version => '~> 1.0'\n  ...\nend\n```\n\nIf using Rails 3.x or up:\n\n```ruby\n# In Gemfile\n...\ngem 'state_machine'\ngem 'ruby-graphviz', :require => 'graphviz' # Optional: only required for graphing\n```\n\nAs usual, run `bundle install` to load the gems.\n\n#### 2. Create a model\n\nCreate a model with a field to store the state, along with other any other\nfields your application requires:\n\n```bash\n$ rails generate model Vehicle state:string\n$ rake db:migrate\n```\n\n#### 3. Configure the state machine\n\nAdd the state machine to your model.  Following the examples above,\n*app\u002Fmodels\u002Fvehicle.rb* might become:\n\n```ruby\nclass Vehicle \u003C ActiveRecord::Base\n  state_machine :initial => :parked do\n    before_transition :parked => any - :parked, :do => :put_on_seatbelt\n    ...\n  end\nend\n```\n\n#### Rake tasks\n\nThere is a special integration Rake task for generating state machines for\nclasses used in a Ruby on Rails application.  This task will load the application\nenvironment, meaning that it's unnecessary to specify the actual file to load.\n\nFor example,\n\n```bash\nrake state_machine:draw CLASS=Vehicle\n```\n\nIf you are using this library as a gem in Rails 2.x, the following must be added\nto the end of your application's Rakefile in order for the above task to work:\n\n```ruby\nrequire 'tasks\u002Fstate_machine'\n```\n\n### Merb\n\n#### Rake tasks\n\nLike Ruby on Rails, there is a special integration Rake task for generating\nstate machines for classes used in a Merb application.  This task will load the\napplication environment, meaning that it's unnecessary to specify the actual\nfiles to load.\n\nFor example,\n\n```bash\nrake state_machine:draw CLASS=Vehicle\n```\n\n## Testing\n\nTo run the core test suite (does **not** test any of the integrations):\n\n```bash\nbundle install\nbundle exec rake test\n```\n\nTo run integration tests:\n\n```bash\nbundle install\nrake appraisal:install\nrake appraisal:test\n```\n\nYou can also test a specific version:\n\n```bash\nrake appraisal:active_model-3.0.0 test\nrake appraisal:active_record-2.0.0 test\nrake appraisal:data_mapper-0.9.4 test\nrake appraisal:mongoid-2.0.0 test\nrake appraisal:mongo_mapper-0.5.5 test\nrake appraisal:sequel-2.8.0 test\n```\n\n## Caveats\n\nThe following caveats should be noted when using state_machine:\n\n* Overridden event methods won't get invoked when using attribute-based event transitions\n* **DataMapper**: Attribute-based event transitions are disabled when using dm-validations 0.9.4 - 0.9.6\n* **DataMapper**: Transitions cannot persist states when run from after :create \u002F :save callbacks\n* **JRuby \u002F Rubinius**: around_transition callbacks in ORM integrations won't work on JRuby since it doesn't support continuations\n* **Factory Girl**: Dynamic initial states don't work because of the way factory_girl\n  builds objects.  You can work around this in a few ways:\n  1. Use a default state that is common across all objects and rely on events to\n  determine the actual initial state for your object.\n  2. Assuming you're not using state-driven behavior on initialization, you can\n  re-initialize states after the fact:\n\n```ruby\n# Re-initialize in FactoryGirl\nFactoryGirl.define do\n  factory :vehicle do\n    after_build {|user| user.send(:initialize_state_machines, :dynamic => :force)}\n  end\nend\n\n# Alternatively re-initialize in your model\nclass Vehicle \u003C ActiveRecord::Base\n  ...\n  before_validation :on => :create {|user| user.send(:initialize_state_machines, :dynamic => :force)}\nend\n```\n\n## Dependencies\n\nRuby versions officially supported and tested:\n\n* Ruby (MRI) 1.8.6+\n* JRuby (1.8, 1.9)\n* Rubinius (1.8, 1.9)\n\nORM versions officially supported and tested:\n\n* [ActiveModel](http:\u002F\u002Frubyonrails.org) integration: 3.0.0 or later\n* [ActiveRecord](http:\u002F\u002Frubyonrails.org) integration: 2.0.0 or later\n* [DataMapper](http:\u002F\u002Fdatamapper.org) integration: 0.9.4 or later\n* [Mongoid](http:\u002F\u002Fmongoid.org) integration: 2.0.0 or later\n* [MongoMapper](http:\u002F\u002Fmongomapper.com) integration: 0.5.5 or later\n* [Sequel](http:\u002F\u002Fsequel.rubyforge.org) integration: 2.8.0 or later\n\nIf graphing state machine:\n\n* [ruby-graphviz](http:\u002F\u002Fgithub.com\u002Fglejeune\u002FRuby-Graphviz): 0.9.17 or later\n","*state_machine* 是一个 Ruby 库，用于在任何 Ruby 类的属性上创建状态机。其核心功能包括定义状态、事件、转换和回调，支持多状态机、命名空间状态机、条件转换以及与 ActiveModel、ActiveRecord 等多种 ORM 的集成。此外，它还提供了状态谓词、基于状态的行为控制、动态生成的状态值、并行事件处理、路径分析等功能，并且能够生成 GraphViz 可视化图表。此库适用于需要管理复杂对象状态的应用场景，如工作流管理系统、订单处理系统等，帮助开发者简化状态管理逻辑，提高代码可维护性。",2,"2026-06-11 03:14:34","top_language"]