Ruby off the Rails

来源:百度文库 编辑:神马文学网 时间:2024/05/02 18:06:26
    Country/region [select]    Terms of use


All of dW -----------------   DB2   eServer   Lotus   Rational   Tivoli   WebSphere   Workplace -----------------   Autonomic computing   Grid computing   Java technology   Linux   Open source   Power Architecture   SOA & Web services   Web architecture   Wireless   XML -----------------   dW forums -----------------   dW Subscription ----------------- alphaWorks ----------------- All of IBM    
Home    Products    Services & solutions    Support & downloads    My account
 
developerWorks
In this article:
Feels so different
Fast moving objects
Collections done right
RubyBeans?
Watch Ruby iterate
Conditionally yours
Ducks love Ruby!
Mix-in it up
In conclusion
Resources
About the author
Rate this page

Related links
Java technology technical library


developerWorks  >  Java technology  >
Ruby off the Rails
Get to know Ruby before you hop on (or off) the Rails bandwagon

Document options

Print this page

E-mail this page
New site feature

Plan and design IT solutions with our new Architecture area
Rate this page

Help us improve this content
Level: Introductory
Andrew Glover (aglover@vanwardtechnologies.com), CTO, Vanward Technologies
20 Dec 2005
Ruby on Rails is just one facet of what makes Ruby great, just like EJB is only part of the Java™ enterprise platform. Andrew Glover digs beneath the hype for a look at what Java developers can do with Ruby, all by itself.
Before I can even begin this article, I need to clarify something. First, this is not an article about Ruby on Rails. If you want to read about Rails, articles and blogs are published weekly (maybe hourly) extolling the manifold features of this exciting framework; seeResources for a list to start from. Second, this article does not foretell the collapse of the Java platform in the face of better languages, tools, and frameworks like Ruby on Rails. So this article is about neither of the subjects most commonly associated with Ruby of late.
Don‘t get me wrong -- I think Rails is fabulous! It‘s amazingly powerful and has clearly changed the face and pace of Web development. My only point is that there‘s more to Ruby than Rails, especially from a Java developer‘s perspective.
The specialty of Rails is Web site development; however, I don‘t find myself building Web sites all that often. Most of the Web sites I work on have already been built using Struts, Tapestry, or some other technology. Primarily, when I utilize Ruby, I use it as part of a development practice that hinges on the Java platform. So in this article, I‘ll write about how to develop in Ruby if you‘re primarily a Java developer.

If you‘ve ever envied the ability of multilingual friends to bridge language gaps wherever they travel or gained new appreciation for your native language by learning a new one, then you can probably see the advantage of being a programming language polyglot. Developer polyglots travel the IT world more freely than do monolinguists (sure that they can apply their skills in any environment), and they also tend to better appreciate the programming language called home, because among other things they know the roots from which that home is sprung. Isn‘t it time you became a polyglot?
Ruby‘s syntax is different from that of the Java language. First, Ruby has no brackets or semicolons, and it makes types completely optional. Some might say that Ruby‘s syntax is terse, and it‘s that way with a purpose: this language lets you create concise working code in short order.
You can see this for yourself by comparing the same classes, defined first in the Java language and then in Ruby. I‘ll start with two classes -- Word and Definition (like in a dictionary) -- in the Java language. In the simple class diagram of Figure 1, you can see that the two classes share a few relationships (just bear with me if all this complexity seems contrived: it serves a purpose!):
A Word can have a collection of synonyms (which are instances of Words). A Word also has a collection of Definitions. A Definition has an aggregation association to a Word.

