Лучший способ получить количество запросов после обновления элементов - PullRequest
0 голосов
/ 07 января 2020

Я ищу лучший способ получить размер набора запросов в рельсах. Тем не менее, элементы обновляются в al oop, и мне нужно количество элементов перед обновлением. Вот пример кода (BUGGY!).

p = participations.where(invited_at: nil).limit(50)
p.each do |participation|
   # Invite may raise an exception, but also contains operations that
   # cannot be undone
   participation.invite()
   participation.invited_at = Time.zone.now
   participation.save
end

DoStuff() if p.count > 0

Этот код не работает, потому что вызов на p.count создает новый запрос к базе данных, который не учитывает записи, которые были обновлены в l oop. Поэтому, если имеется менее 50 записей, все они обновляются и DoStuff() не вызывается.

Какой самый идиоматический c способ в рельсах для обработки этого:

  1. Переместите if p.count часть из l oop и введите l oop, только если есть какие-либо записи?
  2. Замените p.count на p.size (если я понимаю size правильно, это не должно вызывать каких-либо дополнительных запросов)
  3. Подсчитайте количество итераций в l oop и затем используйте это число

У меня такое ощущение, что 1 больше всего идиомати c в ruby, но я не очень разбираюсь в этом языке.

РЕДАКТИРОВАТЬ : улучшенный пример, немного ближе к исходному коду.

РЕДАКТИРОВАТЬ :

Проблема не в запросах на обновление, выполняемых участниками l oop. Эти запросы должны быть отдельными запросами, чтобы отслеживать, какие участия уже были обработаны, даже если возникла ошибка. Скорее проблема в том, что DoStuff() следует вызывать всякий раз, когда в l oop были обработаны какие-либо записи. Однако, поскольку count выполняет новый запрос ПОСЛЕ , записи были обработаны, если будет обработано менее 50 элементов, все будут обновлены и DoStuff() не будет вызван.

Ответы [ 2 ]

1 голос
/ 07 января 2020

Это разница между count, который всегда выполняет запрос, и size, который вернет количество загруженных объектов, если они загружены, или вернется к count в противном случае. Так что самым простым решением будет заменить count на size.

Но тогда each возвращает коллекцию, по которой он повторяется, так что вы можете сделать DoStfuff if p.each(&block).any? (не выглядит красиво, если вы иметь многострочный блок)

Более понятный способ, без того, чтобы рецензент узнал разницу между size и count, и без проверки, привел ли каждый из них к коллекции хотя бы с одним элементом, - иметь код, инкапсулированный в метод, и добавление защитного предложения.

def process_invitations
  p = participations.where(invited_at: nil).limit(50)
  return if p.none?

  p.each do |participation|
   # Invite may raise an exception, but also contains operations that
   # cannot be undone
   participation.invite()
   participation.invited_at = Time.zone.now
   participation.save
  end

  DoStuff()
end

Вы можете даже снять ограничение и использовать p.first(50).each do

0 голосов
/ 07 января 2020

Переместите часть if p.count из l oop и введите l oop только при наличии записей?

.each не вводит l oop если нет записей. Если вы хотите доказательства, попробуйте:

MyModel.none.each { puts "Hello world" }

Если вы хотите более идиоматический способ c, не используйте #each, если вам небезразличны результаты. #each следует использовать только в том случае, если вас интересуют только побочные эффекты итерации.

Вместо этого используйте #map или один из многих других методов итерации.

def process_invitations
  p = participations.where(invited_at: nil).limit(50)
  p.map do |participation|
   # Invite may raise an exception, but also contains operations that
   # cannot be undone
   participation.invite()
   participation.invited_at = Time.zone.now
   participation.save
  end.reject.yeild_self do |updates|
    # run do_stuff if any of the records where updated
    do_stuff() if updates.any?
  end
end

Или если вы, например, хотели do_stuff только для записей, которые были обновлены:

def process_invitations
  p = participations.where(invited_at: nil).limit(50)
  p.map do |participation|
   # Invite may raise an exception, but also contains operations that
   # cannot be undone
   participation.invite()
   participation.invited_at = Time.zone.now
   participation if participation.save
  end.reject.yeild_self do |updated|
    do_stuff(updated) if updated.any?
  end
end

или do_stuff для каждой из записей, которые были обновлены:

def process_invitations
  p = participations.where(invited_at: nil).limit(50)
  p.map do |participation|
   # Invite may raise an exception, but also contains operations that
   # cannot be undone
   participation.invite()
   participation.invited_at = Time.zone.now
   participation if participation.save
  end.reject.map do |record|
    do_stuff(record)
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...