Spec-Zone.ru › Ruby on Rails 5.2

модуль ActiveRecord::Batches

Константы

ORDER_IGNORE_MESSAGE

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

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

# In worker 1, let's process until 9999 records.
Person.find_each(finish: 9_999) do |person|
  person.party_all_night!
end

# In worker 2, let's process from record 10_000 and onwards.
Person.find_each(start: 10_000) 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 126
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 обрабатывал все записи с id от 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»), чтобы обеспечить порядок пакетов. Это также означает, что этот метод работает только при наличии упорядочиваемого первичного ключа (например, целого числа или строки).

ПРИМЕЧАНИЕ: По своей природе пакетная обработка подвержена проблемам гонки, если другие процессы изменяют базу данных.

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 201
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)
  relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
  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

    attr = Relation::QueryAttribute.new(primary_key, primary_key_offset, klass.type_for_attribute(primary_key))
    batch_relation = relation.where(arel_attribute(primary_key).gt(Arel::Nodes::BindParam.new(attr)))
  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 обрабатывал все записи с id от 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.

Spec-Zone.ru

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