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
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/finite_machine/state_machine.rb', line 88 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
422 423 424 425 426 427 428 429 430 |
# File 'lib/finite_machine/state_machine.rb', line 422 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
109 110 111 |
# File 'lib/finite_machine/state_machine.rb', line 109 def auto_methods? @auto_methods end |
#can?(*args) ⇒ Boolean
Checks if event can be triggered
204 205 206 207 |
# File 'lib/finite_machine/state_machine.rb', line 204 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
219 220 221 |
# File 'lib/finite_machine/state_machine.rb', line 219 def cannot?(*args, &block) !can?(*args, &block) end |
#current ⇒ String
Get current state
145 146 147 |
# File 'lib/finite_machine/state_machine.rb', line 145 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
187 188 189 |
# File 'lib/finite_machine/state_machine.rb', line 187 def events events_map.events end |
#inspect ⇒ String
String representation of this machine
391 392 393 394 395 396 397 |
# File 'lib/finite_machine/state_machine.rb', line 391 def inspect sync_shared do "<##{self.class}:0x#{object_id.to_s(16)} @states=#{states}, " \ "@events=#{events}, " \ "@transitions=#{events_map.state_transitions}>" end end |
#is?(state) ⇒ Boolean
Check if current state matches provided state
159 160 161 162 163 164 165 |
# File 'lib/finite_machine/state_machine.rb', line 159 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
268 269 270 271 272 273 |
# File 'lib/finite_machine/state_machine.rb', line 268 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
239 240 241 |
# File 'lib/finite_machine/state_machine.rb', line 239 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
175 176 177 |
# File 'lib/finite_machine/state_machine.rb', line 175 def states sync_shared { events_map.states } end |
#subscribe(*observers) ⇒ Object
Subscribe observer for event notifications
136 137 138 |
# File 'lib/finite_machine/state_machine.rb', line 136 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
126 127 128 |
# File 'lib/finite_machine/state_machine.rb', line 126 def target env.target end |
#terminated? ⇒ Boolean
Checks if terminal state has been reached
228 229 230 |
# File 'lib/finite_machine/state_machine.rb', line 228 def terminated? is?(final_state) end |
#transition(event_name, *data, &block) ⇒ Object
364 365 366 367 368 |
# File 'lib/finite_machine/state_machine.rb', line 364 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
351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/finite_machine/state_machine.rb', line 351 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
377 378 379 380 381 382 383 384 |
# File 'lib/finite_machine/state_machine.rb', line 377 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
340 341 342 343 344 |
# File 'lib/finite_machine/state_machine.rb', line 340 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
303 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 |
# File 'lib/finite_machine/state_machine.rb', line 303 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
281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/finite_machine/state_machine.rb', line 281 def try_trigger(event_name) if valid_state?(event_name) yield else exception = InvalidStateError catch_error(exception) || fail(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
251 252 253 254 |
# File 'lib/finite_machine/state_machine.rb', line 251 def valid_state?(event_name) current_states = events_map.states_for(event_name) current_states.any? { |state| state == current || state == ANY_STATE } end |