⬆️ ⬇️

ActiveRecord Query Interface 3.0

This translation discusses innovations in the next version of ActiveRecrod for Ruby on Rails 3, and also describes a part of the module that will be excluded in favor of supporting new interfaces.



What will lose support in Rails 3.1?



The following methods will be considered obsolete in the release of Rails 3.1 (but not Rails 3.0), and will be completely excluded from Rails 3.2 (although it will be possible to install a special plugin for their further use). Keep in mind this warning because it entails significant changes in the code.



In short, passing the options hash containing :conditions :include :joins :limit :offset :order :select :readonly :group having :from :lock any method of the class provided by ActiveRecord ' This is now considered obsolete.

')

Consider this in more detail. Currently ActiveRecord provides the following methods for searching:As well as methods for computing:Starting with Rails 3.0, passing any options to these methods is considered obsolete, and will be completely excluded in Rails 3.2. Moreover, the methods find(:first) and find(:all) (without any additional options) will also be excluded in favor of first and all . As an exception to the rules, count() will still accept the :distinct option.



The following code illustrates the use of options that are no longer supported:
User.find(:all, :limit => 1)

User.find(:all)

User.find(:first)

User.first(:conditions => {:name => 'lifo'})

User.all(:joins => :items)


But such code will still work:
User.find(1)

User.find(1,2,3)

User.find_by_name('lifo')


On top of that, passing the options hash to the named_scope method will also lose support:

named_scope :red, :conditions => { :colour => 'red' }

named_scope :red, lambda {|colour| {:conditions => { :colour => colour }} }



Support will also be lost by passing options with the methods with_scope , with_exclusive_scope and default_scope :

with_scope(:find => {:conditions => {:name => 'lifo'}) { ... }

with_exclusive_scope(:find => {:limit =>1}) { ... }

default_scope :order => "id DESC"



Dynamic scoped_by_ will likewise go down in history:
red_items = Item.scoped_by_colour('red')

red_old_items = Item.scoped_by_colour_and_age('red', 2)


New API



ActiveRecord in Rails 3 will get the following methods to search for (an existing equivalent of the options hash is shown in parentheses):
Chains


Each of the above methods returns an object of the Relation class. In principle, Relation very similar to anonymous named_scope . All of these methods are also defined in it itself, which makes it possible to create call chains:
lifo = User.where(:name => 'lifo')

new_users = User.order('users.id DESC').limit(20).includes(:items)


You can also apply several finders to existing Relations:
cars = Car.where(:colour => 'black')

rich_ppls_cars = cars.order('cars.price DESC').limit(10)


Almost Model


Relation behaves exactly like a model when it comes to using primary CRUD methods. Any of the following methods can be called on an object of the Relation class:The following code works as expected:

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


It is important to know that calling the update or delete / destroy method will “reset” the Relation , i.e. will remove cache entries used to optimize methods (such as relation.size ).



Lazy loading


Perhaps from the previous examples it has already become clear that Relations loaded “lazily” - i.e. above them it is necessary to call the methods of working with the collection. This is very similar to how associations and named_scope 's already work.
cars = Car.where(:colour => 'black') # Relations ,

cars.each {|c| puts c.name } # "select * cars ..."


This is very useful along with fragment caching. So, in the controller it is enough to call:

def index

@recent_items = Item.limit(10).order('created_at DESC')

end
And in view:

<% cache('recent_items') do %>

<% @recent_items.each do |item| %>

...

<% end %>

<% end %>


In the previous example, @recent_items filled from the database only at the moment of calling @recent_items.each from the view . Since the controller does not execute the query from the database, fragment caching becomes much more efficient without requiring any additional work.



Forced download - all , first & last


In the case when we do not need a lazy load, it is enough just to call, for example, all on an object of the Relation type:

cars = Car.where(:colour => 'black').all


It is important to remember that here all returns Array , not Relation . This is similar to the way named_scope and associations work in Rails 2.3 now.

Similarly, the first and last methods will return an object of type ActiveRecord (or nil ):

cars = Car.order('created_at ASC')

oldest_car = cars.first

newest_car = cars.last


named_scope → scope


Using the named_scope method is considered obsolete in Rails 3.0, in favor of scope . But the only thing that really changed is that now you don’t need to write the named_ prefix. Passing options for the search will be permanently excluded from Rails 3.1.

The named_scope method was simply renamed scope . Those. the following definition:

class Item

named_scope :red, :conditions => { :colour => 'red' }

named_scope :since, lambda {|time| {:conditions => ["created_at > ?", time] }}

end


Now it looks like:

  class Item
     scope: red,: conditions => {: color => 'red'}
     scope: since, lambda {| time |  {: conditions => ["created_at>?", time]}}
 end 


But, since the options hash will be excluded, in fact, you have to write using new methods for searching, i.e. like this:

  class Item
     scope: red, where (: color => 'red')
     scope: since, lambda {| time |  where ("created_at>?", time)}
 end 


Internally, named scope 's are add-ins over Relation , thereby making very simple variations for use in conjunction with finder methods:

red_items = Item.red

available_red_items = red_items.where("quantity > ?", 0)

old_red_items = Item.red.since(10.days.ago)


Model.scoped


If you need to build a complex query, starting with a "pure" Relation, you must use Model.scoped .

cars = Car.scoped

rich_ppls_cars = cars.order('cars.price DESC').limit(10)

white_cars = cars.where(:colour => 'red')


By the way, speaking of internals, ActiveRecord::Base now contains the following delegates:

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 code above can give a more transparent view of what is happening inside ActiveRecord . In addition to this, any dynamic methods, aka find_by_name , find_all_by_name_and_colour , are also delegated to the Relation 'y.



with_scope and with_exclusive_scope


with_scope and with_exclusive_scope now built on top of Relation 'a, providing the ability to use any relation with them:

  with_scope (where (: name => 'lifo')) do
    ...
 end 


Or even named scope:

  with_exclusive_scope (Item.red) do
    ...
 end 

Source: https://habr.com/ru/post/82767/



All Articles