Rails 4.2 Ruby 2.3
I have two optional routing scopes relating to locale information. They are set in a before_action
in the application_controller
which configures the default_url_options method. i.e.
# app/controllers/application_controller
# simplified version, usually has two locale values,
# locale_lang and locale_city
before_action :redirect_to_locale_unless_present
private
# If params[:locale] is not set then
# redirect to the correct locale base on request data
def redirect_to_locale_unless_present
unless params[:locale].present?
redirect_to url_for(locale: request.location.country_code)
end
end
def default_url_options(options = {}
{ locale_lang: params[:locale_lang] }.merge(options)
end
The scopes are locale_lang
and locale_city
which end up looking something like http://localhost:3000/fr/ or http://localhost:3000/en/
This all works as intended in the browser, however I would like to utilize ActionMailer::DeliveryJob to send emails in background processes. The obvious issue to this is that ActionMailer::DeliveryJob doesn't store the value of params[:locale]
.
I would like to be able to call SomeMailer.generic_email(options).deliver_later
and have this send the current default_url_options
to the ActionMailer::DeliveryJob which would then pass those along the chain and use them when actually processing the mail. I could of course define default_url_options as a parameter for each Mailer method but I would much rather set up the app so it was automatically included.
Have you ever encountered this issue or have any suggestions on how to approach the task. Keep in mind that it should also be thread safe.
My currently failing approach is to save the current request in Thread.current and then retrieve those when enqueue_delivery is called via .deliver_later. I then wanted to override ActionMailer::DeliveryJob's perform method to accept the url_options and use class_eval to define the default_url_options method within the current mailer class. However, perform doesn't seem to even be called when using deliver_later any ideas?
class ApplicationController
before_action :store_request
private
def store_request
Thread.current['actiondispatch.request'] = request
end
end
module DeliverLaterWithLocale
module MessageDeliveryOverrides
def enqueue_delivery(delivery_method, options={})
args = [
@mailer.name,
@mail_method.to_s,
delivery_method.to_s,
url_options,
*@args
]
ActionMailer::DeliveryJob.set(options).perform_later(*args)
end
private
def url_options
options = {}
request = Thread.current["actiondispatch.request"]
if request
host = request.host
port = request.port
protocol = request.protocol
lang = request.params[:locale_lang]
city = request.params[:locale_city]
standard_port = request.standard_port
options[:protocol] = protocol
options[:host] = host
options[:port] = port if port != standard_port
options[:locale_lang] = lang
options[:locale_city] = city
end
ActionMailer::Base.default_url_options.merge(options)
end
end
module DeliveryJobOverrides
def perform(mailer, mail_method, delivery_method, url_options, *args)
mailer = mailer.constantize.public_send(mail_method, *args)
Kernel.binding.pry
mailer.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def default_url_options_with_options(*args)
default_url_options_without_current_request(*args).merge(url_options)
end
alias_method_chain :default_url_options, :options
RUBY
mailer.send(delivery_method)
end
end
end
Incase anyone else wants to do this. I fixed it by adding
class ApplicationController
before_action :store_request
private
def store_request
Thread.current['actiondispatch.request'] = request
end
end
module DeliverLaterWithLocale
module MessageDeliveryOverrides
def enqueue_delivery(delivery_method, options={})
args = [
@mailer.name,
@mail_method.to_s,
delivery_method.to_s,
url_options,
*@args
]
ActionMailer::DeliveryJob.set(options).perform_later(*args)
end
private
def url_options
options = {}
request = Thread.current["actiondispatch.request"]
if request
host = request.host
port = request.port
protocol = request.protocol
lang = request.params[:locale_lang]
city = request.params[:locale_city]
standard_port = request.standard_port
options[:protocol] = protocol
options[:host] = host
options[:port] = port if port != standard_port
options[:locale_lang] = lang
options[:locale_city] = city
end
ActionMailer::Base.default_url_options.merge(options)
end
end
module DeliveryJobOverrides
def perform(mailer, mail_method, delivery_method, url_options, *args)
mailer = mailer.constantize
mailer.default_url_options = url_options
mailer.public_send(mail_method, *args).send(delivery_method)
end
end
end
And then prepend these to the respective classes in an initializer