loading...

Validate state transition easily without any gem

uiur profile image Zato ・1 min read

Say, you have a model with status field:

class Order < ApplicationRecord
  enum status: [
    :cart,     
    :requested,
    :confirmed,
    :complete, 
    :canceled 
  ], _prefix: true
Enter fullscreen mode Exit fullscreen mode

In some case, you want to put some constraints on status changes. It can change from cart to requested, but can't change from requested to cart, for example.

You can use state machine gems, such as aasm to write validation.

But the gem introduces new DSL to write state transition and it might be overkill just for validation.

Solution

It can be written with basic ActiveRecord validation this way:

validates :status,
  inclusion: { in: %w[requested canceled] },
  if: -> { status_changed? && status_was == 'cart' }
Enter fullscreen mode Exit fullscreen mode

If you like to write from -> to direction, just use hash:

class Order < ApplicationRecord
  # ...

  {
    'cart' => %w[requested canceled],
    'requested' => %w[confirmed complete canceled],
    'confirmed' => %w[complete canceled],
    'complete' => %w[],
  }.each do |status_from, status_to|
    validates :status,
      inclusion: {
        in: status_to,
        message: " cannot change from '#{status_from}' to '%{value}'"
      },
      if: -> { persisted? && status_changed? && status_was == status_from }
  end
Enter fullscreen mode Exit fullscreen mode

A few notes:

  • Use if: -> { persisted? ... to avoid validation when creating a model instance in tests
  • Set customized message because default ones are not friendly

Discussion

pic
Editor guide