Robot Has No Heart

Xavier Shay blogs here

A robot that does not have a heart

acts_as_state_machine is not concurrent

Here is a short 4 minute screencast in which I show you how the acts as state machine (AASM) gem fails in a concurrent environment, and also how to fix it.


(If embedding doesn’t work or the text is too small to read, you can grab a high resolution version direct from Vimeo)

It’s a pretty safe bet that you want to obtain a lock before all state transitions, so you can use a bit of method aliasing to do just that. This gives you much neater code than the quick fix I show in the screencast, just make sure you understand what it is doing!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ActiveRecord::Base
  def self.obtain_lock_before_transitions
    AASM::StateMachine[self].events.keys.each do |t|
      define_method("#{t}_with_lock!") do
        transaction do
          lock!
          send("#{t}_without_lock!")
        end
      end
      alias_method_chain "#{t}!", :lock
    end
  end
end

class Tractor
  # ...

  aasm_event :buy do
    transitions :to => :bought, :from => [:for_sale]
  end

  obtain_lock_before_transitions
end

This is a small taste of my DB is your friend training course, that helps you build solid rails applications by finding the sweet spot between stored procedures and treating your database as a hash. July through September I am running full day sessions in the US and UK. Chances are I’m coming to your city. Check it out at http://www.dbisyourfriend.com

  1. Matthew Todd says:

    Enjoying these videos, Xavier, thanks! Jared (not your brother) is back here with me at Amani now, and we're doing a bit of gnarly refactoring on the website code.

    Just a quick comment, if I understand properly, I think you'll want to s/Tractor/self/ on line 3 of the code example.

    Cheers! -- M

  2. Xavier Shay says:

    Correct, cheers!

  3. Matt Nichols says:

    This is very timely. I just started using AASM and have been worried about this very issue. Thanks, Xavier, for the demo and elegant solution!

    --Matt

  4. phil says:

    What about optimistic locking? Have you tried to your example with this approach in active record?

  5. Bodaniel Jeanes says:

    Nice!

    I'd probably @alias_method_chain@ the @aasm_event@ method itself as well, so that as soon as you define the event it creates the @alias_method_chain@ around the event method automatically without having to call @ obtain_lock_before_transitions@ in each model.

    BTW, you should totally update lesstile to also support inline-code blocks.

  6. Xavier Shay says:

    Phil, optimistic locking would probably work fine too (though I haven't confirmed AASM respects the rails lock_version, I assume it would). I feel pessimistic is a cleaner solution in this instance, since I don't need to change my existing models. (Patching AASM to by default use optimistic locking without lock_version would be neat though, hmmm)

    Bo, great idea for aasm_event.

  7. Marco says:

    One problem with the pessimistic approach using lock! is that lock! will also reload the model. That means you lose all unsaved changes that would otherwise be persisted on the state change since aasm just sets the state and saves the record (it doesn't only update the state attribute).

    Maybe it's not a good idea to rely on aasm to save the model on state changes but it definitely makes migrating legacy code that does so a little painful.

  8. Xavier Shay says:

    If you're state changing a dirty model, I'd argue that's probably Doing It Wrong.

Post a comment


(lesstile enabled - surround code blocks with ---)

A pretty flower Another pretty flower