модуль ActiveRecord::QueryMethods
Константы
- FROZEN_EMPTY_ARRAY
- FROZEN_EMPTY_HASH
- VALID_UNSCOPING_VALUES
Общедоступные методы экземпляра
# File activerecord/lib/active_record/relation/query_methods.rb, line 101
def bound_attributes
if limit_value && !string_containing_comma?(limit_value)
limit_bind = Attribute.with_cast_value(
"LIMIT".freeze,
connection.sanitize_limit(limit_value),
Type::Value.new,
)
end
if offset_value
offset_bind = Attribute.with_cast_value(
"OFFSET".freeze,
offset_value.to_i,
Type::Value.new,
)
end
connection.combine_bind_parameters(
from_clause: from_clause.binds,
join_clause: arel.bind_values,
where_clause: where_clause.binds,
having_clause: having_clause.binds,
limit: limit_bind,
offset: offset_bind,
)
end # File activerecord/lib/active_record/relation/query_methods.rb, line 818 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 865 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 185 def eager_load(*args) check_if_method_has_arguments!(:eager_load, args) spawn.eager_load!(*args) end
Принудительно загружает связанные данные выполняя LEFT OUTER JOIN на args:
User.eager_load(:posts) # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = # "users"."id"
# File activerecord/lib/active_record/relation/query_methods.rb, line 915
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 846 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
Topic.select('a.title').from(Topic.approved, :a)
# SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
# File activerecord/lib/active_record/relation/query_methods.rb, line 306 def group(*args) check_if_method_has_arguments!(:group, 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 688 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 166 def includes(*args) check_if_method_has_arguments!(:includes, args) spawn.includes!(*args) end
Указывает отношения, которые следует включить в результирующий набор. Например:
users = User.includes(:address) users.each do |user| user.address.city end
позволяет получить доступ к атрибуту address модели User без выполнения дополнительного запроса. Это часто приводит к повышению производительности по сравнению с простым объединением.
Также можно указать несколько отношений, например так:
users = User.includes(:address, :friends)
Загрузка вложенных отношений возможна с помощью хэша:
users = User.includes(:address, friends: [:address, :followers])
условия
Если вы хотите добавить условия к включенным моделям, вам необходимо явно их указать. Например:
User.includes(:posts).where('posts.name = ?', 'example')
Будет выдавать ошибку, но это сработает:
User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
Обратите внимание, что includes работает с именами ассоциаций, а references нуждается в фактическом имени таблицы.
# File activerecord/lib/active_record/relation/query_methods.rb, line 467 def joins(*args) check_if_method_has_arguments!(:joins, 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" "comments_posts" # ON "comments_posts"."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
# File activerecord/lib/active_record/relation/query_methods.rb, line 484 def left_outer_joins(*args) check_if_method_has_arguments!(:left_outer_joins, args) args.compact! args.flatten! spawn.left_outer_joins!(*args) end
Выполняет левые внешние объединения с 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 705 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 739 def lock(locks = true) spawn.lock!(locks) end
Устанавливает параметры блокировки (по умолчанию true). Более подробную информацию о блокировке см. в ActiveRecord::Locking.
# File activerecord/lib/active_record/relation/query_methods.rb, line 782
def none
where("1=0").extending!(NullRelation)
end Возвращает цепочечное отношение с нулевыми записями.
Возвращаемое отношение реализует шаблон Null Объект. Это объект с определённым поведением null и всегда возвращает пустой массив записей без запроса к базе данных.
Любое последующее условие, присоединённое к возвращаемому отношению, будет продолжать генерировать пустое отношение и не будет выполнять никаких запросов к базе данных.
Используется в случаях, когда метод или область могут вернуть ноль записей, но результат должен быть цепочечным.
Например:
@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 728 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 663
def or(other)
unless other.is_a? Relation
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
end
spawn.or!(other)
end Возвращает новое отношение, которое является логическим объединением этого отношения и отношения, переданного в качестве аргумента.
Два отношения должны быть структурно совместимы: они должны охватывать одну и ту же модель и отличаться только where (если не определён group) или having (если group присутствует). Ни одно из отношений не может иметь установленное limit, offset или distinct.
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 337 def order(*args) check_if_method_has_arguments!(:order, args) spawn.order!(*args) end
Позволяет указать атрибут сортировки:
User.order(:name)
# SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
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
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
# File activerecord/lib/active_record/relation/query_methods.rb, line 199 def preload(*args) check_if_method_has_arguments!(:preload, args) spawn.preload!(*args) end
Позволяет предварительно загрузить args, аналогично includes:
User.preload(:posts) # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
# File activerecord/lib/active_record/relation/query_methods.rb, line 796 def readonly(value = true) spawn.readonly!(value) end
Устанавливает атрибуты только для чтения для возвращаемой связи. Если значение равно true (по умолчанию), попытка обновить запись приведет к ошибке.
users = User.readonly users.first.save => ActiveRecord::ReadOnlyRecord: User is marked as readonly
# File activerecord/lib/active_record/relation/query_methods.rb, line 219 def references(*table_names) check_if_method_has_arguments!(:references, table_names) spawn.references!(*table_names) end
Используется для указания того, что указанные table_names ссылаются на строку SQL и, следовательно, должны быть JOINed в любом запросе, а не загружаться отдельно. Этот метод работает только в сочетании с 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 358 def reorder(*args) check_if_method_has_arguments!(:reorder, args) 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 936 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 649 def rewhere(conditions) unscope(where: conditions.keys).where(conditions) 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 271 def select(*fields) return super if block_given? raise ArgumentError, 'Call this with at least one field' if fields.empty? spawn._select!(*fields) end
Работает двумя уникальными способами.
Во-первых: принимает блок, чтобы его можно было использовать так же, как и +Array#select+.
Model.all.select { |m| m.field == value }
Это создаст массив объектов из базы данных для области видимости, преобразует их в массив и проитерируется по ним с помощью +Array#select+.
Во-вторых: изменяет оператор SELECT для запроса, чтобы извлекались только определенные поля:
Model.select(:field) # => [#<Model id: nil, field: "value">]
Хотя в приведенном выше примере кажется, что этот метод возвращает массив, он фактически возвращает объект relations и к нему можно добавить другие методы запросов, такие как другие методы в ActiveRecord::QueryMethods.
Аргументом метода также может быть массив полей.
Model.select(:field, :other_field, :and_one_more) # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
Вы также можете использовать одну или несколько строк, которые будут использоваться без изменений в качестве полей SELECT.
Model.select('field AS field_one', 'other_field AS field_two')
# => [#<Model id: nil, field: "value", other_field: "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
# File activerecord/lib/active_record/relation/query_methods.rb, line 408 def unscope(*args) check_if_method_has_arguments!(:unscope, 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 619
def where(opts = :chain, *rest)
if :chain == opts
WhereChain.new(spawn)
elsif opts.blank?
self
else
spawn.where!(opts, *rest)
end
end Возвращает новую связь, которая является результатом фильтрации текущей связи в соответствии с условиями в аргументах.
where принимает условия в нескольких форматах. В примерах ниже показан результирующий SQL; фактический генерируемый запрос может отличаться в зависимости от адаптера базы данных.
строка
Одна строка без дополнительных аргументов передается в конструктор запроса в качестве фрагмента SQL и используется в предложении where запроса.
Client.where("orders_count = '2'")
# SELECT * from clients where orders_count = '2';
Обратите внимание, что создание собственной строки из пользовательского ввода может сделать ваш приложение уязвимым к атакам с внедрением кода, если это не сделано правильно. В качестве альтернативы рекомендуется использовать один из следующих методов.
массив
Если передается массив, то первый элемент массива обрабатывается как шаблон, а оставшиеся элементы вставляются в шаблон для генерации условия. Active Record позаботится о построении запроса, чтобы избежать атак с внедрением кода, и преобразует тип ruby в тип базы данных при необходимости. Элементы вставляются в строку в том порядке, в котором они появляются.
User.where(["name = ? and email = ?", "Joe", "joe@example.com"]) # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
В качестве альтернативы можно использовать именованные заглушки в шаблоне и передать хеш в качестве второго элемента массива. Имена в шаблоне заменяются соответствующими значениями из хеша.
User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
Это может сделать код более читаемым в сложных запросах.
Наконец, можно использовать экранирование % в стиле sprintf в шаблоне. Это работает немного иначе, чем предыдущие методы; вы сами отвечаете за правильное форматирование значений в шаблоне. Значения передаются в соединитель для форматирования, но вызывающий код отвечает за то, чтобы они были заключены в кавычки в результирующем SQL. После форматирования значения вставляются с помощью тех же экранирований, что и метод ядра Ruby Kernel::sprintf.
User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"]) # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
Если where вызывается с несколькими аргументами, они обрабатываются так, как будто они были переданы как элементы одного массива.
User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
При использовании строк для указания условий вы можете использовать любой оператор, доступный из базы данных. Хотя это обеспечивает максимальную гибкость, вы также можете непреднамеренно ввести зависимость от базовой базы данных. Если ваш код предназначен для общего использования, протестируйте его с несколькими базами данных.
хеш
where также примет условие в виде хеша, в котором ключи — это поля, а значения — значения, которые нужно искать.
Поля могут быть символами или строками. Значения могут быть отдельными значениями, массивами или диапазонами.
User.where({ name: "Joe", email: "joe@example.com" })
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
User.where({ name: ["Alice", "Bob"]})
# SELECT * FROM users WHERE name IN ('Alice', 'Bob')
User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
# SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
В случае связи belongs_to можно использовать ключ ассоциации для указания модели, если в качестве значения используется объект ActiveRecord.
author = Author.find(1) # The following queries will be equivalent: Post.where(author: author) Post.where(author_id: author)
Это также работает с полиморфными связями belongs_to:
treasure = Treasure.create(name: 'gold coins') treasure.price_estimates << PriceEstimate.create(price: 125) # The following queries will be equivalent: PriceEstimate.where(estimate_of: treasure) PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
Объединения
Если связь является результатом объединения, вы можете создать условие, которое использует любую из таблиц в объединении. Для строчных и массивно-ориентированных условий используйте имя таблицы в условии.
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, который может быть объединен с not для возврата новой связи, которая отрицает условие where.
User.where.not(name: "Jon") # SELECT * FROM users WHERE name != 'Jon'
См. WhereChain для получения дополнительной информации о not.
пустое условие
Если условие — это объект любого типа, относящегося к пустому значению, то where — это операция без действия, которая возвращает текущую связь.
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.