In short, passing options hash containing :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock to any of the ActiveRecord provided class methods, is now deprecated.
Going into details, currently ActiveRecord provides the following finder methods :
And the following calculation methods :
Starting with Rails 3, supplying any option to the methods above will be deprecated. Support for supplying options will be removed from Rails 3.2. Moreover, find(:first) and find(:all) ( without any options ) are also being deprecated in favour of first and all. A tiny little exception here is that count() will still accept a :distinct option.
The following shows a few example of the deprecated usages :
1 2 3 4 5 |
User.find(:all, :limit => 1) User.find(:all) User.find(:first) User.first(:conditions => {:name => 'lifo'}) User.all(:joins => :items) |
But the following is NOT deprecated :
1 2 3 |
User.find(1) User.find(1,2,3) User.find_by_name('lifo') |
Additionally, supplying options hash to named_scope is also deprecated :
1 2 |
named_scope :red, :conditions => { :colour => 'red' } named_scope :red, lambda {|colour| {:conditions => { :colour => colour }} } |
Supplying options hash to with_scope, with_exclusive_scope and default_scope has also been deprecated :
1 2 3 |
with_scope(:find => {:conditions => {:name => 'lifo'}) { ... } with_exclusive_scope(:find => {:limit =>1}) { ... } default_scope :order => "id DESC" |
Dynamic scoped_by_ are also going to be deprecated :
1 2 |
red_items = Item.scoped_by_colour('red') red_old_items = Item.scoped_by_colour_and_age('red', 2) |
ActiveRecord in Rails 3 will have the following new finder methods.
1 Value in the bracket ( if different ) indicates the previous equivalent finder option.
All of the above methods returns a Relation. Conceptually, a relation is very similar to an anonymous named scope. All these methods are defined on the Relation object as well, making it possible to chain them.
1 2 |
lifo = User.where(:name => 'lifo') new_users = User.order('users.id DESC').limit(20).includes(:items) |
You could also apply more finders to the existing relations :
1 2 |
cars = Car.where(:colour => 'black') rich_ppls_cars = cars.order('cars.price DESC').limit(10) |
A relation quacks just like a model when it comes to the primary CRUD methods. You could call any of the following methods on a relation :
So the following code examples work as expected :
1 2 3 4 5 6 7 8 |
red_items = Item.where(:colour => 'red') red_items.find(1) item = red_items.new item.colour #=> 'red' red_items.exists? #=> true red_items.update_all :colour => 'black' red_items.exists? #=> false |
Note that calling any of the update or delete/destroy methods would reset the relation, i.e delete the cached records used for optimizing methods like relation.size.
As it might be clear from the examples above, relations are loaded lazily – i.e you call an enumerable method on them. This is very similar to how associations and named_scopes already work.
1 2 |
cars = Car.where(:colour => 'black') # No Query cars.each {|c| puts c.name } # Fires "select * from cars where ..." |
This is very useful along side fragment caching. So in your controller action, you could just do :
1 2 3 |
def index @recent_items = Item.limit(10).order('created_at DESC') end |
And in your view :
1 2 3 4 5 |
<% cache('recent_items') do %> <% @recent_items.each do |item| %> ... <% end %> <% end %> |
In the above example, @recent_items are loaded on @recent_items.each call from the view. As the controller doesn’t actually fire any query, fragment caching becomes more effective without requiring any special work arounds.
For the times you don’t need lazy loading, you could just call all on the relation :
|
cars = Car.where(:colour => 'black').all |
It’s important to note that all returns an Array and not a Relation. This is similar to how things work in Rails 2.3 with named_scopes and associations.
Similarly, first and last will always return an ActiveRecord object ( or nil ).
1 2 3 |
cars = Car.order('created_at ASC') oldest_car = cars.first newest_car = cars.last |
Using the method named_scope is deprecated in Rails 3.0. But the only change you’ll need to make is to remove the “named_” part. Supplying finder options hash will be deprecated in Rails 3.1.
named_scope have now been renamed to just scope.
So a definition like :
1 2 3 4 |
class Item named_scope :red, :conditions => { :colour => 'red' } named_scope :since, lambda {|time| {:conditions => ["created_at > ?", time] }} end |
Now becomes :
1 2 3 4 |
class Item scope :red, :conditions => { :colour => 'red' } scope :since, lambda {|time| {:conditions => ["created_at > ?", time] }} end |
However, as using options hash is going to be deprecated in 3.1, you should write it using the new finder methods :
1 2 3 4 |
class Item scope :red, where(:colour => 'red') scope :since, lambda {|time| where("created_at > ?", time) } end |
Internally, named scopes are built on top of Relation, making it very easy to mix and match them with the finder methods :
1 2 3 |
red_items = Item.red available_red_items = red_items.where("quantity > ?", 0) old_red_items = Item.red.since(10.days.ago) |
If you want to build a complex relation/query, starting with a blank relation, Model.scoped is what you would use.
1 2 3 |
cars = Car.scoped rich_ppls_cars = cars.order('cars.price DESC').limit(10) white_cars = cars.where(:colour => 'red') |
Speaking of internals, ActiveRecord::Base has the following delegations :
1 2 3 |
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped |
The above might give you a better insight on how ActiveRecord is doing things internally. Additionally, dynamic finder methods find_by_name, find_all_by_name_and_colour etc. are also delegated to Relation.
with_scope and with_exclusive_scope are now implemented on top of Relation as well. Making it possible to use any relation with them :
1 2 3 |
with_scope(where(:name => 'lifo')) do ... end |
Or even use a named scope :
1 2 3 |
with_exclusive_scope(Item.red) do ... end |
That’s all. Please open a lighthouse ticket if you find a bug or have a patch for an improvement!
UPDATE 1 : Added information about deprecating scoped_by_ dynamic methods.
UPDATE 2 : Added information about deprecating default_scope with finder options.