Spec-Zone.ru › Ruby on Rails 8.1

class ActiveRecord::Relation

Родительский класс:
Object
Подключённые модули:
Enumerable

Отношение Active Record

Константы

CLAUSE_METHODS
INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL
MULTI_VALUE_METHODS
SINGLE_VALUE_METHODS
VALUE_METHODS

Атрибуты

klass [R]
loaded [R]
loaded? [R]
model [R]
predicate_builder [R]
skip_preloading_value [RW]
table [R]

Публичные методы класса

new (model, table: nil, predicate_builder: nil, values: {}) Показать исходный код
# 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

Открытые методы экземпляра

== (other) Показать исходный код
# 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

Сравнивает два отношения на равенство.

any? (*args) Показать исходный код
# 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
Вызывает метод суперкласса
blank? () Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1294
def blank?
  records.blank?
end

Возвращает true, если отношение пустое.

build (attributes = nil, &block)
Псевдоним для: new
cache_key (timestamp_column = "updated_at") Показать исходный код
# 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)
cache_key_with_version () Показать исходный код
# 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

Возвращает ключ кэша вместе с версией.

cache_version (timestamp_column = :updated_at) Показать исходный код
# 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%')
create (attributes = nil, &block) Показать исходный код
# 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, ...>
create! (attributes = nil, &block) Показать исходный код
# 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!.

create_or_find_by (attributes, &block) Показать исходный код
# 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.

create_or_find_by! (attributes, &block) Показать исходный код
# 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!, поэтому при недопустимости созданной записи возникает исключение.

delete (id_or_array) Показать исходный код
# 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])
delete_all () Показать исходный код
# 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
delete_by (*args) Показать исходный код
# 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)
destroy (id) Показать исходный код
# 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)
destroy_all () Показать исходный код
# 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
destroy_by (*args) Показать исходный код
# 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)
eager_loading? () Показать исходный код
# 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, если для отношения требуется жадная загрузка.

empty? () Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 372
def empty?
  return true if @none

  if loaded?
    records.empty?
  else
    !exists?
  end
end

Возвращает true, если записей нет.

encode_with (coder) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 358
def encode_with(coder)
  coder.represent_seq(nil, records)
end

Сериализует объекты отношения Array.

explain (*options) Показать исходный код
# 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.

find_or_create_by (attributes, &block) Показать исходный код
# 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. Поэтому, если в таблице нет соответствующего ограничения уникальности, могут появиться две или более похожие записи.

find_or_create_by! (attributes, &block) Показать исходный код
# 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!, поэтому при недопустимости созданной записи возникает исключение.

find_or_initialize_by (attributes, &block) Показать исходный код
# 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.

initialize_copy (other) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 97
def initialize_copy(other)
  @values = @values.dup
  reset
end
insert (attributes, returning: nil, unique_by: nil, record_timestamps: nil) Показать исходный код
# 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.

insert! (attributes, returning: nil, record_timestamps: nil) Показать исходный код
# 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!.

insert_all (attributes, returning: nil, unique_by: nil, record_timestamps: nil) Показать исходный код
# 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, чтобы полностью исключить соответствующее предложение SQL RETURNING.

Если требуется больше контроля над возвращаемыми значениями, можно также передать строку 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" }
])
insert_all! (attributes, returning: nil, record_timestamps: nil) Показать исходный код
# 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, чтобы полностью исключить соответствующее предложение SQL RETURNING.

Если требуется больше контроля над возвращаемыми значениями, можно также передать строку 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" }
])
inspect () Показать исходный код
# 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
joined_includes_values () Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1268
def joined_includes_values
  includes_values & joins_values
end

Объединения, которые также помечены для предварительной загрузки. В этом случае следует просто загрузить их заранее. Обратите внимание, что это упрощённая реализация, поскольку могут быть строки и символы, представляющие одну и ту же ассоциацию, но этот метод их не сопоставит. Кроме того, у нас могут быть вложенные хэши, частично совпадающие, например { a: :b } & { a: [:b, :c] }

load (&block) Показать исходный код
# 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>
load_async () Показать исходный код
# 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
many? () Показать исходный код
# 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?
new (attributes = nil, &block) Показать исходный код
# 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
Также имеет псевдоним: build
none? (*args) Показать исходный код
# 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
Вызывает метод суперкласса
one? (*args) Показать исходный код
# 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
Вызывает метод суперкласса
pretty_print (pp) Показать исходный код
# 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
readonly? () Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1298
def readonly?
  readonly_value
end
reload () Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1209
def reload
  reset
  load
end

Принудительно перезагружает связь.

reset () Показать исходный код
# 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
scheduled? () Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1189
def scheduled?
  !!@future_result
end

Возвращает true, если выполнение связи запланировано в пуле фоновых потоков.

scope_for_create () Показать исходный код
# 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
scoping (all_queries: nil) { || ... } Показать исходный код
# 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.

size () Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 363
def size
  if loaded?
    records.length
  else
    count(:all)
  end
end

Возвращает размер набора записей.

to_a ()
Псевдоним для: to_ary
to_ary () Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 347
def to_ary
  records.dup
end

Преобразует объекты связи в Array.

Также имеет псевдоним: to_a
to_sql () Показать исходный код
# 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'
touch_all (*names, time: nil) Показать исходный код
# 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'"
update_all (updates) Показать исходный код
# 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'"))
update_counters (counters) Показать исходный код
# 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)
upsert (attributes, **kwargs) Показать исходный код
# 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.

upsert_all (attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) Показать исходный код
# 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"
values () Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1302
def values
  @values.dup
end

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

load_records (records) Показать исходный код
# 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.

Spec-Zone.ru

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