from The Rational Edge: Take a closer look at Ruby, a dynamic programming language that is growing rapidly in popularity. Explore its intriguing features and learn what programmers are doing with it. This content is part of the The Rational Edge.

Share:

Gary Pollice, Professor of Practice, Worcester Polytechnic Institute

Author photoGary Pollice is a professor of practice at Worcester Polytechnic Institute, in Worcester, MA. He teaches software engineering, design, testing, and other computer science courses, and also directs student projects. Before entering the academic world, he spent more than thirty-five years developing various kinds of software, from business applications to compilers and tools. His last industry job was with IBM Rational Software, where he was known as "the RUP Curmudgeon" and was also a member of the original Rational Suite team. He is the primary author of Software Development for Small Teams: A RUP-Centric Approach, published by Addison-Wesley in 2004. He holds a B.A. in mathematics and an M.S. in computer science.



15 July 2007

Also available in Chinese

rubiesLast month I looked at dynamic languages and some of their capabilities. I took a very superficial look at some of the common features and differences in two of the more popular languages, Perl and Python. This month I'll take a deeper look at a language that has been gaining popularity in the last few years, Ruby. I'll examine some of the interesting features that Ruby offers and the type of programs people are creating with it.

Why Ruby?

Since it's been around for over a decade, why has Ruby become so popular lately? It has jumped more in a year in the Tiobe index than any of the other major languages.1 There are several reasons, I think, that have led to the surge in interest. First of all, Ruby has been adopted as the dynamic language of choice by many of the people who really love programming in Smalltalk. This is a relatively small number of people compared to the entire programming population, but they are fanatically devoted to the language and almost all of the Smalltalk mavens I've met are expert object-oriented programmers. Many of these Smalltalk fans have been active in the Agile movement, and promote Smalltalk along with their Agile testimonials. Their voices are certainly heard in many venues.

A wide choice of IDEs

