Class: NxtStateMachine::StateMachine

Inherits:
Object
  • Object
show all
Defined in:
lib/nxt_state_machine/state_machine.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, class_context, event_registry, **opts) ⇒ StateMachine

Returns a new instance of StateMachine.



3
4
5
6
7
8
9
10
11
12
13
14
15
16
# File 'lib/nxt_state_machine/state_machine.rb', line 3

def initialize(name, class_context, event_registry, **opts)
  @name = name
  @class_context = class_context
  @options = opts

  @states = NxtStateMachine::StateRegistry.new
  @transitions = []
  @events = event_registry
  @callbacks = CallbackRegistry.new
  @error_callback_registry = ErrorCallbackRegistry.new
  @defuse_registry = DefuseRegistry.new

  @initial_state = nil
end

Instance Attribute Details

#callbacksObject (readonly)

Returns the value of attribute callbacks.



18
19
20
# File 'lib/nxt_state_machine/state_machine.rb', line 18

def callbacks
  @callbacks
end

#class_contextObject (readonly)

Returns the value of attribute class_context.



18
19
20
# File 'lib/nxt_state_machine/state_machine.rb', line 18

def class_context
  @class_context
end

#defuse_registryObject (readonly)

Returns the value of attribute defuse_registry.



18
19
20
# File 'lib/nxt_state_machine/state_machine.rb', line 18

def defuse_registry
  @defuse_registry
end

#error_callback_registryObject (readonly)

Returns the value of attribute error_callback_registry.



18
19
20
# File 'lib/nxt_state_machine/state_machine.rb', line 18

def error_callback_registry
  @error_callback_registry
end

#eventsObject (readonly)

Returns the value of attribute events.



18
19
20
# File 'lib/nxt_state_machine/state_machine.rb', line 18

def events
  @events
end

#initial_stateObject

Returns the value of attribute initial_state.



19
20
21
# File 'lib/nxt_state_machine/state_machine.rb', line 19

def initial_state
  @initial_state
end

#nameObject (readonly)

Returns the value of attribute name.



18
19
20
# File 'lib/nxt_state_machine/state_machine.rb', line 18

def name
  @name
end

#optionsObject (readonly)

Returns the value of attribute options.



18
19
20
# File 'lib/nxt_state_machine/state_machine.rb', line 18

def options
  @options
end

Instance Method Details

#after_transition(from:, to:, run: nil, &block) ⇒ Object



124
125
126
# File 'lib/nxt_state_machine/state_machine.rb', line 124

def after_transition(from:, to:, run: nil, &block)
  callbacks.register(from, to, :after, run, block)
end

#all_states_except(*excluded) ⇒ Object



87
88
89
# File 'lib/nxt_state_machine/state_machine.rb', line 87

def all_states_except(*excluded)
  all_states - excluded
end

#all_transitions_from_to(from: all_states, to: all_states) ⇒ Object



66
67
68
# File 'lib/nxt_state_machine/state_machine.rb', line 66

def all_transitions_from_to(from: all_states, to: all_states)
  transitions.select { |transition| transition.transitions_from_to?(from, to) }
end

#any_stateObject Also known as: all_states



74
75
76
# File 'lib/nxt_state_machine/state_machine.rb', line 74

def any_state
  states.values.map(&:enum)
end

#around_transition(from:, to:, run: nil, &block) ⇒ Object



144
145
146
# File 'lib/nxt_state_machine/state_machine.rb', line 144

def around_transition(from:, to:, run: nil, &block)
  callbacks.register(from, to, :around, run, block)
end

#before_transition(from:, to:, run: nil, &block) ⇒ Object



120
121
122
# File 'lib/nxt_state_machine/state_machine.rb', line 120

def before_transition(from:, to:, run: nil, &block)
  callbacks.register(from, to, :before, run, block)
end

#can_transition!(event, from) ⇒ Object



115
116
117
118
# File 'lib/nxt_state_machine/state_machine.rb', line 115

def can_transition!(event, from)
  return true if can_transition?(event, from)
  raise NxtStateMachine::Errors::TransitionNotDefined, "No transition :#{event} for state :#{from} defined"
end

#can_transition?(event_name, from) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
113
# File 'lib/nxt_state_machine/state_machine.rb', line 110

def can_transition?(event_name, from)
  event = events.resolve!(event_name)
  event && event.event_transitions.key?(from)
end

#configure(&block) ⇒ Object



148
149
150
151
# File 'lib/nxt_state_machine/state_machine.rb', line 148

def configure(&block)
  instance_exec(&block)
  self
end

#current_state_name(context) ⇒ Object



181
182
183
# File 'lib/nxt_state_machine/state_machine.rb', line 181

def current_state_name(context)
  Callable.new(get_state_with).bind(context).call(target(context))
end

#defuse(errors = [], from:, to:) ⇒ Object



132
133
134
# File 'lib/nxt_state_machine/state_machine.rb', line 132

def defuse(errors = [], from:, to:)
  defuse_registry.register(from, to, errors)
end

#event(name, **options, &block) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/nxt_state_machine/state_machine.rb', line 91

