Summary
Today, JavaOne 2007 showcased a complete open source
technology stack that lets you develop and
deploy web applications quickly and easily,
including JRuby, Rails, NetBeans, and Glassfish.
Advertisement
While JavaFX Script aims to simplify things for
web designers, a collection of technologies
including JRuby, Rails, NetBeans, and Glassfish
combine for an unparalleled application development
experience. This post focuses on those development
technologies.
I've heard a lot about how easy it is to develop
web applications using Rails, but now I've
actually seen it. At one of their several talks
on JRuby and Rails at JavaOne, Charlie Nutter and
Tom Enebo stepped through the process and took
the audience on a short tour of the application
structure.
At each talk, Charlie asked the mostly-Java audience,
"By a show of hands how many people really love their
development process for web apps?" There were
staggeringly few. But Rails developers, it seems,
are rabid about it. They love doing it. So let's
see how the other half lives.
Note: It's still an open question as to how
much they love maintaining an app they built
a while ago--or someone else's app. That's where
Java's readability really shines. I'd love to see
a straw pole on that topic, one day.
Contents
Why Rails?
Create and Test a Rails App
Architectural Overview
Develop Faster with NetBeans 6
Manage the Project with Mingle
Deploy on a Java Application Server like GlassFish
There are a number of reasons to like the Rails
framework for web development:
The "Convention over Configuration" mantra keeps
things simple. You follow some basic naming conventions
and the framework figures things out for you--so if you're
looking for Person with ID=Fred, Rails will go to
the database, access the People table, find the
row with right ID, wrap it's contents in a Person
object, and deliver it back to you.
The database specs are agnostic, so you
can easily migrate to a different database later.
That makes it easy to develop your app with a test database
and later deploy with a production version.
You can define database migration scripts that
make it possible to rollback to a previous version
of the schema for testing and bug fixing.
There are libraries for login, authentication, and
other common web application tasks.
Rails also gives you an agile development environment:
You have a fully functional app right from the
start that you can incrementally extend to add the
behaviors you need.
You can iterate quickly. Ruby is interpreted,
so there is no need to recompile the app, redeploy it,
or restart the server. You just make the changes you need
to make, then run your tests or try it out interactively.
That's all there is to it.
The testing harness is built into the framework. You
have a place for your tests right from the start, and
many of the code generating functions create an initial
set of tests--so you're encouraged to test as you develop--and
that is the key to keeping it fun.
Rails also gives you the benefits of Ruby. It takes a lot
less code to get things done in Ruby and Rails--to the
point that development time can be measured in days, rather
than weeks. Ruby has many features that conspire to
make you more productive. Even if no one feature
is compelling by itself, those features make Ruby great for
writing the "glue" that ties libraries and components into
an application:
Fully OO: Everything is an object, so there is no
need to remember different ways of doing things
for primitives.
Dynamic, but strongly typed: Once assigned,
a variable stays true to type.
Literals for regular expressions, lists, and hash maps.
Powerful string processing, including multi-line
strings and string interpolation (put #{x} in a
string, and the value of x is inserted inline).
Blocks: Snippets of code you pass around and invoke.
Closures: Snippets that know their context, so this
code knows about the File object, and knows to close it
when the block has finished executing:
File.open(someFile) { |f| ... }
"Duck" typing: If it quacks like a duck, then for
all intents and purposes, it is a duck. If it has the
methods you need, it works. You don't have to declare them
in advance. (But since there is no interface to remind you
to implement them, you have to do more testing--which is a
good idea in any case.)
Ruby is also a better language for metaprogramming
and building domain-specific languages (DSLs):
Open classes: You can literally "extend" a class
by adding behaviors you need to that class.
Module mix-ins: You add stuff to your class by
"requiring" a module.
Hooks like method_missing and method_defined that
let you implement dynamically adaptive behavior.
Reflection behaviors, so x.send(:y args) is
equivalent to x.y(args)
Ruby has a terrific ecosystem, as well. You not only
get Rails, you get a variety of other utilities that
take advantage of Ruby's ability to define a higher
level language that is tailored for a specific domain:
Rake: A powerful build utility.
Raven: Ruby's implementation of Maven.
Rspec: A testing utility in which each test
is literally a functional specification.
There certainly is a lot to like. (For more on the features
that make Ruby great, see the articles in the Resources.)
All of those features are terrific, and they got me
into Ruby. But knowing about them didn't help me get
started building a Rails app. After two Ruby
conventions, I still didn't know how to create
one. (Ok. So I could have read a book. I'm lazy. Shoot me.)
But JavaOne is a different kind of convention. I
don't know how the organizers do it, but they manage
to set things up so that most every presentation
explains the basics and demonstrates how things are done.
That makes JavaOne pretty much the best
educational opportunity in town. And
this year, a lot of time was devoted to other
languages that run on the Java platform, including
JRuby, Groovy, Tapestry, and JavaFX Script.
So this year, I got to see how a Rails app is constructed.
Here's an overview of the process:
(j)rails myapp -- setup the project directory
(j)ruby script/server -- start server and see basic page
(j)ruby script/generate -- create parts of the app skeleton
...do a little editing...
...repeat for other parts of the app...
(j)rake -- build the app
...try it out...
What follows is a mashup of a couple of demos I saw.
Some of the details are bound to be wrong, but the
notes should serve to outline the process.
The first step was to setup the application:
rails myapp
There must have been a configuration step to make
that step work with jruby, but we didn't see it.
The next step was to start the server:
jruby script/server
The default server, Webrick, is built-in, so that's
all you needed to do. Even with no code added, it was
now possible to view http://localhost:8080 and see an
initial start page.
Next, they generated the username/password action for
logging in, along with the unit tests for that behavior:
jruby script/generate controller test hello
The next two steps were single line edits in
very small generated files. The first added
the variable @hello="..." to the action script:
vi app/controller/tests/(target file)
The second displayed the contents of that variable
in the view file (an rhtml file that looks a lot
like a JSP page):
vi app/views/hello.rhtml
The call looked something like this:
<% someDisplayFunction @hello %>
To configure database access (an optional, but typical
part of a Rails app), they copied a small configuration
file, to save a bit of time:
cp ../database.yml config/
Then, just for fun, they took a look at the place
where the Rails conventions are defined:
vi config/environment.rb
Next, they set up for database migration, to show
how that works:
jruby script/generate migration person
vi db/migrate/001_person.rb
As you make changes to the database, such scripts let
you migrate the schema and rollback to a previous
version to fix the bug with a command like this:
rake db:migrate
The next step generated the scaffolding for CRUD operations
(create, read, update, delete):
jruby script/generate scaffold person
It was now possible to take a look at some of the
generated scripts:
Once you have the application skeleton created, you'll
need to add some code. NetBeans 6 will make that process
a heck of a lot easier.
Charlie Nutter's comment underscores that point:
There is a lot of corporate interest in Ruby IDEs, these
days and a lot of work going into tools support.
NetBeans is the furthest ahead. Some people are even
leaving emacs, to get those features in NetBeans.
Actually, NetBeans is supporting more than just Ruby.
They've been putting in generalized support for dynamic
languages and letting users customize the pretty
printing behaviors (among other things) by making
choices in a dialog--a lot better solution than having
to write a plugin that uses the NetBeans APIs.
With respect to Ruby, Tor Norbye from Sun Microsystems
demonstrated the many capabilities that make life easier
for (J)Ruby developers:
Project types for Ruby & Rails
Integrated gem management
Integrated rake builds
Code completion
Go to definition
Syntax coloring
Background error detection with red underlining
Gray for unused variables
Bold for a method
Cursor on an variable highlights it everywhere it exists
Cursor on method call highlights all exits
Ctrl+shift+F to reformat code
As a long time IDE user in Java-land, I have to say
that I desperately missed those capabilities when I
began coding Ruby. I loved Ruby anyway, but I felt
handicapped. With NetBeans, they're back in my arsenal.
Life is going to be good.
To switch between Ruby and JRuby, you change a path
in a configuration file. That turns out to be
important in the early releases, because debugging
works better with native Ruby, for now. But in most
other respects, you'll probably want JRuby. (More
on that subject in a moment.)
For debugging, you get all of NetBeans' normal
capabilities:
Call stack
Local variables view
Evaluate an expression
See threads and switch between them
To make all of that work in the early releases,
there are several things you to need to do:
Mingle is a web-based application designed for
software projects. It includes functions for
project management, issue tracking, source code
versioning, and requirements analysis.
It's targeted at agile IT projects, and built by
agile developers. So it's worth a look. But it
holds a special place in the heart of the JRuby
developers because, when it came time to create
reliable, scalable deployments, they turned to
JRuby. Their reasoning is given at:
http://studios.thoughtworks.com/2007/5/7/mingle-to-run-on-jruby
Once you have your application built and tested,
you need to deploy it to a production server. But
even though it's easy to develop a Rails application,
it can require a lot of work to make it perform well.
For serious scalability, you'll probably want to deploy
on a Java Application Server--for example, the open
source standard in that category, GlassFish. (That
will be especially true when JRuby achieves its goal
of support for database pools and other enterprise
features.)
You may still need to do some performance tuning
eventually, but a fast server can delay the day it
becomes necessary, and make the tuning pay off that
much more when you do it.
As noted by the JRuby speakers, deploying on a Java
Application Server also gives you a major advantage
when you're sneaking Ruby into the enterprise:
You can deliver a WAR file to the IT department,
and they can deploy it the same way they deploy
any other Java app. They don't have to implement
or support some new infrastructure.
JRuby is just Java byte codes under the covers, after
all. And when you're delivering a WAR, that's what
you're delivering--Java byte codes.
To make that work, you use "GoldSpike"--the tie that
binds the rails from east and west, or in this case,
Ruby and Java. The process is pretty simple:
Install the GoldSpike gem from the jruby-extra site.
(See the Resources).
Set up a config file with something like the following:
maven_library, "rome', "rome'. '0.8'
Note: Maven is the Apache Build manager and ROME
is a set of Java utilities for Atom/RSS feeds. It's not
clear from my notes what this step is doing, but I'm
leaving it in with the expectation that I'll be able to
clarify later.
You then need to set an environment variable:
RAILS_ENV=production rake db:migrate
Next, you issue the Rake command to create the war file:
rake war:standalone:create
That command pulls in the Rails app, along with any gems
and java libraries it uses, and puts everything into a
single, standalone WAR file that is ready to deploy on
your favorite Java application server.
To deploy the WAR yourself, you can issue a command like this one:
So, Rails is good framework for web applications. But
why run it on JRuby, rather than native ruby?
Possibly the most important reason is so you can deploy your
Rails application on a Java Application Server, as noted above.
But there are several other reasons for using JRuby, as well:
Installation: Windows installations work fine for
native Ruby, and Unix installations work well when they
go to the expected location.
But when I installed Ruby in a non-standard
directory on a Solaris system, several build files had
to be modified--and additional modifications were needed
to enable gems. I got it working, with the help of
the many friendly people on the mailing list, but I
can't say I fully understand what I did, or could
easily replicate the process. Installing JRuby, on the
other hand, is a study in simplicity: Download it. Run it.
That's all there is to it, as long you have a Java runtime
installed somewhere on your system.
Localization: JRuby takes advantage of Java's
unicode framework for fully localizable applications.
Capability: Java has a huge volume of well-documented,
tested libraries, and you can access them all from JRuby.
Scalability & Reliability: The Java platform's native
threads trump Ruby's green threads. And the HotSpot
compiler/interpreter gives JRuby a speed advantage that
JRuby's developers have taken advantage of. (Those speed
advantages apply to all JRuby scripts, in addition to
web apps.) JRuby is
basically on a par with native Ruby today, but performance
has mostly been secondary to compatibility, until now.
That is next major focus for the project. And in the future, byte
code changes are under development with JSR 292 that will
allow for even more significant code optimization.
Startup speed for Java programs: When a JRuby script
is launching Java apps, there is no start-up overhead,
because the virtual machine is already running. That makes
JRuby the ideal way to launch and manage Java platform
processes--for example, in a Rake build file.
The one item in that list that needs more elaboration is the
way you access the Java libraries from JRuby. Here is a quick
snapshot of the process, using the example the JRuby developers
presented:
jirb>
include Java -- now have access to java libs
import javax.swing.JFrame
import javax.swing.JButton
import java.awt.event.ActionListener
frame = JFrame.new("Quick App")
frame.set_size(300,300)
frame.show
button = JButton.new("Press Me")
frame.add button
class MyListener
include ActionListener
def actionPerformed(even)
event.source.text = "Don't press me again."
end
end
button.add_action_listener MyListener.new
frame.show
Voila! You now have a little GUI app in a few lines of code.
Notes:
jirb is the JRuby version of the interactive
Ruby interpreter, irb.
You can use Java's CamelCase method names,
or the Ruby-standard underscore-versions,
like button.add_action_listener.
JRuby has shown remarkable progress in the last year
or so. It has pretty well achieved it's first goal:
Full compatibility with the native Ruby implementation.
(No mean feat, considering that the only "spec" is
embedded in the C-language sources.)
The developers are now closing in on their second
major goal: exceptional performance. (On many measures,
it is already faster. So now they're zeroing in on the
remaining areas that need improvement.)
Their next major targets are to resolve the only
remaining areas of incompatibility: Those modules
that use native C extensions. Since they're not
written in Ruby, the Ruby compiler doesn't help.
They have to be rewritten in Java.
One area in which that impact is keenly felt is in
database support. MySQL works great, and Derby
works ok, along with some other databases, but
there are some that do not work at all. Hopefully,
the open source community will be able to help with
that endeavor.
Beyond that, JRuby's developers intend to focus
on the enterprise-edition capabilities that are
needed for massively high-performance, high-reliability
apps. For example:
Database pools
Stronger WAR deployment
Finally, they look forward to integrating with more of
the native ruby libraries like the Mongrel web server,
Hpricot, and Rmagick.
All told, it's a very strong story that figures to get
even stronger, with time.
It's good to read there are many people using Ruby/ RoR and loving it. It's a sad fact that due to some frameworks and enterprisy patterns that were pushed as the way to go for J(2)EE in the last few years, many people lost their appetite for working with Java, and found a refreshing alternative in Ruby/ RoR.
However, there are a couple points I'd like to let off my chest (all mho, no offense intended).
The first is that the focus in the article - as often -, is on quick quick quick. That makes for impressive demo material for sure, and I hear that RoR folks are doing very well in RAD races etc. but it's just part of the story. The first few weeks/ months are rarely the problem with software trajectories. It's great if you can achieve short time-to-market, but what you (can) do after the first release is what counts. I would expect, and I can in fact back this opinion up by hearing stories people who used RoR for non-trivial projects, that it's easy to get into an incredible maintenance mess with RoR (or anything that let's you develop quick and dirty). And I mean maintenance in the broadest sense. What happens when after say 3 months you start getting new developers in the project? What happens when the first non-trivial change requests come in?
Also, I've been using Ruby for a couple of sysadmin related tasks this year, and I must say I think it's a lovely language (though certainly not perfect). But I also found some things pretty frustrating. You can't 'discover' an API by just browsing and analyzing (e.g. call/ hierarchy views) it like you would do with Java. One single typo often results in hours wasted on figuring out what went wrong. No restarting needed? Well, that's great, but you'll need it as in my experience you'll spend much more time hunting down stupid little issues (like again, typos) than working on the larger problems. Maybe this is something that gets better when you use Ruby more, I don't know.
Finally, I might be weird, but I'm wondering how many people use RoR, look back at the code they wrote and go 'whoa, this here is a fantastic, elegant piece of code'. Maybe I have a strange taste, but whereas there are is a lot of elegancy to be found in Ruby, imho, RoR seems to be all about just getting the job done with the least number of lines. The convention over configuration constructs indeed are nice, but other than that, finding proper abstractions and avoiding code duplication etc doesn't seem to be a top concern for RoR developers judging from the tutorials I've read. I'm hardly an expert though, so if anyone reading this thread wants to give some examples of RoR code that rocks, that would be great!
> Also, I've been using Ruby for a couple of sysadmin > related tasks this year, and I must say I think it's a > lovely language (though certainly not perfect). But I also > found some things pretty frustrating. You can't 'discover' > an API by just browsing and analyzing (e.g. call/ > hierarchy views) it like you would do with Java.
I tend to do this with object.methods.sort
and
ri <whatevermethod>
but then again I normally read through through source code quickly before starting to do this kind of thing, and you need to be able to get a library up to the point where you can look at this.
> One single typo often results in hours wasted on figuring > out what went wrong.
I suspect I already can tell without looking at your code: You did not write unit tests.
With unit tests in Ruby, I have never had it take hours to find an error.
I also think that using unit tests is more or less a requirement for writing larger projects in Ruby; Ruby is fairly "forgiving" on letting you do things to start with, so the unit tests are a really big help for keeping things structured.
> but then again I normally read through through source code
I do that as well. And for navigating through the source code imo, hierarchy views etc help a lot.
> I suspect I already can tell without looking at your code: > You did not write unit tests.
Yep, I didn't. I'm not against unit testing per se, but I feel that with Ruby you need to write unit tests for things that would simply be caught by the compiler if I would use say Java. And in my particular case, I really was just writing some simple patch programs. Not the sort anyone would be writing loads of unit tests for unless you have something to prove ;)
> With unit tests in Ruby, I have never had it take hours to > find an error. > > I also think that using unit tests is more or less a > requirement for writing larger projects in Ruby; Ruby is > fairly "forgiving" on letting you do things to start with, > so the unit tests are a really big help for keeping things > structured.
It looks like that yes. Writing unit tests is good in general, but in my experience you can over-do it, in which case you end up with brittle code where you spend more time refacoring unit tests than your actual business code at some point. Anyway, that's off topic. Let's agree that writing unit tests from the start would have prevented me having to track down syntax errors later on. :)
There is an even simpler way to build highly interactive rich web applications, using Java, web start and db4o: no maintenance, no conventions, java objects are stored directly in the database, applications are automatically updated, there is no need to do anything else other than code.
Db4o is such a nice product, I think Sun needs to buy it and put it in the SDK.
> It's good to read there are many people using Ruby/ RoR > and loving it... > > However, there are a couple points I'd like to let off my > chest (all mho, no offense intended). > > quick quick quick...is just part of the > story. The first few weeks/ months are rarely the problem > with software trajectories (but) what you (can) do > after the first release is what counts. > You're absolutely correct, of course, but I think I'd like to call attention to several points:
1) In many situations, quick is *really* important. You can always sneak in a few days of development time for a project of the heart. A great manager will buy you a few weeks. But for that project to take on a life of its own, you need something that people use in short order--something useful enough that they want to see it improved. The ability to create them faster means that more of them will come alive.
2) NetBeans and other IDEs are making it possible to browse the APIs interactively. That's a huge win.
3) With JRuby, all of those really well-documented, browsable APIs become accessible, as well.
4) Testing is your friend. I mean, testing is really, really, *really* your friend. Testing keeps bugs shallow. Tests make it possible to experiment with someone else's code, too, so you can verify your understanding and try things out with confidence.
But for your own coding, the primary advantage of continuuous testing is that it keeps bugs *shallow*. A bug is always in the last 5 or 6 lines of code you wrote. It's never buried way down the call chain where it takes weeks to find. That's *why* testing makes development fun--that, and the fact that you can refactor at will, building elegance into the project organically.
RoR makes it possible to write an only "moderately" crappy quick prototype for a "skunk-works" type project. Which you need because your management is stupid and short-sighted. And it's all held together by unit tests which you wrote, by definition, with no customer input and no requirements. So they aren't all that good either...
> RoR makes it possible to write an only "moderately" crappy > quick prototype for a "skunk-works" type project, which > you need because your management is ... short-sighted. > Often the case, granted. But even when management has good long-range instincts, it's often necessary to ensure you're on the right track--and you won't discover the real problems with your conceptual design until you tackle it--even in miniature.
> And it's all held together by unit tests > which you wrote, by definition, with no customer input and > no requirements. So they aren't all that good either... > Major disagreement, here. I get a ton of customer input, even for skunk works projects. I toss out design ideas as well as functional concepts. Once I have a good idea what I'm doing, I write the user guide *first*. Then I let them review that, to see what they think. *Then* I start writing the app. Unit testing makes that process a whole lot more fun--especially late in the project. Instead of having to live with my original crappy design because I'm afraid to change it, I can refactor to my heart's content. The unit tests keep me sane, and let me morph the architecture into one that accepts change *easily*. Not to mention bugs stay shallow. Coding like that is just plain fun.
If you have the customer input and more or less understand the requirements before you start, you're doing o.k. I was thrown off by your "couple of days" comment, which must just count coding, not gathering customer input.
I find that many unit tests are to test exceptional conditions, and without some requirement you often have no idea how to handle them.
> Once I have a good idea what > I'm doing, I write the user guide *first*. Then I let them > review that, to see what they think. *Then* I start > writing the app.
So glad to see someone else who believes in writing the user guide first. (top-down...old-fashioned but still valid) The user guide serves as a high-level design document, and when you've finished your cycles of coding/testing and guide updates, your documentation is practically done.
> So glad to see someone else who believes in writing the > user guide first. (top-down...old-fashioned but still > valid) > The user guide serves as a high-level design document, and > when you've finished your cycles of coding/testing and > guide updates, your documentation is practically done. > Thanks. The user guide is really the functional spec for the app--only instead of being written in spec-eze, it tells exactly how you'll work with it.
Working on that document also gives me a chance to ask myself, "how will I do this or that thing I'm contemplating?". I'll often send queries to my design advisors, and follow up documentation leads far enough to get a good sense of the solution will work.
All of the information that results from that process gets recorded in a parallel document, consisting of design notes. That document describes the alternatives I've evaluated, which ones I've chosen, and why.
Once coding begins, I revisit the document anytime I need to refresh myself on the planned solution for problem. But it's generally not a complete architecture document, so I don't tend to keep it up as I'm coding.
So when coding begins, I tend to use the design notes for reference only. I generally don't keep them updated to reflect the complete architecture--I just add a quick note if I find a better way to do something.
The user guide, on the other hand, stays updated the whole time. If I'm going to change functionality, I put the changes in the user guide first. During that time, I'm thinking out the interface and the implementation, as well as the command line options, APIs, or other interface mechanisms.
At the end of the project, then, I have a complete user guide ready to go after a few last minute fixes. When I look it over, I am usually surprised to find a ton of usage information that was obvious to me when I was working on a particular feature, but which had long since escaped my mind.
In other words, were I to try to create a good user guide at the end of the process, I would be missing so many details that it would be a very difficult thing to do--which helps to account for the many projects that are so seriously deficient in documentation. People code with the best intentions of "writing it all up later", but the fact is that we can only keep a few things in our head, and many of the important details are nothing but a distant memory when "documentation time" comes around. (To figure them out, you have to go back and reexamine old code--but after months of working with that code, the last thing you can bring yourself to do is to wallow in it some more.) :_)