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
Related
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
I've written a little helper method in my ApplicationController like this:
helper_method :dehumanize
def dehumanize (string)
string.parameterize.underscore
end
Now I would like to use it in one of my model files, but it seems not to be available there.
I tried also with:
ApplicationController.dehumanize(title)
in the model but it doesn't work.
any clue on how to make it work there?
thanks,
Models generally can't/don't/shouldn't access methods in controllers (MVC conventions), but the method you've written doesn't necessarily belong in a controller anyway - it would be better as an extension to the string class.
I would suggest you write an initializer to add dehumanize to String:
\config\initializers\string_dehumanize.rb
class String
def dehumanize
self.parameterize.underscore
end
end
You will need to restart your server/console but then you can call .dehumanize on any string:
some model:
def some_method
string1 = 'testing_the_method'
string1.dehumanize
end
Matt's answer is totally right, but to give you some clarification, you want to make sure that you're calling your methods on objects / instances, rather than classes themselves
For example, you mentioned you tried this:
ApplicationController.dehumanize(title)
This will never work because it's calling a method on a class which is not initialized, not to mention the class doesn't have that method. Basically, what will you expect if you called this method?
The way to do it is to use the method Matt recommended, or use a class method on your model itself, which will allow you to call the model's method directly:
#app/models/model.rb
class Model < ActiveRecord::Base
def self.dehumanize string
string.parameterize.underscore
end
end
# -> Model.dehumanize title
Here the docu: https://relishapp.com/rspec/rspec-mocks/v/3-0/docs/message-expectations/expect-a-message-on-any-instance-of-a-class
Im wondering what is the right use of it.
I have a controller
class UserController < ApplicationController
def edit
generate_token!
end
end
And the method generate_token! is defined in the model.
class User < ActiveRecord::Base
def generate_token!
self.update!(token: 'something')
end
end
I just want to check if the method receives something.
The spec would be something like.
describe 'edit'
it 'receives something' do
expect_any_instance_of(Object).to receive(:generate_token!)
end
end
But what do I have to use for the Object? I tried the class and some other random stuff, but nothing worked yet. It seems I dont get the Mock at all.
Any suggestions?
best regards
denym_
You need to replace Object with Client in your spec. Also the method's name is only "generate_token" not "generate_token!" as you have in your spec currently.
It seems you are mixing the generate_token! in your controller and the generate_token method in your Client model.
In the edit action you are calling the generate_token! defined in the same class (controller) (I would assume you only pasted the edit action here, so you might really have this method in your controller).
Anyway, if you do not have a generate_token! method in your controller which has a line like that:
#client.generate_token
the generate_token inside your Client model will never get called from your controller.
One more thing: the name of the controller that handles your client records really called users_controller?
That could also cause problem, if you really have a separate User and Client model.
I think now I know what could be your main confusion.
Expect only set your expectation. You still need to create the instance of the class and trigger the action where you are expecting something that you want to see to happen.
Eg. if you are testing a controller action you need to call the edit action after you set your expectation.
client = create(:client)
get :edit, id: client
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
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.