geekq / workflow

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

Workflow::NoTransitionAllowed error, but event is defined for the state #226

Closed HashtagONUD closed 3 years ago

HashtagONUD commented 3 years ago

I'm using ruby 2.6.3p62 (2019-04-16 revision 67580) with Rails 6 & these gems:

gem 'workflow' # state machine
gem 'workflow-activerecord', '>= 4.1', '< 6.0'

In my Transporter model, I want to make sure that a condition is true before the state is allowed to change:

    state :tsp_isnow_0_state do
      event :tsp_wasasked_1_event, transitions_to: :tsp_wasasked_1_state,
                                   if: :both_locations_exist?

  def both_locations_exist?
    return true if assignment.animal.locations_exist?
    false
  end

I'm doing something perhaps a little unusual. My states and events are defined in the model (that's normal) but the code that fires the events is dynamically built by reading the current state. So for the above code, if the current state is "tsp_isnow_0_state", it makes a string called "tsp_wasasked_1_event" and sends that to the model object:

  def prompt
    next_workflow_event = WorkflowState.construct_next_prompting_event_based_on(stateful_role.workflow_state)
    response = stateful_role.send(next_workflow_event.to_sym)
    response[:notice] = 'The volunteer was prompted.' if response[:success]
    response
  end

The problem is when the prompt method runs, I get this error:

Workflow::NoTransitionAllowed in AssignmentsController#prompt
There is no event tsp_wasasked_1_event defined for the tsp_isnow_0_state state

...but this is wrong because the tsp_wasasked_1_event event is defined in the model workflow (above). In my rails console, I can see the starting state is there:

2.6.3 :007 > pp Transporter.workflow_spec.states.keys
[:tsp_isnow_0_state,
 :tsp_wasasked_1_state,
 :tsp_isnow_1_state,
 :tsp_wasasked_2_state,
 :tsp_wasasked_3_state,
 :tsp_isnow_finished_state]

And I can see the next state in the spec is defined:

2.6.3 :010 > pp Transporter.workflow_spec.states[:tsp_isnow_0_state]
[snip]
 @spec=
  #<Workflow::Specification:0x00007fd8d94cd038
   @initial_state=#<Workflow::State:0x00007fd8d94cce30 ...>,
   @meta={},
   @scoped_state=
    #<Workflow::State:0x00007fd8dbbb9570
     @events={},
     @meta={},
     @name=:tsp_isnow_finished_state,
     @spec=#<Workflow::Specification:0x00007fd8d94cd038 ...>>,
   @states=
    {:tsp_isnow_0_state=>#<Workflow::State:0x00007fd8d94cce30 ...>,
     :tsp_wasasked_1_state=>
      #<Workflow::State:0x00007fd8d94cc6b0
       @events=
        {:tsp_wasasked_1_event=>
          [#<Workflow::Event:0x00007fd8dbbaf3e0
            @action=nil,
            @condition=nil,
            @meta={},
            @name=:tsp_wasasked_1_event,
            @transitions_to=:tsp_wasasked_1_state>],
.
.
.

Any idea why the event isn't being found in the workflow_spec?

Thanks for any assistance!

HashtagONUD commented 3 years ago

Discovered that if I comment out the if symbol, the workflow works as normal:

    state :tsp_isnow_0_state do
      event :tsp_wasasked_1_event, transitions_to: :tsp_wasasked_1_state # , if: :both_locations_exist?
    end

It also works when I keep the if condition, but return true from theboth_locations_exist? method. If the method returns false, I get the same error as before.

geekq commented 3 years ago

To allow/prevent transition during the runtime depending on some condition, please read the documentation for "Conditional event transition" https://github.com/geekq/workflow#conditional-event-transitions You need an :if parameter (will call the function to decide to allow or prevent the transition) and not the if modifier you use, when defining the workflow (this just prevents the event definition from the beginning).