Scope Out ActiveRecord Conditions

Most Rails applications often need to work with ActiveRecord conditions. For example, your application might need to find recent items, inactive users, or non-expired coupon codes. To keep your code DRY, you do not want to be doing this all over your controllers:
class UsersController < ApplicationController
  def list
    @users = User.find(:all, :conditions => {:active => true})
  end
end
In addition, shouldn't your model define what makes a user active or not?

Jamis Buck has written about this; he prefers defining methods on the associations.
For example:
class Group < ActiveRecord::Base
  has_many :users do
    def active(reload = false)
      @active = nil if reload
      @active ||= find(:all, :conditions => ["active = ?", true])
    end
  end
end
Now you can find active users in a group through: Group.find(1).users.active. Defining the scope on the association is cool, especially so you can cache the result, but I have a few problems with it.
(1) To make this scope available to all models which have many users, you have to define it on all the associations or extend all associations with a module.
(2) This provides no easy way to nest scopes, such as finding active users who are female. (That is, without going back to passing in the :conditions option over and over again.)
(3) If I want to know what criteria makes a user active/inactive, I want to look only in the User model.

There are definitely some challenges with this. As Jamis mentioned, you can define the conditions on the model and still access it through the association, such as:
class User < ActiveRecord::Base
  def find_active(options = {})
    find(:all, options.merge(:conditions => {:active => true}))
  end
end

# this works now
Group.find(1).users.find_active 
This seems like a simple solution, but what about the caching? Or nesting conditions? Clearly, any way of doing this has some downsides.

The solution: John Andrews has written a plugin, Scope Out, which you need to use in your models. Take a look at the examples on the project page, but in summary:
(1) Your conditions are defined in ONE place.
(2) You can extend associations if you want caching.
(3) The 'raw' with_scope is available, making applying multiple conditions easy.
(4) The find and calculate methods are both available.

class User < ActiveRecord::Base
  scope_out :active, :conditions => {:active => true}
end
class Group < ActiveRecord::Base
  has_many :users, :extend => User::AssociationMethods
end

# All of these work

# Basic example
User.find_active(:all, :order => 'country')

# Using the with_ (great for combining with other 'withs'
User.with_active { User.find(:all) }

# Calculations
User.calculate_active(:count, :all)

# Accessing from associations (with caching)
Group.find(:first).users.active

It's a great plugin and has been a great benefit to all our projects.
Documentation and code: Scope Out
Hi, I'm Dan Manges, a software developer best known for being the founding CTO of Braintree. Send me an email.
blog comments powered by Disqus