модуль ActiveRecord::QueryMethods
Константы
- DEFAULT_VALUES
- FROZEN_EMPTY_ARRAY
- FROZEN_EMPTY_HASH
- STRUCTURAL_OR_METHODS
- VALID_UNSCOPING_VALUES
Общедоступные методы экземпляров
# File activerecord/lib/active_record/relation/query_methods.rb, line 77
def bound_attributes
if limit_value
limit_bind = Attribute.with_cast_value(
"LIMIT".freeze,
connection.sanitize_limit(limit_value),
Type.default_value,
)
end
if offset_value
offset_bind = Attribute.with_cast_value(
"OFFSET".freeze,
offset_value.to_i,
Type.default_value,
)
end
connection.combine_bind_parameters(
from_clause: from_clause.binds,
join_clause: arel.bind_values,
where_clause: where_clause.binds,
having_clause: having_clause.binds,
limit: limit_bind,
offset: offset_bind,
)
end # File activerecord/lib/active_record/relation/query_methods.rb, line 791 def create_with(value) spawn.create_with!(value) end
Устанавливает атрибуты, которые будут использоваться при создании новых записей из объекта отношения.
users = User.where(name: 'Oscar') users.new.name # => 'Oscar' users = users.create_with(name: 'DHH') users.new.name # => 'DHH'
Вы можете передать nil в create_with для сброса атрибутов:
users = users.create_with(nil) users.new.name # => 'Oscar'
# File activerecord/lib/active_record/relation/query_methods.rb, line 838 def distinct(value = true) spawn.distinct!(value) end
Указывает, должны ли записи быть уникальными или нет. Например:
User.select(:name) # Might return two records with the same name User.select(:name).distinct # Returns 1 record per distinct name User.select(:name).distinct.distinct(false) # You can also remove the uniqueness
# File activerecord/lib/active_record/relation/query_methods.rb, line 156 def eager_load(*args) check_if_method_has_arguments!(:eager_load, args) spawn.eager_load!(*args) end
Принудительно выполняет левое внешнее соединение (LEFT OUTER JOIN) с args:
User.eager_load(:posts) # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = # "users"."id"
# File activerecord/lib/active_record/relation/query_methods.rb, line 884
def extending(*modules, &block)
if modules.any? || block
spawn.extending!(*modules, &block)
else
self
end
end Используется для расширения области видимости дополнительными методами, либо с помощью модуля, либо с помощью переданного блока.
Возвращаемый объект является отношением, которое может быть дополнительно расширено.
Использование модуля
module Pagination
def page(number)
# pagination code goes here
end
end
scope = Model.all.extending(Pagination)
scope.page(params[:page])
Вы также можете передать список модулей:
scope = Model.all.extending(Pagination, SomethingElse)
Использование блока
scope = Model.all.extending do
def page(number)
# pagination code goes here
end
end
scope.page(params[:page])
Вы также можете использовать блок и список модулей:
scope = Model.all.extending(Pagination) do
def per_page(number)
# pagination code goes here
end
end
# File activerecord/lib/active_record/relation/query_methods.rb, line 819 def from(value, subquery_name = nil) spawn.from!(value, subquery_name) end
Указывает таблицу, из которой будут извлекаться записи. Например:
Topic.select('title').from('posts')
# SELECT title FROM posts
Может принимать и другие объекты отношений. Например:
Topic.select('title').from(Topic.approved)
# SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
Topic.select('a.title').from(Topic.approved, :a)
# SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
# File activerecord/lib/active_record/relation/query_methods.rb, line 284 def group(*args) check_if_method_has_arguments!(:group, args) spawn.group!(*args) end
Позволяет указать атрибут группировки:
User.group(:name) # SELECT "users".* FROM "users" GROUP BY name
Возвращает массив с уникальными записями на основе атрибута group:
User.select([:id, :name])
# => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
User.group(:name)
# => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
User.group('name AS grouped_name, age')
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
Также поддерживается передача массива атрибутов для группировки.
User.select([:id, :first_name]).group(:id, :first_name).first(3) # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
# File activerecord/lib/active_record/relation/query_methods.rb, line 668 def having(opts, *rest) opts.blank? ? self : spawn.having!(opts, *rest) end
Позволяет указать условие HAVING. Обратите внимание, что вы не можете использовать HAVING без указания условия GROUP.
Order.having('SUM(price) > 30').group('user_id')
# File activerecord/lib/active_record/relation/query_methods.rb, line 137 def includes(*args) check_if_method_has_arguments!(:includes, args) spawn.includes!(*args) end
Указывает отношения, которые должны быть включены в результат. Например:
users = User.includes(:address) users.each do |user| user.address.city end
позволяет получить доступ к атрибуту address модели User без выполнения дополнительного запроса. Это часто приводит к улучшению производительности по сравнению с простым соединением.
Вы также можете указать несколько отношений, как в этом примере:
users = User.includes(:address, :friends)
Загрузка вложенных отношений возможна с помощью хэша:
users = User.includes(:address, friends: [:address, :followers])
условия
Если вы хотите добавить условия к включённым моделям, вам нужно явно их указать. Например:
User.includes(:posts).where('posts.name = ?', 'example')
Вызовет ошибку, но это будет работать:
User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
Обратите внимание, что includes работает с именами ассоциаций, а references требует фактического имени таблицы.
# File activerecord/lib/active_record/relation/query_methods.rb, line 448 def joins(*args) check_if_method_has_arguments!(:joins, args) spawn.joins!(*args) end
Выполняет соединение (JOIN) с args. Указанные символы должны соответствовать имени(ям) ассоциации(й).
User.joins(:posts) # SELECT "users".* # FROM "users" # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
Несколько соединений:
User.joins(:posts, :account) # SELECT "users".* # FROM "users" # INNER JOIN "posts" ON "posts"."user_id" = "users"."id" # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
Вложенные соединения:
User.joins(posts: [:comments]) # SELECT "users".* # FROM "users" # INNER JOIN "posts" ON "posts"."user_id" = "users"."id" # INNER JOIN "comments" "comments_posts" # ON "comments_posts"."post_id" = "posts"."id"
Вы можете использовать строки для настройки ваших соединений:
User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
# SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
# File activerecord/lib/active_record/relation/query_methods.rb, line 465 def left_outer_joins(*args) check_if_method_has_arguments!(:left_outer_joins, args) args.compact! args.flatten! spawn.left_outer_joins!(*args) end
Выполняет левое внешнее соединение (LEFT OUTER JOIN) с args:
User.left_outer_joins(:posts) => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
# File activerecord/lib/active_record/relation/query_methods.rb, line 685 def limit(value) spawn.limit!(value) end
Устанавливает лимит на количество извлекаемых записей.
User.limit(10) # generated SQL has 'LIMIT 10' User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
# File activerecord/lib/active_record/relation/query_methods.rb, line 712 def lock(locks = true) spawn.lock!(locks) end
Устанавливает параметры блокировки (по умолчанию true). Для получения дополнительной информации о блокировке, пожалуйста, обратитесь к ActiveRecord::Locking.
# File activerecord/lib/active_record/relation/query_methods.rb, line 755 def none spawn.none! end
Возвращает цепочечное отношение без записей.
Возвращаемое отношение реализует шаблон Нулевого объекта. Это объект с определённым нулевым поведением и всегда возвращает пустой массив записей без запроса к базе данных.
Любое последующее условие, присоединённое к возвращаемому отношению, будет продолжать генерировать пустое отношение и не будет выполнять запрос к базе данных.
Используется в случаях, когда метод или область видимости могут вернуть ноль записей, но результат должен быть цепочечным.
Например:
@posts = current_user.visible_posts.where(name: params[:name])
# the visible_posts method is expected to return a chainable Relation
def visible_posts
case role
when 'Country Manager'
Post.where(country: country)
when 'Reviewer'
Post.published
when 'Bad User'
Post.none # It can't be chained if [] is returned.
end
end
# File activerecord/lib/active_record/relation/query_methods.rb, line 701 def offset(value) spawn.offset!(value) end
Указывает количество строк, которые нужно пропустить перед возвратом строк.
User.offset(10) # generated SQL has "OFFSET 10"
Должен использоваться с order.
User.offset(10).order("name ASC")
# File activerecord/lib/active_record/relation/query_methods.rb, line 643
def or(other)
unless other.is_a? Relation
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
end
spawn.or!(other)
end Возвращает новое отношение, которое является логическим объединением этого отношения и отношения, переданного в качестве аргумента.
Два отношения должны быть структурно совместимы: они должны охватывать одну и ту же модель, и они должны отличаться только по where (если нет group) или having (если присутствует group). Ни одно отношение не может иметь заданные limit, offset или distinct.
Post.where("id = 1").or(Post.where("author_id = 3"))
# SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
# File activerecord/lib/active_record/relation/query_methods.rb, line 315 def order(*args) check_if_method_has_arguments!(:order, args) spawn.order!(*args) end
Позволяет указать атрибут сортировки:
User.order(:name)
# SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
User.order(email: :desc)
# SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
User.order(:name, email: :desc)
# SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
User.order('name')
# SELECT "users".* FROM "users" ORDER BY name
User.order('name DESC')
# SELECT "users".* FROM "users" ORDER BY name DESC
User.order('name DESC, email')
# SELECT "users".* FROM "users" ORDER BY name DESC, email
# File activerecord/lib/active_record/relation/query_methods.rb, line 170 def preload(*args) check_if_method_has_arguments!(:preload, args) spawn.preload!(*args) end
Позволяет предварительно загрузить args, аналогично тому, как это делает includes:
User.preload(:posts) # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
# File activerecord/lib/active_record/relation/query_methods.rb, line 769 def readonly(value = true) spawn.readonly!(value) end
Устанавливает атрибуты только для чтения для возвращаемой связи. Если значение равно true (по умолчанию), попытка обновления записи приведет к ошибке.
users = User.readonly users.first.save => ActiveRecord::ReadOnlyRecord: User is marked as readonly
# File activerecord/lib/active_record/relation/query_methods.rb, line 190 def references(*table_names) check_if_method_has_arguments!(:references, table_names) spawn.references!(*table_names) end
Используется для указания того, что данные table_names ссылаются на строку SQL и, следовательно, должны быть подключены в любом запросе, а не загружаться отдельно. Этот метод работает только в сочетании с includes. Подробнее см. includes.
User.includes(:posts).where("posts.name = 'foo'")
# Doesn't JOIN the posts table, resulting in an error.
User.includes(:posts).where("posts.name = 'foo'").references(:posts)
# Query now knows the string references posts, so adds a JOIN
# File activerecord/lib/active_record/relation/query_methods.rb, line 336 def reorder(*args) check_if_method_has_arguments!(:reorder, args) spawn.reorder!(*args) end
Заменяет любое существующее упорядочение в отношении указанным порядком.
User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
Последующие вызовы order для той же связи будут добавлены. Например:
User.order('email DESC').reorder('id ASC').order('name ASC')
генерирует запрос с 'ORDER BY id ASC, name ASC'.
# File activerecord/lib/active_record/relation/query_methods.rb, line 905 def reverse_order spawn.reverse_order! end
Инвертирует существующее условие упорядочивания в отношении.
User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
# File activerecord/lib/active_record/relation/query_methods.rb, line 629 def rewhere(conditions) unscope(where: conditions.keys).where(conditions) end
Позволяет изменить ранее установленное условие where для данного атрибута вместо добавления к этому условию.
Post.where(trashed: true).where(trashed: false) # WHERE `trashed` = 1 AND `trashed` = 0 Post.where(trashed: true).rewhere(trashed: false) # WHERE `trashed` = 0 Post.where(active: true).where(trashed: true).rewhere(trashed: false) # WHERE `active` = 1 AND `trashed` = 0
Это сокращенная форма unscope(where:
conditions.keys).where(conditions). Обратите внимание, что в отличие от reorder, мы отменяем только именованные условия, а не всё условие where.
# File activerecord/lib/active_record/relation/query_methods.rb, line 242
def select(*fields)
if block_given?
if fields.any?
raise ArgumentError, "`select' with block doesn't take arguments."
end
return super()
end
raise ArgumentError, "Call this with at least one field" if fields.empty?
spawn._select!(*fields)
end Работает двумя уникальными способами.
Во-первых: принимает блок, чтобы его можно было использовать так же, как и +Array#select+.
Model.all.select { |m| m.field == value }
Это создаст массив объектов из базы данных для области видимости, преобразует их в массив и перебирает их с помощью +Array#select+.
Во-вторых: изменяет оператор SELECT для запроса, чтобы извлекались только определённые поля:
Model.select(:field) # => [#<Model id: nil, field: "value">]
Хотя на приведенном примере кажется, что этот метод возвращает массив, он фактически возвращает объект отношения и может иметь другие методы запроса, добавленные к нему, такие как другие методы в ActiveRecord::QueryMethods.
Аргумент метода также может быть массивом полей.
Model.select(:field, :other_field, :and_one_more) # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
Вы также можете использовать одну или несколько строк, которые будут использоваться без изменений в качестве полей SELECT.
Model.select('field AS field_one', 'other_field AS field_two')
# => [#<Model id: nil, field: "value", other_field: "value">]
Если было указано псевдоним, он будет доступен из полученных объектов:
Model.select('field AS field_one').first.field_one
# => "value"
Обращение к атрибутам объекта, которые не имеют полей, полученных с помощью select, за исключением id , вызовет ActiveModel::MissingAttributeError:
Model.select(:field).first.other_field # => ActiveModel::MissingAttributeError: missing attribute: other_field
# File activerecord/lib/active_record/relation/query_methods.rb, line 386 def unscope(*args) check_if_method_has_arguments!(:unscope, args) spawn.unscope!(*args) end
Удаляет нежелательное отношение, которое уже определено в цепочке отношений. Это полезно при передаче цепочек отношений и желании изменить отношения без перестроения всей цепочки.
User.order('email DESC').unscope(:order) == User.all
Аргументы метода — символы, соответствующие именам методов, которые должны быть исключены из области видимости. Допустимые аргументы указаны в VALID_UNSCOPING_VALUES. Метод также может быть вызван с несколькими аргументами. Например:
User.order('email DESC').select('id').where(name: "John")
.unscope(:order, :select, :where) == User.all
Кроме того, в качестве аргумента можно передать хэш для исключения определённых :where значений. Это делается путём передачи хэша с единственной парой ключ-значение. Ключ должен быть :where , а значение — значение where для исключения из области видимости. Например:
User.where(name: "John", active: true).unscope(where: :name)
== User.where(active: true) Этот метод похож на except, но в отличие от except, он сохраняется при слияниях:
User.order('email').merge(User.except(:order))
== User.order('email')
User.order('email').merge(User.unscope(:order))
== User.all Это означает, что он может использоваться в определениях ассоциаций:
has_many :comments, -> { unscope(where: :trashed) }
# File activerecord/lib/active_record/relation/query_methods.rb, line 599
def where(opts = :chain, *rest)
if :chain == opts
WhereChain.new(spawn)
elsif opts.blank?
self
else
spawn.where!(opts, *rest)
end
end Возвращает новое отношение, которое является результатом фильтрации текущего отношения в соответствии с условиями в аргументах.
where принимает условия в нескольких форматах. В примерах ниже показан пример SQL; фактический сгенерированный запрос может отличаться в зависимости от адаптера базы данных.
строка
Одна строка без дополнительных аргументов передаётся в конструктор запроса как фрагмент SQL и используется в условии where запроса.
Client.where("orders_count = '2'")
# SELECT * from clients where orders_count = '2';
Обратите внимание, что создание собственной строки из пользовательского ввода может сделать ваш application уязвимым к атакам с помощью инъекций, если это не сделано правильно. В качестве альтернативы рекомендуется использовать один из следующих методов.
массив
Если передается массив, то первый элемент массива рассматривается как шаблон, а оставшиеся элементы вставляются в шаблон для создания условия. Active Record заботится о формировании запроса для предотвращения атак с помощью инъекций и преобразует тип Ruby в тип базы данных при необходимости. Элементы вставляются в строку в порядке их появления.
User.where(["name = ? and email = ?", "Joe", "joe@example.com"]) # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
В качестве альтернативы вы можете использовать именованные плейсхолдеры в шаблоне и передать хэш как второй элемент массива. Имена в шаблоне заменяются соответствующими значениями из хэша.
User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
Это может сделать код более читаемым в сложных запросах.
Наконец, вы можете использовать эскейпы % в стиле sprintf в шаблоне. Это работает немного по-другому, чем предыдущие методы; вы несете ответственность за обеспечение того, чтобы значения в шаблоне были должным образом обрамлены кавычками. Значения передаются подключению для кавычек, но вызов отвечает за то, что они заключены в кавычки в результирующем SQL. После кавычек значения вставляются с помощью тех же эскейпов, что и метод ядра Ruby Kernel::sprintf.
User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"]) # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
Если where вызывается с несколькими аргументами, они обрабатываются так, как если бы они были переданы как элементы одного массива.
User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
При использовании строк для указания условий вы можете использовать любой оператор, доступный в базе данных. Хотя это предоставляет наибольшую гибкость, вы также можете непреднамеренно внести зависимости от базовой базы данных. Если ваш код предназначен для общего использования, протестируйте его с несколькими базами данных.
хэш
where также принимает условие в формате хэша, в котором ключи — поля, а значения — значения, которые нужно найти.
Поля могут быть символами или строками. Значения могут быть одиночными значениями, массивами или диапазонами.
User.where({ name: "Joe", email: "joe@example.com" })
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
User.where({ name: ["Alice", "Bob"]})
# SELECT * FROM users WHERE name IN ('Alice', 'Bob')
User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
# SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
В случае связи belongs_to может использоваться ключ ассоциации для указания модели, если используется объект ActiveRecord в качестве значения.
author = Author.find(1) # The following queries will be equivalent: Post.where(author: author) Post.where(author_id: author)
Это также работает с полиморфными связями belongs_to:
treasure = Treasure.create(name: 'gold coins') treasure.price_estimates << PriceEstimate.create(price: 125) # The following queries will be equivalent: PriceEstimate.where(estimate_of: treasure) PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
Соединения
Если отношение является результатом соединения, вы можете создать условие, которое использует любую из таблиц в соединении. Для строковых и массивов условий используйте имя таблицы в условии.
User.joins(:posts).where("posts.created_at < ?", Time.now)
Для условий в формате хэша вы можете использовать имя таблицы в ключе или использовать вложенный хэш.
User.joins(:posts).where({ "posts.published" => true })
User.joins(:posts).where({ posts: { published: true } })
без аргумента
Если не передано аргументов, where возвращает новый экземпляр WhereChain, который можно объединить с not для возврата нового отношения, которое отрицает условие where.
User.where.not(name: "Jon") # SELECT * FROM users WHERE name != 'Jon'
Подробнее см. WhereChain.
пустое условие
Если условие — любой пустой объект, то where является пустой операцией и возвращает текущее отношение.
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.