Class: Fosm::Lifecycle::Definition

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initializeDefinition

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_definitionObject (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

#eventsObject (readonly)

Returns the value of attribute events.



13
14
15
# File 'lib/fosm/lifecycle/definition.rb', line 13

def events
  @events
end

#statesObject (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)

Returns:

  • (Boolean)


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_namesObject



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_stateObject



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, **options)
  @snapshot_configuration ||= SnapshotConfiguration.new

  if strategy.is_a?(Symbol) || strategy.is_a?(String)
    @snapshot_configuration.send(strategy)
  elsif options[:every]
    @snapshot_configuration.count(options[:every])
  elsif options[:time]
    @snapshot_configuration.time(options[: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_configurationObject

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

Returns:

  • (Boolean)


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_namesObject



122
123
124
# File 'lib/fosm/lifecycle/definition.rb', line 122

def state_names
  @states.map(&:name).map(&:to_s)
end

#to_diagram_dataObject

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