Spec-Zone.ru › Ruby on Rails 8.1

модуль ActiveRecord::QueryMethods

Константы

FROZEN_EMPTY_ARRAY
FROZEN_EMPTY_HASH
VALID_UNSCOPING_VALUES

Открытые методы экземпляра

and (other) Показать исходный код
# 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)
annotate (*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1530
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 «/*» и «*/» будут добавлены автоматически.

Выполняется экранирование, однако не следует использовать недоверенные пользовательские данные.

create_with (value) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1347
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'
distinct (value = true) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1411
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
eager_load (*args) Показать исходный код
# 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"
#   ...

ПРИМЕЧАНИЕ: Загрузка ассоциаций с помощью объединения может привести к появлению множества строк с избыточными данными и плохо масштабируется.

excluding (*records) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1575
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, если записи не указаны или если какая-либо запись в коллекции (если передана коллекция) не является экземпляром той же модели, к которой относится отношение.

Также имеет псевдоним: without
extending (*modules, &block) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1457
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
extract_associated (association) Показать исходный код
# 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)
from (value, subquery_name = nil) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1392
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

Если передать второй аргумент (строку или символ), он задаст псевдоним для выражения FROM в SQL. В противном случае используется псевдоним «subquery»:

Topic.select('a.title').from(Topic.approved, :a)
# SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a

Несколько аргументов в выражение FROM SQL не добавляются. Используется последний добавленный вызов from:

Topic.select('title').from(Topic.approved).from(Topic.inactive)
# SELECT title FROM (SELECT topics.* FROM topics WHERE topics.active = 'f') subquery

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

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')
group (*args) Показать исходный код
# 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">]
having (opts, *rest) Показать исходный код
# 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')
in_order_of (column, values, filter: true) Показать исходный код
# 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 может ссылаться на столбец enum; фактически сгенерированный запрос может различаться в зависимости от адаптера базы данных и определения столбца.

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
includes (*args) Показать исходный код
# 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. Для каждой ассоциации выполняется отдельный запрос, если только условия не требуют объединения таблиц.

Например:

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 отдельных запросов все адреса загружаются одним запросом.

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

Можно также указать несколько ассоциаций. Для каждой ассоциации будет выполнен дополнительный запрос:

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 уже содержит записи posts, второй запрос для posts больше не выполняется.

Обратите внимание: includes использует имена ассоциаций, тогда как references требует фактическое имя таблицы.

Если передать условия через Hash, вызывать references явно не нужно, поскольку where самостоятельно ссылается на таблицы. Например, следующий вариант будет работать правильно:

User.includes(:posts).where(posts: { name: 'example' })

ПРИМЕЧАНИЕ: Условия влияют на обе стороны ассоциации. Например, приведенный выше код вернет только пользователей, у которых есть запись post с именем «example», и включит только записи posts с именем «example», даже если у соответствующего пользователя есть другие записи posts.

invert_where () Показать исходный код
# 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)
joins (*args) Показать исходный код
# 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"

Несколько объединений:

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" ON "comments"."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
left_joins (*args)
Псевдоним для: left_outer_joins
left_outer_joins (*args) Показать исходный код
# 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"
Также имеет псевдоним: left_joins
limit (value) Показать исходный код
# 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'
lock (locks = true) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1239
def lock(locks = true)
  spawn.lock!(locks)
end

Задает параметры блокировки (по умолчанию true). Дополнительные сведения о блокировках см. в разделе ActiveRecord::Locking.

none () Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1282
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
offset (value) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1228
def offset(value)
  spawn.offset!(value)
end

Задает количество строк, которые нужно пропустить перед возвратом результатов.

User.offset(10) # generated SQL has "OFFSET 10"

Следует использовать вместе с order.

User.offset(10).order("name ASC")
optimizer_hints (*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1486
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"
or (other) Показать исходный код
# 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))
order (*args) Показать исходный код
# 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'
preload (*args) Показать исходный код
# 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 ...
readonly (value = true) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1310
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
references (*table_names) Показать исходный код
# 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
regroup (*args) Показать исходный код
# 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). Обратите внимание, что область видимости очищается от всего условия группировки.

reorder (*args) Показать исходный код
# 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.

reselect (*args) Показать исходный код
# 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

Позволяет изменить ранее заданную инструкцию select.

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). Обратите внимание, что область видимости очищается от всей инструкции select.

reverse_order () Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1499
def reverse_order
  spawn.reverse_order!
end

Обращает порядок сортировки, заданный для отношения.

User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
rewhere (conditions) Показать исходный код
# 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.

select (*fields) Показать исходный код
# 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
Вызывает метод суперкласса
strict_loading (value = true) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1325
def strict_loading(value = true)
  spawn.strict_loading!(value)
end

Переводит возвращаемое отношение в режим strict_loading. Если запись попытается лениво загрузить ассоциацию, будет вызвана ошибка.

user = User.strict_loading.first
user.comments.to_a
# => ActiveRecord::StrictLoadingViolationError
structurally_compatible? (other) Показать исходный код
# 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
uniq! (name) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1542
def uniq!(name)
  if values = @values[name]
    values.uniq! if values.is_a?(Array) && !values.empty?
  end
  self
end

Удаляет повторяющиеся значения.

unscope (*args) Показать исходный код
# 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) }
where (*args) Показать исходный код
# 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 они были заключены в кавычки. После экранирования значения подставляются с использованием тех же спецификаторов, что и в основном методе 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 ничего не делает и возвращает текущее отношение.

with (*args) Показать исходный код
# 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))
with_recursive (*args) Показать исходный код
# 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 post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
# )
# SELECT * FROM posts

Дополнительные сведения см. в разделе «#with».

without (*records)
Псевдоним для: excluding

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API