{"id":17195419,"url":"https://github.com/ianpurvis/police_state","last_synced_at":"2026-01-07T19:51:12.782Z","repository":{"id":22202030,"uuid":"95583565","full_name":"ianpurvis/police_state","owner":"ianpurvis","description":"Lightweight state machine for Active Record and Active Model.","archived":false,"fork":false,"pushed_at":"2024-08-02T00:18:33.000Z","size":172,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":2,"default_branch":"trunk","last_synced_at":"2025-03-23T16:15:31.046Z","etag":null,"topics":["activerecord","rails","ruby","state-machine"],"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/ianpurvis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"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}},"created_at":"2017-06-27T17:25:18.000Z","updated_at":"2022-04-20T21:44:29.000Z","dependencies_parsed_at":"2023-01-11T21:31:26.286Z","dependency_job_id":"a7190c82-c4e5-4adf-9078-f670fdc0249f","html_url":"https://github.com/ianpurvis/police_state","commit_stats":{"total_commits":149,"total_committers":4,"mean_commits":37.25,"dds":"0.37583892617449666","last_synced_commit":"600eeac289353e9ed7b2fbd611dbed957a2c0ff4"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ianpurvis%2Fpolice_state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ianpurvis%2Fpolice_state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ianpurvis%2Fpolice_state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ianpurvis%2Fpolice_state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ianpurvis","download_url":"https://codeload.github.com/ianpurvis/police_state/tar.gz/refs/heads/trunk","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246379379,"owners_count":20767696,"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":["activerecord","rails","ruby","state-machine"],"created_at":"2024-10-15T01:50:24.248Z","updated_at":"2026-01-07T19:51:12.751Z","avatar_url":"https://github.com/ianpurvis.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://github.com/ianpurvis/police_state/actions/workflows/continuous.yml/badge.svg)](https://github.com/ianpurvis/police_state/actions/workflows/continuous.yml)\n[![codecov](https://codecov.io/gh/ianpurvis/police_state/branch/trunk/graph/badge.svg)](https://codecov.io/gh/ianpurvis/police_state)\n[![Doc Status](http://inch-ci.org/github/ianpurvis/police_state.svg?branch=trunk)](http://inch-ci.org/github/ianpurvis/police_state)\n\n# Police State\nLightweight state machine for Active Record and Active Model.\n\n\n## Background\nAfter experimenting with state machines in a recent project, I became interested in a workflow that felt more natural for rails. In particular, I wanted to reduce architectural overlap incurred by flow control, guard, and callback workflows.\n\nThe goal of Police State is to let you easily work with state machines based on `ActiveModel::Dirty`, `ActiveModel::Validation`, and `ActiveModel::Callbacks`\n\n\n## Usage\nPolice State revolves around the use of `TransitionValidator` and two helper methods, `attribute_transitioning?` and `attribute_transitioned?`.\n\nTo get started, just include `PoliceState` in your model and define a set of valid transitions:\n\n```ruby\nclass Model \u003c ApplicationRecord\n  include PoliceState\n\n  enum status: {\n    queued: 0,\n    active: 1,\n    complete: 2,\n    failed: 3\n  }\n  \n  validates :status, transition: { from: nil, to: :queued }\n  validates :status, transition: { from: :queued, to: :active }\n  validates :status, transition: { from: :active, to: :complete }\n  validates :status, transition: { from: [:queued, :active], to: :failed }\nend\n```\n\n### Committing a Transition\nOne aspect of Police State that will feel different than other ruby state machines is the idea that in-memory state has not fully transitioned until it is persisted to the database. This lets you operate within a traditional Active Record workflow:\n\n```ruby\nmodel = Model.new(status: :complete)\n# =\u003e #\u003cModel:0x007fa94844d088 @status=:complete\u003e\n\nmodel.status_transitioning?(from: nil)\n# =\u003e true\n\nmodel.status_transitioning?(to: :complete)\n# =\u003e true\n\nmodel.valid?\n# =\u003e false\n\nmodel.errors.to_hash\n# =\u003e {:status=\u003e[\"can't transition to complete\"]}\n\nmodel.save\n# =\u003e false\n\nmodel.save!\n# =\u003e ActiveRecord::RecordInvalid: Validation failed: Status can't transition to complete\n\nmodel.status = :queued\n# =\u003e :queued\n\nmodel.valid?\n# =\u003e true\n\nmodel.save\n# =\u003e true\n\nmodel.status_transitioned?(from: nil, to: :queued)\n# =\u003e true\n\n```\n\n\n### Guard Conditions\nGuard conditions can be introduced for a state by adding a conditional ActiveRecord validation:\n\n```ruby\nvalidates :another_field, :presence, if: -\u003e { queued? }\n```\n\n### Callbacks\n\nCallbacks can be attached to specific transitions by adding a condition on `attribute_transitioned?`. If the callback needs to occur before persistence, `attribute_transitioning?` can also be used.\n\n```ruby\nafter_commit :notify, if: -\u003e { status_transitioned?(to: :complete) }\nafter_commit :alert, if: -\u003e { status_transitioned?(from: :active, to: :failed) }\nafter_commit :log, if: -\u003e { status_transitioned? }\n```\n\n### Events\nExplicit event languge can be added to models by wrapping `update` and / or `update!`\n\n```ruby\ndef run\n  update(status: :active) \nend\n  \ndef run!\n  update!(status: :active)\nend\n```\n\nThe bang methods defined by `ActiveRecord::Enum` work as well:\n\n```ruby\nmodel.active!\n# =\u003e ActiveRecord::RecordInvalid: Validation failed: Status can't transition to active\n```\n\n### Validation Logic\nOne important note about `TransitionValidator` is that it performs a unidirectional validation. For example, the following ensures that the `active` state can only be reached from the `queued` state:\n\n```ruby\nvalidates :status, transition: { from: :queued, to: :active }\n```\n\nHowever, this does not prevent `queued` from transitioning to other states. Those states must be controlled by their own validators.\n\n\n### Active Model\nIf you are using Active Model, make sure your class correctly implements `ActiveModel::Dirty`. For an example, check out [spec/test_model.rb](spec/test_model.rb)\n\n\n## Installation\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'police_state'\n```\n\nAnd then execute:\n```bash\n$ bundle\n```\n\nOr install it yourself as:\n```bash\n$ gem install police_state\n```\n\n\n## License\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n\n[![https://purvisresearch.com](logo.svg)](https://purvisresearch.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fianpurvis%2Fpolice_state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fianpurvis%2Fpolice_state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fianpurvis%2Fpolice_state/lists"}