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?