Active Record запросы в Ruby on Rails 3

сентября 9, 2010  |  Published in Ruby on Rails, Ruby on Rails 3  |  4 Comments

Ruby on Rails 3В Ruby on Rails 3 используется новый движок запросов к базе данных – , который вынесен в отдельный gem. Благодаря новому движку Active Record 3.0.0 стал более удобным и простым.
Давайте сравним то, какими были запросы в Ruby on Rails 2.3.x и какими стали в Ruby on Rails 3.

В нашем примере, приложение Rails будет содержать всего две модели: Article и Comment, которые имеют между собой взаимосвязь:

Article has_many :comments

В первом примере мы выбираем из базы данных десять последних записей в таблице Articles:

Article.find(:all, :order  =>  “publish_at desc”, :limit => 10)

Теперь давайте посмотрим, как выглядит запрос, выполняющий абсолютно ту же выборку в Ruby on Rails 3:

Article.order(“published_at desc”).limit(10)

Как видите, новый синтаксис значительно проще и при этом конвертация старого синтаксиса запросов в новый, используемый в Ruby on Rails 3, не составляет никакого труда. Для конвертации старых запросов в новые достаточно заменить параметры метода find передаваемые в виде хеша, на методы с соответствующими именами.

Старые опции не всегда превращаются в методы. Давайте взглянем на следующий пример:

Article.find(:all, :conditions => ["published_at <= ?", Time.now], :include => :comments)

Данный запрос для Ruby on Rails 3 будет выглядеть следующим образом:

Article.where("published_at <= ?", Time.now).includes(:comments)

Есть только два исключения из правила конвертации опций в методы, и оба они показаны в примере выше. Вместо :conditions теперь следует использовать метод where, передавая ему те же аргументы, что ранее содержались в хеше под ключом :conditions. Аргументы также могут быть переданы в виде массива, однако в данном случае чище будет передавать их разделяя запятой. Данный код выполняет выборку всех записей из базы данных созданных ранее текущего момента. Для выборки всех ассоциируемых со статьями комментариев используется метод include, произошедший от опции :include. Все параметры, передаваемые в Find, становятся методами с такими же именами. Следующий пример делает выборку из базы данных последней добавленной статьи:

Article.find(:first, :order => "published_at desc")[ruby]

Для Ruby on Rails 3 код, выполняющий ту же выборку, приобретает следующий вид:

[ruby]Article.order("published_at desc").first()

Запомните, что в цепочке методов метод first всегда должен занимать последнюю позицию!
Ту же выборку мы можем выполнить и в другой способ:

Article.order("published_at").last()

В Ruby on Rails 3 вы можете использовать старый добрый find, однако с версии 3.1 он будет считаться устаревшим и рекомендуется отказаться от него, а в версии 3.2 он и вовсе будет удален. Такая последовательность действий регламентирована тем, что разработчики позаботились о смягчении перехода к Ruby on Rails 3 и возможности работы приложений написанных на более ранних версиях Ruby on Rails в 3ей версией без болезненного перехода.

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

Термин «Ленивая загрузка» или «Ленивая инициализация» сродни термину «отложенные (ленивые) вычисления». Ленивая загрузка представляет собой прием в программировании, при котором данные загружаются только в момент их непосредственной необходимости.

Для демонстрации того, что такое ленивая загрузка, давайте воспользуемся консолью Rails. Если вы запросите все статьи из базы данных, то вы увидите, что у вас имеется 3 статьи.

ruby-1.9.1-p378 > Article.all => [#<Article id: 1, name: "It's Ancient", published_at: nil, hidden: false,

created_at: "2010-02-22 20:35:42", updated_at: "2010-02-22 20:35:42">,
#<Article id: 2, name: "Can't See Me", published_at: nil, hidden: false,
created_at: "2010-02-22 20:37:03", updated_at: "2010-02-22 20:37:03">,
#<Article id: 3, name: "To the Future!", published_at: nil, hidden: false,
created_at: "2010-02-22 20:38:17", updated_at: "2010-02-22 20:38:17">]

Если вам понадобится получить все статьи, отсортированные по названию в алфавитном порядке, вы можете легко выполнить такой запрос, используя метод order:

