Class: Fosm::Lifecycle::Definition
- Inherits:
-
Object
- Object
- Fosm::Lifecycle::Definition
- Defined in:
- lib/fosm/lifecycle/definition.rb
Overview
Holds the entire lifecycle definition for a FOSM model. Instantiated once per model class at class load time.
Instance Attribute Summary collapse
-
#access_definition ⇒ Object
readonly
Returns the value of attribute access_definition.
-
#events ⇒ Object
readonly
Returns the value of attribute events.
-
#states ⇒ Object
readonly
Returns the value of attribute states.
Instance Method Summary collapse
-
#access(&block) ⇒ Object
DSL: declare the access control block for this lifecycle.
-
#access_defined? ⇒ Boolean
Returns true if an access block was declared (RBAC is active).
-
#available_events_from(state) ⇒ Object
Returns events valid from the given state.
-
#event(name, from:, to:) ⇒ Object
DSL: declare an event.
- #event_names ⇒ Object
- #find_event(name) ⇒ Object
- #find_state(name) ⇒ Object
-
#guard(name, on:, &block) ⇒ Object
DSL: declare a guard on an event.
- #initial_state ⇒ Object
-
#initialize ⇒ Definition
constructor
A new instance of Definition.
-
#side_effect(name, on:, defer: false, &block) ⇒ Object
DSL: declare a side effect on an event Options: defer: false (default), true — run after transaction commits.
-
#snapshot(strategy = nil, **options) ⇒ Object
DSL: configure automatic state snapshots on transitions Supports multiple strategies for how often to snapshot:.
-
#snapshot_attributes(*attrs) ⇒ Object
DSL: specify which attributes to include in snapshots Usage: snapshot_attributes :amount, :status, :line_items_count.
-
#snapshot_configuration ⇒ Object
Returns the snapshot configuration (nil if not configured).
-
#snapshot_configured? ⇒ Boolean
Returns true if snapshot configuration has been set.
-
#state(name, initial: false, terminal: false) ⇒ Object
DSL: declare a state.
- #state_names ⇒ Object
-
#to_diagram_data ⇒ Object
Returns a hash suitable for rendering a state diagram.
Constructor Details
#initialize ⇒ Definition
Returns a new instance of Definition.
15 16 17 18 19 20 21 |
# File 'lib/fosm/lifecycle/definition.rb', line 15 def initialize @states = [] @events = [] @pending_guards = {} # event_name => [GuardDefinition, ...] @pending_side_effects = {} # event_name => [SideEffectDefinition, ...] @access_definition = nil # nil = open-by-default; set when access{} is declared end |
Instance Attribute Details
#access_definition ⇒ Object (readonly)
Returns the value of attribute access_definition.
13 14 15 |
# File 'lib/fosm/lifecycle/definition.rb', line 13 def access_definition @access_definition end |
#events ⇒ Object (readonly)
Returns the value of attribute events.
13 14 15 |
# File 'lib/fosm/lifecycle/definition.rb', line 13 def events @events end |
#states ⇒ Object (readonly)
Returns the value of attribute states.
13 14 15 |
# File 'lib/fosm/lifecycle/definition.rb', line 13 def states @states end |
Instance Method Details
#access(&block) ⇒ Object
DSL: declare the access control block for this lifecycle.
Activates RBAC for this object. Without this block, all authenticated actors have full access (open-by-default — backwards-compatible).
Once declared, deny-by-default: only granted capabilities work. Superadmin and :system/:agent symbol actors always bypass checks.
Example:
access do
role :owner, default: true do
can :crud
can :send_invoice, :cancel
end
role :approver do
can :read
can :pay
end
role :viewer do
can :read
end
end
81 82 83 84 85 |
# File 'lib/fosm/lifecycle/definition.rb', line 81 def access(&block) @access_definition = AccessDefinition.new @access_definition.instance_eval(&block) @access_definition end |
#access_defined? ⇒ Boolean
Returns true if an access block was declared (RBAC is active)
88 89 90 |
# File 'lib/fosm/lifecycle/definition.rb', line 88 def access_defined? @access_definition.present? end |
#available_events_from(state) ⇒ Object
Returns events valid from the given state
131 132 133 |
# File 'lib/fosm/lifecycle/definition.rb', line 131 def available_events_from(state) @events.select { |e| e.valid_from?(state) } end |
#event(name, from:, to:) ⇒ Object
DSL: declare an event
32 33 34 35 36 37 38 39 40 41 |
# File 'lib/fosm/lifecycle/definition.rb', line 32 def event(name, from:, to:) event_def = EventDefinition.new(name: name, from: from, to: to) # Apply any guards/side_effects declared before this event (unusual but handle it) (@pending_guards[name.to_sym] || []).each { |g| event_def.add_guard(g) } (@pending_side_effects[name.to_sym] || []).each { |se| event_def.add_side_effect(se) } @events << event_def event_def end |
#event_names ⇒ Object
126 127 128 |
# File 'lib/fosm/lifecycle/definition.rb', line 126 def event_names @events.map(&:name) end |
#find_event(name) ⇒ Object
114 115 116 |
# File 'lib/fosm/lifecycle/definition.rb', line 114 def find_event(name) @events.find { |e| e.name == name.to_sym } end |
#find_state(name) ⇒ Object
118 119 120 |
# File 'lib/fosm/lifecycle/definition.rb', line 118 def find_state(name) @states.find { |s| s.name == name.to_sym } end |
#guard(name, on:, &block) ⇒ Object
DSL: declare a guard on an event
44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/fosm/lifecycle/definition.rb', line 44 def guard(name, on:, &block) guard_def = GuardDefinition.new(name: name, &block) event_def = find_event(on) if event_def event_def.add_guard(guard_def) else # Event may be declared after guard — store for later @pending_guards[on.to_sym] ||= [] @pending_guards[on.to_sym] << guard_def end end |
#initial_state ⇒ Object
110 111 112 |
# File 'lib/fosm/lifecycle/definition.rb', line 110 def initial_state @states.find(&:initial?) end |
#side_effect(name, on:, defer: false, &block) ⇒ Object
DSL: declare a side effect on an event Options:
defer: false (default), true — run after transaction commits
95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/fosm/lifecycle/definition.rb', line 95 def side_effect(name, on:, defer: false, &block) side_effect_def = SideEffectDefinition.new( name: name, defer: defer, &block ) event_def = find_event(on) if event_def event_def.add_side_effect(side_effect_def) else @pending_side_effects[on.to_sym] ||= [] @pending_side_effects[on.to_sym] << side_effect_def end end |
#snapshot(strategy = nil, **options) ⇒ Object
DSL: configure automatic state snapshots on transitions Supports multiple strategies for how often to snapshot:
snapshot :every # snapshot on every transition
snapshot every: 10 # snapshot every 10 transitions
snapshot time: 300 # snapshot if >5 min since last snapshot
snapshot :terminal # snapshot only when reaching terminal states
snapshot :manual # only snapshot when explicitly requested (default)
snapshot_attributes :amount, :due_date, :line_items_count
146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/fosm/lifecycle/definition.rb', line 146 def snapshot(strategy = nil, **) @snapshot_configuration ||= SnapshotConfiguration.new if strategy.is_a?(Symbol) || strategy.is_a?(String) @snapshot_configuration.send(strategy) elsif [:every] @snapshot_configuration.count([:every]) elsif [:time] @snapshot_configuration.time([:time]) end @snapshot_configuration end |
#snapshot_attributes(*attrs) ⇒ Object
DSL: specify which attributes to include in snapshots Usage: snapshot_attributes :amount, :status, :line_items_count
162 163 164 165 |
# File 'lib/fosm/lifecycle/definition.rb', line 162 def snapshot_attributes(*attrs) @snapshot_configuration ||= SnapshotConfiguration.new @snapshot_configuration.set_attributes(*attrs) end |
#snapshot_configuration ⇒ Object
Returns the snapshot configuration (nil if not configured)
173 174 175 |
# File 'lib/fosm/lifecycle/definition.rb', line 173 def snapshot_configuration @snapshot_configuration end |
#snapshot_configured? ⇒ Boolean
Returns true if snapshot configuration has been set
168 169 170 |
# File 'lib/fosm/lifecycle/definition.rb', line 168 def snapshot_configured? @snapshot_configuration.present? end |
#state(name, initial: false, terminal: false) ⇒ Object
DSL: declare a state
24 25 26 27 28 29 |
# File 'lib/fosm/lifecycle/definition.rb', line 24 def state(name, initial: false, terminal: false) if initial && @states.any?(&:initial?) raise ArgumentError, "Only one initial state is allowed" end @states << StateDefinition.new(name: name, initial: initial, terminal: terminal) end |
#state_names ⇒ Object
122 123 124 |
# File 'lib/fosm/lifecycle/definition.rb', line 122 def state_names @states.map(&:name).map(&:to_s) end |
#to_diagram_data ⇒ Object
Returns a hash suitable for rendering a state diagram
178 179 180 181 182 183 184 185 186 187 |
# File 'lib/fosm/lifecycle/definition.rb', line 178 def to_diagram_data { states: @states.map { |s| { name: s.name, initial: s.initial?, terminal: s.terminal? } }, transitions: @events.map { |e| e.from_states.map { |from| { event: e.name, from: from, to: e.to_state, guards: e.guards.map(&:name) } } }.flatten } end |