I'm a toolsmith and like to use software tools to build software. There are quite a few development environments for Ruby. Regular readers of this column will not be surprised to know that there is an Eclipse plug-in for Ruby that I use quite a bit.2 But, it's not the only IDE that I use when working in Ruby. I have found FreeRIDE (http://freeride.rubyforge.org/wiki/wiki.pl) to be very good, especially on my Linux system. The SciTE editor that is bundled with the Windows MSI installer works well on Windows, and Komodo works well on all platforms (http://www.activestate.com/products/komodo_ide/).3 This is just a sampling of the environments I have used. If you search for Ruby IDEs on the Web, you will find many more, so there should be one that suits your needs.

The fun factor

Another reason for using Ruby is that it really is fun. One of the goals that Yukihiro "Matz" Matsumoto, Ruby's creator, gave for creating the language is to make programming easy and fun. Once you begin to use Ruby, you seem to recapture some of the joy and wonder that you had when you started your programming career (so many years ago in my case that I shudder to think about it sometimes).

For me, Ruby just doesn't "feel" like a scripting language, which is what I've mostly used Perl and Python for; although I haven't done much programming in Python. And, while JavaScript and PHP have been used extensively in building Web applications, they still feel like scripting languages. Ruby just gives me more of a feeling that I'm working with a finely crafted tool.

Let me try to explain this "fun factor" a little better. Some languages are very powerful, but they're like bulldozers. They do a great job at what they're good for, but when you're out for a Sunday drive and want performance and elegance, a bulldozer isn't what you want to be driving. Many languages provide a lot of features but they don't seem to fit together well. These "kitchen sink" languages try to do everything for everyone. They provide many ways to do the same thing. I don't find that with Ruby. Ruby has a few such features as well, but not as many as some languages. I tend to like a little minimalism in my languages since I find this makes them easy to learn.

The Principle of Least Surprise

One other thing that makes me very happy with Ruby is the Principle of Least Surprise. Once you learn the basics, things just seem to work the way you think they should. Sure, there are times when you have to put parentheses around arguments or an expression doesn't evaluate the way you planned, but there's more to this principle than that. Let's look at a couple of examples. For the simple examples, I'll show how to type them into an interactive Ruby shell program, called irb.4

Using Ruby

Java programmers know that there is a Reflection API that lets you look "under the covers" of classes and objects. Let's say you have some Java object and you want to know what messages it responds to (the methods), and print their names out in ascending order. The following Java code (Figure 1) does this:

import java.lang.reflect.*;
import java.util.*;

public class MethodViewer
{
    public static void main(String[] args)
    {
        LinkedList<String> l = new LinkedList<String>();
        Method[] methods = "hello".getClass().getMethods();
        for (Method m : methods) {
            l.add(m.getName());
        }
        String[] names = new String[l.size()];
        names = l.toArray(names);
        Arrays.sort(names);
        for (String m : names) {
            System.out.println(m);
        }
    }
}

Figure 1: Printing a sorted list of an object's methods in Java

There may be some better coding tricks to shorten this a bit, but I like to keep my code somewhat readable.

Now, let's look at how I might do this in Ruby:

puts "hello".methods.sort

That's it! Let's tear this apart a bit. The puts is simply a "print string" command. It prints a string on its own line. The next part of the expression is more interesting. The three parts, separated by '.' make up the arguments to puts. This will seem familiar to most readers who have programmed in either an object-oriented language or a functional language. This expression is a composition of functions. First the methods method is invoked on the string object "hello." That returns an array of method names that you can call on the "hello" string. The sort method is then called to sort the method names according to their default ordering. You might wonder what class's sort method is invoked here. That would be the Array class. Ruby, like other object-oriented languages, supports polymorphism. How do I know that it's the Array class whose sort method is called? I wrote another expression to make sure:

"hello".methods.class

The result of the class method is an object of type Class, but since I'm working from the irb tool, this result of an expression is printed out and I get the string representation of it. That is, the to_s method is called on the Class object. Like Java's toString method, if the object's class has defined a to_s method, it is used; otherwise, it goes up the inheritance tree until it finds one and uses that. The Object class has a default to_s method so there always will be some result.

To me, this all just seems to make sense and I think it's readable. There are no special variables of the kind that good Perl programmers use regularly.5 But these are fairly simple examples, just to give you a flavor for the way Ruby just does what you want it to. Let's look at a slightly larger example, just to show you how you can apply some of the programming principles you already know and write a useful program in a short time.

Generating test data

All of the dynamic languages are excellent for generating data. Most often, you are massaging some sort of textual items and writing out many different records. If you're following along from last month, you might remember that I included a PDF that described a task (writing a grading program) that I gave three of my former students. Here's the description of the input:6

The first record contains the number, n, of distinct type of assignments that contribute to the final grade; for example, quizzes, tests, exams, and so on. The next n records contain the name of each type of assignment, a one-character abbreviation, and the number of points that the assignments of this type contribute to the course grade (you can think of this as the percentage of the final grade). This number is always an integer.
The remaining records contain the actual grades. Each record is of the form:
T SID G P
where T = the type of assignment (the one letter abbreviation from above), SID = the student ID, G = the grade (total number of points awarded for that assignment), and P = the total number of points that the assignment is worth. Each field is separated by one (or more) spaces.

A sample of what the input looks like would be:

4
Quiz Q 25
Homework H 25
Exam E 25
Project P 25
H 125 12 15
H 137 5 15
H 104 15 15
Q 147 9 10
...

Testing the code

In order to make sure the programs worked, I needed some input, and I wanted a fairly good-sized data set. I decided to write a Ruby program to perform the task. The following describes my thinking, how I implemented the code, and what the code looks like.

First, even though Ruby lets you write scripts, I try to place as much as I can into classes. If you define methods but do not put them in a class they are automatically added to the Object class. (Remember, everything is an object in Ruby). Now, for my example, I wanted to have the class, but didn't want to have to create an object of the class. I just wanted to put the methods together that belonged together. So, all of my methods were class methods, like static methods in Java. Since I work top-down, I started by writing a makeGradeData method in my GenerateGradeData class.7 The initial code looked like this (Figure 2):8

class CreateGradeData
  def CreateGradeData.makeGradeData(nStudents, types)
    puts types.size
  end
end

Figure 2: First method in the grade test data generation program

I wasn't sure how I would represent the parameters needed for each type of assignment, but I knew I would need to say how many students I wanted and the types of assignments there are. Notice that methods are defined between the keywords: def and end. You will also notice that I don't need to put statement separators, like semicolons, in between statements except in certain cases. Putting the class name as a prefix to the method name indicates that the method is a class method.

The program in Figure 2 doesn't do much, but it does print out the first line of the test data: the number of types of assignments. At this point, I can test it by adding the following line after the class definition in my file:

 CreateGradeData.makeGradeData(5, [1, 2, 3])

It really doesn't matter what I enter for the number of students, or even what type of object I put there, since it's not used yet in the program. And, the only thing I do with the types array is take its size, so I can add any type of object that has a size. In this case, it's an array of three objects. When I run the script, it prints out the number '3.' We're making progress.9

In order to write the next n lines of test data, I needed to know each assignment type's name, its abbreviation, and the total points for that type. So, I know that each element of the types array must have these three items. I decided to make each element itself an array. Now I change the makeGradeData method by adding one line (Figure 3):

class CreateGradeData
  def CreateGradeData.makeGradeData(nStudents, types)
    puts types.size
    types.each {|i| puts i[0] + " " + i[1] + " " + i[2].to_s}
  end
end

Figure 3: Printing out the assignment types.

The additional line exhibits one of my favorite features of Ruby, the code block. Methods can have associated code blocks. In this case, the each method has the block of code between the braces associated with it.10 At some point in its execution, the each method executes the yield instruction giving it an argument that happens to be the next element of the list in the case of the Array class's each method. This argument is bound to a parameter in the block, indicated by |i|. Every time the each method yields, the block of code is executed and prints out a string consisting of the first three elements of the assignment type, i. Remember, the type is an array, so I can index into it. The third element of the array is the number of points for this assignment type, so I have to invoke the to_s method to convert that integer into a string.

Now, I change the way I call makeGradeData, to this:

t = [
  ["Quizzes", "Q", 10],
  ["Exams", "E", 25],
  ["Homework", "H", 25],
  ["Project", "P", 30],
  ["Participation", "C", 10]
];
CreateGradeData.makeGradeData(25, t)

My output now looks like this:

5
Quizzes Q 10
Exams E 25
Homework H 25
Project P 30
Participation C 10

Next, it's probably a good idea to create the student IDs that I need. I just have to create some set of nStudents numbers. This should probably be fairly random, although I could pass in an array of numbers as well, but I'd like to make the program do the work. So, I'll write a method, makeStudents, that I can call with the number of students I need and it will return an array of student IDs. The code for this is in Figure 4.

def CreateGradeData.makeStudents(nStudents)
    result = []
    (0..(nStudents-1)).each do |i|
      x = 10000 + rand(90000)
      while result.include?(x) do
        x = 10000 + rand(90000)
      end
      result[i] = x
    end
    return result
  end

Figure 4: Creating a random set of student IDs

This is another class method. The first thing I do is create an array to hold the student IDs. Now I need to generate the appropriate number of student IDs. I decide that the student ID should be five digits, from 10000 to 99999. Ruby gives me just what I need. First, I make a Range object: (0..(nStudents-1)). This is an object that represents the range of integers from 0 to the value nStudents-1. Since I've asked for 25 students in the example input above, this is the range 0..24. And, since it's an object I can send it a message. In this case, I send the each message with the block of code between the do and end and have the next number in the range bound to the i parameter. I need i to start at 0 because I want to use it as the index to the resulting array of student IDs. I get a random number between 0 and 89999 inclusively and add 10000 to it to get a number in the desired range. Next I make sure that the number isn't already in the set of IDs and when I'm sure it's not, I add it to the array.

Notice the way the name of the method for checking whether the result array includes the number. The question mark at the end of the method name means that the method is testing for something and will return True or False. This is a convention, not a requirement.

The only thing left to do is generate the individual grades. I make one last change to makeGradeData, shown in Figure 5. For each assignment type I call the makeGrades method with the assignment type and the array of student IDs as arguments.

def CreateGradeData.makeGradeData(nStudents, types)
    puts types.size
    types.each {|i| puts i[0] + " " + i[1] + " " + i[2].to_s}
    studentIds = CreateGradeData.makeStudents(nStudents)
    types.each {|i| CreateGradeData.makeGrades(i, studentIds)}
  end

Figure 5: The final makeGradeData method

The only issues left to tackle are how to identify a range of grades to generate for each assignment, and how many assignments of each type to generate. I decide again to use nested arrays in the types argument. The structure of each element of the types array is:

  • The name of the assignment type
  • Its abbreviation
  • The number of points towards the final grade
  • An array of assignments of this type, in which each element is a two-element array that contains the high and low values to generate for the assignment

Now the complete input and call to makeGradeData is the following:

t = [
  ["Quizzes", "Q", 10, 
	[[0, 10], [0, 10], [0, 10], [0, 10], [0, 10], [0, 10]]],
  ["Exams", "E", 25, [[50, 100],[40, 100]]],
  ["Homework", "H", 25, 
	[[5, 25], [10, 50], [15, 50], [8, 20], [0, 15], [5, 20]]],
  ["Project", "P", 30, [[40, 75], [5, 25]]],
  ["Participation", "C", 10, [[0, 10]]]
];
CreateGradeData.makeGradeData(25, t)

There are two additional methods that I created to finish the test data generator. These are shown in the Figure 6.

def CreateGradeData.makeGrades(type, sIds)
    a = type[1]     # abbreviation 
    g = type[3]     # array of generating parameters
    g.each do |i|
      sIds.each{|s| CreateGradeData.generateGrade(s, a, i)}
    end
  end

  def CreateGradeData.generateGrade(sId, a, g)
    grade = g[0] + rand(g[1]-g[0]+1)
    puts a + " " + sId.to_s + " " + grade.to_s + " " + g[1].to_s
  end

Figure 6: Remaining methods in the program

In the makeGrades method I use a code block nested inside of another code block. This is a nested iteration. For each assignment (in g) the code loops through the student IDs and generates a grade within the appropriate bounds.

Altogether the program contains just thirty lines of code (some of which are just end statements). It's also, in my opinion, quite readable. You can take a look at the program, with comments.

Conclusion

This article just scratches the surface of some of Ruby's features. Many programmers have told me how they've gotten to really like programming again since they started programming in Ruby. For that alone I think we owe Matz a big "thank you." But there's so much more to Ruby than what I could show you here. I hope you take some time to look at some of the references below.

I was also planning on talking about the Rails framework for developing Web applications in Ruby, but that will have to wait for next time.

References

Programming Ruby, 2ed., David Thomas and Andrew Hunt, Pragmatic Bookshelf, 2004, ISBN 0974514055.

Ruby Cookbook, Lucas Carlson and Leonard Richardson, O'Reilly Media, Inc. 2006, ISBN 0596523696.

Programming Ruby, an online version of the book above, but you'll want the paper book. This is a good resource when you're at the computer. http://www.ruby-doc.org/docs/ProgrammingRuby/

Ruby-doc.org, An online resource for everything about Ruby. http://www.ruby-doc.org/

Try Ruby!, An interactive, on-line tutorial for Ruby. http://tryruby.hobix.com/

Learning Ruby, A nice, extended tutorial by Daniel Carrera. http://www.math.umd.edu/~dcarrera/ruby/0.3/

Notes

1 See http://www.tiobe.com.

2 Information about the Ruby Development Tools (RDT) plug-in can be found at http://rubyeclipse.sourceforge.net/. There is also an article on developerWorks by Neal Ford about the RDT.

3 Komodo works for multiple dynamic languages so it's worth looking at if you're going to be doing a lot of work with different languages. (http://www.ibm.com/developerworks/opensource/library/os-rubyeclipse/#resources). There are also Ruby plug-ins for Python (http://pydev.sourceforge.net/) and for Perl (http://e-p-i-c.sourceforge.net/).

4 The irb program, or a similar interactive shell, comes bundled with most Ruby implementations, and some of the IDEs mentioned have irb built into them.

5 Ruby has many of the special variables, but while it provides them for people who want them, there are other, more readable, ways to do the same job. Most of the Ruby code I've looked at eschews the use of too many special variables.

6 This is taken from the SPEC file in the archive attached to last month's column: http://public.dhe.ibm.com/software/dw/rationaledge/jun07/pollice_june07.zip

7 Ruby has some strict naming conventions. Class names must begin with capital letters, methods begin with lowercase letters, instance variables are prefixed with '@' and class variables are prefixed with '@@.' At first these rules were a minor annoyance for me, but every language has some restrictions that you must follow. I got over this quickly.

8 I do have comments in the code, but am leaving them out for this discussion.

9 There is a unit test framework in Ruby that is similar to JUnit for Java. I usually use it, but for this example I just wanted to make sure it did what I expected without worrying about regressions.

10 If the block of code requires more than one line, you put it between the keywords def and end instead of inside of the braces.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Rational software on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Rational
ArticleID=239636
ArticleTitle=Ruby: A gem of a language
publish-date=07152007