I would like all the emails sent from our staging server to have the phrase "[STAGING] " prefaced in the subject. Is there an elegant way to do this in Rails 3.2 using ActionMailer?
Here's an elegant solution I found using ActionMailer Interceptor based on an existing answer.
# config/initializers/change_staging_email_subject.rb
if Rails.env.staging?
class ChangeStagingEmailSubject
def self.delivering_email(mail)
mail.subject = "[STAGING] " + mail.subject
end
end
ActionMailer::Base.register_interceptor(ChangeStagingEmailSubject)
end
This works for Rails 4.x
class UserMailer < ActionMailer::Base
after_action do
mail.subject.prepend('[Staging] ') if Rails.env.staging?
end
(...)
end
Not really, inheritance is about as elegant as it gets.
class OurNewMailer < ActionMailer::Base
default :from => 'no-reply#example.com',
:return_path => 'system#example.com'
def subjectify subject
return "[STAGING] #{subject}" if Rails.env.staging?
subject
end
end
Then you can inherit from each of your mailers.
# modified from the rails guides
class Notifier < OurNewMailer
def welcome(recipient)
#account = recipient
mail(:to => recipient.email_address_with_name, :subject => subjectify("Important Message"))
end
end
I don't think this is as clean as what you were hopeing for, but this will dry it up a bit.
Related
There is a before_action callback method in my ActionMailer object which is responsible for setting some instance variables.
class TestMailer < ApplicationMailer
before_action :set_params
def send_test_mail
mail(to: #email, subject: subject)
end
def set_params
#account = account.email
#date = some_action(account.updated_at)
end
end
The question is How one can test these variables in a rspec test?
some thing like:
describe TestMailer do
describe '#set_params' do
described_class.with(account: account, subject: subject).send_test_mail.deliver_now
expect(#date).to eq(Date.today)
end
end
any clue would be highly appreciated.
I think that instead of testing the instance variables, it would be better to test the email body, for example:
expect(mail.body.encoded).to include(account.updated_at)
you could setup a spy inside a mock method instance_variable_set, then validate that spy
class TestMailer < ApplicationMailer
attr_accessor :day
# ...
end
describe TestMailer do
let(:freeze_today) { Time.now.utc }
it '#set_params' do
# freeze today
allow_any_instance_of(TestMailer).to receive(:some_action)
.with(account.updated_at)
.and_return(freeze_today)
# spy
#spy = nil
allow_any_instance_of(TestMailer).to receive(:day=) do |time|
#spy = time
end
described_class.with(account: account, subject: subject)
.send_test_mail
.deliver_now
expect(#spy).to eq(freeze_today)
# or just simple like this
expect_any_instance_of(TestMailer).to receive(:day=).with(freeze_today)
end
end
I am trying to add a dynamic link to email. The body of the emails is fetched and rendered using a liquid template.
I have added the dynamic link as below, but not sure if it's the most elegant way. Any help in this will be great. Below is the relevant part of the code.
class UserDrop < Liquid::Drop
def search_path
ActionController::Base.helpers.content_tag(
:a,
#user.email,
:href => Rails.application.routes.url_helpers.admin_users_url(
search: #user.email,
host: Rails.application.config.action_mailer.default_url_options[:host])
)
end
end
Liquid template code
Email: {{user.search_path}}
You can really clean this up with a drop of inheritance:
class BaseDrop < Liquid::Drop
# shamelessly stolen from
# http://hawkins.io/2012/03/generating_urls_whenever_and_wherever_you_want/
class Router
include Rails.application.routes.url_helpers
def self.default_url_options
ActionMailer::Base.default_url_options
end
end
private
def router
#router ||= Router.new
end
def helpers
#helpers ||= ActionController::Base.helpers
end
end
class UserDrop < BaseDrop
def search_path
helpers.link_to(#user.email, router.admin_users_url(search: #user.email))
end
end
Here is some code in a recent Railscast:
class UserMailer < ActionMailer::Base
default from: "from#example.com"
def password_reset(user)
#user = user
mail :to => user.email, :subject => "Password Reset"
end
end
and this is in a controller
def create
user = User.find_by_email(params[:email])
UserMailer.password_reset(user).deliver
redirect_to :root, :notice => "Email sent with password reset instructions."
end
The password_reset method looks like an instance method to me, yet it looks like it's being called like a class method. Is it an instance or a class method, or is there something special about this UserMailer class?
Looking in the source (https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb), Rails uses method_missing to create a new instance of the ActionMailer. Here's the relevant part from the source:
def method_missing(method_name, *args) # :nodoc:
if respond_to?(method_name)
new(method_name, *args).message
else
super
end
end
In my Rails application I want to temporarily stop sending email for specific users (e.g. when I get bounces due to quota) until the user confirms he is able to receive email again.
I have a common superclass for all mailer classes. There I always call a method setup_email before sending a mail.
Where is the best place to call #user.mail_suspended??
Here is some simplified sample app, I use Rails 2.3:
# Common super class for all Mailers
class ApplicationMailer < ActionMailer::Base
protected
def setup_mail(user)
#recipients = user.email
#from = ...
end
end
# Specific Mailer for User model
class UserMailer < ApplicationMailer
def message(user, message)
setup_mail(user)
#subject = "You got new message"
#body[:message] = message
end
end
# Use the UserMailer to deliver some message
def MessagesController < ApplicationController
def create
#message = Message.new(params[:message])
#message.save
UserMailer.deliver_message(#message.user, #message)
redirect_to ...
end
end
I solved this by setting the ActionMailer::Base.perform_deliveries to false:
def setup_mail(user)
email = user.default_email
if email.paused?
ActionMailer::Base.perform_deliveries = false
logger.info "INFO: suspended mail for #{user.login} to #{email.email})"
else
ActionMailer::Base.perform_deliveries = true
end
# other stuff here
end
I wouldn't set perform_deliveries universally, just per message, e.g.
after_filter :do_not_send_if_old_email
def do_not_send_if_old_email
message.perform_deliveries = false if email.paused?
true
end
I tried many ways, but no one could help me except this one.
class ApplicationMailer < ActionMailer::Base
class AbortDeliveryError < StandardError; end
before_action :ensure_notifications_enabled
rescue_from AbortDeliveryError, with: -> {}
def ensure_notifications_enabled
raise AbortDeliveryError.new unless <your_condition>
end
...
end
Make a class inherited with standardError to raise exception.
Check the condition, if false then raise exception.
Handle that exception with the empty lambda.
The empty lambda causes Rails 6 to just return an
ActionMailer::Base::NullMail instance, which doesn't get delivered
(same as if your mailer method didn't call mail, or returned
prematurely).
I have small organizatoric issue, in my application I have 3 mailer User_mailer, prduct_mailer, some_other_mailer and all of them store their views in app/views/user_mailer ...
I will want to have a subdirectory in /app/views/ called mailers and put all in the folders user_mailer, product_mailer and some_other_mailer.
Thanks,
You should really create an ApplicationMailer class with your defaults and inherit from that in your mailers:
# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
append_view_path Rails.root.join('app', 'views', 'mailers')
default from: "Whatever HQ <hq#whatever.com>"
end
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
def say_hi(user)
# ...
end
end
# app/views/mailers/user_mailer/say_hi.html.erb
<b>Hi #user.name!</b>
This lovely pattern uses the same inheritance scheme as controllers (e.g. ApplicationController < ActionController::Base).
I so agree with this organization strategy!
And from Nobita's example, I achieved it by doing:
class UserMailer < ActionMailer::Base
default :from => "whatever#whatever.com"
default :template_path => '**your_path**'
def whatever_email(user)
#user = user
#url = "http://whatever.com"
mail(:to => user.email,
:subject => "Welcome to Whatever",
)
end
end
It is Mailer-specific but not too bad!
I had some luck with this in 3.1
class UserMailer < ActionMailer::Base
...
append_view_path("#{Rails.root}/app/views/mailers")
...
end
Got deprecation warnings on template_root and RAILS_ROOT
If you happen to need something really flexible, inheritance can help you.
class ApplicationMailer < ActionMailer::Base
def self.inherited(subclass)
subclass.default template_path: "mailers/#{subclass.name.to_s.underscore}"
end
end
You can put the templates wherever you want, but you will have to specify it in the mailer. Something like this:
class UserMailer < ActionMailer::Base
default :from => "whatever#whatever.com"
def whatever_email(user)
#user = user
#url = "http://whatever.com"
mail(:to => user.email,
:subject => "Welcome to Whatever",
:template_path => '**your_path**',
)
end
end
Take a look at 2.4 Mailer Views for more info.
Easy Solution: Specify Path in ApplicationMailer
class ApplicationMailer < ActionMailer::Base
layout 'mailer'
prepend_view_path "app/views/mailers" # <---- dump your views here
end