Why Rails instance method can be used as class method in rspec - ruby-on-rails

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.

Related

Access ActionView::Helpers::DateHelper from class defined in /lib directory

I have an EmailHelper class defined in /lib/email_helper.rb. the class can be used directly by a controller or a background job. It looks something like this:
class EmailHelper
include ActionView::Helpers::DateHelper
def self.send_email(email_name, record)
# Figure out which email to send and send it
time = time_ago_in_words(Time.current + 7.days)
# Do some more stuff
end
end
When time_ago_in_words is called, the task fails with the following error:
undefined method `time_ago_in_words' for EmailHelper
How can I access the time_ago_in_words helper method from the context of my EmailHelper class? Note that I've already included the relevant module.
I've also tried calling helper.time_ago_in_words and ActionView::Helpers::DateHelper.time_ago_in_words to no avail.
Ruby's include is adding ActionView::Helpers::DateHelper to your class instance.
But your method is a class method (self.send_email). So, you can replace include with extend, and call it with self , like this:
class EmailHelper
extend ActionView::Helpers::DateHelper
def self.send_email(email_name, record)
# Figure out which email to send and send it
time = self.time_ago_in_words(Time.current + 7.days)
# Do some more stuff
end
end
That's the difference between include and extend.
Or...
you can call ApplicationController.helpers, like this:
class EmailHelper
def self.send_email(email_name, record)
# Figure out which email to send and send it
time = ApplicationController.helpers.time_ago_in_words(Time.current + 7.days)
# Do some more stuff
end
end
I prefer to include this on the fly:
date_helpers = Class.new {include ActionView::Helpers::DateHelper}.new
time_ago = date_helpers.time_ago_in_words(some_date_time)

Rails 4 ActionMailer around_action | access action information (action_name & parameters)

I'm building an around_action for my customer_mailer class so that I don't have to wrap begin and rescue around every time I call deliver_now
class CustomerMailer < ApplicationMailer
around_action :rescue_error
def send_email(customer)
...
end
def invite_friend(customer, invitee_email)
...
end
private
def rescue_error
yield
rescue => e
msg = "Caught exception! #{e} | #{action_name}"
puts msg
raise
end
end
So in the rescue, I want to log the message with information such as which action was called, I managed to find the method action_name to show which action was called, but I couldn't find a way to retrieve the parameters that were passed into the action, any ideas?
Thanks!
Before I answer your question: would using Bugsnag or something similar work in your case? Alternatively would rescue_from Exception, with: :exception_handler work for you? (it won't allow you to reraise the exception though)
I dug into Rails source code and it seems that parameters are not stored anywhere. They are just passed as a splat to an instance method defined in your mailer class. However, there is a way to store them (without monkey-patching).
Mailers inherit from AbstractController::Base. Looking at the snippet below:
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
# is the intended way to override action dispatching.
#
# Notice that the first argument is the method to be dispatched
# which is *not* necessarily the same as the action name.
def process_action(method_name, *args)
send_action(method_name, *args)
end
# Actually call the method associated with the action. Override
# this method if you wish to change how action methods are called,
# not to add additional behavior around it. For example, you would
# override #send_action if you want to inject arguments into the
# method.
alias send_action send
we can see that we can override #send_action and make it store the arguments. Add the following to your ApplicationMailer:
class ApplicationMailer < ActionMailer::Base
def send_action(method_name, *args)
#action_args = args
super
end
end
The arguments will be available as #action_args in all your mailers.
Just store the parameters with which the action has been called to an instance variable, say #params. Then these parameters will be accessible in rescue_error via #params. As per your example:
class CustomerMailer < ApplicationMailer
around_action :rescue_error
def send_email(customer)
#params = { customer: customer }
...
end
def invite_friend(customer, invitee_email)
#params = { customer: customer, invitee_email: invitee_email }
...
end
private
def rescue_error
begin
yield
rescue => e
msg = "Caught exception! #{e} | #{action_name} | #{#params.inspect}"
puts msg
raise
end
end
end
You can make the assignment to #params a bit cleaner by using hash parameters in your actions, e.g.
def invite_friend(options = {})
#params = params
...
end
Of course, this requires accessing the parameters via options, such as options[:customer] to access customer, and options[:invitee_email] to access invitee_email.
The action name have to be yielded , it depends on the way you use your rescue_error .
Define a variable in the block that will be yielded
or raise specifics errors (maybe your custom exception class )
this way you'll retrieve invormation via "e"
post an exemple use case of rescue_error.

Inherit a class from a gem and add local methods

I use a gem to manage certain attributes of a gmail api integration, and I'm pretty happy with the way it works.
I want to add some local methods to act on the Gmail::Message class that is used in that gem.
i.e. I want to do something like this.
models/GmailMessage.rb
class GmailMessage < Gmail::Message
def initialize(gmail)
#create a Gmail::Message instance as a GmailMessage instance
self = gmail
end
def something_clever
#do something clever utilising the Gmail::Message methods
end
end
I don't want to persist it. But obviously I can't define self in that way.
To clarify, I want to take an instance of Gmail::Message and create a GmailMessage instance which is a straight copy of that other message.
I can then run methods like #gmail.subject and #gmail.html, but also run #gmail.something_clever... and save local attributes if necessary.
Am I completely crazy?
You can use concept of mixin, wherein you include a Module in another class to enhance it with additional functions.
Here is how to do it. To create a complete working example, I have created modules that resemble what you may have in your code base.
# Assumed to be present in 3rd party gem, dummy implementation used for demonstration
module Gmail
class Message
def initialize
#some_var = "there"
end
def subject
"Hi"
end
end
end
# Your code
module GmailMessage
# You can code this method assuming as if it is an instance method
# of Gmail::Message. Once we include this module in that class, it
# will be able to call instance methods and access instance variables.
def something_clever
puts "Subject is #{subject} and #some_var = #{#some_var}"
end
end
# Enhance 3rd party class with your code by including your module
Gmail::Message.include(GmailMessage)
# Below gmail object will actually be obtained by reading the user inbox
# Lets create it explicitly for demonstration purposes.
gmail = Gmail::Message.new
# Method can access methods and instance variables of gmail object
p gmail.something_clever
#=> Subject is Hi and #some_var = there
# You can call the methods of original class as well on same object
p gmail.subject
#=> "Hi"
Following should work:
class GmailMessage < Gmail::Message
def initialize(extra)
super
# some additional stuff
#extra = extra
end
def something_clever
#do something clever utilising the Gmail::Message methods
end
end
GmailMessage.new # => will call first the initializer of Gmail::Message class..
Building upon what the other posters have said, you can use built-in class SimpleDelegator in ruby to wrap an existing message:
require 'delegate'
class MyMessage < SimpleDelegator
def my_clever_method
some_method_on_the_original_message + "woohoo"
end
end
class OriginalMessage
def some_method_on_the_original_message
"hey"
end
def another_original_method
"zoink"
end
end
original = OriginalMessage.new
wrapper = MyMessage.new(original)
puts wrapper.my_clever_method
# => "heywoohoo"
puts wrapper.another_original_method
# => "zoink"
As you can see, the wrapper automatically forwards method calls to the wrapped object.
I'm not sure why you can't just have a simple wrapper class...
class GmailMessage
def initialize(message)
#message = message
end
def something_clever
# do something clever here
end
def method_missing(m, *args, &block)
if #message.class.instance_methods.include?(m)
#message.send(m, *args, &block)
else
super
end
end
end
Then you can do...
#my_message = GmailMessage.new(#original_message)
#my_message will correctly respond to all the methods that were supported with #original_message and you can add your own methods to the class.
EDIT - changed thanks to #jeeper's observations in the comments
It's not the prettiest, but it works...
class GmailMessage < Gmail::Message
def initialize(message)
message.instance_variables.each do |variable|
self.instance_variable_set(
variable,
message.instance_variable_get(variable)
)
end
end
def something_clever
# do something clever here
end
end
Thanks for all your help guys.

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>

Rails ActionMailer Message Determine Mailer That Created It

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.

Resources