Module: ConcernsOnRails::Models::Stateable

Extended by:
ActiveSupport::Concern
Defined in:
lib/concerns_on_rails/models/stateable.rb

Overview

Lightweight, string-backed state machine — the common 80% of a state machine without an AASM-sized dependency.

class Article < ApplicationRecord
  include ConcernsOnRails::Stateable

  stateable_by :status,
               states: %i[draft pending published archived],
               default: :draft,
               transitions: {
                 publish: { from: %i[draft pending], to: :published },
                 archive: { to: :archived }          # :from omitted => any state
               }
end

Generates, for each state (method names honor prefix:/suffix:):

* predicate  — article.draft?       => status == "draft"
* scope      — Article.draft        => where(status: "draft")
* setter     — article.published!   => update!(status: "published")  (unguarded)

And for each declared transition:

* event!     — article.publish!     => guarded; raises InvalidTransition from a bad state
* guard?     — article.may_publish? => whether the transition is allowed now

Plus a generic ‘transition_to!(state)`.

Options for stateable_by: default:, transitions:, prefix:, suffix: (prefix:/suffix: take ‘true` to use the field name, or a literal string/symbol).

Notes:

* String columns only (store the state name) — not integer-backed like Rails enum.
* A state named like an AR method (`new`, `valid`) or a concern scope
  (`active`, `expired`) will clash — use prefix:/suffix: to disambiguate.

Defined Under Namespace

Modules: ClassMethods Classes: InvalidTransition

Constant Summary collapse

LABEL =
"ConcernsOnRails::Models::Stateable".freeze

Instance Method Summary collapse

Instance Method Details

#transition_to!(state) ⇒ Object

Move to any declared state by name, bypassing transition guards.

Raises:



56
57
58
59
60
61
# File 'lib/concerns_on_rails/models/stateable.rb', line 56

def transition_to!(state)
  state = state.to_sym
  raise InvalidTransition, "#{LABEL}: '#{state}' is not a declared state" unless self.class.stateable_states.include?(state)

  update!(self.class.stateable_field => state.to_s)
end