class ActiveRecord::Relation
Отношение Active Record
Константы
- CLAUSE_METHODS
- INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL
- MULTI_VALUE_METHODS
- SINGLE_VALUE_METHODS
- VALUE_METHODS
Атрибуты
Публичные методы класса
# File activerecord/lib/active_record/relation.rb, line 77
def initialize(model, table: nil, predicate_builder: nil, values: {})
if table
predicate_builder ||= model.predicate_builder.with(TableMetadata.new(model, table))
else
table = model.arel_table
predicate_builder ||= model.predicate_builder
end
@model = model
@table = table
@values = values
@loaded = false
@predicate_builder = predicate_builder
@delegate_to_model = false
@future_result = nil
@records = nil
@async = false
@none = false
end Открытые методы экземпляра
# File activerecord/lib/active_record/relation.rb, line 1273
def ==(other)
case other
when Associations::CollectionProxy, AssociationRelation
self == other.records
when Relation
other.to_sql == to_sql
when Array
records == other
end
end Сравнивает два отношения на равенство.
# File activerecord/lib/active_record/relation.rb, line 401 def any?(*args) return false if @none return super if args.present? || block_given? !empty? end
Возвращает true, если существуют какие-либо записи.
Если передан аргумент-шаблон, этот метод проверяет, соответствуют ли элементы в Enumerable шаблону с помощью оператора проверки равенства с учетом регистра (===).
posts.any?(Post) # => true or false
# File activerecord/lib/active_record/relation.rb, line 1294 def blank? records.blank? end
Возвращает true, если отношение пустое.
# File activerecord/lib/active_record/relation.rb, line 448
def cache_key(timestamp_column = "updated_at")
@cache_keys ||= {}
@cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column)
end Возвращает стабильный ключ кэша, который можно использовать для идентификации этого запроса. Ключ кэша формируется на основе отпечатка SQL-запроса.
Product.where("name like ?", "%Cosmic Encounter%").cache_key
# => "products/query-1850ab3d302391b85b8693e941286659"
Если параметр ActiveRecord::Base.collection_cache_versioning отключен, как это было в Rails 6.0 и более ранних версиях, ключ кэша также будет включать версию.
ActiveRecord::Base.collection_cache_versioning = false
Product.where("name like ?", "%Cosmic Encounter%").cache_key
# => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
Также можно передать пользовательский столбец временной метки, чтобы получить временную метку последней обновленной записи.
Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
# File activerecord/lib/active_record/relation.rb, line 529
def cache_key_with_version
if version = cache_version
"#{cache_key}-#{version}"
else
cache_key
end
end Возвращает ключ кэша вместе с версией.
# File activerecord/lib/active_record/relation.rb, line 475
def cache_version(timestamp_column = :updated_at)
if model.collection_cache_versioning
@cache_versions ||= {}
@cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
end
end Возвращает версию кэша, которую можно использовать вместе с ключом кэша для создания схемы кэширования с повторным использованием. Версия кэша формируется на основе количества записей, соответствующих запросу, и временной метки последней обновленной записи. Когда запросу начинает соответствовать новая запись или одна из существующих записей обновляется либо удаляется, версия кэша меняется.
Если коллекция загружена, метод переберет записи для создания временной метки, в противном случае он выполнит один SQL-запрос, например:
SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%') # File activerecord/lib/active_record/relation.rb, line 154
def create(attributes = nil, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, &block) }
else
block = current_scope_restoring_block(&block)
scoping { _create(attributes, &block) }
end
end Пытается создать новую запись с теми же атрибутами области видимости, которые определены в отношении. Если проверка завершается неудачей, возвращает инициализированный объект.
Ожидает аргументы в том же формате, что и ActiveRecord::Base.create.
Примеры
users = User.where(name: 'Oscar')
users.create # => #<User id: 3, name: "Oscar", ...>
users.create(name: 'fxn')
users.create # => #<User id: 4, name: "fxn", ...>
users.create { |user| user.name = 'tenderlove' }
# => #<User id: 5, name: "tenderlove", ...>
users.create(name: nil) # validation on name
# => #<User id: nil, name: nil, ...>
# File activerecord/lib/active_record/relation.rb, line 169
def create!(attributes = nil, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create!(attr, &block) }
else
block = current_scope_restoring_block(&block)
scoping { _create!(attributes, &block) }
end
end Похоже на create, но вызывает create! базового класса. При ошибке проверки вызывает исключение.
Ожидает аргументы в том же формате, что и ActiveRecord::Base.create!.
# File activerecord/lib/active_record/relation.rb, line 273
def create_or_find_by(attributes, &block)
with_connection do |connection|
record = nil
transaction(requires_new: true) do
record = create(attributes, &block)
record._last_transaction_return_status || raise(ActiveRecord::Rollback)
end
record
rescue ActiveRecord::RecordNotUnique
if connection.transaction_open?
where(attributes).lock.find_by!(attributes)
else
find_by!(attributes)
end
end
end Пытается создать запись с заданными атрибутами в таблице, для одного или нескольких столбцов которой установлено ограничение уникальности базы данных. Если строка уже существует с одним или несколькими такими ограничениями уникальности, перехватывается исключение, которое обычно возникло бы при такой вставке, а существующая запись с этими атрибутами ищется с помощью find_by!.
Похоже на find_or_create_by, но сначала пытается создать запись. Поэтому этот метод лучше подходит для случаев, когда запись, скорее всего, еще не существует.
Однако у create_or_find_by есть несколько недостатков:
-
В таблице должны быть заданы соответствующие столбцы с ограничениями уникальности базы данных.
-
Нарушение ограничения уникальности может быть вызвано только одним или, по крайней мере, не всеми заданными атрибутами. Это означает, что последующий вызов find_by! может не найти соответствующую запись, и тогда будет вызвано исключение
ActiveRecord::RecordNotFound, а не возвращена запись с заданными атрибутами. -
Хотя мы избегаем состояния гонки между SELECT -> INSERT, возникающего при использовании
find_or_create_by, появляется другое состояние гонки между INSERT -> SELECT. Оно может возникнуть, если другой клиент выполнит DELETE между этими двумя инструкциями. Однако для большинства приложений вероятность столкнуться с таким условием значительно ниже. -
Для управления потоком выполнения используются исключения, что может незначительно замедлить работу.
-
Первичный ключ может увеличиваться автоматически при каждом создании, даже если оно завершилось неудачей. Это может ускорить исчерпание целочисленных значений, если в таблице по-прежнему используется первичный ключ типа int (примечание: начиная с Rails 5.1 во всех приложениях Rails по умолчанию используется bigint, для которого эта проблема неактуальна).
-
Для столбцов с ограничениями уникальности базы данных не следует задавать проверки уникальности, иначе
createзавершится ошибкой проверки, а find_by никогда не будет вызван.
Этот метод вернет запись, если все заданные атрибуты защищены ограничениями уникальности (если только не возникнет состояние гонки INSERT -> DELETE -> SELECT). Но если создание было предпринято и завершилось ошибкой проверки, запись не будет сохранена, и вы получите то, что в такой ситуации возвращает create.
# File activerecord/lib/active_record/relation.rb, line 293
def create_or_find_by!(attributes, &block)
with_connection do |connection|
record = nil
transaction(requires_new: true) do
record = create!(attributes, &block)
record._last_transaction_return_status || raise(ActiveRecord::Rollback)
end
record
rescue ActiveRecord::RecordNotUnique
if connection.transaction_open?
where(attributes).lock.find_by!(attributes)
else
find_by!(attributes)
end
end
end Как и create_or_find_by, но вызывает create!, поэтому при недопустимости созданной записи возникает исключение.
# File activerecord/lib/active_record/relation.rb, line 1077 def delete(id_or_array) return 0 if id_or_array.nil? || (id_or_array.is_a?(Array) && id_or_array.empty?) where(model.primary_key => id_or_array).delete_all end
Удаляет строку с первичным ключом, соответствующим аргументу id, с помощью SQL-инструкции DELETE и возвращает количество удаленных строк. Объекты Active Record не создаются, поэтому обратные вызовы объекта не выполняются, включая параметры ассоциации :dependent.
Можно удалить сразу несколько строк, передав Array из id.
Примечание: хотя этот метод часто намного быстрее альтернативы destroy, пропуск обратных вызовов может обойти бизнес-логику приложения, обеспечивающую целостность ссылок или выполняющую другие важные задачи.
Примеры
# Delete a single row Todo.delete(1) # Delete multiple rows Todo.delete([2,3,4])
# File activerecord/lib/active_record/relation.rb, line 1033
def delete_all
return 0 if @none
invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
value = @values[method]
method == :distinct ? value : value&.any?
end
if invalid_methods.any?
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
end
model.with_connection do |c|
arel = eager_loading? ? apply_join_dependency.arel : arel()
arel.source.left = table
key = if model.composite_primary_key?
primary_key.map { |pk| table[pk] }
else
table[primary_key]
end
stmt = arel.compile_delete(key)
c.delete(stmt, "#{model} Delete All").tap { reset }
end
end Удаляет записи, не создавая их предварительно, а значит, не вызывая метод #destroy и не запуская обратные вызовы. Это одна SQL-инструкция DELETE, которая напрямую выполняется в базе данных и намного эффективнее, чем destroy_all. Однако будьте осторожны с отношениями: в частности, правила :dependent, определенные для ассоциаций, не учитываются. Возвращает количество затронутых строк.
Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
Этот вызов удаляет все затронутые записи posts за один раз с помощью одной инструкции DELETE. Если необходимо уничтожить зависимые ассоциации или вызвать обратные вызовы before_* или after_destroy, используйте вместо этого метод destroy_all.
Если передан недопустимый метод, delete_all вызывает ActiveRecordError:
Post.distinct.delete_all # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
# File activerecord/lib/active_record/relation.rb, line 1139 def delete_by(*args) where(*args).delete_all end
Находит и удаляет все записи, соответствующие указанным условиям. Это краткая форма записи для relation.where(condition).delete_all. Возвращает количество затронутых строк.
Если запись не найдена, возвращает 0, поскольку затронуто ноль строк.
Person.delete_by(id: 13)
Person.delete_by(name: 'Spartacus', rating: 4)
Person.delete_by("published_at < ?", 2.weeks.ago)
# File activerecord/lib/active_record/relation.rb, line 1103
def destroy(id)
multiple_ids = if model.composite_primary_key?
id.first.is_a?(Array)
else
id.is_a?(Array)
end
if multiple_ids
find(id).each(&:destroy)
else
find(id).destroy
end
end Уничтожает объект (или несколько объектов) с указанным идентификатором. Сначала объект создается, поэтому перед его удалением запускаются все обратные вызовы и фильтры. Этот метод менее эффективен, чем delete, но позволяет запускать методы очистки и выполнять другие действия.
По сути, метод находит объект (или несколько объектов) с заданным идентификатором, создает новый объект из атрибутов, а затем вызывает для него destroy.
Параметры
-
id— это должен быть идентификатор или массив идентификаторов объектов для уничтожения.
Примеры
# Destroy a single object Todo.destroy(1) # Destroy multiple objects todos = [1,2,3] Todo.destroy(todos)
# File activerecord/lib/active_record/relation.rb, line 1011
def destroy_all
records.each(&:destroy).tap { reset }
end Уничтожает записи, создавая каждый объект и вызывая для него метод #destroy. Для каждого объекта выполняются обратные вызовы (включая параметры ассоциации :dependent). Возвращает коллекцию уничтоженных объектов; каждый объект будет заморожен, чтобы показать, что его не следует изменять (поскольку его нельзя сохранить).
Примечание: создание объектов, выполнение обратных вызовов и удаление каждой записи могут занять много времени при одновременном удалении большого количества записей. Для каждой записи выполняется как минимум один SQL-запрос DELETE (или, возможно, больше, чтобы обеспечить выполнение обратных вызовов). Чтобы быстро удалить много строк, не учитывая их ассоциации и обратные вызовы, используйте вместо этого delete_all.
Примеры
Person.where(age: 0..18).destroy_all
# File activerecord/lib/active_record/relation.rb, line 1126 def destroy_by(*args) where(*args).destroy_all end
Находит и уничтожает все записи, соответствующие указанным условиям. Это краткая форма записи для relation.where(condition).destroy_all. Возвращает коллекцию уничтоженных объектов.
Если запись не найдена, возвращает пустой массив.
Person.destroy_by(id: 13)
Person.destroy_by(name: 'Spartacus', rating: 4)
Person.destroy_by("published_at < ?", 2.weeks.ago)
# File activerecord/lib/active_record/relation.rb, line 1258
def eager_loading?
@should_eager_load ||=
eager_load_values.any? ||
includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
end Возвращает true, если для отношения требуется жадная загрузка.
# File activerecord/lib/active_record/relation.rb, line 372
def empty?
return true if @none
if loaded?
records.empty?
else
!exists?
end
end Возвращает true, если записей нет.
# File activerecord/lib/active_record/relation.rb, line 358 def encode_with(coder) coder.represent_seq(nil, records) end
Сериализует объекты отношения Array.
# File activerecord/lib/active_record/relation.rb, line 342 def explain(*options) ExplainProxy.new(self, options) end
Выполняет EXPLAIN для запроса или запросов, инициируемых этим отношением, и возвращает результат в виде строки. Формат строки имитирует вывод оболочки базы данных.
User.all.explain # EXPLAIN SELECT `users`.* FROM `users` # ...
Обратите внимание, что этот метод фактически выполняет запросы, поскольку при жадной загрузке результаты некоторых запросов необходимы для выполнения последующих.
Чтобы выполнить EXPLAIN для запросов, созданных с помощью first, pluck и count, вызовите эти методы для explain:
User.all.explain.count # EXPLAIN SELECT COUNT(*) FROM `users` # ...
При необходимости можно передать имя столбца:
User.all.explain.maximum(:id) # EXPLAIN SELECT MAX(`users`.`id`) FROM `users` # ...
Дополнительные сведения см. в руководстве по интерфейсу запросов Active Record.
# File activerecord/lib/active_record/relation.rb, line 231 def find_or_create_by(attributes, &block) find_by(attributes) || create_or_find_by(attributes, &block) end
Находит первую запись с заданными атрибутами или создает запись с этими атрибутами, если такая не найдена:
# Find the first user named "Penélope" or create a new one. User.find_or_create_by(first_name: 'Penélope') # => #<User id: 1, first_name: "Penélope", last_name: nil> # Find the first user named "Penélope" or create a new one. # We already have one so the existing record will be returned. User.find_or_create_by(first_name: 'Penélope') # => #<User id: 1, first_name: "Penélope", last_name: nil> # Find the first user named "Scarlett" or create a new one with # a particular last name. User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett') # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
Этот метод принимает блок, который передается в create. Последний пример выше можно альтернативно записать так:
# Find the first user named "Scarlett" or create a new one with a # particular last name. User.find_or_create_by(first_name: 'Scarlett') do |user| user.last_name = 'Johansson' end # => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
Этот метод всегда возвращает запись, но если создание было предпринято и завершилось ошибкой проверки, запись не будет сохранена, и вы получите то, что в такой ситуации возвращает create.
Если создание завершилось неудачей из-за ограничения уникальности, метод предполагает, что возникло состояние гонки, и повторно пытается найти запись. Если по какой-либо причине второй поиск также не находит запись из-за параллельного DELETE, будет вызвано исключение ActiveRecord::RecordNotFound.
Обратите внимание: этот метод не является атомарным. Сначала он выполняет SELECT, а если результатов нет — пытается выполнить INSERT. Поэтому, если в таблице нет соответствующего ограничения уникальности, могут появиться две или более похожие записи.
# File activerecord/lib/active_record/relation.rb, line 238 def find_or_create_by!(attributes, &block) find_by(attributes) || create_or_find_by!(attributes, &block) end
Как и find_or_create_by, но вызывает create!, поэтому при недопустимости созданной записи возникает исключение.
# File activerecord/lib/active_record/relation.rb, line 312 def find_or_initialize_by(attributes, &block) find_by(attributes) || new(attributes, &block) end
Как и find_or_create_by, но вызывает new вместо create.
# File activerecord/lib/active_record/relation.rb, line 97 def initialize_copy(other) @values = @values.dup reset end
# File activerecord/lib/active_record/relation.rb, line 664 def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil) insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) end
Вставляет одну запись в базу данных с помощью одной SQL-инструкции INSERT. Модели не создаются, обратные вызовы и проверки Active Record не запускаются. Однако переданные значения проходят приведение типов и сериализацию Active Record.
Документацию см. в разделе insert_all.
# File activerecord/lib/active_record/relation.rb, line 753 def insert!(attributes, returning: nil, record_timestamps: nil) insert_all!([ attributes ], returning: returning, record_timestamps: record_timestamps) end
Вставляет одну запись в базу данных с помощью одной SQL-инструкции INSERT. Модели не создаются, обратные вызовы и проверки Active Record не запускаются. Однако переданные значения проходят приведение типов и сериализацию Active Record.
Дополнительные сведения см. в разделе insert_all!.
# File activerecord/lib/active_record/relation.rb, line 743 def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil) InsertAll.execute(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) end
Вставляет несколько записей в базу данных с помощью одной SQL-инструкции INSERT. Модели не создаются, обратные вызовы и проверки Active Record не запускаются. Однако переданные значения проходят приведение типов и сериализацию Active Record.
Параметр attributes — это Array хэшей. Каждый Hash задает атрибуты одной строки и должен содержать одинаковые ключи.
Строки считаются уникальными с учетом каждого уникального индекса таблицы. Все дублирующиеся строки пропускаются. Это поведение можно переопределить с помощью :unique_by (см. ниже).
Возвращает ActiveRecord::Result, содержимое которого зависит от :returning (см. ниже).
Параметры
- :returning
-
(Только PostgreSQL, SQLite3 и MariaDB) Массив атрибутов, которые нужно вернуть для всех успешно вставленных записей; по умолчанию возвращается первичный ключ. Передайте
returning: %w[ id name ], чтобы получить и id, и name, илиreturning: false, чтобы полностью исключить соответствующее предложение SQLRETURNING.Если требуется больше контроля над возвращаемыми значениями, можно также передать строку SQL (например,
returning: Arel.sql("id, name as new_name")). - :unique_by
-
(Только PostgreSQL и SQLite) По умолчанию строки считаются уникальными с учетом каждого уникального индекса таблицы. Все дублирующиеся строки пропускаются.
Чтобы пропускать строки только на основании одного уникального индекса, передайте
:unique_by.Рассмотрим модель Book, в которой не допускаются повторяющиеся ISBN. Если же у какой-либо строки уже есть существующий id или она не уникальна по другому уникальному индексу, будет вызвано исключение
ActiveRecord::RecordNotUnique.Уникальные индексы можно указать по столбцам или имени:
unique_by: :isbn unique_by: %i[ author_id name ] unique_by: :index_books_on_isbn
- :record_timestamps
-
По умолчанию автоматическое заполнение столбцов временных меток управляется параметром конфигурации модели
record_timestamps, что соответствует обычному поведению.Чтобы переопределить это поведение и принудительно включить или отключить автоматическое заполнение столбцов временных меток, передайте
:record_timestamps:record_timestamps: true # Always set timestamps automatically record_timestamps: false # Never set timestamps automatically
Поскольку этот метод использует сведения об индексах из базы данных, рекомендуется сочетать :unique_by с schema_cache Active Record.
Пример
# Insert records and skip inserting any duplicates.
# Here "Eloquent Ruby" is skipped because its id is not unique.
Book.insert_all([
{ id: 1, title: "Rework", author: "David" },
{ id: 1, title: "Eloquent Ruby", author: "Russ" }
])
# insert_all works on chained scopes, and you can use create_with
# to set default attributes for all inserted records.
author.books.create_with(created_at: Time.now).insert_all([
{ id: 1, title: "Rework" },
{ id: 2, title: "Eloquent Ruby" }
])
# File activerecord/lib/active_record/relation.rb, line 810 def insert_all!(attributes, returning: nil, record_timestamps: nil) InsertAll.execute(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps) end
Вставляет несколько записей в базу данных с помощью одной SQL-инструкции INSERT. Модели не создаются, обратные вызовы и проверки Active Record не запускаются. Однако переданные значения проходят приведение типов и сериализацию Active Record.
Параметр attributes — это Array хэшей. Каждый Hash задает атрибуты одной строки и должен содержать одинаковые ключи.
Если какая-либо строка нарушает уникальный индекс таблицы, вызывается исключение ActiveRecord::RecordNotUnique. В этом случае не вставляется ни одна строка.
Чтобы пропускать дублирующиеся строки, см. insert_all. Чтобы заменять их, см. upsert_all.
Возвращает ActiveRecord::Result, содержимое которого зависит от :returning (см. ниже).
Параметры
- :returning
-
(Только PostgreSQL, SQLite3 и MariaDB) Массив атрибутов, которые нужно вернуть для всех успешно вставленных записей; по умолчанию возвращается первичный ключ. Передайте
returning: %w[ id name ], чтобы получить и id, и name, илиreturning: false, чтобы полностью исключить соответствующее предложение SQLRETURNING.Если требуется больше контроля над возвращаемыми значениями, можно также передать строку SQL (например,
returning: Arel.sql("id, name as new_name")). - :record_timestamps
-
По умолчанию автоматическое заполнение столбцов временных меток управляется параметром конфигурации модели
record_timestamps, что соответствует обычному поведению.Чтобы переопределить это поведение и принудительно включить или отключить автоматическое заполнение столбцов временных меток, передайте
:record_timestamps:record_timestamps: true # Always set timestamps automatically record_timestamps: false # Never set timestamps automatically
Примеры
# Insert multiple records
Book.insert_all!([
{ title: "Rework", author: "David" },
{ title: "Eloquent Ruby", author: "Russ" }
])
# Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
# does not have a unique id.
Book.insert_all!([
{ id: 1, title: "Rework", author: "David" },
{ id: 1, title: "Eloquent Ruby", author: "Russ" }
])
# File activerecord/lib/active_record/relation.rb, line 1310
def inspect
subject = loaded? ? records : annotate("loading for inspect")
entries = subject.take([limit_value, 11].compact.min).map!(&:inspect)
entries[10] = "..." if entries.size == 11
"#<#{self.class.name} [#{entries.join(', ')}]>"
end # File activerecord/lib/active_record/relation.rb, line 1268 def joined_includes_values includes_values & joins_values end
Объединения, которые также помечены для предварительной загрузки. В этом случае следует просто загрузить их заранее. Обратите внимание, что это упрощённая реализация, поскольку могут быть строки и символы, представляющие одну и ту же ассоциацию, но этот метод их не сопоставит. Кроме того, у нас могут быть вложенные хэши, частично совпадающие, например { a: :b } & { a: [:b, :c] }
# File activerecord/lib/active_record/relation.rb, line 1199
def load(&block)
if !loaded? || scheduled?
@records = exec_queries(&block)
@loaded = true
end
self
end Загружает записи из базы данных, если они ещё не были загружены. Этот метод можно использовать, если по какой-либо причине нужно явно загрузить некоторые записи до того, как начать их использовать. Возвращаемое значение — сама связь, а не записи.
Post.where(published: true).load # => #<ActiveRecord::Relation>
# File activerecord/lib/active_record/relation.rb, line 1158
def load_async
with_connection do |c|
return load if !c.async_enabled?
unless loaded?
result = exec_main_query(async: !c.current_transaction.joinable?)
if result.is_a?(Array)
@records = result
else
@future_result = result
end
@loaded = true
end
end
self
end Запланировать выполнение запроса в пуле фоновых потоков.
Post.where(published: true).load_async # => #<ActiveRecord::Relation>
При переборе Relation, если фоновый запрос ещё не был выполнен, он будет выполнен потоком переднего плана.
Обратите внимание, что для фактического параллельного выполнения запросов необходимо настроить config.active_record.async_query_executor. В противном случае по умолчанию они выполняются на переднем плане.
Если запрос действительно был выполнен в фоновом режиме, в журналах Active Record это будет обозначено префиксом ASYNC в строке журнала:
ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100
# File activerecord/lib/active_record/relation.rb, line 423 def many? return false if @none return super if block_given? return records.many? if loaded? limited_count > 1 end
Возвращает true, если имеется более одной записи.
Enumerable#many? # File activerecord/lib/active_record/relation.rb, line 125
def new(attributes = nil, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| new(attr, &block) }
else
block = current_scope_restoring_block(&block)
scoping { _new(attributes, &block) }
end
end Инициализирует новую запись из связи, сохраняя текущую область действия.
Ожидает аргументы в том же формате, что и ActiveRecord::Base.new.
users = User.where(name: 'DHH') user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
Также можно передать блоку new новую запись в качестве аргумента:
user = users.new { |user| user.name = 'Oscar' }
user.name # => Oscar
# File activerecord/lib/active_record/relation.rb, line 388 def none?(*args) return true if @none return super if args.present? || block_given? empty? end
Возвращает true, если записей нет.
Если передан аргумент-шаблон, этот метод проверяет, соответствуют ли элементы в Enumerable шаблону с помощью оператора case-равенства (===).
posts.none?(Comment) # => true or false
# File activerecord/lib/active_record/relation.rb, line 414 def one?(*args) return false if @none return super if args.present? || block_given? return records.one? if loaded? limited_count == 1 end
Возвращает true, если имеется ровно одна запись.
Если передан аргумент-шаблон, этот метод проверяет, соответствуют ли элементы в Enumerable шаблону с помощью оператора case-равенства (===).
posts.one?(Post) # => true or false
# File activerecord/lib/active_record/relation.rb, line 1284
def pretty_print(pp)
subject = loaded? ? records : annotate("loading for pp")
entries = subject.take([limit_value, 11].compact.min)
entries[10] = "..." if entries.size == 11
pp.pp(entries)
end # File activerecord/lib/active_record/relation.rb, line 1298 def readonly? readonly_value end
# File activerecord/lib/active_record/relation.rb, line 1209 def reload reset load end
Принудительно перезагружает связь.
# File activerecord/lib/active_record/relation.rb, line 1214 def reset @future_result&.cancel @future_result = nil @delegate_to_model = false @to_sql = @arel = @loaded = @should_eager_load = nil @offsets = @take = nil @cache_keys = nil @cache_versions = nil @records = nil self end
# File activerecord/lib/active_record/relation.rb, line 1189 def scheduled? !!@future_result end
Возвращает true, если выполнение связи запланировано в пуле фоновых потоков.
# File activerecord/lib/active_record/relation.rb, line 1251
def scope_for_create
hash = where_clause.to_h(model.table_name, equality_only: true)
create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
hash
end # File activerecord/lib/active_record/relation.rb, line 551
def scoping(all_queries: nil, &block)
registry = model.scope_registry
if global_scope?(registry) && all_queries == false
raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
elsif already_in_scope?(registry)
yield
else
_scoping(self, registry, all_queries, &block)
end
end Применяет область действия текущей области ко всем запросам.
Comment.where(post_id: 1).scoping do Comment.first end # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
Если передан all_queries: true, область действия будет применяться ко всем запросам для этой связи, включая update и delete для экземпляров. После того как all_queries установлено в true, его нельзя установить в false во вложенном блоке.
Если во время выполнения блока нужно удалить все предыдущие области действия (включая default_scope), используйте unscoped.
# File activerecord/lib/active_record/relation.rb, line 363
def size
if loaded?
records.length
else
count(:all)
end
end Возвращает размер набора записей.
# File activerecord/lib/active_record/relation.rb, line 347 def to_ary records.dup end
Преобразует объекты связи в Array.
# File activerecord/lib/active_record/relation.rb, line 1230
def to_sql
@to_sql ||= if eager_loading?
apply_join_dependency do |relation, join_dependency|
relation = join_dependency.apply_column_aliases(relation)
relation.to_sql
end
else
model.with_connection do |conn|
conn.unprepared_statement { conn.to_sql(arel) }
end
end
end Возвращает SQL-оператор для связи.
User.where(name: 'Oscar').to_sql # SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
# File activerecord/lib/active_record/relation.rb, line 991 def touch_all(*names, time: nil) update_all model.touch_attributes_with_time(*names, time: time) end
Обновляет временные метки всех записей в текущей связи, устанавливая атрибуты updated_at/updated_on в текущее время или указанное время. Этот метод не создаёт экземпляры соответствующих моделей и не вызывает обратные вызовы или проверки Active Record. В этот метод можно передать имена атрибутов и необязательный аргумент времени. Если переданы имена атрибутов, они обновляются вместе с атрибутами updated_at/updated_on. Если аргумент времени не передан, по умолчанию используется текущее время.
Примеры
# Touch all records Person.all.touch_all # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'" # Touch multiple records with a custom attribute Person.all.touch_all(:created_at) # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'" # Touch multiple records with a specified time Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0)) # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'" # Touch records with scope Person.where(name: 'David').touch_all # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
# File activerecord/lib/active_record/relation.rb, line 598
def update_all(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
return 0 if @none
invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
value = @values[method]
method == :distinct ? value : value&.any?
end
if invalid_methods.any?
ActiveRecord.deprecator.warn <<~MESSAGE
`#{invalid_methods.join(', ')}` is not supported by `update_all` and was never included in the generated query.
Calling `#{invalid_methods.join(', ')}` with `update_all` will raise an error in Rails 8.2.
MESSAGE
end
if updates.is_a?(Hash)
if model.locking_enabled? &&
!updates.key?(model.locking_column) &&
!updates.key?(model.locking_column.to_sym)
attr = table[model.locking_column]
updates[attr.name] = _increment_attribute(attr)
end
values = _substitute_values(updates)
else
values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name))
end
model.with_connection do |c|
arel = eager_loading? ? apply_join_dependency.arel : arel()
arel.source.left = table
key = if model.composite_primary_key?
primary_key.map { |pk| table[pk] }
else
table[primary_key]
end
stmt = arel.compile_update(values, key)
c.update(stmt, "#{model} Update All").tap { reset }
end
end Обновляет все записи в текущей связи указанными значениями. Этот метод формирует один SQL-оператор UPDATE и напрямую отправляет его в базу данных. Он не создаёт экземпляры соответствующих моделей и не вызывает обратные вызовы или проверки Active Record. Однако значения, переданные в update_all, всё равно проходят обычное приведение типов и сериализацию Active Record. Возвращает количество затронутых строк.
Примечание: поскольку обратные вызовы Active Record не вызываются, этот метод не обновляет автоматически столбцы updated_at/updated_on.
Параметры
-
updates— строка, массив или хэш, представляющий часть SET SQL-оператора. Все переданные строки будут приведены к нужному типу, если не использоватьArel.sql. (Не передавайте пользовательские значения вArel.sql.)
Примеры
# Update all customers with the given attributes
Customer.update_all wants_email: true
# Update all books with 'Rails' in their title
Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
# Update all books that match conditions, but limit it to 5 ordered by date
Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
# Update all invoices and set the number column to its id value.
Invoice.update_all('number = id')
# Update all books with 'Rails' in their title
Book.where('title LIKE ?', '%Rails%').update_all(title: Arel.sql("title + ' - volume 1'"))
# File activerecord/lib/active_record/relation.rb, line 948
def update_counters(counters)
touch = counters.delete(:touch)
updates = {}
counters.each do |counter_name, value|
attr = table[counter_name]
updates[attr.name] = _increment_attribute(attr, value)
end
if touch
names = touch if touch != true
names = Array.wrap(names)
options = names.extract_options!
touch_updates = model.touch_attributes_with_time(*names, **options)
updates.merge!(touch_updates) unless touch_updates.empty?
end
update_all updates
end Обновляет счётчики записей в текущей связи.
Параметры
-
counter—Hash, содержащий имена обновляемых полей в качестве ключей и величину обновления в качестве значений. -
Параметр
:touch— обновлять временные метки при обновлении. -
Если переданы имена атрибутов, они обновляются вместе с атрибутами update_at/on.
Примеры
# For Posts by a given author increment the comment_count by 1. Post.where(author_id: author.id).update_counters(comment_count: 1)
# File activerecord/lib/active_record/relation.rb, line 820 def upsert(attributes, **kwargs) upsert_all([ attributes ], **kwargs) end
Обновляет или вставляет (upsert) одну запись в базу данных с помощью одного SQL-оператора INSERT. Метод не создаёт экземпляры моделей и не вызывает обратные вызовы или проверки Active Record. При этом переданные значения проходят приведение типов и сериализацию Active Record.
Документацию см. в разделе upsert_all.
# File activerecord/lib/active_record/relation.rb, line 932 def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) InsertAll.execute(self, attributes, on_duplicate: on_duplicate, update_only: update_only, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) end
Обновляет или вставляет (upsert) несколько записей в базу данных с помощью одного SQL-оператора INSERT. Метод не создаёт экземпляры моделей и не вызывает обратные вызовы или проверки Active Record. При этом переданные значения проходят приведение типов и сериализацию Active Record.
Параметр attributes — это Array хэшей. Каждый Hash задаёт атрибуты одной строки; ключи во всех хэшах должны совпадать.
Возвращает ActiveRecord::Result, содержимое которого зависит от :returning (см. ниже).
По умолчанию upsert_all при конфликте обновляет все столбцы, которые можно обновить. Это все столбцы, кроме первичных ключей, столбцов только для чтения и столбцов, указанных в необязательном параметре unique_by.
Параметры
- :returning
-
(Только PostgreSQL, SQLite3 и MariaDB) Массив атрибутов, возвращаемых для всех успешно обработанных записей; по умолчанию возвращается первичный ключ. Передайте
returning: %w[ id name ], чтобы получить и идентификатор, и имя, либоreturning: false, чтобы полностью исключить нижележащую SQL-конструкциюRETURNING.Для более точного управления возвращаемыми значениями также можно передать SQL-строку (например,
returning: Arel.sql("id, name as new_name")). - :unique_by
-
(Только PostgreSQL и SQLite) По умолчанию строки считаются уникальными по каждому уникальному индексу таблицы. Все повторяющиеся строки пропускаются.
Чтобы пропускать строки только согласно одному уникальному индексу, передайте
:unique_by.Рассмотрим модель Book, в которой дублирование ISBN не имеет смысла. Если у какой-либо строки уже существует идентификатор или она не уникальна по другому уникальному индексу, будет вызвано исключение
ActiveRecord::RecordNotUnique.Уникальные индексы можно указать по столбцам или имени:
unique_by: :isbn unique_by: %i[ author_id name ] unique_by: :index_books_on_isbn
Поскольку метод использует сведения об индексах из базы данных, рекомендуется сочетать :unique_by с schema_cache Active Record.
- :on_duplicate
-
Настраивает поведение при конфликте. Используйте ‘:skip`, чтобы игнорировать конфликты, или передайте безопасный фрагмент SQL, обёрнутый в `Arel.sql`.
ПРИМЕЧАНИЕ: при использовании этого параметра необходимо самостоятельно указать все столбцы, которые требуется обновить.
Пример:
Commodity.upsert_all( [ { id: 2, name: "Copper", price: 4.84 }, { id: 4, name: "Gold", price: 1380.87 }, { id: 6, name: "Aluminium", price: 0.35 } ], on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)") )См. связанный параметр
:update_only. Одновременно использовать оба параметра нельзя. - :update_only
-
Укажите список имён столбцов, которые будут обновлены при конфликте. Если список не задан,
upsert_allобновит все столбцы, которые можно обновить. Это все столбцы, кроме первичных ключей, столбцов только для чтения и столбцов, указанных в необязательном параметреunique_byПример:
Commodity.upsert_all( [ { id: 2, name: "Copper", price: 4.84 }, { id: 4, name: "Gold", price: 1380.87 }, { id: 6, name: "Aluminium", price: 0.35 } ], update_only: [:price] # Only prices will be updated )См. связанный параметр
:on_duplicate. Одновременно использовать оба параметра нельзя. - :record_timestamps
-
По умолчанию автоматическая установка столбцов временных меток управляется настройкой
record_timestampsмодели, что соответствует обычному поведению.Чтобы переопределить это поведение и принудительно включить или отключить автоматическую установку столбцов временных меток, передайте
:record_timestamps:record_timestamps: true # Always set timestamps automatically record_timestamps: false # Never set timestamps automatically
Примеры
# Inserts multiple records, performing an upsert when records have duplicate ISBNs.
# Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
Book.upsert_all([
{ title: "Rework", author: "David", isbn: "1" },
{ title: "Eloquent Ruby", author: "Russ", isbn: "1" }
], unique_by: :isbn)
Book.find_by(isbn: "1").title # => "Eloquent Ruby"
# File activerecord/lib/active_record/relation.rb, line 1302 def values @values.dup end
Защищённые методы экземпляров
# File activerecord/lib/active_record/relation.rb, line 1351 def load_records(records) @records = records.freeze @loaded = true end
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.