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
endand you extend it in the top-level:
extend Fooyou'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 # => NoMethodErrorCheck 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 # => mainI know, crazy, right?
But since the top-level is a wrapper of the Object class, self should equal Object, right?
self == Object # => falseNope. In fact, self is an instance of Object:
self.class # => ObjectAnd 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 # => trueThe 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
endwill result in the method being added to self's singleton:
foo # => :fooand not be available to any other instance of any class:
Object.new.foo # => NoMethodErroras 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) # => trueIf you want to use other access modifiers, then you can do so:
public
def bar
:bar
end
Object.public_method_defined?(:bar) # => trueNote 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 # => truebut no protected method:
self.method(:protected) # => NameErrorTop-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 = :foobarwhich can then be accessible through either singleton methods:
def self.foo
@foobar
end
foo # => :foobaror can be accessible by methods defined on Object:
def bar
@foobar
end
bar # => :foobarSo, 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?