TweetFollow Us on Twitter

Introduction to Ruby for Mac OS X

Volume Number: 19 (2003)
Issue Number: 3
Column Tag: Mac OS X

Introduction to Ruby for Mac OS X

The Principle of Least Surprise

by Jim Menard

Introducing Ruby

Yukihiro "Matz" Matsumoto was looking for an object oriented scripting language. Python wasn't OO enough. Perl's "Swiss Army Chainsaw" approach was too messy. When he didn't find what he was looking for, Matz decided to write his own language. His goal was to create one good enough to replace Perl. Ruby was born in February of 1993 and first released to the public in 1995. Today, Ruby is more popular than Python in Japan. Ruby started hitting the shores of the United States around 2000.

Here's how I think of Ruby. Take Smalltalk, where everything--even a number--is an object. Make it a file-based, interpreted scripting language. Give it a syntax familiar to Perl and Python users. Add the best features of many different languages such as regular expressions, iterators, block closures, garbage collection, and dynamic typing. Abstract many features into classes like Regexp and mixin modules like Enumerable. Deliver it with a mature, useful set of library modules. Finally, add a helpful and responsive developer community. The result is a language that is easy to learn, powerful, and a joy to use.

I've fallen in love with Ruby. It's a pure object oriented scripting language. It's simple, consistent, and powerful. It stays out of my way and makes me more productive. Best of all, it comes pre-installed with Jaguar. Open a terminal window and type ruby -v. See? If you aren't yet running Jaguar, you can download Ruby from http://www.ruby-lang.org and compile and install it yourself.

To attempt to illustrate why I like Ruby so much, let's take a look at the same class written in Java, Objective-C, Perl, and Ruby. Listing 1 defines a Song class with two instance variables, accessor (getter and setter) methods, a method that converts the song to a string for printing, and code to create a Song object and print it. The Ruby code is smallest and cleanest (therefore easiest to understand and maintain) without being terse or obfuscated.

Listing 1: song.java, song.m, song.pl, song.rb

Java, Objective-C, Perl, and Ruby code samples that each do the same thing: define a 
simple Song class and use it.

// ======== Java (song.java) ========
public class Song {
      
   protected String name;
   protected int lengthInSeconds;
      
   Song(String name, int len) {
      this.name = name;
      lengthInSeconds = len;
   }
      
   public String getName() { return name; }
   public void setName(String str) { name = str; }
      
   public int getLengthInSeconds() {
      return lengthInSeconds;
   }
   public void setLengthInSeconds(int secs) {
      lengthInSeconds = secs;
   }
      
   public String toString() {
      return name + " (" + lengthInSeconds + " seconds)"
   }
      
   // Create and print
   public void main(String[] args) {
      s = new Song("name", 60);
      System.out.println(s);
   }
}
// ======== Objective-C (song.m) ========
#import <Foundation/NSString.h>
@interface Song : NSObject
{
   NSString *name;
   int lengthInSeconds;
}
- initWithName:(NSString *)name length:(int)length;
- (void)dealloc;
- (NSString *)name;
- (void)setName:(NSString *)name;
- (int)lengthInSeconds;
- (void)setLengthInSeconds:(int)length;
- (NSString *)description;
@end
@implementation Song
- initWithName:(NSString *)nameString length:(int)length
{
   [super init];
   [self setName:nameString];
   [self setLengthInSeconds:length];
   return self;
}
- (void)dealloc
{
   [name release];
   [super dealloc];
}
- (NSString *)name { return name; }
- (void)setName:(NSString *)nameString
{
   [name autorelease];
   name = nameString;
   [name retain];
}
- (int)lengthInSeconds { return lengthInSeconds; }
- (void)setLengthInSeconds:(int)length
{
   lengthInSeconds = length;
}
- (NSString *)description
{
   return [NSString stringWithFormat:@"%@ (%d seconds)",
      [self name], [self lengthInSeconds]];
}
@end
int main(int argc, char *argv[])
{
   // Create and print
   Song *song = [[Song alloc] initWithName:@"name"
                            length:60];
   NSLog(@"%@", song);
   return 0;
}
// ======== Perl (song.pl) ========
package Song;
      
sub new {
   my($class, $name, $len) = @_;
   my $self = {};
   $self->{'name'} = $name;
   $self->{'lengthInSeconds'} = $len;
   bless $self, class;
   return $self;
}
      
sub name {
   my($self) = shift;
   if (@_) { $self->{'name'} = shift }
   return $self->{'name'};
}
      
