модуль ActiveRecord::QueryMethods
Константы
- FROZEN_EMPTY_ARRAY
- FROZEN_EMPTY_HASH
- VALID_UNSCOPING_VALUES
Открытые методы экземпляров
# File activerecord/lib/active_record/relation/query_methods.rb, line 1135
def and(other)
if other.is_a?(Relation)
spawn.and!(other)
else
raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
end
end Возвращает новое отношение, которое представляет собой логическое пересечение этого отношения и отношения, переданного в качестве аргумента.
Два отношения должны быть структурно совместимы: они должны охватывать одну и ту же модель, и они должны отличаться только по where (если не определён group) или having (если присутствует group).
Post.where(id: [1, 2]).and(Post.where(id: [2, 3])) # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
# File activerecord/lib/active_record/relation/query_methods.rb, line 1529 def annotate(*args) check_if_method_has_arguments!(__callee__, args) spawn.annotate!(*args) end
Добавляет SQL комментарий к запросам, сгенерированным из этого отношения. Например:
User.annotate("selecting user names").select(:name)
# SELECT "users"."name" FROM "users" /* selecting user names */
User.annotate("selecting", "user", "names").select(:name)
# SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
Разделители SQL блочных комментариев «/*» и «*/» будут автоматически добавлены.
Некоторая обработка экранирования выполняется, но не следует использовать недоверенные данные пользователя.
# File activerecord/lib/active_record/relation/query_methods.rb, line 1346 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 1410 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 290 def eager_load(*args) check_if_method_has_arguments!(__callee__, args) spawn.eager_load!(*args) end
Укажите ассоциации args для леничной загрузки с помощью LEFT OUTER JOIN. Выполняет один запрос, объединяя все указанные ассоциации. Например:
users = User.eager_load(:address).limit(5) users.each do |user| user.address.city end # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users" # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id" # LIMIT 5
Вместо загрузки 5 адресов с помощью 5 отдельных запросов, все адреса загружаются с помощью одного объединённого запроса.
Загрузка нескольких и вложенных ассоциаций возможна с помощью массивов и хешей, аналогично includes:
User.eager_load(:address, friends: [:address, :followers]) # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users" # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id" # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id" # ...
ПРИМЕЧАНИЕ. Загрузка ассоциаций в объединении может привести к множеству строк, содержащих избыточные данные, и плохо масштабируется.
# File activerecord/lib/active_record/relation/query_methods.rb, line 1574
def excluding(*records)
relations = records.extract! { |element| element.is_a?(Relation) }
records.flatten!(1)
records.compact!
unless records.all?(model) && relations.all? { |relation| relation.model == model }
raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
end
spawn.excluding!(records + relations.flat_map(&:ids))
end Исключает указанную запись (или коллекцию записей) из результирующего отношения. Например:
Post.excluding(post) # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1 Post.excluding(post_one, post_two) # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2) Post.excluding(Post.drafts) # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
Это также может быть вызвано для ассоциаций. Как и в приведённом выше примере, может быть указана одна запись или коллекция:
post = Post.find(1) comment = Comment.find(2) post.comments.excluding(comment) # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
Это сокращённая запись для .where.not(id: post.id) и .where.not(id: [post_one.id, post_two.id]).
Будет выброшено исключение ArgumentError если записи не указаны или если какая-либо из записей в коллекции (если передаётся коллекция) не является экземпляром той же модели, что и отношение.
# File activerecord/lib/active_record/relation/query_methods.rb, line 1456
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 341 def extract_associated(association) preload(association).collect(&association) end
Извлекает указанную association из отношения. Сначала предварительно загружается указанная ассоциация, затем индивидуальные записи ассоциации собираются из отношения. Например:
account.memberships.extract_associated(:user) # => Returns collection of User records
Это сокращённая запись для:
account.memberships.preload(:user).collect(&:user)
# File activerecord/lib/active_record/relation/query_methods.rb, line 1391 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
Передача второго аргумента (строка или символ) создаёт псевдоним для SQL from-фрагмента. В противном случае используется псевдоним «subquery»:
Topic.select('a.title').from(Topic.approved, :a)
# SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
Он не добавляет несколько аргументов в SQL from-фрагмент. Последнее from цепочкой является тем, которое используется:
Topic.select('title').from(Topic.approved).from(Topic.inactive)
# SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery
Для нескольких аргументов в SQL from-фрагменте можно передать строку с точными элементами в списке SQL from:
color = "red"
Color
.from("colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)")
.where("colorvalue->>'color' = ?", color)
.select("c.*").to_a
# SELECT c.*
# FROM colors c, JSONB_ARRAY_ELEMENTS(colored_things) AS colorvalues(colorvalue)
# WHERE (colorvalue->>'color' = 'red')
# File activerecord/lib/active_record/relation/query_methods.rb, line 573 def group(*args) check_if_method_has_arguments!(__callee__, 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 1197 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 717
def in_order_of(column, values, filter: true)
model.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
return spawn.none! if values.empty?
references = column_references([column])
self.references_values |= references unless references.empty?
values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))
if filter
where_clause =
if values.include?(nil)
arel_column.in(values.compact).or(arel_column.eq(nil))
else
arel_column.in(values)
end
scope = scope.where!(where_clause)
end
scope
end Применяет фрагмент ORDER BY на основе заданного column, упорядоченного и отфильтрованного по определённому набору values.
User.in_order_of(:id, [1, 5, 3]) # SELECT "users".* FROM "users" # WHERE "users"."id" IN (1, 5, 3) # ORDER BY CASE # WHEN "users"."id" = 1 THEN 1 # WHEN "users"."id" = 5 THEN 2 # WHEN "users"."id" = 3 THEN 3 # END ASC
column может указывать на столбец перечисления; фактический сгенерированный запрос может отличаться в зависимости от базы данных и определения столбца.
class Conversation < ActiveRecord::Base enum :status, [ :active, :archived ] end Conversation.in_order_of(:status, [:archived, :active]) # SELECT "conversations".* FROM "conversations" # WHERE "conversations"."status" IN (1, 0) # ORDER BY CASE # WHEN "conversations"."status" = 1 THEN 1 # WHEN "conversations"."status" = 0 THEN 2 # END ASC
values может также включать nil.
Conversation.in_order_of(:status, [nil, :archived, :active])
# SELECT "conversations".* FROM "conversations"
# WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
# ORDER BY CASE
# WHEN "conversations"."status" IS NULL THEN 1
# WHEN "conversations"."status" = 1 THEN 2
# WHEN "conversations"."status" = 0 THEN 3
# END ASC
filter может быть установлено в false для включения всех результатов, а не только тех, которые указаны в values.
Conversation.in_order_of(:status, [:archived, :active], filter: false) # SELECT "conversations".* FROM "conversations" # ORDER BY CASE # WHEN "conversations"."status" = 1 THEN 1 # WHEN "conversations"."status" = 0 THEN 2 # ELSE 3 # END ASC
# File activerecord/lib/active_record/relation/query_methods.rb, line 250 def includes(*args) check_if_method_has_arguments!(__callee__, args) spawn.includes!(*args) end
Укажите ассоциации args для ленивой загрузки, чтобы предотвратить запросы N + 1. Для каждой ассоциации выполняется отдельный запрос, если join не требуется по условиям.
Например:
users = User.includes(:address).limit(5) users.each do |user| user.address.city end # SELECT "users".* FROM "users" LIMIT 5 # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
Вместо загрузки 5 адресов с 5 отдельными запросами, все адреса загружаются с помощью одного запроса.
Загрузка ассоциаций в отдельном запросе часто приводит к улучшению производительности по сравнению с простым join, поскольку join может возвращать множество строк с избыточными данными и плохо работает при масштабировании.
Вы также можете указать несколько ассоциаций. Каждая ассоциация приведет к дополнительному запросу:
User.includes(:address, :friends).to_a # SELECT "users".* FROM "users" # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5) # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
Загрузка вложенных ассоциаций возможна с помощью хеша:
User.includes(:address, friends: [:address, :followers])
Условия
Если вы хотите добавить строковые условия к включенным моделям, вам нужно явно их указать. Например:
User.includes(:posts).where('posts.name = ?', 'example').to_a
Вызовет ошибку, но это сработает:
User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
# SELECT "users"."id" AS t0_r0, ... FROM "users"
# LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
# WHERE "posts"."name" = ? [["name", "example"]]
Поскольку LEFT OUTER JOIN уже содержат посты, второй запрос на посты больше не выполняется.
Обратите внимание, что includes работает с именами ассоциаций, в то время как references требует фактического имени таблицы.
Если вы передаёте условия через Hash, вам не нужно явно вызывать references, так как where ссылается на таблицы за вас. Например, это сработает правильно:
User.includes(:posts).where(posts: { name: 'example' })
ПРИМЕЧАНИЕ: Условия влияют на обе стороны ассоциации. Например, приведенный выше код вернёт только пользователей, у которых есть пост с именем «example», и будет включать только посты с именем «example», даже если у соответствующего пользователя есть другие дополнительные посты.
# File activerecord/lib/active_record/relation/query_methods.rb, line 1101 def invert_where spawn.invert_where! end
Позволяет инвертировать весь предикат where вместо ручного применения условий.
class User
scope :active, -> { where(accepted: true, locked: false) }
end
User.where(accepted: true)
# WHERE `accepted` = 1
User.where(accepted: true).invert_where
# WHERE `accepted` != 1
User.active
# WHERE `accepted` = 1 AND `locked` = 0
User.active.invert_where
# WHERE NOT (`accepted` = 1 AND `locked` = 0)
Будьте осторожны, так как это инвертирует все условия до invert_where вызова.
class User
scope :active, -> { where(accepted: true, locked: false) }
scope :inactive, -> { active.invert_where } # Do not attempt it
end
# It also inverts `where(role: 'admin')` unexpectedly.
User.where(role: 'admin').inactive
# WHERE NOT (`role` = 'admin' AND `accepted` = 1 AND `locked` = 0)
# File activerecord/lib/active_record/relation/query_methods.rb, line 868 def joins(*args) check_if_method_has_arguments!(__callee__, args) spawn.joins!(*args) end
Выполняет JOINы на args. Указанный(ые) символ(ы) должны соответствовать имени(ям) ассоциации(ей).
User.joins(:posts) # SELECT "users".* # FROM "users" # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
Несколько JOINов:
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"
Вложенные JOINы:
User.joins(posts: [:comments]) # SELECT "users".* # FROM "users" # INNER JOIN "posts" ON "posts"."user_id" = "users"."id" # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
Вы можете использовать строки для настройки своих JOINов:
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 883 def left_outer_joins(*args) check_if_method_has_arguments!(__callee__, args) 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 1211 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 1238 def lock(locks = true) spawn.lock!(locks) end
Устанавливает параметры блокировки (по умолчанию true). Более подробную информацию о блокировках см. в ActiveRecord::Locking.
# File activerecord/lib/active_record/relation/query_methods.rb, line 1281 def none spawn.none! end
Возвращает цепочечный объект отношений с нулём записей.
Возвращаемое отношение реализует шаблон Null Object. Это объект с определённым поведением для нуля и всегда возвращает пустой массив записей без запроса к базе данных.
Любое последующее условие, присоединённое к возвращаемому отношению, будет продолжать генерировать пустое отношение и не будет выполнять запрос к базе данных.
Используется в случаях, когда метод или область видимости могут возвращать ноль записей, но результат должен быть цепочечным.
Например:
@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 1227 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 1485 def optimizer_hints(*args) check_if_method_has_arguments!(__callee__, args) spawn.optimizer_hints!(*args) end
Укажите подсказки оптимизатору, которые будут использоваться в операторе SELECT.
Пример (для MySQL):
Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
# SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
Пример (для PostgreSQL с pg_hint_plan):
Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
# SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
# File activerecord/lib/active_record/relation/query_methods.rb, line 1167
def or(other)
if other.is_a?(Relation)
if @none
other.spawn
else
spawn.or!(other)
end
else
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
end
end Возвращает новое отношение, которое является логическим объединением этого отношения и отношения, переданного в качестве аргумента.
Два отношения должны быть структурно совместимы: они должны охватывать одну и ту же модель и отличаться только where (если не определено group) или having (если присутствует group).
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 656
def order(*args)
check_if_method_has_arguments!(__callee__, args) do
sanitize_order_arguments(args)
end
spawn.order!(*args)
end Применяет предложение ORDER BY к запросу.
order принимает аргументы в нескольких форматах.
символы
Символ представляет имя столбца, по которому вы хотите отсортировать результаты.
User.order(:name) # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
По умолчанию сортировка происходит по возрастанию. Если вам нужна сортировка по убыванию, вы можете сопоставить имя столбца со значением :desc.
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
строки
Строки передаются напрямую в базу данных, позволяя указывать простые SQL-выражения.
Это может быть источником SQL-инъекций, поэтому допускаются только строки, состоящие из простых имён столбцов и простых function(column_name) выражений с необязательными ASC/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
Arel
Если вам нужно передать сложные выражения, безопасность которых для базы данных проверена, вы можете использовать Arel.
User.order(Arel.sql('end_date - start_date'))
# SELECT "users".* FROM "users" ORDER BY end_date - start_date
Поддерживается пользовательская синтаксическая конструкция запроса, например JSON-столбцы для PostgreSQL.
User.order(Arel.sql("payload->>'kind'"))
# SELECT "users".* FROM "users" ORDER BY payload->>'kind'
# File activerecord/lib/active_record/relation/query_methods.rb, line 322 def preload(*args) check_if_method_has_arguments!(__callee__, args) spawn.preload!(*args) end
Укажите ассоциации args для ленивой загрузки, используя отдельные запросы. Для каждой ассоциации выполняется отдельный запрос.
users = User.preload(:address).limit(5) users.each do |user| user.address.city end # SELECT "users".* FROM "users" LIMIT 5 # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
Вместо загрузки 5 адресов с 5 отдельными запросами, все адреса загружаются с помощью отдельного запроса.
Загрузка множественных и вложенных ассоциаций возможна с помощью хешей и массивов, аналогично includes:
User.preload(:address, friends: [:address, :followers]) # SELECT "users".* FROM "users" # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5) # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5) # SELECT ...
# File activerecord/lib/active_record/relation/query_methods.rb, line 1309 def readonly(value = true) spawn.readonly!(value) end
Отметить отношение как только для чтения. Попытка обновить запись приведет к ошибке.
users = User.readonly users.first.save => ActiveRecord::ReadOnlyRecord: User is marked as readonly
Чтобы сделать отношение для чтения-записи доступным для записи, передайте false.
users.readonly(false) users.first.save => true
# File activerecord/lib/active_record/relation/query_methods.rb, line 355 def references(*table_names) check_if_method_has_arguments!(__callee__, table_names) spawn.references!(*table_names) end
Используйте для указания, что указанные table_names ссылаются на строку SQL и, следовательно, должны быть +JOIN+ены в любом запросе, а не загружаться отдельно. Этот метод работает только в сочетании с 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 593 def regroup(*args) check_if_method_has_arguments!(__callee__, args) spawn.regroup!(*args) end
Позволяет изменить ранее заданное утверждение группировки.
Post.group(:title, :body) # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body` Post.group(:title, :body).regroup(:title) # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
Это сокращенная запись для unscope(:group).group(fields). Обратите внимание, что мы отключаем все утверждение группировки.
# File activerecord/lib/active_record/relation/query_methods.rb, line 752
def reorder(*args)
check_if_method_has_arguments!(__callee__, args) do
sanitize_order_arguments(args)
end
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 541 def reselect(*args) check_if_method_has_arguments!(__callee__, args) args = process_select_args(args) spawn.reselect!(*args) end
Позволяет изменить ранее заданное утверждение выбора.
Post.select(:title, :body) # SELECT `posts`.`title`, `posts`.`body` FROM `posts` Post.select(:title, :body).reselect(:created_at) # SELECT `posts`.`created_at` FROM `posts`
Это сокращенная запись для unscope(:select).select(fields). Обратите внимание, что мы отключаем все утверждение выбора.
# File activerecord/lib/active_record/relation/query_methods.rb, line 1498 def reverse_order spawn.reverse_order! end
Инвертировать существующий порядок в предложении ORDER BY.
User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
# File activerecord/lib/active_record/relation/query_methods.rb, line 1061 def rewhere(conditions) return unscope(:where) if conditions.nil? scope = spawn where_clause = scope.build_where_clause(conditions) scope.unscope!(where: where_clause.extract_attributes) scope.where_clause += where_clause scope 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 413
def select(*fields)
if block_given?
if fields.any?
raise ArgumentError, "`select' with block doesn't take arguments."
end
return super()
end
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
fields = process_select_args(fields)
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">]
Аргументом также может быть хеш полей и псевдонимов.
Model.select(models: { field: :alias, other_field: :other_alias })
# => [#<Model id: nil, alias: "value", other_alias: "value">]
Model.select(models: [:field, :other_field])
# => [#<Model id: nil, field: "value", other_field: "value">]
Вы также можете использовать одну или несколько строк, которые будут использоваться без изменений в качестве полей SELECT.
Model.select('field AS field_one', 'other_field AS field_two')
# => [#<Model id: nil, field_one: "value", field_two: "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' for Model
# File activerecord/lib/active_record/relation/query_methods.rb, line 1324 def strict_loading(value = true) spawn.strict_loading!(value) end
Устанавливает возвращаемое отношение в режим strict_loading. Это вызовет ошибку, если запись попытается загрузить ассоциацию лениво.
user = User.strict_loading.first user.comments.to_a => ActiveRecord::StrictLoadingViolationError
# File activerecord/lib/active_record/relation/query_methods.rb, line 1121 def structurally_compatible?(other) structurally_incompatible_values_for(other).empty? end
Проверяет, совместимо ли данное отношение со структурной точки зрения с этим отношением, чтобы определить, можно ли использовать методы and и or без возникновения ошибки. Структурная совместимость определяется следующим образом: они должны охватывать одну и ту же модель, и они должны отличаться только where (если не определена group) или having (если присутствует group).
Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
# => true
Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
# => false
# File activerecord/lib/active_record/relation/query_methods.rb, line 1541
def uniq!(name)
if values = @values[name]
values.uniq! if values.is_a?(Array) && !values.empty?
end
self
end Удалить дублирующие значения.
# File activerecord/lib/active_record/relation/query_methods.rb, line 806 def unscope(*args) check_if_method_has_arguments!(__callee__, 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 1033
def where(*args)
if args.empty?
WhereChain.new(spawn)
elsif args.length == 1 && args.first.blank?
self
else
spawn.where!(*args)
end
end Возвращает новое отношение, которое является результатом фильтрации текущего отношения в соответствии с условиями в аргументах.
where принимает условия в одном из нескольких форматов. В примерах ниже показан результирующий SQL; фактическая сгенерированная строка запроса может отличаться в зависимости от адаптера базы данных.
Строка
Одна строка без дополнительных аргументов передается в конструктор запроса как фрагмент SQL и используется в части where запроса.
Client.where("orders_count = '2'")
# SELECT * from clients where orders_count = '2';
Обратите внимание, что создание собственной строки из пользовательского ввода может подвергнуть вашу программу атакам с внедрением кода, если это не сделано должным образом. В качестве альтернативы рекомендуется использовать один из следующих методов.
Массив
Если передается массив, то первый элемент массива обрабатывается как шаблон, а оставшиеся элементы вставляются в шаблон для генерации условия. 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. После оформления значения вставляются с помощью тех же escape-символов, что и метод ядра 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)
Hash условия также могут быть заданы в синтаксисе типа кортежа. Hash ключи могут быть массивом столбцов со значениями в виде массива кортежей.
Article.where([:author_id, :id] => [[15, 1], [15, 2]]) # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
Соединения
Если отношение является результатом соединения, вы можете создать условие, которое использует любую из таблиц в соединении. Для строчных и массивно-условных условий используйте имя таблицы в условии.
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, который можно использовать с WhereChain#not, WhereChain#missing или WhereChain#associated.
Цепочка с WhereChain#not:
User.where.not(name: "Jon") # SELECT * FROM users WHERE name != 'Jon'
Цепочка с WhereChain#associated:
Post.where.associated(:author) # SELECT "posts".* FROM "posts" # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id" # WHERE "authors"."id" IS NOT NULL
Цепочка с WhereChain#missing:
Post.where.missing(:author) # SELECT "posts".* FROM "posts" # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id" # WHERE "authors"."id" IS NULL
Пустое условие
Если условие — любой пустой объект, то where — это no-op и возвращает текущее отношение.
# File activerecord/lib/active_record/relation/query_methods.rb, line 493 def with(*args) raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given? check_if_method_has_arguments!(__callee__, args) spawn.with!(*args) end
Добавляет выражение общего вида (CTE), которое затем можно использовать в другом операторе SELECT.
Примечание: CTE поддерживаются только в MySQL для версий 8.0 и выше. Вы не сможете использовать CTE с MySQL 5.7.
Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
# => ActiveRecord::Relation
# WITH posts_with_tags AS (
# SELECT * FROM posts WHERE (tags_count > 0)
# )
# SELECT * FROM posts
Вы также можете передать массив подзапросов, которые должны быть объединены с помощью +UNION ALL+.
Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
# => ActiveRecord::Relation
# WITH posts_with_tags_or_comments AS (
# (SELECT * FROM posts WHERE (tags_count > 0))
# UNION ALL
# (SELECT * FROM posts WHERE (comments_count > 0))
# )
# SELECT * FROM posts
После определения выражения общего вида можно использовать настраиваемые FROM значения или JOIN для ссылки на него.
Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
# => ActiveRecord::Relation
# WITH posts_with_tags AS (
# SELECT * FROM posts WHERE (tags_count > 0)
# )
# SELECT * FROM posts_with_tags AS posts
Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
# => ActiveRecord::Relation
# WITH posts_with_tags AS (
# SELECT * FROM posts WHERE (tags_count > 0)
# )
# SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
Рекомендуется передавать запрос в виде ActiveRecord::Relation. Если это невозможно, и вы убедились, что это безопасно для базы данных, вы можете передать его как SQL-литерал с помощью Arel.
Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
Следует проявлять особую осторожность, чтобы избежать уязвимостей SQL-инъекций. Этот метод не следует использовать со значениями, которые содержат небезопасные данные, включая необработанный ввод.
Для добавления нескольких CTE просто передайте несколько пар ключ-значение
Post.with(
posts_with_comments: Post.where("comments_count > ?", 0),
posts_with_tags: Post.where("tags_count > ?", 0)
)
или используйте цепочку вызовов .with
Post
.with(posts_with_comments: Post.where("comments_count > ?", 0))
.with(posts_with_tags: Post.where("tags_count > ?", 0))
# File activerecord/lib/active_record/relation/query_methods.rb, line 518 def with_recursive(*args) check_if_method_has_arguments!(__callee__, args) spawn.with_recursive!(*args) end
Добавляет рекурсивное выражение общего вида (CTE), которое затем можно использовать в другом операторе SELECT.
Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
# => ActiveRecord::Relation
# WITH RECURSIVE post_and_replies AS (
# (SELECT * FROM posts WHERE id = 42)
# UNION ALL
# (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
# )
# SELECT * FROM posts
См. «#with» для получения дополнительной информации.
Защищенные методы экземпляров
# File activerecord/lib/active_record/relation/query_methods.rb, line 1662
def arel_columns(columns)
columns.flat_map do |field|
case field
when Symbol, String
arel_column(field)
when Proc
field.call
when Hash
arel_columns_from_hash(field)
else
field
end
end
end # File activerecord/lib/active_record/relation/query_methods.rb, line 1656 def async! @async = true self end
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.