Thursday, July 24, 2008

Metaclasses and Ruby Inheritance Chain

This is a very large and involved topic. (great topic! it is part of what makes Ruby so powerful)
You can add new methods to a Class on the fly at anytime. Can also overwrite methods at any time. And, in Ruby, this truly means virtually any Class. Including classes like Array, String, etc.

Let's say we have

> class Course
> attr_accessor :id, :name
> def initialize( id, name)
> @id, @name= id, name

> end
> end

> c = Course.new( 1, "Augusta National" )
=> #<
Course:0x81cfb94 @id=1, @name="Augusta National"...

> c.class
=> Course

> c.id
=> 1
> c.name
=> "Augusta National"


So in this case, a Course object, once initialized, will have a
@id and a @course variable. It can hold
any other variables at any time too...

> c.instance_variable_set( "@par", 72 )
=> 72
> c.par
=> MoMethodError: undefined method 'par' for #<Course..

It's holding that variable for par, but now we cannot get at it without a read method. So let's say you
wanted to only have the reader/writer methods for par on that instance,
not on the class
(just an example).


Classes hold methods, objects do not. Except in the case of metaclasses:
> class << c
> attr_accessor :par
> def pretty_par
> puts "par: #{@par}"
> end
> end

Now you can do
> c.par
=> 72
Also when you do:
> c.methods
=> ["par", "id", "name", "pretty_par", ...] #=> these are referred to as the
#=> object's "Singleton methods"

but when you show the methods for the Course class:
> Course.methods
=> ["id", "name", ...] # i.e. no par, or pretty_par, because there is no method
# for these in the Course class, only the c metaclass
likewise:

> c2 = Course.new(2, "Shinnecock Hills")
=>
#<Course:0x91cfb94 @id=2, @name="Shinnecock Hills"

but it won't have access to any of the Singleton methods in c (i.e. neither "c2.par", nor "c2.pretty_par" method
calls will work. They only work in the c metaclass).


You see, because when Ruby looks for a method, it first looks at the metaclass.
i.e. the metaclass intercepts the request, and checks for methods there first.

Then it goes to the object's class, then the SuperClass of that object, then any super's of that object and so on
...all the way back to the Object base class.


You can insert methods into the class, or into an object's metaclass at any level in the inheritance chain.
Very powerful.



One more beauty... if you attach a:
...
def method_missing
puts "there was no method that matched that"
end
...
you can intercept the call to a missing method (anywhere in the inheritance chain, the class, the metaclass, etc)
and do things like creating methods on the fly, programmatically.

It's called "monkey patching" (some people call it "Duck punching", and there are other names).
Ruby is a great language to metaprogram with. Rails uses method_missing often (along with const_missing) --
for instance, ActiveRecord uses method_missing to dynamically support the find_ methods.

That's a whole other interesting topic in itself to explore!

1 comment:

kaviya said...

My cousin recommended this blog and she was totally right keep up the fantastic work!



Ruby Development