Is there a way for a mail message created by ActionMailer via mail() to determine which mailer method created it? Assume that an interceptor wants to determine which mailer and which mailer method it was sent from and to modify something based on this.
Assume this mailer:
class ApplicationMailer < ActionMailer::Base
def some_mail
mail
end
end
And this interceptor:
class MailInterceptor
def self.delivering_email(message)
# `delivery_handler` exists, and answers question of which Mailer was used
logger.debug "Mailer: #{message.delivery_handler}" # "ApplicationMailer"
# NOTE: `method_name` doesn't exist, it shows the desired functionality
logger.debug "Message Method: #{message.mailer.method_name}" # "some_mail"
case message.delivery_handler
when ApplicationMailer
message.bcc.append "bccmetoo#test.com" if message.mailer.method_name == "some_mail"
when OtherMailer
message.bcc.append "bccthisperson#foo.com"
end
end
end
Are there such methods, or some way of determining this?
UPDATE
Changed the question to reflect how the mailer can be determined by the existing delivery_handler method, as answered in the question of which this question was marked a duplicate, but that the exact mailer method used is still unknown.
Related
I found a snippet in an article about sending mails in a Rails application:
class ExampleMailerPreview < ActionMailer::Preview
def sample_mail_preview
ExampleMailer.sample_email(User.first)
end
end
in this link: http://www.gotealeaf.com/blog/handling-emails-in-rails.
I do not know why the method: sample_email(), which in my mind should be an instance method, can be accessed like class method here as ExampleMailer.sample_email(). Can anyone explain?
It's not an rspec thing, it's an ActionMailer thing. Looking at:
https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb
Take a look at the comments in lines 135-146:
# = Sending mail
#
# Once a mailer action and template are defined, you can deliver your message or defer its creation and
# delivery for later:
#
# NotifierMailer.welcome(User.first).deliver_now # sends the email
# mail = NotifierMailer.welcome(User.first) # => an ActionMailer::MessageDelivery object
# mail.deliver_now # generates and sends the email now
#
# The <tt>ActionMailer::MessageDelivery</tt> class is a wrapper around a delegate that will call
# your method to generate the mail. If you want direct access to delegator, or <tt>Mail::Message</tt>,
# you can call the <tt>message</tt> method on the <tt>ActionMailer::MessageDelivery</tt> object.
The functionality is implemented by defining a method_missing method on the ActionMailer::Base class that looks like:
def method_missing(method_name, *args) # :nodoc:
if action_methods.include?(method_name.to_s)
MessageDelivery.new(self, method_name, *args)
else
super
end
end
Essentially, defining a method on an ActionMailer instance (NotifierMailer in the comment example) and then calling it on the class creates a new MessageDelivery instance which delegates to a new instance of the ActionMailer class.
How do I test that a certain instance variable is set in my my mailer with rspec? assigns is coming back undefined..
require File.dirname(__FILE__) + '/../../spec_helper'
describe UserMailer do
it "should send the member user password to a User" do
user = FG.create :user
user.create_reset_code
mail = UserMailer.reset_notification(user).deliver
ActionMailer::Base.deliveries.size.should == 1
user.login.should be_present
assigns[:person].should == user
assigns(:person).should == user #both assigns types fail
end
end
The error returned is:
undefined local variable or method `assigns' for #<RSpec::Core::ExampleGroup::Nested_1:0x007fe2b88e2928>
assigns is only defined for controller specs and that's done via the rspec-rails gem. There is no general mechanism to test instance variables in RSpec, but you can use Kernel's instance_variable_get to access any instance variable you want.
So in your case, if object were the object whose instance variable you were interested in checking, you could write:
expect(object.instance_variable_get(:#person)).to eql(user)
As for getting ahold of the UserMailer instance, I can't see any way to do that. Looking at the method_missing definition inside https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb, a new mailer instance will be created whenever an undefined class method is called with the same name as an instance method. But that instance isn't saved anywhere that I can see and only the value of .message is returned. Here is the relevant code as currently defined on github:
Class methods:
def respond_to?(method, include_private = false) #:nodoc:
super || action_methods.include?(method.to_s)
end
def method_missing(method_name, *args) # :nodoc:
if respond_to?(method_name)
new(method_name, *args).message
else
super
end
end
Instance methods:
attr_internal :message
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
# will be initialized according to the named method. If not, the mailer will
# remain uninitialized (useful when you only need to invoke the "receive"
# method, for instance).
def initialize(method_name=nil, *args)
super()
#_mail_was_called = false
#_message = Mail.new
process(method_name, *args) if method_name
end
def process(method_name, *args) #:nodoc:
payload = {
mailer: self.class.name,
action: method_name
}
ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
lookup_context.skip_default_locale!
super
#_message = NullMail.new unless #_mail_was_called
end
end
I don't think this is possible to test unless Rails changes its implementation so that it actually provides access to the ActionMailer (controller) object and not just the Mail object that is generated.
As Peter Alfvin pointed out, the problem is that it returns the 'message' here:
new(method_name, *args).message
instead of just returning the mailer (controller) like this:
new(method_name, *args)
This post on the rspec-rails list might also be helpful:
Seems reasonable, but unlikely to change. Here's why. rspec-rails
provides wrappers around test classes provided by rails. Rails
functional tests support the three questions you pose above, but rails
mailer tests are different. From
http://guides.rubyonrails.org/action_mailer_basics.html: "Testing
mailers normally involves two things: One is that the mail was queued,
and the other one that the email is correct."
To support what you'd like to see in mailer specs, rspec-rails would
have to provide it's own ExampleGroup (rather than wrap the rails
class), which would have to be tightly bound to rails' internals. I
took great pains in rspec-rails-2 to constrain coupling to public
APIs, and this has had a big payoff: we've only had one case where a
rails 3.x release required a release of rspec-rails (i.e. there was a
breaking change). With rails-2, pretty much every release broke
rspec-rails because rspec-rails was tied to internals (rspec-rails'
fault, not rails).
If you really want to see this change, you'll need to get it changed
in rails itself, at which point rspec-rails will happily wrap the new
and improved MailerTestCase.
I'm trying to add an interceptor to one of several mailers in my Rails application. Even though I'm trying to register it on only a single mailer, it seems to be intercepting the emails from both. Here is some sample code of how I'm trying to register just a single Mailer.
class Mailer1 < ActionMailer::Base; end
class Mailer2 < ActionMailer::Base; end
Mailer1.register_interceptor(MailInterceptor)
Is it possible to follow just a single mailer? Thanks for the help
What you can do is apply the interceptor only on specific Mailers:
class SpecificMailerInterceptor
FILTERED_MAILERS = %w(SpecificMailer)
def self.delivering_email(message)
return if deliver?(message)
message.perform_deliveries = false
end
def self.deliver?(message)
# If you are in the other mailers always deliver message
return true unless FILTERED_MAILERS.include?(message.delivery_handler.to_s)
# Apply interceptor logic here
end
end
i was wondering how do you make a helper file's functions available to an action mailer? i have an action mailer called UserMailer and a helper called sessions_helper.rb. how do i make the methods available to UserMailer? ive tried 'include' but it gives the following error
the method im trying to get is "current_user" and i receive the error
undefined local variable or method `cookies'
im using rails 3.2.1
thanks
You can helpers in your mailers like this,
class Notifier < ActionMailer::Base
add_template_helper(ApplicationHelper)
#...
end
As far as current_user go, I don't think mailers have any concept of cookies as ActionController does. As a better design choice I'd keep my mailer independent of the current_user. Mailers are not concerned with who the current_user is ( similar to models ).
For that matters, mailers are not even concerned with who the user is, they are concerned with "email, subject, and body".
So when calling mailer methods, you can pass them the user object ( it can be of the current_user or any body else ) so that they can get the email , generate the subject and the body.
I have had to add a file to config/initializers with this:
class ActionMailer::Base
helper MiscHelper
helper ExtraMailHelper
end
I guess you would add lines for other helpers as needed, e.g.:
class ActionMailer::Base
helper MiscHelper
helper ExtraMailHelper
helper SessionsHelper
end
If you want to include specific helpers in specific mailers you have, you can use this.
class RegistrationMailer < ActionMailer::Base
include MyOwnHelper
def method
end
end
Given a Mailer instance in Rails 3, is there a way to override the delivery_method on it?
I want to push certain emails out through a high-priority transport, and others use a busier, low-priority method.
I can adjust the config at runtime and change it back again, but this is fraught with potential side-effects.
It turns out that doing this affects just this specific Mailer, without changing ActionMailer::Base globally.
class SomeMailer < ActionMailer::Base
self.delivery_method = :high_priority
def some_email(params)
end
end
You can also do this (warning: will affect all instances of AnotherMailer), if you have the instance up-front:
mail = AnotherMailer.whatever_email
mail.delivery_handler.delivery_method = :something_else
These don't seem to be documented, but work.
I used to do that in a Rails 2 application where some emails were sent via ActionMailer and others were sent via ArMailer. It's not a bad solution as long as you don't change the delivery method back in the same delivery because it could cause the delivery method not to be changed in case of error with the delivery, this is what a I made:
class Mailer1
def my_mail1
config
end
private
def config
ActionMailer::Base.delivery_method = :smtp
end
end
class Mailer2
def my_mail2
config
end
private
def config
ActionMailer::Base.delivery_method = :sendmail
end
end