ActiveRecord Query Interface 3.0

http://m.onkey.org/2010/1/22/active-record-query-interface
  • Перевод
В данном переводе рассмотрены нововведения в следующей версии 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
Поделиться публикацией

Комментарии 38

    0
    Неплохо. Жаль что в рельсах страдает совместимость со старыми версиями. А когда нам ожидать стабильную версию 3.0?
      0
      Можно начинать гадать на кофейной гуще с выходом первого альфа-кандидата. Пока еще вообще что-то говорить рано.
        0
        Можно начинать гадать на кофейной гуще с выходом первого альфа-кандидата. Более конкретно пока рано говорить.
          0
          Возьму свои слова назад, и процетирую сегодняшнее сообщение DHH:
          — Rails 3 beta is almost ready for public testing. We're just hammering out the last bugs in Bundler. Stay tuned.
            0
            3.0b будет на неделе, а вот когда будет релиз, а главное — 3.1 — неизвестно.
          0
          они читают мои мысли :)
          • НЛО прилетело и опубликовало эту надпись здесь
              0
              Да. Пример отсюда? :)

              Суть «ленивости» в том, что можно сколько угодно строить цепочку вызовов (очень грубо говоря — стоить SQL запрос), и даже после последнего вызова не будет выполнен непосредственно запрос к БД.

              Запорос к БД для указанной цепочки можно выполнить обратившись к переменной new_users как к коллекции записей, которая ожидается от БД.
              • НЛО прилетело и опубликовало эту надпись здесь
              0
              Еще победить медленные шаблоны и рельсы можно будет сравнивать с джангой.

              Это не святая война, это блин объективное мнение :-Р
                –1
                Не обязательно использовать Erb. Можно же использовать Erubis, к примеру.
                  0
                  Скорость компиляции значения не имеет.
                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    С приходом Ruby 1.9.1 в рельсы (а это будет реккомендованой версией для Rails 3) ситуация со скоростью интерпретации значительно улучшится. Прибавте к этому всевозможные правки и оптимизации, которые они сейчас проводят (судя по отзывам ребят из Ruby noname podcast — Rails 3 уже быстрее в ~1.5 раза) — вот вам и победа на «медленными шаблонами».
                    0
                    Все это хорошо для хеллоу ворлдов подходит… которые и без того было легко делать.
                    А изменится ли что-то для нетривиальных запросов?
                      0
                      Если вы знакомы с реляционной алгеброй или хотя бы английским языком — предлагаю почитать вот эту статью
                        0
                        1) я думаю connection.execution(«сложный запрос») они оставят — без него иногда никак

                        2) если создавать веб-сайт, ориентируясь на сложные нагрузки, то задумываешься о том, чтобы использовать mongoDB, amazonDB, simpleDB, может быть и не сейчас, но чтобы код был готов к этому и легко портировался и зная, что там не особо разыграешься с SQL, то стараешься писать простые запросы из одной таблицы… и такие вещи самое то

                        3) в одном моем проекте (переписанным на Rails сначала как калька с PHP) запросы начали постепенно приходит к виду ObjectName.active.most_popular, где active, most_popular это соответствующие name_scope
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          Это современный стиль, чем то похоже на LINQ.
                            +1
                            ага, в статье мутотень, а вы написали самый понятный, компактный и _не_мутотень_style в мире код:)
                            а before_save и прочие есть и в рельсах
                            • НЛО прилетело и опубликовало эту надпись здесь
                                +2
                                В Rails 3.0 специально исключают такой подход (см. начало статьи, там где говорится о хеше options) в пользу адаптации реляционной алгебры, где составление запросов происходит несравнимо гибче.
                                  0
                                  чуть ниже ответил, первый раз в жизни промахнулся веткой o_O
                                    0
                                    _гагляднее некуда
                                    соглашусь с Огневским, что этот синтаксис уступает стандартному рельсовому запросу
                                    $model = Item::model->findAll(array('limit'=>10, 'order'=>'id DESC', 'condition '=> 'date > 01-02-2010'));
                                    vs
                                    model = Item.find(:all, :limit => 10, :order => 'id DESC', :conditions => 'date > 01-02-2010')
                                    речь лишь о паре лишних символов, но тем не менее камень в сторону _гаглядности

                                    далее, ребята из кортим пошли ровно тем же путем, что вы демонстрировали ниже в своем многострочном запросе. Они тоже своеобразно разбили запрос. Только он вызывается цепочкой, а не объявлением параметров поиска с новой строки. И это бесспорно умнее и просто красивее.

                                    model = Item.limit(10).order('id DESC').conditions('date > 01-02-2010').all

                                    Обратите внимание, что буковок стало еще меньше, а читабельность повысилась.
                                      0
                                      В принципе, эти вот «последствия объектности» многим (и мне в том числе) очень нравятся.
                                      Будет больше похоже на какое-нибудь array.map(&:id).map(&:name).capitalize.to_sym ;)
                                  0
                                  ну если вам 10 строк вместо одной писать легче…

                                  в рельсах новых всё достаточно красиво сделано.
                                  А ваш вариант + указанный выше уже реализован в Propel 1.5
                                    0
                                    а это
                                    $cr->condition = 'date > :pdate';
                                    $cr->params = array(':pdate'=>'01-02-2010');

                                    что за зверь?
                                      0
                                      обычный placeholder
                                        0
                                        я догадался уже, но уж больно синтаксис страшный
                                    +1
                                    ну вот так, например, нагляднее:
                                    items = Item.find(:all, :limit => 10, :order => 'id DESC', :conditions => 'date > 01-22-2010')

                                    без всяких этих $, кавычек у свойств и прочего непотребства:)
                                      0
                                      Ага, PHP страшный, но зато шустрый относительно и память не жрет.
                                        0
                                        Британские ученыеTM подсказывают мне, что не такой уж он уже и шустрый;)
                                          0
                                          PHP не так страшен, как на нем «кодют».
                                          со временем его приведут в порядок.
                                          проблема в его популярности на фоне криворукости разработчиков.
                                            +1
                                            Ну не знаю, мне банально надоедает смотреть на все эти str_*() и array_*(), почему нельзя сделать ООП, хотя бы в части того, чтобы у строк/массивов были методы? Уродливый синтаксис, правда.
                                              0
                                              ну они движутся в этом направлении :)
                                                0
                                                Надоело? Возьми и сделай. Или заплати тому, кто сделает. Или STFU.
                                                  0
                                                  Ага, каждый сейчас будет выпускать свою версию php. Какой-то вы недальновидный develop.
                                                    0
                                                    Значит, не надоело.
                                                    Кстати, есть ещё вариант — не пользоваться PHP. Go on.

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое