objectiveview.online
objectiveview.online

ObjectiveView

for the serious software developer - since 1996

Ruby is a GemAmy Hoy

pdf-download epub-download

Althought it's been around for a fair while, Ruby became widely popular with the release of the Ruby on Rails web development framewrk. Rails took specific advantage of certain Ruby features. In this article, Amy Hoy provides an introduction and overview of the Ruby Programming language - in her own uniquely entertaining style...

Ah, Ruby. I'll skip the simpering introduction where I say how wonderful Ruby is, and demonstrate by example why you should give Ruby a thorough checking out - so keep reading!

Help, What Am I Doing In This Nutshell?!

If I had to pick one word to describe Ruby — to sum it up in the shell of some kind of nut, as you might say — that word would have to be: elegance. Elegance, in this case, meaning as simple as it should be—and no simpler.

On the other hand, if "elegance," was unavailable as a choice for some reason, I'd probably pick "happiness." Ruby brings out the smiles, and it may just remind you of your salad days, when programming was fun and you enjoyed the tingly feel of photosynthesis in your leaves.

On the other hand, the recent smart-phone enabled trend of sketching on whiteboards and putting photos of the results on a wiki often doesn’t cut it when you’re asked to produce your “architecture documents” or when you’re trying to remember crucial details to explain the system to a new team member. So in between these extremes, how much architectural documentation is enough? In this article we’re going to explore these questions and some approaches that can help you to answer them in the context of your own projects.

Contraindicators

You might be one of those few who have an unpleasant visceral reaction to Ruby. It's been known to happen in a small percentage of test subjects. Don't worry—even though you might get teased in the playground, you're reaction is completely within acceptable norms. Programming languages are, above all, an aesthetic choice.

Vitals — Stat!

Ruby's a great kid and it's got a lot going for it. For one, it's a very object-oriented programming language. Everything's an object, and there are no primitives.

Witness:

5.times { puts "Mice! " }

And:

Elephants Like Peanuts.length 

On the other hand, you don't have to encapsulate everything you write in classes—you're free to write procedural code if you so desire. Ruby classes may seem a bit strange at first, but we'll get to that later.

Happily it turns out that both of these questions can be answered by using an approach that has been around for quite a while – structuring your architectural description into a set of related “views” (in fact the approach has been around long enough that there are books and even an ISO standard [1] on the topic). A view describes one aspect of an architecture, from the perspective of one or more stakeholder concerns. A formal definition of a view taken from the book Nick Rozanski and I wrote on this topic [2] is as follows:

Feature Fantastic

Ruby also has a lot of very powerful features you've probably come to associate with "enterprise-class" development and typing lots and lots of extra characters, including operator overloading, a mature inheritance model (called mixins), and more.

Are You My Mommy?

Ruby descends from a dazzling array of languages, with names you might recognize: Smalltalk, Perl, and even Ada. It has such high-order programming features as blocks and closures, and so is an instant darling of folks who love the power of Lisp and yet find themselves violently allergic to anything with that much punctuation.

Ruby's made to be readable

For example:

Child.open('present') unless Child.bad?

You might recognize this as a Perlism. You would be right.

Zen is In

Ruby's daddy, Matz (Yukihiro Matsumoto) set out to "make programmers happy." Ruby made its first debut in 1995, and since then, Matz has repeatedly propounded the needs of humans over the needs of computers when it comes to language design. After all, the computers don't really care what the language looks like, and more processor power is easier to come by than joy.

Things in Ruby tend to work the way you'd think they would if you inhabited a sane, well-designed universe. Of course, you may not have inhabited such an environment till now, and may need to be re-educated to unlearn bad habits picked up elsewhere — cough — before things start just making sense.

This results in Ruby being a very simple-looking language. Perhaps even deceptively simple, as Ruby's arguably more powerful than a number of other popular languages (see Paul Graham's arguments in his book Hackers and Painters on what makes one language more powerful than another). While anyone can make a language look dense for an obfuscated insert-language-here contest, typically Ruby looks like no such thing.

Dive In — You Won't Break Your Neck

Learn by doing, I always say. So if you are so inclined, open up your operating system's command-line interface (if it has one) and type irb to see if you already have Ruby installed. IRb stands for Interactive Ruby.

A Little Experimentation Never Hurt Anyone

When you've loaded up IRb try these lines. Lines you type begin with >>, while responses from the Ruby interpreters begin with =>.

First, let's test your (and Ruby's) basic math skills:

>> 6 * 7
=> 42 

Voila! You don't have to use any primitives to perform these elementary math operations. But just like in Java, those operators — and the others as well — are class methods which can be overloaded.

Beyond the third grade math class:

>> 42.zero?
=> false
>> myvar = 0
=> 0
>> myvar.zero?
=> true

In addition to addition, the above code demonstrates that Ruby can answer direct questions! When you see a ? tacked on the end of a method, you can assume it will return a boolean value. This human-friendly convention makes code clearer with just a single character. Ruby's full of sweet little touches like this.

Now we leave numbers behind and enter the wild world of letters.

>> "Hi, I'm a String!".reverse
=> "!gnirtS a m'I ,iH"
>> "Monkeys! " * 3
=> "Monkeys! Monkeys! Monkeys! "
>> mystring = "cheese"
=> "cheese"
>> mystring[1..3]
=> "hee"

Ruby's String class is chock full of useful—or at least amusing—methods like the ones above. But you don't have to create a container variable to use class methods on an object. You need no intermediary to transform dairy product into pure glee:

Of course, not all methods available on one data type are available for another. When in doubt or in error, you can convert between them to get the result you want.

When you try to use a nonexistent method on a class, you'll get an error that looks like this:

