класс ActiveRecord::Relation
Отображение Active Record
Константы
- CLAUSE_METHODS
- INVALID_METHODS_FOR_DELETE_ALL
- MULTI_VALUE_METHODS
- SINGLE_VALUE_METHODS
- VALUE_METHODS
Атрибуты
Публичные методы класса
# File activerecord/lib/active_record/relation.rb, line 77
def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
@klass = klass
@table = table
@values = values
@loaded = false
@predicate_builder = predicate_builder
@delegate_to_klass = false
@future_result = nil
@records = nil
@async = false
@none = false
end Общедоступные методы экземпляров
# File activerecord/lib/active_record/relation.rb, line 1239
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 384 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 1260 def blank? records.blank? end
Возвращает true, если отношение пустое.
# File activerecord/lib/active_record/relation.rb, line 431
def cache_key(timestamp_column = "updated_at")
@cache_keys ||= {}
@cache_keys[timestamp_column] ||= klass.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 512
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 458
def cache_version(timestamp_column = :updated_at)
if 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 147
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 162
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 266
def create_or_find_by(attributes, &block)
with_connection do |connection|
transaction(requires_new: true) { create(attributes, &block) }
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+ по умолчанию используют bigint, что не подвержено этой проблеме).
-
Столбцы с уникальными ограничениями базы данных не должны иметь определённых валидаций уникальности, в противном случае
createзавершится неудачей из-за ошибок валидации, и find_by никогда не будет вызвано.
Этот метод вернёт запись, если все заданные атрибуты охватываются уникальными ограничениями (если не вызвана гонка INSERT -> DELETE -> SELECT), но если попытка создания завершилась неудачей из-за ошибок валидации, она не сохранится; вы получите то, что возвращает create в такой ситуации.
# File activerecord/lib/active_record/relation.rb, line 281
def create_or_find_by!(attributes, &block)
with_connection do |connection|
transaction(requires_new: true) { create!(attributes, &block) }
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 1050 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 1004
def delete_all
return 0 if @none
invalid_methods = INVALID_METHODS_FOR_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
klass.with_connection do |c|
arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
arel.source.left = table
group_values_arel_columns = arel_columns(group_values.uniq)
having_clause_ast = having_clause.ast unless having_clause.empty?
key = if klass.composite_primary_key?
primary_key.map { |pk| table[pk] }
else
table[primary_key]
end
stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
c.delete(stmt, "#{klass} Delete All").tap { reset }
end
end Удаляет записи, не создавая их экземпляров, и, следовательно, не вызывая метод #destroy и не вызывая обратные вызовы. Это одно SQL-заявление DELETE, которое отправляется напрямую в базу данных, гораздо эффективнее, чем destroy_all. Однако будьте осторожны с отношениями, в частности :dependent правила, определенные для ассоциаций, не соблюдаются. Возвращает количество затронутых строк.
Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
Оба вызова удаляют затронутые записи сразу одним оператором 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 1112 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 1076
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 982
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 1099 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 1224
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 355
def empty?
return true if @none
if loaded?
records.empty?
else
!exists?
end
end Возвращает true, если записей нет.
# File activerecord/lib/active_record/relation.rb, line 341 def encode_with(coder) coder.represent_seq(nil, records) end
Сериализует объекты отношения Array.
# File activerecord/lib/active_record/relation.rb, line 325 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 Active Record Query Interface guide.
# File activerecord/lib/active_record/relation.rb, line 224 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 в такой ситуации.
Если создание завершилось ошибкой из-за ограничения уникальности, этот метод предположит, что столкнулся с проблемой гонки, и попробует еще раз найти запись. Если каким-то образом второй поиск по-прежнему не находит запись, потому что произошло одновременное удаление, он поднимет исключение ActiveRecord::RecordNotFound.
Обратите внимание, что этот метод не атомен; он сначала выполняет SELECT, и если результатов нет, пытается выполнить INSERT. Поэтому, если в таблице нет соответствующего ограничения уникальности, может случиться, что у вас окажется две или более похожих записи.
# 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_or_create_by, но вызывает create!, поэтому, если созданная запись недопустима, генерируется исключение.
# File activerecord/lib/active_record/relation.rb, line 295 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 90 def initialize_copy(other) @values = @values.dup reset end
# File activerecord/lib/active_record/relation.rb, line 637 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 726 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 716 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для пропуска подлежащегоRETURNINGSQL-запроса.Вы также можете передать строку 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 в паре с кэшем схемы 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 783 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для пропуска подлежащегоRETURNINGSQL-запроса.Вы также можете передать строку 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 1272
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 1234 def joined_includes_values includes_values & joins_values end
Объединения, которые также помечены для предварительной загрузки. В этом случае мы должны просто загрузить их с жадностью. Обратите внимание, что это простое реализация, так как у нас могут быть строки и символы, которые представляют одну и ту же ассоциацию, но не соответствуют этому. Также у нас могут быть вложенные хеши, которые частично совпадают, например, { a: :b } & { a: [:b, :c] }
# File activerecord/lib/active_record/relation.rb, line 1165
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 1134
def load_async
with_connection do |c|
return load if !c.async_enabled?
unless loaded?
result = exec_main_query(async: c.current_transaction.closed?)
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 должен быть настроен для выполнения запросов асинхронно. В противном случае они выполняются в основном потоке.
load_async также вернётся к выполнению в основном потоке в тестовой среде, когда включены транзакционные фикстуры.
Если запрос был действительно выполнен в фоновом режиме, журналы 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 406 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 118
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 371 def none?(*args) return true if @none return super if args.present? || block_given? empty? end
Возвращает true, если записей нет.
Если задан аргумент шаблона, этот метод проверяет, соответствуют ли элементы в Enumerable шаблону с помощью оператора точного совпадения (===).
posts.none?(Comment) # => true or false
# File activerecord/lib/active_record/relation.rb, line 397 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 шаблону с помощью оператора равенства (===).
posts.one?(Post) # => true or false
# File activerecord/lib/active_record/relation.rb, line 1250
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 1175 def reload reset load end
Вынуждает перезагрузку отношения.
# File activerecord/lib/active_record/relation.rb, line 1180 def reset @future_result&.cancel @future_result = nil @delegate_to_klass = 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 1155 def scheduled? !!@future_result end
Возвращает true, если отношение было запланировано в пуле фоновых потоков.
# File activerecord/lib/active_record/relation.rb, line 1217
def scope_for_create
hash = where_clause.to_h(klass.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 534
def scoping(all_queries: nil, &block)
registry = klass.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 Ограничивает все запросы текущим scope.
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 вложенном блоке.
Проверьте unscoped, если хотите удалить все предыдущие scope (включая default_scope) во время выполнения блока.
# File activerecord/lib/active_record/relation.rb, line 346
def size
if loaded?
records.length
else
count(:all)
end
end Возвращает размер набора записей.
# File activerecord/lib/active_record/relation.rb, line 330 def to_ary records.dup end
Преобразует объекты отношения в Array.
# File activerecord/lib/active_record/relation.rb, line 1196
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
klass.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 962 def touch_all(*names, time: nil) update_all klass.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 581
def update_all(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
return 0 if @none
if updates.is_a?(Hash)
if klass.locking_enabled? &&
!updates.key?(klass.locking_column) &&
!updates.key?(klass.locking_column.to_sym)
attr = table[klass.locking_column]
updates[attr.name] = _increment_attribute(attr)
end
values = _substitute_values(updates)
else
values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
end
klass.with_connection do |c|
arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
arel.source.left = table
group_values_arel_columns = arel_columns(group_values.uniq)
having_clause_ast = having_clause.ast unless having_clause.empty?
key = if klass.composite_primary_key?
primary_key.map { |pk| table[pk] }
else
table[primary_key]
end
stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
c.update(stmt, "#{klass} 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 919
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 = klass.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 793 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 903 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 ]для 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 вместе с кешем схемы Active Record.
- :on_duplicate
-
Настройте предложение 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 1264 def values @values.dup end
Защищенные методы экземпляра
# File activerecord/lib/active_record/relation.rb, line 1313 def load_records(records) @records = records.freeze @loaded = true end
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.