Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial state from target class #1

Closed
geoffroymontel opened this issue Feb 24, 2014 · 6 comments
Closed

initial state from target class #1

geoffroymontel opened this issue Feb 24, 2014 · 6 comments

Comments

@geoffroymontel
Copy link

Hello

I like your coding style and the separation of the state machine and the target class.

I'd like the state machine to set the initial value according to a value from the target class.

Something like

require 'finite_machine'

class Video
  attr_accessor :state

  def initialize
    self.state = 'pending'
  end

  def state_machine
    @state_machine ||= FiniteMachine.define do
      initial self.state

      target self

      events do
        event :enqueue_generate, :pending => :waiting_for_generation
        event :generate, :waiting_for_generation => :generating
        event :finish_generation, :generating => :waiting_for_upload
        event :enqueue_youtube_upload, :waiting_for_upload => :uploading
        event :finish_upload, :uploading => :uploaded
      end

      callbacks do
        on_enter do |obj, event|
          puts "on_enter from #{event.from} to #{event.to}"
        end

        on_exit do |obj, event|
          puts "on_exit from #{event.from} to #{event.to}"
        end

        on_transition do |obj, event|
          puts "on_transition from #{event.from} to #{event.to}"
        end
      end
    end
  end
end

v = Video.new
v.state
# "pending"
v.state_machine.generate
# FiniteMachine::TransitionError: inappropriate current state 'none'

How can I sync the state of the machine with the state of a variable in my target class ?
Basically I'd like to save the state of the state machine in an ActiveRecord class.

Thanks !

Geoffroy

@piotrmurach
Copy link
Owner

Hi Geoffroy,

Thanks for using the library! The target currently has minial scope and only works for guarding conditions. I like your approach of using the initial to pull the state from the instance! I will need to implement this, probably this would get evaluated if you pass additional parameter? The idea is to pass the target into the callbacks as well(need to implement this) so that it is up to you to define what happens on the state change. If you can wait I will be working on it in next few days. Any suggestions please send them my way!

@piotrmurach
Copy link
Owner

For the time being to make your case work do

  def state_machine
     context = self
     @state_machine ||= FiniteMachine.define do
        initial context.state

        target context

        ....

        callbacks do
          on_enter do |event|
            context.state = event.to
            puts "on_enter from #{event.from} to #{event.to}"
          end
          .....
        end
    end

Bear in mind that callbacks only take event object at the minute. In future you will not need to pass context as the callback content will be evaluated on the target object. At the minute closure on context will solve the problem. Of course, in your callback you may wish to call some other AR method to persist the state etc...

v = Video.new
v.state_machine.enqueue_generate # this will take the machine from 'pending' to 'waiting_for_generation' state rather than throw error

I have updated the example. Let me know how this works for you ?

@geoffroymontel
Copy link
Author

Hi

Thanks for your quick answer and for your solution, which works, but I got duplicate callbacks plus unexpected callbacks from state :none to state :pending

require 'finite_machine'

class Video
  attr_accessor :state

  def initialize
    self.state = :pending
  end

  def state_machine
    context = self
    @state_machine ||= FiniteMachine.define do
      initial context.state

      target context

      events do
        event :enqueue_generate, :pending => :waiting_for_generation
        event :generate, :waiting_for_generation => :generating
        event :finish_generation, :generating => :waiting_for_upload
        event :enqueue_youtube_upload, :waiting_for_upload => :uploading
        event :finish_upload, :uploading => :uploaded
      end

      callbacks do
        on_enter do |event|
          puts "on_enter from #{event.from} to #{event.to}"
        end

        on_exit do |event|
          puts "on_exit from #{event.from} to #{event.to}"
        end

        on_transition do |event|
          puts "on_transition from #{event.from} to #{event.to}"
        end
      end
    end
  end
end

v = Video.new
v.state
# :pending
v.state_machine.enqueue_generate
# on_exit from none to pending
# on_enter from none to pending
# on_transition from none to pending
# on_transition from none to pending
# on_enter from none to pending
# on_exit from none to pending
# on_exit from pending to waiting_for_generation
# on_enter from pending to waiting_for_generation
# on_transition from pending to waiting_for_generation
# on_transition from pending to waiting_for_generation
# on_enter from pending to waiting_for_generation
# on_exit from pending to waiting_for_generation
# => 1

Best regards

Geoffroy

@piotrmurach
Copy link
Owner

The duplicate callbacks are due to the fact that on_enter listens for state and event changes. If you want to just listen for state changes I have already added on_enter_state in master but will release the gem later on today ( see callbacks section ) You can also listen for specific state changes as well on_enter_generating etc...

My guess would be that the initial context.state sets nothing thus the lib defaults to none state, can you for the time being pass the :pending directly? I will be looking into this as well this evening.

@geoffroymontel
Copy link
Author

Crystal clear ! Thanks Peter ! I can wait, no hurry

@piotrmurach
Copy link
Owner

Release v0.2.0 which fixes issues around the target helper. Please see integration section for udpated version. Essentially, when you provide target then any method called inside a callback will lookup the target object first and then the machine itself etc... Use on_enter_state if state changes are your only concern and you want to avoid duplicate callbacks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants