Some Real Examples
At this point, we'll present two larger Ruby programs. The first is a basic web server that echoes back the headers it receives. It's written as two classes; see Listing One. WebSession
is a convenience class that provides two methods for writing to a TCP connection. The standardPage
method is interesting. At a minimum, it writes a standard page header and footer. If called with a block, however, it inserts the value returned by that block as the page body. This kind of wrapping functionality is a natural use for Ruby's blocks.
Listing One
require "socket" class WebSession def initialize(connection) @connection = connection end def write(string) @connection.write string end def standardPage(title) write "HTTP/1.1 200 OK\r\n" write "Content-Type: text/html\r\n\r\n" write "<html><head> <title>#{title}</title> </head>\n" write yield if block_given? write "</body></html>" end end class WebServer def initialize(port) @listen = TCPServer.new('localhost', port || 8080); end def run loop do Thread.start(@listen.accept) do |aConnection| begin session = WebSession.new(aConnection) request = [] loop do line = aConnection.gets.chomp("\r\n") break if line.length == 0 request << line end session.standardPage("Your Request") { "<h1>Your request was:</h1>\n" + request.join('<br>') + "<p>Thank you for testing our system." } ensure aConnection.close end # begin end # Thread end # loop end end WebServer.new(ARGV[0]).run
The WebServer
class uses Ruby's TCP library to accept incoming connections on a given port. For each connection, it spawns a Ruby thread that reads the header and writes the contents back to the client. The code in the thread is wrapped in a begin
/end
block, used in Ruby to handle exceptions. In this case, we use an ensure clause to make sure that the connection to the client is closed, even if we encounter errors while handling the request.
The second program packs a number of features into a small space. At its core, it represents the list of songs in an MP3 collection as an array, providing all the existing array functionality plus the ability to shuffle the entries randomly. If the array is sorted, then the entries will be ordered by song title. Each entry in the array is an object of class Song
. As well as providing a container for the song title, album, and artist, this class implements the general comparison operator, <=>. This operator is used when sorting containers containing songs: In this case, we compare song titles. There are two common approaches to making our MP3List
act as if it were an array: delegation or subclassing. Listing Two shows the approach using delegation. The library module delegate
provides a class SimpleDelegator
, which handles all the details of forwarding method calls from class MP3List
to the delegate. We create the array containing the songs, then invoke SimpleDelegator
's initialize
method (using super(songlist)
) to set up the delegation. From that point on, MP3List
will act as if it were an array. When users shuffle a song list, we create a new array containing the entries of the original in a random order, and use SimpleDelegator
's __setobj__
method to delegate to that new array.
Listing Two
require 'delegate' require 'find' class Song attr_reader :title, :album, :artist def initialize(filename) @artist, @album, @title = filename.split("/")[-3..-1] @title.chomp!(".mp3") end def <=>(anOther) title <=> anOther.title end def to_s "'#{@title}' #{@artist}--'#{@album}'\n" end end class MP3List < SimpleDelegator def initialize(base) songlist = Array.new Find.find(base) do |entry| if entry =~ /\.mp3$/ if !block_given? or yield entry songlist << Song.new(entry) end end end super(songlist) end def shuffle! newlist = Array.new length.times do newlist << delete_at(rand(length)) end __setobj__(newlist) self end end base = ARGV[0] || "/mp3" list = MP3List.new(base + "/Hatfield And The North") puts "Original: ", list.sort puts "Shuffled: ", list.shuffle! puts "5 entries: ", list[0..4] puts "Filtered: " list = MP3List.new(base) { |x| x =~ /Woke Up This Morning/ } puts list
Listing Three shows an alternative implementation of MP3List
in which we subclass the built-in Array
class and add our own shuffle!
method. Why the exclamation mark? Ruby convention is to append a "!" to methods that change their object (or are otherwise dangerous), and to append a question mark to predicate method names.
Listing Three
class MP3List < Array def initialize(base) super() Find.find(base) do |entry| if entry =~ /\.mp3$/ if !block_given? or yield entry self << Song.new(entry) end end end end def shuffle! newlist = Array.new length.times do newlist << delete_at(rand(length)) end replace(newlist) end end
Conclusion
Programming in Ruby is an immensely satisfying experience the language is able to represent high-level concepts concisely, efficiently, and readably. It's easy to learn, and at the same time it is deep enough to excite even the most jaded language collector. Download a copy and try it for yourself.
Dave and Andy are consultants and coauthors of Programming Ruby and The Pragmatic Programmer, both from Addison-Wesley. They can be contacted at http:// www.pragmaticprogrammer.com/.
Ruby Resources
The official Ruby Home Page is http://www.ruby-lang.org/. You can also find Ruby information at http://www.rubycentral.com/. In particular, you'll find complete online references to Ruby's built-in classes and modules at http://www.rubycentral.com/ref/, and to the Ruby FAQ at http://www.rubycentral.com/faq/. The latest version of Ruby can be downloaded from: http://www.ruby-lang.org/en/download.html. Ruby also has its own newsgroup (comp.lang.ruby). Traffic on this group is archived and mirrored to the ruby-talk mailing list.
For information on subscribing to ruby-talk, the English-language mailing list, see http://www.ruby-lang.org/en/ml.html.
D.T. and A.H.
Designing Ruby
By Yukihiro Matsumoto
Designing the ideal language has been a dream of mine since my student days. It wasn't until 1993, however, that I realized that as computers increased in power, new opportunities were opening up for object-oriented programming (OOP) and interpretive (scripting) languages. Over time, I narrowed my interest to Perl and Python, but they still weren't what I wanted. Consequently, I designed Ruby.
When I first read Programming Perl, by Larry Wall, et al. (O'Reilly & Associates, 2000), I was impressed not only by Perl's features and syntax, but by Larry Wall's basic tenants, in particular: "Easy things should be easy, hard things should be possible" and "There's more than one way to do it."
When I started designing what ended up being Ruby, I borrowed the best from languages such C, Perl, Smalltalk, Lisp, CLU, Eiffel, and the like. In the process, I soon realized that computer languages should follow the principles of human-machine interface design, specifically:
- Consistency. I'm not a minimalist, but I do believe that programming languages should be consistent. A small set of rules should cover the entire language. For instance, in Ruby, all data is an instance of some class without exception. Everything is treated equally.
- Conciseness. I want my computer to be a servant, not a master, so I'd like to tell it what to do quickly and efficiently. I removed extraneous language elements, such as declarations, static type specifications, and the like. They are all good, but aren't necessary for concise programming. A rich set of literals (as in Perl) helps concise programming, too. In general, Ruby scripts are as concise as Perl scripts, yet far more readable.
- Flexibility. A language should not restrict expression, but should enhance it. It should be general purpose. "Easy things should be easy, hard things should be possible." But, you know, even hard things can become easy using proper abstraction, thanks to Ruby's deep object-oriented nature.
I also wanted Ruby to be natural to use, and I wanted programmers to feel at ease when coding in it so they can enjoy the act of programming itself. To me, this is the ultimate goal of language design. I have to admit, I don't believe I can satisfy everyone every programmer has different needs. But I still believe I can satisfy many, including myself.
Programming in Ruby makes me happy, and I've been told that many others like programming in Ruby as much as I do. I want you to enjoy programming and hope Ruby helps you to this end.
Yukihiro Matsumoto is the creator of Ruby. He can be contacted at matz@ zetabits.com.