geekq / workflow

Ruby finite-state-machine-inspired API for modeling workflow
MIT License
1.75k stars 207 forks source link

Trigger event within transition event handler possible? #155

Closed duffyjp closed 2 years ago

duffyjp commented 9 years ago

From within a transition event handler method, I'd like to branch to a "failed" state if something goes wrong. I can't seem to do that, and I'm wondering if I'm overlooking something?

This is the basic setup I'm looking for:

workflow do
  state :new do
    event :parse,       transitions_to: :parsed  
    event :parse_fail,  transitions_to: :parse_failed
  end
  state :parse_failed 
  state :parsed
end

Then within my parse method:

def parse
  # ... does a bunch of work looking for errors.
  if errors.blank?
    return true  # transition to parsed  (this part is fine)
  else
    self.parse_fail!   # transition to parse_failed  (doesn't work)
    return false
  end
end

I've tried using halt! which okay, throws an exception and prevents the transition to parsed, but I need to also transition to parse_failed. I think I could maybe hack it together with on_error but it seems hacky to have to check which state I was in during the failure and trigger an event again there, outside the event handler.

If this is possible I'd suggest an addition to the README, which I'd be happy to write once I know how I should be doing this.

Thanks.

duffyjp commented 9 years ago

It it helps demonstrate what I'm trying to do, this is the on_error I ended up with to do what I want.

on_error do |error, from, to, event, *args|   
  case [from, event]                          
    when [:new, :parse] then self.parse_fail! 
    else raise error                          
  end                                         
end        

I just had the parse method throw an exception instead.

if errors.blank?                           
  true                                     
else                                       
  halt! "Parse Failed"                     
end                                        
ashrocket commented 8 years ago

I succeeded in doing this by calling the new transition event, followed by a halt. So for instance if your parse_fail! event is the one that will transtion to 'failed' state you can call:

 if errors.blank?                           
    #doing nothing here, will cause transition to the expected original state 
    true                                     
 else                                       
    #This will cause transition to the parse failed state
    parse_fail!
    # you need this to make sure that when the method returns, it doesn't go back to transitioning to the     originally expected state.
    halt! "Parse Failed"                     
 end         
duffyjp commented 8 years ago

@ashrocket That makes sense, you call another event (callable from the still current :new state) which succeeds, updates the state, then you abandon the original event with a halt! Brilliant.

Do you have a custom on_error method? Your solution is exactly what I wanted to do, I guess I just never thought to call halt! there instead of returning false.

jufemaiz commented 7 years ago

@ashrocket thank you! I was trying to get this same solution happening :)