def event(name, **options, &block)
  name = name.to_sym
  event = Event.new(name, self, **options, &block)
  events.register(name, event)

  Event::Names.set_state_method_map(name).each do |event_name, set_state_method|
    class_context.define_method event_name do |*args, **opts|
      event.state_machine.can_transition!(name, event.state_machine.current_state_name(self))
      transition = event.event_transitions.resolve!(event.state_machine.current_state_name(self))
      # Transition is build every time and thus should be thread safe!
      transition.build_transition(event, self, set_state_method, *args, **opts)
    end
  end

  class_context.define_method "can_#{name}?" do
    event.state_machine.can_transition?(name, event.state_machine.current_state_name(self))
  end
end

#event_methodsObject



78
79
80
81
82
83
# File 'lib/nxt_state_machine/state_machine.rb', line 78

def event_methods
  event_names = events.keys
  event_names_with_bang = event_names.map { |e| e + '!' }

  (event_names + event_names_with_bang).map(&:to_sym)
end

#events_for_state(state) ⇒ Object



70
71
72
# File 'lib/nxt_state_machine/state_machine.rb', line 70

def events_for_state(state)
  events.values.select { |event| event.transitions_from?(state) }
end

#find_error_callback(error, transition) ⇒ Object



166
167
168
# File 'lib/nxt_state_machine/state_machine.rb', line 166

def find_error_callback(error, transition)
  error_callback_registry.resolve(error, transition)
end

#get_state_with(method = nil, &block) ⇒ Object



21
22
23
# File 'lib/nxt_state_machine/state_machine.rb', line 21

def get_state_with(method = nil, &block)
  @get_state_with ||= (method || block) || raise_missing_configuration_error(:get_state_with)
end

#on_error(error = StandardError, from:, to:, run: nil, &block) ⇒ Object



136
137
138
# File 'lib/nxt_state_machine/state_machine.rb', line 136

def on_error(error = StandardError, from:, to:, run: nil, &block)
  error_callback_registry.register(from, to, error, run, block)
end

#on_error!(error = StandardError, from:, to:, run: nil, &block) ⇒ Object



140
141
142
# File 'lib/nxt_state_machine/state_machine.rb', line 140

def on_error!(error = StandardError, from:, to:, run: nil, &block)
  error_callback_registry.register!(from, to, error, run, block)
end

#on_success(from:, to:, run: nil, &block) ⇒ Object



128
129
130
# File 'lib/nxt_state_machine/state_machine.rb', line 128

def on_success(from:, to:, run: nil, &block)
  callbacks.register(from, to, :success, run, block)
end

#run_after_callbacks(transition, context) ⇒ Object



158
159
160
# File 'lib/nxt_state_machine/state_machine.rb', line 158

def run_after_callbacks(transition, context)
  run_callbacks(transition, :after, context)
end

#run_before_callbacks(transition, context) ⇒ Object

TODO: Everything that require context should live in some sort of proxy



154
155
156
# File 'lib/nxt_state_machine/state_machine.rb', line 154

def run_before_callbacks(transition, context)
  run_callbacks(transition, :before, context)
end

#run_callbacks(transition, kind, context) ⇒ Object



170
171
172
173
174
175
176
177
178
179
# File 'lib/nxt_state_machine/state_machine.rb', line 170

def run_callbacks(transition, kind, context)
  current_callbacks = callbacks.resolve!(transition, kind)
  return unless current_callbacks.any?

  current_callbacks.each do |callback|
    Callable.new(callback).bind(context).call(transition)
  end

  true
end

#run_success_callbacks(transition, context) ⇒ Object



162
163
164
# File 'lib/nxt_state_machine/state_machine.rb', line 162

def run_success_callbacks(transition, context)
  run_callbacks(transition, :success, context)
end

#set_state_with(method = nil, &block) ⇒ Object



25
26
27
# File 'lib/nxt_state_machine/state_machine.rb', line 25

def set_state_with(method = nil, &block)
  @set_state_with ||= (method || block) || raise_missing_configuration_error(:set_state_with)
end

#set_state_with!(method = nil, &block) ⇒ Object



29
30
31
# File 'lib/nxt_state_machine/state_machine.rb', line 29

def set_state_with!(method = nil, &block)
  @set_state_with_bang ||= (method || block) || raise_missing_configuration_error(:set_state_with!)
end

#state(*names, **opts, &block) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/nxt_state_machine/state_machine.rb', line 33

def state(*names, **opts, &block)
  defaults = { initial: false }
  opts.reverse_merge!(defaults)
  machine = self

  Array(names).map do |name|
    if opts.fetch(:initial) && initial_state.present?
      raise NxtStateMachine::Errors::InitialStateAlreadyDefined, ":#{initial_state.enum} was already defined as the initial state"
    else
      state = new_state_class(&block).new(name, self, **opts.reverse_merge(index: states.size))
      states.register(name, state)
      self.initial_state = state if opts.fetch(:initial)

      class_context.define_method "#{name}?" do
        machine.current_state_name(self) == name
      end

      state
    end
  end
end

#states(*names, **opts, &block) ⇒ Object



55
56
57
58
59
60
# File 'lib/nxt_state_machine/state_machine.rb', line 55

def states(*names, **opts, &block)
  # method overloading in ruby ;-)
  return @states unless names.any?

  state(*names, **opts, &block)
end

#target(context) ⇒ Object



185
186
187
188
# File 'lib/nxt_state_machine/state_machine.rb', line 185

def target(context)
  @target_method ||= (options[:target] || :itself)
  context.send(@target_method)
end

#transitionsObject



62
63
64
# File 'lib/nxt_state_machine/state_machine.rb', line 62

def transitions
  @transitions ||= events.values.flat_map(&:event_transitions)
end