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.
If you have a module:
module Foo def bar :bar end end
extend it in the top-level:
you'd expect that it would
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
self should equal
self == Object # => false
Nope. In fact,
self is an instance of
self.class # => Object
And this is why when we
extend a module at the top-level, the entire
Object class doesn't get
But what about module inclusion?
extend doesn't work on the
Object class, then how come
include work on the
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
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
Singleton method definitions
Defining methods on
def self.foo :foo end
will result in the method being added to
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
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
private as overridden methods which handle this task:
self.method(:public).owner == self.singleton_class # => true self.method(:private).owner == self.singleton_class # => true
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
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?