Wednesday 10 December 2008

Ruby Classes

In Ruby, everything is an object. You can find the class of an object with my_object.class, which returns an object of the Class class.
10.class  # => Fixnum
10.class.class # => Class
"Hello World".class # => String

If you do not have an instance of a class, you can determine the name of the class using the inspect method (this might be useful if you are in a class method in a super class and want to know the subclass).

The superclass method will produce the superclass.
10.class.superclass # => Integer
10.class.superclass.superclass # => Numeric
10.class.superclass.superclass.superclass # => Object

As in Java, everything inherits from Object.

You can check if an object is of a certain class using the is_a? method. This will return true if the object is of the given class or of a subclass of it.

"Hello World".is_a? String # => true
"Hello World".is_a? Fixnum # => false
"Hello World".is_a? Object # => true


What methods are there?
The methods method returns an array of method names for an object, while a call on methods from the class will produce an array of class methods.

"Hello".methods
String.methods

There are also methods that list more particular methods, such as public_instance_methods or private_methods.

You can also check if an object has an associated method with the respond_to? or method_defined? methods. Unlike methods, these two methods will not find private methods. The respond_to? method really does what it say - it tells you if the object will respond to the method call, rather than if the method exists. Often an object can respond to method calls where there is no method present using the method_missing functionality (discussed here). Rails uses this a lot. For a model, Post, calling Post.methods will locate no find methods, but the respond_to? method confirms they are there. method_defined? will tell you if the method actually exists.

"Hello".respond_to? 'length' # => true
String.respond_to? 'constants' # => true
Post.respond_to? 'find_by_name' # => true
Post.method_defined? 'find_by_name' # => false


See also:
http://www.ruby-doc.org/docs/ProgrammingRuby/html/ospace.html

Dynamically adding a method
You can add a method to a class at runtime, using the define_method method. Note that this method is private (so the first attempt below results in an error), and so must be invoked with the send method.
a = "what".class.define_method('hello') {p 'Hello World'}
# => NoMethodError: private method `define_method' called for String:Class
a = "what".class.send(:define_method, 'hello') {p 'Hello World'}
# => #<Proc:0x08cb9294@(irb):17>
irb(main):018:0> "say hello".hello
# => Hello World

You can even replace existing methods:
"what".length
=> 4
a = "random".class.send(:define_method, 'length') { 10 }
=> #<Proc:0x08caaf28@(irb):21>
"what".length
=> 10

Alternatively (and more simply), you can define methods for existing classes just like you would for your own class. The first example could be done like this:
class String
def hello
p 'Hello World'
end
end

I can see security issues here if someone can change how your classes' behave at runtime, and how do you comprehensively unit test if you cannot be sure how the String class behaves? All part of the fun of a dynamic language...

ObjectSpace
ObjectSpace is the Ruby reflection mechanism. It is a module that tracks all the current objects. You can list them with this:

ObjectSpace.each_object{ i puts i}


I had over 8000 objects in one IRB session. You can specify the class of an object to list, for example, this will list all objects of the Class class (only 466 of them):

ObjectSpace.each_object(Class) { i puts i}


Creating new objects dynamically
You can use the ObjectSpace to create a new object from a string containing the class name (see here), but I prefer using the eval method. eval simply interprets a given string as Ruby (like the synonymous method in JavaScript). The eval method is none too fast, but I do not know how it compares to using ObjectSpace.

def create_object class_name
eval("#{class_name}.new")
end


Struggling with Ruby: Contents Page

No comments: