I am trying to figure out the initialization of rails 3. And i know that every application will calling the following function in environment.rb to initialize the whole app:
MyApp::Application.initialize!
From it we should expect that initialize! is a class method of Rails::Application. But after i red the source code, i found that instead initialize! is an instance method which is actually called:
def initialize!(group=:default) #:nodoc:
raise "Application has been already initialized." if #initialized
run_initializers(group, self)
#initialized = true
self
end
So why is the instance method called although we expect a class method? Is there some trick like method delegation or else?
Yes. there is a little trick delegation. if you look at file lib/rails/railtie/configurable.rb in railties gem you will see following code that delegates to instance.
def method_missing(*args, &block)
instance.send(*args, &block)
end
in case you are not aware of method_missing hook, you can read up more about it here: http://rubylearning.com/satishtalim/ruby_method_missing.html
Related
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
Looking through the Rails codebase sometimes respond_to_missing? invokes super and sometimes not. Are there cases where you should not invoke super from respond_to_missing?
It depends on the implementation of the class and the behavior you want out of #respond_to_missing?. Looking at ActiveSupport::TimeWithZone, it is a proxy wrapper for Time. It tries to mimic it, fooling you into thinking it is an instance of Time. TimeWithZone#is_a? would respond true when passed Time, for example.
# Say we're a Time to thwart type checking.
def is_a?(klass)
klass == ::Time || super
end
alias_method :kind_of?, :is_a?
respond_to_missing? should catch cases that would be caught by method_missing, so you have to look at both methods. TimeWithZone#method_missing delegates missing methods to Time instead of super.
def method_missing(sym, *args, &block)
wrap_with_time_zone time.__send__(sym, *args, &block)
rescue NoMethodError => e
raise e, e.message.sub(time.inspect, inspect), e.backtrace
end
So it makes sense that it would delegate respond_to_missing? to Time as well.
# Ensure proxy class responds to all methods that underlying time instance
# responds to.
def respond_to_missing?(sym, include_priv)
return false if sym.to_sym == :acts_like_date?
time.respond_to?(sym, include_priv)
end
respond_to_missing? appears in Ruby version 1.9.2 as a solution to the method method. Here's a blog post by a Ruby core committer about it: http://blog.marc-andre.ca/2010/11/15/methodmissing-politely/
The reason for calling super however, is so that in the event your logic returns false, the call will bubble up the class hierarchy to Object which returns false. If your class is a subclass to a class that implements respond_to_missing? then you would want to call super in the event your logic returns false. This is generally an issue for library code and not so much for application code.
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'm working on rails app (3.2.11), and i'm implementing services as singleton, using the Ruby Library's one. I was trying to avoid calling :instance method every time I need it (SomethingService.instance.get(some_id)), solving my problem like this
class SomethingService
include Singleton
class << self
extend Forwardable
def_delegators :instance, *ShoppingListService.instance_methods(false)
end
end
So this solution was working perfectly fine but i've got a lot of services, and i don't want to add this code to all my classes! Instead i was trying to put in in a super class like this :
class ServiceBase
include Singleton
def self.inherited(subclass)
super(subclass) # needed because inherited is override by Singleton
class << subclass
extend Forwardable
def_delegators :instance, *self.instance_methods(false)
end
end
end
But this is giving me a stack level too deep error... Any ideas guys?
It's probably a better idea to use method_missing here. def_delegators is executed when the class is evaluated, and may happen before your methods are even defined, simply because you are inheriting from that base class.
You could try something like this instead, which forwards any undefined message onto the instance:
class ServiceBase
class << self
def method_missing(name, *args, &block)
instance.send(name, *args, &block)
end
end
end
It may look like a bit of a scattershot approach when compared to delegation. You could do a check to see if the instance method exists first, but I don't think that's necessary - you're simply transferring the undefined method handling to the instance rather than the class.
One final point/question - can you elaborate on what benefit you get from the Singleton class in the first place?
Ok, I'm a bit of a newb. I know this error is occuring because I don't properly understand something about how methods are called. So can you help me understand what is going wrong here?
NoMethodError in ThingController#index
undefined method `initialized?' for Thing::Backend:Class
From the erroring section of ThingController.rb:
class ThingController
def init_things
Backend.init_things unless Backend.initialized?
end
t = ThingController.new
t.init_things
end
inside Backend.rb
class Backend
# checks if the things hash is initialized
def initialized?
#initialized ||= false
end
# loads things
def init_things
puts "I've loaded a bunch of files into a hash"
#initialized = true
end
end
I'm not calling the method correctly and I cannot find any clear explanations for this error on the internet. Please help.
Thanks
It appears that the issue is that the initialized method that you have declared in Backend is an instance method. When you then call Backend.initialized? you are calling calling the class method initialized? for the Backend class. This method is not defined, and so it raises the NoMethodError. You can solve this by declaring the method using def self.initialized?. If you really want this to be a class method, you may need to consider how the rest of your code is organized.
You can find more information on class vs. instance methods at http://railstips.org/blog/archives/2009/05/11/class-and-instance-methods-in-ruby/
You've declared initialized? as an instance method but are calling it as if it were a class method. Here's an explanation of the difference between instance methods and class methods.