class ActiveRecord::Relation
Active Record Отношение
Константы
- JoinOperation
- MULTI_VALUE_METHODS
- SINGLE_VALUE_METHODS
- VALUE_METHODS
Атрибуты
Общедоступные методы класса
# File activerecord/lib/active_record/relation.rb, line 22
def initialize(klass, table, values = {})
@klass = klass
@table = table
@values = values
@offsets = {}
@loaded = false
end Методы общего использования экземпляров
Сравнивает два отношения на предмет равенства.
# File activerecord/lib/active_record/relation.rb, line 576
def ==(other)
case other
when Associations::CollectionProxy, AssociationRelation
self == other.to_a
when Relation
other.to_sql == to_sql
when Array
to_a == other
end
end Возвращает true, если существуют какие-либо записи.
# File activerecord/lib/active_record/relation.rb, line 263
def any?
if block_given?
to_a.any? { |*block_args| yield(*block_args) }
else
!empty?
end
end Возвращает true, если отношение пустое.
# File activerecord/lib/active_record/relation.rb, line 592 def blank? to_a.blank? end
Пытается создать новую запись с теми же атрибутами, которые определены в отношении. Возвращает инициализированный объект, если проверка не пройдена.
Ожидаются аргументы в том же формате, что и 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 137
def create(*args, &block)
scoping { @klass.create(*args, &block) }
end Аналогично create, но вызывает create! в базовом классе. Вызывает исключение, если произошла ошибка валидации.
Ожидаются аргументы в том же формате, что и Base.create!.
# File activerecord/lib/active_record/relation.rb, line 145
def create!(*args, &block)
scoping { @klass.create!(*args, &block) }
end Удаляет строку с первичным ключом, соответствующим аргументу id, используя SQL DELETE оператор и возвращает количество удалённых строк. Объекты Active Record не создаются, поэтому обратные вызовы объекта не выполняются, включая любые :dependent опции ассоциации.
Вы можете удалить несколько строк одновременно, передав массив Array id.
Примечание: Хотя это часто намного быстрее, чем альтернатива, #destroy, пропуск обратных вызовов может обойти бизнес-логику в вашем приложении, которая гарантирует целостность ссылок или выполняет другие важные задачи.
Примеры
# Delete a single row Todo.delete(1) # Delete multiple rows Todo.delete([2,3,4])
# File activerecord/lib/active_record/relation.rb, line 481 def delete(id_or_array) where(primary_key => id_or_array).delete_all end
Удаляет записи, соответствующие conditions без предварительного создания записей и, следовательно, не вызывая метод destroy и не вызывая обратные вызовы. Это одно SQL-оператор DELETE, который напрямую отправляется в базу данных, значительно эффективнее, чем destroy_all. Будьте осторожны с отношениями, в частности, :dependent правила, определённые для ассоциаций, не соблюдаются. Возвращает количество затронутых строк.
Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
Оба вызова удаляют все затронутые записи одновременно с помощью одного оператора DELETE. Если вам нужно уничтожить зависимые ассоциации или вызвать свои before_* или after_destroy обратные вызовы, используйте метод destroy_all вместо этого.
Если предоставлен ограничитель, delete_all генерирует ошибку ActiveRecord:
Post.limit(100).delete_all # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit scope
# File activerecord/lib/active_record/relation.rb, line 440
def delete_all(conditions = nil)
raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
if conditions
where(conditions).delete_all
else
stmt = Arel::DeleteManager.new(arel.engine)
stmt.from(table)
if joins_values.any?
@klass.connection.join_to_delete(stmt, arel, table[primary_key])
else
stmt.wheres = arel.constraints
end
affected = @klass.connection.delete(stmt, 'SQL', bind_values)
reset
affected
end
end Уничтожает объект (или несколько объектов), имеющий(ие) указанный(ые) идентификатор(ы). Объект сначала создаётся, поэтому все обратные вызовы и фильтры срабатывают до удаления объекта. Этот метод менее эффективен, чем ActiveRecord#delete, но позволяет выполнять методы очистки и другие действия.
По существу, этот метод находит объект (или несколько объектов) с заданным идентификатором, создаёт новый объект на основе атрибутов и затем вызывает уничтожение на нём.
Параметры
Примеры
# Destroy a single object Todo.destroy(1) # Destroy multiple objects todos = [1,2,3] Todo.destroy(todos)
# File activerecord/lib/active_record/relation.rb, line 413
def destroy(id)
if id.is_a?(Array)
id.map { |one_id| destroy(one_id) }
else
find(id).destroy
end
end Уничтожает записи, соответствующие conditions, создавая каждый экземпляр записи и вызывая его метод destroy. Обратные вызовы каждого объекта выполняются (включая :dependent параметры ассоциаций). Возвращает коллекцию объектов, которые были уничтожены; каждый будет заморожен, чтобы отразить, что к нему не должно быть никаких изменений (поскольку они не могут быть сохранены).
Примечание: Создание экземпляров, выполнение обратных вызовов и удаление каждой записи может быть длительным, когда вы удаляете сразу много записей. Он генерирует по крайней мере один SQL DELETE запрос на запись (или возможно больше, для обеспечения ваших обратных вызовов). Если вы хотите быстро удалить много строк, не заботясь о их ассоциациях или обратных вызовах, используйте delete_all вместо этого.
Параметры
-
conditions- Строка, массив или хеш, определяющие, какие записи уничтожить. Если опущен, все записи уничтожаются. Дополнительная информация по условиям содержится в разделе Условия во введении к ActiveRecord::Base.
Примеры
Person.destroy_all("last_login < '2004-04-04'")
Person.destroy_all(status: "inactive")
Person.where(age: 0..18).destroy_all
# File activerecord/lib/active_record/relation.rb, line 386
def destroy_all(conditions = nil)
if conditions
where(conditions).destroy_all
else
to_a.each {|object| object.destroy }.tap { reset }
end
end Возвращает true, если для отношения требуется жадное загрузка.
# File activerecord/lib/active_record/relation.rb, line 555
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 251
def empty?
return @records.empty? if loaded?
if limit_value == 0
true
else
c = count(:all)
c.respond_to?(:zero?) ? c.zero? : c.empty?
end
end Выполняет EXPLAIN для запроса или запросов, инициированных этим отношением, и возвращает результат в виде строки. Строка отформатирована, имитируя строки, выводимые оболочкой базы данных.
Обратите внимание, что этот метод фактически выполняет запросы, поскольку результаты некоторых требуются следующим запросам при жадной загрузке.
Дополнительную информацию см. в руководстве Active Record Query Interface.
# File activerecord/lib/active_record/relation.rb, line 231
def explain
exec_explain(collecting_queries_for_explain { exec_queries })
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. Если есть другие потоки или процессы, возникает гонка между этими вызовами, и может произойти ситуация, когда у вас появятся две похожие записи.
Является ли это проблемой или нет, зависит от логики приложения, но в том конкретном случае, когда строки имеют ограничение UNIQUE, может быть вызвано исключение, просто повторите попытку:
begin CreditAccount.find_or_create_by(user_id: user.id) rescue ActiveRecord::RecordNotUnique retry end
# File activerecord/lib/active_record/relation.rb, line 207 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 213 def find_or_create_by!(attributes, &block) find_by(attributes) || create!(attributes, &block) end
Как find_or_create_by, но вызывает new вместо create.
# File activerecord/lib/active_record/relation.rb, line 218 def find_or_initialize_by(attributes, &block) find_by(attributes) || new(attributes, &block) end
# File activerecord/lib/active_record/relation.rb, line 30 def initialize_copy(other) # This method is a hot spot, so for now, use Hash[] to dup the hash. # https://bugs.ruby-lang.org/issues/7166 @values = Hash[@values] @values[:bind] = @values[:bind].dup if @values.key? :bind reset end
# File activerecord/lib/active_record/relation.rb, line 600
def inspect
entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
entries[10] = '...' if entries.size == 11
"#<#{self.class.name} [#{entries.join(', ')}]>"
end Объединяет включения, которые также помечены для предварительной загрузки. В этом случае мы должны просто загрузить их с нетерпением. Обратите внимание, что это примитивная реализация, потому что у нас могут быть строки и символы, которые представляют одно и то же отношение, но которые не совпадают с этим. Кроме того, у нас могут быть вложенные хеши, которые частично совпадают, например { a: :b } & { a: [:b, :c] }
# File activerecord/lib/active_record/relation.rb, line 565 def joined_includes_values includes_values & joins_values end
Заставляет загрузить записи из базы данных, если они еще не загружены. Вы можете использовать это, если по какой-то причине вам нужно явно загрузить некоторые записи перед фактическим использованием. Возвращаемое значение — это само отношение, а не записи.
Post.where(published: true).load # => #<ActiveRecord::Relation>
# File activerecord/lib/active_record/relation.rb, line 491 def load exec_queries unless loaded? self end
Возвращает true, если записей более одной.
# File activerecord/lib/active_record/relation.rb, line 272
def many?
if block_given?
to_a.many? { |*block_args| yield(*block_args) }
else
limit_value ? to_a.many? : size > 1
end
end Инициализирует новую запись из отношения, сохраняя текущий контекст.
Ожидает аргументов в том же формате, что и 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 114
def new(*args, &block)
scoping { @klass.new(*args, &block) }
end # File activerecord/lib/active_record/relation.rb, line 587 def pretty_print(q) q.pp(self.to_a) end
Принудительно перезагружает отношение.
# File activerecord/lib/active_record/relation.rb, line 498 def reload reset load end
# File activerecord/lib/active_record/relation.rb, line 503
def reset
@last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
@should_eager_load = @join_dependency = nil
@records = []
@offsets = {}
self
end # File activerecord/lib/active_record/relation.rb, line 550 def scope_for_create @scope_for_create ||= where_values_hash.merge(create_with_value) 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 289 def scoping previous, klass.current_scope = klass.current_scope, self yield ensure klass.current_scope = previous end
Возвращает размер записей.
# File activerecord/lib/active_record/relation.rb, line 246 def size loaded? ? @records.length : count(:all) end
Преобразует объекты отношения в Массив.
# File activerecord/lib/active_record/relation.rb, line 236 def to_a load @records end
Возвращает SQL-запрос для отношения.
User.where(name: 'Oscar').to_sql # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
# File activerecord/lib/active_record/relation.rb, line 515
def to_sql
@to_sql ||= begin
relation = self
connection = klass.connection
visitor = connection.visitor
if eager_loading?
find_with_associations { |rel| relation = rel }
end
ast = relation.arel.ast
binds = relation.bind_values.dup
visitor.accept(ast) do
connection.quote(*binds.shift.reverse)
end
end
end uniq и uniq! молча устарели. uniq_value делегирует distinct_value для сохранения обратной совместимости. Используйте distinct_value вместо этого.
# File activerecord/lib/active_record/relation.rb, line 571 def uniq_value distinct_value end
Обновляет объект (или несколько объектов) и сохраняет его в базе данных, если проверки пройдены. Результирующий объект возвращается, независимо от того, был ли объект успешно сохранен в базе данных или нет.
Параметры
-
id- Это должен быть id или массив id для обновления. -
attributes- Это должен быть хеш атрибутов или массив хешей.
Примеры
# Updates one record
Person.update(15, user_name: 'Samuel', group: 'expert')
# Updates multiple records
people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)
# File activerecord/lib/active_record/relation.rb, line 351
def update(id, attributes)
if id.is_a?(Array)
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
else
object = find(id)
object.update(attributes)
object
end
end Обновляет все записи с подробными данными, если они соответствуют набору условий, также могут быть заданы лимиты и порядок. Этот метод создает одно SQL-утверждение UPDATE и отправляет его непосредственно в базу данных. Он не создает экземпляры вовлеченных моделей и не вызывает обратные вызовы или проверки Active Record.
Параметры
-
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')
# File activerecord/lib/active_record/relation.rb, line 315
def update_all(updates)
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
stmt = Arel::UpdateManager.new(arel.engine)
stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
stmt.table(table)
stmt.key = table[primary_key]
if joins_values.any?
@klass.connection.join_to_update(stmt, arel)
else
stmt.take(arel.limit)
stmt.order(*arel.orders)
stmt.wheres = arel.constraints
end
@klass.connection.update stmt, 'SQL', bind_values
end # File activerecord/lib/active_record/relation.rb, line 596 def values Hash[@values] end
Возвращает хеш условий where.
User.where(name: 'Oscar').where_values_hash
# => {name: "Oscar"}
# File activerecord/lib/active_record/relation.rb, line 537
def where_values_hash(relation_table_name = table_name)
equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
node.left.relation.name == relation_table_name
}
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
Hash[equalities.map { |where|
name = where.left.name
[name, binds.fetch(name.to_s) { where.right }]
}]
end
© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.