sub lengthInSeconds {
   my($self) = shift;
   if (@_) { $self->{'lengthInSeconds'} = shift }
   return $self->{'lengthInSeconds'};
}
      
sub toString {
   my($self) = shift;
   return $self->name() . "(" . $self->lengthInSeconds()
      . ") seconds";
}
# Create and print
$s = Song->new('name', 60);
print $s->toString() . "\n";
// ======== Ruby (song.rb) ========
class Song
   
   # Not only declare instance variables (which is
   # unnecessary) but also create accessor methods
   # (getters and setters).
   attr_accessor :name, :lengthInSeconds
   
   # The constructor, sort of. This method is called
   # by the class method "new".
   def initialize(name, len)
      @name = name
      @lengthInSeconds = len
   end
   
   def to_s
      return "#{@name} (#{@lengthInSeconds} seconds)"
   end
end
   
# Create and print.
s = Song.new('name', 60)
puts s

This article will cover Ruby's language features, syntax, built-in classes, and libraries. We'll review a few Mac OS X-specific modules, look at some sample code, and leave you with a list of resources. This isn't a tutorial or a complete description of Ruby's features and idioms. There are a number of excellent Ruby books and online resources available that can fill in the details. See the "Resources" section below for a list.

Ruby's Features

This section attempts to briefly describe many of Ruby's features. Please bear in mind that there isn't enough room in this article to list everything or even fully explain or justify each item.

Object Orientation

Ruby's object oriented nature is consistent and complete. Everything is an object or a method. Even numbers are objects and even operators are methods. You may see code that looks like it is calling a global function; that is because the top-level code is actually executing within the context of an invisible magical variable representing the script's main module. This object includes the Kernel module, giving it access to methods such as print, chop, system, sleep, and all the others you might expect from a scripting language. You can write code that looks procedural, but it really isn't.

Classes inherit via single inheritance but can include multiple modules. Modules provide name spaces and--unlike Java's interfaces--method implementations. Like Smalltalk but unlike C++ or Objective-C, a metaclass (the class of a class) is a full-blown class itself, with instance variables and methods.

Classes remain "open" so you can add or replace methods for any class at any time. Go ahead: add a method to the String class! Methods can also be added to individual objects, giving them unique behavior.

Introspection and reflection let objects and classes inspect and manipulate their instance variables and methods.

