Meet six misunderstood Ruby features
Learn about Ruby features likely to trip up C++ programmers
If you're a C++
programmer and need to work in
Ruby, you have a bit of learning to do. This article discusses six Ruby
features that the Ruby newbie is likely to misunderstand, especially if he
or she comes from a similar-but-not-quite environment like
C++
:
- The Ruby class hierarchy
- Singleton methods in Ruby
- The
self
keyword - The
method_missing
method - Exception handling
- Threading
Note: All code in this article was tested and based on Ruby version 1.8.7.
Class hierarchy in Ruby
Class hierarchy in Ruby can be tricky. Create a class of type
Cat
and start to play with its hierarchy (see
Listing 1).
Listing 1. Implicit class hierarchy in Ruby
irb(main):092:0> class Cat irb(main):093:1> end => nil irb(main):087:0> c = Cat.new => #<Cat:0x2bacb68> irb(main):088:0> c.class => Cat irb(main):089:0> c.class.superclass => Object irb(main):090:0> c.class.superclass.superclass => nil irb(main):091:0> c.class.superclass.superclass.superclass NoMethodError: undefined method `superclass' for nil:NilClass from (irb):91 from :0
All objects in Ruby (even the user-defined objects) are descendants of the
Object
class, as is obvious from Listing 1.
This is in sharp contrast to C++
. There's
nothing like a plain datatype—for example,
C/C++ int
or double
.
Listing 2 shows the class hierarchy for the
integer 1.
Listing 2. Class hierarchy for 1
irb(main):100:0> 1.class => Fixnum irb(main):101:0> 1.class.superclass => Integer irb(main):102:0> 1.class.superclass.superclass => Numeric irb(main):103:0> 1.class.superclass.superclass.superclass => Object
So far so good. Now you know that classes themselves are objects of type
Class
. Class
in turn
is ultimately derived from Object
, as
demonstrated in Listing 3, with the Ruby built-in
String
class.
Listing 3. Class hierarchy for classes
irb(main):100:0> String.class => Class irb(main):101:0> String.class.superclass => Module irb(main):102:0> String.class.superclass.superclass => Object
Module
is the base class for
Class
, and it comes with the caveat that you
cannot instantiate user-defined Module
objects
directly. If you don't want to get into Ruby internals, it's safe to
consider Module
having similar characteristics
to a C++
namespace: You can define your own
methods, constants, and so on. You include a
Module
inside a
Class
, and voilà, all the elements of the
Module
are now magically the elements of the
Class
. Listing 4 provides
an example.
Listing 4. Modules cannot be directly instantiated and can be used only with classes
irb(main):020:0> module MyModule irb(main):021:1> def hello irb(main):022:2> puts "Hello World" irb(main):023:2> end irb(main):024:1> end irb(main):025:0> test = MyModule.new NoMethodError: undefined method `new' for MyModule:Module from (irb):25 irb(main):026:0> class MyClass irb(main):027:1> include MyModule irb(main):028:1> end => MyClass irb(main):029:0> test = MyClass.new => #<MyClass:0x2c18bc8> irb(main):030:0> test.hello Hello World => nil
Here's the recap, then: When you write
c = Cat.new
in Ruby,
c
is an object of type
Cat
that is derived from
Object
. The Cat
class is an object of type Class
, which is
derived from Module
, which is in turn derived
from Object
. Both the object and its type are
thus valid Ruby objects.
Singleton methods and editable classes
Now, look at singleton methods. Suppose you want to model something
akin to the human society in C++
. How would you
do it? Have a class called Human
, and then have
millions of Human
objects? That's more like
modeling a zombie society; each human must have some unique
characteristic. Ruby's singleton methods come in handy here, as explained
in Listing 5.
Listing 5. Singleton methods in Ruby
irb(main):113:0> y = Human.new => #<Human:0x319b6f0> irb(main):114:0> def y.paint irb(main):115:1> puts "Can paint" irb(main):116:1> end => nil irb(main):117:0> y.paint Can paint => nil irb(main):118:0> z = Human.new => #<Human:0x3153fc0> irb(main):119:0> z.paint NoMethodError: undefined method `paint' for #<Human:0x3153fc0> from (irb):119
Singleton methods in Ruby are methods only associated with a
particular object and not available to the general class. They are
prefixed with the object name. In Listing 5, the
paint
method is specific to the object
y
and y
alone;
z.paint
results in an undefined method error.
You can figure out the list of singleton methods in an object by calling
singleton_methods
:
irb(main):120:0> y.singleton_methods => ["paint"]
There's yet another way of defining singleton methods in Ruby. Consider the code in Listing 6.
Listing 6. Yet another way of creating singleton methods
irb(main):113:0> y = Human.new => #<Human:0x319b6f0> irb(main):114:0> class << y irb(main):115:1> def sing irb(main):116:1> puts "Can sing" irb(main):117:1> end irb(main):118:1>end => nil irb(main):117:0> y.sing Can sing => nil
Listing 5 also opens interesting possibilities about adding new methods to
user-defined classes and to built-in Ruby existing classes
like String
. That's impossible in
C++
unless you have access to the source code
for the classes that you use. Look into the
String
class once more (Listing 7).
Listing 7. Ruby allows you to modify an existing class
irb(main):035:0> y = String.new("racecar") => "racecar" irb(main):036:0> y.methods.grep(/palindrome/) => [ ] irb(main):037:0> class String irb(main):038:1> def palindrome? irb(main):039:2> self == self.reverse irb(main):040:2> end irb(main):041:1> end irb(main):050:0> y.palindrome? => true
Listing 7 clearly illustrates how you can edit an existing Ruby class to
add the methods of your choice. Here, I added the
palindrome?
method to the
String
class. Ruby classes are therefore
editable at run time—a powerful property.
Now that you have some idea of Ruby's class hierarchy and singletons, let's
move on to the self
. Note that I used
self
while defining the
palindrome?
method.
Discovering self
The most common use of the self
keyword is
perhaps to declare a static method in a Ruby class, as in Listing 8.
Listing 8. Using self to declare class static methods
class SelfTest def self.test puts "Hello World with self!" end end class SelfTest2 def test puts "This is not a class static method" end end SelfTest.test SelfTest2.test
As you can see in the output from Listing 8—shown in Listing 9—you cannot invoke non-static
methods without objects. The behavior resembles
C++
.
Listing 9. Error when non-static methods are called without objects
irb(main):087:0> SelfTest.test Hello World with self! => nil irb(main):088:0> SelfTest2.test NoMethodError: undefined method 'test' for SelfTest2:Class from (irb):88
Before moving on to more esoteric uses and meanings of
self
, note that you can also define a static
method in Ruby by prefixing the class name before the method name:
class TestMe def TestMe.test puts "Yet another static member function" end end TestMe.test # works fine
Listing 10 offers a more interesting but rather
difficult-looking use of self
.
Listing 10. Using meta-class to declare static methods
class MyTest class << self def test puts "This is a class static method" end end end MyTest.test # works fine
This code defines test
as a class static method
in a slightly different way. To understand what's happening, you need to
look at the class << self
syntax in some
detail. class << self … end
creates a
meta-class. In the method lookup chain, the meta-class of an object is searched
before accessing the base class of the object. If you define a method in the
meta-class, it can be invoked on the class. This is similar to the notion
of static methods in C++
.
Is it possible to access a meta-class? Yes: Just return
self
from inside
class << self … end
. Note that in a Ruby
class declaration, you are under no obligation to put only method
definitions. Listing 11 shows the meta-class.
Listing 11. Getting hold of the meta-class
irb(main):198:0> class MyTest irb(main):199:1> end => nil irb(main):200:0> y = MyTest.new => #< MyTest:0x2d43fe0> irb(main):201:0> z = class MyTest irb(main):202:1> class << self irb(main):203:2> self irb(main):204:2> end irb(main):205:1> end => #<Class: MyTest > irb(main):206:0> z.class => Class irb(main):207:0> y.class => MyTest
Coming back to the code in Listing 7, you see that
palindrome
is defined as
self == self.reverse
. In this context,
self
is no different from
C++
. The methods in
C++
and likewise in Ruby need an object to act
upon to modify or extract state information.
self
refers to that object here. Note that
public methods can optionally be called by prefixing the
self
keyword to indicate the object on which
the method is acting, as in Listing 12.
Listing 12. Using self to invoke methods
irb(main):094:0> class SelfTest3 irb(main):095:1> def foo irb(main):096:2> self.bar() irb(main):097:2> end irb(main):098:1> def bar irb(main):099:2> puts "Testing Self" irb(main):100:2> end irb(main):101:1> end => nil irb(main):102:0> test = SelfTest3.new => #<SelfTest3:0x2d15750> irb(main):103:0> test.foo Testing Self => nil
You cannot call private methods in Ruby with the
self
keyword prefixed. For a
C++
developer, this might get pretty
confusing. The code in Listing 13 clearly shows that
self
cannot be used with private methods: The
call to the private method must only be made with an implicit object.
Listing 13. self cannot be used with private method invocation
irb(main):110:0> class SelfTest4 irb(main):111:1> def method1 irb(main):112:2> self.method2 irb(main):113:2> end irb(main):114:1> def method3 irb(main):115:2> method2 irb(main):116:2> end irb(main):117:1> private irb(main):118:1> def method2 irb(main):119:2> puts "Inside private method" irb(main):120:2> end irb(main):121:1> end => nil irb(main):122:0> y = SelfTest4.new => #<SelfTest4:0x2c13d80> irb(main):123:0> y.method1 NoMethodError: private method `method2' called for #<SelfTest4:0x2c13d80> from (irb):112:in `method1' irb(main):124:0> y.method3 Inside private method => nil
Because everything in Ruby is an object, here's what you get when you
invoke self
on the
irb
prompt:
irb(main):104:0> self => main irb(main):105:0> self.class => Object
The moment you launch irb
, the Ruby interpreter
creates the main object for you. This main object is also known as the
top-level context in the Ruby-related literature.
Enough of self
. Let's move on to dynamic methods and the
method_missing
method.
The mystery behind method_missing
Consider the Ruby code in Listing 14.
Listing 14. method_missing in action
irb(main):135:0> class Test irb(main):136:1> def method_missing(method, *args) irb(main):137:2> puts "Method: #{method} Args: (#{args.join(', ')})" irb(main):138:2> end irb(main):139:1> end => nil irb(main):140:0> t = Test.new => #<Test:0x2c7b850> irb(main):141:0> t.f(23) Method: f Args: (23) => nil
Clearly, if voodoo is your thing, Listing 14 should bring smile to your
face. What happened here? You created an object of type
Test
, and then called
t.f
with 23
as an
argument. But Test
did not have
f
as a method, and you should get a
NoMethodError
or similar error message. Ruby is
doing something fascinating here: Your method calls are being intercepted
and handled by method_missing
. The first
argument to method_missing
is the method name
that's missing—in this case, f
. The
second and last argument is *args
, which
captures the arguments being passed to f
. Where
could you use something like this? Among other options, you could easily
forward method calls to an included Module
or a
component object without explicitly providing a wrapper application
programming interface for each call in the top-level class.
Take a look at some more voodoo in Listing 15.
Listing 15. Using the send method to pass arguments to a routine
irb(main):142:0> class Test irb(main):143:1> def method1(s, y) irb(main):144:2> puts "S: #{s} Y: #{y}" irb(main):145:2> end irb(main):146:1> end => nil irb(main):147:0>t = Test.new irb(main):148:0> t.send(:method1, 23, 12) S: 23 Y: 12 => nil
In Listing 15, class Test
has a method
called method1
defined. However, instead of
calling the method directly, you make a call to the
send
method. send
is
a public method of the class Object
and
therefore available for Test
(all classes are
derived from Object
, remember). The first
argument to the send
method is a symbol or
string that denotes the method name. What can the
send
method do that in the normal course you
cannot? You can access private methods of a class using the
send
method. Of course, whether this is a good
feature to have remains debatable. Look at the code in Listing 16.
Listing 16. Accessing class private methods
irb(main):258:0> class SendTest irb(main):259:1> private irb(main):260:1> def hello irb(main):261:2> puts "Saying Hello privately" irb(main):262:2> end irb(main):263:1> end => nil irb(main):264:0> y = SendTest.new => #< SendTest:0x2cc52c0> irb(main):265:0> y.hello NoMethodError: private method `hello' called for #< SendTest:0x2cc52c0> from (irb):265 irb(main):266:0> y.send(:hello) Saying Hello privately => nil
Throw and catch are not what they seem
If you come from a C++
background like I do and
have a tendency to try writing exception-safe code, then you will
probably start feeling at home the moment you see that Ruby has the
throw
and catch
keywords. Unfortunately, throw
and
catch
have a completely different meaning in
Ruby.
Ruby typically handles exceptions using
begin…rescue
blocks. Listing 17 provides an example.
Listing 17. Exception handling in Ruby
begin f = File.open("ruby.txt") # .. continue file processing rescue ex => Exception # .. handle errors, if any ensure f.close unless f.nil? # always execute the code in ensure block end
In Listing 17, if something bad happens while trying to open the file
(maybe a missing file or an issue with file permissions), the code in the
rescue
block runs. The code in the
ensure
block always runs, irrespective
of whether any exceptions were raised. Note that the presence of the
ensure
block after the
rescue
block is optional, however. Also, if an
exception must be thrown explicitly, then the syntax is
raise <MyException>
. If you choose to
have your own exception class, you might want to derive the same from
Ruby's built-in Exception
class to take
advantage of existing methods.
The catch-and-throw facility in Ruby is not really exception handling: You
can use throw
to alter program flow. Listing 18 shows an example using
throw
and catch
.
Listing 18. Throw and catch in Ruby
irb(main):185:0> catch :label do irb(main):186:1* puts "This will print" irb(main):187:1> throw :label irb(main):188:1> puts "This will not print" irb(main):189:1> end This will print => nil
In Listing 18, when the code flow hits the throw
statement, the execution is interrupted, and the interpreter begins
looking for a catch
block that handles the
corresponding symbol. Execution is restarted from where the
catch
block ends. Look at the example of
throw
and catch
in
Listing 19: Note that you can easily spread
catch
and throw
statements across functions.
Listing 19. Exception handling in Ruby: nested catch blocks
irb(main):190:0> catch :label do irb(main):191:1* catch :label1 do irb(main):192:2* puts "This will print" irb(main):193:2> throw :label irb(main):194:2> puts "This won't print" irb(main):195:2> end irb(main):196:1> puts "Neither will this print" irb(main):197:1> end This will print => nil
Some people have gone to the extent of saying that Ruby takes the
C
goto
madness to altogether new heights
with its catch
and
throw
support. Given that there could be
several nested layers of functions with catch
blocks possible at every level, the goto
madness analogy does seem to have some steam here.
Threads in Ruby can be green
Ruby version 1.8.7 does not support true
concurrency. It really, truly does not. But you have the Thread construct
in Ruby, you say. Right you are. But that
Thread.new
does not spawn a real operating
system thread every time you make a call to the same. What Ruby supports
is green threads: The Ruby interpreter uses a single operating
system thread to handle the workload from multiple application-level
threads.
This "green thread" concept is useful while some thread is waiting on some I/O to occur, and you can easily schedule a different Ruby thread to make good use of the CPU. But this construct cannot use a modern multi-core CPU. (Wikipedia provides an excellent piece that explains what green threads are. See Related topics for a link.)
This final example (see Listing 20) proves the point.
Listing 20. Multiple threads in Ruby
#!/usr/bin/env ruby def func(id, count) i = 0; while (i < count) puts "Thread #{i} Time: #{Time.now}" sleep(1) i = i + 1 end end puts "Started at #{Time.now}" thread1 = Thread.new{func(1, 100)} thread2 = Thread.new{func(2, 100)} thread3 = Thread.new{func(3, 100)} thread4 = Thread.new{func(4, 100)} thread1.join thread2.join thread3.join thread4.join puts "Ending at #{Time.now}"
Assuming that you have the top
utility on your
Linux® or UNIX® box, run the code in a terminal, get the process
ID, and run top –p <process id>
. When
top
starts, press Shift-H to list the number of
threads running. You should see only a single thread confirming what you
knew anyway: concurrency in Ruby 1.8.7 is a myth.
All that said, green threads are not bad. They are still useful in heavy-duty I/O-bound programs, not to mention that this approach is probably the most portable across operating systems.
Conclusion
This article covered quite a few areas:
- Notions of class hierarchy in Rub
- nSingleton methods
- Deciphering the
self
keyword andmethod_missing
method - Exceptions
- Threading
Notwithstanding its quirks, Ruby is fun to program in and extremely powerful in its ability to do a lot with minimum code. No wonder, then, that large-scale applications like Twitter are using Ruby to harness their true potential. Happy coding in Ruby!
Downloadable resources
Related topics
- Read Programming Ruby: The Pragmatic Programmers' Guide (Dave Thomas, Chad Fowler, and Andy Hunt; 2nd edition), a Ruby must-read that is popularly known as the Pickaxe book.
- Check out another invaluable Ruby resource, The Ruby Programming Language [Yukihiro "Matz" Matsumoto (Ruby's creator) and David Flanagan, O'Reilly, 2008].
- Visit To Ruby From C and C++, a great site for
C/C++
programmers who want to learn Ruby. - Learn more about green threads in a good explanation on Wikipedia.
- In the developerWorks Linux zone, find hundreds of how-to articles and tutorials, as well as downloads, discussion forums, and a wealth of other resources for Linux developers and administrators.
- Follow developerWorks on Twitter.