Rails 3.2 delayed job a pdf email

111 views Asked by At

I have a page where the user can select multiple costprojects to create a single pdf (including S3 attachments). It works fine - except if the user selects quite a few, then app will time out (30 sec).

So, I would like to create the pdf and email it in the background using gem 'delayed_job_active_record'.

This works without delayed job:

 def pdfemail
    @costprojects = Costproject.find(params[:costproject_ids])
    pdf = CombinePDF.new
    @costprojects.each do |costproject|
      @costproject = costproject
      pdf2 = render_to_string pdf: "SLCO Captital Projects.pdf", template: "costprojects/viewproject", encoding: "UTF-8"
      pdf << CombinePDF.parse(pdf2)
      costproject.attachments.each do |attachment|
        pdf << CombinePDF.parse( Net::HTTP.get( URI.parse( attachment.attach.url ) ) )
      end
    end
    useremail = current_user.email
    CostpdfMailer.costpdf_email(useremail,pdf).deliver
    redirect_to :back
    flash[:notice] = 'An Email containing a PDF has been sent to you!'
  end

This was my first try that didn't work - I added:

handle_asynchronously :pdfemail

My second try:

 def pdfemail
    @costprojects = Costproject.find(params[:costproject_ids])
    CostprojectsController.delay.pdfemail2(@costprojects)
  end

  def self.pdfemail2(costprojects)
    @costprojects = costprojects
    pdf = CombinePDF.new
    @costprojects.each do |costproject|
      @costproject = costproject
      pdf2 = render_to_string pdf: "SLCO Captital Projects.pdf", template: "costprojects/viewproject", encoding: "UTF-8"
      pdf << CombinePDF.parse(pdf2)
      costproject.attachments.each do |attachment|
        pdf << CombinePDF.parse( Net::HTTP.get( URI.parse( attachment.attach.url ) ) )
      end
    end
    useremail = current_user.email
    CostpdfMailer.costpdf_email(useremail,pdf).deliver
    redirect_to :back
    flash[:notice] = 'An Email containing a PDF has been sent to you!'
  end

With the 2nd try, I get:

undefined method `render_to_string' for CostprojectsController:Class

The same render_to_string worked when it was just pdfemail.

3rd try:

pdf2 = CostprojectsController.new.render_to_string pdf: "SLCO Captital Projects.pdf", template: "costprojects/viewproject", encoding: "UTF-8"

With the 3rd try, @costproject isn't getting passed to costprojects/viewproject.pdf.erb

1

There are 1 answers

4
DiegoSalazar On BEST ANSWER

You can't handle a controller action asynchronously.

And you can't call controller methods like render, render_to_string and flash[:notice] from within a class method self.pdfemail since those methods are instance methods on the controller.

You'll have to separate out the pdf email logic from the controller response logic, like this:

def pdfemail      
  pdf_template = render_to_string pdf: "SLCO Captital Projects.pdf", template: "costprojects/viewproject", encoding: "UTF-8"

  PdfMailer.new.pdf_email params[:costproject_ids], current_user.email, pdf_template

  redirect_to :back
  flash[:notice] = 'An Email containing a PDF has been sent to you!'
end

Then in another file:

class PdfMailer
  def pdf_email(costproject_ids, useremail, pdf_template)
    @costprojects = Costproject.find(costproject_ids)
    pdf = CombinePDF.new

    @costprojects.each do |costproject|
      @costproject = costproject
      # This part needs work, you can't use render_to_string here.
      # You'll have to generate this pdf some other way.
      pdf2 = render_to_string pdf: "SLCO Captital Projects.pdf", template: "costprojects/viewproject", encoding: "UTF-8"
      pdf << CombinePDF.parse(pdf2)

      costproject.attachments.each do |attachment|
        pdf_response = Net::HTTP.get URI.parse(attachment.attach.url)
        pdf << CombinePDF.parse(pdf_response)
      end
    end

    CostpdfMailer.costpdf_email(useremail, pdf).deliver
  end
  handle_asynchronously :pdf_email
end

The PdfMailer class does not use any controller instance methods such as render, render_to_string, or flash, that has to be taken care of in the controller since you can't handle controller action asynchronously. Consequently you have to pass in the data you need, in this case the costproject_ids, useremail from current_user.email.

Inside the PdfMailer class you'll have to generate the pdf in that loop with some other method than the controller's render_to_string. There's plenty of other tools for this here: https://www.ruby-toolbox.com/categories/pdf_generation.

On your 3rd try: it isn't working because you're instantiating a new controller which doesn't have the @costproject instance variable set in it.