Rails 3 Tutorial: Модели: Простая выборка данных

февраля 20, 2011  |  Published in Ruby on Rails, Ruby on Rails 3  |  9 Comments

Ruby on Rails 3Прежде чем начачать, нам необходимо создать модель, таблицу и записи в таблице. Как это сделать читайте в статье Rails Tutorial: Модели: Введение. Итак, нам необхобима модель Person (id: integer, name: string, last_name: string, age: integer, sex: string, created_at: datetime, updated_at: datetime).

В этой статье мы рассмотрим следущие простейшие методы выборки или поиска данных:

.all
.first
.last
.find
.where
.order
.limit

… а также обслуживаемые с помощью method_missing методы:

find_by_
find_by_*_and_*
find_all_by_
find_all_by_*_and_*

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

Let it snow is go!
Метод .all просто возвращает нам все записи.
Пример:

Person.all
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 2, name: "Grigoriy", last_name: "Sopiga", age: 27, sex: "male", created_at: "2011-02-20 12:11:29", updated_at: "2011-02-20 12:11:29">,
#<Person id: 3, name: "Olena", last_name: "Kuznetzova", age: 21, sex: "female", created_at: "2011-02-20 12:13:38", updated_at: "2011-02-20 12:13:38">,
#<Person id: 4, name: "Marina", last_name: "Tomova", age: 23, sex: "female", created_at: "2011-02-20 12:14:10", updated_at: "2011-02-20 12:14:10">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">,
#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">,
#<Person id: 7, name: "Ivan", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:32", updated_at: "2011-02-20 12:15:32">]

Метод .all сам по себе применяется относительно редко, так как мало кому необходимы сразу все записи из базы данных. особенно когда записей несколько тысячь или миллионов. Место метода .all часто занимает метод .limit, который ограничивает количество выбираемых записей, например:

Person.limit(1)
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">] 

Метод .limit не просто хаотично выбирает произвольную запись, а руководствуется в своем выборе порядком следования записей. В данном конкретном случае  метод .limit с параметром 1 можно заменить на метод .first, который возвращает первую запись из таблицы. Под первой записью подразумевается не запись с id равным 1, а запись с минимальным id из всех существующих. Пример использования .first:

Person.first
=> #<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">

У метода .first существует метод — антоним: метод :last, который возвращает запись с максимальным id, то есть самую свежую:

Person.last
=> #<Person id: 7, name: "Ivan", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:32", updated_at: "2011-02-20 12:15:32">

Не путайте описанные здесь методы .first и .last из ActiveRecord::FinderMethods с методами .first и .last для массивов.

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

Person.find(1)
=> #<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">

Person.find(1,5)
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">]

Person.find(*(1..3))
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 2, name: "Grigoriy", last_name: "Sopiga", age: 27, sex: "male", created_at: "2011-02-20 12:11:29", updated_at: "2011-02-20 12:11:29">,
#<Person id: 3, name: "Olena", last_name: "Kuznetzova", age: 21, sex: "female", created_at: "2011-02-20 12:13:38", updated_at: "2011-02-20 12:13:38">]

Как видно из примеров, метод .find возвращает записи с определенным(и) id, который(е) мы передаем ему в качестве аргументов.

Пока мы здесь обсуждали .find, он, у нас за спиной, обзавелся детьми:

Метод .find_by_* на самом деле не определен в ActiveRecord::Base, этот метод является генерируемым автоматически, точнее его вызов генерирует ошибку NoMethodError, которая отлавливается в методе method_missing и обрабатывается. Метод .find_by_* на самом деле называется .find_by_<имя свойства>, где вместо имени свойства, для нашей модели мы можем подставить: id, name, last_name, sex, age, таким образом .find_by_ в нашем случае превращается аж в 5 методов:

Person.find_by_id(1)
=> #<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">

Person.find_by_name('Grigoriy')
=> #<Person id: 2, name: "Grigoriy", last_name: "Sopiga", age: 27, sex: "male", created_at: "2011-02-20 12:11:29", updated_at: "2011-02-20 12:11:29">

Person.find_by_last_name('Titov')
=> #<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">

Person.find_by_sex('female')
=> #<Person id: 3, name: "Olena", last_name: "Kuznetzova", age: 21, sex: "female", created_at: "2011-02-20 12:13:38", updated_at: "2011-02-20 12:13:38">

Person.find_by_age(21)
=> #<Person id: 3, name: "Olena", last_name: "Kuznetzova", age: 21, sex: "female", created_at: "2011-02-20 12:13:38", updated_at: "2011-02-20 12:13:38">

