state-machines / state_machines

Adds support for creating state machines for attributes on any Ruby class
https://github.com/state-machines/state_machines
MIT License
814 stars 91 forks source link

How to specify conditional state changes in dynamic state definitions #59

Closed tonyvince closed 3 years ago

tonyvince commented 7 years ago

I had a previous state machine like the following

state_machine :state, :initial => :awaiting_quote, use_transactions: false do
  event :submit_quote do
    transition :awaiting_quote => :awaiting_quote_acceptance, 
              if: :line_item_details_complete?
  end
  event :accept_quote do
    transition :awaiting_quote_acceptance => :awaiting_final_invoice
  end
  event :decline_quote do
    transition :awaiting_quote_acceptance => :awaiting_quote
  end
  event :submit_final_invoice do
    transition :awaiting_final_invoice => :awaiting_order_acceptance,
              if: :attachment?
  end
  event :accept_order do
    transition :awaiting_order_acceptance => :order_placed
  end
end

as you can see 2 of the state changes are dependent on line_item_details_complete? and attachment? methods in my model

Now I am trying to implement the same state machine using dynamic state definitions

  # Replace this with an external source (like a db)
  def transitions
    [
      { :awaiting_quote => :awaiting_quote_acceptance, :on => :submit_quote },
      { :awaiting_quote_acceptance => :awaiting_final_invoice, :on => :accept_quote },
      { :awaiting_quote_acceptance => :awaiting_quote, :on => :decline_quote },
      { :awaiting_final_invoice => :awaiting_order_acceptance, :on => :submit_final_invoice },
      { :awaiting_order_acceptance => :order_placed, :on => :accept_order }
      # ...
    ]
  end

  # Create a state machine for this quotation instance dynamically based on the
  # transitions defined from the source above
  def machine
    quotation = self
    @machine ||= Machine.new(quotation, :initial => :awaiting_quote, :action => :save_state) do
      quotation.transitions.each { |attrs| transition(attrs) }
    end
  end

  # Save the state change
  def save_state
    self.save!
  end

Now how do I use the same conditional state changes in this approach? Where should I add the if: option? Thank you

aviisekh commented 4 years ago

Maybe you can add the if condition in method that returns transitions. i.e.

def transitions
    transitions = [
      { :awaiting_quote_acceptance => :awaiting_final_invoice, :on => :accept_quote },
      { :awaiting_quote_acceptance => :awaiting_quote, :on => :decline_quote },
      { :awaiting_order_acceptance => :order_placed, :on => :accept_order }
    ]

    transitions << { :awaiting_quote => :awaiting_quote_acceptance, :on => :submit_quote } if  line_item_details_complete?
    transitions << { :awaiting_final_invoice => :awaiting_order_acceptance, :on => :submit_final_invoice } if attachment?
    transitions
  end