модуль ActiveRecord::QueryMethods
Константы
- FROZEN_EMPTY_ARRAY
- FROZEN_EMPTY_HASH
- VALID_UNSCOPING_VALUES
Общедоступные методы экземпляров
# 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)
# 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-блочных комментариев «/*» и «*/» будут добавлены автоматически.
Некоторая обработка экранирования выполняется, однако не следует использовать недоверенный пользовательский ввод.
# 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'
# 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
# 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" # ...
ПРИМЕЧАНИЕ: Загрузка ассоциаций в соединении может привести к большому количеству строк с избыточными данными и низкой производительностью при масштабировании.
# 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, если либо не указаны записи, либо какая-либо запись в наборе (если передан набор) не является экземпляром той же модели, что и отношение.
# 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
# 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)
# 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')
# 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">]
# 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')
# 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
# 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», даже если у соответствующего пользователя есть другие сообщения.
# 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)
# 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
# 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"
# 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'
# File activerecord/lib/active_record/relation/query_methods.rb, line 1137 def lock(locks = true) spawn.lock!(locks) end
Устанавливает настройки блокировки (по умолчанию true). Для получения более подробной информации о блокировке, пожалуйста, обратитесь к ActiveRecord::Locking.
# 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
# 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")
# 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"
# 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))
# 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'
# 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 ...
# 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
# 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
# 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). Обратите внимание, что мы отменяем всю установку условия группировки.
# 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.
# 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.
# 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'
# 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.
# 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
# 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
# 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
# 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 Удалить дублирующие значения.
# 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) }
# 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 является недействующей операцией и возвращает текущее отношение.
# 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))
Защищенные методы экземпляра
# 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.