In Listing 1, I define the Word class in the Java language. Note the relationship validation I had to do with respect to my collection of Definitions and synonyms. This is necessary because in the example (as coded), Definitions can be created without a Word relationship initially and Words can be defined without Definitions initially, too.
package com.vanward.dictionary;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class Word { private String spelling; private String partOfSpeech; private Collection definitions; private Collection synonyms; public Word(String spelling, String partOfSpeech) { this.spelling = spelling; this.partOfSpeech = partOfSpeech; this.definitions = new ArrayList(); this.synonyms = new ArrayList(); } public Word(String spelling, String partOfSpeech, Collection definitions) { this(spelling, partOfSpeech); if(definitions != null){ for(Iterator iter = definitions.iterator(); iter.hasNext();){ this.validateRelationship((Definition)iter.next()); } this.definitions = definitions; } } public Word(String spelling, String partOfSpeech, Collection definitions, Collection synonyms) { this(spelling, partOfSpeech, definitions); if(synonyms != null){ this.synonyms = synonyms; } } private void validateRelationship(Definition def){ if(def.getWord() == null || def.getWord() != this){ def.setWord(this); } } public Collection getDefinitions() { return definitions; } public void addDefinition(Definition definition) { this.validateRelationship(definition); this.definitions.add(definition); } public String getPartOfSpeech() { return partOfSpeech; } public void setPartOfSpeech(String partOfSpeech) { this.partOfSpeech = partOfSpeech; } public String getSpelling() { return spelling; } public void setSpelling(String spelling) { this.spelling = spelling; } public Collection getSynonyms() { return synonyms; } public void addSynonym(Word synonym) { this.synonyms.add(synonym); } }
The Word class in Listing 1 is fairly simple -- it‘s a JavaBean with a constructor chain allowing users to create Words with various properties set. Also note that both its synonyms and definitions properties are intended to be read-only (that is, there is no setter for them). You can only add an instance of a Definition or another Word for a synonym.
In Listing 2, you see the related Definition class, which is similar to the Word class in that its exampleSentences property doesn‘t have a corresponding set() method:
package com.vanward.dictionary;import java.util.Collection;public class Definition { private Word word; private String definition; private Collection exampleSentences; public Definition(String definition){ this.definition = definition; this.exampleSentences = new ArrayList(); } public Definition(String definition, Word word) { this(definition); this.word = word; } public Definition(String definition, Word word, Collection exampleSentences) { this(definition, word); if(exampleSentences != null){ this.exampleSentences = exampleSentences; } } public String getDefinition() { return definition; } public void setDefinition(String definition) { this.definition = definition; } public Collection getExampleSentences() { return exampleSentences; } public void addExampleSentence(String exampleSentence) { this.exampleSentences.add(exampleSentence); } public Word getWord() { return word; } public void setWord(Word word) { this.word = word; }}
In Listing 3, you can see the same two classes defined in Ruby. Listing 3 does look quite different, doesn‘t it?
module Dictionary class Word attr_reader :spelling, :part_of_speech, :definitions, :synonyms attr_writer :spelling, :part_of_speech def initialize(spelling, part_of_speech, definitions = [], synonyms = []) @spelling = spelling @part_of_speech = part_of_speech definitions.each{ |idef| idef.word = self} @definitions = definitions @synonyms = synonyms end def add_definition(definition) definition.word = self if definition.word != self @definitions << definition end def add_synonym(synonym) @synonyms << synonym end end class Definition attr_reader :definition, :word, :example_sentences attr_writer :definition, :word def initialize(definition, word = nil, example_sentences = []) @definition = definition @word = word @example_sentences = example_sentences end endend
If there‘s one thing you‘ll notice from Listing 3, it‘s that Ruby‘s syntax is quite terse. But don‘t let its brevity fool you -- there‘s a lot going on in that code! First, both classes are defined in a module, which is essentially a package in the Java language. Moreover, I was able to define the classes in one file -- not two -- as required in the Java language. You‘ll also note that Ruby‘s constructors are named initialize, whereas in the Java language, constructors are named using the class name.


Back to top
Creating new object instances is different in Ruby. Rather than the new ObjectInstance() syntax used in Java code, Ruby actually supports calling a new method on an object, which internally calls the initialize method. In Listing 4, you can see how I create an instance of a Word and some corresponding Definitions in Ruby:
require "dictionary"happy_wrd = Dictionary::Word.new("ebullient", "adjective") defin_one = Dictionary::Definition.new("Overflowing with enthusiasm")defin_two = Dictionary::Definition.new("Boiling up or over")happy_wrd.add_definition(defin_one)happy_wrd.add_definition(defin_two)
In Listing 4, I "imported" the dictionary module with Ruby‘s require method (which can be found in the Kernel class). I then proceeded to create a new instance of a Word (ebullient) via the Object.new syntax. Even though I imported the dictionary module, I still need to qualify object instances, hence the Dictionary::Word qualification. I could have also dropped the Dictionary:: prefix code if I had written include Dictionary after the require clause.
Did you notice how I didn‘t specify a collection of Definitions or synonyms when I created the happy_wrd instance shown in Listing 4? I only passed in values for spelling and part_of_speech. I got away with that omission because Ruby supports default values for parameters. In Word‘s initialize method defined in Listing 3, I‘ve specified definitions = [] and synonyms = [] as parameters, which basically says to Ruby if they are not included by the caller, then default them to empty collections.
Note also inListing 3 how Definition‘s initialize method supports default parameters by setting example_sentences to an empty collection (word‘s default value of nil is Ruby‘s version of null in the Java language). Back inListing 1, I had to create three constructors to get that same flexibility from the Java language!
Now watch as I create a different Word instance with my flexible initialize() method, in Listing 5:
require "dictionary"defin = Dictionary::Definition.new("Skill in or performance of tricks")defin_two = Dictionary::Definition.new("sleight of hand") defs = [defin, defin_two] tricky_wrd = Dictionary::Word.new("prestidigitation", "noun", defs)
After I define two Definitions, I add them to a collection (which looks just like an array in the Java language). I then pass that collection to Word‘s initialize() method.


Back to top
Ruby‘s collections handling is amazingly simple too -- see the add_definition and add_synonym methods in the Word class? The << syntax is overloaded to mean add. If you look back to the Definition class inListing 2, you‘ll see that the corresponding code in the Java language is a much bigger mouthful: this.exampleSentences.add(exampleSentence).

Speaking of collections, isn‘t Ruby‘s [] syntax slick? If you‘re a Groovy user, it should look familiar to you as well.
Ruby‘s collection handling is extremely concise. In Listing 6, you can see how easy it is to combine collections (using the + operator) and access members (via [position]) -- and do so without the fear of things blowing up on you!
require "dictionary"idef_1 = Dictionary::Definition.new("Sad and lonely because deserted")idef_2 = Dictionary::Definition.new("Bereft; forsaken")defs = [idef_1, idef_2]idef_3 = Dictionary::Definition.new("Wretched in appearance or condition")idef_4 = Dictionary::Definition.new("Almost hopeless; desperate")defs_2 = [idef_3, idef_4]n_def = defs + defs_2 #n_def is now [idef_1, idef_2, idef_3, idef_4]n_def[1] # produces idef_2n_def[9] # produces niln_def[1..2] # produces [idef_2, idef_3]
The code in Listing 6 only scratches the surface of Ruby‘s collection handling!


Back to top
You may have noticed in both classes ofListing 3 that Ruby supports a shortcut notation for defining properties: attr_reader and attr_writer. Because I used this notation, I can set and get corresponding properties in my Word class, as shown in Listing 7:
require "dictionary"wrd = Dictionary::Word.new("turpitude", "Noun")wrd.part_of_speech # "Noun"wrd.spelling # "turpitude"wrd.spelling = "bibulous"wrd.spelling # "bibulous"syns = [Dictionary::Word.new("absorptive", "Adjective"), Dictionary::Word.new("imbibing", "Noun") ]# Danger! wrd.synonyms = syns = syns #Exception: undefined method `synonyms=‘...
Both attr_reader and attr_writer are not keywords but are actual methods in Ruby (found in the Module class) that take symbols as arguments. A symbol is any variable that is preceded by a colon (:), and what‘s even neater is that symbols themselves are objects!
Note that because I made synonyms read-only inListing 3, Ruby denied my attempt in the last line of code in Listing 7. Also, I could have written the property declaration code using the attr_accessor method to indicate that a property was both readable and writeable.


Back to top
Flexible iteration is one of the joys of coding in Ruby. Look at Listing 8, where I‘ve singled out Word‘s initialize() method:
def initialize(spelling, part_of_speech, definitions = [], synonyms = []) @spelling = spelling @part_of_speech = part_of_speech definitions.each{ |idef| idef.word = self} @definitions = definitions @synonyms = synonymsend
Something different is going on in the fourth line of Listing 8, for sure. For starters, I used brackets when I called the each method on the definitions instance. The each method is essentially like an Iterator in the Java language, but it‘s a bit more concise. In Listing 8, the each method handles the details of iteration and allows the caller to focus on the desired effect. In this case, I passed in a block stating the following: for each value in the collection -- that is, idef, which is an instance of Definition -- set its word property to self (which is the same as this in the Java language).
Listing 9 shows essentially the same line of code in the Java language (taken from Word‘s constructor shown inListing 1):
for(Iterator iter = definitions.iterator(); iter.hasNext();){ this.validateRelationship((Definition)iter.next());}
Let me acknowledge right now that Java 5‘s generics and new for loop syntax is much, much nicer than what‘s shown in Listing 9. Ruby does support Java‘s familiar looping constructs such as for and while; however, in practice, they are rarely utilized since most everything in Ruby supports the notion of iteration. For example, in Listing 10, look how easy it is to iterate over the contents of a file:
count = 0File.open("./src/dictionary.rb").each { |loc| puts "#{count += 1}:" + loc }
Any class in Ruby that supports the each method (like File) lets you iterate this way. By the way, Ruby‘s puts method (seen in Listing 10) is the same as the Java language‘s System.out.println.


Back to top
While I‘m on the subject of looping, let‘s take a closer look at a conditional statement found in the Word class ofListing 3. In Listing 11, I‘ve singled out the add_definition() method:
def add_definition(definition) definition.word = self if definition.word != self @definitions << definition end
Look closely at the second line of code. See how the if statement follows the expression? You certainly could write it normal-style, as shown in Listing 12, but isn‘t Listing 11 nicer?
def add_definition(definition) if definition.word != self definition.word = self end @definitions << definition end
In the Java language, if the body of a conditional is a single line, you can drop the brackets. In Ruby, if the body of a conditional is a single line, you can write expressions like the one shown inListing 11. Also note that the same conditional could also be written as definition.word = self unless definition.word == self, which uses Ruby‘s unless feature. Nice, eh?


Back to top
Because Ruby is a dynamically typed language, it doesn‘t require interfaces. Mind you, the power of interfaces is completely present in Ruby, but in a much more flexible manner. Affectionately known as "duck typing" (i.e., if it waddles like a duck and quacks like one, then it must be a duck!), polymorphism in Ruby is just a matter of matching method names. Let‘s compare polymorphism in Ruby and the Java language.
One way to capture the power of polymorphism in the Java language is to declare an interface type and have other types implement that interface. You can then refer to implementing objects as that interface type and call whatever methods exist on that interface. As an example, in Listing 13, I‘ve defined a simple interface called Filter:
package com.vanward.filter;public interface Filter { boolean applyFilter(String value);}
In Listing 14, I‘ve defined an implementing class, called RegexPackageFilter, that applies a regular expression for filtering purposes:
package com.vanward.filter.impl;import org.apache.oro.text.regex.MalformedPatternException;import org.apache.oro.text.regex.Pattern;import org.apache.oro.text.regex.PatternCompiler;import org.apache.oro.text.regex.PatternMatcher;import org.apache.oro.text.regex.Perl5Compiler;import org.apache.oro.text.regex.Perl5Matcher;import com.vanward.filter.Filter;public class RegexPackageFilter implements Filter { private String filterExpression; private PatternCompiler compiler; private PatternMatcher matcher; public RegexPackageFilter() { this.compiler = new Perl5Compiler(); this.matcher = new Perl5Matcher(); } public RegexPackageFilter(final String filterExpression){ this(); this.filterExpression = filterExpression; } public boolean applyFilter(final String value) { try{ Pattern pattrn = this.getPattern(); return this.matcher.contains(value, pattrn); }catch(MalformedPatternException e){ throw new RuntimeException("Regular Expression was uncompilable " + e.getMessage()); } } private Pattern getPattern() throws MalformedPatternException{ return compiler.compile(this.filterExpression); }}
Now, imagine that there are multiple implementations of the Filter interface (such as the RegexPackageFilter, a ClassInclusionFilter type, and perhaps a SimplePackageFilter type). To maximize flexibility in an application, other objects can now refer to the interface type (Filter) and not the implementers, as shown in Listing 15:
private boolean applyFilters(final String value, final Filter[] filters){ boolean passed = false; for(int x = 0; (x < filters.length && !passed); x++){ passed = filters[x].applyFilter(value); } return passed;}
In Ruby, interfaces don‘t matter! So long as method names match, you have polymorphism. Watch.
In Listing 16, I‘ve recreated the Java Filter types in Ruby. Note how each class isn‘t related (other than that they share the same method apply_filter). Yes, these two classes beg to be refactored into extending a base Filter class; however, I want to show polymorphism in action without the classes sharing a type.
class RegexFilter attr_reader :fltr_exprs def initialize(fltr_exprs) @fltr_exprs = fltr_exprs end def apply_filter(value) value =~ @fltr_exprs endendclass SimpleFilter attr_reader :fltr_exprs def initialize(fltr_exprs) @fltr_exprs = fltr_exprs end def apply_filter(value) value.include?(@fltr_exprs) endend
Note how in Listing 16, I can create a regular expression matcher in RegexFilter‘s apply_filter() method via the =~ syntax. (If you‘re a Groovy user, you should be smiling right about now, because Listing 16 shows how heavily Groovy has been influenced by Ruby!)
In Listing 17, I‘ve used Ruby‘s Test::Unit (which is like Java‘s JUnit) to demonstrate duck typing in action. Making an automated test in Ruby, by the way, is as easy as extending Test::Unit and adding methods that start with test. Similar to JUnit, right?
require "test/unit"require "filters"class FiltersTest < Test::Unit::TestCase def test_filters fltrs = [SimpleFilter.new("oo"), RegexFilter.new(/Go+gle/)] fltrs.each{ | fltr | assert(fltr.apply_filter("I love to Goooogle")) } end end
Notice how in the test_filters() method, I‘ve created a collection containing my two classes SimpleFilter and RegexFilter. These classes do not share a common base class, yet when I iterate over the collection, I can easily call the apply_filter() method.
Also note how easily Ruby supports regular expressions. To create one, you simply use the /regex/ syntax. Hence, my regular expression in Listing 17‘s RegexFilter is a capital G followed by one or more o‘s followed by gle.


