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

Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »

Price Scanner via MacPrices.net

Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more

Jobs Board

Housekeeper, *Apple* Valley Village - Cassi...
Apple Valley Village Health Care Center, a senior care campus, is hiring a Part-Time Housekeeper to join our team! We will train you for this position! In this role, Read more
Sublease Associate Optometrist- *Apple* Val...
Sublease Associate Optometrist- Apple Valley, CA- Target Optical Date: Apr 20, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92307 **Requisition Read more
*Apple* Systems Administrator - JAMF - Syste...
Title: Apple Systems Administrator - JAMF ALTA is supporting a direct hire opportunity. This position is 100% Onsite for initial 3-6 months and then remote 1-2 Read more
Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.