Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic State Machine in Ruby? Do State Machines Have to be Classes?

Question is, are state machines always defined statically (on classes)? Or is there a way for me to have it so each instance of the class with has it's own set of states?

I'm checking out Stonepath for implementing a Task Engine. I don't really see the distinction between "states" and "tasks" in there, so I'm thinking I could just map a Task directly to a state. This would allow me to be able to define task-lists (or workflows) dynamically, without having to do things like:

aasm_event :evaluate do
  transitions :to => :in_evaluation, :from => :pending
end

aasm_event :accept do
  transitions :to => :accepted, :from => :pending
end

aasm_event :reject do
  transitions :to => :rejected, :from => :pending
end

Instead, a WorkItem (the main workflow/task manager model), would just have many tasks. Then the tasks would work like states, so I could do something like this:

aasm_initial_state :initial

tasks.each do |task|
  aasm_state task.name.to_sym
end

previous_state = nil
tasks.each do |tasks|
  aasm_event task.name.to_sym do
    transitions :to => "#{task.name}_phase".to_sym, :from => previous_state ? "#{task.name}_phase" : "initial"
  end
  previous_state = state
end

However, I can't do that with the aasm gem because those methods (aasm_state and aasm_event) are class methods, so every instance of the class with that state machine has the same states. I want it so a "WorkItem" or "TaskList" dynmically creates a sequence of states and transitions based on the tasks it has.

This would allow me to dynamically define workflows and just have states map to tasks.

Are state machines ever used like this? It seems that this ruby workflow gem is similar to what I'm describing.

Update: I can see doing something like the following, but it seems sort of hackish:

@implementation_state_machine = Class::new do
  include AASM
  aasm_initial_state :initial

  tasks.each { |state| aasm_state :"#{task.name}"}
  # ...
end

... where a property on my model would be implementation_state_machine. I'd have to override method_missing to delegate state-related methods (accepted_phase?) to the implementation anonymous class.

like image 796
Lance Avatar asked Nov 05 '22 15:11

Lance


1 Answers

Yeah, that does seem very hacky and quite messy. I wrote a new gem recently that allows you to use dynamic 'to' transitions with a decision setting.

So instead of building your events and transitions dynamically, would be it be possible to map them out first, and use the decide setting to allow the transition decide which new state to enter? You can also wrap your from transition in an array so you wouldn't need to do :from => previous_state ? "#{task.name}_phase" : "initial", you could just do :from => [ :cool_task_phase, :initial ]

I find that setting out your transitions and events out first, allows you to get a greater picture on what your model is doing.

Check it out at http://github.com/ryanza/stateflow

Hopefully you can find some use out of this.

like image 112
Ryan Avatar answered Nov 15 '22 02:11

Ryan