I am trying to send an email from my rails 4 app like so (condensed version from the console):
> ActionMailer::Base.mail(from: 'mail@example.com', to: 'foo@example.com', subject: 'test', body: "Hello, you've got mail!").deliver_later
The mail would be sent by delayed_lob, in my local test setup I trigger it like so:
> Delayed::Job.last.invoke_job
However the job crashes with the following message:
Devise::Mailer#invitation_instructions: processed outbound mail in 56234.1ms
Performed ActionMailer::DeliveryJob from DelayedJob(mailers) in 56880.04ms
TypeError: no implicit conversion of nil into Array
from /Users/de/.rvm/gems/ruby-2.4.0/gems/actionview-4.2.10/lib/action_view/lookup_context.rb:51:in `concat'
from /Users/de/.rvm/gems/ruby-2.4.0/gems/actionview-4.2.10/lib/action_view/lookup_context.rb:51:in `block in <class:LookupContext>'
from /Users/de/.rvm/gems/ruby-2.4.0/gems/actionview-4.2.10/lib/action_view/lookup_context.rb:39:in `initialize_details'
from /Users/de/.rvm/gems/ruby-2.4.0/gems/actionview-4.2.10/lib/action_view/lookup_context.rb:205:in `initialize'
...
I have looked into the code of lookup_context.rb:51
GitHub, the problem is here:
register_detail(:locale) do
locales = [I18n.locale]
locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks # < crashes here
# from the debugger I got:
# I18n.locale => :de
# I18n.fallbacks => {:en=>[]}
So obviously fallbacks does not contain my locale (:de
) which results in a nil
exception.
Apparently I18n.fallbacks is not configured correctly.
Question: How can I fix this?
I got the answer with the help of this blog post I found: https://sjoker.net/2013/12/30/delayed_job-and-localization/
It contains half of what I need. The proposed solution goes like this:
In order to propagate state from the time of creating a job, that state has to be transferred to the time the job is invoked by storing it on the job object in the database.
In the blog post, the author only stores the current locale to localize the mail at invitation time. However I also needed to store the fallbacks
which required a little bit of serialization.
Here is my solution:
# Add a state attributes to delayed_jobs Table
class AddLocaleToDelayedJobs < ActiveRecord::Migration
def change
change_table :delayed_jobs do |t|
t.string :locale # will hold the current locale when the job is invoked
t.string :fallbacks # ...
end
end
end
# store the state when creating the job
Delayed::Worker.lifecycle.before(:enqueue) do |job|
# If Locale is not set
if(job.locale.nil? || job.locale.empty? && I18n.locale.to_s != I18n.default_locale.to_s)
job.locale = I18n.locale
job.fallbacks = I18n.fallbacks.to_json
end
end
# retrieve the state when invoking the job
Delayed::Worker.lifecycle.around(:invoke_job) do |job, &block|
# Store locale of worker
savedLocale = I18n.locale
savedFallbacks = I18n.fallbacks
begin
# Set locale from job or if not set use the default
if(job.locale.nil?)
I18n.locale = I18n.default_locale
else
h = JSON.parse(job.fallbacks, {:symbolize_names => true})
I18n.fallbacks = h.each { |k, v| h[k] = v.map(&:to_sym) } # any idea how parse this more elegantly?
I18n.locale = job.locale
end
# now really perform the job
block.call(job)
ensure
# Clean state from before setting locale
I18n.locale = savedLocale
I18n.fallbacks = savedFallbacks
end
end