ruby-1.9.1-p378 > articles = Article.order("name")
=> #<ActiveRecord::Relation:0x00000101669b90 @table=#<Arel::Table:0x000001023e9af8
@name="articles", @options={:engine=>#<Arel::Sql::Engine:0x000001023a15c8 @ar=ActiveRecord::Base,
@adapter_name="SQLite">}, @engine=#<Arel::Sql::Engine:0x000001023a15c8 @ar=ActiveRecord::Base,
@adapter_name="SQLite">, …

Вместо возвращения списка статей, в данном случае мы получаем объект ActiveRecord::Relation. Этот объект хранит информацию о нашем поиске, однако запрос к базе данных не осуществляется. Это и есть ленивая загрузка: данные не загружаются, пока в них нет нужды. Если мы начнем проходить по массиву записей с помощью each или сделаем запрос на выборку всех записей, или, например, только первую из них, то только тогда будет выполнен запрос.

ruby-1.9.1-p378 > articles.first
=> #<Article id: 2, name: "Can't See Me", published_at: nil, hidden: false,
created_at: "2010-02-22 20:37:03", updated_at: "2010-02-22 20:37:03">

Теперь давайте посмотрим на то, как это применимо к нашему Ruby on Rails приложению. Нам необходимо воспользоваться генератором scaffold для модели статей, таким образом, мы имеем контроллер статей с типичными семью экшенами. Код для экшена index использует Article.all для получения всех статей немедленно:

/app/controllers/articles_controller.rb
def index
@articles = Article.all

respond_to do |format|
format.html # index.html.erb
format.xml  { render :xml => @articles }
end
end

Если мы используем любую из поисковых опций применительно к статьям, например сортирование по имени, тогда будет возвращен объект ActiveRecord::Relation, и запрос к базе данных произведен в контроллере не будет. Запрос к базе данных вместо этого будет выполнен из представления, когда мы будем пробегаться по массиву моделей Article.

/app/views/articles/index.html.erb
<% @articles.each do |article| %>
<tr>
<td><%= article.name %></td>
<td><%= article.published_at %></td>
<td><%= article.hidden %></td>
<td><%= link_to 'Show', article %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<td><%= link_to 'Destroy', article, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>

Если мы сейчас загрузим представление экшена index, то увидим список всех статей в алфавитном порядке.

ruby on rails 3

Хорошый момент здесь заключается в том, что если вы используете кэширование фрагментов с помощью метода cache в вашем представлении, то теперь это будет работать лучше т.к. запрос к базе данных не будет выполняться до тех пор, пока в нем нет нужды.

Новый синтаксис запросов делает построения поисковых условий более простым. Давайте сообщим Ruby on Rails, что мы хотим отфильтровать статьи таким образом что будут показаны только статьи помеченные скрытыми, если нам передан параметр hidden=1 в адресной строке. Мы легко можем сделать такую фильтрацию выборки модифицировав экшен index:

/app/controllers/articles_controller.rb
def index
@articles = Article.order('name')
if params[:hidden]
@articles = @articles.where(:hidden =>(params[:hidden] == "1"))
end
respond_to do |format|
format.html # index.html.erb
format.xml  { render :xml => @articles }
end
end

Теперь мы проверим, как это работает, передав параметр hidden в метод where, который будет делать выборку только скрытых статей, если параметр hidden будет иметь значение 1. Если мы добавим параметр hidden к URL и обновим страницу, то мы увидим только скрытые статьи.
ruby on rails 3

Аналогично, если мы передадим параметру hidden значение 0, то увидим только видимые статьи.
ruby on rails 3

Способность объединить вместе методы как этот – это прекрасное решение, которое обеспечивает возможность построить более сложные запросы к базе данных пока известно, что запрос фактически исполняется до тех пор, пока есть необходимость в данных.

Named Scopes — Именованные пространства для поиска

Далее мы покажем вам некоторые из изменений, что произошли в именованных пространствах для поиска в Ruby on Rails 3. Ниже представлена наша модель Article с двумя именованными пространствами, одно забирает видимые статьи, а другое забирает все статьи, что опубликованы.

/app/models/article.rb
class Article < ActiveRecord::Base
named_scope :visible, :conditions => ["hidden != ?", true]
named_scope :published, lambda { {:conditions => ["published_at <= ?", Time.zone.now]} }
end

