Class: Signoff::Definition

Inherits:
Object
  • Object
show all
Defined in:
lib/signoff/definition.rb

Overview

Immutable-once-validated description of a model’s workflow: its states, the allowed forward transitions, the reject state, authorization guards and lifecycle callbacks. Built by Signoff::DSL and stored on the model class as signoff_definition.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(state_column: :approval_state) ⇒ Definition

Returns a new instance of Definition.



13
14
15
16
17
18
19
20
21
22
# File 'lib/signoff/definition.rb', line 13

def initialize(state_column: :approval_state)
  @states           = []
  @transitions      = {} # from(Symbol) => [to(Symbol), ...]
  @authorizations   = {} # from(Symbol) => callable guard
  @before_callbacks = []
  @after_callbacks  = []
  @initial_state    = nil
  @reject_state     = nil
  @state_column     = state_column.to_sym
end

Instance Attribute Details

#after_callbacksObject (readonly)

Returns the value of attribute after_callbacks.



9
10
11
# File 'lib/signoff/definition.rb', line 9

def after_callbacks
  @after_callbacks
end

#authorizationsObject (readonly)

Returns the value of attribute authorizations.



9
10
11
# File 'lib/signoff/definition.rb', line 9

def authorizations
  @authorizations
end

#before_callbacksObject (readonly)

Returns the value of attribute before_callbacks.



9
10
11
# File 'lib/signoff/definition.rb', line 9

def before_callbacks
  @before_callbacks
end

#initial_stateObject

— queries ———————————————————



54
55
56
# File 'lib/signoff/definition.rb', line 54

def initial_state
  @initial_state || @states.first
end

#reject_stateObject

Returns the value of attribute reject_state.



50
51
52
# File 'lib/signoff/definition.rb', line 50

def reject_state
  @reject_state
end

#state_columnObject (readonly)

Returns the value of attribute state_column.



9
10
11
# File 'lib/signoff/definition.rb', line 9

def state_column
  @state_column
end

#statesObject (readonly)

Returns the value of attribute states.



9
10
11
# File 'lib/signoff/definition.rb', line 9

def states
  @states
end

#transitionsObject (readonly)

Returns the value of attribute transitions.



9
10
11
# File 'lib/signoff/definition.rb', line 9

def transitions
  @transitions
end

Instance Method Details

#add_authorization(from, guard) ⇒ Object



42
43
44
# File 'lib/signoff/definition.rb', line 42

def add_authorization(from, guard)
  @authorizations[from.to_sym] = guard
end

#add_state(name) ⇒ Object

— builders (called by the DSL) ————————————

Raises:



26
27
28
29
30
31
# File 'lib/signoff/definition.rb', line 26

def add_state(name)
  name = name.to_sym
  raise DefinitionError, "duplicate state #{name.inspect}" if @states.include?(name)

  @states << name
end

#add_transition(from, to) ⇒ Object



33
34
35
36
37
38
39
40
# File 'lib/signoff/definition.rb', line 33

def add_transition(from, to)
  from = from.to_sym
  Array(to).each do |target|
    target = target.to_sym
    @transitions[from] ||= []
    @transitions[from] << target unless @transitions[from].include?(target)
  end
end

#approval_terminal_statesObject

Terminal states that represent successful completion (everything that is terminal except the reject state).



70
71
72
# File 'lib/signoff/definition.rb', line 70

def approval_terminal_states
  terminal_states - [reject_state].compact
end

#forward_targets(state) ⇒ Object

Targets reachable from state via a declared forward transition.



59
60
61
# File 'lib/signoff/definition.rb', line 59

def forward_targets(state)
  @transitions[state.to_sym] || []
end

#guard_for(state) ⇒ Object



74
75
76
# File 'lib/signoff/definition.rb', line 74

def guard_for(state)
  @authorizations[state.to_sym]
end

#next_state(from, to: nil) ⇒ Object

Resolve the next state when advancing forward from from. to may be supplied to disambiguate when several forward transitions exist.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/signoff/definition.rb', line 80

def next_state(from, to: nil)
  from = from.to_sym
  targets = forward_targets(from)

  if to
    to = to.to_sym
    return to if targets.include?(to)

    raise InvalidTransitionError,
          "no transition declared from #{from.inspect} to #{to.inspect}"
  end

  case targets.size
  when 0
    raise InvalidTransitionError,
          "no forward transition declared from #{from.inspect}"
  when 1
    targets.first
  else
    raise InvalidTransitionError,
          "ambiguous transition from #{from.inspect}; pass to: " \
          "(one of #{targets.inspect})"
  end
end

#terminal_statesObject

States with no outgoing forward transition.



64
65
66
# File 'lib/signoff/definition.rb', line 64

def terminal_states
  @states.reject { |s| forward_targets(s).any? }
end

#validate!Object

— validation ——————————————————

Raises:



107
108
109
110
111
112
113
114
115
# File 'lib/signoff/definition.rb', line 107

def validate!
  raise DefinitionError, "no states have been defined" if @states.empty?

  validate_initial_state!
  validate_transitions!
  validate_reject_state!
  validate_authorizations!
  self
end