Log transitions on a state_machines gem to support auditing and business process analytics.
This plugin for the state_machines gem adds support for keeping an audit trail for any state machine. Having an audit trail gives you a complete history of the state changes in your model. This history allows you to investigate incidents or perform analytics, like: "How long does it take on average to go from state a to state b?", or "What percentage of cases goes from state a to b via state c?"
For more information read Why developers should be force-fed state machines.
Note: while the state_machines gem integrates with multiple ORMs, this plugin is currently limited to the following ORM backends:
It should be easy to add new backends by looking at the implementation of the current backends. Pull requests are welcome!
First, make the gem available by adding it to your
Gemfile, and run
# this gem gem 'state_machines-audit_trail' # required runtime dependency for your ORM; either gem 'state_machines-activerecord' # OR gem 'state_machines-mongoid'
For the examples below, we will assume you have a pre-existing model called
Subscription that has a
state_machine configured to utilize the
Create/generate a model and migration that holds the audit trail specific to your target model
A Rails generator is provided to create a model and a migration, call it with:
rails generate state_machines:audit_trail Subscription state
will generate the
SubscriptionStateTransition model and an accompanying migration.
audit_trail in your
class Subscription < ActiveRecord::Base state_machine :state, initial: :start do audit_trail ...
audit_trail will register an
after_transition callback that is used to log all transitions including the initial state if there is one.
Upgrading from state_machine-audit_trail
See the wiki, https://github.com/state-machines/state_machines-audit_trail/wiki/Converting-from-former-state_machine-audit_trail-to-state_machines-audit_trail
:initial - turn off initial state logging
By default, upon instantiation, a
StateTransition is saved for
null => initial state. This is useful to understand the full history
of any model, but there are cases where this can pollute the
audit_trail. For example, when a model has multiple
that use a single
StateTransition model for persistence (in conjunction with
context below), there would be multiple initial state
transitions. By configuring
initial: false, it will skip the initial state transition logging for this specific
leaving the others in the model unaffected.
audit_trail initial: false
:class - custom state transition class
Transition model does not use the default naming scheme, provide it using the
audit_trail class: FooStateTransition
An example use of a custom
:context (below) would be for a model that has multiple
state_machine definitions. The combination
of these options would allow the use of one transition class that logged state information from both state machines.
:context - storing additional attribute or method values
:context option, you can store method results (or attributes exposed as methods) in the state transition class.
In order to utilize this feature, you need to:
- add a field/column to your state transition class (i.e.
SubscriptionStateTransitions) and perhaps underlying database through a migration
- expose the attribute as a method, or create a method to compute a dynamic value
Example 1 - Store a single attribute value
audit_trail context: :field1
Example 2 - Store multiple attribute values
audit_trail context: [:field1, :field2]
Example 3 - Store multiple values from a single context object
class Subscription < ActiveRecord::Base state_machine :state, initial: :start do audit_trail context: :user ... end end class SubscriptionStateTransition < ActiveRecord::Base def user=(u) self.user_id = u.id self.user_name = u.name end end
Example 4 - Store simple method results
Store simple method results.
Sometimes it can be useful to store dynamically computed information, such as those from a
class Subscription < ActiveRecord::Base state_machine :state, initial: :start do audit_trail :context: :plan_time_remaining ... def plan_time_remaining # Dynamically computed field e.g., based on other models ...
Example 5 - Store advanced method results
Store method results that interrogate the transition for information such as
class Subscription < ActiveRecord::Base state_machine :state, initial: :start do audit_trail :context: :user_name ... # method receives a state_machines transition object def user_name(transition) if transition.args.present? user_id = transition.args.last.delete(:user_id) User.find(user_id).name else 'Undefined User' end ... model = Subscription.first model.ignite!('arg1, 'arg2', 'arg3', user_id: 1)
Conversion from the original code to
state_machines by AlienFast.
The original plugin was written by Jesse Storimer and Willem van Bergen for Shopify. Mongoid support was contributed by Siddharth.
Released under the MIT license (see LICENSE).
- Fork it ( https://github.com/state-machines/state_machines-audit_trail/fork )
- Create your feature branch (
git checkout -b my-new-feature)
- Commit your changes (
git commit -am 'Add some feature')
- Push to the branch (
git push origin my-new-feature)
- Create a new Pull Request