Spec-Zone.ru › Ruby on Rails 8.0

класс ActiveRecord::Relation

Родитель:
Объект
Включенные модули:
Enumerable

Связь Active Record

Константы

CLAUSE_METHODS
INVALID_METHODS_FOR_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 1253
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 391
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 1274
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 438
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 519
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 465
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|
    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 в такой ситуации.

create_or_find_by!(attributes, &block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 288
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!, поэтому при создании невалидной записи будет возбуждено исключение.

delete(id_or_array) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1057
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 1011
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

  model.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 model.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, "#{model} 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
delete_by(*args) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1119
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 1083
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 989
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 1106
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 1238
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 362
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 348
def encode_with(coder)
  coder.represent_seq(nil, records)
end

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

explain(*options) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 332
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.

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 в такой ситуации.

Если создание завершилось ошибкой из-за ограничения уникальности, этот метод предположит, что столкнулся с проблемой гонки, и попробует еще раз найти запись. Если каким-то образом второй поиск по-прежнему не находит запись, потому что произошло одновременное удаление, он поднимет исключение 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 302
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 644
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 733
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 723
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 для пропуска базового RETURNING SQL-запроса полностью.

Вы также можете передать строку 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" }
])
insert_all!(attributes, returning: nil, record_timestamps: nil) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 790
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 для пропуска базового RETURNING SQL-запроса полностью.

Вы также можете передать строку 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 1290
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 1248
def joined_includes_values
  includes_values & joins_values
end

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

load(&block) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1179
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 1138
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 413
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 378
def none?(*args)
  return true if @none

  return super if args.present? || block_given?
  empty?
end

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

Когда задан аргумент шаблона, этот метод проверяет, соответствуют ли элементы в Enumerable шаблону с помощью оператора точного соответствия (===).

posts.none?(Comment) # => true or false
Вызывает метод суперкласса
one?(*args) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 404
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
Вызывает метод суперкласса
pretty_print(pp) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1264
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 1278
def readonly?
  readonly_value
end
reload() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1189
def reload
  reset
  load
end

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

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

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

scope_for_create() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1231
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 541
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 вложенном блоке.

Обратитесь к методу unscoped, если необходимо удалить все предыдущие области видимости (включая default_scope) во время выполнения блока.

size() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 353
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 337
def to_ary
  records.dup
end

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

Также является псевдонимом для: to_a
to_sql() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1210
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 969
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 588
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 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 : 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 model.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, "#{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 926
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 800
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 910
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"
values() Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1282
def values
  @values.dup
end

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

load_records(records) Показать исходный код
# File activerecord/lib/active_record/relation.rb, line 1331
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