Where Did That Method Come From?

Posted on Jun 14th, 2012
Categories: ruby

How do you learn a new programming language? Attend a class? Buy the book? Just start writing code? All of those things will get you going – especially that last one – but if you really want to know how the experts do it, you gotta read some code. There is nothing like grabbing a well written application and trying to figure out how it all works to get you into the spirit of a new language.

If you are trying to learn Ruby by reading code, then I have some good news and some bad news. The good news is that Ruby is designed to be a language that is easy for humans to read. Ruby is so flexible that you can usually say a lot in very little code and this generally makes reading Ruby programs a pleasure. The bad news is that Ruby is so flexible that beginners sometimes find it a bit trying to spelunk their way through a serious application. In particular, Ruby’s flexible nature means that the code that gets executed when you sayobject.method does not always live where you think that it might.

So in the spirit of getting more people spun up on my favorite language, I’m going to take you through some of the ways that you might hook a method name on some object to a particular bit of Ruby code. We will start with some of the more pedestrian ways that a program can connect some Ruby code to a method name and then dive into the more exotic techniques.

Here we go…

The Usual Suspects: Classes and Superclasses

Let’s start with the most basic of basics: Every Ruby object has a class and a super class and a super-duper class and so on until you get to Object (or in Ruby 1.9, BasicObject), which stands alone at the top of the heap. And to no one’s surprise, when Ruby goes looking for a method it starts with the class and runs up the tree from class to super class until it finds the method or runs out of classes.

For example, I might be trying to model a car:

class Car
  def color
    "Boring blue"
  end

  def number_wheels
    4
  end
end

class SportsCar < Car
  def color
    "Sizzling red"
  end
end

my_car = SportsCar.new

What we have in the code above is a SportsCar class which is a subclass of Car which is, by default, a subclass of Object.

Call a method on an instance of SportsCar and you get what you expect: my_car.color will return a very vivid shade of red. After all, thecolor method is defined right there in the SportsCar class. Similarly, if you call my_car.number_wheels, you will find out that no matter how snazzy your new toy, it puts its tires on four at a time. Thanks to the magic of inheritance, Ruby will discover the number_wheels method in the Car class after failing to find it in SportsCar.

You might also call to_s on your sports car instance and get something like this back:

"#<SportsCar:0xb7d073b8>"

The to_s method comes to you courtesy of the super-duper class, Object.

Finally, if you call some non-existent method, say my_car.foobar you will get an unpleasant message stating that there ain’t no such method: Ruby ran up the super class chain and complained when it fell off the top without finding a thing.

So if you are reading some code and you see my_car.number_wheels, start with the obvious: Look for the number_wheels method in SportsCarand then in Car and so on up the line.

The Module Behind the Curtain

Once you get past classes, things get a bit more interesting. Besides the standard class and super classes, Ruby also features modules. A module is a bit like a class, and in fact you define the two with nearly identical syntax:

module Convertible
  def top_down
    "Putting the top down"
  end

  def top_up
    "Putting the top up"
  end
end

Modules may look like classes, but you can’t instantiate a module. What you can do with a module is include it in your class:

class SportsCar < Car

  include Convertible

  def color
    "Sizzling red"
  end
end

The effect of including a module in a class is to insert the module in the inheritance chain between the class and its super class. So if we called top_down on an instance of our new SportsCar class above, Ruby would look first go looking for the method in the SportsCar class (nope not there) and then in the Convertible module (got cha!). If we called the color method, then Ruby would look first in SportsCar (no luck), then in Convertible (nope) and then in Car (ah ha!).

The nice thing about modules is that while a class can only have one super class, it can include as many modules as it likes:

module NavigationSystem
  def location
    "39 53 N 75 15 W"
  end
end

class SportsCar < Car
  include Convertible
  include NavigationSystem

  def color
  "Sizzling red"
  end
end

The SportsCar class now includes both the Convertible and the NavigationSystem modules, so we will know exactly where we are when we put the top down. The fact that modules get inserted into the class hierarchy just above the including class has some interesting implications. First, since a module acts like a sort of super class, a module cannot override a method in the class that includes it. For example, imagine that we defined the location method directly in the SportsCar class and also included the NavigationSystem module:

class SportsCar < Car
  include Convertible
  include NavigationSystem

  # Stuff deleted ...

  def location
    "Right here"
  end
end

With the code above, if your call location on an instance of SportsCar, you are always going to find yourself “Right here” – the method defined in the class always wins.

Second, it is not just your class that can have modules: your super class and its higher level relatives can also suck in modules by the basketful. But it all works the way you expect: When Ruby goes looking for a method, it first looks in the class, then in all of the modules included in the class, then in the super class and then in all of its modules and on up the tree.

Third, if you include several modules in a class, the modules get hooked into the class hierarchy such that the last module included is the first one to be searched. This means that when you call a method on your sports car object, Ruby will look first at SportsCar, than at NavigationSystem, and then at Convertible in that order. Generally the order that you include modules doesn’t make much difference, but if two modules happen to define the same method it’s the method from the last module that you include that will win.

So if you are trying to figure out that automotive app and you just can’t find the location method in any of the classes, go looking in the modules included by the classes.

Singleton Methods: The Mysterious Stranger

But what if the method is nowhere to be found in the modules either? Well it might be completely alone. You see, Ruby allows you to define singleton methods (no relation to the design pattern of the same name). A singleton method is a method that you define on an object, independent of its class or modules.

I have taught Ruby to a fair number of experienced Java programmers and generally their initial reaction to singleton methods ranges from a good old Texas That just ain’t right to actual nausea. But once your ears stop ringing, it turns out that being able to customize a single object without having to worry about what the rest of the class is doing is incredibly useful. Think of all those cases that you have come across in your programming life, the ones that almost but don’t quite fit into some class, the programming equivalents of the three legged dog or the flying car. Singleton methods are made for this stuff.

It turns out that there are a couple of ways that you might hang a singleton method on an object. For example, if you wanted to install a custom, one of a kind, sound system in your car, you might use the class style notation:

my_car = SportsCar.new class << my_car
  def custom_sound_system
    "... BYYYYING A STAIIIIRWAY to heaVennnnnnn"
  end
end

Alternatively you might enhance your tunes by simply defining the method right on your object:

my_car = SportsCar.new

def my_car.custom_sound_system
  "... BYYYYING A STAIIIIRWAY to heaVennnnnnn"
end

Either way you end up with a my_car object that is one method different from all of the other sport cars.

And just how does all of this work? Very simply: every object has a sort of stealth class that sits between the object and its regular class. When you define a singleton method on an object, the method goes straight into the singleton class. The singleton class is a real class, and like any other class it can include modules. So one way to customize your car would be to add a cool new exhaust system:

module CoolExhaust
  def rev_engine
    "VAROOOOOOOOOOOOM"
  end

  def monthly_disturbing_the_peace_fines
    279
  end
end

my_car = SportsCar.new

class << my_car
  include CoolExhaust
end

Your car, and only your car, now sports the new exhaust system: You can now rev your engine in the morning and spend all afternoon in court.

The singleton class is a bit like a module: While a module fits in between a class and its super class, the singleton class fits between an instance and its class. When Ruby goes hunting for a method, it is actually the singleton class that Ruby consults first, before the regular class, before any modules and certainly before any super classes.

method_missing: The Method With a Thousand Faces

Imagine that you are trying to follow some code that calls my_car.drive_to_washington, but you can’t find the drive_to_washington method anywhere. You have searched your classes and modules. You have kept an eye out for those pesky singleton methods. You have searched and grep’ed and grep’ed and searched and that stinking drive_to_washington method is just not there. Relax, you may have just come across a case of method_missing (cue scary music).

We have seen that when you call a method on an object, Ruby goes through this exhaustive search – it looks in the object’s singleton class and its modules, then in the regular class and its modules, and then in the super-class and so on. But what if there is no method to be found? You just get an exception right?

Not exactly: when Ruby fails to find a method on the first pass, it doesn’t just give up. Instead Ruby begins the method hunt all over again, starting with the singleton class and working its way up the tree, this time looking for a method called method_missing. Essentially, Ruby tries to inform your object that someone called a method on it and that that method was, well, missing. It does this by calling the method_missing method. What happens from there is really up to you. Do nothing and the method_missing from the Object class will eventually throw the exception that you expect. But you can also define your own method_missing method and catch those unexpected calls and do whatever you want with them.

To see how this works in practice, let’s define a method_missing to implement that mysterious my_car.drive_to_washington call:

class Car
  def method_missing(method_name, *arguments)
    if /^drive_to_/ =~ method_name.to_s
      place = method_name.to_s.sub(/^drive_to_/, '')
      puts "I'm driving to #{place}"
    else
      super
    end
  end
