модуль ActiveRecord::Batches
Константы
- ORDER_IGNORE_MESSAGE
Общедоступные методы экземпляров
# File activerecord/lib/active_record/relation/batches.rb, line 60
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
if block_given?
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
records.each { |record| yield record }
end
else
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
relation = self
apply_limits(relation, start, finish).size
end
end
end Итерация по набору записей из базы данных (например, с помощью метода ActiveRecord::Scoping::Named::ClassMethods#all) очень неэффективна, так как она пытается создать все объекты сразу.
В этом случае методы пакетной обработки позволяют работать с записями по частям, значительно уменьшая потребление памяти.
Метод find_each использует find_in_batches с размером пакета 1000 (или как указано в параметре :batch_size).
Person.find_each do |person|
person.do_awesome_stuff
end
Person.where("age > 21").find_each do |person|
person.party_all_night!
end
Если вы не предоставите блок методу find_each, он вернёт итератор для цепочки с другими методами:
Person.find_each.with_index do |person, index| person.award_trophy(index + 1) end
Параметры
-
:batch_size— Указывает размер пакета. По умолчанию 1000. -
:start— Указывает значение первичного ключа, с которого следует начать, включая его. -
:finish— Указывает значение первичного ключа, до которого следует дойти, включая его. -
:error_on_ignore— Переопределяет конфигурацию приложения, чтобы указать, должно ли генерироваться исключение, если в отношении присутствует порядок.
Ограничения учитываются, и при их наличии размер пакета не является обязательным: он может быть меньше, равен или больше ограничения.
Параметры start и finish особенно полезны, если вы хотите, чтобы несколько задач работали с одной и той же очередью обработки. Вы можете поручить задаче 1 обработку всех записей с идентификаторами от 1 до 9999, а задаче 2 — с идентификаторами от 10000 и далее, установив параметр :start и :finish для каждой задачи.
# Let's process from record 10_000 on. Person.find_each(start: 10_000) do |person| person.party_all_night! end
ПРИМЕЧАНИЕ: Установить порядок невозможно. Он автоматически устанавливается в возрастающем порядке по первичному ключу («id ASC»), чтобы работа с пакетами работала корректно. Это также означает, что этот метод работает только в том случае, если первичный ключ упорядочен (например, целое число или строка).
ПРИМЕЧАНИЕ: По своей природе пакетная обработка подвержена гонкам, если другие процессы изменяют базу данных.
# File activerecord/lib/active_record/relation/batches.rb, line 119
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
relation = self
unless block_given?
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
total = apply_limits(relation, start, finish).size
(total - 1).div(batch_size) + 1
end
end
in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
yield batch.to_a
end
end Возвращает каждый пакет записей, найденных в соответствии с параметрами поиска, в виде массива.
Person.where("age > 21").find_in_batches do |group|
sleep(50) # Make sure it doesn't get too crowded in there!
group.each { |person| person.party_all_night! }
end
Если вы не предоставите блок методу find_in_batches, он вернёт итератор для цепочки с другими методами:
Person.find_in_batches.with_index do |group, batch|
puts "Processing group ##{batch}"
group.each(&:recover_from_last_night!)
end
Для получения каждой записи по отдельности используйте find_each.
Параметры
-
:batch_size— Указывает размер пакета. По умолчанию 1000. -
:start— Указывает значение первичного ключа, с которого следует начать, включая его. -
:finish— Указывает значение первичного ключа, до которого следует дойти, включая его. -
:error_on_ignore— Переопределяет конфигурацию приложения, чтобы указать, должно ли генерироваться исключение, если в отношении присутствует порядок.
Ограничения учитываются, и при их наличии размер пакета не является обязательным: он может быть меньше, равен или больше ограничения.
Параметры start и finish особенно полезны, если вы хотите, чтобы несколько задач работали с одной и той же очередью обработки. Вы можете поручить задаче 1 обработку всех записей с идентификаторами от 1 до 9999, а задаче 2 — с идентификаторами от 10000 и далее, установив параметр :start и :finish для каждой задачи.
# Let's process from record 10_000 on.
Person.find_in_batches(start: 10_000) do |group|
group.each { |person| person.party_all_night! }
end
ПРИМЕЧАНИЕ: Установить порядок невозможно. Он автоматически устанавливается в возрастающем порядке по первичному ключу («id ASC»), чтобы работа с пакетами работала корректно. Это также означает, что этот метод работает только в том случае, если первичный ключ упорядочен (например, целое число или строка).
ПРИМЕЧАНИЕ: По своей природе пакетная обработка подвержена гонкам, если другие процессы изменяют базу данных.
# File activerecord/lib/active_record/relation/batches.rb, line 194
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
relation = self
unless block_given?
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
end
if arel.orders.present?
act_on_ignored_order(error_on_ignore)
end
batch_limit = of
if limit_value
remaining = limit_value
batch_limit = remaining if remaining < batch_limit
end
relation = relation.reorder(batch_order).limit(batch_limit)
relation = apply_limits(relation, start, finish)
batch_relation = relation
loop do
if load
records = batch_relation.records
ids = records.map(&:id)
yielded_relation = where(primary_key => ids)
yielded_relation.load_records(records)
else
ids = batch_relation.pluck(primary_key)
yielded_relation = where(primary_key => ids)
end
break if ids.empty?
primary_key_offset = ids.last
raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
yield yielded_relation
break if ids.length < batch_limit
if limit_value
remaining -= ids.length
if remaining == 0
# Saves a useless iteration when the limit is a multiple of the
# batch size.
break
elsif remaining < batch_limit
relation = relation.limit(remaining)
end
end
batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
end
end Возвращает объекты ActiveRecord::Relation для работы с пакетом записей.
Person.where("age > 21").in_batches do |relation|
relation.delete_all
sleep(10) # Throttle the delete queries
end
Если вы не предоставите блок методу in_batches, он вернёт BatchEnumerator, который является перечислимым.
Person.in_batches.each_with_index do |relation, batch_index|
puts "Processing relation ##{batch_index}"
relation.delete_all
end
Примеры вызова методов на возвращенном объекте BatchEnumerator:
Person.in_batches.delete_all Person.in_batches.update_all(awesome: true) Person.in_batches.each_record(&:party_all_night!)
Параметры
-
:of— Указывает размер пакета. По умолчанию 1000. -
:load— Указывает, должна ли быть загружена запись. По умолчанию false. -
:start— Указывает значение первичного ключа, с которого следует начать, включая его. -
:finish— Указывает значение первичного ключа, до которого следует дойти, включая его. -
:error_on_ignore— Переопределяет конфигурацию приложения, чтобы указать, должно ли генерироваться исключение, если в отношении присутствует порядок.
Ограничения учитываются, и при их наличии размер пакета не является обязательным: он может быть меньше, равен или больше ограничения.
Параметры start и finish особенно полезны, если вы хотите, чтобы несколько задач работали с одной и той же очередью обработки. Вы можете поручить задаче 1 обработку всех записей с идентификаторами от 1 до 9999, а задаче 2 — с идентификаторами от 10000 и далее, установив параметр :start и :finish для каждой задачи.
# Let's process from record 10_000 on. Person.in_batches(start: 10_000).update_all(awesome: true)
Пример вызова метода запроса к отношению:
Person.in_batches.each do |relation|
relation.update_all('age = age + 1')
relation.where('age > 21').update_all(should_party: true)
relation.where('age <= 21').delete_all
end
ПРИМЕЧАНИЕ: Если вы собираетесь перебирать каждую запись, вы должны вызвать метод each_record на полученном BatchEnumerator:
Person.in_batches.each_record(&:party_all_night!)
ПРИМЕЧАНИЕ: Установить порядок невозможно. Он автоматически устанавливается в возрастающем порядке по первичному ключу («id ASC»), чтобы работа с пакетами была согласованной. Поэтому первичный ключ должен быть упорядочен, например, целое число или строка.
ПРИМЕЧАНИЕ: По своей природе пакетная обработка подвержена гонкам, если другие процессы изменяют базу данных.
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.