Эти именованные пространства объявлены в стиле Ruby on Rails 2, однако в Ruby on Rails 3 подход немного отличается. Первое отличие заключается в том, что метод для объявления именованного пространства стал носить более короткое название, вместо named_scope мы объявляем именованные пространства для поиска при помощи метода scope. Аналогичным с поиском образом, мы отказываемся от использования опций и заменяем их на одноименные методы, например мы используем метод where вместо опции :conditions. В синтаксисе Ruby on Rails 3 именованные пространства поиска будут выглядеть следующим образом:

/app/models/article.rb
class Article < ActiveRecord::Base
scope :visible, where("hidden != ?", true)
scope :published, lambda { where("published_at <= ?", Time.zone.now) }
end

Другим новшеством является возможность выстраивания пространств. Если вы хотите создать пространство именуемое recent, что будет возвращать недавно созданные видимые статьи ранжируя их по дате публикации, вы можете это сделать, при помощи повторного использования 2 ранее созданных пространств поиска:

scope :recent, visible.published.order("published_at desc") 

В нашем новом именованном пространстве поиска мы объединили вместе два ранее созданных пространства поиска, и метода order. Возможность создавать новые пространства поиска с использованием ранее созданных – это элегантное нововведение, позволяющее максимально придерживаться принципа DRY – не повторять один и тот же код. Мы можем испытать наше новое именованное пространство поиска в консоле. Если вы вызовете Article.recent, то объект возвратится экземпляр класса ActiveRecord::NamedScope::Scope. Это объект, который ведет себя похоже на объект ActiveRecord::Relation, о котором говорилось выше.

ruby-1.9.1-p378 > Article.recent
=> #<ActiveRecord::NamedScope::Scope:0x0000010318bd08 @table=#<Arel::Table:0x00000102740ea8
@name="articles", @options={:engine=>#<Arel::Sql::Engine:0x00000102651900 @ar=ActiveRecord::Base>},
@engine=#<Arel::Sql::Engine:0x00000102651900 @ar=ActiveRecord::Base>>, …

Если мы вызовем all в объекте Scope, то мы увидим возвращение соответствующей статьи.

ruby-1.9.1-p378 > Article.recent.all
=> [#<Article id: 1, name: "It's Ancient", published_at: "2010-01-01",
hidden: false, created_at: "2010-02-22 20:35:42", updated_at: "2010-02-22 23:00:16">]

На последок

Если у вас есть объект Relation или Scope и вы хотите увидеть SQL запрос к вашей базе данных, то вы можете применить метод to_sql для этого объекта.

ruby-1.9.1-p378 > Article.recent.to_sql
=> "SELECT     \"articles\".* FROM       \"articles\"
WHERE     (hidden != 't') AND (published_at <= '2010-02-22 22:47:12.023289')
ORDER BY  published_at desc"

С помощью этого метода, вы можете увидеть SQL запрос, который ActiveRecord выполнит для возврата недавно опубликованных статей, что не помечены как скрытые.

Оригинал на английском:

Tags:

Responses

  1. says:

    ноября 25, 2010 at 20:29 (#)

    Ну, тогда к Вам вопрос, почему, не смотря на то, что от Find решено отходить, scaffold генерирует код именно с Find, а не с Order?

  2. admin says:

    ноября 25, 2010 at 21:05 (#)

    vadimbaldin, вы должно быть имеете введу код типа:

     
    def show
        @s_category = SCategory.find(params[:id])
    
        respond_to do |format|
          format.html # show.html.erb
          format.xml  { render :xml => @s_category }
        end
      end
    

    В статье, которую я переводил об этом не говорилось, но я об этом обязательно напишу в следующей, темболее, что уже нашел приличную доку по этому вопросу, но вам отвечу:

    Дело в том, что find будет не полностью исключен, теперь он будет применяться для выборки по id:

    @products = Product.find(1,2,3,10,100,200)

    В примере выше будут выбраны 1я,2я,3я,…200я — записи о продуктах.

    Кроме того, Rails 3.0 это скорее переходная версия и от find будут отказываться уже в Rails 3.1. а в Rails 3.2 они будут полностью удалены и их поддержка будет осуществляться с помощью официального плагина от разработчиков Ruby on Rails (не полностью, а как я уже говорил, будет осуществляться выборка по id).

  3. says:

    января 19, 2011 at 11:40 (#)

    Спасибо, понятно.

  4. Dmitriy Nesteryuk says:

    апреля 18, 2011 at 13:49 (#)

    Спасибо, за перевод

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.