Spec-Zone.ru › Ruby on Rails 5.0

модуль ActiveRecord::Batches

Константы

ORDER_OR_LIMIT_IGNORED_MESSAGE

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

find_each(start: null, finish: null, batch_size: 1000, error_on_ignore: null) { |record| ... } Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 56
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 — Переопределяет конфигурацию приложения, чтобы указать, должно ли генерироваться исключение, когда порядок и ограничение должны быть проигнорированы из-за пакетной обработки.

Это особенно полезно, если вы хотите, чтобы несколько рабочих процессов обрабатывали одну и ту же очередь задач. Вы можете поручить рабочему процессу 1 обработку всех записей между id 0 и 10 000, а рабочему процессу 2 — от 10 000 и далее (установив параметры :start и :finish для каждого рабочего процесса).

# Let's process for a batch of 2000 records, skipping the first 2000 rows
Person.find_each(start: 2000, batch_size: 2000) do |person|
  person.party_all_night!
end

ПРИМЕЧАНИЕ: Установить порядок невозможно. Он автоматически устанавливается по возрастанию первичного ключа («id ASC»), чтобы пакетная сортировка работала. Это также означает, что этот метод работает только тогда, когда первичный ключ упорядочивается (например, целое число или строка).

ПРИМЕЧАНИЕ: Также нельзя задать предел, он используется для управления размерами пакетов.

find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil) { |to_a| ... } Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 111
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 — Переопределяет конфигурацию приложения, чтобы указать, должно ли генерироваться исключение, когда порядок и ограничение должны быть проигнорированы из-за пакетной обработки.

Это особенно полезно, если вы хотите, чтобы несколько рабочих процессов обрабатывали одну и ту же очередь задач. Вы можете поручить рабочему процессу 1 обработку всех записей между id 0 и 10 000, а рабочему процессу 2 — от 10 000 и далее (установив параметры :start и :finish для каждого рабочего процесса).

# Let's process the next 2000 records
Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
  group.each { |person| person.party_all_night! }
end

ПРИМЕЧАНИЕ: Установить порядок невозможно. Он автоматически устанавливается по возрастанию первичного ключа («id ASC»), чтобы пакетная сортировка работала. Это также означает, что этот метод работает только тогда, когда первичный ключ упорядочивается (например, целое число или строка).

ПРИМЕЧАНИЕ: Также нельзя задать предел, он используется для управления размерами пакетов.

in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil) { |yielded_relation| ... } Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 184
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? || arel.taken.present?
    act_on_order_or_limit_ignored(error_on_ignore)
  end

  relation = relation.reorder(batch_order).limit(of)
  relation = apply_limits(relation, start, finish)
  batch_relation = relation

  loop do
    if load
      records = batch_relation.records
      ids = records.map(&:id)
      yielded_relation = self.where(primary_key => ids)
      yielded_relation.load_records(records)
    else
      ids = batch_relation.pluck(primary_key)
      yielded_relation = self.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 < of
    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 — Переопределяет конфигурацию приложения, чтобы указать, должно ли генерироваться исключение, когда порядок и ограничение должны быть проигнорированы из-за пакетной обработки.

Это особенно полезно, если вы хотите работать с объектом ActiveRecord::Relation вместо массива записей или если вы хотите, чтобы несколько рабочих процессов обрабатывали одну и ту же очередь задач. Вы можете поручить рабочему процессу 1 обработку всех записей между id 0 и 10 000, а рабочему процессу 2 — от 10 000 и далее (установив параметры :start и :finish для каждого рабочего процесса).

# Let's process the next 2000 records
Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)

Пример вызова метода `query` для отношения:

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.

Spec-Zone.ru

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