peter-murach/finite_machine restore persisted state

80 views Asked by At

Using finite machine gem for Ruby from Piotr Murach.

Would like to restore the state machine with a persisted (stored) state. The documentation teaches the initial state, and restoring state from and ActiveRecord model.

All I've been able to find is the DSL that lets me define an initial state, or define an event that transitions to an initial state. Both require me to define the initial state at coding time.

fm = FiniteMachine.define do
  initial :red

or

fm = FiniteMachine.define do
  events {
    event :start, :none   => :red

In practice, I'm defining a "standalone" along the lines of,

class Engine < FiniteMachine::Definition
  initial :neutral

What I'd like is to define the initial state in the initializer for that class, something like:

class Engine < FiniteMachine::Definition
     def initialize(car, state)
       initial state
       target car
     end

However that does not work. I get :none as the current state after initialization.

Found the restore! method and the section in the doc about persisting state with ActiveRecord. Tried something along the lines of:

class Engine < FiniteMachine::Definition
     def initialize(car, state)
       restore! state
       target car
     end

However the constructor returns a class FiniteMachine::StateMachine when called new on it. The new method takes any number of arguments, and any initialize method of the class is never called. It returns a different class.

Here is output from the program that follows:

GSM class is FiniteMachine::StateMachine
GSM current state is red
.gems/gems/finite_machine-0.10.1/lib/finite_machine/state_machine.rb:259:in `valid_state?': inappropriate current state 'red' (FiniteMachine::InvalidStateError)

The program:

require 'finite_machine'

class GenericStateMachine < FiniteMachine::Definition
  initial :red

  def initialize(light)
    puts "INITIALIZER WITH #{light}"
    super
    restore! light.state
    target light
  end

  events {
    event :start, :red => :green
    event :stop, :green => :red
  }

  callbacks {
    on_enter { |event| target.state = event.to }
  }
end

class Light
  attr_accessor :state
  def initialize
    state = 'green'
  end
  def to_s
    "Light in state #{state}"
  end
end

light = Light.new
gsm = GenericStateMachine.new(light)
puts "GSM class is #{gsm.class.to_s}"
puts "GSM current state is #{gsm.current}"
gsm.stop
puts "GSM state after stop is #{gsm.current}"
puts "Light state after stop is #{light.state}"
1

There are 1 answers

0
Douglas Lovell On

What works better is to make a state machine factory method, that uses the DSL via FiniteMachine.define

Here is output from the program that follows:

CREATING MACHINE with Light in state green
GSM class is FiniteMachine::StateMachine
GSM current state is green
GSM state after stop is red
Light state after stop is red

The program:

require 'finite_machine'

class LightMachineFactory
  def self.create_machine(light)
    FiniteMachine.define do
      puts "CREATING MACHINE with #{light}"
      initial light.state
      target light

      events {
        event :start, :red => :green
        event :stop, :green => :red
      }

      callbacks {
        on_enter { |event| target.state = event.to }
      }
    end
  end
end

class Light
  attr_accessor :state
  def initialize
    @state = 'green'
  end
  def to_s
    "Light in state #{state}"
  end
  def state
    @state.to_sym
  end
end

light = Light.new
gsm = LightMachineFactory.create_machine(light)
puts "GSM class is #{gsm.class.to_s}"
puts "GSM current state is #{gsm.current}"
gsm.stop
puts "GSM state after stop is #{gsm.current}"
puts "Light state after stop is #{light.state}"

This content is shared from (my) closed issue in the Github source repository for the Gem. (This is a better forum for what is more a usage question than an issue.)