класс ActiveRecord::Relation
Отношение Active Record
Константы
- CLAUSE_METHODS
- НЕДОПУСТИМЫЕ_МЕТОДЫ_ДЛЯ_DELETE_ALL
- MULTI_VALUE_METHODS
- SINGLE_VALUE_METHODS
- VALUE_METHODS
Атрибуты
Открытые методы класса
# File activerecord/lib/active_record/relation.rb, line 27
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
end Общедоступные методы экземпляров
# File activerecord/lib/active_record/relation.rb, line 708
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 276 def any? return super if block_given? !empty? end
Возвращает true, если есть какие-либо записи.
# File activerecord/lib/active_record/relation.rb, line 724 def blank? records.blank? end
Возвращает true, если отношение пустое.
# File activerecord/lib/active_record/relation.rb, line 310
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 388
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 337
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 94
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 109
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 208
def create_or_find_by(attributes, &block)
transaction(requires_new: true) { create(attributes, &block) }
rescue ActiveRecord::RecordNotUnique
find_by!(attributes)
end Попытка создания записи с заданными атрибутами в таблице, имеющей уникальное ограничение на один или несколько столбцов. Если строка уже существует с одним или несколькими из этих уникальных ограничений, исключение, которое обычно возникает при такой вставке, перехватывается, и существующая запись с этими атрибутами находится с помощью find_by!.
Это похоже на find_or_create_by, но избегает проблемы устаревших чтений между SELECT и INSERT, так как этому методу сначала необходимо запросить таблицу, а затем попытаться вставить строку, если она не найдена.
Однако существуют несколько недостатков create_or_find_by:
-
Базовая таблица должна иметь соответствующие столбцы, определенные с уникальными ограничениями.
-
Нарушение уникального ограничения может быть вызвано только одним или по крайней мере менее чем всеми заданными атрибутами. Это означает, что последующий find_by! может не найти соответствующую запись, которая затем вызовет исключение
ActiveRecord::RecordNotFound, а не запись с заданными атрибутами. -
Хотя мы избегаем гонки условий между SELECT -> INSERT из
find_or_create_by, у нас фактически есть еще одна гонка условий между INSERT -> SELECT, которая может быть вызвана, если другой клиент выполнит DELETE между этими двумя операторами. Но для большинства приложений это значительно менее вероятно. -
Он полагается на обработку исключений для управления потоком, что может быть немного медленнее.
-
Первичный ключ может автоматически инкрементироваться при каждом создании, даже если он не удается. Это может ускорить проблему исчерпания целых чисел, если базовая таблица все еще использует первичный ключ типа int (прим.: все приложения Rails с версии 5.1+ по умолчанию используют bigint, который не подвержен этой проблеме).
Этот метод вернет запись, если все заданные атрибуты покрываются уникальными ограничениями (если не вызвана гонка условий INSERT -> DELETE -> SELECT), но если попытка создания не удалась из-за ошибок проверки, она не будет сохранена, вы получите то, что возвращает create в такой ситуации.
# File activerecord/lib/active_record/relation.rb, line 217
def create_or_find_by!(attributes, &block)
transaction(requires_new: true) { create!(attributes, &block) }
rescue ActiveRecord::RecordNotUnique
find_by!(attributes)
end Подобно create_or_find_by, но вызывает create!, поэтому при возникновении ошибки проверки созданной записи будет вызвано исключение.
# File activerecord/lib/active_record/relation.rb, line 576
def delete_all
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
if eager_loading?
relation = apply_join_dependency
return relation.delete_all
end
stmt = Arel::DeleteManager.new
stmt.from(arel.join_sources.empty? ? table : arel.source)
stmt.key = table[primary_key]
stmt.take(arel.limit)
stmt.offset(arel.offset)
stmt.order(*arel.orders)
stmt.wheres = arel.constraints
affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
reset
affected
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 626 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 554
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 613 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 693
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 264 def empty? return @records.empty? if loaded? !exists? end
Возвращает true, если записей нет.
# File activerecord/lib/active_record/relation.rb, line 254 def encode_with(coder) coder.represent_seq(nil, records) end
Сериализует объекты связи Array.
# File activerecord/lib/active_record/relation.rb, line 238
def explain
exec_explain(collecting_queries_for_explain { exec_queries })
end Выполняет EXPLAIN для запроса или запросов, инициированных этой связью, и возвращает результат в виде строки. Строка отформатирована, имитируя те, которые выводятся оболочкой базы данных.
Обратите внимание, что этот метод фактически выполняет запросы, поскольку результаты некоторых из них необходимы для последующих, когда происходит нетерпеливая загрузка.
Дополнительные сведения см. в руководстве по интерфейсу запросов Active Record.
# File activerecord/lib/active_record/relation.rb, line 167 def find_or_create_by(attributes, &block) find_by(attributes) || create(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 # different 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 в такой ситуации.
Обратите внимание, что этот метод не является атомарным, он сначала выполняет SELECT, а если результатов нет, пытается выполнить INSERT. Если есть другие потоки или процессы, существует условие гонки между двумя вызовами, и может оказаться так, что вы получите две похожие записи.
Если это может быть проблемой для вашего приложения, см. create_or_find_by.
# File activerecord/lib/active_record/relation.rb, line 174 def find_or_create_by!(attributes, &block) find_by(attributes) || create!(attributes, &block) end
Как find_or_create_by, но вызывает create!, поэтому исключение возникает, если созданная запись недействительна.
# File activerecord/lib/active_record/relation.rb, line 225 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 36 def initialize_copy(other) @values = @values.dup reset end
# File activerecord/lib/active_record/relation.rb, line 732
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 703 def joined_includes_values includes_values & joins_values end
Объединения, которые также помечены для предварительной загрузки. В этом случае мы должны просто нетерпеливо загрузить их. Обратите внимание, что это наивная реализация, потому что у нас могут быть строки и символы, которые представляют одну и ту же ассоциацию, но которые не соответствуют этому. Кроме того, у нас могут быть вложенные хеши, которые частично совпадают, например { a: :b } & { a: [:b, :c] }
# File activerecord/lib/active_record/relation.rb, line 636
def load(&block)
unless loaded?
@records = exec_queries(&block)
@loaded = true
end
self
end Заставляет записи загружаться из базы данных, если они еще не были загружены. Вы можете использовать это, если по какой-либо причине вам нужно явно загрузить некоторые записи перед их фактическим использованием. Возвращаемое значение — сама связь, а не записи.
Post.where(published: true).load # => #<ActiveRecord::Relation>
# File activerecord/lib/active_record/relation.rb, line 288 def many? return super if block_given? limit_value ? records.many? : size > 1 end
Возвращает true, если записей больше одной.
Enumerable#many? # File activerecord/lib/active_record/relation.rb, line 69
def new(attributes = nil, &block)
block = current_scope_restoring_block(&block)
scoping { _new(attributes, &block) }
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 270 def none? return super if block_given? empty? end
Возвращает true, если записей нет.
# File activerecord/lib/active_record/relation.rb, line 282 def one? return super if block_given? limit_value ? records.one? : size == 1 end
Возвращает true, если есть ровно одна запись.
# File activerecord/lib/active_record/relation.rb, line 719 def pretty_print(q) q.pp(records) end
# File activerecord/lib/active_record/relation.rb, line 646 def reload reset load end
Принудительно перезагружает связь.
# File activerecord/lib/active_record/relation.rb, line 651 def reset @delegate_to_klass = false @to_sql = @arel = @loaded = @should_eager_load = nil @offsets = @take = nil @records = [].freeze self end
# File activerecord/lib/active_record/relation.rb, line 685
def scope_for_create
hash = where_values_hash
hash.delete(klass.inheritance_column) if klass.finder_needs_type_condition?
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 405
def scoping
already_in_scope? ? yield : _scoping(self) { yield }
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
Проверьте unscoped, если вы хотите удалить все предыдущие области видимости (включая default_scope) во время выполнения блока.
# File activerecord/lib/active_record/relation.rb, line 259 def size loaded? ? @records.length : count(:all) end
Возвращает размер набора записей.
# File activerecord/lib/active_record/relation.rb, line 243 def to_ary records.dup end
Преобразует объекты отношения в Array.
# File activerecord/lib/active_record/relation.rb, line 663
def to_sql
@to_sql ||= begin
if eager_loading?
apply_join_dependency do |relation, join_dependency|
relation = join_dependency.apply_column_aliases(relation)
relation.to_sql
end
else
conn = klass.connection
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 534 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 440
def update_all(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
if eager_loading?
relation = apply_join_dependency
return relation.update_all(updates)
end
stmt = Arel::UpdateManager.new
stmt.table(arel.join_sources.empty? ? table : arel.source)
stmt.key = table[primary_key]
stmt.take(arel.limit)
stmt.offset(arel.offset)
stmt.order(*arel.orders)
stmt.wheres = arel.constraints
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
stmt.set _substitute_values(updates)
else
stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
end
@klass.connection.update stmt, "#{@klass} Update All"
end Обновляет все записи в текущем отношении с указанными данными. Этот метод создает один SQL-запрос UPDATE и отправляет его напрямую в базу данных. Не создает вовлеченные модели и не запускает обратные вызовы или проверки Active Record. Однако значения, переданные в update_all, всё ещё будут проходить обычное приведение типов и сериализацию Active Record. Возвращает количество изменённых строк.
Примечание: Поскольку обратные вызовы Active Record не вызываются, этот метод не будет автоматически обновлять столбцы updated_at/updated_on.
Параметры
-
updates- Строка, массив или хеш, представляющий часть SET 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')
# File activerecord/lib/active_record/relation.rb, line 491
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 728 def values @values.dup end
# File activerecord/lib/active_record/relation.rb, line 681 def where_values_hash(relation_table_name = klass.table_name) where_clause.to_h(relation_table_name) end
Возвращает хеш с условиями where.
User.where(name: 'Oscar').where_values_hash
# => {name: "Oscar"}
Защищённые методы экземпляров
# File activerecord/lib/active_record/relation.rb, line 775 def load_records(records) @records = records.freeze @loaded = true end
© 2004–2020 David Heinemeier Hansson
Licensed under the MIT License.