Spec-Zone.ru › Ruby on Rails 7.1

модуль ActiveRecord::QueryMethods

Константы

FROZEN_EMPTY_ARRAY
FROZEN_EMPTY_HASH
VALID_UNSCOPING_VALUES

Общедоступные методы экземпляров

and(other) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1034
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 1428
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 1245
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 1309
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 267
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 1470
def excluding(*records)
  records.flatten!(1)
  records.compact!

  unless records.all?(klass)
    raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
  end

  spawn.excluding!(records)
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 = 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 1355
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 318
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 1290
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 512
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 1096
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 620
def in_order_of(column, values)
  klass.disallow_raw_sql!([column], permit: connection.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

Позволяет указать порядок по определенному набору значений.

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
includes(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 231
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' })

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

invert_where() Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1000
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 767
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 782
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 1110
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 1137
def lock(locks = true)
  spawn.lock!(locks)
end

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

none() Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 1180
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 1126
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 1384
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 1066
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 595
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 299
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 1208
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 332
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 532
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 651
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 480
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 1397
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 960
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 390
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 1223
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 1020
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 1440
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 705
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 932
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';

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

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

with(*args) Показать исходный код
# File activerecord/lib/active_record/relation/query_methods.rb, line 459
def with(*args)
  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

После определения выражения общего вида вы можете использовать пользовательское значение 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))
without(*records)
Псевдоним для: excluding

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

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

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

Spec-Zone.ru

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