Mark-and-sweep garbage collection (superior to Python's reference counting scheme) means you don't have to worry about memory management.

The Language

Ruby's syntax is simple and consistent. The language has been designed using the Principle of Least Surprise: things work the way you expect them to, with very few special cases or exceptions.

Variable naming rules are simple. The first character determines a variable's use: @instance_variable, @@class_variable, $global, CONSTANT, :symbol, and everything_else. This is different from Perl, where the first character determines the variable's type. We'll cover variable and method names in more detail later.

Before going any further, let's look at some Ruby code. Listing 2 implements a jukebox full of songs to play. This version gets its list of songs by reading the names of the files that iTunes stores in your Music directory. We'll augment this code later by using the RubyAEOSA module (available separately) to add AppleScript that talks directly to iTunes.

The first line of this script isn't Ruby code; it's a magical incantation that tells the shell what program to run to execute the script. Instead of hard-coding the path to the Ruby executable--which could be in /usr/bin/ruby, /usr/local/bin/ruby, or even somewhere else depending upon how it was configured when Ruby was installed--we use the env program to figure out where the Ruby executable lives.

Listing 2: jukebox1.rb

This jukebox reads the filesystem to retrieve the names of all of your iTunes files. It 
prints all artists' names and their album names. It then prints all of the song names from the album 
you specify and "plays" each of the songs from that album whose names start with a vowel.

#! /usr/bin/env ruby
#
# usage: jukebox1.rb [itunes-dir] [artist-name] [album-name]
#
# If itunes-dir, artist-name, or album-name are unspecified,
# default values (defined below) are used.
#
# Normally, I would put each class in its own file.
# This constant holds the name of the iTunes music directory
DEFAULT_ITUNES_DIR =
   "#{ENV['HOME']}/Music/iTunes/iTunes Music"
# A regular expression for making file names palatable
# to the Dir class file name globbing.
FNAME_BAD_CHARS_REGEX = /['"\s]/
# Create a Jukebox class. A Jukebox holds a hash (dictionary)
# whose keys are artist names and values are artist objects.
class Jukebox
   # Declare an instance variable. Declaring it isn't
   # necessary, but by using "attr_accessor" two accessor
   # methods (a getter and a setter) are created for us.
   attr_accessor :artists
   # This method is called by the constructor when a new
   # jukebox is created.
   def initialize
      @artists = Hash.new
   end
   # Return a list of all of the artists' albums.
   def albums
      return @artists.values.collect { | a | a.albums }
   end
   # Load all of the artists, albums, and songs. Provide
   # a default value for the parameter tunes_dir.
   #
   # This isn't the only way to traverse the directory
   # structure, but it will do.
   def load(tunes_dir = DEFAULT_ITUNES_DIR)
      artist_glob = File.join(tunes_dir, '*')
      artist_glob.gsub!(FNAME_BAD_CHARS_REGEX, '?')
      Dir[artist_glob].each { | artist_dir |
         next if artist_dir[0] == ?. # Skip dot files
         artist = Artist.new(File.basename(artist_dir))
         album_glob = File.join(artist_dir, '*')
         album_glob.gsub!(FNAME_BAD_CHARS_REGEX, '?')
         Dir[album_glob].each { | album_dir |
            next if album_dir[0] == ?.
            album = Album.new(File.basename(album_dir))
            song_glob = File.join(album_dir, '*')
            song_glob.gsub!(FNAME_BAD_CHARS_REGEX, '?')
            Dir[song_glob].each { | song |
               song_name = File.basename(song)
               next if song_name[0] == ?.
               # Add the song to the album's song list
               album.songs <<
                  Song.new(song_name.sub(/\.mp3$/, ''))
            }
            artist.albums[album.name] = album
         }
         @artists[artist.name] = artist
      }
   end
end
class Nameable
   attr_accessor :name
   def initialize(name)
      @name = name
   end
   def to_s
      return @name
   end
end
class Artist < Nameable
   attr_accessor :albums
   def initialize(name)
      super(name)
      @albums = Hash.new
   end
end
class Album < Nameable
   attr_accessor :songs
   def initialize(name)
      super(name)
      @songs = []
   end
end
class Song < Nameable
   alias_method :title, :name      # Make song respond to
   alias_method :title=, :name=    # "title" and "title="
   def play
      puts "Played song #{title()}" # See? title() works
   end
end
# ==========================================================
# This code will only execute if this file is the file
# being run from the command line.
if $0 == __FILE__
   DEFAULT_ARTIST = 'Thomas Dolby'
   DEFAULT_ALBUM = 'Astronauts And Heretics'
   fave_artist_name = ARGV[1] || DEFAULT_ARTIST
   fave_album_name = ARGV[2] || DEFAULT_ALBUM
   jukebox = Jukebox.new
   jukebox.load(ARGV[0] || DEFAULT_ITUNES_DIR)
   # Print some stuff
   puts "All Artists:"
   puts "\t" + jukebox.artists.keys.join("\n\t")
   puts "#{fave_artist_name}'s albums:"
   artist = jukebox.artists[fave_artist_name]
   artist.albums.each_value { | album |
      puts "\t#{album.name}"
      puts "\t\t" + album.songs.join("\n\t\t")
   }
   puts "\"Play\" all songs from \"#{fave_album_name}\"" +
      " that start with a vowel"
   album = artist.albums[fave_album_name]
   # Make a new list by rejecting (skipping) songs that
   # do not start with a vowel.
   vowel_songs = album.songs.reject { | song |
      song.name =~ /^[^aeiou]/i
   }
   vowel_songs.each { | song | song.play }
end

In Ruby, variables generally hold references to objects. May hold any kind of object. Thus Ruby is strongly typed but dynamic. Dynamic typing leads to quicker implementation. The benefit becomes apparent when you decide to change your application's phone numbers from strings to objects of class MyPhoneNumber. You don't have to change your code everywhere.

Closures are code blocks that remember their context including local variables declared outside the block, the value of self, and more. They provide a way to pass snippets of code to iterators or methods. Listing 3 shows a block being passed to an array's collect method. It also shows that any local variables defined when the block is created are available to the block.

Listing 3: blocks.rb

In this example, we create an array and a local variable. We then call the array's 
collect method, passing a block that uses the local variable.

#! /usr/bin/env ruby
a = [1, 2, 3]
x = 5
# Pass a block to the collect method. The collect method
# calls the block once for each element in the array,
# passing the element to the block. (The method p calls
# its arguments' inspect method, which is defined in the
# Object class but may be overridden.)
b = a.collect { | elem | elem + x }
p a            # prints [1, 2, 3]
p b            # prints [6, 7, 8]

The keyword yield, when used within a method, calls the block passed to the method. This gives blocks one more nifty use: adding behind-the-scenes behavior before or after the block is executed. For example, when the open method of the built-in File class is given a block argument, it not only passes the opened file into the block but it automatically closes the file when the block exits. Listing 4 shows how that happens.

Listing 4: autoclose.rb

This is how the open method of the File class can automatically close a file for you. 
It also shows how you specify default method argument values. The block isn't declared in the 
method's signature. It must be the last thing passed in when the method is called.

#! /usr/bin/env ruby
# In Ruby, you can add code to any class at any time.
class File
   # The real File.open method is probably a bit more
   # robust than this.
   def File.open(file_name, mode_string = "r", perm = nil)
      f = File.new(file_name, mode_string, perm)
      yield f      # Execute the block, passing the file to it
      f.close()
   end
   # We want to redefine close() but call the original
   # version. We can't just call super() because we are
   # not creating a subclass. Instead, we create a new
   # name for the original version of the method, then
   # use that name inside the redefined method. (This
   # implies that alias_method really clones the method
   # definition instead of simply creating a new name.)
   def close
      alias_method :old_close, :close
      old_close()
      $stdout.puts "file has been closed"
   end
end
File.open(some_file_name) { | f |
   # do something with the file...
}
# "file has been closed" will be printed to stdout.

Traversing a list of values is one of the most common things done in any program. Ideally, while enumerating the elements of a list you don't care how many things are in the list or what type of object each thing is. In Ruby, enumeration has been abstracted into a mixin module available to all classes and independent of the language syntax. Built-in classes like Array, Dir, Hash, IO, Range, and String all mix in (include) the Enumerable module. If a class includes the Enumerable module and implements one method called each that takes a block argument, it gets all the other methods for free: each_with_index, sort, collect, detect, reject, select, entries, find, grep, include?, map, max, reject, and more.

The Range class represents ranges of integers or strings. (0..3) is syntactic sugar that creates a Range object that will generate the values 0, 1, 2, and 3. (0...3) will generate 0, 1, and 2. Two dots means "include the final value", three dots means "exclude it".

A method's name can be aliased, meaning that more than one name can refer to the same method. Can't decide between indexes and indices? Can't remember if it's array.size or array.length? That's OK: use either one. The built-in classes make use of aliases to let you decide. We used alias_method in Listing 2 so a song's name could be referred to as it's title.

Ruby's exception mechanism is similar to Java's. You can raise exceptions, rescue (catch) them, and re-throw them. Within the rescue block, if you can fix the error you can use retry to jump back to the top of the block. See Listing 5 for an example.

Listing 5: exceptions.rb

An example of exception handling and retrying. This script will throw the "argument was 
true" exception the first time it is run. After the fixing the cause of the exception, it uses 
"retry" to cause the begin block to start over.

#! /usr/bin/env ruby
def raise_error_if_true(flag)
   raise "argument was true" if flag
end
def always_executed
   puts "always executed"
end
silly_flag = true
begin
   raise_error_if_true(silly_flag)
   puts "silly_flag was false"
rescue => ex
   $stderr.puts "error: #{ex.message()}; trying again"
   silly_flag = false
   retry
ensure
   # This is exactly like Java's "finally" keyword:
   # code here will always be executed.
   always_executed()
end
# The output of this script will be:
# error: argument was true; trying again
# silly_flag was false
# always executed

In Ruby, boolean expressions are slightly different than what you are probably used to: only nil and false are false; everything else (including the number 0 and empty strings, arrays, and hashes) evaluate to true.

The Rest of the World

Ruby is written in C. Not only does this make Ruby reasonably fast, but it makes integration with existing C code easy. You can extend Ruby with C code or embed a Ruby interpreter within your C code.

Because Ruby is written in C, it has been ported to many different operating systems. Ruby runs under Mac OS 9 and OS X, BSD, Linux, Solaris, BeOS, OS/2, DOS, Windows 95/98/NT/2K, and more. Ruby can also load libraries dynamically on operating systems like Mac OS X that support it.

As with most scripting languages, it is easy to execute commands just like you would from the command line. You can run Unix commands or even launch Mac applications by using the system method or enclosing the command in backquotes. The system method runs the command and returns the exit status; using backquotes returns the output of the command as a string.

In the section "Ruby and Mac OS X" below we will take a look at some modules that add Apple Event support and Cocoa integration to Ruby.

Ruby is a great language for building Web-based applications. The Web server built in to OS X is Apache, which can be configured and extended through the use of modules (not Ruby modules) such as those used to add PHP, Fast CGI, and--you guessed it--Ruby. The Apache module mod_ruby adds support for Ruby CGI scripts and the eRuby Ruby module adds support for Ruby embedded into HTML, just like PHP or JSP.

Any scripting language used for Web services and page scripting, system administration, and network communications needs some security measures. For example, Ruby allows any string to be executed as code using the eval method. If that string is supplied from someplace outside the script itself such as a file or user input, it may contain destructive or harmful code. The value of the global variable $SAFE determines how much Ruby trusts data stored in its variables. Data is either "tainted" or "untainted". Tainted data is that which has been supplied externally (for example, use input or file data). By default (when $SAFE == 0), Ruby trusts all data. Larger values of $SAFE cause Ruby to disallow the use of tainted data, prohibit loading programs from unsafe locations, distrust all newly created objects, or prevent modification of untainted objects. Objects can also be "frozen" to prevent further changes.

Ruby implements operating system-independent threading. The good news is that the threading model is highly portable; your threads will work under Jaguar or DOS. The bad news is that Ruby does not yet take advantage of an operating system's underlying threading, if any.

In the next major release of Ruby, Matz plans to add support for internationalization and multilingualization throughout Ruby. In 1.6.7, the only internationalization support is via the String class and the kconv module, which support a small number of codings, including UTF and Kanji.

The JRuby project is a pure Java implementation of the Ruby interpreter. Its URL is in the "Resources" section below. A module available at the Ruby Application Archive (RAA, URL in the "References" section) provides integration with Objective-C. See "Ruby and Mac OS X" below.

Syntax

Ruby's syntax is so close to Perl's and Java's that it won't take long to learn. I was writing tiny but useful scripts a few hours after installing Ruby.

Each line of code is a new statement. Semicolons are optional and unnecessary unless you need more than one statement on a line. To continue a line of code on multiple lines, end the line with something that logically continues the line: a comma or operator, for example.

To create a new object, use thing = ClassName.new(arguments). Each class can define the method initialize which is called by the constructor. You can use attr_accessor and friends to automatically create accessor methods (setters and getters). See Listing 6 for an example that defines a simple class with a instance variable definition that uses attr_accessor to automatically generate accessor methods and another that uses attr_reader to generate a getter but no setter.

Listing 6: constructors.rb

An example of constructors and automatically generated accessors.

#! /usr/bin/env ruby
class MyClass
   # attr_accessor creates setter and getter methods for
   # the listed symbols (instance variable names)
   attr_accessor :name
   # attr_reader creates a getter but not setter.
   attr_reader :the_answer
   def initialize(name="DefaultName")
      @name = name
      @the_answer = 42
   end
   # The method to_s is like Java's toString() method.
   def to_s
      "My name is #{@name}; the answer is #{@the_answer}."
   end
end
my_thing = MyClass.new("Ruby")
my_thing.name = 'New Name'
# If this line was uncommented, an exception would be
# thrown stating "undefined method `the_answer='"
# my_thing.the_answer = 3
puts my_thing.to_s

As mentioned previously, a variable's use is determined by the first character of its name: @instance_variable, @@class_variable, $global, CONSTANT, :symbol, and everything_else. Note that Ruby class and module names must start with capital letters. This implies they are constants.

By convention, method and variable names are all lowercase and words are separated_by_underscores. This is not enforced by the parser. Another convention that may look unfamiliar at first is the use of ? and ! in method names. Method names that end with ? return boolean values. Examples include Object.nil? and Array.empty?. Method names ending with ! modify the receiver. For example, String.strip returns a new copy of the receiver with leading and trailing whitespace removed; String.strip! modifies the receiver by removing leading and trailing whitespace.

A Symbol is a unique identifier. All occurrences of the same symbol share the same instance. Symbols are used to represent method and instance variable names and can also act as unique values for constants (think C's enums). Symbols have both string and integer representations.

Parentheses around method arguments are optional. Beware, though: leaving them out can occasionally cause unexpected results. Instead of memorizing complex rules, do what I do: when in doubt, use parentheses.

Arrays are created using either a = Array.new or a = [1, 'foo', thing]. The new method takes two optional arguments: array size and initial value. Hashes (also called hash maps, associative arrays, or dictionaries) are created using either h = Hash.new or h = {'a' => 1, 'b' => 'foo', 'c' => thing}. The new method takes one optional argument: the default value for undefined hash keys.

String constants can be contained within double or single quotes. When surrounded by double quotes, the contents of the string are interpolated. All occurrences of "#{x}" within the string are replaced by the value of x. Note that x may be anything: a variable, an expression, or an entire block of code. As a shortcut, "#{$var}" may be written as "#$var" and "#{@instance_var}" as "#@instance_var".

The [] and []= (assignment) methods of String can take an integer and return a single character, take two integers (starting index and length) to return a substring, or even take a regular expression and return the substring matching that expression. See Listing 7 for some examples.

One important note: there is no character class in Ruby. When you retrieve a character from a string you get back its integer value. To retrieve a one character string you have to ask for a string of length one. See Listing 7.

To write a character constant, use ? before the character. For example, ?A is an upper-case A.

Listing 7: string_accessor.rb

String accessor examples. Additionally, the String class provides a rich series of 
methods for string manipulation.

#! /usr/bin/env ruby
s = "abcdefg"
s[0]                     # => 97, the ASCII value of 'a'
s[0,1]                  # => "a"
s[1,2]                  # => "bc"
s[/b.*f/]            # => "bcdef"
s[0..3]               # => "abcd"
s[0...3]               # => "abc"
s[-4..-1]            # => "defg"
s[0] = ?x            # s == "xbcdef"
 s[/b.*f/] = "z"   # s == "xzg"

Ruby's control structures will be familiar to users of most languages. There's nothing surprising, except perhaps the use of elsif instead of else if. Like Perl, you can write do_this() if that or do_that() unless this.

The case structure (called "switch" in Java and C) is interesting. The branches use the === method compare the target with any other kind of object: a number, a regular expression, or even a class. See Listing 8.

Listing 8: case.rb

The case statement uses the === method to perform comparisons.

#! /usr/bin/env ruby
x = 'abcdefg'
case x
when 'xyzzy', 'plugh'         # Compare with constants
   # You can specify multiple potential matches in the
   # same "when" clause.
   puts "xyzzy or plugh"
when /def/                        # Compare with regex
   # Matches; this code will execute because the previous
   # comparison failed.
   puts "found 'def'"
when String                     # Compare with class
   # This matches, but the previous comparison executed
   # already.
   puts "It's a String, all right"
else
   # The default case, when all others fail
   puts "oops"
end

Listing 9 contains some examples of enumerating over objects' contents. Since the Enumerable module can be included in any class and only requires implementation of one method (each), it is easy to remember how to use and easy to add to your own classes.

Listing 9: enumerating.rb

Enumerating over an array, a hash, a string, and a user-defined class.

#! /usr/bin/env ruby
array = [1, 2, 3]
hash = {"a" => 1, "b" => 2, "c" => 3}
file = File.open($0, "r")      # $0 is this script's name
string = "abc"
# Notice how many different classes access their contents
# the same way because they all import Enumerable and
# implement the "each" method.
array.each { | elem | puts elem }
hash.each { | key, val | puts "#{key} => #{val}" }
file.each { | line | puts line }
string.each { | line | puts line }
# each_with_index is defined in the Enumerable module
array.each_with_index { | elem, i |
   puts "array[#{i}] = #{elem}"
}
# The String class adds the each_byte method, since
# String.each iterates over lines of text, not characters.
# The output will be a list of integers.
string.each_byte { | c | puts c }
# The include? method is defined in the Enumerable module
puts "yup" if array.include?(42)
# Now let's define our own class and make it enumerable.
class TrainTrip
   include Enumerable
   def initialize
      @stops = %w(Paris London Boston Tokyo Kiev)
   end
   # Implement the single method when including the
   # Enumerable module. By doing so, all the other
   # methods in that module (each_with_index, sort,
   # collect, detect, reject, select, entries,
   # find, grep, include?, map, max, reject, and
   # more) for free.
   #
   # If the last parameter in an argument list starts
   # with an ampersand, then if a block is passed to
   # the method Ruby will convert it into a Proc
   # object.
   def each(&block)
      @stops.each(&block)
   end
end
trip = TrainTrip.new
trip.each_with_index { | stop, i | puts "#{i+1}: #{stop}" }
# Because Enumerable takes care of everything else, we
# get lots more behavior for free.
trip.include?("London")      # => true
trip.sort.each { | stop | puts stop }
file.close

Classes and Libraries

Ruby comes with an impressive and useful library of classes and modules. The Ruby Application Archive is the best place to find and publish additional Ruby libraries and applications. When I counted in February of 2003 there were over 800 entries in the archive. They include libraries for Mac OS X, database access, networking, WWW and CGI programming, XML, unit testing, documentation, cryptography, AI, graphics, editors, GUI creation, games, and more.

Built-In Classes

The classes and modules that come with Ruby include value holders (Array, String, Hash, Numeric, Range, and Time), I/O classes (IO, File, and Dir), OO classes (Object, Class, Module, Struct, and the ObjectSpace module), operating system integration classes (Thread, ThreadGroup, and the Kernel module), regular expression classes (Regexp and Match), and more.

The hierarchy of numeric classes (Numeric, Float, Fixnum, and Bignum) provides double-precision floating point and infinite-precision integer arithmetic. The Math module provides sin, sqrt, log, and friends.

Objects can be marshalled--converted into byte streams and back--via the Marshall module. The library module named drb (Distributed Ruby) found on the Ruby Application Archive combines this ability with the networking library to provide an easy-to-use framework for writing distributed Ruby applications.

Libraries

The standard Ruby distribution comes with a number of libraries including networking, XML parsing, date manipulations, persistent object storage, Tk (a cross-platform GUI infinitely less pretty than Aqua), mutex thread synchronization, MD5 (cryptography), debugging, matrix math and complex numbers, design patterns like observer/observable and delegation, OpenGL, and much more.

The standard Ruby libraries are found in /usr/lib/ruby/1.6 (or /usr/local/lib/ruby/1.6 if you've installed it yourself and used the default install directory). Additional libraries you download and install usually place themselves in /usr/lib/ruby/site_local/1.6.

Ruby and Mac OS X

Fujimoto Hisakuni has written three Ruby bindings for Mac OS X: RubyAEOSA, RubyCocoa, and an Objective-C binding. They come with plenty of example code. All three are available within the same package. Download RubyCocoa version 0.3.2 or higher from the RAA. The download is a disk image (".dmg") file which contains, among other things, a ".pgk" package file. Install the package and you are ready to use the three libraries. Version 0.3.2 is for Jaguar only; it won't work with earlier versions of Mac OS X. If you are running an earlier version, download an earlier version of the libraries and follow the included installation instructions.

RubyAEOSA lets you send create and send Apple Events, run AppleScript code, and retrieve the results from either. RubyCocoa allows your scripts to use Cocoa objects. You can even write small Cocoa applications using Ruby and Interface Builder.

Let's use some AppleScript to let the jukebox from Listing 2 talk directly to iTunes to retrieve song information and play a song. See Listing 10. Two warnings: first, this AppleScript takes a few seconds to execute if you have a lot of music, whether it is run from Script Editor or this Ruby script. Second, for some reason this AppleScript code kept trying to launch the OS 9 version of iTunes. I had to move the OS 9 iTunes folder into the Trash and reboot before it would launch the OS X version. Even then, it started trying to launch the OS 9 version after a while. I emptied my trash. I'm no AppleScript expert, so all this was probably my fault.

Listing 10: jukebox2.rb

Add some AppleScript to the jukebox so it can talk to iTunes. Lists artist's albums and 
tells iTunes to play the first song from the specified album whose name starts with a vowel.

#! /usr/bin/env ruby
#
# usage: jukebox2.rb [artist-name] [album-name]
#
# Add some AppleScript to the jukebox so it can talk to
# iTunes. Lists artist's albums and tells iTunes to play the 
# first song from the specified album whose name starts
# with a vowel.
#
# If artist-name or album-name are unspecified, default
# values (defined below) are used.
require 'osx/aeosa'
require 'jukebox1'         # The original jukebox code
# Here's the AppleScript we will use to gather song
# information.
LOAD_SCRIPT = <<EOF
set all_tracks to {}
tell application "iTunes"
  tell source "Library"
   tell playlist "Library"
     set the track_count to the count of tracks
     repeat with z from 1 to the track_count
      tell track z
        set all_tracks to all_tracks & {{name, artist, album}}
      end tell
     end repeat
   end tell
  end tell
end tell
all_tracks
EOF
# Since class definitions never "close", we can easily
# define new methods or overwrite existing ones.
class Jukebox
   def load
      ret = OSX.do_osascript(LOAD_SCRIPT)
      all_tracks = ret.to_rbobj
      all_tracks.each { | track_ae |
         song_name, artist_name, album_name =
            track_ae.collect { | ae_obj | ae_obj.to_s }
         # See if we already have this artist in the
         # jukebox. If not, add it.
         artist = @artists[artist_name]
         if artist.nil?
            artist = Artist.new(artist_name)
            @artists[artist_name] = artist
         end
         # See if this artist already has this album.
         album = artist.albums[album_name]
         if album.nil?
            album = Album.new(album_name)
            artist.albums[album_name] = album
         end
         # Add the song to the album
         album.songs << Song.new(song_name)
      }
   end
   def play(album, song)
      script = "tell application \"iTunes\"\n" +
         "tell source \"Library\"\n" +
         "tell playlist \"Library\"\n" +
         "play (tracks whose name is \"#{song.name}\"" +
         " and album is \"#{album.name}\")\n" +
         "end tell\n" +
         "end tell\n" +
         "end tell\n"
      OSX.do_osascript(script)
   end
end
# ==========================================================
# This code will only execute if this file is the file
# being run from the command line.
if $0 == __FILE__
   DEFAULT_ARTIST = 'Thomas Dolby'
   DEFAULT_ALBUM = 'Astronauts And Heretics'
   fave_artist_name = ARGV[0] || DEFAULT_ARTIST
   fave_album_name = ARGV[1] || DEFAULT_ALBUM
   jukebox = Jukebox.new
   jukebox.load()            # Use AppleScript
   # Print some stuff
   puts "All Artists:"
   puts "\t" + jukebox.artists.keys.join("\n\t")
   puts "#{fave_artist_name}'s albums:"
   artist = jukebox.artists[fave_artist_name]
   artist.albums.each_value { | album |
      puts "\t#{album.name}"
      puts "\t\t" + album.songs.join("\n\t\t")
   }
   puts "Play the first song from \"#{fave_album_name}\"" +
      " that start with a vowel"
   album = artist.albums[fave_album_name]
   # Make a new list by rejecting (skipping) songs that
   # do not start with a vowel.
   vowel_songs = album.songs.reject { | song |
      song.name =~ /^[^aeiou]/i
   }
   # Play the first song we found
   jukebox.play(album, vowel_songs[0])
end

Finally, Listing 11 lets your computer declare out loud the name of its new favorite language.

Listing 11: speak.rb

Tell me: what's my favorite language?

#! /usr/bin/env ruby
require 'osx/cocoa'
include OSX
def speak(str)
   str.gsub!(/"/, '\"')      # Put backslash before quotes
   src = %(say "#{str}")
   # Call Objective-C code
   script = NSAppleScript.alloc.initWithSource(src)
   script.executeAndReturnError(nil)
end
speak "Ruby is my favorite language!"

Conculsion

I hope this article has given you enough of a taste of Ruby's features, power, and elegance that you want to explore it more. Apple liked it enough to include it with Jaguar. May you find it half as fun and useful as I have.

Resources

Books

Thomas, David and Andrew Hunt. Programming Ruby. Addison Wesley, 2001. This book, known also as "The Pickaxe Book" for its cover picture, was the first English book on Ruby. It is an excellent tutorial and reference. Published under the Open Publication License, the entire book is available online. I highly recommend you buy a copy.

Matsumoto, Yukihuro. Ruby in a Nutshell. O'Reilly, 2002. This is a "Desktop Quick Reference" based on Ruby 1.6.5.

Feldt, Robert, Lyle Johnson, Michael Neumann (Technical Editor). Ruby Developer's Guide. Syngress, 2002.

Fulton, Hal. The Ruby Way. Sams, 2002.

Internet Resources

The Ruby home page is http://www.ruby-lang.org. The Ruby Application Archive (RAA) lives there at http://raa.ruby-lang.org.

The Usenet news group comp.lang.ruby and the mailing list ruby-talk are synchronized (using Ruby code, of course). comp.lang.ruby is where I go first if I can't find something in the manuals or books. Matz or other expert Ruby programmers often answer questions there. See the Ruby Web site for mailing list details. The Web site http://www.ruby-talk.org contains a searchable archive of the list. The community of Ruby users is among the most friendly and helpful I have seen.

Ruby Central (http://www.rubycentral.com) is hosted by the Pragmatic Programmers (Dave Thomas and Andy Hunt, authors of Programming Ruby).

Ruby Garden (http://www.rubygarden.org) is an excellent collection of Ruby news, links, polls, and the Ruby Garden Wiki (http://www.rubygarden.org/wiki). (A Wiki is a Web site with user-editable pages. They're great for collaboration.)

William Tjokroaminata's Web page "Things that Newcomers to Ruby Should Know" (http://www.glue.umd.edu/~billtj/ruby.html) contains a number of tips for new Ruby programmers.

The JRuby projects' home is http://jruby.sourceforge.net.

DataVision, my database reporting tool project, is written in Java but uses Ruby as its formula and scripting language. DataVision's home page is http://jruby.sourceforge.net.


Jim Menard is a senior technologist with twenty years of experience in development, design, and management. Like so many of us, Jim is an ex-dot-com CTO and a consultant. A language maven, Jim loves everything about Ruby. He has developed Open Source projects such as NQXML (the first pure-Ruby XML parser), Rice, DataVision (a Java GUI report writer), and TwICE. You can contact him at jimm@io.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.