Существует также метод .find_by_*_and_*, который абсолютно идентичен методу .find_by_* с той лишь разницей, что производится поиск сразу по нескольким свойствам, это необходимо потому, что в базе данных может содержаться, например два разныхчеловека с именем Vasiliy, при этом метод .find_by_*_ будет возвращать только самую старую запись, а метод .find_by_*_and_* позволяет конкретизировать запрос:

Person.find_by_name_and_last_name('Vasiliy', 'Tomov')
=> #<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48"> 

Самое веселое здесь то, что метод .find_by_*_and_*_ не ограничивает вас на двух параметрах и позволяет вам реально вдоволь поизвращаться:

Person.find_by_name_and_last_name_and_sex_and_age('Vasiliy', 'Tomov', 'male', 27)
=> #<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">

У методов .find_by_*_ и .find_by_*_and_* есть один недостаток — они возвращают только первую запись соответствующую условию выборки. Если вы пользуетесь социальными сетями, например Вконтакте, то вы знаете, что можно найти 100500 Серег, живущих в Москве, имеющих возраст 20 лет и имеющих отчество Александрович. К сожалению .find_by_*_ и .find_by_*_and_* не в силах вернуть запись именно о том Сереже, который нам нужен, потому-то в Rails и созданы такие методы, как .find_all_by_* и .find_all_by_*_and_*, которые абсолютно идентичны  .find_by_*_ и .find_by_*_and_* с той лишь разницей, что возвращают массив всех соответствующих условию записей:

Person.find_all_by_sex('male')
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 2, name: "Grigoriy", last_name: "Sopiga", age: 27, sex: "male", created_at: "2011-02-20 12:11:29", updated_at: "2011-02-20 12:11:29">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">,
#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">,
#<Person id: 7, name: "Ivan", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:32", updated_at: "2011-02-20 12:15:32">]

