Class: FiniteMachine::StateMachine
- Inherits:
-
Object
- Object
- FiniteMachine::StateMachine
- Extended by:
- Forwardable
- Includes:
- Catchable, Threadable
- Defined in:
- lib/finite_machine/state_machine.rb
Overview
Base class for state machine
Instance Method Summary collapse
-
#auto_methods? ⇒ Boolean
Check if event methods should be auto generated.
-
#can?(*args) ⇒ Boolean
Checks if event can be triggered.
-
#cannot?(*args, &block) ⇒ Boolean
Checks if event cannot be triggered.
-
#current ⇒ String
Get current state.
-
#dsl ⇒ DSL
private
Machine dsl.
-
#events ⇒ Array[Symbol]
Retireve all event names.
-
#initialize(*args, &block) ⇒ StateMachine
constructor
private
Initialize state machine.
-
#inspect ⇒ String
String representation of this machine.
-
#is?(state) ⇒ Boolean
Check if current state matches provided state.
-
#notify(hook_event_type, event_name, from, *data) ⇒ nil
private
Notify about event all the subscribers.
-
#observer ⇒ Observer
private
The state machine observer.
-
#restore!(state) ⇒ Object
Restore this machine to a known state.
-
#state ⇒ Symbol
private
Current state.
-
#states ⇒ Array[Symbol]
Retrieve all states.
-
#subscribe(*observers) ⇒ Object
Subscribe observer for event notifications.
-
#subscribers ⇒ Subscribers
private
The state machine subscribers.
-
#target ⇒ Object|FiniteMachine::StateMachine
Attach state machine to an object.
-
#terminated? ⇒ Boolean
Checks if terminal state has been reached.
- #transition(event_name, *data, &block) ⇒ Object
-
#transition!(event_name, *data, &block) ⇒ Object
private
Find available state to transition to and transition.
-
#transition_to!(new_state) ⇒ Object
private
Update this state machine state to new one.
-
#trigger(event_name, *data, &block) ⇒ Boolean
Trigger transition event without raising any errors.
-
#trigger!(event_name, *data, &block) ⇒ Boolean
Trigger transition event with data.
-
#try_trigger(event_name) ⇒ Boolean
private
Attempt performing event trigger for valid state.
-
#valid_state?(event_name) ⇒ Boolean
private
Check if state is reachable.
Methods included from Catchable
#catch_error, #handle, included
Constructor Details
#initialize(*args, &block) ⇒ StateMachine
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Initialize state machine
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/finite_machine/state_machine.rb', line 89 def initialize(*args, &block) = args.last.is_a?(::Hash) ? args.pop : {} @initial_state = DEFAULT_STATE @auto_methods = .fetch(:auto_methods, true) @subscribers = Subscribers.new @observer = Observer.new(self) @events_map = EventsMap.new @env = Env.new(self, []) @dsl = DSL.new(self, ) env.target = args.pop unless args.empty? env.aliases << [:alias_target] if [:alias_target] dsl.call(&block) if block_given? trigger_init end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_name, *args, &block) ⇒ self (private)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Forward the message to observer or self
425 426 427 428 429 430 431 432 433 |
# File 'lib/finite_machine/state_machine.rb', line 425 def method_missing(method_name, *args, &block) if observer.respond_to?(method_name.to_sym) observer.public_send(method_name.to_sym, *args, &block) elsif env.aliases.include?(method_name.to_sym) env.send(:target, *args, &block) else super end end |
Instance Method Details
#auto_methods? ⇒ Boolean
Check if event methods should be auto generated
110 111 112 |
# File 'lib/finite_machine/state_machine.rb', line 110 def auto_methods? @auto_methods end |
#can?(*args) ⇒ Boolean
Checks if event can be triggered
205 206 207 208 |
# File 'lib/finite_machine/state_machine.rb', line 205 def can?(*args) event_name = args.shift events_map.can_perform?(event_name, current, *args) end |
#cannot?(*args, &block) ⇒ Boolean
Checks if event cannot be triggered
220 221 222 |
# File 'lib/finite_machine/state_machine.rb', line 220 def cannot?(*args, &block) !can?(*args, &block) end |
#current ⇒ String
Get current state
146 147 148 |
# File 'lib/finite_machine/state_machine.rb', line 146 def current sync_shared { state } end |
#dsl ⇒ DSL
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Machine dsl
48 |
# File 'lib/finite_machine/state_machine.rb', line 48 attr_threadsafe :dsl |
#events ⇒ Array[Symbol]
Retireve all event names
188 189 190 |
# File 'lib/finite_machine/state_machine.rb', line 188 def events events_map.events end |
#inspect ⇒ String
String representation of this machine
392 393 394 395 396 397 398 399 400 |
# File 'lib/finite_machine/state_machine.rb', line 392 def inspect sync_shared do "<##{self.class}:0x#{object_id.to_s(16)} " \ "@current=#{current.inspect} " \ "@states=#{states} " \ "@events=#{events} " \ "@transitions=#{events_map.state_transitions}>" end end |
#is?(state) ⇒ Boolean
Check if current state matches provided state
160 161 162 163 164 165 166 |
# File 'lib/finite_machine/state_machine.rb', line 160 def is?(state) if state.is_a?(Array) state.include? current else state == current end end |
#notify(hook_event_type, event_name, from, *data) ⇒ nil
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Notify about event all the subscribers
269 270 271 272 273 274 |
# File 'lib/finite_machine/state_machine.rb', line 269 def notify(hook_event_type, event_name, from, *data) sync_shared do hook_event = hook_event_type.build(current, event_name, from) subscribers.visit(hook_event, *data) end end |
#observer ⇒ Observer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The state machine observer
55 |
# File 'lib/finite_machine/state_machine.rb', line 55 attr_threadsafe :observer |
#restore!(state) ⇒ Object
Restore this machine to a known state
240 241 242 |
# File 'lib/finite_machine/state_machine.rb', line 240 def restore!(state) sync_exclusive { self.state = state } end |
#state ⇒ Symbol
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Current state
26 |
# File 'lib/finite_machine/state_machine.rb', line 26 attr_threadsafe :state |
#states ⇒ Array[Symbol]
Retrieve all states
176 177 178 |
# File 'lib/finite_machine/state_machine.rb', line 176 def states sync_shared { events_map.states } end |
#subscribe(*observers) ⇒ Object
Subscribe observer for event notifications
137 138 139 |
# File 'lib/finite_machine/state_machine.rb', line 137 def subscribe(*observers) sync_exclusive { subscribers.subscribe(*observers) } end |
#subscribers ⇒ Subscribers
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The state machine subscribers
62 |
# File 'lib/finite_machine/state_machine.rb', line 62 attr_threadsafe :subscribers |
#target ⇒ Object|FiniteMachine::StateMachine
Attach state machine to an object
This allows state machine to initiate events in the context of a particular object
127 128 129 |
# File 'lib/finite_machine/state_machine.rb', line 127 def target env.target end |
#terminated? ⇒ Boolean
Checks if terminal state has been reached
229 230 231 |
# File 'lib/finite_machine/state_machine.rb', line 229 def terminated? is?(terminal_states) end |
#transition(event_name, *data, &block) ⇒ Object
365 366 367 368 369 |
# File 'lib/finite_machine/state_machine.rb', line 365 def transition(event_name, *data, &block) transition!(event_name, *data, &block) rescue InvalidStateError, TransitionError false end |
#transition!(event_name, *data, &block) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Find available state to transition to and transition
352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'lib/finite_machine/state_machine.rb', line 352 def transition!(event_name, *data, &block) from_state = current to_state = events_map.move_to(event_name, from_state, *data) block.call(from_state, to_state) if block if log_transitions Logger.report_transition(event_name, from_state, to_state, *data) end try_trigger(event_name) { transition_to!(to_state) } end |
#transition_to!(new_state) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Update this state machine state to new one
378 379 380 381 382 383 384 385 |
# File 'lib/finite_machine/state_machine.rb', line 378 def transition_to!(new_state) from_state = current self.state = new_state self.initial_state = new_state if from_state == DEFAULT_STATE true rescue Exception => e catch_error(e) || raise_transition_error(e) end |
#trigger(event_name, *data, &block) ⇒ Boolean
Trigger transition event without raising any errors
341 342 343 344 345 |
# File 'lib/finite_machine/state_machine.rb', line 341 def trigger(event_name, *data, &block) trigger!(event_name, *data, &block) rescue InvalidStateError, TransitionError, CallbackError false end |
#trigger!(event_name, *data, &block) ⇒ Boolean
Trigger transition event with data
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/finite_machine/state_machine.rb', line 304 def trigger!(event_name, *data, &block) from = current # Save away current state sync_exclusive do notify HookEvent::Before, event_name, from, *data status = try_trigger(event_name) do if can?(event_name, *data) notify HookEvent::Exit, event_name, from, *data stat = transition!(event_name, *data, &block) notify HookEvent::Transition, event_name, from, *data notify HookEvent::Enter, event_name, from, *data else stat = false end stat end notify HookEvent::After, event_name, from, *data status end rescue Exception => err self.state = from # rollback transition raise err end |
#try_trigger(event_name) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Attempt performing event trigger for valid state
282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/finite_machine/state_machine.rb', line 282 def try_trigger(event_name) if valid_state?(event_name) yield else exception = InvalidStateError catch_error(exception) || raise(exception, "inappropriate current state '#{current}'") false end end |
#valid_state?(event_name) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Check if state is reachable
252 253 254 255 |
# File 'lib/finite_machine/state_machine.rb', line 252 def valid_state?(event_name) current_states = events_map.states_for(event_name) current_states.any? { |state| state == current || state == ANY_STATE } end |