If the thought of diving into yet another hot new technology gives you a headache, I sympathize. I’ve stuck mainly to PHP and Perl for years, happily ignoring most other development fads.
However, I’m rethinking this as my projects get steadily more complex. After hearing a full-time developer talk about the limits he bumped into when using PHP for larger database-driven Web projects, and how useful a development framework turned out to be, I decided it was time to look seriously at some more robust tools.
There are several interesting options out there if you’re looking for a framework — something to give structure to your applications and help you do more with less code. Rails for the Ruby programming language is probably the most popular framework right now, and I was curious to find out why.
But can a part-time coding enthusiast really learn Ruby on Rails (RoR)?
SitePoint Pty.’s Build Your Own Ruby on Rails Web Applications by Patrick Lenz pledges that it’s written for both hard-core programmers and the less experienced — even those who know only HTML and CSS. “Web development has never been easier, or as much fun as it is using Ruby on Rails,” Lenz says.
Fun? OK, I’m ready to see if RoR can indeed be faster, easier and more fun than some recent projects I’ve sweat through using PHP and Perl.
Over several weeks, I went through a portion of Lenz’s book, as well as a few other resources. I was, in the end, able to create a simple application to pull items from a MySQL database. And I came to see why coding Ruby applications on Rails has become an important Web development trend.
Why use a framework like Rails?
Ruby on Rails is all about adding structure and built-in functionality to your projects, with the goal of limiting dull, repetitive and confusing coding. A caution, though: At the beginning, it seems to add complexity. It ends up paying major dividends down the road, but you have to hang onto the prospect of long-term benefits when you first dive in.
I liken it to doing your taxes. Say someone offered you a beautifully crafted, perfectly constructed file system for tracking your finances — one that was intuitive and easy to use but had a bit of a learning curve. If you’re single, fill out the short form and take the standard deduction, it would probably be easier to stick everything in a folder and deal with it in April. Likewise, for very simple scripts and applications, there’s no need for a development framework like Rails.
But now imagine you have a family. And changed jobs. And have rental property income. And some self-employment income. Now that file system starts to look more appealing. So it is with Rails. As your application becomes more complicated and you want to keep adding features (or members of a programming team), a well-thought-out, preexisting structure can be mighty handy.
“For me, discovering and learning Rails had a similar effect to Google Maps; it seemed almost too good to be true,” Rob Orsini writes in Rails Cookbook. “Rails handled all of the things that I found most unpleasant about Web development automatically or so elegantly that they were no longer painful. … I’ve often said that the simple act of creating a Rails project felt like there was a room full of experienced software veterans imparting their knowledge about sound application design, ensuring that my project started off in the right direction.”
I can see this even in a simple app I coded for myself in PHP. It started out as a few pages of PHP and a simple MySQL database. Then I wanted a mobile version to view on my cell phone. Then I needed a couple of new fields in the database. Then I changed Web hosts. You get the picture.
An external configure file and style sheet took care of some of those issues, but it turned out there were subtle differences in some SQL commands used to access my database. Suddenly, I needed to change SQL code sprinkled throughout numerous pages. Yes, I could have structured my code more modularly at the outset. But I didn’t. That’s one advantage of a framework — it makes me write more modularly, counteracting my natural tendency to just go the quick-and-dirty route.
Ruby on Rails “lets you write beautiful code by favoring convention over configuration,” the framework’s official Web site promises. In other words, instead of having to come up with structures for your application, Rails does much of that work for you. That means first off, you don’t have to create a separate file for functions you’ll be using throughout your app; Rails generates files like that for you automatically. It’s not all that much work to create such files, of course. But since every Rails project uses the same file structure, everyone on a team knows where to find them — even programmers who join a project midstream. It also makes it easier to maintain your own code when you haven’t looked at it in awhile.
Also, if you use the Rails naming conventions, as you create new “instances” of objects, they automatically get mapped to an appropriate database row — without having to write any SQL code. This is really handy if you’re doing a lot of data storage and retrieval.
Rails even elegantly connects objects in different database tables with a few lines of easy-to-understand code. If I’m saving stories in a database and will be searching later to see which ones were published on a certain date, I can easily define at the outset that PublishDate has_many :stories and Story belongs_to :publishdate in the files that define PublishDate and Story. It looks like this:
Class Story < ActiveRecord::Base
belongs to :publishdate
end
Class PublishDate < ActiveRecord::Base
has_many :stories
end
(ActiveRecord is a predefined Rails class.) With those simple, human-readable lines of code, a relationship is set up allowing me to find what stories have been posted on a given date. How? If mydate is a variable representing a specific PublishDate, I find all the stories on that date just by
mydate.stories
That’s it — a lot less code than I’ve been using to do database queries from PHP.
Another RoR development principle, Lenz notes, is “don’t repeat yourself.” I’ve been violating that ever more egregiously, and will be glad to get help streamlining my code. Discovering after the fact what code snippets I should have made into repeatable functions is a serious time sink, and I haven’t found the knack of sketching that out ahead of time.
Ruby basics
Ruby, the object-oriented programming language used by the Rails framework, was created by Yukihiro “Matz” Matsumoto in 1995. Everything in Ruby is an object — every variable and even such literals as “Hello World.”
To begin coding a Ruby on Rails application, I first need RoR on my system. On Windows, Lenz recommends using Curt Hibbs’ simple-to-run Instant Rails. Good idea. After a couple of minutes unzipping 18,000 or so files, Instant Rails quickly appears on my Windows laptop. Updating Rails is quick and painless with the command
gem install rails
–include-dependencies
Installing RoR on a Linux machine is a bit more manual. Without the complete Instant Rails package, you need to first install the Ruby programming language on your system, and then the Rails framework (which was written in Ruby by David Heinemeier Hansson as part of a project-management Web application called Basecamp). There are some links explaining how to install RoR on various Linux distros on the project wiki.
First impressions
I’m impressed by the elegance of some simple Ruby commands. Want to find the length of a string? Just apply the length method to the string, with a period to separate the object from its method:
“Sharon Machlis”.length
Type that into an interactive Ruby console and you get back 14. (An interactive Ruby console will run commands as you type them in. Open a Ruby window and type the command irb to get to interactive mode.)
Ruby commands don’t require a semicolon at the end, which is appealing considering all the PHP and MySQL semicolons I’ve left off over the years. And if you use a method that doesn’t need a value passed to it, you don’t have to add empty parentheses like you do in a lot of other languages.
Ruby is more intuitive than other languages, developer Jay Powers at Vermonster LLC told me recently — the code is easier to read and understand. He says it takes him less time to code major projects with Ruby on Rails than PHP or Perl because there are more methods built in.
That’s the good news. The more daunting discovery is that it’s initially more complex to set up classes than I’m used to in PHP.
When you define a class, you typically need some “instance methods” and some “instance variables” to store data. These variables always start with an @ symbol, as in @name or @ssnum.
Here’s how Lenz does it for the class Car (everything after each # mark is an explanation I’ve added).
class Car # creates a new class named “Car”
@mileage = 0 # sets the initial mileage for each car to 0
def set_mileage(x) # begins a method to alter the mileage
@mileage = x # sets a new value for the mileage
end # ends the method
def get_mileage # begins “instance method” that lets us read the mileage data out of the car object
@mileage # the method will return the value of this variable
end # ends the method
end # ends the class
@kitt = Car.new # now we’ve got a car, the Car object named kitt
@kitt.set_mileage(5667) # stores the mileage data of 5667 for kitt the car
@kitt.get_mileage # reads the mileage information for kitt the car
At the outset, my old way of doing things, not using objects in PHP, seems a lot simpler:
$kitt_mileage = 5667; # creates a PHP variable $kitt_mileage and sets it to 5667
echo $kitt_mileage; # prints out the value of the variable $kitt_mileage
However, manipulating those variables down the road would end up being more complicated. My colleague and fellow programmer Joyce Carpenter assures me that the front-end investment in object structuring will pay dividends. I forge ahead.
Ruby has many typical features I expect in a programming language, such as arrays, associative arrays (often called hashes), strings, conditionals, loops and so on.
One point Lenz makes early: To loop through data in an array, Ruby programmers are more likely to use “blocks” than things like for/else/while/until loops. “However, they’re also one of those things that take a while to ‘click’ for Ruby newcomers,” he notes. He’s right on that. I don’t see how a construct like
[ kitt, herbie, batmobile, larry ].each do |car_name|
puts car_name.mileage #
end
is vastly superior to something like
$car_array = array(“kitt”, “herbie”, “batmobile”, “larry”);
foreach ($car_array as $key => $value){
echo “$value\”;
}
in PHP. I need to keep reading.
Hands-on Rails: Come code along
Now that I’ve gotten a small taste of Ruby, it’s on to the Rails development framework. To start a new project in Rails, you type
rails yourprojectname
in a Rails console.
If you’d like, follow along and create your own project. If you’re using InstantRails on Windows, click on the I at the upper left and select Rails Applications –> Manage Rails Applications. That gives you the option of creating a new Rails app, which opens a shell in the correct rails_app directory.
I type
rails myfavorites
This creates a bunch of new directories — a dozen at the top level plus even more subdirectories.
If you’re used to starting small with a couple of unstructured files, “this wealth of subdirectories can be overwhelming at first,” Lenz says.
Yup.
But, he explains, “a lot of thought has gone into establishing and naming the folders, and the result is an application whose file system is well structured.” This makes possible a lot of automatic associations between variables, objects, folders, databases and the like. The upside? You have a lot less work to do.
Architecture fundamentals
Rails follows the Model-View-Controller (MVC) architectural principles. Basically, Lenz explains, models handle data and business logic; controllers deal with application logic and information being passed to the user interface; and views handle GUI and presentation HTML that displays in a browser.
In Rails, the model, view and controller files are created in their own automatically generated subdirectories, one of the dozen-plus that live under the auto-generated main project directory, app/project.
If you’re used to merging everything together in a single file, as I am, it takes some getting used to. Initially, the concept seems downright silly: Create one file in my app/models directory to set up the data structure; create another file in my app/controllers directory to set up calculations, database queries and other logic; create yet a third file in my app/views directory with the HTML code. All this just to accomplish the simple task of displaying something. Good grief, I can do all that with a few lines of code in a single PHP or Perl file and be done.
However, what looks like a lot of initial complexity and constraints is actually the foundation for less repetitive, more maintainable code. Follow the rules, and I’ll know where each part of my application lives; I won’t have to make changes by hunting for code scattered through a bazillion separate .php files.
As soon as a project is created, Rails sets up three different environments for it: development, test and production, expecting separate databases for each. “Configuring the database for a Rails application is frighteningly easy — all of the critical information is contained in just one file,” Lenz assures.
He’s right, it’s pretty slick. The file database.yml is one of many that are generated automatically when I issue the rails myfavorites command. Database.yml lives in the config directory, another one created when you first start a Rails project. Since I called my project myfavorites, my database configuration file contents look like this:
# MySQL (default setup). Versions 4.1 and 5.0 are recommended.
development:
adapter: mysql
database: myfavorites_development
username: root
password:
host: localhost
test:
adapter: mysql
database: myfavorites_test
username: root
password:
host: localhost
production:
adapter: mysql
database: myfavorites_production
username: root
password:
host: localhost
Alas, the databases themselves aren’t created automatically when I issue the rails myfavorites command, so my next step is to create them in MySQL.
In my InstantRails command-line console, I type in
mysql -u root
CREATE DATABASE myfavorites_development;
CREATE DATABASE myfavorites_test;
CREATE DATABASE myfavorites_production;
and then exit. (The exit part is important, otherwise you’ll still be in MySQL when you’re trying to execute Rails commands.)
If you don’t like the command-line SQL interface, Lenz suggests the graphical MySQL Query Browser from MySQL AB that runs on Windows, Mac OS X or Linux. Another option is the open-source project phpMyAdmin.
Reminder: If you’re doing a project with another name, it’s important to use the Rails naming convention here (one of many examples of the Rails “convention over configuration” mantra). The root name of each database has to be the same name as whatever you’ve named your Rails project (the one I’m calling myfavorites), and it’s got to use the _development, _test and _production endings. Otherwise, you start losing Rails’ built-in associations, such as those in the database.yml configuration file.
As Lenz describes it, Rails implements the MVC concept with several key components. ActiveRecord, the model, “handle(s) all of an application’s tasks that relate to the database.” I no longer have to worry about setting up connections to my databases whenever I need to do a query. Instead, a Rails component handles that. Useful but not extraordinarily labor-saving, since most languages have pretty structured methods of setting up that initial database connection.
Much more interesting is the way Rails handles creation of tables that map to classes. “Rows map to individual objects, and the columns map to the attributes of those objects,” says Lenz. Of course, I could go into MySQL directly and set up tables, but this bypasses some of the useful tools the Rails framework offers.
Since my little application will hold stories in a database, I need a model to match. I change to the myfavorites directory (don’t forget that part) and run the command
ruby script/generate model Story
You can create a model with the name of your choice, but it should be capitalized. My command, using the model name of Story, creates several files, including story.rb, which will store the class definition for the Story model as well as other files that are set up for testing and adding data.
Creating a model
One of these autogenerated files is what’s called a database migration file. This can add tables to my database and fields to my tables within the Rails framework. Each time I make changes to my database structure, I use another migration file. They’re numbered, making it easy to roll back my changes, or decide later that I want to add, drop or alter fields.
Inside the db/migrate folder, I open 001_create_stories.rb in a text editor (not a word processor — you don’t want extra characters added to your code that can cause problems when running scripts). This file was also auto-generated when I created the Story model.
Text editors for Ruby
It’s helpful to use an editor that knows and color-codes Ruby syntax. There’s one packaged with the Ruby Windows installer called SciTE.
Another option, ActiveState Software Inc.’s free Komodo Edit, handles Ruby syntax and is available for Linux, Mac OS X and Windows. UltraEdit ($50) from IDM Computer Solutions Inc. is another popular text editor that handles Ruby. There is also a free, open-source RoR development environment called Aptana RadRails that runs on Linux, Mac OS X and Windows.
Lenz also suggests Eden Kirin’s free ConTEXT on Windows and Allan Odgaard’s TextMate (approx. $40) on Mac OS X. According to Lenz, TextMate is “the hands-down winner in the Rails code editor popularity contest.”
Rails project step by step
If you’re working on your own project, with whatever text editor you’ve chosen, you can edit that 001_create_yourmodelname.rb file to add a table to your database and fields to your table.
My 001_create_stories.rb file comes prepopulated with this:
class CreateStories < ActiveRecord::Migration
def self.up
create_table :stories do |t|
# t.column :name, :string
end
end
def self.down
drop_table :stories
end
end
I add the bolded lines below to create fields in the table:
create_table :stories do |t|
t.column :contentid, :integer
t.column :headline, :string
t.column :summary, :text
end
They’re now part of the new self.up method. Those self.up instructions are executed when the migrate command runs the file. (The automatically generated self.down method deletes the table, which will only be executed if I want to roll back and undo the table creation.)
I return to my Rails command shell within the myfavorites directory, and run the command
rake db:migrate
to execute my migration file. I take a peek via phpMyAdmin and, sure enough, a stories table has been created with appropriate rows: contentid, headline and summary, as well as an autocreated primary key of id.
Now, each new story object I use in my application can easily be saved as an entry in my stories table.
Next, I generate a Story controller and an associated view named index by running this command in a Ruby console:
ruby script/generate controller Story index
By running the script/generate controller with the options Story and index, Rails generates a Story controller associated with my Story model and an index file that will handle the presentation HTML to display information to users.
First look at the results
Now the bare bones of all three parts of the MVC are in place. I can see what the default looks like by starting the WEBrick server (included with Ruby on Rails) with the command
ruby script/server
and going to http://localhost:3000/story in a browser.
If everything was created properly, I should see this default text in my browser:
This, Lenz explains, shows the connection between controller and view is working, because the index view can display information from the controller file.
The joys of scaffolding
For fun, as Lenz suggests, I next go into the story_controller.rb file (in the app/controllers directory) and edit it so it says:
class StoryController < ApplicationController
scaffold :story
end
instead of the autogenerated
class StoryController < ApplicationController
def index
end
end
Now I’m impressed. With that one line of code, I get a quick and dirty interface to my database where I can add data.
Now I enter a couple of stories from Computerworld.com into the myfavorites database:
Contentid: 106458
Headline: QuickStudy: Ruby on Rails
Summary: Ruby on Rails is a software development environment with the overall aim of making programming both more fun and more productive.
Contentid: 9002857
Headline: Ruby on Rails hands on: What’s so hot about Rails?
Summary: Why all the buzz around this development framework? Because creating database-driven Web apps can be faster and simpler. Find out more in this excerpt from O’Reilly Media’s Ruby on Rails: Up and Running.
Contentid: 9018460
Headline: Rails for Java Developers
Summary: Using the Rails framework, Ruby is giving Java a run for its money.
Scaffolding 2
The scaffolding interface allows you to view existing database entries as well as add new ones.
All these entries show up in the scaffolding, along with the option to add new stories.
Then, to get back to where I can see the index.rhtml view in my browser, I replace scaffold :story with
class StoryController <
ApplicationController
def index
end
end
which was there before. I need to make sure there are now two “end” lines in the file, one ending the class definition and one ending the index method. (I quickly find that leaving off “end” commands is among my most common Ruby errors.)
Querying for data
Next up, I want to tap into the database, find some data and display it. To do that, I need to 1) add logic to the controller, and then 2) add code so the view generates HTML to display data in a browser.
So I go back to the story controller, the file story_controller.rb (in the app/controllers directory), and type
def index
@story_list = Story.find(:all)
end
This creates a variable called story_list that will hold an array of all the story objects from the database.
Now, in the index.rhtml view, I enter
The stories in my database are:
What’s all that code about?
@story_list.each do |title| sets up my code loop. Each is a method that cycles through the @story_list array. Do does just what you’d expect — tells the program to start doing something. The title between the | pipes is a variable set up for the loop that follows, and it could be named anything.
brackets signify Ruby code embedded within an HTML page that should be executed but not displayed. Slightly different, the brackets show embedded Ruby that’s meant to be both executed and displayed. So
will display the headline and insert a line break in the browser.
When I go back to my browser and reload, I see
The stories in my database are:
Ruby on Rails hands on: What’s so hot about Rails?
QuickStudy: Ruby on Rails
Rails for Java Developers
With a large database, of course, you might not want to show everything in a table. Lenz shows how to get the most recent entry. I change the controller logic tied to my index view to say
def index
@laststory = Story.find(:first,
:order => ‘id DESC’)
end
This creates a variable laststory, which will store the most recent entry to the table. The Ruby code to the right of @laststory is using the built-in “find” method for the Story class.
This is something else that’s slick about Ruby on Rails, the many built-in methods whenever you create an object. Anytime I add another attribute to my class (and thus another field to my database table), I can use the find method to search my database for entries that have a certain value for that attribute. For example, if I want to get the story “Rails for Java Developers” out of my database, I can simply use Story.find_by_name(‘Rails for Java Developers’).
Story.find(:first, :order=> ‘id DESC’ will find just one item and assign it to the @laststory variable. That item will be the one at the top of the list, which is ordered in descending order. It will be the last one that was created in the table.
Adding logic
Back in the controller, I can add more sophisticated logic for creating a Computerworld.com URL. At the top of the story_controller.rb file, I define my own new method, geturl, that takes one argument:
def geturl(x) # a method to create a URL using our content ID
x = x.to_s # to_s turns a number into a string so it can be joined to the URL
@theurl = ‘http://www.computerworld.com/action/article.do?
command=viewArticleBasic&articleId=’ + x
end
Now, I can use this on any page of my project. If the URL structure changes on our site, I only need to update it once, in that one place. And voila, it’s changed everywhere.
Yes, I could create a similar function in another programming language. The question is: Would I? Or would I first build the logic into one page, realize later that I want to repeat it and then have to pull it out of some pages to store the string somewhere else? More importantly, a year later, would I remember where it was? Even more importantly, if this were a team project and the initial coder left, how long would it take someone else to find the function and change it? With Rails, it’s more likely you’ll know where the logic lives.
To take advantage of this on my index page, as a last step, I need to use my new method. I’ll add it to my controller:
def index
@last_story = Story.find(:first, :order => ‘id DESC’) # finds the most recent story
@theurl = geturl(@last_story.contentid) # uses the geturl method
end
With all the logic in place, the final piece is to code the view to make this actually display in the browser. I replace the contents of index.rhtml with this:
The last story added to my database of favorite Ruby on Rails stories is at the URL
I go to http://localhost:3000/story and see
The last story added to the database is Rails for Java Developers at the URL http://www.computerworld.com/action/article.do?command=viewArticleBasic&articleId=9018460
Finally, I tweak the code to get a clickable headline. Instead of manually coding the HTML link, I can use the Ruby on Rails link_to function followed by the text I want to be clickable, and then the link itself:
The last story added to my database of favorite Ruby on Rails stories is .
And so, a Ruby on Rails Model-View-Controller application is born.
Tune in for Part 2 of this review