acts_as_state_machine with auto named_scope powers May 21st, 2008

Rails 2.1 (Soon to be released?) brings us integrated named_scope‘ing. I’ve already mentioned named_scope here and there, and you can find a complete writeup on Ryan Daigle’s Blog

While working on projects on Rails Edge, we used to have the same pattern over and over.

class Mail < ActiveRecord::Base
  include AASM

  aasm_state :created
  aasm_state :sending
  aasm_state :sent
  aasm_state :failed
  aasm_state :delayed

  named_scope :created, :conditions => {:status => 'created'}
  named_scope :sending, :conditions => {:status => 'sending'}
  named_scope :sent, :conditions => {:status => 'sent'}
  named_scope :failed, :conditions => {:status => 'failed'}
  named_scope :delayed, :conditions => {:status => 'delayed'}
end

For each state we had, we created a named_scope, because it is so great and easy to be able to say Mail.failed.each .... However, this isn’t really DRY, and we can see a clear pattern here. When you think about it, you really should have a scope for every state you define. So I forked from AASM on github and started hacking away.

The result:

class Mail < ActiveRecord::Base
  include AASM

  aasm_state :created
  aasm_state :sending
  aasm_state :sent
  aasm_state :failed
  aasm_state :delayed
end

...
>> Mail.failed
=> []
>> Mail.sent
=> [#<Mail id: 1, email: "jan@domain.com", content: "hallo", :aasm_state: "sent", created_at: "2008-05-21 15:39:38", updated_at: "2008-05-21 15:39:38">, #<Mail id: 2, email: "jan@domain.com", status: "sent", created_at: "2008-05-21 15:39:39", updated_at: "2008-05-21 15:39:39">]

You can find my named_scope enabled AASM on github. At the moment only the master branch has been patched with my functionality, I’ll do the no_aasm_prefix branch somewhere this evening / tomorrow. Let me know if you’re using it, and what could be better This has all been implemented in the AASM Core now.

For those interested in the code, this is the bit that does the magic.

module AASM::NamedScopeMethods
  def self.add_named_scope base
    # Don't add unless it's a class which understands `named_scope`
    return unless base.respond_to? :named_scope

    base.extend AASM::NamedScopeMethods::ClassMethods
    base.class_eval do
      class << self
        alias_method :aasm_state_without_named_scope, :aasm_state
        alias_method :aasm_state, :aasm_state_with_named_scope
      end
    end
  end

  module ClassMethods
    def aasm_state_with_named_scope name, options = {}
      aasm_state_without_named_scope name, options

      self.named_scope name, :conditions => {self.aasm_column => name.to_s} unless self.scopes.include?(name)
    end
  end
end

Gotcha

While testing the plugin we found out 1 gotcha to this: do not define a aasm_state :new, because this will override the constructor of the class, and you’ll lose all creating-functionality.

Update

This is now default AASM behaviour as of this commit

tags: , , , l

5 Responses to “acts_as_state_machine with auto named_scope powers”

  • over 3 years ago EppO said

    You seem to know very well AASM so I give it a try: Is-it possible to add args to events methods and add additional code to process them ?

    aasm_event :view (one) do
      transitions :to => :read, :from => [:new]
      bla(one)
    end
    

    I don’t know if this is a good purpose but I needed this point on a project…

  • over 3 years ago Jan De Poorter said

    That is not possible in the current implementation. The bla(one) will be run in the Event class when the event is defined. Patches to add this behaviour are always welcome, ofcourse ;-)

  • over 3 years ago EppO said

    Thanks for your reply, I am interested for this behaviour but the AASM complexity is too high for me actually :) (I don’t know very well block/yield coding concepts)

  • over 3 years ago Enrico said

    Another pattern I find myself using is to add the following method for each state:

    def failed? status.to_sym == :failed end

    so I can query a single instance about its status

    You might want to consider adding it to your fork…

  • over 3 years ago Jan De Poorter said

    @Enrico: As far as I know AASM automatically does this when you define a state. aasm_state gives you the “#{state}?” method for free.

Sorry, comments are closed for this article.