about ruby class method invoke - ruby-on-rails

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.

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

Override ActionMailer defaults with a method called from inside any mailer

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

How to call another method from a self method with ruby?

# app/models/product.rb
class Product < ApplicationRecord
def self.method1(param1)
# Here I want to call method2 with a parameter
method2(param2)
end
def method2(param2)
# Do something
end
end
I call method1 from controller. When I run the program. I got an error:
method_missing(at line method2(param2))
.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.0/lib/active_record/relation/batches.rb:59:in `block (2 levels) in find_each
...
class Product < ApplicationRecord
def self.method1(param1)
# Here I want to call method2 with a parameter
method2(param2)
end
def self.method2(param2)
# Do something
end
end
Explanation: first one is a class method, the latter was an instance method. Class methods don't need a receiver (an object who call them), instance methods need it. So, you can't call an instance method from a class method because you don't know if you have a receiver (an instanciated object who call it).
It does not work because method2 is not defined for Product object.
method2 is an instance method, and can be called only on the instance of Product class.
Of course #Ursus and #Andrey Deineko answers are right solution of this problem. Besides that, if anyone want to know how we can call instance method with in class method(though this is not actually class method in ruby) for those self.new.instance_method.

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>

Pass a symbol to a method and call the corresponding method

In a Rails controller you can pass a symbol to the layout method that corresponds to a method in you controller that will return the layout name like this:
layout :my_method
def my_method
'layout_1'
end
I want to have a similar functionality to likewise pass a symbol to my classes method and that class should call the corresponding function and use its return value, like this
myClass.foo :my_method
def my_method
'layout_1'
end
I've read posts[1] that tell me I need to pass
myClass.foo(method(:my_method))
which I find ugly and inconvenient. How is rails here different allowing to pass just the symbol without any wrapper? Can this be achieved like Rails does it?
[1] How to implement a "callback" in Ruby?
If you want to only pass a :symbol into the method, then you have to make assumptions about which method named :symbol is the one you want called for you. Probably it's either defined in the class of the caller, or some outer scope. Using the binding_of_caller gem, we can snag that information easily and evaluate the code in that context.
This surely has security implications, but those issues are up to you! :)
require 'binding_of_caller'
class Test
def foo(sym)
binding.of_caller(1).eval("method(:#{sym})").call
end
end
class Other
def blork
t = Test.new
p t.foo(:bar)
p t.foo(:quxx)
end
def bar
'baz'
end
end
def quxx
'quxx'
end
o = Other.new
o.blork
> "baz"
> "quxx"
I still don't understand, what is author asking about. He's saying about "callbacks", but only wrote how he wants to pass parameter to some method. What that method(foo) should do - i have no idea.
So I tried to predict it's implementation. On class initialising it gets the name of method and create private method, that should be called somewhere under the hood. It possible not to create new method, but store method name in class variable and then call it somewhere.
module Foo
extend ActiveSupport::Concern
module ClassMethods
def foo(method_name)
define_method :_foo do
send method_name
end
end
end
end
class BaseClass
include Foo
end
class MyClass < BaseClass
foo :my_method
private
def my_method
"Hello world"
end
end
MyClass.new.send(:_foo)
#=> "Hello world"
And really, everything is much clearer when you're not just wondering how it works in rails, but viewing the source code: layout.rb

Resources