问题
I am handling the upload of PDFs' files on Cloundinary
through a background jobs. I enqueue them from an after_save
callback. The dilemna is that for one update my background job get triggered multiples times. To counter this flaw, I tried to implement a method using around_perform
, to ensure that my job would be triggered only one time. But it actually did not work. I was wondering if any of you know how to handle those unwanted calls to the job
Here is my code
My after_save
callback
The callback is placed on both my model Invoice and Quote.
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
end
My Job 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
My around_perform
In this part, I put my instance in a variable named element
.
The idea was that, if the UploadPdfJob
job is enqueued more than once. The PDF will only be uploaded once. The first job will set uploaded
to true
, then the second job will exit after checking done
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
Also, as I did not want to trigger the callback on the update, I tried this way. Using a variable called start
, that does not rely on my retrieved instance
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
回答1:
I had to roll up my sleeves, but I finally got the last word. I debugged with binding.pry
,got to the very root of the problem and find out and updated every part of codes that were triggering my Job.
Also to further prevent any unwanted triggering, I added a customed guard callback stating which parameters saved should set the Job in motion.
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
来源:https://stackoverflow.com/questions/64994252/backgroundjob-triggered-multiple-times-on-save-rails-5-sidekiq