Search code examples
ruby-on-railsdelayed-job

Rails 3.2 delayed job a pdf email


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


Solution

  • 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.