piotrmurach / finite_machine

A minimal finite state machine with a straightforward syntax.
https://piotrmurach.github.io/finite_machine/
MIT License
808 stars 38 forks source link

Persistence #38

Closed wbreeze closed 9 years ago

wbreeze commented 9 years ago

Pardon me for making this an issue. It's really a question.

I'm having trouble persisting the state. More specifically, I'd like to instantiate the state machine with the prior saved state.

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
fm = FiniteMachine.define do
  events {
    event :start, :none   => :green

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. Should it? I get :none as the current state after initialization. Still reading the code.

wbreeze commented 9 years ago

Found the restore! method and the section in the doc about persisting state with ActiveRecord.

The constructor for the class inheriting from FiniteMachine::Definition, for example, Engine

class Engine < FiniteMachine::Definition

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

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)
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}"
wbreeze commented 9 years ago

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

https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)

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
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}"
wbreeze commented 9 years ago

Reposted at stackoverflow as http://stackoverflow.com/questions/31040725/peter-murach-finite-machine-restore-persisted-state