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:, lock:
(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.
* Guarded transitions check the in-memory state: two processes firing the
same <event>! concurrently can both pass the guard (check-then-write).
`lock: true` closes that race — each <event>! takes a row lock
(SELECT ... FOR UPDATE) and re-checks the guard against the fresh row
first. Requires a clean record (with_lock reloads; AR refuses to
reload unsaved changes) and costs a SELECT per transition.
Defined Under Namespace
Modules: ClassMethods Classes: InvalidTransition
Constant Summary collapse
- LABEL =
"ConcernsOnRails::Models::Stateable".freeze
- OPTIONS =
Valid stateable_by keyword options (everything besides field/states:).
%i[default transitions prefix suffix lock].freeze
Instance Method Summary collapse
- #after_transition(_event, _from, _to) ⇒ Object
-
#before_transition(_event, _from, _to) ⇒ Object
Transition lifecycle hooks — override in the model.
-
#transition_to!(state) ⇒ Object
Move to any declared state by name, bypassing transition guards.
Instance Method Details
#after_transition(_event, _from, _to) ⇒ Object
77 |
# File 'lib/concerns_on_rails/models/stateable.rb', line 77 def after_transition(_event, _from, _to); end |
#before_transition(_event, _from, _to) ⇒ Object
Transition lifecycle hooks — override in the model. Fired by guarded
76 |
# File 'lib/concerns_on_rails/models/stateable.rb', line 76 def before_transition(_event, _from, _to); end |
#transition_to!(state) ⇒ Object
Move to any declared state by name, bypassing transition guards.
67 68 69 70 71 72 |
# File 'lib/concerns_on_rails/models/stateable.rb', line 67 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 |