Top level methods in Ruby

How top level methods work in Ruby

There are many quirks in the Ruby language which IMO show funny behaviours. Take the top level methods, for example – that is, methods defined outside of a class or module. There is something weird about them that makes me wonder about the reasons behind certain design choices. In particular, one thing that I find weird is that Ruby top level methods become private instance methods in all objects.

The reason is that main (the name of the top level context) is an instance of Object:

p self.class
=> Object

So for top level methods to be available on main they are defined as private instance methods on Object, as we can also see if we run:

p Object.private_instance_methods.size

def title

p Object.private_instance_methods.size

=> 70
=> 71

This in turn means these methods are basically attached to every Ruby object due to inheritance – that’s how Ruby implements global functions. I am not 100% sure of the reasons behind this behaviour (I might have an idea, read on), but it really looks weird to me. For example, say that we have a top level method called title:

def title
"Mr" # just an example

That method will become a private instance method on Object:

Object.private_instance_methods(false).include? :title
=> true

Having a class Person:

class Person
# ...

We cannot then call title on any instance of Person because despite the method is available due to inheritance, it is private:
=> :in `<main>': private method `title' called for #<Person:0x007fba5abbad70> (NoMethodError)

Unless, of course… we use send:

p :title
=> "Mr"

And, because classes are also objects in Ruby, top level methods also become class methods on all classes…

p Person.send :title
=> "Mr"


This may seem innocuous, but it results in ‘polluting’ all Ruby objects, and it certainly happens often. For example, take Cucumber step definitions. I have seen often methods defined directly in the files containing step definitions without being encapsulated into modules or classes; so those methods are basically top level methods, and as such they are attached to all Ruby objects. In the case of Cucumber, it’s easy to avoid this by creating modules and including them at runtime with World(ModuleName). But the problem is, when you add top level methods to a step definitions file -in the Cucumber example- you don’t necessarily intend to be able to call those methods from anywhere.

So.. why aren’t top level methods simply defined as singleton methods on main, instead of being instance methods on Object? Methods defined as top level methods should ideally result in a NoMethodError when called from any other class, but that’s not the case.

One possible reason behind this design choice is that this way you can avoid referring to the main object when calling methods like puts or require, for example. So we can just say puts and require from everywhere instead of something like Main.puts, Main.require. But wouldn’t it be better to explicitly call a method on main rather than polluting all the other objects just for this?

If my assumption is right – that this design choice is explained by the convenience of calling methods like puts and require without having to refer to main – is this behaviour a feature? (Open question for the readers).

It is also interesting that when you try the same code in IRB instead -at least with Ruby 1.9.3- the behaviour is different, and top level methods become instead public instance methods in all objects. So, if you run, in IRB, you’ll see that the behaviour is the opposite of that shown when running the code with the Ruby interpreter:

irb(main):001:0> def title
irb(main):002:1> "Mr"
irb(main):003:1> end
=> nil
irb(main):004:0> Object.private_instance_methods(false).include? :title
=> false
irb(main):005:0> Object.public_instance_methods(false).include? :title
=> true

Does anyone know the reason for this?

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s