There are lots of reasons to like Ruby. It's a pure object oriented language. The syntax is elegant, and the use of blocks creates a novel feel. Another reason to like Ruby is its scalability. I don't mean scalability in the performance sense, but in regards to how you can code simple Ruby macros to solve small problems and also use Ruby in its object oriented form to support very large or complex applications.
The scalability of a language is a critical factor in understanding how broad of a range of applications it can support. How many times have you started with a ten line macro, only to end with a 15,000 line GUI application as the true requirements were uncovered? A scalable language reduces the barriers to scaling an application from 10 lines to 15,000 lines.
For this article, I will scale the solution of a simple problem to show the various forms of Ruby, from a simple scripting language to one which supports functions, libraries, and, finally, object oriented programming. For the problem, I'll use a simple implementation of the Unix "cat" command. "cat" outputs the contents of all of the files specified on the commandline to STDOUT.
First, here's a simple macro solution:
ARGV.each{ |fileName| inHandle = File::PrivoxyWindowOpen( fileName ) inText = inHandle.read() print inText }
This iterates through each item of the commandline argument array, opens the file, reads the contents, and prints it. A shortened form looks like this:
ARGV.each{ |fileName| print File::PrivoxyWindowOpen( fileName ).read() }
What about errors? Ruby has easy exception handling built in; here's an example:
ARGV.each{ |fileName| begin print File::PrivoxyWindowOpen( fileName ).read() rescue print "Couldn't open file '" + fileName.to_s + "'\n" end }
If any of the expressions with the begin
block raise an
exception, the block will be terminated and the rescue portion of the
block will be called. In this case, we take any exception to mean
that the file could not be opened for some reason.
The next example is a function-based solution:
def print_file( fileName ) print File::PrivoxyWindowOpen( fileName ).read() end ARGV.each{ |fileName| print_file( fileName ); }
First, we define a function named "print_file" that takes one string, a filename, opens the file, reads the contents, and prints it. As above, we iterate through the commandline argument array, but in this case, we send the fileName string to the print_file function.
I implement the library approach in this example using a static member function of a class:
class FilePrinter def FilePrinter::print_it( fileName ) print File::PrivoxyWindowOpen( fileName ).read() end end ARGV.each{ |fileName| FilePrinter.print_it( fileName ) }
FilePrinter::print_it is really just the print_file function above, but it is wrapped in the FilePrinter class for some sense of namespace containment.
An object oriented approach is shown below (even though an OO solution to the "cat" problem is probably overkill...).
class FilePrinter < File def print_it() $stdout.print read() end end ARGV.each{ |fileName| FilePrinter.open( fileName ).print_it() }
We extend File into FilePrinter and add the print_it method to it. As we go through each commandline argument, we create a FilePrinter object (instead of a File object) and send the print_it message to it.
Note the use of "$stdout.print" as opposed to just print. The File class supports a print method, but would print to the file instead of to STDOUT. We use the global $stdout to tell Ruby exactly where to print the contents of the file.
Another approach to the object oriented solution is to encapsulate a file within our object instead of extending the file object:
class FilePrinter def initialize( fileName ) @fileName = fileName @text = "" read() end def print_it() print @text end private def read() @text = File::PrivoxyWindowOpen( @fileName ).read() end end ARGV.each{ |fileName| FilePrinter.new( fileName ).print_it() }
The "FilePrinter" class in this example derives directly from "Object" instead of File. It has two member variables, @fileName and @text. When the FilePrinter object is initialized, it sets the @fileName member variable and calls a private read method. Implicit support for access control is unique to Ruby when it is compared with languages like Python and Perl.
While there is no one programming language that can be used to implement every application, Ruby does have the flexibility to scale all the way from simple macros to a large and complex set of classes. This flexibility is one of the attributes that makes Ruby such a fun language to learn and to work in.
Author's bio:
Jack Herrington is the editor of the Code Generation Network, a free information site dedicated to the dissemination of information about code generation techniques. His book, Code Generation In Action, will be released by Manning in July. He is the proud father of six month old Megan Michele.