BackgroundJob uruchamiany wiele razy przy zapisie (Rails 5, Sidekiq)

Nov 24 2020

Obsługuję przesyłanie plików PDF Cloundinaryza pomocą zadań w tle. Kolejkuję je z after_saveoddzwonienia. Dylematem jest to, że w przypadku jednej aktualizacji moje zadanie w tle jest uruchamiane wielokrotnie. Aby temu przeciwdziałać, próbowałem wdrożyć metodę wykorzystującą around_perform, aby moja praca była uruchamiana tylko raz. Ale to faktycznie nie zadziałało. Zastanawiałem się, czy ktoś z was wie, jak radzić sobie z niechcianymi wezwaniami do pracy

Oto mój kod

Moje after_saveoddzwonienie

Oddzwonienie jest umieszczane zarówno na fakturze, jak i wycenie.

Class Invoice
 after_save :upload_pdf

 def upload_pdf
   UploadPdfJob.perform_later(self.id,'invoice')

   new_notif_paid = Notification.create(user: self.user,
     category: "PDF",
     content: "Your PDF #{self.reference}
            is available ",
     element: "invoice",
     element_id: self.id)
 end

koniec

Moja praca UploadPDFJob

def perform(id, type)
   create_pdf_copy(id, type)
end


def create_pdf_copy(id, type)

  wicked = WickedPdf.new

  value = type == 'invoice'? Invoice.find(id) : Quote.find(id)
  template_path = type == 'invoice'? 'invoices/show': 'quotes/show.html.erb'
  file_type = type == 'invoice'? 'facture': 'devis'


  pdf_html = ApplicationController.render(
    locals: {
      current_user: value.user,
    },
    assigns: {
      "#{type}": value,
      format: 'pdf'
    },
    template: template_path,
    layout: 'pdf'
  )

  pdf_file = wicked.pdf_from_string(pdf_html,
    page_size: 'A4',
    orientation: "portrait",
    lowquality: true,
    zoom: 0.9,
    dpi: 75
  )

  tempfile = Tempfile.new("#{file_type}-#{value.id}.pdf")

  File.open(tempfile.path, 'wb') do |file|
    file << pdf_file
  end

  tempfile.close


  unless pdf_file.blank?
    value.photo.attach(io: File.open(tempfile.path), filename: "#{file_type}-#{value.id}.pdf")
  end
end

Mój around_perform

W tej części umieściłem instancję w zmiennej o nazwie element.

Pomysł był taki, że jeśli UploadPdfJobzadanie jest kolejkowane więcej niż raz. Plik PDF zostanie przesłany tylko raz. Pierwsze zadanie zostanie ustawione uploadedna true, a drugie zadanie zostanie zakończone po sprawdzeniudone

  around_perform do |job, block|
    id = job.arguments.first
    element = job.arguments.last == 'invoice'? Invoice.find(id) : Quote.find(id)
    element.with_lock do
      return if element.uploaded
      if block.call
        element.update(uploaded: true)
      else
        retry_job
      end
  end

Ponadto, ponieważ nie chciałem wywoływać wywołania zwrotnego w aktualizacji, próbowałem w ten sposób. Używając zmiennej o nazwie start, która nie zależy od mojej pobranej instancji

    around_perform do |job, block|
      id = job.arguments.first
      element = job.arguments.last == 'invoice'? Invoice.find(id) : Quote.find(id)
      start = false
      element.with_lock do
        return if start == true
        if block.call
          start = true
        else
          retry_job
        end
      end
    end

Odpowiedzi

DaviGo Nov 25 2020 at 21:48

Musiałem zakasać rękawy, ale w końcu dotarło do mnie ostatnie słowo. Debugowałem binding.pry, dotarłem do samego źródła problemu i odkryłem i zaktualizowałem każdą część kodów, które wyzwalały moją pracę.

Aby dodatkowo zapobiec niepożądanemu wyzwalaniu, dodałem niestandardowe wywołanie zwrotne strażnika określające, które zapisane parametry powinny wprawić zadanie w ruch.

Class Invoice

      after_save :upload_pdf, if: :should_upload?

      def should_upload?
        attributes = self.saved_changes
        %i[title client_id creation_date].any? {|val| attributes.key? val}
      end
    end