Spec-Zone.ru › Ruby on Rails 8.0

модуль 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 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 блочных комментариев «/*» и «*/» будут автоматически добавлены.

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

create_with(value) Показать исходный код
# 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'
distinct(value = true) Показать исходный код
# 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
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 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 если записи не указаны или если какая-либо из записей в коллекции (если передаётся коллекция) не является экземпляром той же модели, что и отношение.

Также алиас: without
extending(*modules, &block) Показать исходный код
# 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
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 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')
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 может указывать на столбец перечисления; фактический сгенерированный запрос может отличаться в зависимости от базы данных и определения столбца.

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. Для каждой ассоциации выполняется отдельный запрос, если 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», даже если у соответствующего пользователя есть другие дополнительные посты.

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"

Несколько 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
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 1238
def lock(locks = true)
  spawn.lock!(locks)
end

Устанавливает параметры блокировки (по умолчанию true). Более подробную информацию о блокировках см. в ActiveRecord::Locking.

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

Позволяет изменить ранее заданное утверждение выбора.

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). Обратите внимание, что мы отключаем все утверждение выбора.

reverse_order() Показать исходный код
# 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'
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 1324
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 1541
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. После оформления значения вставляются с помощью тех же 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 и возвращает текущее отношение.

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 posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
# )
# SELECT * FROM posts

См. «#with» для получения дополнительной информации.

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

Защищенные методы экземпляров

arel_columns(columns) Показать исходный код
# 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
async!() Показать исходный код
# 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.

Spec-Zone.ru

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