Override ActionMailer defaults with a method called from inside any mailer - ruby-on-rails

Given we have a custom header set in application.rb as
config.action_mailer.default_options = {
'X-MyCustomHeader' => "Default_value" }
I would like to be able to call a method, such as remove_custom_headerfrom inside any mailer and it have the header removed, or at least set to nil.
Because its a custom header (starting with X-) ActionMailer will allow more than one to be created rather than reseting it as it would with standard headers.
What is the best way to define a method that can overide it?
The issue i have is that is you call headers['X-MyCustomHeader'] = nilinside the mailer method it will not override it, it simply creates a second header where the entry is nil, leaving the original from application.rb on the email.
The only way i can find to override the default set there is to call default 'X-MyCustomHeader' => nil inside the ApplicationMailer (or in any inheriting mailers class'), like below, but not in the method.
class ApplicatioMailer < ActionMailer::Base
helper :application
default 'X-MyCustomHeader' => nil
end
but i would like to call this on a method basis, not in the mailer class itself as the app has many many mailer classes and im looking to disable this for one or 2 methods in some of the Mailers.
My current solution is:
class ApplicatioMailer < ActionMailer::Base
helper :application
default 'X-MyCustomHeader' => Proc.new { #is_disabled ? nil : Rails.config.action_mailer.default_header }
def remove_custom_header
#is_disabled = true
end
end
and this seems to work as you are using the default call from ActionMailer to override. Now i have tested this and it does work with multiple calls, so the value of #is_disableddoes not seem to persist between mailer calls. I dont understand how the class var works here, in a SuperClass of the mailer that calls it and when no new object of the class is created, but it seems to be null every new call to the ApplicationMailer, so for me it works. However, is this a good solution? Have i missed something obvious? I dont feel comfterable using a class var in this situation, but i can think of another way!
Thanks in advance for any help!
EDIT:: For example, i would like to call in my mailers like so..
class MyMailer < ApplicatioMailer
def mail_method_one(email)
# my call
remove_custom_header
mail from: a.format, subject: email.subject
end
end
And if my method above of setting the class var is valid and sane (which i doubt somehow) can someone explain why as i would be interested how it works and will have to justify it to my lead dev! :)

Contrary to what it looks like, #is_disabled is not a class variable, it's an instance variable. Just as the method you defined is defined as an instance class
Note that you're doing...
def mail_method_one(email)
...not...
def self.mail_method_one(email)
You can confirm this yourself by stopping execution using the pry gem or some other inspection tool...
def mail_method_one(email)
binding.pry
And if you examine self you'll see you're in an instance of the mailer.
You do call it as a class method, but that's because action mailer uses method_missing to test if the missing class method is actually an instance method and creates a new instance of MessageDelivery passing the class name, the method name and the arguments, and subsequently calls the method on an instance of the mailer class.
Here's the code where it does that...
def method_missing(method_name, *args)
if action_methods.include?(method_name.to_s)
MessageDelivery.new(self, method_name, *args)
else
super
end
end
You can examine the code here...
https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb

Related

Reason for calling an instance method on a class in Ruby?

I would like to know is there any specific reason for doing this or is this a silly mistake done by someone (or is it something else that I am not understanding).
class SomeMailer < ActionMailer::Base
def first_method(user)
mail(to: user.email, subject: "Testing")
end
end
This method is called at some other place in the code as follows
SomeMailer.first_method(user).deliver
ActionMailer::Base classes are weird... Yes, you do indeed call instance methods on the class - which obviously won't work for 'normal' classes!
But there's some meta-programming magic under the hood:
module ActionMailer
class Base < AbstractController::Base
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
end
end
If you look through the rails documentation, you'll see that calling instance methods on the class is, strangely, the normal thing to do for mailers.
This is how rails is intended to work.
It is also mention in rails guides that
You never instantiate your mailer class. Rather, you just call the method you defined on the class itself.
Rails do the internal processing by invoking method_missin.
Basically, any action method defined in mailer class will be intercepted by method_missing and will return an instance of MessageDelivery, otherwise it runs the default implementation. And where do action methods come from? ActionMailer::Base inherits from AbstractController::Base, so it works exactly the same as for controllers - it returns a set of public instance methods of a given class.
Rails itself encourage this behavior. For more information, you can refer this link
I will try to answer this since I have come across similar situations myself while working on existing code.
The instance methods like this in a class help when you do call backs on a class. For example, if you want to perform some action on a object that was created from that class.
Say you have another class User and you want to send an email to a user immediately after creating a new user. In that case you can call this method on that object by doing
after_save :method_name

Ruby on Rails instance vs class methods

I have studied major difference between Ruby class ,instance method and the major difference I found is we don't need to create instance of that class we can directly call that method on class name directly.
class Notifier
def reminder_to_unconfirmed_user(user)
headers['X-SMTPAPI'] = '{"category": "confirmation_reminder"}'
#user = user
mail(:to => #user["email"], :subject => "confirmation instructions reminder")
end
end
So,here I defined instance method reminder_to_unconfirmed_user in my Notifier class to send email to unconfirmed users, and when I run Notifier.reminder_to_unconfirmed_user(User.last) it get called provided it's a instance method not a class method.
To define a class method, use the self keyword in the method's definition (or the class' name):
class Notifier
def self.this_is_a_class_method
end
def Notifier.this_a_class_method_too
end
def this_is_an_instance_method
end
end
In your case, reminder_to_unconfirmed_user should be defined as a class method:
class Notifier
def self.reminder_to_unconfirmed_user(user)
# ...
end
end
Then you can use it like this:
Notifier.reminder_to_unconfirmed_user(User.last)
I had the same question the OP did and after digging around I finally figured it out! The other answers just addressed when to use instance vs class methods in Ruby however Rails does some sneaky stuff behind the scences. The question wasn't when to use class vs instance methods but instead how come Rails allows you to call an instance method as if it's a class method as shown by his mailer example above. It's due to: AbstractController::Base and can be seen here: AbstractController::Base
Basically, in all controllers (whether they be your mailer or a standard controller), all defined methods are intercepted by "method_missing" and then returns an instance of that class! The defined methods are then also converted to public instance methods. Thus, because you never instantiate these classes (for example you never do Mailer.new.some_method) Rails automagically calls method_missing and returns an instance of that Mailer which then takes advantage of all the methods defined within that class.
In your case it must be :
class Notifier
def self.reminder_to_unconfirmed_user(user)
headers['X-SMTPAPI'] = '{"category": "confirmation_reminder"}'
#user = user
mail(:to => #user["email"], :subject => "confirmation instructions reminder")
end
end
As their name suggests:
Instance methods on a model should be used for logic/operations that relate to a specific instance of a model (the one on which the method is called.)
Class methods are for things which don't operate on an individual instance of a model or for cases where you don't have the instance available to you. Like in some cases you do want to apply changes on few group of objects.
If you want to update all users on a specific condition, Then you should go for class method.
They do have different way of calling :
class Test
def self.hi
puts 'class method'
end
def hello
puts 'instance method'
end
end
Foo.hi # => "class method"
Foo.hello # => NoMethodError: undefined method ‘hello’ for Test:Class
Foo.new.hello # => instance method
Foo.new.hi # => NoMethodError: undefined method ‘hi’ for #<Test:0x1e871>

Setting instance variables in Action Mailer?

I am wondering what is a clean and conventional way for setting instance variables in Mailers? Currently, I have re-defined the initialize method in Mailer and subsequently overwrite certain instance variables when needed in any mailers that inherit from Mailer.
class Mailer < ActionMailer::Base
attr_reader :ivar
def initialize
super
#ivar = :blah
...
end
end
This only seems weird to me because new is a private method for mailers. For example, if I were to try to retrieve these in the rails console, I need to do the following:
mailer = Mailer.send(:new)
mailer.ivar
I have also considered adding them to the default hash like so:
class Mailer < ActionMailer::Base
default ivar: :blah,
...
end
The only problem being that I need to create a method like this to retrieve the ivars:
def default_getter(ivar)
self.class.default[ivar]
end
Neither way seems particularly clean to me. I've considered using class variables, but I'm wondering if someone could suggest a cleaner way. Thanks.
Just a little bit late...
You can use before_action callbacks
http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-callbacks

about ruby class method invoke

in rails3 i defined a class
#coding:utf-8
class CoreMail < ActionMailer::Base
def consulting_reply(email)
mail(:to => email, :subject => 'ssss')
end
end
i found i could invoke this method like this
CoreMail.consulting_reply(email)
but want i thought the right way is :
instance=CoreMail.new
instance.consulting_reply(email)
because the consulting_reply is the instance method,
did i missing something? hope someone could give me a help
ActionMailer::Base has a method_missing defined on it:
def method_missing(method, *args) #:nodoc:
return super unless respond_to?(method)
new(method, *args).message
end
This will call your instance method with the same arguments and then call the message method, returning the object of that mailer call. To deliver it, call deliver! on the end of your method call:
CoreMail.consulting_reply(email).deliver!
You're right, normally that would be an instance method and you would have to create an instance of the class before calling consulting_reply() (since it's not defined as self.consulting_reply). However, your class is inheriting from ActionMailer::Base, and there's a little ruby magic happening behind the scenes. Checkout the source for ActionMailer::Base to see it for yourself (https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb), but to simplify for you what's happening, here's a quick example:
class Test
def self.method_missing(method, *args)
puts "Called by a non class method"
end
end
Test.fake_class_method
Which will output:
Called by a non class method
ActionMailer::Base does something similar to this, which dynamically looks for the method you called. I believe it also creates a instance of the class using:
new(method, *args).message
Anyway, I hope this sheds some light as to what's happening.

How can I stub a before_filter in a super class in Rails?

I'm using RR for mocking and stubbing in RSpec, and I've run across a situation where I'd like to stub a method from a super class of a controller that sets some instance variables. I can work out how to stub the method call and if I debug I can see that my stubbed block is called, but I cannot get the instance variables in the block to propagate into the class I'm testing.
Just to break it down :
class A < ApplicationController
before_filter :bogglesnap
def bogglesnap
#instancevar = "totally boggled"
end
end
class B < A
def do_something_with_instance
if #instancevar
....
else
....
end
end
end
That's the basic setup, and so then in my tests for controller B I'd like to stub out the bogglesnap method from A to set #instancevar to something I want. I just can't figure out how to do it.
I've tried RR's instance_of stubbing and just stubbing out the controller definition :
stub.instance_of(A).bogglensap { #instancevar = "known value" }
stub(controller).bogglesnap { #instancevar = "known value" }
but neither of these seem to work, well, they don't work :)
Does anyone have any pointers on how you should be able to stub that method call out and have it set instance variables? I'm assuming it has to do with the context in which the block is run but am hoping someone has run across something like this before.
Thanks
You can use instance_variable_set method by calling on the object instance and set it to whatever you want, like so
controller.instance_variable_set("#instancevar", "known value")
and similarly, if you ever want to fetch the value of an instance variable in your spec or debug or do something else from outside the class then you can get the value by doing
controller.instance_variable_get("#instancevar")
Mind you, instance_variable_set and instance_variable_get methods are available not only to controllers but all objects as it is provided by ruby. Infact, these two methods play an important role in rails magic :)

Resources