Nested events and boomeranging states #201

Closed
steveluscher opened this Issue Jul 30, 2012 · 3 comments

3 participants

@steveluscher

Start with ActiveRecord. Let's say that you fire an event, and that during that event you fire another event. Let's say you're dealing with an Order model that has a checkout event, during which payment is processed:

class Order < ActiveRecord::Base
  state_machine :state, initial: :incomplete do
    state :processing_payment
    state :paid

    after_transition :incomplete => :processing_payment, do: :process_payment

    event :checkout do
      transition :incomplete => :processing_payment
    end
    event :mark_as_paid do
      transition :processing_payment => :paid
    end
    event :mark_as_having_failed_payment do
      transition :processing_payment => :incomplete
    end
  end
end

Let's say that, in process_payment, you discover the buyer's card has been declined, and you want to call mark_as_having_failed_payment.

def process_payment
  …
  mark_as_having_failed_payment! if payment_failed
end

I would expect the record to now have the state incomplete, but it has the state processing_payment instead. Here's the log from the transition between :incomplete and :processing_payment:

(0.4ms)  UPDATE `orders` SET `state` = 'processing_payment', `updated_at` = '2012-07-30 00:16:20' WHERE `orders`.`id` = 123

And here's the log generated as a result of calling mark_as_having_failed_payment!:

(1.1ms)  UPDATE `orders` SET `updated_at` = '2012-07-30 00:16:20' WHERE `orders`.`id` = 123

Why didn't it set the state? It's as though the machine thinks that since it started in the :incomplete state, there's nothing to do, even though the previous event has already set the state to :processing_payment in the same transaction.

Note that this is not a problem if you're going to a different state altogether (like :paid for example). It's only a problem when you go from state A => B => A.

@the8472

While this might a bug I don't think it's The Right Way(tm) to do it.

You could just use a before_transition handler and return false to rollback the transition.
Another way would be to determine the target state beforehand by using an event with multiple, conditional transitions

@obrie obrie added a commit that closed this issue Mar 9, 2013
@obrie obrie Completely rewrite ORM action hooks to behave more consistently acros…
…s the board. Closes #201, closes #214, closes #219, closes #230

* Change transitions to be executed the same whether using ORM save actions or not
* Fix around_transition callbacks not being executed properly in ORM integrations
* Fix additional transitions not being able to be fired in transition callbacks
* Add documentation on the order in which transition callbacks run within ORM integrations
5dfc92b
@obrie obrie closed this in 5dfc92b Mar 9, 2013
@obrie
PluginAWeek member

Thanks for reporting this! I've revamped the way state_machine integrates with ActiveRecord -- this has subsequently fixed this issue. It'll get included in the upcoming 1.2.0 release.

@obrie obrie was assigned Mar 9, 2013
@steveluscher

Awesome. Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment