Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can someone help explain the post_initialize callback for class creation (Sandi Metz)

Tags:

ruby

I am reading Sandi Metz's POODR and have come across a coding principle that I don't quite understand. Here is the code:

class Bicycle
 attr_reader :size, :chain, :tire_size

  def initialize(args = {})
    @size = args[:size] || 1
    @chain = args[:chain] || 2
    @tire_size = args[:tire_size] || 3
    post_initialize(args)
  end
end

class MountainBike < Bicycle
  attr_reader :front_shock, :rear_shock

  def post_initialize(args)
    @front_shock = args[:front_shock]
    @rear_shock = args[:rear_shock]
  end
end

mb = MountainBike.new(front_shock: 4, rear_shock: 5)
puts mb.size
puts mb.chain
puts mb.tire_size
puts mb.front_shock
puts mb.rear_shock

This code will output 1,2,3,4,5 for its respective attributes. What I don't understand is the method look up.

When a mountain bike is instantiated, because it does not have its own initialize method, it will travel up the method lookup chain onto its super class (Bicycle). But now from there, it seems that Bicycle then moves back down into MountainBike's post_initialize method. Instead of continuing up the method chain, how can it go back down? Is post_initialize a ruby keyword like initialize in that it serves some sort of special function? Is there some other ruby introspection methods I can use to see what is going on?

like image 788
Dan Rubio Avatar asked Jun 08 '16 18:06

Dan Rubio


2 Answers

The important thing to understand here is that in this code:

def initialize(args = {})
  # ...
  post_initialize(args)
end

...post_initialize has an implicit receiver, self. In other words, post_initialize(args) here is equivalent to self.post_initialize(args), and self is an instance of MountainBike. Method lookup always starts with the receiver's class, so it has no trouble finding MountainBike#post_initialize.

 That's a lie; it's not equivalent when it comes to privacy; private methods cannot be called with an explicit receiver.
 That's also a lie; it actually starts with the receiver's singleton class, but then it tries its class.

like image 147
Jordan Running Avatar answered Nov 14 '22 08:11

Jordan Running


There is nothing special about the post_initialize method. It is merely a plain vanilla instance method of the subclass.

In Ruby, a superclass instance method is able to call a subclass instance method, even in its constructor. Check out this irb session:

2.3.0 :003 > class Base
2.3.0 :004?>   def initialize
2.3.0 :005?>     foo
2.3.0 :006?>     end
2.3.0 :007?>   end
 => :initialize
2.3.0 :015 > class Derived < Base
2.3.0 :016?>   def foo
2.3.0 :017?>     puts 'I am foo.'
2.3.0 :018?>     end
2.3.0 :019?>   end
 => :foo
2.3.0 :020 > Derived.new
I am foo.

The usual way this is done is by having the subclass call super, but I guess Sandi is suggesting the post_initialize approach to require that the subclass provide its own initialization, or formally decline to do so by implementing an empty method. (Plus, writers of subclasses may forget to call super.) Here is how it would be done with super:

2.3.0 :001 > class Base
2.3.0 :002?>   def initialize
2.3.0 :003?>     puts 'in base'
2.3.0 :004?>     end
2.3.0 :005?>   end
 => :initialize
 => #<Derived:0x007fda6ba291d8>
2.3.0 :012 > class Derived < Base
2.3.0 :013?>   def initialize
2.3.0 :014?>     super
2.3.0 :015?>     puts 'in derived'
2.3.0 :016?>     end
2.3.0 :017?>   end
 => :initialize
2.3.0 :018 > Derived.new
in base
in derived
 => #<Derived:0x007fda6b104b98>
like image 1
Keith Bennett Avatar answered Nov 14 '22 06:11

Keith Bennett