Spec-Zone.ru › Ruby on Rails 6.1

модуль ActiveRecord::Batches

Константы

ORDER_IGNORE_MESSAGE

Общедоступные методы экземпляров

find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc) { |record| ... } Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 68
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
  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 { |record| yield record }
    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, 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.

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

Параметры 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

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

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

find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc) { |to_a| ... } Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 128
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
  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, 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.

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

Параметры 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

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

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

in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :asc) { |yielded_relation| ... } Показать исходный код
# File activerecord/lib/active_record/relation/batches.rb, line 204
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :asc)
  relation = self
  unless block_given?
    return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
  end

  unless [:asc, :desc].include?(order)
    raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}"
  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(order)).limit(batch_limit)
  relation = apply_limits(relation, start, finish, order)
  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

    batch_relation = relation.where(
      predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
    )
  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.

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

Параметры 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!)

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

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

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

Spec-Zone.ru

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