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
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
User.find_active(:all, :order => 'country')
User.with_active { User.find(:all) }
User.calculate_active(:count, :all)
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