[![Version](https://img.shields.io/gem/v/workflow-activerecord.svg)](https://rubygems.org/gems/workflow-activerecord)
[![Test](https://github.com/geekq/workflow-activerecord/actions/workflows/test.yml/badge.svg)](https://github.com/geekq/workflow-activerecord/actions/workflows/test.yml)
[![Code Climate](https://codeclimate.com/github/geekq/workflow-activerecord/badges/gpa.svg)](https://codeclimate.com/github/geekq/workflow-activerecord)
[![Test Coverage](https://codeclimate.com/github/geekq/workflow-activerecord/badges/coverage.svg)](https://codeclimate.com/github/geekq/workflow-activerecord/coverage)
# workflow-activerecord
**ActiveRecord/Rails Integration for the Workflow library**
Major+minor versions of workflow-activerecord are based on the oldest
compatible ActiveRecord API. To use [`workflow`][workflow] with
Rails/ActiveRecord 6.*, 7.* please use:
gem 'workflow-activerecord', '~> 6.0'
This will also automatically include the newest compatible version of
the core 'workflow' gem. But you can also choose a specific version:
gem 'workflow', '~> 2.0'
gem 'workflow-activerecord', '~> 4.1'
Please also have a look at [the sample application][]!
For detailed introduction into workflow DSL please read the
[`workflow` README][workflow]!
[workflow]: https://github.com/geekq/workflow
[the sample application]: https://github.com/geekq/workflow-rails-sample
State persistence with ActiveRecord
-----------------------------------
Workflow library can handle the state persistence fully automatically. You
only need to define a string field on the table called `workflow_state`
and include the workflow mixin in your model class as usual:
class Order < ApplicationRecord
include WorkflowActiverecord
workflow do
# list states and transitions here
end
end
On a database record loading all the state check methods e.g.
`article.state`, `article.awaiting_review?` are immediately available.
For new records or if the `workflow_state` field is not set the state
defaults to the first state declared in the workflow specification. In
our example it is `:new`, so `Article.new.new?` returns true and
`Article.new.approved?` returns false.
At the end of a successful state transition like `article.approve!` the
new state is immediately saved in the database.
You can change this behaviour by overriding `persist_workflow_state`
method.
### Scopes
Workflow library also adds automatically generated scopes with names based on
states names:
class Order < ApplicationRecord
include WorkflowActiverecord
workflow do
state :approved
state :pending
end
end
# returns all orders with `approved` state
Order.with_approved_state
# returns all orders with `pending` state
Order.with_pending_state
### Custom workflow database column
[meuble](http://imeuble.info/) contributed a solution for using
custom persistence column easily, e.g. for a legacy database schema:
class LegacyOrder < ApplicationRecord
include WorkflowActiverecord
workflow_column :foo_bar # use this legacy database column for
# persistence
end
### Single table inheritance
Single table inheritance is also supported. Descendant classes can either
inherit the workflow definition from the parent or override with its own
definition.
Custom Versions of Existing Adapters
------------------------------------
Other adapters (such as a custom ActiveRecord plugin) can be selected by adding a `workflow_adapter` class method, eg.
```ruby
class Example < ApplicationRecord
def self.workflow_adapter
MyCustomAdapter
end
include Workflow
# ...
end
```
(The above will include `MyCustomAdapter` *instead* of the default
`WorkflowActiverecord` adapter.)
Multiple Workflows
------------------
I am frequently asked if it's possible to represent multiple "workflows"
in an ActiveRecord class.
The solution depends on your business logic and how you want to
structure your implementation.
### Use Single Table Inheritance
One solution can be to do it on the class level and use a class
hierarchy. You can use [single table inheritance][STI] so there is only
single `orders` table in the database. Read more in the chapter "Single
Table Inheritance" of the [ActiveRecord documentation][ActiveRecord].
Then you define your different classes:
class Order < ActiveRecord::Base
include WorkflowActiverecord
end
class SmallOrder < Order
workflow do
# workflow definition for small orders goes here
end
end
class BigOrder < Order
workflow do
# workflow for big orders, probably with a longer approval chain
end
end
### Individual workflows for objects
Another solution would be to connect different workflows to object
instances via metaclass, e.g.
# Load an object from the database
booking = Booking.find(1234)
# Now define a workflow - exclusively for this object,
# probably depending on some condition or database field
if # some condition
class << booking
include WorkflowActiverecord
workflow do
state :state1
state :state2
end
end
# if some other condition, use a different workflow
You can also encapsulate this in a class method or even put in some
ActiveRecord callback. Please also have a look at [the full working
example][multiple_workflow_test]!
### on_transition
You can have a look at an advanced [`on_transition`][] example in
[this test file][advanced_hooks_and_validation_test].
[STI]: http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
[ActiveRecord]: http://api.rubyonrails.org/classes/ActiveRecord/Base.html
[multiple_workflow_test]: https://github.com/geekq/workflow-activerecord/blob/develop/test/multiple_workflows_test.rb
[`on_transition`]: https://github.com/geekq/workflow#on_transition
[advanced_hooks_and_validation_test]: http://github.com/geekq/workflow-activerecord/blob/develop/test/advanced_hooks_and_validation_test.rb
### Handling the state transition in a transaction
You might want to perform the state transition in a database transaction,
so that the state is persisted atomically with all the other attributes.
To do so, define a module:
```ruby
module TransitionTransaction
def process_event!(name, *, **)
transaction { super(name, *, **) }
end
end
```
and then prepend it in your model:
```ruby
class Task
workflow do
...
end
prepend TransitionTransaction
end
```
Changelog
---------
### New in the version 6.0.0
* GH-14 retire Ruby 2.6 and Rails 5.* and older since they have reached end of
live; please use workflow-activerecord 4.1.9, if you still depend on
those versions
### New in the version 4.1.9
* GH-13 Switch CI (continuous integration) from travis-CI to GitHub
* Tested Rails 7.0 support
Support
-------
### Reporting bugs