Favorite new tool: named_scope

Update: I’ve been using named_scope for a bit longer now and totally don’t agree with my former self about naming conventions when creating named_scopes.  I now do what comes natural.  Love, me.

I’ve been using the new-ish Rails feature named_scope for a couple days now, and I love it.  The expressiveness was something I really missed jumping from OpenACS, a “framework” where you’re encouraged to write powerful queries in raw SQL.  So you get the best of both worlds: modular business logic and (relatively) powerful queries.  I wish Rails had more features to enable INNER JOINs using finders in a way that still allows the pre-fetching of associated objects, but that’s another matter entirely.  Some observations, though:

Naming named_scopes

I’ve now come to the conclusion that all objects in the titles of named_scopes should be singular (unless there’s a complex case I haven’t encountered yet where it makes sense).  So CourseMembership#in_sections_of_term should be CourseMembership#in_section_of_term, etc, even if the named_scope might return multiple rows — in fact, most do.  My reasoning is that you’ll typically be operating on something singular (a model class), and you’re really describing a criterion, not the result set.  Also named_scopes should never be nouns or they may collide with attribute or association names.  They should be adjectives or predicates (in the grammar sense), like:

Car#red (obviously a Car doesn’t belong_to or has_one Red: it probably has an attribute named “color”)
Course#live (Live is an adjective. Course doesn’t have a Live: it has a belongs_to association to CourseItem which in turn has a live_revision_id attribute)
User#is_student (This is tricky: i was tempted to call it User#student, but the User might well have a Student – though I don’t know how that might be defined)

Moral of the story is that named_scopes need to be named carefully so they don’t confuse the programmer into thinking that they’re calling an association or attribute instead.  named_scopes return proxies to arrays of the class in question where obviously associations and attributes return proxies to arrays or objects of different classes.

named_scopes on associated objects

The one thing that is preventing named_scopes from being as DRY as possible is that you can define all these fancy named_scopes on your models, but then there is no way to talk to the named_scope of an associated object in order to scope your own object.

For instance: at Berklee’s online extension school we’ve got Sections (of courses).  Each one has a starts_on date, ends_on date, closes_on date (after which you can’t post anymore), and archived_until date (after which you can’t see the course).  So I’ve got a bunch of named scopes on the Section class, including current (between start and end), open (between start and close), archived (between close and archived_until).  They work great.  But there’s a possiblity that these implementations might actually need to become more complicated, so it’s critical that this code be DRY.

So the kicker is that I need to be able to find enrollments based on those criteria.  There’s even a comment in the HasFinder blog post (HasFinder is the progenitor of named_scope) requesting something along these lines.  But I consider that to be a bit of a special case of the general problem.  I see there being two ways to grapple with it.  Either find() needs a way of letting you specify named_scopes to fire on :included associations (which is very powerful but gets away from the uber-fancy named_scope compositions that are currently allowed), or perhaps there is an even more sugary solution: What if there were an alternate form of named_scope that let you steal and alias named_scopes from associated classes?  For example, in the Enrollment class we could have:

named_scope :in_current_section, :inherit_from => :section, :scope_name => :current

Then I could find all the enrollments in Music Theory 101 by calling:

Enrollment.in_current_section.in_section_of_course(Course.live.find_by_title(“Music Theory 101″).course_item)

Sugary and DRY like Cap’n Crunch.

My gut is that you’d want both the finder enhancement and the aliasing options because there may be some objects that you need to scope a ton of ways based on inherited named_scopes, and you wouldn’t want to have to name all of them if they’re used relatively few times each.  Also, the semantics of the finder vs. aliasing approach would be different.  The aliased scope would additionally require that the association_table.primary_key be NOT NULL so that the scoping will actually have an effect on the result set of the primary object (or maybe this would be an additional boolean parameter to the named_scope call), whereas from the finder you might geniuinely want to return an unscoped set of objects, but have a pre-scoped set of children available to play with without hitting the database again.