{"id":21692538,"url":"https://github.com/nonblockio/ecto_transit","last_synced_at":"2025-07-08T06:06:47.176Z","repository":{"id":57493682,"uuid":"231375034","full_name":"nonblockio/ecto_transit","owner":"nonblockio","description":"transition validator for ecto","archived":false,"fork":false,"pushed_at":"2020-04-18T03:36:22.000Z","size":11,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-08T06:06:17.721Z","etag":null,"topics":["aasm","ecto","elixir","enum","state","transition"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/nonblockio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-02T12:12:28.000Z","updated_at":"2024-04-08T02:46:16.000Z","dependencies_parsed_at":"2022-08-30T04:01:10.127Z","dependency_job_id":null,"html_url":"https://github.com/nonblockio/ecto_transit","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/nonblockio/ecto_transit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nonblockio%2Fecto_transit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nonblockio%2Fecto_transit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nonblockio%2Fecto_transit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nonblockio%2Fecto_transit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nonblockio","download_url":"https://codeload.github.com/nonblockio/ecto_transit/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nonblockio%2Fecto_transit/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264207102,"owners_count":23572729,"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":["aasm","ecto","elixir","enum","state","transition"],"created_at":"2024-11-25T18:16:24.945Z","updated_at":"2025-07-08T06:06:47.157Z","avatar_url":"https://github.com/nonblockio.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EctoTransit\n\n[![Hex.pm version](https://img.shields.io/hexpm/v/ecto_transit.svg?style=flat)](https://hex.pm/packages/ecto_transit)\n[![Hex.pm downloads](https://img.shields.io/hexpm/dt/ecto_transit.svg?style=flat)](https://hex.pm/packages/ecto_transit)\n[![CircleCI](https://circleci.com/gh/nonblockio/ecto_transit.svg?style=svg)](https://circleci.com/gh/nonblockio/ecto_transit)\n\nEctoTransit is a transition validator for Ecto and EctoEnum\n\n## Installation\n\nAdd `ecto_transit` in your `mix.exs` and run `mix deps.get`\n\n```elixir\ndef deps do\n  [\n    {:ecto_transit, \"~\u003e 0.1.0\"}\n  ]\nend\n```\n\n## How to use\n\n### Generate validator\n\n```elixir\ndefmodule TODO do\n  @states ~w(created scheduled doing overdued done closed)a\n\n  @transitions %{\n    :* =\u003e :closed,\n    :created =\u003e ~w(scheduled doing)a,\n    :scheduled =\u003e ~w(doing overdued)a,\n    :doing =\u003e ~w(created scheduled done)a\n  }\n\n  use EctoTransit, on: @states, rules: @transitions\nend\n\niex\u003e TODO.can_transit?(:created, :scheduled)\ntrue\n\niex\u003e TODO.can_transit?(:closed, :created)\nfalse\n```\n\n### Change validator name\n\nSet `:with` to change validator name\n\n```elixir\nuse EctoTransit, on: @states, rules: @transitions, with: :should_change?\n\niex\u003e TODO.should_change?(:created, :scheduled)\ntrue\n```\n\n### Integration with EctoEnum\n\n```elixir\ndefmodule Order do\n  import EctoEnum\n  defenum State, ~w(created paid in_deliver done)\n\n  @transitions %{\n    ~w(created paid in_deliver)a =\u003e :done,\n    :created =\u003e :paid,\n    :paid =\u003e :in_deliver\n  }\n\n  use EctoTransit, on: State, rules: @transitions, with: :can_continue?\nend\n```\n\n### Validate changset\n\n```elixir\ndefmodule Order do\n  import EctoEnum\n  defenum State, ~w(created paid in_deliver done)\n\n  @transitions %{\n    ~w(created paid in_deliver)a =\u003e :done,\n    :created =\u003e :paid,\n    :paid =\u003e :in_deliver\n  }\n\n  use EctoTransit, on: State, rules: @transitions, with: :can_continue?\n\n  use Ecto.Schema\n\n  schema \"orders\" do\n    field :state, State\n  end\nend\n\niex\u003e order_changeset |\u003e unsafe_validate_transit(:state)\n```\n\n### Validate safely\n\nUse lock mechanism to validate safely\n\n```elixir\nchangeset\n|\u003e unsafe_validate_transit(:state)\n|\u003e Ecto.Changeset.optimistic_lock(:lock_version)\n# raise if concurrent update happens\n```\n\n\n### Use other validator\n\nSet `:with` to change validator\n\n```elixir\n%Post{}\n|\u003e Ecto.Changeset.change()\n|\u003e unsafe_validate_transit(:state, with: :should_change?)\n## Post.should_change?(from, to)\n```\n\n```elixir\nunsafe_validate_transit(:state, with: fn changeset, {from, to} -\u003e\n    get_field(changeset, :receive_count) == 0 \u0026\u0026\n      match?({:sent, :recalled}, {from, to})\n  end\nend)\n```\n\n```elixir\nunsafe_validate_transit(:state, {Audit, :can_change?, [:update, %{context: nil}]})\n## Audit.can_change?(changeset, {from, to}, :update, %{context: nil})\n```\n\n## FAQ\n\n### How to do callback\n\nEctoTransit does not support callbacks like other libs do. The reason callbacks are needed is in OOP ORM frameworks state and change are sealed inside. So to callbacks are useful when you wanna to check or react. But in Ecto, those information are stored directly in a changeset unconcealed.\n\nJust add more validators you could do checking(before).For reaction, db transaction is more explicit and good for maintenance(after).\n\n### Validator fail when no change on field\n\n`EctoTransit.unsafe_validate_transit/3` requires change happens by default in case other fields change leaving state untouched, which may break state contracts.\n\nSo transition validator should not be cuddled with other normal update validator. Or add `require: false` to skip no change checking if you really know what you are doing.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnonblockio%2Fecto_transit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnonblockio%2Fecto_transit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnonblockio%2Fecto_transit/lists"}