Dec 6 09

Announcement: associated_named_scope plugin for Rails

by Mechaferret

History

The overwhelming usefulness of named_scope is common knowledge in the Rails community. Named scopes are so useful that you want to use them everywhere, and that’s when you begin to encounter one of their frustrations: while they are quite easy to write and use, they are not always so easy to re-use.

Most of the time, reuse is provided by the (also overwhelmingly useful) chaining feature of named scopes. If you want to use a named scope in an association (has_one, has_many, or belongs_to), you can simply define the association and then chain the scope to it, optionally defining a method to access it if you will be using it often. For example, if you have a named scope published_in_month on the Post class that finds all posts published in a given month, and the User class has_many posts, then the code to find all posts for a given user written in a given month is simply

user.posts.published_in_month(month)

If you want to combine two named scopes together, again, you can just chain them (again optionally defining a new method — in this case a class method). If Post has another named scope with_comments that returns posts with comments, then the following code finds all posts published in a given month that have comments:

Post.published_in_month(month).with_comments

So far, so very good. But what if you want to define a named scope that reuses a named scope not for the given class, but for one of its associations? To be more precise, what if you have a named scope published_in_month on the Post class that finds all posts published in a given month, and the User class has_many posts, and you want to write a named scope on User to find all users who have posts published in a given month? Sure, you can do

(Post.published_in_month(month).collect {|post| post.user}).uniq

but that’s a little harder to read — and is not itself a named scope, and thus doesn’t get executed as a single query and doesn’t have the nice features of named scopes such as chaining.

This problem came up emphatically in some code I was writing recently. In our system, many objects belongs to a user, a user has many addresses that include a travel radius, and an address may be within the range of a given lat/lng point. The SQL to calculate whether an address is in-range is pretty messy, as you’d expect, and it was showing up all over the code. I refactored to put it in a named scope in_range on Address, but what I really needed was a named scope to find all objects whose addresses (through their user) were within range of a given specific address, and there wasn’t a clean way to reuse the named scope on Address. And from that need was born this plugin, associated_named_scope.

Note: While looking around online to see if there was already such a thing available, I discovered that this request had been made before [discussion now moved here]. This plugin doesn’t exactly provide the syntax as requested there, but it does provide the same functionality.

The plugin

associated_named_scope is a Rails plugin that, quite simply, allows (re)using named scopes on child associations to define named scopes in the parent classes. It is available at

http://github.com/Mechaferret/associated_named_scope

Usage

associated_named_scope adds a new option :association to named_scope, which expects two suboptions:

  • :source — the source association for the child named scope. You can specify nested associations using standard hash syntax (e.g., {:user=>:addresses} for the addresses of the user of the current model).
  • :scope — the named scope itself. You can specify arguments to the scope by using an array [:scope_name, arg1, ...]

These scopes chain and in every way behave just like normal named_scopes.

Examples

Here’s a simple example to find all users who have posts published in a given month:

class Post < ActiveRecord::Base
  belongs_to :user
  named_scope :published_in_month, lambda{|month|
    {:conditions=>["month(posts.publication_date)=?", month]}}
end

class User < ActiveRecord::Base
  has_many :posts
  named_scope :has_posts_for_month, lambda{|month|
    {:association=>{:source=>:posts, :scope=>[:published_in_month, month]}}
  }
end

And here’s a more complex example, using a nested association:

class Address < ActiveRecord::Base
  belongs_to :addressable, :polymorphic => true

  named_scope :in_range, lambda {|lat, lng, zip_code|
    {:conditions =>
      "(
        (POW(addresses.radius,2) >=
          (POW(69.1*(#{lat}-addresses.lat),2) +
            POW(69.1*(#{lng}-addresses.lng) * cos(#{lat} / 57.3), 2)
          )
        )
        OR (addresses.zip = '#{zip_code}')
      )"
    }}
end

class User < ActiveRecord::Base
  has_one :object_with_user
  has_many :addresses, :as => :addressable
end

class ObjectWithUser < ActiveRecord::Base
  belongs_to :user
  named_scope :within_range, lambda{|address|
   {:association=>{:source=>{:user=>:addresses},
     :scope=>[:in_range, address.lat, address.lng, address.zip]}}}
end
Nov 27 09

Everyone should update to the new AASM plugin

by Mechaferret

This post is dedicated to lavishing praise on something I had no hand in creating, but which I am very fond of right now: the new AASM update of the acts_as_state_machine plugin. It’s brilliant. Really. Everyone who uses acts_as_state_machine (which should be everyone who has a Rails app, because there’s no such thing as a web application without at least one object with complex, business-driven state rules) should go and update it to use AASM right now.

Some background: I’m writing code that needed to create an instance of NewModel for every existing instance of OldModel that was in one particular state (where OldModel implemented acts_as_state_machine). I check the code: sure enough, there are no named scopes to get only objects in that state. I could just write code to cover this case, but given how easy it should be to generate a named scope for each state, that seems like the wrong answer. Thus, I begin the following dialog:

Me: Does anyone know if acts_as_state_machine can generate named scopes for each state?
Other developer: No, I don’t think it does.
OD: That sure would be useful though.
M: And pretty easy to write…. But before I do that, let me check and see if anyone has updated it.
Other other developer: I made some modifications to acts_as_state_machine for that model, so that it could transition to multiple states.
M: (looking at the code in lib) Cool! It’s basically an implementation of a nondeterministic state machine.
OOD: I didn’t do it myself, I got it here. (IM’s me a link to http://justbarebones.blogspot.com/2007/11/actsasstatemachine-enhancements.html.)
M: Now I’m definitely looking to see what the latest acts_as_state_machine is.

To github I go, where I find AASM at http://github.com/rubyist/aasm/. Documentation is weak (I may try to contribute to that), but the code looks promising and has the multiple-state transitions OOD was using, as well as the ability to define specific actions on initial_state/final_state/transition triads. I download it, do a quick check to verify that it does in fact automatically generate named scopes for each state, and pop it in. A few syntax adjustments (everything is now prefixed with “aasm_”) and my new code works great.

Some of the old code, however, has started throwing errors. Why? Because it’s calling invalid state transitions! Apparently the old acts_as_state_machine just silently ignored them, while this one complains. Which is good, because now I can see that our mandatory email notifications weren’t getting generated sometimes, specifically in the cases in which the object was in an invalid state for the event that should generate them.

The new AASM allows transitions from a state to itself, and the aforementioned specific actions on initial_state/final_state/transition triads, though, so fixing the newly-discovered bug is as easy as defining a new transition and specific action. And now everything works, correctly, and AASM has helped me fix a bug I didn’t even know we had.

Summary of advantages of AASM over old acts_as_state_machine that I discovered in about half an hour of usage:

  1. AASM generates named scopes for each state.
  2. AASM allows nondeterministic transitions: that is, an object can transition from one state to a list of other states on an event, with the actual final state determined at runtime.
  3. AASM allows specification of actions on initial_state/final_state/transition triads.
  4. AASM actually throws errors on invalid state transitions, thus making debugging easier.

I’m sure there any many more, but that was more than enough to make my coding day much easier. And the actual code activity required to do the update is, as noted above, minimal.

I’m not sure if the authors think that the new version is completely ready for prime time. But in my experience it’s so much better than the old one (and also hasn’t displayed any bugginess), so I don’t think you need to wait.