>> 1337.reverse
NameError: undefined local variable 
or method `l337' for main:Object
	from (irb):12
	from (null):0

Whoops! That's a String method, but we tried to call it on a Fixnum. Bad mojo. Let's try that again:

>> 1337.to_s.reverse
=> "7331" 

And, as you can see, you're able to stack up method calls like so many... precariously balanced things which are stackable.

>> (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 

This converts a Range object of (1..10) to an Array using the method to_a, Range#to_a comes from the mixin Enumerable, actually. Phew!

Basic Types

Your basic data types in Ruby are Numeric (subtypes include Fixnum, Integer, and Float), String, Array, Hash, Object, Symbol, Range, and Regexp. Ruby doesn't require you to use primitives when manipulating data of these types—if it looks like an integer, it's probably an integer; if it looks like a string, it's probably a string.

Which Type Are You?

And now, back to that idea of Ruby being brain-friendly. If you were assuming a sensible universe, how would you determine what type a given object was?

>> mysterytype = "hello"
>> mysterytype.class
=> String
>> (1..2).class
=> Range 

You have to admit, it does make sense.

If you want to create an object which is of a specific type, and Ruby isn't inclined to do what you want, you can create it explicitly just like you create any other object from a class:

>> givemeastring = String.new("42")
=> "42"

Duck Typing

Another one of Ruby's vaunted features is its duck typing — it's not a statically typed language, far from it. Much like your social status in high school, an object's type (class) merely informs its very beginnings. Class is really not nearly as important as what an object can do, once it's out in the Real World.

"If it looks like a duck, and quacks like a duck, we have at least to consider the possibility that we have a small aquatic bird of the family anatidae on our hands."

— Dirk Gently (as written by Douglas Adams) of Dirk Gently's Holistic Detective Agency

The essentials of duck typing can be boiled down to this: If you call a method, or reach for some capability, and the object in question has it, great—look no further, who cares what type it is! When put this way, it sounds less like a major programming theory thingamabobber and more like common sense, but there you have it.

Even so, duck typing can throw folks who have more statictyped language experience when, for example, an Array object responds to methods generally associated with Lists, Stacks, and Queues. If this happens to you, just relax, take a deep breath, and remember that that which does not kill you will only make you stronger. And I promise you, you won't be the first victim of terminal Ruby Exposure™.

Modules & Mixins

Ruby couldn't be outfitted with so many interesting features and then fall through on something as major as its inheritance paradigm. (Yeah, I said it — paradigm!) It'd be... well, a shameful lack of imagination!

And so, in Ruby, classes have only single inheritance—but you can get all the benefits (and only a few of the costs) of multiple inheritance through mixins. Mixins are a way of including a module of modular (get it?) code in one or more classes. Modules are very similar to classes, but are meant for extending classes, and creating namespaces, rather than standing on their own.

For example, the Enumerable module is mixed-in to both Array and Hash. It facilitates fun functions like each (for looping), map (for doing an array-walk kind of thing), sort (for sorting, natch), and so on. The best part is, the client code usage for all the methods in Enumerable is the same for both Array and Hash, so you just have to learn them the once and you're golden.

But mixins can be complex creatures, if they need be. The Enumerable module is a great example of this, because while the code for the module itself is fixed, it accomodates two rather different data types. How does it pull this off? Simple: Enumerable relies on the implementing class (Array, Hash) to provide its own each function to make everything work. It can delegate, while still maintaining the namespace. It's very purty.

Healthy eating?

Here’s some example code using a mixin:

module Digestion
	def eat(*args)
		args.each { |food|
			#look for foods the including class cannot eat -- instance variable
			puts " is delicious!" unless self.inedible_stuff.include?(food)
		}
	end
end

class Dog
	include Digestion
	attr :inedible_stuff
	def initialize
		#dogs can't eat this stuff
		@inedible_stuff = ['rocks','rubber','chocolate','people']
	end 
end

class MonsterUnderMyBed
	include Digestion
	attr :inedible_stuff
	def initialize
		#monsters can eat anything
		@inedible_stuff = []
	end
end

# examples:
# @inedible_stuff accessible from including class (dogs can't eat rocks)

irb(main):025:0> Dog.new.eat('rocks','dog food','stuff from the trash
can')
dog food is delicious!
stuff from the trash can is delicious!
=> ["rocks", "dog food", "stuff from the trash can"]

irb(main):028:0> MonsterUnderMyBed.new.eat('your sock','your
foot','your thigh')your sock is delicious!
your foot is delicious!
your thigh is delicious!
=> ["your sock", "your foot", "your thigh"] 

Fancier Stuff

Ruby has blocks, which let you pass in segments of code as an argument to some functions. If you've ever dallied with Lisp, you'll recognize block as another word for closure. For example, a simple block is used in this code, which uses the each method described in the previous section:

>> ['monkey','cheese','pants'].each { |thing| puts "I put  on
my head!" }
I put monkey on my head!
I put cheese on my head!
I put pants on my head!
=> ["monkey", "cheese", "pants"] 

Here's how this works: When somebody wrote the each method, he used the yield statement, which tells Ruby to execute any code supplied in a block. In this case, the programmer used yield to put an object in local scope for the block—the object in this case being the array element in question.

You too can use yield. It can simply execute code in the block, or you can use it to pass around data as each does. And if yield is supplying variables, you access them in the block by giving them names in pipes, one for each object, separated by commas.

Here's a simple example of how to use yield in a method of your own:

>> def using_yield
.. number = 2;
.. yield('yeehaw!', number)
.. end
=> nil 

Now that the method is written, give it a call. As you can see, yield is yielding two different variables, so make sure to catch them both in the block (see code below).

Neat, huh? This is how easy Ruby makes higher-order programming. (The console still returns nil in addition to the output because using_yield() does not in fact return anything.)

>> using_yield { |word,num| puts " --  times!" }
yeehaw! -- 2 times!
=> nil

An Object Lesson

Classes in Ruby can be an interesting mix of straight-up code and other code wrapped in methods.

Here's a sample class to get you started:

class Junk
	attr_reader :socks, :glass, :book

	def initialize
		@socks, @glass, @book = 'floor', 'table', 'under the chair'
		@mappings = {'one' => 'socks', 'two' => 'glass', 'three' => 'book'}
	end

	def clean!
		@socks = @glass = @book = 'put away'
	end

	['one','two','three'].each do |name|
		define_method(name) do
			"The {@mappings[name]} is + self.send(@mappings[name])
		end
	end
end

If you're feeling so inclined, you can use the do..end keywords instead of curly braces when using blocks, as I did in this example.

The section at the end ([‘one’, ‘two’, ‘three’ …]]) is an example of meta-programming in Ruby. This code is the equivalent of writing:

def one
	"The {@mappings[‘one’]} is "+ self.send(@mappings[‘one’])
end
def two
	"The {@mappings[‘two’]} is "+ self.send(@mappings[‘two’])
end
def three
	"The {@mappings[‘three’]} is"+self.send(@mappings[‘three’])
end

>> stuff = Junk.new
=> #"book",
"two"=>"glass", "one"=>"socks"}, @book="under the chair",
@glass="table">

That's certainly an ugly mishmash, but somewhat informative nonetheless, with the new object's class name, ID, and all the instance variables. Now, let's try out that dynamic method magic:

>> stuff.three
=> "The book is under the chair"

Crazy! And what's this do?

>> stuff.clean!
=> ["put away", "put away", "put away"] 

I've never felt so excited about cleaning! That's another Rubyism for you: a method ending in a ! will typically alter the object itself or otherwise do something that might be destructive or unexpected. Ruby programmers believe in putting punctuation to good use. (Remember, kids, this is a convention—not a rule.)

So, we modified the object. Where's the book now?

>> stuff.three
=> "The book is put away" 	

Readin' & Writin' in One Line (Each)

You may have noticed that lone line at the top of the class which begins with attr_reader. That's a Ruby method to automatically generate accessors for class and instance variables. If you want write access as well, do up a line with attr_writer, too. You use symbols (they're a whole 'nother article) to denote the variables you want accessible. Use the format :variablename.

A Method To The Madness

This simple class has two regular methods (initialize is what you call the constructor), and some procedural code which actually defines new methods on the fly. A simple loop through an array creates three methods which would otherwise involve lots of duplicated lines—methods called one, two, and three, which spit out info corresponding to their mapped instance variables.

The define_method method will create a new method in the current context (e.g., our class); it takes a single argument, the name for the new method, and then in the block you supply what the contents of the method would be. If the method takes arguments, you put them in the block in |pipes|, just like you do for blocks elsewhere.

While this particular code does isn't particularly interesting because of its extreme simplicity (also, it's useless), it becomes quite intriguing when you consider that you can have any number of behaviors trigger method generation.

You may have thought I'd make it through the whole article without mentioning Ruby on Rails, but I hope you didn't make any bets. Ruby on Rails is one project that uses dynamic method creation to great effect, by catching exceptions thrown for missing constants and then making new methods as necessary. This is what allows you to use such as-yet-nonexistent methods as Model.find_by_column1_and_column2. Instead of spending the startup time creating these kinds of goodies, it waits until they're needed. Code creation, on demand!

Message in the Parenthesis

Near the end of our little Junk class, there's the little bit of code [i self.send(@mappings[name])]. Conceptually, it's a surprisingly dense snippet, especially if you don't already know Ruby. But explaining those dense concepts—why, that's what I'm here for!

Ruby's got a message-based system for interacting with objects. If you want to access a data member or method of a class, you can use the send method which belongs to the base Object class (which everything subsequently inherits).

And the reason this works here is because when you append a variable name to the end of a Ruby object using the dot notation, you're actually calling a function—an accessor method. So you can use the same method, send, to access methods and variables—because in reality, they're the same.

One Last Trick

One of my favorite Ruby features is open classes. You can redefine a class anywhere, and instead of getting angry messages about how a class is already defined, you can modify it. (but note: you will get a warning you put the earlier code and the new code in a text file and run them with Ruby's warnings enabled).

class Junk
	def clean!
		@socks, @glass, @book = ['donated']*3
	end
end 

Pop this little extension into your console and try stuff.clean! again. Get rid of that clutter... all the items will now report themselves as being donated. It's very freeing, you know.

Caveat Coder: Like “the force”, open classes can be used for good or evil. Or programming practices so suspect they take on the patina of evil, anyway. Use “the force” for good and Ruby will treat you right.

So Long, Farewell...

Our time together is coming to an end! I hope your interest is tickled, or better yet piqued, and you're excited to pursue Ruby further. I promise you, you won't regret it. Except maybe when you have to use other languages. If you are so inclined towards further Ruby scholarship, here are some great resources for you to contemplate:

And of course, please feel free to stop by my site ((24)Slash7, http://www.slash7.com) and leave comments or drop me an email. Til then, happy Rubying!