Skip to content

corp-gp/enum_machine

Repository files navigation

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 transitions map with enum_machine-contrib

Why is enum_machine better then state_machines / aasm?

  • faster 5x
  • code lines: enum_machine - 348, AASM - 2139
  • namespaced (via attr) by default: order.state.to_collected
  • aliases
  • guarantees of existing transitions
  • simple run transitions with callbacks order.update(state: "collected") or order.state.to_collected
  • aasm / state_machines event driven, enum_machine state driven
# aasm
event :complete do # complete/collected - dichotomy between states and events
  before { puts "event complete" }
  transitions from: :collecting, to: :collected
end

# pay/archived difficult to remember the relationship between statuses and events
# try to explain this to the logic of business stakeholders
event :pay do
  transitions from: [:created, :collected], to: :archived
end

order = Order.create(state: "collecting")
order.update(state: "archived") # not check transitions, invalid logic
order.update(state: "collected") # not run callbacks 
order.complete # need use event for transition, but your object in UI and DB have only states

# enum_machine
transitions( # simple readable transitions map
  "collecting" => "collected",
  "collected"  => "archived",
)
before_transition("collecting" => "collected") { puts "event complete" }

order = Order.create(state: "collecting")
order.update(state: "archived") # checked transitions, raise exception
order.update(state: "collected") # run callbacks

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] }]
  # or reuse from model
  Product::COLOR.decorator_module
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
product.color.human_name # => "Красный"

Aliases

class Product < ActiveRecord::Base
  enum_machine :state, %w[created approved published] do
    aliases(
      "forming" => %w[created approved],
    )
  end
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(any => any) - allow all transitions
    transitions(
      nil                    => "created",
      "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: "approve")`

Skip transitions

product = Product.new(state: "created")
product.skip_state_transitions { product.save }

method generated as skip_#{enum_name}_transitions

Skip in factories

FactoryBot.define do
  factory :product do
    name { Faker::Commerce.product_name }
    to_create { |product| product.skip_state_transitions { product.save! } }
  end
end

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

Benchmarks

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

License

The gem is available as open source under the terms of the MIT License.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •