Undr

На память

Интерфейс запросов ActiveRecord

1 Star2 Stars3 Stars4 Stars5 Stars (2 голосов, средний: 5.00 из 5)
Loading ... Loading ...

with 3 comments

Перевод: Active Record Query Interface

Это руководство описывает различные способы получать данные от базы данных, используя ActiveRecord. Прочтя это руководство, Вы сможете:

  • Искать записи в базе данных с помощью множества методов и всевозможных условий
  • Определять сортировку, требуемые атрибуты, группировку и другие свойства для поиска записей
  • Использовать отложенную загрузку, чтобы сократить количество запросов к базе данных, необходимых для поиска
  • Использовать динамические методы поиска
  • Создавать именованные области видимости (named_scope), для добавления новых средств поиска Ваших моделей
  • Проверять существование определенных записей
  • Выполнять различные расчеты в моделях ActiveRecord

Если Вы привыкли использовать чистый SQL для поиска в базе, то обнаружите что в Rails есть более удобные способы делать тоже самое. В большинстве случаев ActiveRecord избавит Вас от необходимости писать SQL код.

Ниже определены модели, которые используют примеры этого руководства:

Все эти модели использует id в качестве первичного ключа, если, конечно, не оговорено другое

class Client < ActiveRecord::Base
  has_one :address
  has_one :mailing_address
  has_many :orders
  has_and_belongs_to_many :roles
end
class Address < ActiveRecord::Base
  belongs_to :client
end
class MailingAddress < Address
end
class Order < ActiveRecord::Base
  belongs_to :client, :counter_cache => true
end
class Role < ActiveRecord::Base
  has_and_belongs_to_many :clients
end

ActiveRecord совместима со множеством баз данных (к примеру: MySQL, PostgreSQL и SQLite). Независимо от используемых баз данных, ActiveRecord предоставляет унифицированный доступ к ним.

Поиск объектов в базе данных

Для поиск объектов в базе данных, ActiveRecord предоставляет метод класса Model.find. Этот метод позволяет осуществлять поиск в баз,е используя разные параметры, без необходимости использовать чистый SQL.

Так что же делает метод Model.find(options)?

  • Преобразует переданные параметры в SQL запрос.
  • Выполняет SQL запрос и получает требуемые строки из базы данных.
  • Создает эквивалентный Ruby объект, соответствующей модели данных для каждой найденной строки.
  • Выполняет обратные вызовы after_find если таковые имеются.

Поиск единственного объекта

Active Record позволяет искать единственный объект тремя различными способами.

Используя первичный ключ

Используя Model.find(primary_key, options = nil), Вы ищите объект у которого первичный ключ равен primary_key. К примеру:

# Найти клиента с первичный ключом (id) 10.
client = Client.find(10)
=> #<Client id: 10, name: => "Ryan">

Этот код выполнит такой SQL запрос:

SELECT * FROM clients WHERE (clients.id = 10)

Если нет ни одной записи соответствующей условию, то метод Model.find(primary_key) вызывает исключение ActiveRecord::RecordNotFound.

Используя метод first

Model.first(options = nil) ищет первую строку, соответствующую условию отпределенному в options. Если options неопределен, то возвращается первая запись. К примеру:

client = Client.first
=> #<Client id: 1, name: => "Lifo">

SQL запрос:

SELECT * FROM clients LIMIT 1

Model.first возвращает nil если ни одной записи не найлено. Исключение не вызывается.

Model.find(:first, options) равноценен Model.first(options)

PS: Второй вариант устарел

Используя метод last

Метод Model.last(options = nil) возвращает последнюю запись соответстующую условию отпределенному в options. Если options неопределена, то возвращается последняя запись. К примеру:

client = Client.last
=> #<Client id: 221, name: => "Russel">

SQL запрос:

SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1

Model.last возвращает nil если ни одной записи не найлено. Исключение не вызывается.

Model.find(:last, options) равноценен Model.last(options)

PS: Второй вариант устарел

Поиск множетва объектов

Используя масив первичных ключей

Model.find(array_of_primary_key, options = nil) так же принимает массив с первичными ключами. Будет возвращен массив с соответствующими записями. К примеру:

# Найти клиентов с первичными ключами 1 и 10.
client = Client.find(1, 10) # Или так Client.find([1, 10])
=> [#<Client id: 1, name: => "Lifo">, #<Client id: 10, name: => "Ryan">]

SQL запрос:

SELECT * FROM clients WHERE (clients.id IN (1,10))

Если хоть одна запись не найдена, то метод Model.find(array_of_primary_key) вызовет исключение ActiveRecord::RecordNotFound.

Используя find :all

Метод Model.all(options = nil) находит все записи соответствующие условию, описанному в options. Если options не указан, то возвращаются все записи из таблицы.

# Найти всех клиентов.
clients = Client.all
=> [#<Client id: 1, name: => "Lifo">, #<Client id: 10, name: => "Ryan">, #<Client id: 221, name: => "Russel">]

SQL запрос:

SELECT * FROM clients

Метод Model.all возвращает пустой масив, если нет совпадений. Исключение не вызывается.

Model.find(:all, options) соответствует Model.all(options)

PS: Второй вариант устарел

Поиск множества объектов в пакетной обработке

Иногда нужно выполнить итерации по большому набору записей в базе данных. Например разослать спам по базе пользователей :) , экспортировать данные, и т.д.

Первое что приходит в голову:

# Очень не эффективно если в базе более тысячи строк.
User.all.each do |user|
  NewsLetter.weekly_deliver(user)
end

Если в базе слишком много данных, то этот подход слишком накладный. Это потому что метод User.all заставляет ActiveRecord выбрать всю таблицу и держать ее в памяти. Для большого количества объектов может потребоваться слишком много памяти.

Используя find_each

Для эффективной итерации по таблице с большим количеством записей, ActiveRecord предоставляет метод find_each

User.find_each do |user|
  NewsLetter.weekly_deliver(user)
end

Конфигурирование размера выборки

Поумолчанию find_each последовательно выбирает по 1000 строк из базы данных и выполняет код в блоке, и так до тех пор пока не обойдет всю таблицу. Это значение можно переопределить с помощью параметра :batch_size.

Выбрать пользователей по 5000 штук:

User.find_each(:batch_size => 5000) do |user|
  NewsLetter.weekly_deliver(user)
end

Начало выборки с определенного первичного ключа

Записи выбираются в порядке возрастания первичного ключа, который должен быть целым числом. Опция :start позволяет изменить первичный ключ с которого начнется выборка. Это может быть полезно когда, например, мы хотим возобновить возобновить прерванную пакетную обработку, правда для этого нужно сохранять последний обработанный идентификатор.

Начать пакетную обработку пользователей, начиная с пользователя, с первичным ключом равным 2000:

User.find_each(:batch_size => 5000, :start => 2000) do |user|
  NewsLetter.weekly_deliver(user)
end

Дополнительные параметры

Метод find_each принимает теже параметры что и обычный метод find. Однако :order и :limit используются методом и следовательно не могут быть переданны явно.

Используя метод find_in_batches

Вы так же можете работать с массивом записей, а не отдельной записью, используя метод find_in_batches. Этот метод действует так же как и find_each, но в блок передает массив объектов.

# Работать с кусками по 1000 счетов за раз.
Invoice.find_in_batches(:include => :invoice_lines) do |invoices|
  export.add_invoices(invoices)
end

Это передаст в блок массив счетов по 1000 за раз.

Условия

Метод find позволяет Вам определять условия для выборки записей, представляя раздел в WHERE оператора SELECT эти условия. Условия могут быть определены как строка, массив, или хэш.

Условия как строка

Если бы Вы хотели добавить условия к запросу, Вы могли бы только определить ключ :conditions, так как в примере: Client.first (:conditions => "orders_count = '2'"). Это найдет всех клиентов, у которых значение поля orders_count равно 2.

Но такой подход череват проблемами с безопасностью приложения, так как открывает лазейку для SQL инъекций. Например, Client.first(:conditions => "name LIKE '%#{params[:name]}%'") не безопасен. Как этого избежать? Смотри следующий раздел для безопасного способа определять условия, используя массив.

Условия как масив

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

Client.first(:conditions => ["orders_count = ?", params[:orders]])

ActiveRecord оставит перый элемент в условии и заменит в нем все вопросительные знаки (?) на на последующие элементы массива. (То есть размер массива должен быть на 1 элемент больше, чем число вопросительных знаков в условии)

Или если Вы хотите определить два условия, Вы можете сделать это как:

Client.first(:conditions => ["orders_count = ? AND locked = ?", params[:orders], false])

В этом примере первый вопросительный знак будет заменен значением params[:orders], а второй будет замененен SQL вариантом false, который зависит от адаптера.

Причина почему надо делать так:

Client.first(:conditions => ["orders_count = ?", params[:orders]])

а не так:

Client.first(:conditions => "orders_count = #{params[:orders]}")

состоит в безопасной передаче параметров. Если мы разместим переменную непосредтвенно в строке SQL, то она будет передана базе данных без изменений. Это означает, что в запрос попадут непроверенные данные переданные пользователем, а они могут содержать зловредный код. В таком случае Вы создадите потенциальную угрозу для своей базы данных, потому что, как только пользователь узнает что может использовать Вашу базу в обход логики приложения, он обязательно этим воспользуется. Никогда не помещайте данные переданные пользователем непосредственно в строку запроса.

Для получения дополнительной информации о SQL injection, читайте Ruby on Rails Security Guide.

Именованные параметры

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

Client.all(:conditions => ["created_at >= :start_date AND created_at <= :end_date", { :start_date => params[:start_date], :end_date => params[:end_date] }])

Это удобнее особенно если у Вас большое число параметров

Условия диапазона

Если Вы ищете диапазон значений в таблице (например, пользователей созданных в определенном промежудке), Вы можете использовать подставить ruby-диапазон в оператор IN. Если в контроллер передали две даты, Вы бы так могли бы найти записи созданные в этом промежутке:

Client.all(:conditions => ["created_at IN (?)", (params[:start_date].to_date)..(params[:end_date].to_date)])

ActiveRecord сгенерировала бы запрос который является допустимым для небольших диапазонов, для больших диапазонов он не так хорош. Например, если Вы хотите получить данные за год, в котором 365 дней (или 366, в зависимости от года).

SELECT * FROM users WHERE (created_at IN ('2007-12-31','2008-01-01','2008-01-02','2008-01-03','2008-01-04','2008-01-05',
                                          '2008-01-06','2008-01-07','2008-01-08','2008-01-09','2008-01-10','2008-01-11',
                                          '2008-01-12','2008-01-13','2008-01-14','2008-01-15','2008-01-16','2008-01-17',
                                          '2008-01-18','2008-01-19','2008-01-20','2008-01-21','2008-01-22','2008-01-23',
                                          ...
                                          '2008-12-15','2008-12-16','2008-12-17','2008-12-18','2008-12-19','2008-12-20',
                                          '2008-12-21','2008-12-22','2008-12-23','2008-12-24','2008-12-25','2008-12-26',
                                          '2008-12-27','2008-12-28','2008-12-29','2008-12-30','2008-12-31'))

Условия для даты и времени

Действительно ужасные последствия могут быть, когда Вы используете диапазон Time объектов в условии, поскольку ActiveRecord попытается сравнить поле с каждой секундой в диапазоне:

Client.all(:conditions => ["created_at IN (?)", (params[:start_date].to_date.to_time)..(params[:end_date].to_date.to_time)])
SELECT * FROM users WHERE (created_at IN ('2007-12-01 00:00:00', '2007-12-01 00:00:01' ... '2007-12-01 23:59:59', '2007-12-02 00:00:00'))

Это может стать причиной появления ошибки, например MySQL возвратит ошибку: Got a packet bigger than 'max_allowed_packet' bytes: _query_

В этом примере было бы лучше использовать операторы > и <

Client.all(:conditions => ["created_at > ? AND created_at < ?", params[:start_date], params[:end_date]])

Вы можете так же использовать операторы >= и <=

Client.all(:conditions => ["created_at >= ? AND created_at <= ?", params[:start_date], params[:end_date]])

Точно также как в Ruby. Если Вы хотите использовать краткий синтаксис, прочитайте раздел “Условия как хэш” этого руководства.

Условия как хэш

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

ВЫ можете использовать только проверки на равенство, на наличие в диапазоне и на массив значений

Проверка на равенство

Client.all(:conditions => { :locked => true })

В качестве названия поля можно использовать и строку

Client.all(:conditions => { 'locked' => true })

Проверка на наличие в диапазоне

Хорошо то, что мы можем использовать диапазон без генерации большого запроса как в предидущем разделе.

Client.all(:conditions => { :created_at => (Time.now.midnight - 1.day)..Time.now.midnight})

Этот запрос находит всех клиентов созданных вчера используя BETWEEN оператор.

SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00')

Это демонстрирует более короткий синтаксис для примеров в разделе “Условия как массив”.

Проверка на массив значений

Если Вы хотите найти записи, используя выражение IN, Вы можете передать массив в хэш условий:

Client.all(:conditions => { :orders_count => [1,3,5] })

Будет сгенерирован такой SQL запрос:

SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))

Параметры find

Кроме :conditions, Model.find принимает множество других параметров для того, чтобы настроить результат.

Model.find(id_or_array_of_ids, options_hash)
Model.find(:last, options_hash)
Model.find(:first, options_hash)
Model.first(options_hash)
Model.last(options_hash)
Model.all(options_hash)

Последующие разделы дают краткий обзор всех возможных параметров для options_hash.

Сортировка

Чтобы получить данные в определенном порядке, Вы должны определить параметр :orderпри вызове метода find.

Например, если Вы хотите получть список клиентов отсортированный по полю created_at, от должны сделать так:

Client.all(:order => "created_at")

Вы можете так же добавить ASC и DESC

Client.all(:order => "created_at DESC") # OR Client.all(:order => "created_at ASC")

Или сортировку по множественным полям:

Client.all(:order => "orders_count ASC, created_at DESC")

Выборка определенных полей таблицы

По умолчанию, Model.find возвращает все поля таблицы, используя SELECT *.

Чтобы выбрать определенные поля таблицы, Вы можете определить их в параметре :select.

Если параметр :select использоваться, то все объекты будут доступны только для чтения.

Например, чтобы выбрать колонки только viewable_by и locked, используйте:

Client.all(:select => "viewable_by, locked")

SQL запрос будет выглядеть так:

SELECT viewable_by, locked FROM clients

Будьте внимательны, потому что это также означает, что Вы инициализируете объект модели с только с теми аттрибутами, какие Вы выбрали. Если Вы попытаетесь обратиться к аттрибуту, который небыл инициализирован, то получите ошибку: ActiveRecord::MissingAttributeError: missing attribute: <attribute>

Попытка обратиться к аттрибуту id не вызовет исключения ActiveRecord:: MissingAttributeError, так что будьте внимательны, работая с ассоциациями, потому что для своей работы они нуждаются в аттрибуте id.

Параметре :select можно использовать функции SQL. Например, если Вам нужно получить записи из таблицы с уникальными значениями определенного поля используя функцию DISTINCT:

Client.all(:select => "DISTINCT(name)")

Параметры :limit и :offset

Чтобы использовать SQL конструкцию LIMIT, Вы должны передать Model.find параметры :limit и :offset.

:limit – максимальное число записей, которые вернет метод, а :offset – это начиная с какой записи будут возвращены данные. Например:

Client.all(:limit => 5)

Этот код возратит максимум пять клиентов и, так как не определено смещение, это будут первые пять клиентов в таблице. Выпонится такой SQL запрос:

SELECT * FROM clients LIMIT 5

Если определены оба параметра: и :limit, и :offset:

Client.all(:limit => 5, :offset => 5)

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

SELECT * FROM clients LIMIT 5, 5

Групировка

For example, if you want to find a collection of the dates orders were created on:

Чтобы использовать конструкцию GROUP BY в SQL, генерируемому методом Model.find, Вы должны определить параметр :group.

Например:

Order.all(:group => "date(created_at)", :order => "created_at")

Это вернет по одному объекту Order для каждой даты, в которой есть заказы.

Получается такой SQL запрос:

SELECT * FROM orders GROUP BY date(created_at)

Параметр :having

Чтобы использовать конструкцию HAVING в SQL, которая определяет условия для групировки GROUP BY, генерируемому методом Model.find, Вы должны определить параметр :having.

Например:

Order.all(:group => "date(created_at)", :having => ["created_at > ?", 1.month.ago])

Вызов этого метода сгенерирует запрос:

SELECT * FROM orders GROUP BY date(created_at) HAVING created_at > '2009-01-15'

Метод возвратит по одному заказу за каждый день прошлого месяца

Объекты только для чтения

Чтобы запретить модификацию/удвление отчетов, возвращенных Model.find, Вы должны определить параметр :readonly как true.

Любая попытка изменить или удалить объект будет вызывать исключение ActiveRecord::ReadOnlyRecord. К примеру так:

Client.first(:readonly => true)

Следующий код вызовет исключение ActiveRecord::ReadOnlyRecord:

client = Client.first(:readonly => true)
client.locked = false
client.save

Блокировка объектов для модификаций

Блокировка нужна для предотвращения одновременного изменения строки таблицы разными процессами, гарантируя атомарность модификаций. ActiveRecord обеспечивает два механизма блокировки:

  • Оптимистическая блокировка
  • Пессимистическая блокировка

Оптимистическая блокировка

Оптимистическая блокировка позволяет разным пользователям обращаться к одной и той же записи в таблици для редактирования, без возникновления конфликтов данных. Перед сохранением происходит проверка была ли изменена запись другим процессом после загрузки. Если изменение было то вызывается исключение ActiveRecord:: StaleObjectError и сохранении записи не происходит.

Столбец оптимистической блокировки

Чтобы использовать оптимистическую блокировку создайте в таблице столбец lock_version. Каждый раз, когда запись обновляется, ActiveRecord увеличивает на один значение в столбеце lock_version, если значение lock_version изенилось значит другой процесс модифицирова данные, значит вызывается исключение ActiveRecord:: StaleObjectError. Пример:

c1 = Client.find(1)
c2 = Client.find(1)
c1.name = "Michael"
c1.save
c2.name = "should fail"
c2.save # Raises a ActiveRecord::StaleObjectError

Если возникает конфликт данных Вы должны перехватить исключение и откатить назад изменения или как-то иначе решить конфликт. Вы так же должны быть уверены в том что колонка lock_version имеет значение по-умолчанию равное 0

Это поведение по-умолчанию, его можно отменить использя настройку ActiveRecord::Base.lock_optimistically = false.

Для смены названия блокировочной колонки ActiveRecord::Base имеет метод set_locking_column:

class Client < ActiveRecord::Base
  set_locking_column :lock_client_column
end

Пессимистическая блокировка

Пессимистическая блокировка использует механизм поддерживаемый базой данных. Передайте :lock => true в метод Model.find для включения эксклюзивной блокировки на выбранные строки. Model.find с :lock обычно выполняется внутри трансакций, для отката назад вслучае конфликта.

К примеру:

Item.transaction do
  i = Item.first(:lock => true)
  i.name = 'Jones'
  i.save
end

Этот код сгенерирует такой SQL код для mySQL:

SQL (0.2ms) BEGIN
Item LOAD (0.3ms) SELECT * FROM `items` LIMIT 1 FOR UPDATE
Item UPDATE (0.4ms) UPDATE `items` SET `updated_at` = '2009-02-07 18:05:56', `name` = 'Jones' WHERE `id` = 1
SQL (0.8ms) COMMIT

В параметр :lock можно передать чистый SQL код, чтобы использовать различные типы блокировок. Например в mySQL есть выражение LOCK IN SHARE MODE для блокировки строки с возможностью другим запросам читать данные. Чтобы использовать эту блокировку, передайте это выражение в параметре :lock:

Item.transaction do
  i = Item.find(1, :lock => "LOCK IN SHARE MODE")
  i.increment!(:views)
end

Соединение таблиц

Метод Model.find имеет параметр :joins для соединения таблиц с помощью SQL выражения JOIN. Существует множество различных способов определить параметр :joins:

Использование фрагмента SQL

Вы можете передать параметром :joins чистый SQL код, он будет подставлен в запрос как JOIN выражение. Например:

Client.all(:joins => 'LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')

создаст вот такой SQL запрос:

SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id

Использование масива/хэша для именованной ассоциации

Этот способ работает только как INNER JOIN.

ActiveRecord позволяет использовать имена ассоциаций, определенных в модели, как ссылку для параметра :joins.

К примеру создадим модели Category, Post, Comments и Guest:

class Category < ActiveRecord::Base
  has_many :posts
end
class Post < ActiveRecord::Base
  belongs_to :category
  has_many :comments
  has_many :tags
end
class Comments < ActiveRecord::Base
  belongs_to :post
  has_one :guest
end
class Guest < ActiveRecord::Base
  belongs_to :comment
end

Все последующие вызовы будут связывать таблицы используя выражение INNER JOIN:

Присоединение одной ассоциации

Category.all :joins => :posts

создаст SQL запрос:

SELECT categories.* FROM categories INNER JOIN posts ON posts.category_id = categories.id

Присоединение нескольких ассоциаций

Post.all :joins => [:category, :comments]

создаст SQL запрос:

SELECT
    posts.*
FROM
    posts
    INNER JOIN categories ON posts.category_id = categories.id
    INNER JOIN comments ON comments.post_id = posts.id

Присоединение вложенных ассоциаций (Один уровень вложенности)

Post.all :joins => {:comments => :guest}

Присоединение вложенных ассоциаций (Multiple Level)

Category.all :joins => {:posts => [{:comments => :guest}, :tags]}

Определение условий для присоединенных ассоциаций

Вы можете указать определенные условия для присоединенных таблиц, используя обычный массив или строку в параметре :conditions. При передачи хэша требуется специальный синтаксис для определения того к каким таблицам относится условие:

time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.all :joins => :orders, :conditions => {'orders.created_at' => time_range}

Другой более правильный синтаксис использует вложенный хэш:

time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.all :joins => :orders, :conditions => {:orders => {:created_at => time_range}}

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

Немедленная загрузка ассоциаций

Немедленная загрузка – это механизм загрузки связанных объектов, возвращаемых методом Model.find, используя несколько запросов.

Проблема N + 1

Рассмотрим следующий код, который находит 10 клиентов и печатает и почтовый индекс:

clients = Client.all(:limit => 10)
clients.each do |client|
  puts client.address.postcode
end

С первого взгляда выглядит неплохо. Но проблема в количестве запросов к базе данных. Код выполняет один запрос чтобы найти 10 клиентов + 10 запрсов (один на каждого найденного клиента) чтобы узнать почтовый индекс. Итого 11 запросов к базе. И чем больше значение параметра :limit, тем будет больше запросов.

Решение проблемы N + 1

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

Если вернуться к предыдущему случаю, то код нужно переписать используя немедленную загрузку ассоциации :address:

clients = Client.all(:include => :address, :limit => 10)
clients.each do |client|
  puts client.address.postcode
end

Этот код выполнит 2 запроса к базе, а не 11 как в предыдущем случае:

SELECT * FROM clients LIMIT 10
SELECT addresses.* FROM addresses WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10))

Немедленная загрузка нескольких ассоциаций

ActiveRecord позволяет указывать любое количество ассоциаций в одном вызове метода Model.find, просто передайте в параметр :include массив или хэш, так же можно передать вложенный хэш, для вложенных ассоциаций.

Использование массива

Post.all :include => [:category, :comments]

Загрузятся все публикации и связанные объекты (категории и комментарии) для каждой публикации.

Хэш для вложенных связей

Category.find 1, :include => {:posts => [{:comments => :guest}, :tags]}

Этот код найдет категорию с id равным 1, также загрузит публикации из этой категории. Дополнительно загрузятся тэги и комментарии для каждой публикации. Для комментариев будут загружены ассоциации :guest.

Определение условий для немедленной загрузки

Несмотря на то, что ActiveRecord позволяет определять условия выборки для зспросов с немедленной загрузкой, так же как с :joins, все же рекомендуется вместо этого использовать :joins.

Динамические средства поиска

Для каждого поля (или атрибута) определенного в вашей таблице ActiveRecord поддерживает поисковый метод. Эсли вы имеете атрибут name в моделе Client, ActiveRecord добавляет методы find_by_name, find_all_by_name. Если Вы имеете атрибут locked в тойже модели, ActiveRecord добавит методы find_by_locked, find_all_by_locked.

Так же поддерживаются методы find_last_by_*, которые возвращают последнюю запись удовлетворяющую условию.

Чтобы заставить методы динамического поиска вызывать исключение ActiveRecord::RecordNotFound в случае, если записей не найдено, просто добавьте к ним восклицательный знак. Например, Client.find_by_name!("Ryan")

Если вы хотите искать по двум атрибутам, по name и по locked, просто соедините оба атрибута поставив между ними and, вот так: Client.find_by_name_and_locked("Ryan", true).

Есть еще один набор методов динамического поиска, которые ищут запись и если ничего не найдут, то создают запись. Их работа похожа на другие методы, использовать их можно так: find_or_create_by_name(params[:name]). Сначало происходит поиск записи в базе данных, потом ее создание, если запись не найдена. Метод Client.find_or_create_by_name("Ryan") выполнит следующие запросы:

SELECT * FROM clients WHERE (clients.name = 'Ryan') LIMIT 1
BEGIN
INSERT INTO clients (name, updated_at, created_at, orders_count, locked) VALUES('Ryan', '2008-09-28 15:39:12', '2008-09-28 15:39:12', 0, '0')
COMMIT

