{"id":13463326,"url":"https://github.com/troessner/transitions","last_synced_at":"2025-03-25T06:31:49.379Z","repository":{"id":816795,"uuid":"527464","full_name":"troessner/transitions","owner":"troessner","description":"State machine extracted from ActiveModel","archived":false,"fork":false,"pushed_at":"2022-03-31T10:12:04.000Z","size":306,"stargazers_count":536,"open_issues_count":6,"forks_count":91,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-03-19T21:50:07.546Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/troessner.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-02-20T14:30:30.000Z","updated_at":"2025-02-19T21:25:27.000Z","dependencies_parsed_at":"2022-08-08T13:00:16.034Z","dependency_job_id":null,"html_url":"https://github.com/troessner/transitions","commit_stats":null,"previous_names":[],"tags_count":40,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/troessner%2Ftransitions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/troessner%2Ftransitions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/troessner%2Ftransitions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/troessner%2Ftransitions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/troessner","download_url":"https://codeload.github.com/troessner/transitions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245414127,"owners_count":20611357,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T13:00:51.019Z","updated_at":"2025-03-25T06:31:49.045Z","avatar_url":"https://github.com/troessner.png","language":"Ruby","readme":"### Overview \n[![Build status](https://secure.travis-ci.org/troessner/transitions.png?branch=master)](http://travis-ci.org/troessner/transitions) \n[![Gem Version](https://badge.fury.io/rb/transitions.png)](http://badge.fury.io/rb/transitions)\n[![Code Climate](https://codeclimate.com/github/troessner/transitions.png)](https://codeclimate.com/github/troessner/transitions)\n[![Dependency Status](https://gemnasium.com/troessner/transitions.png)](https://gemnasium.com/troessner/transitions)\n[![Inline docs](http://inch-ci.org/github/troessner/transitions.png)](http://inch-ci.org/github/troessner/transitions)\n\n\n### Synopsis\n\n`transitions` is a ruby state machine implementation.\n\n### Installation\n\n#### Rails\n\nThis goes into your Gemfile:\n```ruby\ngem \"transitions\", :require =\u003e [\"transitions\", \"active_model/transitions\"]\n```\n\n… and this into your ORM model:\n```ruby\ninclude ActiveModel::Transitions\n```\n\n#### Standalone\n```shell\ngem install transitions\n```\n\n… and this into your class:\n```ruby\ninclude Transitions\n```\n\n### Using transitions\n```ruby\nclass Product\n  include ActiveModel::Transitions\n\n  state_machine do\n    state :available # first one is initial state\n    state :out_of_stock, :exit =\u003e :exit_out_of_stock\n    state :discontinued, :enter =\u003e lambda { |product| product.cancel_orders }\n\n    event :discontinued do\n      transitions :to =\u003e :discontinued, :from =\u003e [:available, :out_of_stock], :on_transition =\u003e :do_discontinue\n    end\n    event :out_of_stock, :success =\u003e :reorder do\n      transitions :to =\u003e :out_of_stock, :from =\u003e [:available, :discontinued]\n    end\n    event :available do\n      transitions :to =\u003e :available, :from =\u003e [:out_of_stock], :guard =\u003e lambda { |product| product.in_stock \u003e 0 }\n    end\n  end\nend\n```\nIn this example we assume that you are in a rails project using Bundler, which\nwould automatically require `transitions`. If this is not the case then you\nhave to add\n```ruby\nrequire 'transitions'\n```\n\nwherever you load your dependencies in your application.\n\n**Known limitations:** \n\n*   You can only use one state machine per model. While in theory you can\n    define two or more, this won't work as you would expect. Not supporting\n    this was intentional, if you're interested in the rational look up version\n    0.1.0 in the CHANGELOG.\n\n*   Use symbols, not strings for declaring the state machine. Using strings is\n    **not** supported as is using whitespace in names (because `transitions`\n    possibly generates methods out of this).\n\n### Features\n\n#### Getting and setting the current state\n\nUse the (surprise ahead) `current_state` method - in case you didn't set a\nstate explicitly you'll get back the state that you defined as initial state.\n```ruby\n\u003e\u003e Product.new.current_state\n=\u003e :available\n```\n\nYou can also set a new state explicitly via `update_current_state(new_state,\npersist = true / false)` but you should never do this unless you really know\nwhat you're doing and why - rather use events / state transitions (see below).\n\nPredicate methods are also available using the name of the state.\n```ruby\n\u003e\u003e Product.new.available?\n=\u003e true\n```\n\n#### Events\n\nWhen you declare an event, say `discontinue`, three methods are declared for\nyou: `discontinue`, `discontinue!` and `can_discontinue?`. The first two\nevents will modify the `state` attribute on successful transition, but only\nthe bang(!)-version will call `save!`.  The `can_discontinue?` method will not\nmodify state but instead returns a boolean letting you know if a given\ntransition is possible.\n\nIn addition, a `can_transition?` method is added to the object that expects one or more event names as arguments. This semi-verbose method name is used to avoid collisions with [https://github.com/ryanb/cancan](the authorization gem CanCan).\n```ruby\n\u003e\u003e Product.new.can_transition? :out_of_stock\n=\u003e true\n```\n\nIf you need to get all available transitions for the current state you can simply call:\n```ruby\n\u003e\u003e Product.new.available_transitions\n=\u003e [:discontinued, :out_of_stock]\n```\n\n\n#### Callback overview\n\n`transitions` offers you the possibility to define a couple of callbacks during the different stages / places of transitioning from one state to another. So let's say you have an event `discontinue` which transitions the current state from `in_stock` to `sold_out`. The callback sequence would look like this:\n\n\n      | discontinue event |\n            |\n            |\n            |\n      | current_state `in_stock` | ----\u003e executes `exit` callback\n            |\n            |\n            |\n      | current_state `in_stock` | ----\u003e executes `on_transition` callback if and only the `guard` check was successfull. If not successfull, the chain aborts here and the `event_failed` callback is executed\n            |\n            |\n            |\n      | current_state `in_stock` | ----\u003e executes `enter` callback for new state `sold_out`\n            |\n            |\n            |\n      | current_state `in_stock` | ----\u003e executes `event_fired` callback\n            |\n            |\n            |\n      | current_state `in_stock` | ----\u003e move state from `in_stock` to `sold_out`\n            |\n            |\n            |\n      | current_state `sold_out` | ----\u003e executes `success` callback of the `discontinue` event\n\n\n\nThis all looks very complicated (I know), but don't worry, in 99% of all cases you don't have to care about the details and the usage itself is straightforward as you can see in the examples below where each callback is explained a little more throrough.\n\n\n#### Callback # 1: State callbacks `enter` and `exit`\n\nIf you want to trigger a method call when the object enters or exits a state regardless\nof the transition that made that happen, use `enter` and `exit`.\n\n`exit` will be called before the transition out of the state is executed. If you want the method\nto only be called if the transition is successful, then use another approach.\n\n`enter` will be called after the transition has been made but before the object is persisted. If you want\nthe method to only be called after a successful transition to a new state including persistence,\nuse the `success` argument to an event instead.\n\nAn example:\n\n```ruby\nclass Motor \u003c ActiveRecord::Base\n  include ActiveModel::Transitions\n\n  state_machine do\n    state :off, enter: :turn_power_off\n    state :on,  exit: :prepare_shutdown\n  end\nend\n```\n\n#### Callback # 2: Transition callback `on_transition`\n\n\nEach event definition takes an optional `on_transition` argument, which allows\nyou to execute code on transition. This callback is executed after the `exit` callback of the former state (if it has been defined) but before the `enter` callback of the new state and only if the `guard` check succeeds. There is no check if the callback itself succeeds (meaning that `transitions` does not evaluate its return value somewhere). However, you can easily add some properly abstracted error handling yourself by raising an exception in this callback and then handling this exception in the (also defined by you) `event_failed` callback (see below and / or the wonderful ascii diagram above).\n\nYou can pass in a Symbol, a String, a Proc or an Array containing method names\nas Symbol or String like this:\n```ruby\nevent :discontinue do\n  transitions :to =\u003e :discontinued, :from =\u003e [:available, :out_of_stock], :on_transition =\u003e [:do_discontinue, :notify_clerk]\nend\n```\n\nAny arguments passed to the event method will be passed on to the `on_transition` callback.\n\n\n#### Callback #3 : Event callback `success`\n\nIn case you need to trigger a method call after a successful transition you\ncan use `success`. This will be called after the `save!` is complete (if you\nuse the `state_name!` method) and should be used for any methods that require\nthat the object be persisted.\n```ruby\nevent :discontinue, :success =\u003e :notify_admin do\n  transitions :to =\u003e :discontinued, :from =\u003e [:available, :out_of_stock]\nend\n```\n\nIn addition to just specify the method name on the record as a symbol you can\npass a lambda to perfom some more complex success callbacks:\n```ruby\nevent :discontinue, :success =\u003e lambda { |order| AdminNotifier.notify_about_discontinued_order(order) } do\n  transitions :to =\u003e :discontinued, :from =\u003e [:available, :out_of_stock]\nend\n```\n\nIf you need it, you can even call multiple methods or lambdas just passing an\narray:\n```ruby\nevent :discontinue, :success =\u003e [:notify_admin, lambda { |order| AdminNotifier.notify_about_discontinued_order(order) }] do\n  transitions :to =\u003e :discontinued, :from =\u003e [:available, :out_of_stock]\nend\n```\n\n#### Callback caveats\n\nSince callbacks will not be called by you but by `transitions` the scope is different when they are called and you'll run into problems if you use classes / modules in those callbacks that have the same names like `transitions` ones, e.g. \"Event\":\n\n```Ruby\ndef event_fired(current_state, new_state, event)\n  Event.create!\nend\n```\n\nThis will crash because `transitions` uses an Event class as well, and, since the scope has changed when `transitions` calls this method, `transitions` will use it's own Event class here, not yours.\nIn this case you can try to prefix your models with the \"::\" operator and see if that solves your problems. See https://github.com/troessner/transitions/issues/123 for details.\n\n#### Automatic scope generation\n\n`transitions` will automatically generate scopes for you if you are using\nActiveRecord and tell it to do so via the `auto_scopes` option:\n\nGiven a model like this:\n```ruby\nclass Order \u003c ActiveRecord::Base\n  include ActiveModel::Transitions\n  state_machine :auto_scopes =\u003e true do\n    state :pick_line_items\n    state :picking_line_items\n    event :move_cart do\n      transitions to: :pick_line_items, from: :picking_line_items\n    end\n  end\nend\n```\n\nyou can use this feature a la:\n```ruby\n\u003e\u003e Order.pick_line_items\n=\u003e []\n\u003e\u003e Order.create!\n=\u003e #\u003cOrder id: 3, state: \"pick_line_items\", description: nil, created_at: \"2011-08-23 15:48:46\", updated_at: \"2011-08-23 15:48:46\"\u003e\n\u003e\u003e Order.pick_line_items\n=\u003e [#\u003cOrder id: 3, state: \"pick_line_items\", description: nil, created_at: \"2011-08-23 15:48:46\", updated_at: \"2011-08-23 15:48:46\"\u003e]\n```\n\n#### Using `guard`\n\nEach event definition takes an optional `guard` argument, which acts as a\npredicate for the transition.\n\nYou can pass in Symbols, Strings, or Procs like this:\n```ruby\nevent :discontinue do\n  transitions :to =\u003e :discontinued, :from =\u003e [:available, :out_of_stock], :guard =\u003e :can_discontinue\nend\n```\n\nor\n```ruby\nevent :discontinue do\n  transitions :to =\u003e :discontinued, :from =\u003e [:available, :out_of_stock], :guard =\u003e [:can_discontinue, :super_sure?]\nend\n```\n\nAny arguments passed to the event method will be passed on to the `guard`\npredicate.\n\nNote that guards will **not** raise on failure on their own. This means that if you want to\ntreat the failure of a guard exceptional you'll need to raise an exception yourself explicitly\nin that guard (see [here](https://github.com/troessner/transitions/issues/149) for the\ncorresponding discussion).\n\n\n#### Timestamps\n\nIf you'd like to note the time of a state change, Transitions comes with\ntimestamps free! To activate them, simply pass the `timestamp` option to the\nevent definition with a value of either true or the name of the timestamp\ncolumn. *NOTE - This should be either true, a String, a Symbol, or an Array of\nthese*\n```ruby\n# This will look for an attribute called exploded_at or exploded_on (in that order)\n# If present, it will be updated\nevent :explode, :timestamp =\u003e true do\n  transitions :from =\u003e :complete, :to =\u003e :exploded\nend\n\n# This will look for an attribute named repaired_on to update upon save\nevent :rebuild, :timestamp =\u003e :repaired_on do\n  transitions :from =\u003e :exploded, :to =\u003e :rebuilt\nend\n```\n\n#### Using `event_fired` and `event_failed`\n\nIn case you define `event_fired` and / or `event_failed`, `transitions` will\nuse those callbacks correspondingly.\n\nYou can use those callbacks like this:\n```ruby\ndef event_fired(current_state, new_state, event)\n  MyLogger.info \"Event fired #{event.inspect}\"\nend\n\ndef event_failed(event)\n  MyLogger.warn \"Event failed #{event.inspect}\"\nend\n```\n\n#### Listing all the available states and events\n\nYou can easily get a listing of all available states:\n```ruby\nOrder.available_states # Uses the \u003ctt\u003edefault\u003c/tt\u003e state machine\n# =\u003e [:pick_line_items, :picking_line_items]\n```\n\nSame goes for the available events:\n```ruby\nOrder.available_events\n# =\u003e [:move_cart]\n```\n\n#### Explicitly setting the initial state with the `initial` option\n```ruby\nstate_machine :initial =\u003e :closed do\n  state :open\n  state :closed\nend\n```\n\nThe explicitly specified state **must** be one of the states listed in the state definition below, otherwise `transitions` will raise a rather unhelpful exception like \"NoMethodError: undefined method `call_action' for nil:NilClass\" (there's a ticket to fix this already: https://github.com/troessner/transitions/issues/112)\n\n\n### Configuring a different column name with ActiveRecord\n\nTo use a different column than `state` to track it's value simply do this:\n```ruby\nclass Product \u003c ActiveRecord::Base\n  include Transitions\n\n  state_machine :attribute_name =\u003e :different_column do\n\n    ...\n\n  end\nend\n```\n\n### Ruby Compatibility\n\nSupported versions:\n\n* 2.1\n* 2.2\n\nSupported implementations:\n\n* MRI\n* Rubinius\n* Jruby\n\n### Supported Rails versions:\n\n* 3\n* 4\n\n### Known bugs / limitations\n\n*   Right now it seems like `transitions` does not play well with `mongoid`. A\n    possible fix had to be rolled back due to other side effects:\n    https://github.com/troessner/transitions/issues/76. Since I know virtually\n    zero about mongoid, a pull request would be highly appreciated.\n*   Multiple state machines are not and will not be supported. For the rationale\n    behind this see the Changelog.\n\n\n### Associated projects\n\n* [GraphvizTransitions](https://github.com/itkin/graphviz_transitions) - Adds support for generating graphs based on states, events and transitions\n\n### Documentation, Guides \u0026 Examples\n\n*   [Online API Documentation](http://rdoc.info/github/troessner/transitions/master/Transitions)\n*   [Railscasts #392: A Tour of State Machines](http://railscasts.com/episodes/392-a-tour-of-state-machines) (requires Pro subscription)\n\n\n### Copyright\n\nCopyright (c) 2010 Jakub Kuźma, Timo Rößner. See LICENSE for details.\n","funding_links":[],"categories":["Rails Plugins","Libraries","State Machines","Ruby"],"sub_categories":["State Machines","Ruby"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftroessner%2Ftransitions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftroessner%2Ftransitions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftroessner%2Ftransitions/lists"}