end

There really is not a lot going on here. The method_missing method gets called with the name of the absent method along with all of the arguments that it was called with. What the Car class does is to look at the name of the method: If the method name begins with the string drive_to_ then the code will clip off the last part of the method name and announce that that is where it’s going. Notice that if the method name does not look right, method_missing calls super which will eventually call method_missing from Object and we will experience an exception.

With our new version of Car we can drive where ever we want:

c = Car.new
c.drive_to_washington
c.drive_to_ny

The key thing to notice about the way we implemented the drive_to_washington and drive_to_ny methods is that the strings – drive_to_washington– and “drive_to_ny” never actually appear in the source code. That is something that you need to keep in mind as you are scouring some application for an enigmatic method: it may be hiding, nascent, in method_missing.

Methods Here and Methods There

Sometimes the explanation for those hard to find methods is a lot simpler than method_missing. For example, you might be looking for a method and you have looked at the class definition, you have looked at the modules and you have searched diligently for any method_missing’s and you still can’t find that method. What gives?

Wait… Did you say the class definition? You see, in Ruby there is no such thing as the class definition. Oh a lot of classes are just defined in one place, like this:

class Convertible

But it is also perfectly possible to define a class in several chunks. You might, for example, find the first part of your convertible in one file: class Convertible

And then come across more of it in a second file:

class Convertible
  def top_down
    puts "Putting the top down"
  end

  def top_up
    puts "Putting the top up"
  end
end

Note that that second bit of code doesn’t create a new Convertible class; instead it reopens the existing Convertible class and adds a couple of methods to it.

The thing to keep in mind about Ruby is that when you define a class, you are not doing something all that special. What you are doing is creating a new object – an instance of Class. The object that you create, Convertible in our example, is special in some ways (you can create new instances with it, for instance) but in the end it is just an object. In Ruby you can modify existing classes as easily as you can create new ones. So if it is convenient to define one part of a class over here and another other there, people will. You just gotta keep your eye open for it.

Manufacturing Methods With Metaprogramming

The final twist in method making that we will look at is creating methods with metaprogramming. Metaprogramming is one of those very powerful techniques that can make writing an application so much easier but which can also make the code completely incomprehensible if you don’t get it. But read on, because get it you shall.

The idea of metaprogramming is that your program modifies itself as it goes. This is relevant for us, because a very popular way to modify a class via metaprogramming is to add a method to it. One way to do this is to simply evaluate the code to add the method:

class Car
  def self.add_destination(dest)
    code = %Q{
      def drive_to_#{dest}
        puts "Good bye!"
        puts "I'm driving to #{dest}"
      end
    }
    eval(code)
  end
end

The example above defines a class method called add_destination. The first thing that the add_destination method does is to conjure up a string (the %Q{...} syntax is just a convenient Ruby way of making a multi-line string) containing the Ruby code for the new method. It then evaluates that string as Ruby code. Instant method.

So if you call Car.add_destination('washington'), the string ends up being:

def drive_to_washington
  puts "Good bye!"
  puts "I'm driving to washington"
end

Evaluate this and you end up with a new method in the Car class called drive_to_washington.

You can also define methods a little more directly by using the (what else?) define_method method:

class Car
  def self.add_destination(dest)
    define_method "drive_to_#{dest}" do
      puts "Good bye!"
      puts "I'm driving to #{dest}"
    end
  end
end

There are some practical differences between the eval technique and define_method, mostly having to do with the visibility of variables, but at the very gross level they are the same: in both cases you end up with a new method without an explicit definition of that method in the source code.

Wrapping Up

So there you have it, a whole bunch of ways to connect that method call with some Ruby code. We saw that you that can do the traditional put all of your methods in the class or its superclasses thing, or you can define methods in a module and include the module in your class. Not only that, but you can also hang methods on individual objects. We also saw how you can catch calls to non-existent methods with method_missing or spread your method definitions in different files. Finally we took a quick look at metaprogramming, a way to cook up brand new methods on the fly.

If you are new to Ruby you may be thinking that the only possible reason that Ruby would support all of this variety is to drive the beginner nuts. I know that is how I felt at first. But every one of the more exotic Ruby method definition techniques has a purpose; Each one eases the burden of building programs in its own way. Want to know how? I think the best way to learn is to pick up your favorite Ruby book and start reading.

Russ Olsen

comments powered by Disqus