Enum machine
Enum machine is a library for defining enums and setting state machines for attributes in ActiveRecord models and plain Ruby classes.
You can visualize the state machine with enum_machine-contrib
Why not state_machines/aasm?
The aasm and state_machines gems suggest calling special methods to change the state
. In practice, this can lead to errors. In enum_machine, the state
is changed by updating the corresponding field, and the validation of the ability to change from one state to another is done in the after_validation
callback. This allows the state
of a model to be changed consistently with the usual save!
. In addition aasm/state_machines add many autogenerated methods to the model class and instances. This makes it much more difficult to search by the project. Pollutes the method's space. Adds a bottleneck in method naming because you have to remember these methods in your code.
Performance comparison (see test/performance.rb)
Gem | Method | |
---|---|---|
enum_machine | order.state.forming? | 894921.3 i/s |
state_machines | order.forming? | 189901.8 i/s - 4.71x slower |
aasm | order.forming? | 127073.7 i/s - 7.04x slower |
enum_machine | order.state.can_closed? | 473150.4 i/s |
aasm | order.may_to_closed? | 24459.1 i/s - 19.34x slower |
state_machines | order.can_to_closed? | 12136.8 i/s - 38.98x slower |
enum_machine | Order::STATE.values | 6353820.4 i/s |
aasm | Order.aasm(:state).states.map(&:name) | 131390.5 i/s - 48.36x slower |
state_machines | Order.state_machines[:state].states.map(&:value) | 108449.7 i/s - 58.59x slower |
enum_machine | order.state = "forming" and order.valid? | 13873.4 i/s |
state_machines | order.state_event = "to_forming" and order.valid? | 6173.6 i/s - 2.25x slower |
aasm | order.to_forming | 3095.9 i/s - 4.48x slower |
Installation
Add to your Gemfile:
gem 'enum_machine'
Usage
Enums
# With ActiveRecord
class Product < ActiveRecord::Base
enum_machine :color, %w(red green)
end
# Or with plain class
class Product
include EnumMachine[color: { enum: %w[red green] }]
end
Product::COLOR.values # => ["red", "green"]
Product::COLOR::RED # => "red"
Product::COLOR::RED__GREEN # => ["red", "green"]
product = Product.new
product.color # => nil
product.color = 'red'
product.color.red? # => true
Aliases
class Product < ActiveRecord::Base
enum_machine :state, %w[created approved published] do
aliases(
'forming' => %w[created approved],
)
end
Product::STATE.forming # => %w[created approved]
product = Product.new(state: 'created')
product.state.forming? # => true
Transitions
class Product < ActiveRecord::Base
enum_machine :color, %w[red green blue]
enum_machine :state, %w[created approved cancelled activated] do
transitions(
nil => 'red',
'created' => [nil, 'approved'],
%w[cancelled approved] => 'activated',
'activated' => %w[created cancelled],
)
# Will be executed in `before_save` callback
before_transition 'created' => 'approved' do |product|
product.color = 'green' if product.color.red?
end
# Will be executed in `after_save` callback
after_transition %w[created] => %w[approved] do |product|
product.color = 'red'
end
after_transition any => 'cancelled' do |product|
product.cancelled_at = Time.zone.now
end
end
end
product = Product.create(state: 'created')
product.state.possible_transitions # => [nil, "approved"]
product.state.can_activated? # => false
product.state.to_activated! # => EnumMachine::Error: transition "created" => "activated" not defined in enum_machine
product.state.to_approved! # => true; equal to `product.update!(state: 'approved')`
I18n
ru.yml
ru:
enums:
product:
color:
red: Красный
green: Зеленый
# ActiveRecord
class Product < ActiveRecord::Base
enum_machine :color, %w(red green)
end
# Plain class
class Product
# `i18n_scope` option must be explicitly set to use methods below
include EnumMachine[color: { enum: %w[red green], i18n_scope: 'product' }]
end
Product::COLOR.human_name_for('red') # => 'Красный'
Product::COLOR.values_for_form # => [["Красный", "red"], ["Зеленый", "green"]]
product = Product.new(color: 'red')
product.color.human_name # => 'Красный'
I18n scope can be changed with i18n_scope
option:
# For AciveRecord
class Product < ActiveRecord::Base
enum_machine :color, %w(red green), i18n_scope: 'users.product'
end
# For plain class
class Product
include EnumMachine[color: { enum: %w[red green], i18n_scope: 'users.product' }]
end
ru.yml
ru:
enums:
users:
product:
color:
red: Красный
green: Зеленый
License
The gem is available as open source under the terms of the MIT License.