Pull to refresh

ActiveRecord Query Interface 3.0

Reading time5 min
Views5.7K
Original author: Pratik Naik
В данном переводе рассмотрены нововведения в следующей версии ActiveRecrod для Ruby on Rails 3, а так-же описана часть модуля, которая будет исключена в пользу поддержки новых интерфейсов.

Что потеряет поддержку в Rails 3.1?


Следующие методы будут считаться устаревшими в релизе Rails 3.1 (но не Rails 3.0), и будут полностью исключены из Rails 3.2 (хотя можно будет установить специальный плагин для их дальнейшего использования). Имейте в виду это предупреждение, т.к. оно влечет за собой значительные изменения в коде.

В кратце, передача хеша options, содержащего :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock любому методу класса, предоставленного ActiveRecord’ом отныне считается устаревшим.

Рассмотрим это более подробно. На данный момент ActiveRecord предоставляет следующие методы для поиска:
  • find(id_or_array_of_ids, options)
  • find(:first, options)
  • find(:all, options)
  • first(options)
  • all(options)
  • update_all(updates, conditions, options)
А также методы для вычислений:
  • count(column, options)
  • average(column, options)
  • minimum(column, options)
  • maximum(column, options)
  • sum(column, options)
  • calculate(operation, column, options)
Начиная с версии Rails 3.0, передача любых опций этим методам считается устаревшим, и будет полностью исключена в Rails 3.2. Более того, методы find(:first) и find(:all) (без каких-либо дополнительных опций) также будет исключены в пользу first и all. В виде исключения из правил count() по прежнему будет принимать опцию :distinct.

Следующий код иллюстрирует использование более не поддерживаемых опций:
User.find(:all, :limit => 1)
User.find(:all)
User.find(:first)
User.first(:conditions => {:name => 'lifo'})
User.all(:joins => :items)

Но вот такой код по прежнему будет работать:
User.find(1)
User.find(1,2,3)
User.find_by_name('lifo')

В добавок ко всему, передача хеша options методу named_scope также потеряет поддержку:
named_scope :red, :conditions => { :colour => 'red' }
named_scope :red, lambda {|colour| {:conditions => { :colour => colour }} }

