BackgroundJob déclenché plusieurs fois lors de l'enregistrement (Rails 5, Sidekiq)

Nov 24 2020

Je gère le téléchargement des fichiers PDF Cloundinaryvia un travail en arrière-plan. Je les mets en file d'attente à partir d'un after_saverappel. Le dilemme est que pour une mise à jour, mon travail d'arrière-plan est déclenché plusieurs fois. Pour contrer cette faille, j'ai essayé de mettre en place une méthode utilisant around_perform, pour m'assurer que mon travail ne serait déclenché qu'une seule fois. Mais cela n'a pas fonctionné. Je me demandais si l'un d'entre vous savait comment gérer ces appels indésirables au travail

Voici mon code

Mon after_saverappel

Le rappel est placé sur mon modèle de facture et de devis.

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

fin

Mon boulot 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

ma around_perform

Dans cette partie, je place mon instance dans une variable nommée element.

L'idée était que, si le UploadPdfJobtravail est mis en file d'attente plus d'une fois. Le PDF ne sera téléchargé qu'une seule fois. Le premier travail sera défini uploadedsur true, puis le deuxième travail se terminera après vérificationdone

  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

De plus, comme je ne voulais pas déclencher le rappel sur la mise à jour, j'ai essayé de cette façon. En utilisant une variable appelée start, qui ne dépend pas de mon instance récupérée

    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

Réponses

DaviGo Nov 25 2020 at 21:48

J'ai dû retrousser mes manches, mais j'ai finalement eu le dernier mot. J'ai débogué avec binding.pry, je suis allé à la racine du problème et j'ai découvert et mis à jour chaque partie des codes qui déclenchaient mon Job.

Aussi pour empêcher davantage tout déclenchement indésirable, j'ai ajouté un rappel de garde personnalisé indiquant quels paramètres sauvegardés devraient mettre le Job en mouvement.

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