Spec-Zone.ru › Ruby on Rails 7.1

модуль ActiveRecord::Batches

Обработка записей Active Record по частям

Константы

DEFAULT_ORDER
ORDER_IGNORE_MESSAGE

Публичные методы экземпляров

find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER, &block) Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 79
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: DEFAULT_ORDER, &block)
  if block_given?
    find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
      records.each(&block)
    end
  else
    enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
      relation = self
      apply_limits(relation, start, finish, build_batch_orders(order)).size
    end
  end
end

Перебор коллекции записей из базы данных (например, с помощью метода 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 — Переопределяет конфигурацию приложения, чтобы указать, нужно ли генерировать ошибку, если в запросе указан порядок.

  • :order — Указывает порядок первичного ключа (может быть :asc или :desc или массивом из :asc или :desc). По умолчанию :asc.

    class Order < ActiveRecord::Base
      self.primary_key = [:id_1, :id_2]
    end
    
    Order.find_each(order: [:asc, :desc])
    

    В приведенном коде id_1 отсортирован по возрастанию, а id_2 по убыванию.

Ограничения учитываются, и если они заданы, размер пакета может быть меньше, равен или больше предела.

Параметры start и finish особенно полезны, если несколько рабочих процессов работают с одной очередью обработки. Можно настроить, чтобы рабочий процесс 1 обрабатывал записи с id от 1 до 9999, а рабочий процесс 2 — с id от 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

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

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

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

  • :order — Указывает порядок первичного ключа (может быть :asc или :desc или массивом из :asc или :desc). По умолчанию :asc.

    class Order < ActiveRecord::Base
      self.primary_key = [:id_1, :id_2]
    end
    
    Order.find_in_batches(order: [:asc, :desc])
    

    В приведенном коде id_1 отсортирован по возрастанию, а id_2 по убыванию.

Ограничения учитываются, и если они заданы, размер пакета может быть меньше, равен или больше предела.

Параметры start и finish особенно полезны, если несколько рабочих процессов работают с одной очередью обработки. Можно настроить, чтобы рабочий процесс 1 обрабатывал записи с id от 1 до 9999, а рабочий процесс 2 — с id от 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

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

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

in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: DEFAULT_ORDER, use_ranges: nil, &block) Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 239
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: DEFAULT_ORDER, use_ranges: nil, &block)
  unless Array(order).all? { |ord| [:asc, :desc].include?(ord) }
    raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
  end

  unless block
    return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, order: order, use_ranges: use_ranges)
  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

  if self.loaded?
    batch_on_loaded_relation(
      relation: self,
      start: start,
      finish: finish,
      order: order,
      batch_limit: batch_limit,
      &block
    )
  else
    batch_on_unloaded_relation(
      relation: self,
      start: start,
      finish: finish,
      load: load,
      order: order,
      use_ranges: use_ranges,
      remaining: remaining,
      batch_limit: batch_limit,
      &block
    )
  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 — Переопределяет конфигурацию приложения, чтобы указать, нужно ли генерировать ошибку, если в запросе указан порядок.

  • :order — Указывает порядок первичного ключа (может быть :asc или :desc или массивом из :asc или :desc). По умолчанию :asc.

    class Order < ActiveRecord::Base
      self.primary_key = [:id_1, :id_2]
    end
    
    Order.in_batches(order: [:asc, :desc])
    

    В приведенном коде id_1 отсортирован по возрастанию, а id_2 по убыванию.

  • :use_ranges — Указывает, использовать ли итерацию по диапазону (id >= x AND id <= y). Может значительно ускорить итерацию по всей или почти всей таблице. По умолчанию используется этот стиль итерации только при итерации по всей таблице. Вы можете отключить это поведение, передав false. Если вы итерируетесь по таблице и единственное условие, например, archived_at: nil (и только небольшая часть записей архивирована), имеет смысл использовать этот подход.

Ограничения учитываются, и если они заданы, размер пакета может быть меньше, равен или больше предела.

Параметры start и finish особенно полезны, если несколько рабочих процессов работают с одной очередью обработки. Можно настроить, чтобы рабочий процесс 1 обрабатывал записи с id от 1 до 9999, а рабочий процесс 2 — с id от 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!)

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

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

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

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