Ruby, an Introduction
Jim Menard, jim@jimmenard.com
July 10, 2001


Contents

Introductions

Me

``But enough about me ...what do you think about me?''

Ruby

A pure-OO scripting language

History and Pedigree

Why Ruby?

Why Not?

Language Features

There are too many to list. I'll try anyway.

Comparison With Other Languages

Much of this section is stolen from the Ruby site (see References below).

Java

Perl

Smalltalk

Python

Code Examples

Here we have four code examples. The first three are relatively simple ``textbook'' examples that perform the same task in Java, C++, Smalltalk, Perl, and Ruby. The last is a slightly more complex example in Ruby only.

The Smalltalk code below was written using Squeak, a free implementation of Smalltalk. Sorry, but there are no Python examples. I don't know that language well enough. ObPython: ``My hovercraft is full of eels.''

A class with a few simple methods

The first example defines the same class in five different languages. In each, we define the class, create an instance, and print the string representation of the instance.

The example class is a Song. It has has a constructor, two instance variables, accessor methods (``getters'' and ``setters''), and a method for representing instances as strings.


     1	// ================
     2	// Java
     3	
     4	public class Song {
     5	
     6	    protected String name;
     7	    protected int lengthInSeconds;
     8	
     9	    Song(String name, int len) {
    10	        this.name = name;
    11	        lengthInSeconds = len;
    12	    }
    13	
    14	    public String getName() { return name; }
    15	    public void setName(String str) { name = str; }
    16	
    17	    public int getLengthInSeconds()
    18	        { return lengthInSeconds; }
    19	    public void setLengthInSeconds(int secs)
    20	        { lengthInSeconds = secs; }
    21	
    22	    public String toString() {
    23	        return name + " (" + lengthInSeconds + " seconds)"
    24	    }
    25	
    26	    // Create and print
    27	    public void main(String[] args) {
    28	        s = new Song("name", 60);
    29	        System.out.println(s);
    30	    }
    31	}
    32	
    33	// ================
    34	// C++
    35	
    36	// Song.h
    37	#ifndef Song_h
    38	#define Song_h
    39	
    40	#include <iostream>
    41	#include <string>
    42	
    43	class Song {
    44	public:
    45	    Song(const char * nameStr = 0, int len = 0)
    46	        : name(nameStr ? nameStr : ""), 
    47	          lengthInSeconds(len) {}
    48	
    49	    // The default copy constructor, destructor, and
    50	    // operator= are all acceptable. I'm glad, 'cause
    51	    // it's a pain to have to write them.
    52	
    53	    const char * getName() const { return name.data(); }
    54	    void setName(const char *str) { name = str; }
    55	
    56	    int getLengthInSeconds() const
    57	        { return lengthInSeconds; }
    58	    void setLengthInSeconds(int len)
    59	        { lengthInSeconds = len; }
    60	
    61	protected:
    62	    string name;
    63	    int lengthInSeconds;
    64	
    65	    friend ostream &
    66	        operator<<(ostream &out, const Song &s);
    67	};
    68	
    69	ostream & operator<<(ostream &out, const Song &s) {
    70	    return out << s.name << " ("
    71	               << s.lengthInSeconds << " seconds)";
    72	}
    73	
    74	#endif Song_h
    75	
    76	// Song.cpp
    77	#include "Song.h"
    78	
    79	main()
    80	{
    81	    Song s("name", 60);
    82	    cout << s << endl;
    83	}
    84	
    85	" ================"
    86	" Smalltalk"
    87	
    88	Object subclassNamed: #Song
    89	    instanceVariables: 'name lengthInSeconds'
    90	    classVariables: ''
    91	"..."
    92	
    93	name
    94	    ^ name
    95	
    96	name: aString
    97	    name := aString
    98	
    99	lengthInSeconds
   100	    ^ lengthInSeconds
   101	
   102	lengthInSeconds: aNumber
   103	    lengthInSeconds := aNumber
   104	
   105	printOn: aStream
   106	    aStream nextPutAll: name.
   107	    aStream nextPut: $(.
   108	    aStream nextPutAll: lengthInSeconds printString.
   109	    aStream nextPutAll: ') seconds'
   110	
   111	"Create and print a song.
   112	Copy this code into a workspace and execute it."
   113	s := Song new name: 'name'; lengthInSeconds: 60.
   114	Transcript show: s asString; cr.
   115	
   116	# ================
   117	# Perl
   118	
   119	package Song;
   120	
   121	sub new {
   122	    my($class, $name, $len) = @_;
   123	    my $self = {};
   124	    $self->{'name'} = $name;
   125	    $self->{'lengthInSeconds'} = $len;
   126	    bless $self, class;
   127	    return $self;
   128	}
   129	
   130	sub name {
   131	    my($self) = shift;
   132	    if (@_) { $self->{'name'} = shift }
   133	    return $self->{'name'};
   134	}
   135	
   136	sub lengthInSeconds {
   137	    my($self) = shift;
   138	    if (@_) { $self->{'lengthInSeconds'} = shift }
   139	    return $self->{'lengthInSeconds'};
   140	}
   141	
   142	sub toString {
   143	    my($self) = shift;
   144	    return $self->name() . "(" . $self->lengthInSeconds()
   145	        . ") seconds";
   146	}
   147	
   148	# Create and print
   149	$s = Song->new('name', 60);
   150	print $s->toString() . "\n";
   151	
   152	# ================
   153	# Ruby
   154	
   155	class Song
   156	
   157	    # Not only declare instance variables (which is
   158	    # unnecessary) but also create accessor methods
   159	    # (getters and setters). "attr_reader" creates
   160	    # just getters and "attr_writer" creates just
   161	    # setters.
   162	    attr_accessor :name, :lengthInSeconds
   163	
   164	    # The constructor, sort of. This method is called
   165	    # by the class method "new".
   166	    def initialize(name, len)
   167	        @name = name
   168	        @lengthInSeconds = len
   169	    end
   170	
   171	    def to_s
   172	        return "#{name} (#{lengthInSeconds} seconds)"
   173	    end
   174	end
   175	
   176	# Create and print. Only run this code if this file
   177	# is being executed directly, else ignore it.
   178	if $0 == __FILE__
   179	    s = Song.new('name', 60)
   180	    puts s
   181	end

Iterating over a collection

In this example we will create a linear collection of Song instances, add a song, and iterate over the collection.

The Java version uses the J2EE collection classes. The C++ version uses the Standard Template Library (STL) for its vector and iterator classes.


     1	// ================
     2	// Java
     3	
     4	import java.util.*;
     5	
     6	ArrayList songs = new ArrayList();
     7	songs.add(new Song("name", 60));
     8	
     9	for (Iterator iter = songs.iterator(); iter.hasNext(); ) {
    10	    Song s = (Song)iter.next();        // Yuck!
    11	    // Do something with s...
    12	}
    13	
    14	// ================
    15	// C++
    16	
    17	#include <vector>
    18	#include "Song.h"
    19	
    20	main()
    21	{
    22	    vector<Song> songs;
    23	    songs.push_back(Song("name", 60));
    24	
    25	    vector<Song>::iterator iter(songs.begin());
    26	    for (; iter != songs.end(); ++iter) {
    27	        Song s = *iter;
    28	        // Do something with s...
    29	    }
    30	}
    31	
    32	" ================"
    33	" Smalltalk"
    34	
    35	songs := OrderedCollection new.
    36	songs add: (Song new name: 'name'; lengthInSeconds: 60).
    37	songs do: [ :s |
    38	    "Do something with s..."
    39	].
    40	
    41	# ================
    42	# Perl
    43	
    44	@songs = ();
    45	push(@songs, Song->new("name", 60));
    46	foreach $s (@songs) {
    47	    # Do something with $s...
    48	}
    49	
    50	# ================
    51	# Ruby
    52	
    53	songs = []
    54	songs << Song.new("name", 60)
    55	songs.each { | s |
    56	    # Do something with s...
    57	}

Reading a file

In this example, we will manipulate a comma-delimited data file by opening it, reading each line, separating each line into columns, and printing the columns as a SQL INSERT statement.

For simplicity's sake, we will assume that the data does not contain any comma characters. This code does handle ``empty'' columns (two consecutive commas). Because of this, we often can't use the most obvious or clean solution (for example, a StringTokenizer in Java or strtok() in C++).

I have Ruby, Perl, and Smalltalk classes that handle Excel comma- and tab-delimited data--quotes and all--that are yours for the asking. A mini state machine is necessary to handle the quotes and delimiter chars properly.


     1	// ================
     2	// Java
     3	
     4	import java.io.*;
     5	import java.util.*;
     6	
     7	public class DataFileReader {
     8	
     9	public static void main(String[] args) {
    10	    DataFileReader dfr = new DataFileReader();
    11	    dfr.readFile(args[0]);
    12	}
    13	
    14	public void readFile(String fileName) {
    15	    try {
    16	        BufferedReader in =
    17	            new BufferedReader(new FileReader(fileName));
    18	        String line;
    19	        while ((line = in.readLine()) != null) {
    20	            Collection list = splitIntoColumns(line);
    21	            printAsInsertStatements(list);
    22	        }
    23	    }
    24	    catch (FileNotFoundException fnfe) {
    25	        System.err.println(fnfe.toString());
    26	    }
    27	    catch (IOException ioe) {
    28	        System.err.println(ioe.toString());
    29	    }
    30	}
    31	
    32	public Collection splitIntoColumns(String line) {
    33	    ArrayList array = new ArrayList();
    34	
    35	    // Using StringTokenizer here is almost useless. It
    36	    // either skips or returns multiple adjacent commas,
    37	    // but doesn't report the emptiness between as empty
    38	    // data columns.
    39	    int pos = line.indexOf(",");
    40	    while (pos != -1) {
    41	        array.add(line.substring(0, pos));
    42	        line = line.substring(pos + 1);
    43	        pos = line.indexOf(",");
    44	    }
    45	    if (line.length() > 0)
    46	        array.add(line);
    47	
    48	    return array;
    49	}
    50	
    51	public void printAsInsertStatements(Collection list) {
    52	    System.out.print("insert into table values (\n\t");
    53	    boolean first = true;
    54	
    55	    Iterator iter = list.iterator();
    56	    while (iter.hasNext()) {
    57	        String col = (String)iter.next();
    58	
    59	        if (first) first = false;
    60	        else System.out.print(",\n\t");
    61	
    62	        System.out.print("'" + col + "'");
    63	    }
    64	    System.out.println("\n)");
    65	}
    66	
    67	}
    68	
    69	// ================
    70	// C++
    71	
    72	#include <string>
    73	#include <iostream>
    74	#include <fstream>
    75	#include <vector>
    76	
    77	static const int BUFSIZ = 1024;
    78	
    79	vector<string> splitIntoColumns(char *line);
    80	void printAsInsertStatements(vector<string> &list);
    81	
    82	void
    83	main(int argc, char *argv[])
    84	{
    85	    ifstream in(argv[1]);
    86	    char line[BUFSIZ];
    87	    while (!in.eof()) {
    88	        in.getline(line, BUFSIZ);
    89	        if (strlen(line) == 0)
    90	            break;
    91	        vector<string> list = splitIntoColumns(line);
    92	        printAsInsertStatements(list);
    93	    }
    94	}
    95	
    96	vector<string>
    97	splitIntoColumns(char *line)
    98	{
    99	    vector<string> list;
   100	
   101	    // Using strtok() here is useless. It skips multiple
   102	    // adjacent commas and doesn't report the emptyness
   103	    // between as empty data columns.
   104	    char *nextComma = index(line, ',');
   105	    while (nextComma != 0) {
   106	        list.push_back(string(line, nextComma - line));
   107	        line = nextComma + 1;
   108	        nextComma = index(line, ',');
   109	    }
   110	    if (strlen(line) > 0)
   111	        list.push_back(string(line));
   112	
   113	    return list;
   114	}
   115	
   116	void
   117	printAsInsertStatements(vector<string> &list)
   118	{
   119	    cout << "insert into table values (" << endl << "\t";
   120	    bool first = true;
   121	
   122	    vector<string>::iterator iter(list.begin());
   123	    for ( ; iter != list.end(); ++iter) {
   124	        string col = *iter;
   125	
   126	        if (first) first = false;
   127	        else cout << "," << endl << "\t";
   128	
   129	        cout << "'" << col << "'";
   130	    }
   131	    cout << endl << ")" << endl;
   132	}
   133	
   134	
   135	" ================"
   136	" Smalltalk"
   137	
   138	Object subclass: #DataFileReader
   139	    instanceVariableNames: ''
   140	    classVariableNames: ''
   141	    poolDictionaries: ''
   142	    category: 'Jim-Utils'
   143	
   144	readFile: fileName
   145	    | inStream outStream list |
   146	    inStream :=
   147	        CrLfFileStream readOnlyFileNamed: fileName.
   148	    outStream :=
   149	        FileStream newFileNamed: fileName, '.out'.
   150	    [inStream atEnd] whileFalse: [
   151	        list := self parseNextLine: inStream.
   152	        self output: list onStream: outStream.
   153	    ].
   154	
   155	parseNextLine: aStream
   156	    "Read columns in line and shove them into a list.
   157	     Return the list."
   158	    | list line lineStream |
   159	    list := OrderedCollection new. "Create empty list."
   160	    line := aStream nextLine. "Read line from input file."
   161	
   162	    "Read columns in line and shove into list."
   163	    lineStream := ReadStream on: line
   164	                           from: 1
   165	                             to: line size.
   166	    [lineStream atEnd] whileFalse: [
   167	        list add: (lineStream upTo: $,).
   168	    ].
   169	    ^ list
   170	
   171	output: list onStream: outStream
   172	    "Output insert statement."
   173	    outStream nextPutAll: 'insert into table values (
   174	    '''.
   175	    list do: [ :col |
   176	        outStream nextPutAll: col
   177	    ] separatedBy: [
   178	        outStream nextPutAll: ''',
   179	    '''.
   180	    ].
   181	    outStream nextPutAll: '''
   182	)
   183	'.
   184	
   185	"Finally, create and use one of these suckers. Type
   186	 this in a workspace, select it, and choose 'do it'.
   187	 Actually, this code can be anywhere, including in
   188	 a comment. Just select it and execute it."
   189	DataFileReader new readFile: 'foo.dat'.
   190	
   191	# ================
   192	# Perl
   193	
   194	#! /usr/bin/perl
   195	
   196	open(FILE, $ARGV[0]) ;
   197	while (($line = <FILE>) ne '') {
   198	    chomp($line);
   199	    print "insert into table values (\n\t'";
   200	    print join("',\n\t'", split(/,/, $line));
   201	    print "',\n)\n";
   202	}
   203	close(FILE);
   204	
   205	# ================
   206	# Ruby
   207	
   208	#! /usr/local/bin/ruby
   209	
   210	# File is a subclass of IO. "foreach" is a class method
   211	# that opens the file, executes the block of code once
   212	# for each line, and closes the file.
   213	IO.foreach(ARGV[0]) { | line |
   214	    line.chomp!()
   215	    puts "insert into table values ("
   216	    print "\t'"
   217	    print line.split(/,/).join("',\n\t'")
   218	    puts "'\n)"
   219	}

