Understanding how Ruby initializes objects
Deep buried in Rails source code, there is a code snippet:
class DeprecationProxy
def self.new(*args, &block)
object = args.first
return object unless object
super
end
...
end
It was added 9 years ago and the commit message said:
Override new on proxy objects so that they never wrap nil or false.
The question is that how does this work? Because we know that when an object is initialized using new
, Ruby executes the initialize
method.
class Greeting
def initialize(message)
puts "Hello there! #{message}"
end
end
Greeting.new("Welcome to Ruby world.")
=> "Hello there! Welcome to Ruby world."
Notice that we have not actually defined new
method anywhere. So how does this code from Rails work?
To see how things work under the hood, let’s introspect where the new
method is defined on the class.
Ruby provides a handy method which is named method
to see where a method is defined.
>> Greeting.method(:new)
=> #<Method: Class#new>
We can see that the new
method actually comes from Class
. But then how does the initialize
method gets called when we call new
on a class?
Turns out, Ruby internally calls initialize
on the object passing all the arguments that were passed to new
.
greeting = Greeting.new(message)
=> Calls Class.new(message)
=> Calls initialize on the object
=> which results in:
=> greeting.initialize(message)
As Ruby allows us to override any method, we can override the new
class method as well.
class Greeting
def self.new(args)
puts args
puts "New ends here."
end
def initialize(args)
puts "I am getting initialized with #{args}"
end
end
Let’s see what is the output of creating an object of the Greeting class now.
Greeting.new({name: "Prathamesh", country: "India"})
>> Greeting.new({name: "Prathamesh", country: "India"})
{:name=>"Prathamesh", :country=>"India"}
New ends here.
=> nil
As expected, Ruby happily calls the overridden new
method and no longer calls the initialize
method because we did not call it from our definition of new
.
Now let’s look at the snippet from Rails that we discussed earlier:
class DeprecationProxy
def self.new(*args, &block)
object = args.first
return object unless object
super
end
...
end
It is clear that the new
method is overridden by the DeprecationProxy
class and it returns same object when the object has a falsey value.
return nil unless nil
return false unless false
It should also be noted that this overridden new
method calls super
on the last line which in turn calls Class#new
. This makes sure that Ruby’s semantics of new
method are preserved. This will make sure that DeprecationProxy.new
will call initialize
method of DeprecationProxy
after the overridden new
method is executed.
Subscribe to my newsletter to know more about how Ruby works by prying out Rails code.