module Celluloid::FSM

Simple finite state machines with integrated Celluloid timeout support Inspired by Erlang's gen_fsm (www.erlang.org/doc/man/gen_fsm.html)

Basic usage:

class MyMachine
  include Celluloid::FSM # NOTE: this does NOT pull in the Celluloid module
end

Inside an actor:

#
machine = MyMachine.new(current_actor)

Constants

DEFAULT_STATE

Attributes

actor[R]
state[R]

Obtain the current state of the FSM

Public Class Methods

included(klass) click to toggle source

Included hook to extend class methods

# File lib/celluloid/fsm.rb, line 23
def self.included(klass)
  klass.send :extend, ClassMethods
end
new(actor = nil) click to toggle source

Be kind and call super if you must redefine initialize

# File lib/celluloid/fsm.rb, line 65
def initialize(actor = nil)
  @state = self.class.default_state
  @delayed_transition = nil
  @actor = actor
  @actor ||= Celluloid.current_actor if Celluloid.actor?
end

Public Instance Methods

actor=(actor)
Alias for: attach
attach(actor) click to toggle source

Attach this FSM to an actor. This allows FSMs to wait for and initiate events in the context of a particular actor

# File lib/celluloid/fsm.rb, line 77
def attach(actor)
  @actor = actor
end
Also aliased as: actor=
transition(state_name, options = {}) click to toggle source

Transition to another state Options:

  • delay: don't transition immediately, wait the given number of seconds.

    This will return a Celluloid::Timer object you can use to
    cancel the pending state transition.

Note: making additional state transitions will cancel delayed transitions

# File lib/celluloid/fsm.rb, line 89
def transition(state_name, options = {})
  new_state = validate_and_sanitize_new_state(state_name)
  return unless new_state

  if handle_delayed_transitions(new_state, options[:delay])
    return @delayed_transition
  end

  transition_with_callbacks!(new_state)
end
transition!(state_name) click to toggle source

Immediate state transition with no sanity checks, or callbacks. “Dangerous!”

# File lib/celluloid/fsm.rb, line 101
def transition!(state_name)
  @state = state_name
end

Protected Instance Methods

current_state() click to toggle source
# File lib/celluloid/fsm.rb, line 140
def current_state
  states[@state]
end
current_state_name() click to toggle source
# File lib/celluloid/fsm.rb, line 144
def current_state_name
  current_state && current_state.name || ""
end
default_state() click to toggle source
# File lib/celluloid/fsm.rb, line 136
def default_state
  self.class.default_state
end
handle_delayed_transitions(new_state, delay) click to toggle source
# File lib/celluloid/fsm.rb, line 148
def handle_delayed_transitions(new_state, delay)
  if delay
    fail UnattachedError, "can't delay unless attached" unless @actor
    @delayed_transition.cancel if @delayed_transition

    @delayed_transition = @actor.after(delay) do
      transition_with_callbacks!(new_state)
    end

    return @delayed_transition
  end

  return unless defined?(@delayed_transition) && @delayed_transition
  @delayed_transition.cancel
  @delayed_transition = nil
end
states() click to toggle source
# File lib/celluloid/fsm.rb, line 132
def states
  self.class.states
end
transition_with_callbacks!(state_name) click to toggle source
# File lib/celluloid/fsm.rb, line 127
def transition_with_callbacks!(state_name)
  transition! state_name.name
  state_name.call(self)
end
validate_and_sanitize_new_state(state_name) click to toggle source
# File lib/celluloid/fsm.rb, line 107
def validate_and_sanitize_new_state(state_name)
  state_name = state_name.to_sym

  return if current_state_name == state_name

  if current_state and !current_state.valid_transition? state_name
    valid = current_state.transitions.map(&:to_s).join(", ")
    fail ArgumentError, "#{self.class} can't change state from '#{@state}' to '#{state_name}', only to: #{valid}"
  end

  new_state = states[state_name]

  unless new_state
    return if state_name == default_state
    fail ArgumentError, "invalid state for #{self.class}: #{state_name}"
  end

  new_state
end