Person.find_all_by_sex_and_last_name('male','Titov')
=> [#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">,
#<Person id: 7, name: "Ivan", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:32", updated_at: "2011-02-20 12:15:32">]

Согласитесь, все эти find — методы круты, но все это частные случаи одного, действительно могущественного метода — .where, который возвращает все записи соответствующие условию, а еще может принимать в качестве аргумента SQL — запрос. Пример использования:

Person.where(name:"Vasiliy")
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">]

Person.where(sex:"female")
=> [#<Person id: 3, name: "Olena", last_name: "Kuznetzova", age: 21, sex: "female", created_at: "2011-02-20 12:13:38", updated_at: "2011-02-20 12:13:38">,
#<Person id: 4, name: "Marina", last_name: "Tomova", age: 23, sex: "female", created_at: "2011-02-20 12:14:10", updated_at: "2011-02-20 12:14:10">]

Person.where(name:"Vasiliy", sex:"shemale")
=> [] # Фууух... Повезло!

Person.where(age:21, sex:"male")
=> [#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">,
#<Person id: 7, name: "Ivan", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:32", updated_at: "2011-02-20 12:15:32">]

Когда в качестве аргумента передается диапазон, то таким образом мы эмулируем использование SQL выражение BETWEEN:

Person.where(age:20..25)
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 3, name: "Olena", last_name: "Kuznetzova", age: 21, sex: "female", created_at: "2011-02-20 12:13:38", updated_at: "2011-02-20 12:13:38">,
#<Person id: 4, name: "Marina", last_name: "Tomova", age: 23, sex: "female", created_at: "2011-02-20 12:14:10", updated_at: "2011-02-20 12:14:10">,
#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">,
#<Person id: 7, name: "Ivan", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:32", updated_at: "2011-02-20 12:15:32">] 

Когда в качестве аргумента передается массив, то, как не сложно догадаться, выбираются все записи с параметрами соответствующими аргументам переданными в массиве, таким образом эмулируется SQL выражение IN:

Person.where(name:["Vasiliy","Anton"])
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">,
#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">]

Ах да, совсем забыли об использовании SQL в контексте оператора .where:

Person.where("name IN ('Vasiliy', 'Anton')")
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">,
#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">] 

В SQL, понятное дело, можно вставить Ruby код и он будет успешно интерполирован в что-то, что вам необходимо:

Person.where("age > #{20+2}")
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 2, name: "Grigoriy", last_name: "Sopiga", age: 27, sex: "male", created_at: "2011-02-20 12:11:29", updated_at: "2011-02-20 12:11:29">,
#<Person id: 4, name: "Marina", last_name: "Tomova", age: 23, sex: "female", created_at: "2011-02-20 12:14:10", updated_at: "2011-02-20 12:14:10">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">] 

Метод .where так крут, что по крутости занимает 2е место после Чака Норриса!

Еще один метод, который нам осталось рассмотреть — это метод .order. Кто знаком с SQL, у того в уме сразу появилась ассоциация с ORDER_BY, и да, вы правы. Метод .order используется для сортировки записей в определенном порядке по определенному критерию. Пример использования:


# Отсортируем человечишек по возросту, сначала старики, потом молодежь:

Person.order('age DESC')
=> [#<Person id: 2, name: "Grigoriy", last_name: "Sopiga", age: 27, sex: "male", created_at: "2011-02-20 12:11:29", updated_at: "2011-02-20 12:11:29">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">,
#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 4, name: "Marina", last_name: "Tomova", age: 23, sex: "female", created_at: "2011-02-20 12:14:10", updated_at: "2011-02-20 12:14:10">,
#<Person id: 3, name: "Olena", last_name: "Kuznetzova", age: 21, sex: "female", created_at: "2011-02-20 12:13:38", updated_at: "2011-02-20 12:13:38">,
#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">,
#<Person id: 7, name: "Ivan", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:32", updated_at: "2011-02-20 12:15:32">]

А теперь давайте отсортируем людей по их именам в алфавитном порядке, а при совпадении имет будет ориентироваться на возраст, чем старше, тем выше позиция:


Person.order('name ASC, age DESC')
=> [#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">,
#<Person id: 2, name: "Grigoriy", last_name: "Sopiga", age: 27, sex: "male", created_at: "2011-02-20 12:11:29", updated_at: "2011-02-20 12:11:29">,
#<Person id: 7, name: "Ivan", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:32", updated_at: "2011-02-20 12:15:32">,
#<Person id: 4, name: "Marina", last_name: "Tomova", age: 23, sex: "female", created_at: "2011-02-20 12:14:10", updated_at: "2011-02-20 12:14:10">,
#<Person id: 3, name: "Olena", last_name: "Kuznetzova", age: 21, sex: "female", created_at: "2011-02-20 12:13:38", updated_at: "2011-02-20 12:13:38">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">,
#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">]

Вот теперь и думайте, нужно ли Ruby \ Rails программистам знать SQL или нет.

Знали вы или нет, но некоторые вышеперечисленные методы можно комбинировать, например:


Person.order('name ASC, age DESC').find(1,2)
=> [#<Person id: 2, name: "Grigoriy", last_name: "Sopiga", age: 27, sex: "male", created_at: "2011-02-20 12:11:29", updated_at: "2011-02-20 12:11:29">,
#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">]

Person.order('name ASC, age DESC').limit(3)
=> [#<Person id: 6, name: "Anton", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:13", updated_at: "2011-02-20 12:15:13">,
#<Person id: 2, name: "Grigoriy", last_name: "Sopiga", age: 27, sex: "male", created_at: "2011-02-20 12:11:29", updated_at: "2011-02-20 12:11:29">,
#<Person id: 7, name: "Ivan", last_name: "Titov", age: 21, sex: "male", created_at: "2011-02-20 12:15:32", updated_at: "2011-02-20 12:15:32">]

Person.where(name: "Vasiliy").limit(1)
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">]

Person.limit(5).find_all_by_name("Vasiliy")
=> [#<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">,
#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">]

#под конец самый безсмысленный пример:

Person.limit(5).first
=> #<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">

Методы, которые объединяются в цепочку называются chainable методам (от слова chain — цепь, соединять). Такими методами называются все методы из вышеперечисленных, которые возвращают экземпляр класса ActiveRecord::Relation, а именно: .where, .limit и .order.  Все остальные методы не позволяют присоединять другие методы после себя и являются финазирующими условие выборки методами. Одна из следующих статей будет посвящена  LazyLoading’у — ленивой загрузке (ленивым запросам) к базе данных в которой мы лучше разберемся с chainable методами и методами финализирующими условие выборки.

Спасибо за внимание и ждите с нетерпением следущей статьи=)

Особая благодарность , который указал в комментариях на некоторые неточности и ошибки в статье.

P.S. Объявляется конкурс на создание логотипа для RubyDev Rails Tutorial. Присылайте свои работы мне на egotraumatic[гав-гав]gmail.com. Дизайнеру-победетелю реклама его услуг и сылка на портфолио. Лого должно содержать собственно слова RubyDev Rails Tutorial — это единственное требование. Если никто, ничего не пришлет -  пеняйте на себя: я сам какую-то бяку нарисую и вам придется видеть ее в каждой статье =) Присылайте работы в PSD, времени 1 неделя.

Лучшая благодарность автору — ваши комментарии!

Tags: , , ,

Responses

  1. says:

    февраля 20, 2011 at 21:44 (#)

    >> Метод .limit не просто хаотично выбирает произвольную запись, а руководствуется в >> своем выборе уникальным идентификатором записи (столбцом id)

    Вот тут как раз с точностью до «наоборот». first хаотичен и зависим от БД, id руководствуется #last

    > User.all.map {|u| u.id }
      User Load (0.6ms)  SELECT "users".* FROM "users"
     => [2, 3, 1] 
    ruby-1.9.2-p136 :019 > User.all.first
      User Load (0.6ms)  SELECT "users".* FROM "users"
     => #>

    Методу .limit с параметром 1 соответствует метод .first, который возвращает
    >> первую запись из таблицы. Под первой записью подразумевается не запись с id >> равным 1, а запись с минимальным id из всех существующих

    limit(1) и first не одно и то же

    User.limit(1).class
     => ActiveRecord::Relation 
    ruby-1.9.2-p136 :030 > User.first.class
     => User(id, ...
    

    where, limit, order … это все chainable методы, которые можно выстроить в цепочку для lazy load.
    all, first, last — «обналичивают выборку», прерывают цепочку и отпаравлют запрос я базу и позвращают объекты или их масив
    так работает

    > User.limit(1).first
    => User Load (0.6ms)  SELECT "users".* FROM "users" LIMIT 1
    

    а так

     > User.first.limit(1)
    => undefined method `limit' for #
    

    where лучше вызывать так User.where(‘age > ?’, 3) чтобы небыло проблем с квотированием аргументов

  2. admin says:

    февраля 20, 2011 at 23:35 (#)

    Роман, вы немного сумбурно написали комментарий и, возможно, я не правильно понял вас в некоторых моментах.

    Определение .last из официальной документации:
    A convenience wrapper for find(:last, *args). You can pass in all the same arguments to this method as you can to find(:last).

    То же актуально и для .first. То есть оба метода ориентируются на ID записи. Не следует путать .first и .last из ActiveRecord с методами массива, которые ориентируются на порядок следования элементов.

    По поводу того, что .limit не связан с БД, а ориентируется на порядке следования записей в массиве, то вы правы, здесь я допустил ошибку, сейчас поправлю. Спасибо, что заметили!

    p=Person.where(id:[1,3,5]).order('id DESC')
    => [#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">, #<Person id: 3, name: "Olena", last_name: "Kuznetzova", age: 21, sex: "female", created_at: "2011-02-20 12:13:38", updated_at: "2011-02-20 12:13:38">, #<Person id: 1, name: "Vasiliy", last_name: "Ivanov", age: 25, sex: "male", created_at: "2011-02-20 12:10:53", updated_at: "2011-02-20 12:10:53">]
    p.limit 1
    => [#<Person id: 5, name: "Vasiliy", last_name: "Tomov", age: 27, sex: "male", created_at: "2011-02-20 12:14:48", updated_at: "2011-02-20 12:14:48">]
    

    .limit(1) и first это не одно и тоже и я это знаю, просто в конкретно том примере они возвращают один и тот же результат. Сейчас, поправлю, чтобы не было двусмысленности.

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

  3. admin says:

    февраля 21, 2011 at 00:35 (#)

    >>> where лучше вызывать так User.where(‘age > ?’, 3) чтобы небыло проблем с квотированием аргументов

    По сравнению с чем лучше, с
    User.where(‘age > 3’) или с вариантом, где вставляется Ruby код?

    И что это за проблемы с квотированием?

    P.S. что используете для вывода времени выполнения и самого SQL запроса? rails c —debugger или что-то другое? Не подскажите, что для 1.9.2 можно использовать, так как ruby-debuger не совместим.

  4. admin says:

    февраля 21, 2011 at 01:26 (#)

    c debugger’ом разобрался, необходим gem ruby-debug19.

  5. says:

    февраля 21, 2011 at 09:14 (#)

    [blockquote]Если на клетке слона прочтёшь надпись «буйвол», не верь глазам своим.[/blockquote] (С) Козьма Прутков

    Это я про чтение документации и перевод чужих статей.

    [blockquote]Лучшая документация — это исходные коды и собственная практика.
    Roman V. Babenko[/blockquote]

    SQL это работа с множества, которые сами по себе не упорядочены. Первый мой пример показывает, что результирующий SQL #first от вывода #last отличается наличием секции order у последнего.

     > User.last
      User Load (0.9ms)  SELECT "users".* FROM "users" ORDER BY users.id DESC LIMIT 1

    С #first ситуация

     > User.first
      User Load (0.7ms)  SELECT "users".* FROM "users" LIMIT 1

    так, что хорошо видно, что никакого id #first нет и записи будут выбираться хаотично на усмотрение внутренней организации БД.

    [blockquote]
    По поводу того, что .limit не связан с БД, а ориентируется на порядке следования записей в массиве,[/blockquote]

    Вот тут я ничего не понял и ничего не замечал. В твоем примере «p» не являлось массивом, а ActiveRecord::Relation. И limit не получил масив, а только присоединился к SQL выражению.

    [blockquote].limit(1) и first это не одно и тоже и я это знаю, просто в конкретно том примере они возвращают один и тот же результат[/blockquote]

    Это чистая случайность. Неправильный код тоже иногда работает как сломаные часы показывают правильное время два раза в день.

    > p=User.where(id:[3,1,2]).order('id desc')
      User Load (0.8ms)  SELECT "users".* FROM "users" WHERE ("users"."id" IN (3, 1, 2)) ORDER BY id desc
    => > p.class
     => ActiveRecord::Relation 
    

    В SQL нельзя задать множество значений поля и надеяться на получение результата в таком же порядке. Эсть только две предопределенных стратегии asc и desc. Все остальное чистые совпадения.

    Мда. Количество ошибочных ситуаций растет экспоненциально.
    Наверное будет проще если у нас бедет одна и та же клетка перед глазами.
    в консоли подменяем логгер

    ActiveRecord::Base.logger = Logger.new(STDOUT) 

    теперь можно видеть запросы которые идут в базу

    Проблема с квотированием возникает когда в значении параметра находятся различные кавычки. Различные БД по разному реагируют на различные кавычки.
    потому
    User.where(‘name = «`a’»’ — это твоя проблема c экранированием, а User.where(‘age > ?’, ‘»`a’»‘) проблема rails

  6. admin says:

    февраля 21, 2011 at 15:23 (#)

    по поводу .first и .last суть не в том, какой SQL получается, а в том, что они делают, их поведение в официальной документации описано как:

    A convenience wrapper for find(:last, *args). You can pass in all the same arguments to this method as you can to find(:last).

    Т.е. они таки возвращают записи с минимальным и максимальным id исходя из определения метода .find. Ну, или если нет, то приведите примеры, когда они возвратят значение не по id. Ну да, если в базу записана сначала запись с большим id, а затем с меньшим, то .first вернет ту, которая записана раньше, а не с меньшим id, однако когда такое происходит? id почти всегда генерируются автоматически.

    >>>.limit(1) и first это не одно и тоже и я это знаю, просто в конкретно том примере они возвращают один и тот же результат

    >>> >>>Это чистая случайность. Неправильный код тоже иногда работает как сломаные часы показывают правильное время два раза в день.

    Ну да, я так и исправил, что .limit(1) в данном конкретном случае можно заменить на .first.

    За
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    спасибо! Я еще не разбирался с дебагом, записью логов и настройкой рабочей среды.

  7. says:

    февраля 22, 2011 at 11:42 (#)

    Бугагашеньки

    Попробуй User.first(:order => ‘email DESC’)

  8. says:

    февраля 22, 2011 at 11:52 (#)

    Покажи мне выдержку и ссылку из документации где сказано про максимальный и минимальный id?!?

  9. admin says:

    февраля 22, 2011 at 15:58 (#)

    hoblin, если вы заметили, в статье нет модели User, но есть модель Person.

    Признаю свою ошибку, чуть позже поправлю и дополню статью. Дело в том, что я сделал ложный вывод после прочтения информации о find в Rails 3.1, который будет работать только с id записей. Далее я нашел в оригинальной документации информацию, о том, что .first и .last обертки для .find(:first) и .find(:last), почему, то показалось очевидным, что они так же работают ориентируюсь на ID.

    На самом деле более правильно определение звучит так: Возвращают первую или последнюю запись и результата выполнения SQL запроса (ну а на самом деле формируют такой запрос, что оба возвращают первую запись):

    Person.last(:order => ‘age DESC’)
    SELECT «people».* FROM «people» ORDER BY age ASC LIMIT 1

    Person.last(:order => ‘age ASC’)
    SELECT «people».* FROM «people» ORDER BY age DESC LIMIT 1

    Я правильно здесь сформулировал суть?

    P.S. Спасибо за подсказки

Leave a Response

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