The Ruby on Rails web application framework has built up a tremendous head of steam over the last year. Fueled by some significant benefits and an impressive portfolio of real-world applications already in production, Rails is destined to continue making significant inroads in 2006. Simply put, Ruby on Rails is an open source tool that gives you the advantage of rapidly creating great web applications backed by SQL databases to keep up with the speed of the web. And with the release of Rails 1.0 kicking off the new year, there's never been a better time to climb aboard. It should come as no surprise that Mac OS X is a favored platform for Rails development. Rails and its supporting cast of web servers and databases thrive on the rich Mac OS X environment. A popular text editor used by many Rails programmers is TextMate, a Cocoa application. And all members of the Rails core development team work with Macs. This article introduces you to Ruby on Rails by building a trivial web application step by step. Consider it a ride on the express train—an overview of what Rails can do, including a look at features new to Rails 1.0. In the end you'll be better equipped to consider the advantages of powering your web application with Rails. Why Ruby on Rails?First, you might be wondering: Web application frameworks are a dime a dozen, so what's different about Rails? Here are a few things that make Ruby on Rails stand above the crowd:
All that being said, the best way to judge Rails is to experience it while building an application. So let's get right to it. Installing RailsThe version of Ruby that shipped on Mac OS X Tiger prior to v10.4.6 did not work well with Rails.† †If you're running an earlier version of Tiger, you'll need to either upgrade to 10.4.6 or upgrade your copy of Ruby to version 1.8.4 or later using the open source distribution. And to do any serious Rails development you'll want to install a production-quality web server, database server, and a few other goodies. Thankfully, a golden-path installation guide is already available. NOTE: If you already have MySQL, you can get everything else by using Locomotive, a†drag-install of all the relevant pieces. Walk through the step-by-step instructions outlined in "Building Ruby, Rails, LightTPD, and MySQL on Tiger". It starts by installing Ruby 1.8.4 without overwriting the system-installed Ruby (it puts the new version in /usr/local). That way, the default system works as expected for other users or programs already coded for the system-installed ruby. It also installs the RubyGems package manager, Rails, the LightTPD web server, and the MySQL database server (with native bindings), all of which we'll use in this tutorial. And the instructions have even been tuned to work well for those of you that are using the latest Intel-based Macs. By the end you'll have an ideal development and production environment. Once you have a working version of Ruby and RubyGems installed, you can upgrade to future versions of Rails simply by typing $ gem install rails --include-dependencies Finally, we recommend that you download and install the 30-day trial of TextMate to use throughout this tutorial. It's a popular editor for Rails developers on Mac OS X. Creating a New Rails ApplicationWe're going to build an online expense tracking application. You could use this for tracking expenditures for your personal budget, a small company, a group or club, etc. Say, for example, our club organizes fund-raising campaigns and we need to make sure expenses are in line with the budget for each event. The old paper and pencil system broke down last month, so we've decided that we need an online web application that everyone can share. First we need to create the application. All Rails applications have a consistent directory structure so that Rails can find stuff without needing to be told where to look. Create a skeleton directory structure and a set of files for our expenses application by typing $ rails expenses Next, open the new project directory with TextMate by typing $ mate expenses In the TextMate Project Drawer, shown in Figure 1, you should see the directory structure that the rails command created. ![]() Figure 1: Application Directory Structure (as shown in TextMate) We'll peek inside these directories a bit later. But first, we're ready to run our application for the first time. Change directory to the expenses directory, then type: $ script/server You've just started a web server. If you have the LightTPD web server installed, Rails will attempt to use it by default. Otherwise WEBrick, the pure-Ruby web server that comes with Ruby, will be used. Either web server works well for development purposes. Now point your web browser at http://localhost:3000. You should see a web page welcoming you aboard Rails. That tells us that the application is running, but it doesn't help our fund-raising campaign. Let's fix that. Jump-Starting the ApplicationNow we're ready to put some meat on the bones of the generated directory structure. Ultimately (in the next few minutes) we want to end up with a web interface that lets us track expenditures for accounts. For starters, we need to create and configure a database to hold those accounts and expenses. Setting Up the DatabaseTelling Rails which database to use and how to connect to it is one of the few times you'll need to touch a configuration file. In the config/database.yml file you'll notice that Rails has pre-configured three database connections—development, test, and production—that correspond to three runtime environments. We're doing development now, so have a look at that particular section of the config/database.yml file. development: adapter: mysql database: expenses_development username: root password: socket: /tmp/mysql.sock By default, Rails assumes we're using the MySQL database server with a database called expenses_development. If you set a MySQL password for the 'root' user to something other than the default (which is always a good idea), then set that password as the value of the password field. Also, if your mysqld.sock isn't in the usual place, you can explicitly set it in the socket field. The default database.yml file includes example configurations for other databases. The following database servers are currently supported by Rails: MySQL, PostgreSQL, SQLite, SQL Server, DB2, Firebird, and Oracle. Now we need to actually create the expenses_development database. Feel free to use whatever tool you're comfortable with. Here's how to create the database using the mysqladmin command-line tool: $ mysqladmin -u root -p create expenses_development We'll test the database connectivity a bit later. Creating the Database SchemaWe've created the database, but it doesn't have any tables yet. We need to store accounts in an accounts table, so let's start there. Rather than using raw DDL or a SQL administration tool, we'll use a Rails migration to create the accounts table. Migrations are database-agnostic representations of database schemas, which means we can easily switch to another database server later, if necessary. Better yet, migrations let us conveniently evolve a schema over time. First, create the migration using the Rails migration generator. $ script/generate migration accounts That command creates a db/migrate/001_accounts.rb file that defines an empty migration. We need to fill it in to create an accounts table with two columns: a name that identifies the account (a text string) and a budget (we'll use a float for the sake of simplicity). Update the db/migrate/001_accounts.rb file as follows: class Accounts < ActiveRecord::Migration def self.up create_table :accounts do |table| table.column :name, :string table.column :budget, :float end end def self.down drop_table :accounts end end This may be your first look at Ruby code. The file defines a class with two class-level methods—up and down. The up method moves the migration forward by creating the accounts table and its columns; the down method rolls the migration backward by dropping the accounts table. Note that unless told not to, create_table will implicitly create a primary key column. Now let's apply the migration. We'll use Rake (Ruby's equivalent of Make) to run a task that's available in every Rails application. Simply type: $ rake migrate Our expenses_development database should now have an accounts table. (Running 'rake migrate' is also a handy way to test database connectivity, so if the migration failed it's probably because your database isn't configured properly.) Migrations are also reversible. For example, if you wanted to migrate the schema back to its original form, you'd simply type 'rake migrate VERSION=0'. Putting Up ScaffoldingNow that we have a database and a schema for storing accounts, we need to start creating accounts. The fastest way to get from a database schema to a web interface is to use what Rails calls scaffolding. Scaffolding is the initial support for building an application—models, controllers, and views that handle the basic CRUD (create/read/update/delete) operations of any database-backed web application. Unlike tools that generate code that's never intended to be modified by humans, Rails scaffolding is generated with the express intent of encouraging you to tweak it. And just like scaffolding used to support the construction of a building, gradually the application scaffolding disappears. Generate the scaffolding by typing $ script/generate scaffold account expenses That command generates a number of files for us; files we'd rather not create by hand. The first parameter (account) specifies the name of the model, which is generated in the app/models/account.rb file. The second parameter (expenses) specifies the name of the controller, which is generated in the app/controllers/expenses_controller.rb file. The scaffold generator also created template files for the views of our application. We'll look at how all these components work together a bit later. Let's see how close scaffolding get us to a web interface for managing accounts. If the application is still running, press CTRL-C in the terminal where it's running. You may have changed the database connection, which requires a restart, so restart the application by typing $ script/server Then point your web browser at http://localhost:3000/expenses. You should see the web page that lets you create new accounts. Go ahead: create an account, click the "Create" button, and the new account will show up in the account listing. From there you can show, edit, or delete the account. Figure 2 shows example accounts for our fund-raising campaign. ![]() Figure 2: Using scaffolding to manage accounts This interface won't win any web design awards, but it's functional and gives us a jump-start on our application. With no explicit configuration, the model, views, and controller work together to give us a quick and dirty web interface. Tiptoeing Through the Generated CodeHow does all this work? At the core it uses simple naming conventions to eliminate most configuration. Take, for example, the "Show" hyperlink for the account with an id of 1. The URL for this link is: http://localhost:3000/expenses/show/1 The first part of the URL path, expenses, is the name of the controller responsible for handling the incoming request. The second part of the path, show, is the name of an action defined by that controller. The last part of the path, 1, is the id (in this case, the database primary key) of the account to be shown. Figure 3 shows the MVC components in play, and their file equivalents. ![]() Figure 3: MVC components and their files So, using those URL path parts (called a route) as a guide, Rails will invoke the show method of the ExpensesController, supplying the account id in the HTTP request parameters. The controller code is found in the app/controllers/expenses_controller file. class ExpensesController < ApplicationController def show @account = Account.find(params[:id]) end # plus other action methods for creating, reading, # updating, and deleting accounts end The show method uses the account id supplied in params[:id] to find and populate an Account model object containing the values of the row in the accounts database table corresponding to the account id. The resulting Account model object is referenced by the @account instance variable. (Ruby identifies instance variables as variables starting with an @ symbol.) The Account model class itself lives in the app/models/account.rb file. class Account < ActiveRecord::Base end At first glance, this class appears to do nothing, but in fact it already has a lot of functionality inherited through its parent class ActiveRecord::Base. Rails uses reflection and naming conventions to transparently map relational tables into model objects. The Account model encapsulates access to the accounts table in our database. An instance of an Account model class represents a row in the accounts table, with each table column mapping to an attribute of the Account instance. After fetching and populating an Account model object and assigning it to the @account instance variable, the show action renders the template found in app/views/expenses/show.rhtml. Rendering this template is all implicitly done with naming conventions. If an action doesn't explicitly render a named template, then a template matching the action's name is used. <% for column in Account.content_columns %> <p> <b><%= column.human_name %>:</b> <%=h @account.send(column.name) %> </p> <% end %> <%= link_to 'Edit', :action => 'edit', :id => @account %> | <%= link_to 'Back', :action => 'list' %> The template is a mix of HTML and Ruby code. Any expression between <%= and %> is evaluated as Ruby code and the result is substituted for the expression. Notice that the show template has access to the @account instance variable that was set in the show action of the controller. The show template simply outputs the value of each attribute of the @account object with its associated column name. Hyperlinks to the edit and list actions are generated using the built-in link_to helper. The resulting web view is shown in Figure 4. ![]() Figure 4: The 'show' action renders the 'show' template Again, this template doesn't output the most visually appealing interface, but it's easy enough to change to your liking. Customizing: Validating ModelsYou may have noticed that the web form used to create and edit accounts will let you enter anything in the fields, and even let you leave required fields blank. Maintaining the consistency of the application's data is a model's job, so let's add some validations to ensure the account has a name and a valid budget amount. Thankfully, running the scaffold generator created a set of working files that we can incrementally tweak. Update the app/models/account.rb file as follows: class Account < ActiveRecord::Base validates_presence_of :name validates_numericality_of :budget end Now try to get past the web form by leaving the name field blank or using anything but a number in the budget field. You should see something similar to the web form shown in Figure 5. ![]() Figure 5: Model validations at work Linking Models TogetherAccount management is working well, but we're missing the ability to add expenses to accounts. An expense is comprised of the date on which it was paid, a string identifying who it was paid to, and the amount paid. We need to store expenses in their own database table, which calls for another migration. Create the expense migration by typing $ script/generate migration expenses Then fill it in by updating the resulting db/migrate/002_expenses.rb file as follows: class Expenses < ActiveRecord::Migration def self.up create_table :expenses do |table| table.column :paid_on, :date table.column :payable_to, :text table.column :amount, :float, :default => 0.0 table.column :account_id, :integer end end def self.down drop_table :expenses end end Notice that in addition to expense data, rows in the expenses table have a foreign key reference (account_id) that links the expense to its account. Apply the migration by typing $ rake migrate Next we need an Expense model to wrap the expenses table. Use the generator to create the model by typing $ script/generate model expense That command gives us an empty Expense model class. Now we have to tell Rails that a relationship exists between the accounts and expenses tables. (Rails can't accurately derive relationships simply by looking at the database schema.) Specifically, an expense belongs to an account. Declare that relationship in the Expense model by updating the app/models/expense.rb file as follows: class Expense < ActiveRecord::Base belongs_to :account end The belongs_to declaration dynamically adds methods to the Expense class, including methods for assigning an account to the expense and returning the account that the expense belongs to. For that to work, Rails assumes that the account_id column in the expenses table references the id column of the accounts table. It's another example of how Rails uses naming conventions to keep external configuration at a minimum, and you can always override the default behavior. That takes care of one side of the relationship. We also need to tell Rails about the link in the opposite direction: an account has many expenses. Declare that relationship in the Account model by updating the app/models/account.rb file as follows: class Account < ActiveRecord::Base validates_presence_of :name validates_numericality_of :budget has_many :expenses, :order => "paid_on" end The has_many declaration dynamically adds several methods to the Account class for managing an account's expenses. Note, however, that it's not required that we add relationships to both the Account and Expense models. Setting up relationships in both directions just means that we can access and operate on the relationships from either side. Accessing Your Application Through the Back Door$ script/console That command loads the application into an interactive environment, and prompts you to enter Ruby code. Let's add two expenses to an existing account. Using one of the account names you created through the web interface, type the following lines, with each line followed by a Return: >> account = Account.find_by_name("Chili Cookoff") >> account.expenses.create(:paid_on => Time.now, :payable_to => 'Parties R Us', :amount => 75.00) >> account.expenses.create(:paid_on => 1.day.ago, :payable_to => 'Fire Department', :amount => 25.00) >> account.expenses(true) (After entering each line and pressing Return, it will show the value of each expression. Use quit to exit the session.) First we use a dynamic finder (find_by_name) to find an Account model object in the database. Dynamic finder methods work for any database column (e.g., find_by_budget(150)). Then we create and add two Expense model objects to the expenses relationship of the account. Finally, we call account.expenses(true) to get a collection of all expenses belonging to the account, ordered by the date the expenses were paid. (Using true forces the expenses to be reloaded.) Now that we have an account with expenses, it would be helpful if we listed the expenses on the account detail page. Dressing Up the ViewThe account detail page is still being generated by the show template that was created by the scaffolding. So when we show an account, we don't see any expenses. But we can easily change that by customizing the show template slightly. Add the following snippet to the bottom of the app/views/expenses/show.rhtml file: <% if @account.has_expenses? %> <h3>Itemized Expenses</h3> <table> <% for expense in @account.expenses %> <tr> <td><%= expense.paid_on %></td> <td><%= expense.payable_to %></td> <td align="right"><%= number_to_currency(expense.amount) %></td> </tr> <% end %> </table> <% end %> The template loops through all the expenses for the account, generating an HTML table row for each expense. Notice the use of the built-in number_to_currency helper to display the expense amount as dollars. Navigating to the account detail page for the account you added expenses to using script/console previously, you should now see a page similar to the one shown in Figure 6. ![]() Figure 6: An account has many expenses Working with Web FormsWe don't yet have a way to record expenditures for an account using the web interface, so let's add an HTML form that captures the expense. We'll have the form post the expense to the record action in the ExpensesController. First, add the following snippet to the bottom of the app/views/expenses/show.rhtml file: <%= start_form_tag :action => 'record', :id => @account %> <p> On <%= date_select 'expense', 'paid_on', :order => [:month, :day, :year] %> to <%= text_field 'expense', 'payable_to', :size => 25 %> in the amount of $<%= text_field 'expense', 'amount', :size => 9 %> </p> <%= submit_tag 'Record!' %> <%= end_form_tag %> This creates an HTML form using built-in form helpers. The start_form_tag helper generates an HTML form that, in this case, posts to the record action of the ExpensesController with the current account id assigned to the :id URL parameter. The date_select helper generates a date selection widget bound to the paid_on attribute of the "thing" named expense. (Stay tuned for what we mean by "thing".) The text_field helper generates an <input> tag of type text bound to the payable_to attribute of the thing named expense. Finally, the submit_tag generates a submit button. Reload a show page and you should see a page similar to the one shown in Figure 7. ![]() Figure 7: Recording expenses through a web form If you try to submit the form, Rails complains that it can't find the record action to handle the form post. We need to define that action in the ExpensesController. Add the following action method to the app/controllers/expenses_controller.rb file: def record Account.find(params[:id]).expenses.create(params[:expense]) redirect_to :action => 'show', :id => params[:id] end Remember we said that the date and text were bound to a thing called "expense"? Just what is that "thing?" Well, in this case, it simply means that we can reference all the expense fields in the form by referencing params[:expense]. First we find the account corresponding to the account id supplied by the form (in params[:id]). Then we create an Expense model object from the form data (available in params[:expenses]) and add the expense to the account's collection of expenses. Finally, we redirect the browser to the show action to display the updated account details, including the newly-added expense. Go ahead and use the web form to add expenses to your accounts. You can get insight into what's going on behind the scenes (including what SQL is being run) by watching the log file. In a separate terminal, type: $ tail -f log/development.log Note that if script/server started with the LightTPD web server (rather than falling back onto WEBrick), the log will be tailed automatically for you. Adding Business LogicWe have an account budget and a list of expenses, now all we need to do is total the expenses to see if we're within budget. That calculation is business logic that belongs in the Account model. So add the following method to the app/models/account.rb file: def total_expenses expenses.inject(0) {|total, expense| total + expense.amount } end We use the expenses relationship to iterate through the account's expenses and accumulate the total. Don't worry about the syntax used here. The inject method is a Ruby idiom for accumulating while stepping through a collection of objects. The total_expenses method then returns the value of the last expression, which is the total of the expenses. Using HelpersAt this point we need to update the view to show the total. But there's a bit of view logic that has to happen: If the expense total is greater than the account budget, then we want to display the total in red. Otherwise, we'll display the total in black. (The club treasurer likes it better this way.) We could put this logic in the template itself, but it's wise to keep logic out of the template. Instead, view-specific logic is best kept in a custom helper method. Add the following method to the app/helpers/expenses_helper.rb file: def total(account) style = account.total_expenses > account.budget ? 'color: red' : 'color: black' %(<span style="#{style}">#{number_to_currency(account.total_expenses)}</span>) end This helper method simply generates styled HTML to show the total expenses for an account, depending on whether the account is within budget or not. Finally, modify the template in the app/views/expenses/show.rhtml file to display the total using the total helper by adding a row to the bottom of the itemized expenses table, as follows: <tr> <td align="right" colspan="3"> <strong>Total</strong>: <%= total(@account) %> </td> </tr> Reload a show page, add enough expenses to bust the budget, and you should see a page similar to Figure 8. ![]() Figure 8: Using business logic and a helper to show total expenses Now that we have business logic, trivial as it may be, we're wise to test it. Writing TestsRails makes doing the right things easy. One of those things is testing. In fact, when we generated the scaffolding Rails went ahead and created unit tests for our models and functional tests for our controllers. But the tests won't run until we create the test database. Remember that Rails has three default runtime environments. When you run tests, they're run with Rails configured according to the test environment. In our case, the default database configured in the config/database.yml file for the test environment is called expenses_test. To create that database using the mysqladmin command-line tool, type $ mysqladmin -u root -p create expenses_test Now let's add a new unit test to cover the total_expenses method we added to the Account model. Add the following test method to the test/unit/account_test.rb file: def test_total_expenses account = Account.new account.expenses.create :amount => 10.00 account.expenses.create :amount => 20.50 assert_equal 30.50, account.total_expenses end The test adds two expenses to an account, then checks that the total expenses is the sum of both expenses. Run all the unit tests by running 'rake test_units'. $ rake test_units ... Finished in 0.084544 seconds. 3 tests, 3 assertions, 0 failures, 0 errors Rails encourages you to test more by making it easy to write and run tests. In addition to unit tests, functional tests let you easily generate simulated HTTP requests against a controller, then assert that the controller works as you'd expect. To run all the unit and functional tests in one fell swoop, simply run rake. (As it stands, the functional tests fail because we've modified the ExpensesController and its test is now out of step with the code.) Ship It!We're done with version 1.0 of the application. Now it's time to deploy it onto a production server and share it with the world. We need to do at least two things: configure a web server on the production machine and deploy our code onto that machine. Configuring a Web ServerThe easiest path is to use the LightTPD (or "lighty") web server: a production-quality web server that's very popular in the Rails community. In fact, Rails 1.0 will use the LightTPD web server by default if it's installed on the local machine. The first time you start the application by typing script/server, a LightTPD configuration file is created in the config/lighttpd.conf file. By default, your Rails application will run in the development runtime environment. To switch to the production environment, simply update the config/lighttpd.conf file and set the RAILS_ENV environment variable as follows: "RAILS_ENV" => "production" The production environment changes the runtime characteristics of the application to suit production needs: the application runs faster, logging is less verbose, users see friendlier error pages, etc. Deploying the ApplicationWhen it comes to deploying the code to the production server, and re-deploying your application with each new release, you're in for a treat. Capistrano (formerly SwitchTower) is an automated deployment utility that makes deploying Rails applications a breeze. To summarize, after installing Capistrano, deploying a new version of your application to one or more deployment machines is as simple as typing $ rake deploy If there's a problem with that version, you can easily roll back to the previous version by typing $ rake rollback When you run a SwitchTower task such as deploy or rollback, it's run in parallel (and atomically) on all machines that are assigned to that task in the Capistrano recipe file. Think about that: As the number of machines and processes in your deployment environment increases, your deployment procedure remains constant at a single command. For complete instructions on how to configure LightTPD and use Capistrano, see the online recipe Deploying Rails with LightTPD. Features for Version 2.0In a short amount of time we've built a functional expense-tracking application. Our treasure's job is already made easier. Indeed, we're off to a good start, but here are just a few ideas we could implement by the end of the day:
ConclusionRuby on Rails is a highly-productive and industrial-strength web application framework. It scales from the simplest expense tracking application we built to full-featured applications with respectable numbers of users. As with any useful tool, it's not suited to handle every job, but it's a great complement to your Mac OS X development environment. For More InformationThis article has introduced you to Ruby on Rails. The following references will help you dig in deeper:
Updated: 2006-06-08 |