Parsing XML

This final example is Ruby-only. It shows a ``real'' script performing a slightly more complex task: parsing XML and capturing and printing any errors returned by the parser. The XML isn't processed by this script.

This is one of the example scripts that comes with NQXML.


     1	#! /usr/bin/env ruby
     2	#
     3	# usage: parseTestStream.rb file_or_directory
     4	#
     5	# This script runs the streaming parser over the specified
     6	# file or all .xml files within the specified directory.
     7	#
     8	# If an NQXML::ParserError is seen, an error message is
     9	# printed and parsing stops.
    10	#
    11	
    12	# Start looking for NQXML classes in the directory above
    13	# this one. This forces us to use the local copy of NQXML,
    14	# even if there is a previously installed version out
    15	# there somewhere. (Since this script comes as an example
    16	# with NQXML, we want to make sure we are using the latest
    17	# version that's right here, not one previously installed
    18	# elsewhere.)
    19	$LOAD_PATH[0, 0] = '..'
    20	
    21	require 'nqxml/streamingparser'
    22	
    23	def testParser(file)
    24	    print "Parsing file #{file}..."
    25	    $stdout.flush()
    26	    file = File.open(file, 'r')
    27	    parser = NQXML::StreamingParser.new(file)
    28	    begin
    29	        # Just let parser run through the XML
    30	        parser.each { | entity | }
    31	    rescue NQXML::ParserError => ex
    32	          puts "\n  NQXML parser error, line #{ex.line}" +
    33	            " column #{ex.column}: #{$!}"
    34	        return
    35	    rescue
    36	        puts "\n  Non-parser error: #{$!}"
    37	        return
    38	    ensure
    39	        # An "ensure" is like Java's "finally" clause:
    40	        # No matter what happens, this code gets executed.
    41	        # Instead of using an "ensure" we could have used
    42	        # a block as an argument to File.open. At the end
    43	        # of the block the file would have been closed
    44	        # automagically.
    45	        file.close() unless file.nil?
    46	    end
    47	    puts 'OK'
    48	end
    49	
    50	# Start of main code
    51	
    52	# If we have a command-line argument, grab it and
    53	# remove the trailing slash (if any). If no argument
    54	# is specified, use '.' (the current directory).
    55	DIR = ARGV[0] ? ARGV[0].gsub(/\/$/, '') : '.'
    56	
    57	if File.directory?(DIR)
    58	    Dir.entries(DIR).each { | f |
    59	        testParser("#{DIR}/#{f}") if f =~ /\.xml$/
    60	    }
    61	else
    62	    testParser(DIR)
    63	end

Practical Ruby Experience

Tools

Advantages and Disadvantages

Advantages

Disadvantages

Resources

About this document ...

This document was generated using the LaTeX2HTML translator Version 99.1 release (March 30, 1999)

Copyright © 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The command line arguments were:
latex2html -no_navigation -split 0 talk.tex

The translation was initiated by on 2001-07-13



2001-07-13