Search code examples
ruby-on-rails-3cachingactionmailerdelayed-jobprawn

DelayedJob causing variables in ActionMailer to be cached in production?


We're using Amazon SES to send our e-mails, which is why we send all e-mail that could be bulkish using DelayedJob. DelayedJob queues these e-mails to be sent with at least a second between them.

It generally works fine, but I've seen problems with variables appearing to be cached inside the ActionMailer templates and in the generated PDFs that are sent along with some of these e-mails. The PDFs are generated using Prawn.

Say, for instance, all our clients have an unique ID and I send an e-mail to two clients. The first client has ID 1234 and the second has ID 2345. What happens is that both clients receive an e-mail with the ID 1234 in it. Both e-mails go to the correct person though and some other variables will render properly.

I originally thought this problem to be related to the use of helpers in my e-mail template. I'll share an example:

<%- @extra_lines.each do |line|
  if @interpolations
    @interpolations.each do |key,value|
      line = line.html_safe.sub "%{#{key}}", value.to_s
    end
  end %>
  <p><%= line.html_safe %></p>
<%- end %>

I hope this makes sense, @extra_lines is a variable that contains some extra paragraphs for our e-mail that's generated based on a set of conditions. The value of each member of the @interpolations contains what's duplicated in the second e-mail. In the mentioned case @interpolations would look something like {user_id: 1234}.

Now, not all variables have this problem. If I simply render a variable that's passed on from the Mailer class, this doesn't happen. Which is why I'm worried the output of one of the helper methods I'm using is being cached. All the more because similar behaviour happens in my PDF classes.

So I've tried to reproduce this by generating a new Rails application with DelayedJob enabled and created this mailer:

class MyMailer < ActionMailer::Base
  default from: "[email protected]"

  def random_amount(arguments)
    @extra_lines = arguments[:extra_lines]
    @interpolations = arguments[:interpolations]
    mail to: '[email protected]'
  end
end

This mailer template:

<%- @extra_lines.each do |line|
  if @interpolations
    @interpolations.each do |key,value|
      line = line.html_safe.sub "%{#{key}}", value.to_s
    end
  end %>
  <p><%= line.html_safe %></p>
<%- end %>

And this controller:

class RandomController < ApplicationController
  def index
    extra_lines = [
      'Test line 1',
      'Test line 2 with %{variable}',
      'Test line 3'
    ]
    10.times do
      interpolations = {variable: rand(1..100)}
      arguments = {extra_lines: extra_lines, interpolations: interpolations}
      MyMailer.delay.random_amount(arguments)
    end
    render :text => "Master, I've scheduled mail for you."
  end
end

This is as close as I could get to the "real" application and it results in 10 e-mails with unique random amounts being sent to me. So, basically, I'm not able to reproduce the problem and, even more confusingly, most of the time it just works.

Is there anyone out there to whom this rings any bells?


Solution

  • I think I've found the answer to my question and it's much simpler than I originally thought. The @extra_lines were built from an I18n array. I'm doing this because these texts are used in e-mail, PDF and on a webpage and because they are multilingual, I wanted to store these texts in one central place, for which I feel I18n is perfect, but it doesn't support interpolation in arrays, which meant I had to roll that myself.

    So, long story short, in the PDF class, I was looping through the resulting array and interpolating using line.sub! "%{#{key}}", value.to_s (as opposed to line = line.sub(..)) which also updates the @extra_lines variable, which updates the (cached) result of the I18n.t('get.specific.extra.lines') query. When it tries to interpolate these strings again, it runs the interpolations on the already interpolated (cached) strings and thus, there are no interpolations taking place. This explains why e-mails were working fine in my tests: if I had generated a PDF before sending an e-mail it would have worked.