Spec-Zone.ru › Ruby on Rails 7.2

модуль ActiveRecord::QueryMethods

Константы

FROZEN_EMPTY_ARRAY
FROZEN_EMPTY_HASH
VALID_UNSCOPING_VALUES

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

and(other) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1113
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 1507
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 1324
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 1388
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 283
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 1552
def excluding(*records)
  relations = records.extract! { |element| element.is_a?(Relation) }
  records.flatten!(1)
  records.compact!

  unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
    raise ArgumentError, "You must only pass a single or collection of #{klass.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 1434
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 334
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 1369
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. В противном случае используется псевдоним «подзапрос»:

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 564
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 1175
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) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 699
def in_order_of(column, values)
  klass.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| type_caster.type_cast_for_database(column, value) }
  arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)

  where_clause =
    if values.include?(nil)
      arel_column.in(values.compact).or(arel_column.eq(nil))
    else
      arel_column.in(values)
    end

  spawn
    .order!(build_case_for_value_position(arel_column, values))
    .where!(where_clause)
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
includes(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 247
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 уже содержит записи, второй запрос для записей больше не выполняется.

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

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

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

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

invert_where() Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1079
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 846
def joins(*args)
  check_if_method_has_arguments!(__callee__, args)
  spawn.joins!(*args)
end

Выполняет объединения 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 861
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 1189
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 1216
def lock(locks = true)
  spawn.lock!(locks)
end

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

none() Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1259
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 1205
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 1463
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 1145
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 647
def order(*args)
  check_if_method_has_arguments!(__callee__, args) do
    sanitize_order_arguments(args)
  end
  spawn.order!(*args)
end

Применяет условие сортировки к запросу.

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 315
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 1287
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 348
def references(*table_names)
  check_if_method_has_arguments!(__callee__, table_names)
  spawn.references!(*table_names)
end

Используйте для указания, что указанные table_names ссылаются на строку SQL и, следовательно, должны быть +JOIN+ed в любом запросе, а не загружаться отдельно. Этот метод работает только совместно с 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 584
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 730
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 532
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 1476
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 1039
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 406
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">]

Хотя в приведенном выше примере кажется, что этот метод возвращает массив, на самом деле он возвращает объект relation и к нему можно добавить другие методы запросов, такие как другие методы в 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 1302
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 1099
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 1519
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 784
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 1011
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 заботится о формировании запроса для предотвращения атак с внедрением кода и преобразует типы Руби в типы базы данных при необходимости. Элементы вставляются в строку в порядке их появления.

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';

Это может сделать код более читаемым в сложных запросах.

Наконец, вы можете использовать % escapes в стиле sprintf в шаблоне. Это работает немного иначе, чем предыдущие методы; вы отвечаете за обеспечение того, что значения в шаблоне правильно заключены в кавычки. Значения передаются соединителю для оформления, но вызов должен убедиться, что они заключены в кавычки в результирующем SQL. После оформления значения вставляются с использованием тех же escape-последовательностей, что и метод ядра Руби 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 486
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 510
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 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

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

async!() Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1634
def async!
  @async = true
  self
end

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

Spec-Zone.ru

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