Back to top
While Ruby doesn‘t have interfaces, it does have mix-ins. You can think of mix-ins as multiple inheritance without the multiple-inheritance headaches. Mix-ins are modules (which cannot be instantiated) that contain methods that a class can chose to include. Those module methods then become instance methods of the including class.
In JUnit, for example, the Assertion class is a concrete class with a boatload of static assert methods, which the all-too-familiar TestCase class extends. Hence, any implementing class of TestCase can refer to an assert method within its own defined methods.
Ruby‘s unit testing framework is a bit different. Rather than defining an Assertion class, it defines an Assertions module. This module defines a cornucopia of assertion methods, but rather than being extended, Ruby‘s TestCase class includes the assertion as a mix-in. Therefore, all those assert methods are now instance methods on TestCase, as you can see back inListing 17.


Back to top
As you‘ve seen, Ruby‘s syntax is quite different from that of the Java language, but it‘s amazingly easy to pick up. Moreover, some things are just plain easier to do in Ruby than they are in the Java language.
The nice thing about learning new languages, as programming language polyglots will tell you, is that nothing says they can‘t play together. Being able to code in multiple languages will make you more versatile in the face of both humdrum programming tasks and more complicated ones. It will also greatly increase your appreciation for the programming language you call home.
As I said at the beginning of this article, I‘m mainly a Java developer, but I‘ve found plenty of ways to include Ruby (and Groovy, and Jython ...) in my bag of tricks too. And I‘ve been able to do it without using Rails! If you‘ve written off Ruby on Rails because you don‘t actually need to build a shopping cart application in just four hours, take a closer look at Ruby on its own. I think you‘ll like what you see.


Back to top
Learn
"Programming in the Ruby language" (Joshua Drake, developerWorks, July 2001): A four-part introduction to programming in Ruby.
"Test-first Ruby programming" (Pat Eyler, developerWorks, May 2005): A tutorial introduction to unit testing with Ruby‘s Test::Unit library.
"Using the Ruby Development Tools plug-in for Eclipse" (Neal Ford, developerWorks, October 2005): One way to bridge the difference between the Java platform and Ruby.
"Fast-track your Web apps with Ruby on Rails" (David Mertz, developerWorks, June 2005): An inside-out look at the little platform that could.
"alt.lang.jre: Take a shine to JRuby" (Michael Squillace and Barry Feigenbaum, developerWorks, September 2004): For the OO strength of Smalltalk, the expressiveness of Perl, and the flexibility of the Java class libraries, try JRuby!
Programming Ruby: The Pragmatic Programmer‘s Guide (David Thomas and Andrew Hunt; Addison-Wesley, 2001): An online version of the now famous pick-axe book.
Ten things every Java programmer should know about Ruby (Jim Weirich, O‘Reilly Open Source Convention, 2005): What matters about Ruby, from a Java developer‘s perspective.
The Practically Groovy series (Andrew Glover, developerWorks): Learn all about Groovy -- a Ruby-inspired language for the Java platform.
The Java technology zone: Hundreds of articles about every aspect of Java programming.
Get products and technologies
The Ruby home page: Download Ruby.
Discuss
developerWorks blogs: Get involved in the developerWorks community!


Back to top


Andrew Glover is the CTO ofVanward Technologies, aJNetDirect company. Vanward helps companies address software quality early with effective developer testing strategies and frameworks, software metric analysis and correlations, and continuous integration techniques that enable development teams and management to monitor code quality continuously. He is the co-author ofJava Testing Patterns (Wiley, September 2004).


Back to top

Please take a moment to complete this form to help us better serve you.

Did the information help you to achieve your goal?

YesNoDon‘t know


Please provide us with comments to help improve this page:



How useful is the information?
(1 = Not at all,
5 = Extremely useful)

12345





Back to top

About IBM    Privacy    Contact
_xyz