In the previous post, we looked at how Ruby's top-level acts as a wrapper of the Object class. And that the definitions you put in the top-level act as if they were put in the Object class itself. But, there was one thing that I left out in that blog post, because it deserves its own attention.

Module extension

If you have a module:

module Foo
  def bar
    :bar
  end
end

and you extend it in the top-level:

extend Foo

you'd expect that it would extend the Object class because of what we saw in that previous post. But you'd be wrong!

Object.bar # => NoMethodError

Check yo-self before you wreck yo-self!

self is a keyword that gives us access to the object upon which a particular method was called. It's usually available inside methods. And since the top-level kinda acts like a method, we can access the self at the top-level!

self # => main

I know, crazy, right?

But since the top-level is a wrapper of the Object class, self should equal Object, right?

self == Object # => false

Nope. In fact, self is an instance of Object:

self.class # => Object

And this is why when we extend a module at the top-level, the entire Object class doesn't get extend-ed.

But what about module inclusion?

But if extend doesn't work on the Object class, then how come include work on the Object class?

self.method(:include).owner == self.singleton_class # => true

The above statement means that the include method is overridden inside of self's singleton class to act on Object instead of self.

Top-level as a Class/Object hybrid

So as you can see, the top-level sometimes acts as the Object class, and sometimes acts as a modified instance of the Object class. But it's not magic, it's clever thinking which helps it achieve that. But there are a couple of other things we can achieve now that we know that self is an instance of Object.

Singleton method definitions

Defining methods on self:

def self.foo
  :foo
end

will result in the method being added to self's singleton:

foo # => :foo

and not be available to any other instance of any class:

Object.new.foo # => NoMethodError

as clear from the previous post.

Method access modification

The methods defined at the top-level are usually put inside the Object class as private methods:

def foo
  :foo
end

Object.private_method_defined?(:foo) # => true

If you want to use other access modifiers, then you can do so:

public

def bar
  :bar
end

Object.public_method_defined?(:bar) # => true

Note that if no access modifier is specified, then the default is private, but only in a file. In an IRB session however, the default is public. So if you want to mark a method as private in an IRB session, use the private keyword. Note that there is no protected access modifier in the top-level though.

The way this works is very simple. The top-level self has the methods public, and private as overridden methods which handle this task:

self.method(:public).owner == self.singleton_class # => true
self.method(:private).owner == self.singleton_class # => true

but no protected method:

self.method(:protected) # => NameError

Top-level as a function

Since we already know that the top-level also behaves as a function (also clear from the fact that we can access self in it), weirdly enough, you can define instance variables in it:

@foobar = :foobar

which can then be accessible through either singleton methods:

def self.foo
  @foobar
end

foo # => :foobar

or can be accessible by methods defined on Object:

def bar
  @foobar
end

bar # => :foobar

So, to reiterate what I said in the first blog post,

Ruby is weird, and that's why we love it.

What do you think of this weirdness in Ruby? Do share your thoughts/feedback/suggestions in the comments below. We would love to hear them. Cheers!


References