Метод find_or_initialize похож на метод find_or_create, только для создания объекта использкет new, а не create. Например:

client = Client.find_or_initialize_by_name('Ryan')

код либо назначает переменной client существующий объект с именем “Ryan” или инициализирует новый объект используя Client.new(:name => 'Ryan'). Далее Вы можете модифицировать любые его атрибуты, к примеру: client.locked = true и сохранить его в базе данных, используя save.

Поиск с использованием чистого SQL

Если Вы хотите использовать свой собственный SQL запрос, для поиска в базе, Вы можете использовать find_by_sql. Метод find_by_sql всегда возвращает массив объектов, даже SQL запрос возвращает одну записть. Пример:

Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id ORDER clients.created_at desc")

find_by_sql обеспечивает простой способ выполнять свои собственные, не стандартные SQL запросы и получать набор инициализированных объектов.

select_all

Метод find_by_sql вызывает метод connection#select_all. Метод select_all возвращает массив хэш объектов с именами колонок в качестве ключей.

Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")

Проверка на существование объектов

Если Вы просто хотите проверить существование объекта, то для этого есть метод, exists?. Этот метод выполнит тот же самый SQL запрос что и метод find, но вместо того, чтобы возвратить объект или коллекцию объектов, он возвратит или true или false.

Client.exists?(1)

Метод exists? так же принимает массив идентификаторов ивозвртит true, если хоть один из этих объектов существует.

Client.exists?(1,2,3) # or Client.exists?([1,2,3])

Более того, он принимает параметр :conditions, как метод find:

Client.exists?(:conditions => "first_name = 'Ryan'")

Его можно вызвать даже вообще без параметров:

Client.exists?

Он вернет false если таблица пуста и true если нет.

Извлечение статистических данных

Этот раздел использует метод count для описания параметров методов описанных в подразделах.

Метод count принимает параметр :conditions, так же как метод exists?:

Client.count(:conditions => "first_name = 'Ryan'")

SQL запрос:

SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan')

Вы можете использовать параметры :include и :joins чтобы сделать что-нибудь более сложное:

Client.count(:conditions => "clients.first_name = 'Ryan' AND orders.status = 'received'", :include => "orders")

SQL код:

SELECT count(DISTINCT clients.id) AS count_all FROM clients LEFT OUTER JOIN orders ON orders.client_id = client.id WHERE (clients.first_name = 'Ryan' AND orders.STATUS = 'received')

count

Если Вы хотите узнать, сколько записей находится в таблице, Вы можете вызвать Client.count. Чтобы найти определенных клиентов, нпример тех которых определен возраст можно использовать Client.count (:age).

Параметры функции описаны выше.

average

Если хотите вычислить среднее значение какого-нибудь поля, вызовите метод average. Примерно так:

Client.average("orders_count")

Результатом будет число, являющиеся средним значением всех значений поля orders_count

Параметры функции описаны выше.

minimum

If you want to find the minimum value of a field in your table you can call the minimum method on the class that relates to the table. This method call will look something like this: Если хотите узнать минимальное значение какого-нибудь столбца таблицы, вызывайте метод minimum

Client.minimum("age")

Параметры функции описаны выше.

maximum

Для максимального значения подойдет функция maximum:

Client.maximum("age")

Параметры функции описаны выше.

sum

Чтобы подсчитать сумму:

Client.sum("orders_count")

Параметры функции описаны выше.

SQL функции зависимые от типа базы данных

Все выше описанные методы работают в режиме, независимом от типа базы данных. Чтобы использовать специфичные функции воспользуйтесь методом calculate

Client.calculate(:std, :orders_count)

Перевод: Андрей Лепешкин

Написал undr ()

22 сентября 2009 в 23:29

Размещено в Документация

Метки: , , , ,

3 Responses to 'Интерфейс запросов ActiveRecord'

Подписаться на комментарии or TrackBack to 'Интерфейс запросов ActiveRecord'.

  1. Спасибо за переводы!

    Антон

    24 Окт 09 at 1:57

  2. Спасибо. Очень приятно, что кому-то это пригодилось.

    undr

    25 Окт 09 at 10:40

  3. Хороший сайт, особенно переводы доков по рельсам! Большое спасибо! Судя по всему я второй подписавшийся на блог буду :)

    Alex

    26 Окт 09 at 0:07

Оставьте комментарий