Поддержку потеряет так-же передача options методами with_scope, with_exclusive_scope и default_scope:
with_scope(:find => {:conditions => {:name => 'lifo'}) { ... }
with_exclusive_scope(:find => {:limit =>1}) { ... }
default_scope :order => "id DESC"

Динамический scoped_by_ аналогично уйдет в историю:
red_items = Item.scoped_by_colour('red')
red_old_items = Item.scoped_by_colour_and_age('red', 2)

Новый API


ActiveRecord в Rails 3 получит слудующие методы для поиска (в скобках указан существующий эквивалент из хеша options):
  • where (:conditions)
  • select
  • group
  • order
  • limit
  • joins
  • includes (:include)
  • lock
  • readonly
  • from
Цепочки

Каждый из вышеозначенных методов возвращает объект класса Relation. В принципе, Relation очень схож с анонимными named_scope. Все эти методы также определены и в нём самом, что предоставляет возможно создавать цепочки вызовов:
lifo = User.where(:name => 'lifo')
new_users = User.order('users.id DESC').limit(20).includes(:items)

Можно также применить несколько finder’ов к существующим Relation’ам:
cars = Car.where(:colour => 'black')
rich_ppls_cars = cars.order('cars.price DESC').limit(10)

Почти Model

Отношение (Relation) ведет себя точно так как и модель, когда дело доходит до использования первичных CRUD методов. Любой из приведенных ниже методов можно вызвать на объекте класса Relation:
  • new (attributes)
  • create (attributes)
  • create! (attributes)
  • find (id_or_array)
  • destroy (id_or_array)
  • destroy_all
  • delete (id_or_array)
  • delete_all
  • update (ids, updates)
  • update_all (updates)
  • exists?
Следующий код работает так как и ожидается:
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

Важно знать, что вызов метода update или delete/destroy «сбросит» Relation, т.е. удалит записи в кеше, используемые для оптимизации методов (таких как relation.size).

Ленивая загрузка

Возможно, из предыдущих примеров уже стало ясно, что Relations подгружаются «лениво» — т.е. над ними необходимо вызывать методы работы с коллекцией. Это очень похоже на то, как уже работают ассоциации (associations) и named_scope’ы.
cars = Car.where(:colour => 'black') # Relations ленивый, поэтому запрос еще не выполняется
cars.each {|c| puts c.name } # Выполняется запрос "select * из таблицы cars где ..."

Это очень полезно на ряду с фрагментным кешированием. Так, в контроллере достаточно вызвать:
def index
@recent_items = Item.limit(10).order('created_at DESC')
end
А во view:
<% cache('recent_items') do %>
<% @recent_items.each do |item| %>
...
<% end %>
<% end %>

В предыдущем примере @recent_items наполняется из БД только в момент вызова @recent_items.each из view. Так как контроллер не выполняет запрос у БД, фрагментное кеширование становится гораздо более эффективным, не требуя никакой дополнительно работы.

Принужденная загрузка — all, first & last

В случае, когда нам не нужна ленивая загрузка, достаточно лишь вызывать, к примеру, all на объекте типа Relation:
cars = Car.where(:colour => 'black').all

Важно помнить, что здесь all возвращает Array, а не Relation. Это похоже на то, как сейчас, в Rails 2.3 работают named_scope и associations.
Точно также, методы first и last вернут объект типа ActiveRecord (или nil):
cars = Car.order('created_at ASC')
oldest_car = cars.first
newest_car = cars.last

named_scopescope

Использование метода named_scope считаеться устаревшим в Rails 3.0, в пользу scope. Но единственное что действительно изменилось, так это то, что теперь не нужно писать приставку named_. Передача опций для поиска будет окончательно исключена из Rails 3.1.
Метод named_scope был просто переименован в scope. Т.е. следующее определение:
class Item
named_scope :red, :conditions => { :colour => 'red' }
named_scope :since, lambda {|time| {:conditions => ["created_at > ?", time] }}
end

Теперь выглядит как:
class Item
    scope :red, :conditions => { :colour => 'red' }
    scope :since, lambda {|time| {:conditions => ["created_at > ?", time] }}
end

Но, ввиду того что хеш options будет исключен, на самом деле придется писать используя новые методы для поиска, т.е. вот так:
class Item
    scope :red, where(:colour => 'red')
    scope :since, lambda {|time| where("created_at > ?", time) }
end

Внутренне, named scope'ы являются надстройками над Relation, делая тем самым очень простые вариации для использования вперемешку с finder-методами:
red_items = Item.red
available_red_items = red_items.where("quantity > ?", 0)
old_red_items = Item.red.since(10.days.ago)

Model.scoped

Если необходимо построить сложный запрос, начав с «чистого» Relation, необходимо использовать Model.scoped.
cars = Car.scoped
rich_ppls_cars = cars.order('cars.price DESC').limit(10)
white_cars = cars.where(:colour => 'red')

К слову, говоря о внутренностях, ActiveRecord::Base теперь содержит следующие делегаты:
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

Код выше может дать более прозрачное представление тому, что происходит внутри ActiveRecord. В добавок к этому любые динамический методы, aka find_by_name, find_all_by_name_and_colour, так-же делегируются Relation’у.

with_scope и with_exclusive_scope

with_scope и with_exclusive_scope теперь надстроены поверх Relation’a, предоставляя возможность использовать с ними любой relation:
with_scope(where(:name => 'lifo')) do
   ...
end

Или даже named scope:
with_exclusive_scope(Item.red) do
   ...
end
Tags:
Hubs:
+24
Comments38

Articles