{"id":13484590,"url":"https://github.com/piotrmurach/finite_machine","last_synced_at":"2025-12-17T06:58:50.701Z","repository":{"id":13974155,"uuid":"16674862","full_name":"piotrmurach/finite_machine","owner":"piotrmurach","description":"A minimal finite state machine with a straightforward syntax.","archived":false,"fork":false,"pushed_at":"2024-03-16T22:51:36.000Z","size":4296,"stargazers_count":805,"open_issues_count":3,"forks_count":37,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-12-09T12:15:24.104Z","etag":null,"topics":["conditional-transitions","finite-machine","ruby-gem","state-machine","state-transitions"],"latest_commit_sha":null,"homepage":"https://piotrmurach.github.io/finite_machine/","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/piotrmurach.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"piotrmurach"}},"created_at":"2014-02-09T20:27:52.000Z","updated_at":"2025-12-03T07:10:55.000Z","dependencies_parsed_at":"2024-01-12T18:05:08.096Z","dependency_job_id":"dba27546-2882-4f30-bbb1-f915647886f7","html_url":"https://github.com/piotrmurach/finite_machine","commit_stats":{"total_commits":802,"total_committers":17,"mean_commits":47.1764705882353,"dds":"0.029925187032418976","last_synced_commit":"8b1e421e02948f60d10e850c55c585e3cd3ce91a"},"previous_names":["peter-murach/finite_machine"],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/piotrmurach/finite_machine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ffinite_machine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ffinite_machine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ffinite_machine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ffinite_machine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piotrmurach","download_url":"https://codeload.github.com/piotrmurach/finite_machine/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Ffinite_machine/sbom","scorecard":{"id":734807,"data":{"date":"2025-08-11","repo":{"name":"github.com/piotrmurach/finite_machine","commit":"34229a2eb258afbbbb3af2527572b453d9aad39c"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.8,"checks":[{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 1/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:52: update your workflow using https://app.stepsecurity.io/secureworkflow/piotrmurach/finite_machine/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:54: update your workflow using https://app.stepsecurity.io/secureworkflow/piotrmurach/finite_machine/ci.yml/master?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 1 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T15:28:14.490Z","repository_id":13974155,"created_at":"2025-08-22T15:28:14.490Z","updated_at":"2025-08-22T15:28:14.490Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27665023,"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","status":"online","status_checked_at":"2025-12-11T02:00:11.302Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["conditional-transitions","finite-machine","ruby-gem","state-machine","state-transitions"],"created_at":"2024-07-31T17:01:26.744Z","updated_at":"2025-12-17T06:58:50.683Z","avatar_url":"https://github.com/piotrmurach.png","language":"Ruby","funding_links":["https://github.com/sponsors/piotrmurach"],"categories":["State Machines","Ruby","Libraries"],"sub_categories":["Ruby"],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://piotrmurach.github.io/finite_machine/\"\u003e\u003cimg width=\"236\" src=\"https://github.com/piotrmurach/finite_machine/raw/master/assets/finite_machine_logo.png\" alt=\"finite machine logo\" /\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n# FiniteMachine\n\n[![Gem Version](https://badge.fury.io/rb/finite_machine.svg)][gem]\n[![Actions CI](https://github.com/piotrmurach/finite_machine/workflows/CI/badge.svg?branch=master)][gh_actions_ci]\n[![Build status](https://ci.appveyor.com/api/projects/status/8ho4ijacpr7b4f4t?svg=true)][appveyor]\n[![Code Climate](https://codeclimate.com/github/piotrmurach/finite_machine/badges/gpa.svg)][codeclimate]\n[![Coverage Status](https://coveralls.io/repos/github/piotrmurach/finite_machine/badge.svg?branch=master)][coverage]\n[![Inline docs](http://inch-ci.org/github/piotrmurach/finite_machine.svg)][inchpages]\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]\n\n[gem]: http://badge.fury.io/rb/finite_machine\n[gh_actions_ci]: https://github.com/piotrmurach/finite_machine/actions?query=workflow%3ACI\n[appveyor]: https://ci.appveyor.com/project/piotrmurach/finite-machine\n[codeclimate]: https://codeclimate.com/github/piotrmurach/finite_machine\n[coverage]: https://coveralls.io/github/piotrmurach/finite_machine?branch=master\n[inchpages]: http://inch-ci.org/github/piotrmurach/finite_machine\n[gitter]: https://gitter.im/piotrmurach/finite_machine\n\n\u003e A minimal finite state machine with a straightforward and intuitive syntax. You can quickly model states and transitions and register callbacks to watch for triggered transitions.\n\n## Features\n\n* plain object state machine\n* easy [custom object integration](#29-target)\n* natural DSL for declaring events, callbacks and exception handlers\n* [callbacks](#4-callbacks) for state and event changes\n* ability to check [reachable](#28-can-and-cannot) state(s)\n* ability to check for [terminal](#25-terminal) state(s)\n* transition [guard conditions](#38-conditional-transitions)\n* dynamic [choice pseudostates](#39-choice-pseudostates)\n* thread safe\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem \"finite_machine\"\n\nThen execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install finite_machine\n\n## Contents\n\n* [1. Usage](#1-usage)\n* [2. API](#2-api)\n    * [2.1 new](#21-new)\n    * [2.2 define](#22-define)\n    * [2.3 current](#23-current)\n    * [2.4 initial](#24-initial)\n    * [2.5 terminal](#25-terminal)\n    * [2.6 is?](#26-is)\n    * [2.7 trigger](#27-trigger)\n      * [2.7.1 :auto_methods](#271-auto_methods)\n    * [2.8 can? and cannot?](#28-can-and-cannot)\n    * [2.9 target](#29-target)\n      * [2.9.1 :alias_target](#27-alias_target)\n    * [2.10 restore!](#210-restore)\n    * [2.11 states](#211-states)\n    * [2.12 events](#212-events)\n* [3. States and Transitions](#3-states-and-transitions)\n    * [3.1 Triggering transitions](#31-triggering-transitions)\n    * [3.2 Dangerous transitions](#32-dangerous-transitions)\n    * [3.3 Multiple from states](#33-multiple-from-states)\n    * [3.4 any_state transitions](#34-any_state-transitions)\n    * [3.5 Collapsing transitions](#35-collapsing-transitions)\n    * [3.6 Silent transitions](#36-silent-transitions)\n    * [3.7 Logging transitions](#37-logging-transitions)\n    * [3.8 Conditional transitions](#38-conditional-transitions)\n      * [3.8.1 Using a Proc](#381-using-a-proc)\n      * [3.8.2 Using a Symbol](#382-using-a-symbol)\n      * [3.8.3 Using a String](#383-using-a-string)\n      * [3.8.4 Combining transition conditions](#384-combining-transition-conditions)\n    * [3.9 Choice pseudostates](#39-choice-pseudostates)\n      * [3.9.1 Dynamic choice conditions](#391-dynamic-choice-conditions)\n      * [3.9.2 Multiple from states](#392-multiple-from-states)\n* [4. Callbacks](#4-callbacks)\n    * [4.1 on_(enter|transition|exit)](#41-on_entertransitionexit)\n    * [4.2 on_(before|after)](#42-on_beforeafter)\n    * [4.3 once_on](#43-once_on)\n    * [4.4 Execution sequence](#44-execution-sequence)\n    * [4.5 Callback parameters](#45-callback-parameters)\n    * [4.6 Duplicate callbacks](#46-duplicate-callbacks)\n    * [4.7 Fluid callbacks](#47-fluid-callbacks)\n    * [4.8 Methods inside callbacks](#48-methods-inside-callbacks)\n    * [4.9 Cancelling callbacks](#49-cancelling-callbacks)\n    * [4.10 Asynchronous callbacks](#410-asynchronous-callbacks)\n    * [4.11 Instance callbacks](#411-instance-callbacks)\n* [5. Error Handling](#5-error-handling)\n    * [5.1 Using target](#51-using-target)\n* [6. Stand-alone](#6-stand-alone)\n    * [6.1 Creating a Definition](#61-creating-a-definition)\n    * [6.2 Targeting definition](#62-targeting-definition)\n    * [6.3 Definition inheritance](#63-definition-inheritance)\n* [7. Integration](#7-integration)\n    * [7.1 Plain Ruby Objects](#71-plain-ruby-objects)\n    * [7.2 ActiveRecord](#72-activerecord)\n    * [7.3 Transactions](#73-transactions)\n* [8. Tips](#8-tips)\n\n## 1. Usage\n\nHere is a very simple example of a state machine:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :red\n\n  event :ready, :red    =\u003e :yellow\n  event :go,    :yellow =\u003e :green\n  event :stop,  :green  =\u003e :red\n\n  on_before(:ready) { |event| ... }\n  on_exit(:yellow)  { |event| ... }\n  on_enter(:green)  { |event| ... }\n  on_after(:stop)   { |event| ... }\nend\n```\n\nBy calling the `new` method on **FiniteMachine**, you gain access to a powerful DSL for expressing transitions and registering callbacks.\n\nHaving declared the states and transitions, you can check current state:\n\n```ruby\nfm.current # =\u003e :red\n````\n\nAnd then trigger transitions using the `trigger`:\n\n```ruby\nfm.trigger(:ready)\n```\n\nOr you can use direct method calls:\n\n```ruby\nfm.ready\n```\n\nRead [States and Transitions](#3-states-and-transitions) and [Callbacks](#4-callbacks) sections for more details.\n\nAlternatively, you can construct the state machine like a regular object using the same DSL methods. Similar machine could be reimplemented as follows:\n\n```ruby\nfm = FiniteMachine.new(initial: :red)\nfm.event(:ready, :red    =\u003e :yellow)\nfm.event(:go,    :yellow =\u003e :green)\nfm.event(:stop,  :green  =\u003e :red)\nfm.on_before(:ready) { |event| ... }\nfm.on_exit(:yellow)  { |event| ... }\nfm.on_enter(:green)  { |event| ... }\nfm.on_after(:stop)   { |event| ... }\n```\n\n## 2. API\n\n### 2.1 new\n\nIn most cases you will want to create an instance of **FiniteMachine** class using the `new` method. At the bare minimum you need specify the transition events inside a block using the `event` helper:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green\n\n  event :slow,  :green  =\u003e :yellow\n  event :stop,  :yellow =\u003e :red\n  event :ready, :red    =\u003e :yellow\n  event :go,    :yellow =\u003e :green\nend\n```\n\nAlternatively, you can skip block definition and instead call DSL methods directly on the state machine instance:\n\n```ruby\nfm = FiniteMachine.new\nfm.initial(:green)\nfm.event(:slow, :green  =\u003e :yellow)\nfm.event(:stop, :yellow =\u003e :red)\nfm.event(:ready,:red    =\u003e :yellow)\nfm.event(:go,   :yellow =\u003e :green)\n```\n\nAs a guiding rule, any method exposed via DSL is available as a regular method call on the state machine instance.\n\n### 2.2 define\n\nTo create a reusable definition for a state machine use `define` method. By calling `define` you're creating an anonymous class that can act as a factory for state machines. For example, below we create a `TrafficLights` class that contains our state machine definition:\n\n```ruby\nTrafficLights = FiniteMachine.define do\n  initial :green\n\n  event :slow,  :green  =\u003e :yellow\n  event :stop,  :yellow =\u003e :red\n  event :ready, :red    =\u003e :yellow\n  event :go,    :yellow =\u003e :green\nend\n```\n\nThen we can create however many instance of above class:\n\n```ruby\nlights_fm_a = TrafficLights.new\nlights_fm_b = TrafficLights.new\n```\n\nEach instance will start in consistent state:\n\n```ruby\nlights_fm_a.current # =\u003e :green\nlights_fm_b.current # =\u003e :green\n```\n\nWe can then trigger event for one instance and not the other:\n\n```ruby\nlights_fm_a.slow\nlights_fm_a.current # =\u003e :yellow\nlights_fm_b.current # =\u003e :green\n```\n\n### 2.3 current\n\nThe **FiniteMachine** allows you to query the current state by calling the `current` method.\n\n```ruby\nfm.current  # =\u003e :red\n```\n\n### 2.4 initial\n\nThere are number of ways to provide the initial state in  **FiniteMachine** depending on your requirements.\n\nBy default the **FiniteMachine** will be in the `:none` state and you will need to provide an explicit event to transition out of this state.\n\n```ruby\nfm = FiniteMachine.new do\n  event :init,  :none   =\u003e :green\n  event :slow,  :green  =\u003e :yellow\n  event :stop,  :yellow =\u003e :red\nend\n\nfm.current # =\u003e :none\nfm.init    # =\u003e true\nfm.current # =\u003e :green\n```\n\nIf you specify initial state using the `initial` helper, then the state machine will be created already in that state and an implicit `init` event will be created for you and automatically triggered upon the state machine initialization.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green   # fires init event that transitions from :none to :green state\n\n  event :slow,  :green  =\u003e :yellow\n  event :stop,  :yellow =\u003e :red\nend\n\nfm.current # =\u003e :green\n```\n\nOr by passing named argument `:initial` like so:\n\n```ruby\nfm = FiniteMachine.new(initial: :green) do\n  ...\nend\n```\n\nIf you want to defer setting the initial state, pass the `:defer` option to the `initial` helper. By default **FiniteMachine** will create `init` event that will allow to transition from `:none` state to the new state.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green, defer: true # Defer calling :init event\n\n  event :slow,  :green  =\u003e :yellow\n  event :stop,  :yellow =\u003e :red\nend\nfm.current # =\u003e :none\nfm.init    # execute initial transition\nfm.current # =\u003e :green\n```\n\nIf your target object already has `init` method or one of the events names redefines `init`, you can use different name by passing `:event` option to `initial` helper.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green, event: :start, defer: true # Rename event from :init to :start\n\n  event :slow,  :green  =\u003e :yellow\n  event :stop,  :yellow =\u003e :red\nend\n\nfm.current # =\u003e :none\nfm.start   # =\u003e call the renamed event\nfm.current # =\u003e :green\n```\n\nBy default the `initial` does not trigger any callbacks. If you need to fire callbacks and any event associated actions on initial transition, pass the `silent` option set to `false` like so:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green, silent: false  # callbacks are triggered\n\n  event :slow,  :green  =\u003e :yellow\n  event :stop,  :yellow =\u003e :red\nend\n```\n\n### 2.5 terminal\n\nTo specify a final state **FiniteMachine** uses the `terminal` method.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green\n\n  terminal :red\n\n  event :slow, :green  =\u003e :yellow\n  event :stop, :yellow =\u003e :red\n  event :go,   :red    =\u003e :green\nend\n```\n\nWhen the terminal state has been specified, you can use `terminated?` method on the state machine instance to verify if the terminal state has been reached or not.\n\n```ruby\nfm.terminated?  # =\u003e false\nfm.slow         # =\u003e true\nfm.terminated?  # =\u003e false\nfm.stop         # =\u003e true\nfm.terminated?  # =\u003e true\n```\n\nThe `terminal` can accept more than one state.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :open\n\n  terminal :close, :canceled\n\n  event :resolve, :open =\u003e :close\n  event :decline, :open =\u003e :canceled\nend\n```\n\nAnd the terminal state can be checked using `terminated?`:\n\n```ruby\nfm.decline\nfm.terminated? # =\u003e true\n```\n\n### 2.6 is?\n\nTo verify whether or not a state machine is in a given state, **FiniteMachine** uses `is?` method. It returns `true` if the machine is found to be in the given state, or `false` otherwise.\n\n```ruby\nfm.is?(:red)    # =\u003e true\nfm.is?(:yellow) # =\u003e false\n```\n\nMoreover, you can use helper methods to check for current state using the state name itself like so\n\n```ruby\nfm.red?     # =\u003e true\nfm.yellow?  # =\u003e false\n```\n\n### 2.7 trigger\n\nTransition events can be fired by calling the `trigger` method with the event name and remaining arguments as data. The return value is either `true` or `false` depending whether the transition succeeded or not:\n\n```ruby\nfm.trigger(:ready) # =\u003e true\nfm.trigger(:ready, \"one\", \"two\", \"three\") # =\u003e true\n```\n\nBy default, the **FiniteMachine** automatically converts all the transition event names into methods:\n\n```ruby\nfm.ready # =\u003e true\nfm.ready(\"one\", \"two\", \"three\") # =\u003e true\n```\n\nPlease see [States and Transitions](#3-states-and-transitions) for in-depth treatment of firing transitions.\n\n\n#### 2.7.1 `:auto_methods`\n\nBy default, all event names will be converted by **FiniteMachine** into method names. This also means that you won't be able to use event names such as `:fail` or `:trigger` as these are already defined on the machine instance. In situations when you wish to use any event name for your event names use `:auto_methods` keyword to disable automatic methods generation. For example, to define `:fail` event:\n\n```ruby\nfm = FiniteMachine.new(auto_methods: false) do\n  initial :green\n\n  event :fail, :green =\u003e :red\nend\n```\n\nAnd then you can use `trigger` to fire the event:\n\n```ruby\nfm.trigger(:fail)\nfm.current # =\u003e :red\n```\n\n### 2.8 `can?` and `cannot?`\n\nTo verify whether or not an event can be fired, **FiniteMachine** provides `can?` or `cannot?` methods. `can?` checks if **FiniteMachine** can fire a given event, returning `true`, otherwise, it will return `false`. The `cannot?` is simply the inverse of `can?`.\n\n```ruby\nfm.can?(:ready)    # =\u003e true\nfm.can?(:go)       # =\u003e false\nfm.cannot?(:ready) # =\u003e false\nfm.cannot?(:go)    # =\u003e true\n```\n\nThe `can?` and `cannot?` helper methods take into account the `:if` and `:unless` conditions applied to events. The set of values that `:if` or `:unless` condition takes as block parameter can be passed in directly via `can?` and `cannot?` methods' arguments, after the name of the event. For instance,\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green\n\n  event :slow,  :green  =\u003e :yellow\n  event :stop,  :yellow =\u003e :red, if: -\u003e(_, param) { :breaks == param }\nend\n\nfm.can?(:slow) # =\u003e true\nfm.can?(:stop) # =\u003e false\n\nfm.slow                    # =\u003e true\nfm.can?(:stop, :breaks)    # =\u003e true\nfm.can?(:stop, :no_breaks) # =\u003e false\n```\n\n### 2.9 target\n\nIf you need to execute some external code in the context of the current state machine, pass that object as a first argument to `new` method.\n\nAssuming we have a simple `Engine` class that holds an internal state whether the car's engine is on or off:\n\n```ruby\nclass Engine\n  def initialize\n    @engine = false\n  end\n\n  def turn_on\n    @engine = true\n  end\n\n  def turn_off\n    @engine = false\n  end\n\n  def engine_on?\n    @engine\n  end\nend\n```\n\nAnd given an instance of `Engine` class:\n\n```ruby\nengine = Engine.new\n```\n\nYou can provide a context to a state machine by passing it as a first argument to a `new` call. You can then reference this context inside the callbacks by calling the `target` helper:\n\n```ruby\nfm = FiniteMachine.new(engine) do\n  initial :neutral\n\n  event :start, :neutral =\u003e :one, unless: \"engine_on?\"\n  event :stop,  :one =\u003e :neutral\n\n  on_before_start { |event| target.turn_on }\n  on_after_stop { |event| target.turn_off }\nend\n```\n\nFor more complex example see [Integration](#7-integration) section.\n\n#### 2.9.1 `:alias_target`\n\nIf you wish to better express the intention behind the context object, in particular when calling actions in callbacks, you can use the `:alias_target` option:\n\n```ruby\nengine = Engine.new\n\nfm = FiniteMachine.new(engine, alias_target: :engine) do\n  initial :neutral\n\n  event :start, :neutral =\u003e :one, unless: \"engine_on?\"\n  event :stop, :none =\u003e :neutral, if: \"engine_on?\"\n\n  on_before_start { |event| engine.turn_on }\n  on_after_stop { |event| engine.turn_off }\nend\n```\n\nAlternatively, you can use the `alias_target` helper method:\n\n```ruby\nengine = Engine.new\n\nCar = FiniteMachine.define do\n  alias_target :engine\n\n  initial :neutral\n\n  event :start, :neutral =\u003e :one, if: \"engine_on?\"\n  event :stop, :none =\u003e :neutral, if: \"engine_on?\"\n\n  on_before_start { |event| engine.turn_on }\n  on_after_stop { |event| engine.turn_off }\nend\n```\n\nThen to link `Car` definition with `Engine` instance, pass the `Engine` instance as a first argument:\n\n```ruby\ncar = Car.new(engine)\n```\n\nTriggering `start` event will change `Engine` instance state from `false` to `true`:\n\n```ruby\nengine.engine_on? # =\u003e false\ncar.start\ncar.current       # =\u003e :one\nengine.engine_on? # =\u003e true\n```\n\n### 2.10 restore!\n\nIn order to set the machine to a given state and thus skip triggering callbacks use the `restore!` method:\n\n```ruby\nfm.restore!(:neutral)\n```\n\nThis method may be suitable when used testing your state machine or in restoring the state from datastore.\n\n### 2.11 states\n\nYou can use the `states` method to return an array of all the states for a given state machine.\n\n```ruby\nfm.states # =\u003e [:none, :green, :yellow, :red]\n```\n\n### 2.12 events\n\nTo find out all the event names supported by the state machine issue `events` method:\n\n```ruby\nfm.events # =\u003e [:init, :ready, :go, :stop]\n```\n\n## 3. States and Transitions\n\nThe **FiniteMachine** DSL exposes the `event` helper to define possible state transitions.\n\nThe `event` helper accepts as a first argument the transition's name which will later be used to create\nmethod on the **FiniteMachine** instance. As a second argument the `event` accepts an arbitrary number of states either\nin the form of `:from` and `:to` hash keys or by using the state names themselves as key value pairs.\n\n```ruby\nevent :start, from: :neutral, to: :first\n# or\nevent :start, :neutral =\u003e :first\n```\n\nOnce specified, the **FiniteMachine** will create custom methods for transitioning between each state.\nThe following methods trigger transitions for the example state machine.\n\n* ready\n* go\n* stop\n\nYou can always opt out from automatic method generation by using [:auto_methods](#271-auto_methods) option.\n\n### 3.1 Triggering transitions\n\nIn order to transition to the next reachable state, simply call the event's name on the **FiniteMachine** instance. If the transition succeeds the `true` value is returned, otherwise `false`.\n\n```ruby\nfm.ready         # =\u003e true\nfm.current       # =\u003e :yellow\n```\n\nIf you prefer you can also use `trigger` method to fire any event by its name:\n\n```ruby\nfm.trigger(:ready)  # =\u003e true\n```\n\nFurthermore, you can pass additional parameters with the method call that will be available in the triggered callback as well as used by any present guarding conditions.\n\n```ruby\nfm.go(\"Piotr!\")  # =\u003e true\nfm.current       # =\u003e :green\n```\n\nBy default **FiniteMachine** will swallow all exceptions when and return `false` on failure. If you prefer to be notified when illegal transition occurs see [Dangerous transitions](#22-dangerous-transitions).\n\n### 3.2 Dangerous transitions\n\nWhen you declare event, for instance `ready`, the **FiniteMachine** will provide a dangerous version with a bang `ready!`. In the case when you attempt to perform illegal transition or **FiniteMachine** throws internal error, the state machine will propagate the errors. You can use handlers to decide how to handle errors on case by case basis see [6. Error Handling](#6-errors)\n\n```ruby\nfm.ready!  #  =\u003e raises FiniteMachine::InvalidStateError\n```\n\nIf you prefer you can also use `trigger!` method to fire event:\n\n```ruby\nfm.trigger!(:ready)\n```\n\n### 3.3 Multiple from states\n\nIf an event transitions from multiple states to the same state then all the states can be grouped into an array.\nAlternatively, you can create separate events under the same name for each transition that needs combining.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :neutral\n\n  event :start,  :neutral             =\u003e :one\n  event :shift,  :one                 =\u003e :two\n  event :shift,  :two                 =\u003e :three\n  event :shift,  :three               =\u003e :four\n  event :slow,   [:one, :two, :three] =\u003e :one\nend\n```\n\n### 3.4 `any_state` transitions\n\nThe **FiniteMachine** offers few ways to transition out of any state. This is particularly useful when the machine already defines many states.\n\nYou can use `any_state` as the name for a given state, for instance:\n\n```ruby\nevent :run, from: any_state, to: :green\n# or\nevent :run, any_state =\u003e :green\n```\n\nAlternatively, you can skip the `any_state` call and just specify `to` state:\n\n```ruby\nevent :run, to: :green\n```\n\nAll the above `run` event definitions will always transition the state machine into `:green` state.\n\n### 3.5 Collapsing transitions\n\nAnother way to specify state transitions under single event name is to group all your state transitions into a single hash like so:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :initial\n\n  event :bump, :initial =\u003e :low,\n               :low     =\u003e :medium,\n               :medium  =\u003e :high\nend\n```\n\nThe same can be more naturally rewritten also as:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :initial\n\n  event :bump, :initial =\u003e :low\n  event :bump, :low     =\u003e :medium\n  event :bump, :medium  =\u003e :high\nend\n```\n\n### 3.6 Silent transitions\n\nThe **FiniteMachine** allows to selectively silence events and thus prevent any callbacks from firing. Using the `silent` option passed to event definition like so:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :yellow\n\n  event :go    :yellow =\u003e :green, silent: true\n  event :stop, :green =\u003e :red\nend\n\nfm.go   # no callbacks\nfm.stop # callbacks are fired\n```\n\n### 3.7 Logging transitions\n\nTo help debug your state machine, **FiniteMachine** provides `:log_transitions` option.\n\n```ruby\nFiniteMachine.new(log_transitions: true) do\n  ...\nend\n```\n\n### 3.8 Conditional transitions\n\nEach event takes an optional `:if` and `:unless` options which act as a predicate for the transition. The `:if` and `:unless` can take a symbol, a string, a Proc or an array. Use `:if` option when you want to specify when the transition **should** happen. If you want to specify when the transition **should not** happen then use `:unless` option.\n\n#### 3.8.1 Using a Proc\n\nYou can associate the `:if` and `:unless` options with a Proc object that will get called right before transition happens. Proc object gives you ability to write inline condition instead of separate method.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green\n\n  event :slow, :green =\u003e :yellow, if: -\u003e { return false }\nend\n\nfm.slow    # doesn't transition to :yellow state\nfm.current # =\u003e :green\n```\n\nCondition by default receives the current context, which is the current state machine instance, followed by extra arguments.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :red\n\n  event :go, :red =\u003e :green,\n        if: -\u003e(context, a) { context.current == a }\nend\n\nfm.go(:yellow) # doesn't transition\nfm.go          # raises ArgumentError\n```\n\n**Note** If you specify condition with a given number of arguments then you need to call an event with the exact number of arguments, otherwise you will get `ArgumentError`. Thus in above scenario to prevent errors specify condition like so:\n\n```ruby\nif: -\u003e(context, *args) { ... }\n```\n\nProvided your **FiniteMachine** is associated with another object through `target` helper. Then the target object together with event arguments will be passed to the `:if` or `:unless` condition scope.\n\n```ruby\nclass Engine\n  def initialize\n    @engine = false\n  end\n\n  def turn_on\n    @engine = true\n  end\n\n  def turn_off\n    @engine = false\n  end\n\n  def engine_on?\n    @engine\n  end\nend\n\nengine = Engine.new\nengine.turn_on\n\ncar = FiniteMachine.new(engine) do\n  initial :neutral\n\n  event :start, :neutral =\u003e :one, if: -\u003e(target, state) do\n    state ? target.engine_on : target.engine_off\n  end\nend\n\nfm.start(false)\nfm.current        # =\u003e :neutral\nengine.engine_on? # =\u003e false\n\nfm.start(true)\nfm.current        # =\u003e :one\nengine.engine_on? # =\u003e true\n```\n\nWhen the one-liner conditions are not enough for your needs, you can perform conditional logic inside the callbacks. See [4.9 Cancelling callbacks](#49-cancelling-inside-callbacks)\n\n#### 3.8.2 Using a Symbol\n\nYou can also use a symbol corresponding to the name of a method that will get called right before transition happens.\n\n```ruby\nfm = FiniteMachine.new(engine) do\n  initial :neutral\n\n  event :start, :neutral =\u003e :one, if: :engine_on?\nend\n```\n\n#### 3.8.3 Using a String\n\nFinally, it's possible to use string that will be evaluated using `eval` and needs to contain valid Ruby code. It should only be used when the string represents a short condition.\n\n```ruby\nfm = FiniteMachine.new(engine) do\n  initial :neutral\n\n  event :start, :neutral =\u003e :one, if: \"engine_on?\"\nend\n```\n\n#### 3.8.4 Combining transition conditions\n\nWhen multiple conditions define whether or not a transition should happen, an Array can be used. Furthermore, you can apply both `:if` and `:unless` to the same transition.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green\n\n  event :slow, :green =\u003e :yellow,\n    if: [ -\u003e { return true }, -\u003e { return true} ],\n    unless: -\u003e { return true }\n  event :stop, :yellow =\u003e :red\nend\n```\n\nThe transition only runs when all the `:if` conditions and none of the `unless` conditions are evaluated to `true`.\n\n### 3.9 Choice pseudostates\n\nChoice pseudostate allows you to implement conditional branch. The conditions of an event's transitions are evaluated in order to select only one outgoing transition.\n\nYou can implement the conditional branch as ordinary events grouped under the same name and use familiar `:if/:unless` conditions:\n\n```ruby\nfm = FiniteMachine.define do\n  initial :green\n\n  event :next, :green =\u003e :yellow, if: -\u003e { false }\n  event :next, :green =\u003e :red,    if: -\u003e { true }\nend\n\nfm.current # =\u003e :green\nfm.next\nfm.current # =\u003e :red\n```\n\nThe same conditional logic can be implemented using much shorter and more descriptive style using `choice` method:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green\n\n  event :next, from: :green do\n    choice :yellow, if: -\u003e { false }\n    choice :red,    if: -\u003e { true }\n  end\nend\n\nfm.current # =\u003e :green\nfm.next\nfm.current # =\u003e :red\n```\n\n#### 3.9.1 Dynamic choice conditions\n\nJust as with event conditions you can make conditional logic dynamic and dependent on parameters passed in:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green\n\n  event :next, from: :green do\n    choice :yellow, if: -\u003e(context, a) { a \u003c 1 }\n    choice :red,    if: -\u003e(context, a) { a \u003e 1 }\n    default :red\n  end\nend\n\nfm.current # =\u003e :green\nfm.next(0)\nfm.current # =\u003e :yellow\n```\n\nIf more than one of the conditions evaluates to true, a first matching one is chosen. If none of the conditions evaluate to true, then the `default` state is matched. However if default state is not present and non of the conditions match, no transition is performed. To avoid such situation always specify `default` choice.\n\n#### 3.9.2 Multiple from states\n\nSimilarly to event definitions, you can specify the event to transition from a group of states:\n\n```ruby\nFiniteMachine.new do\n  initial :red\n\n  event :next, from: [:yellow, :red] do\n    choice :pink, if: -\u003e { false }\n    choice :green\n  end\nend\n```\n\nOr from any state using the `:any` state name like so:\n\n```ruby\nFiniteMachine.new do\n  initial :red\n\n  event :next, from: :any do\n    choice :pink, if: -\u003e { false }\n    choice :green\n  end\nend\n```\n\n## 4. Callbacks\n\nYou can register a callback to listen for state transitions and events triggered, and based on these perform custom actions. There are five callbacks available in **FiniteMachine**:\n\n* `on_before` - triggered before any transition\n* `on_exit` - triggered when leaving any state\n* `on_transition` - triggered during any transition\n* `on_enter` - triggered when entering any state\n* `on_after` - triggered after any transition\n\nUse the state or event name as a first parameter to the callback helper followed by block with event argument and a list arguments that you expect to receive like so:\n\n```ruby\non_enter(:green) { |event, a, b, c| ... }\n```\n\nWhen you subscribe to the `:green` state change, the callback will be called whenever someone triggers event that transitions in or out of that state. The same will happen on subscription to event `ready`, namely, the callback will be called each time the state transition method is triggered regardless of the states it transitions from or to.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :red\n\n  event :ready, :red    =\u003e :yellow\n  event :go,    :yellow =\u003e :green\n  event :stop,  :green  =\u003e :red\n\n  on_before :ready do |event, time1, time2, time3|\n    puts \"#{time1} #{time2} #{time3} Go!\" }\n  end\n  on_before :go do |event, name|\n    puts \"Going fast #{name}\"\n  end\n  on_before(:stop) { |event| ... }\nend\n\nfm.ready(1, 2, 3)\nfm.go(\"Piotr!\")\n```\n\n**Note** Regardless of how the state is entered or exited, all the associated callbacks will be executed. This provides means for guaranteed initialization and cleanup.\n\n### 4.1 on_(enter|transition|exit)\n\nThe `on_enter` callback is executed before given state change is fired. By passing state name you can narrow down the listener to only watch out for enter state changes. Otherwise, all enter state changes will be watched.\n\nThe `on_transition` callback is executed when given state change happens. By passing state name you can narrow down the listener to only watch out for transition state changes. Otherwise, all transition state changes will be watched.\n\nThe `on_exit` callback is executed after a given state change happens. By passing state name you can narrow down the listener to only watch out for exit state changes. Otherwise, all exit state changes will be watched.\n\n### 4.2 on_(before|after)\n\nThe `on_before` callback is executed before a given event happens. By default it will listen out for all events, you can also listen out for specific events by passing event's name.\n\nThis callback is executed after a given event happened. By default it will listen out for all events, you can also listen out for specific events by passing event's name.\n\n### 4.3 once_on\n\n**FiniteMachine** allows you to listen on initial state change or when the event is fired first time by using the following 5 types of callbacks:\n\n* `once_on_enter`\n* `once_on_transition`\n* `once_on_exit`\n* `once_before`\n* `once_after`\n\n### 4.4 Execution sequence\n\nAssuming we have the following event specified:\n\n```ruby\nevent :go, :red =\u003e :yellow\n```\n\nThen by calling `go` event the following callbacks sequence will be executed:\n\n* `on_before` - generic callback before `any` event\n* `on_before :go` - callback before the `go` event\n* `on_exit` - generic callback for exit from `any` state\n* `on_exit :red` - callback for the `:red` state exit\n* `on_transition` - callback for transition from `any` state to `any` state\n* `on_transition :yellow` - callback for the `:red` to `:yellow` transition\n* `on_enter` - generic callback for entry to `any` state\n* `on_enter :yellow` - callback for the `:yellow` state entry\n* `on_after` - generic callback after `any` event\n* `on_after :go` - callback after the `go` event\n\n### 4.5 Callback parameters\n\nAll callbacks as a first argument yielded to a block receive the `TransitionEvent` object with the following attributes:\n\n* `name` - the event name`\n* `from` - the state transitioning from`\n* `to` - the state transitioning to`\n\nfollowed by the rest of arguments that were passed to the event method.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :red\n\n  event :ready, :red =\u003e :yellow\n\n  on_before_ready do |event, time|\n    puts \"lights switching from #{event.from} to #{event.to} in #{time} seconds\"\n  end\nend\n\nfm.ready(3)\n#  =\u003e \"lights switching from red to yellow in 3 seconds\"\n```\n\n### 4.6 Duplicate callbacks\n\nYou can define any number of the same kind of callback. These callbacks will be executed in the order they are specified.\n\nGiven the following state machine instance:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green\n\n  event :slow, :green =\u003e :yellow\n\n  on_enter(:yellow) { puts \"this is run first\" }\n  on_enter(:yellow) { puts \"then this is run\" }\nend\n```\n\nTriggerring the `:slow` event results in:\n\n```ruby\nfm.slow\n# =\u003e \"this is run first\"\n# =\u003e \"then this is run\"\n```\n\n### 4.7 Fluid callbacks\n\nCallbacks can also be specified as full method calls separated with underscores:\n\n```ruby\nfm = FiniteMachine.define do\n  initial :red\n\n  event :ready, :red    =\u003e :yellow\n  event :go,    :yellow =\u003e :green\n  event :stop,  :green  =\u003e :red\n\n  on_before_ready { |event| ... }\n  on_before_go    { |event| ... }\n  on_before_stop  { |event| ... }\nend\n```\n\n### 4.8 Methods inside callbacks\n\nGiven a class `Car`:\n\n```ruby\nclass Car\n  attr_accessor :reverse_lights\n\n  def turn_reverse_lights_off\n    @reverse_lights = false\n  end\n\n  def turn_reverse_lights_on\n    @reverse_lights = true\n  end\nend\n```\n\nWe can easily manipulate state for an instance of a `Car` class:\n\n```ruby\ncar = Car.new\n```\n\nBy defining finite machine using the instance:\n\n```ruby\nfm = FiniteMachine.new(car) do\n  initial :neutral\n\n  event :forward, [:reverse, :neutral] =\u003e :one\n  event :back,    [:neutral, :one] =\u003e :reverse\n\n  on_enter_reverse { |event| target.turn_reverse_lights_on }\n  on_exit_reverse  { |event| target.turn_reverse_lights_off }\nend\n```\n\nNote that you can also fire events from callbacks.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :neutral\n\n  event :forward, [:reverse, :neutral] =\u003e :one\n  event :back,    [:neutral, :one] =\u003e :reverse\n\n  on_enter_reverse { |event| forward(\"Piotr!\") }\n  on_exit_reverse  { |event, name| puts \"Go #{name}\" }\nend\n```\n\nThen triggerring `:back` event gives:\n\n```ruby\nfm.back  # =\u003e Go Piotr!\n```\n\nFor more complex example see [Integration](#7-integration) section.\n\n### 4.9 Cancelling callbacks\n\nA simple way to prevent transitions is to use [3 Conditional transitions](#3-conditional-transitions).\n\nThere are times when you want to cancel transition in a callback. For example, you have logic which allows transition to happen only under certain complex conditions. Using `cancel_event` inside the `on_(enter|transition|exit)` or `on_(before|after)` callbacks will stop all the callbacks from firing and prevent current transition from happening.\n\nFor example, the following state machine cancels any event leaving `:red` state:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :red\n\n  event :ready, :red    =\u003e :yellow\n  event :go,    :yellow =\u003e :green\n  event :stop,  :green  =\u003e :red\n\n  on_exit :red do |event|\n    ...\n    cancel_event\n  end\nend\n```\n\nThen firing `:ready` event will not transition out of the current `:red` state:\n\n```ruby\nfm.current  # =\u003e :red\nfm.ready\nfm.current  # =\u003e :red\n```\n\n### 4.10 Asynchronous callbacks\n\nBy default all callbacks are run synchronously. In order to add a callback that runs asynchronously, you need to pass second `:async` argument like so:\n\n```ruby\non_enter(:green, :async) do |event| ... end\n# or\non_enter_green(:async) { |event| }\n```\n\nThis will ensure that when the callback is fired it will run in separate thread outside of the main execution thread.\n\n### 4.11 Instance callbacks\n\nWhen defining callbacks you are not limited to the **FiniteMachine** block definition. After creating an instance, you can register callbacks the same way as before by calling `on` and supplying the type of notification and state/event you are interested in.\n\nFor example, given the following state machine:\n\n```ruby\nfm = FiniteMachine.new do\n  initial :red\n\n  event :ready, :red    =\u003e :yellow\n  event :go,    :yellow =\u003e :green\n  event :stop,  :green  =\u003e :red\nend\n```\n\nWe can add callbacks as follows:\n\n```ruby\nfm.on_enter(:yellow) { |event| ... }\n# or\nfm.en_enter_yellow { |event| ... }\n```\n\n## 5. Error Handling\n\nBy default, the **FiniteMachine** will throw an exception whenever the machine is in invalid state or fails to transition.\n\n* `FiniteMachine::TransitionError`\n* `FiniteMachine::InvalidStateError`\n* `FiniteMachine::InvalidCallbackError`\n\nYou can attach specific error handler using the 'handle' with the name of the error as a first argument and a callback to be executed when the error happens. The `handle` receives a list of exception class or exception class names, and an option `:with` with a name of the method or a Proc object to be called to handle the error. As an alternative, you can pass a block.\n\n```ruby\nfm = FiniteMachine.new do\n  initial :green, event: :start\n\n  event :slow,  :green  =\u003e :yellow\n  event :stop,  :yellow =\u003e :red\n\n  handle FiniteMachine::InvalidStateError do |exception|\n    # run some custom logging\n    raise exception\n  end\n\n  handle FiniteMachine::TransitionError, with: -\u003e { |exception| ... }\nend\n```\n\n### 5.1 Using target\n\nYou can pass an external context as a first argument to the **FiniteMachine** initialization that will be available as context in the handler block or `:with` value. For example, the `log_error` method is made available when `:with` option key is used:\n\n```ruby\nclass Logger\n  def log_error(exception)\n    puts \"Exception : #{exception.message}\"\n  end\nend\n\nfm = FiniteMachine.new(logger) do\n  initial :green\n\n  event :slow, :green  =\u003e :yellow\n  event :stop, :yellow =\u003e :red\n\n  handle \"InvalidStateError\", with: :log_error\nend\n```\n\n## 6. Stand-alone\n\n**FiniteMachine** allows you to separate your state machine from the target class so that you can keep your concerns broken in small maintainable pieces.\n\n### 6.1 Creating a Definition\n\nYou can turn a class into a **FiniteMachine** by simply subclassing `FiniteMachine::Definition`. As a rule of thumb, every single public method of the **FiniteMachine** is available inside your class:\n\n```ruby\nclass Engine \u003c FiniteMachine::Definition\n  initial :neutral\n\n  event :forward, [:reverse, :neutral] =\u003e :one\n  event :shift, :one =\u003e :two\n  event :back,  [:neutral, :one] =\u003e :reverse\n\n  on_enter :reverse do |event|\n    target.turn_reverse_lights_on\n  end\n\n  on_exit :reverse do |event|\n    target.turn_reverse_lights_off\n  end\n\n  handle FiniteMachine::InvalidStateError do |exception|\n    ...\n  end\nend\n```\n\n### 6.2 Targeting definition\n\nThe next step is to instantiate your state machine and use a custom class instance to load specific context.\n\nFor example, having the following `Car` class:\n\n```ruby\nclass Car\n  def turn_reverse_lights_off\n    @reverse_lights = false\n  end\n\n  def turn_reverse_lights_on\n    @reverse_lights = true\n  end\n\n  def reverse_lights?\n    @reverse_lights ||= false\n  end\nend\n```\n\nThus, to associate `Engine` to `Car` do:\n\n```ruby\ncar = Car.new\nengine = Engine.new(car)\n\ncar.reverse_lignts?  # =\u003e false\nengine.back\ncar.reverse_lights?  # =\u003e true\n```\n\nAlternatively, create method inside the `Car` that will do the integration like so:\n\n```ruby\nclass Car\n  ... #  as above\n\n  def engine\n    @engine ||= Engine.new(self)\n  end\nend\n```\n\n### 6.3 Definition inheritance\n\nYou can create more specialised versions of a generic definition by using inheritance. Assuming a generic state machine definition:\n\n```ruby\nclass GenericStateMachine \u003c FiniteMachine::Definition\n  initial :red\n\n  event :start, :red =\u003e :green\n\n  on_enter { |event| ... }\nend\n```\n\nYou can easily create a more specific definition that adds new events and more specific callbacks to the mix.\n\n```ruby\nclass SpecificStateMachine \u003c GenericStateMachine\n  event :stop, :green =\u003e :yellow\n\n  on_enter(:yellow) { |event| ... }\nend\n```\n\nFinally to use the specific state machine definition do:\n\n```ruby\nspecific_fsm = SpecificStateMachine.new\n```\n\n## 7. Integration\n\nSince **FiniteMachine** is an object in its own right, it leaves integration with other systems up to you. In contrast to other Ruby libraries, it does not extend from models (i.e. ActiveRecord) to transform them into a state machine or require mixing into existing classes.\n\n### 7.1 Plain Ruby Objects\n\nIn order to use **FiniteMachine** with an object, you need to define a method that will construct the state machine. You can implement the state machine using the `new` DSL or create a separate object that can be instantiated. To complete integration you will need to specify `target` context to allow state machine to communicate with the other methods inside the class like so:\n\n```ruby\nclass Car\n  def turn_reverse_lights_off\n    @reverse_lights = false\n  end\n\n  def turn_reverse_lights_on\n    @reverse_lights = true\n  end\n\n  def reverse_lights_on?\n    @reverse_lights || false\n  end\n\n  def gears\n    @gears ||= FiniteMachine.new(self) do\n      initial :neutral\n\n      event :start, :neutral =\u003e :one\n      event :shift, :one =\u003e :two\n      event :shift, :two =\u003e :one\n      event :back,  [:neutral, :one] =\u003e :reverse\n\n      on_enter :reverse do |event|\n        target.turn_reverse_lights_on\n      end\n\n      on_exit :reverse do |event|\n        target.turn_reverse_lights_off\n      end\n\n      on_transition do |event|\n        puts \"shifted from #{event.from} to #{event.to}\"\n      end\n    end\n  end\nend\n```\n\nHaving written the class, you can use it as follows:\n\n```ruby\ncar = Car.new\ncar.gears.current      # =\u003e :neutral\ncar.reverse_lights_on? # =\u003e false\n\ncar.gears.start        # =\u003e \"shifted from neutral to one\"\n\ncar.gears.back         # =\u003e \"shifted from one to reverse\"\ncar.gears.current      # =\u003e :reverse\ncar.reverse_lights_on? # =\u003e true\n```\n\n### 7.2 ActiveRecord\n\nIn order to integrate **FiniteMachine** with ActiveRecord simply add a method with state machine definition. You can also define the state machine in separate module to aid reusability. Once the state machine is defined use the `target` helper to reference the current class. Having defined `target` you call ActiveRecord methods inside the callbacks to persist the state.\n\nYou can use the `restore!` method to specify which state the **FiniteMachine** should be put back into as follows:\n\n```ruby\nclass Account \u003c ActiveRecord::Base\n  validates :state, presence: true\n\n  before_validation :set_initial_state, on: :create\n\n  def set_initial_state\n    self.state = manage.current\n  end\n\n  after_find :restore_state\n  after_initialize :restore_state\n\n  def restore_state\n    manage.restore!(state.to_sym) if state.present?\n  end\n\n  def manage\n    @manage ||= FiniteMachine.new(self) do\n      initial :unapproved\n\n      event :enqueue, :unapproved =\u003e :pending\n      event :authorize, :pending =\u003e :access\n\n      on_enter do |event|\n        target.state = event.to\n      end\n    end\n  end\nend\n\naccount = Account.new\naccount.state   # =\u003e :unapproved\naccount.manage.enqueue\naccount.state   # =\u003e :pending\naccount.manage.authorize\naccount.state   # =\u003e :access\n```\n\nPlease note that you do not need to call `target.save` inside callback, it is enough to just set the state. It is much more preferable to let the `ActiveRecord` object to persist when it makes sense for the application and thus keep the state machine focused on managing the state transitions.\n\n### 7.3 Transactions\n\nWhen using **FiniteMachine** with ActiveRecord it advisable to trigger state changes inside transactions to ensure integrity of the database. Given Account example from section 7.2 one can run event in transaction in the following way:\n\n```ruby\nActiveRecord::Base.transaction do\n  account.manage.enqueue\nend\n```\n\nIf the transition fails it will raise `TransitionError` which will cause the transaction to rollback.\n\nPlease check the ORM of your choice if it supports database transactions.\n\n## 8 Tips\n\nCreating a standalone **FiniteMachine** brings a number of benefits, one of them being easier testing. This is especially true if the state machine is extremely complex itself. Ideally, you would test the machine in isolation and then integrate it with other objects or ORMs.\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n## Code of Conduct\n\nEveryone interacting in the FiniteMachine project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/finite_machine/blob/master/CODE_OF_CONDUCT.md).\n\n## Copyright\n\nCopyright (c) 2014 Piotr Murach. See LICENSE for further details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Ffinite_machine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiotrmurach%2Ffinite_machine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Ffinite_machine/lists"}