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
This has all been implemented in the AASM Core now.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
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
l 5 comments »