В данном переводе рассмотрены нововведения в следующей версии ActiveRecrod для Ruby on Rails 3, а так-же описана часть модуля, которая будет исключена в пользу поддержки новых интерфейсов.
Следующие методы будут считаться устаревшими в релизе Rails 3.1 (но не Rails 3.0), и будут полностью исключены из Rails 3.2 (хотя можно будет установить специальный плагин для их дальнейшего использования). Имейте в виду это предупреждение, т.к. оно влечет за собой значительные изменения в коде.
В кратце, передача хеша
Рассмотрим это более подробно. На данный момент ActiveRecord предоставляет следующие методы для поиска:
Следующий код иллюстрирует использование более не поддерживаемых опций:
Но вот такой код по прежнему будет работать:
В добавок ко всему, передача хеша options методу
Поддержку потеряет так-же передача options методами
Динамический
ActiveRecord в Rails 3 получит слудующие методы для поиска (в скобках указан существующий эквивалент из хеша
Каждый из вышеозначенных методов возвращает объект класса
Можно также применить несколько finder’ов к существующим Relation’ам:
Отношение (
Важно знать, что вызов метода
Возможно, из предыдущих примеров уже стало ясно, что
Это очень полезно на ряду с фрагментным кешированием. Так, в контроллере достаточно вызвать:
В предыдущем примере
Принужденная загрузка —
В случае, когда нам не нужна ленивая загрузка, достаточно лишь вызывать, к примеру,
Важно помнить, что здесь
Точно также, методы
Использование метода
Метод
Теперь выглядит как:
Но, ввиду того что хеш options будет исключен, на самом деле придется писать используя новые методы для поиска, т.е. вот так:
Внутренне,
Если необходимо построить сложный запрос, начав с «чистого» Relation, необходимо использовать
К слову, говоря о внутренностях,
Код выше может дать более прозрачное представление тому, что происходит внутри
Или даже named scope:
Что потеряет поддержку в 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_scope
→ scope
Использование метода
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