Mac Dev Center

Developing Rails Applications
on Mac OS X Leopard

Ruby on Rails is a popular and powerful open source web framework for rapidly creating high-quality web applications to help you keep up with the speed of the Web. Rails is thriving on Mac OS X, and Leopard comes pre-installed with Ruby, Rails, Mongrel, Capistrano, Subversion, and other tools that help to streamline the development and deployment of Rails applications. In addition, the Organizer feature of XCode 3.0 keeps your development workflow efficient.

This article gives you a full tour of Ruby on Rails 2.0 on Leopard—starting with building a web application using the latest Rails features with Xcode 3.0, and finishing with deploying the application to a production server running Leopard Server. Along the way we'll explore unique features and benefits that Leopard brings to the party. In the end you'll be better equipped to consider the advantages of powering your web application with Rails on Leopard.

This is the first in a series of three articles:

  • This article on Development, where you learn to build a basic RESTful Rails application using Xcode 3.0;
  • Customization, where we discuss working with views and web forms, adding AJAX support, and supporting an iPhone interface;
  • Deployment, where we set up version control, write a Capistrano recipe, and deploy on Leopard Server.


Together they will give you a great start in working with Rails on Mac OS X Leopard.

Ruby, Rails, and Leopard

In Leopard and Leopard Server, Ruby and Rails are pre-installed along with a bounty of other useful RubyGems. This means we can hit the ground running when it comes to developing a Rails application and keep up the pace when deploying new releases of our application to Leopard Server.

Out of the box, you get Ruby version 1.8.6 and Rails version 1.2.6, the latest stable releases at the time Leopard shipped. Ruby releases are few and far between (it's still at 1.8.6), but Rails has frequent new releases. In fact, the application we'll build requires Rails 2.0.2. The good news is it's easy to upgrade Rails and RubyGems. Make sure your system is up to date now by running these commands:

 sudo gem update --system
 sudo gem install rails
 sudo gem update rake
 sudo gem update sqlite3-ruby

The first command updates the RubyGems system itself, which is required by the latest version of Rails. The second command updates Rails-specific gems and installs any new components (specifically Active Resource). The third command updates Rake, the make-like build tool used by Rails. Finally, the last command updates the Ruby bindings for the SQLite3 database. Running all these commands may take a while. When it's finished, you can list all the installed gems by typing

 gem list

You can view the documentation for all the installed gems by running

 gem server

This command starts a web server on port 8808 by default. To access the documentation, point Safari to http://localhost:8808/. You'll see a list of the gems you have installed with a link to their RDoc documentation.

If you've used Ruby before, you may be interested to know where the Ruby gems live in Leopard. The answer is in two places:

/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8 
/Library/Ruby/Gems/1.8

The /System repository is where all the pre-installed gems live, while the /Library repository is empty by default. When you install new gems, they'll end up in the /Library repository. Thankfully, it's all transparent.

Creating a New Rails Application

Note: This tutorial uses Rails version 2.0.2. With future Rails updates, some of the functionality described in this article may change.

Let's say, for example, that we have a club that organizes fund-raising events. When we put on an event we incur expenses from vendors who provide goods and services. Our club operates on a fairly tight budget, so 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 and we've decided to replace it with an online expense tracking 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. In a directory of your choosing (e.g., your home directory), create a skeleton application directory structure and a set of files for our expenses application by typing

 rails expenses

That command generates a number of directories and files in an expenses directory. We'll be editing various files as we go, and to streamline your development workflow you'll want quick access to frequently used files. Xcode 3.0 in Leopard has an Organizer feature that makes it easy to navigate around Rails projects. You don't even need to create a new Xcode project.

Follow these steps to open the Rails application in the Organizer:

  1. Launch Xcode and choose "Window > Organizer". An empty Organizer window appears.

  2. Drag your expenses directory from a Finder window into the Organizer window.

  3. Click the disclosure triangle next to the top-level expenses directory to display its contents.

  4. Drill down into the app/controllers directory and select the application.rb file.

  5. Display the text editor pane by clicking the rectangle icon in the bottom-left corner of the Organizer.

In the Organizer window, shown in Figure 1, you should see the files and directories that the rails command created. You'll notice in the Organizer that you have familiar Xcode editing features such as syntax coloring, code folding, line numbers, method shortcuts, and so on.

Organizer-1.jpg

Figure 1: Xcode 3.0 Organizer showing the Rails application directory structure

At this point, we could go ahead and fire up our expenses application from a Terminal command line using the script/server command. However, in addition to giving you a central place to edit files, the Xcode Organizer also lets you assign actions to folders. So rather than running the Rails application in a separate Terminal window, we'll assign an action to run it straight from the Organizer.

Follow these steps to create a new Organizer Action to run the Rails application:

  1. Select the top-level expenses folder.

  2. Click and hold the Run toolbar item until a pop-up menu appears. Choose "Edit Actions..." and you should see the Action Editor as shown in Figure 2.

    Organizer-2.jpg

    Figure 2: Xcode 3.0 Organizer Action Editor

    The list on the left side of the Action Editor displays actions associated with the Run toolbar item, along with their with optional shortcut keys. On the right side is a drop-down directory specifier and a script editor.

  3. Add a new Shell Script Action using the add button (+) in the bottom-left corner of the Organizer. Name the action "start server" by double-clicking on the default name "Shell Script".

  4. Set the directory to "Top Level Organizer Item". This indicates that the action's command should be run from the top-level folder (expenses), regardless of the folder that's currently selected when the action is run.

  5. Change the command to "script/server" and clear out the arguments.

    The Action Editor should now look like the one shown in Figure 3.

    Organizer-3.jpg

    Figure 3: Adding a new Organizer Action

  6. Click "OK" to save the new action.

  7. Back in the Organizer window, select the expenses folder, then click and hold the Run toolbar item. When the pop-up menu appears, choose the "start server" action. (Or just click the Run button and it'll run that action since there's only one assigned action.)

    A Debugger window opens and starts the server with output similiar to the output shown in Figure 4.

    Organizer-4.jpg

    Figure 4: Running an Organizer Action

  8. Now point Safari at http://localhost:3000. You should see a web page welcoming you aboard Rails.

So far, we've stood up a brand new application and we're organized, but it doesn't help our fund-raising campaign. Let's fix that.

Jump-Starting the Application

Now we're ready to put some meat on the bones of the skeleton directory structure. For starters, we need a web interface for creating some events in the database. An event is simply a name and a budgeted amount. The fastest way to put up a web interface backed by a database is by using what Rails calls scaffolding. Scaffolding is the initial supporting code that handles the basic create, read, update, and delete (CRUD) operations of any database-backed web application.

Open a Terminal window, then change directory to the expenses directory and create the event scaffolding by typing

 script/generate scaffold event name:string budget:decimal

This single command generates all the Ruby code we need to start administering events:

  • Model: An Event model class in the app/models/event.rb file

  • View: View template files in the app/views/events directory for listing, showing, creating, and editing events

  • Controller: An EventsController class in the app/controllers/events_controller.rb file

  • Database Migration: A CreateEvents class in the db/migrate/001_create_events.rb file that when run creates an events database table with three columns: id, name, and budget

We'll look at these files in more detail shortly. All we need to do now is apply the database migration to create the events database table so we can start creating events. Rails applications use Rake (Ruby's equivalent of Make) to automate recurring tasks such as applying migrations. We could run rake db:migrate from a Terminal command line, but again the Organizer makes this easier. All the Rake tasks are already available as pre-configured actions.

Click and hold the Action toolbar item, then choose the "db:migrate" action (the first one). A window opens showing the results of applying the migration, as shown in Figure 5.

Organizer-5.jpg

Figure 5: Applying the events database migration

Let's see how close scaffolding gets us to a web interface for administering events. Point Safari at http://localhost:3000/events. You should see a web page that lets you create new events.

Go ahead: create an event and the new event will show up in the event listing. From there you can show, edit, or delete the event. Figure 6 shows example events for our fund-raising campaign.

ListingEvents.jpg

Figure 6: Using scaffolding to administer events

That takes care of event management. Now let's repeat the cycle to put up a web interface for administering vendors. Generate the vendor scaffolding by typing

 script/generate scaffold vendor name:string email:string

This command generates all the supporting code for a vendor web interface, including a database migration in the db/migrate/002_create_vendors.rb file.

Apply the new migration to update the database schema to include the vendors table by choosing the "db:migrate" action from the Action toolbar item. The results are shown in Figure 7.

Organizer-6.jpg

Figure 7: Applying the vendors database migration

Then point Safari at http://localhost:3000/vendors. You should see a web page that lets you create new vendors.

To recap: In a very short amount of time, and with no explicit configuration, we've created a database-backed web application to CRUD (the verb form) events and vendors. It won't win any web design awards, but it's functional and gives us a jump-start on our application.

How It Works

Looking at the code generated by the script/generate scaffold command is a great way to learn how Rails applications work. After all, the templates used by the generator were written by the Rails core team. And as we'll see a bit later, scaffold-generated code serves merely as an exemple that should be tweaked as necessary for your application.

Let's follow a request through the request/response cycle, looking inside the scaffold-generated code as we go to see how all the components work together. Figure 8 shows the MVC components in play for events, and their file equivalents.

MVC.jpg

Figure 8: MVC components and their files

RESTful Conventions

The speed at which we were able to create a fully-functioning web application can be credited in large part to conventions. Rails relies heavily on conventions to eliminate most configuration, thus making web developers a lot happier.

Take, for example, the "Show" hyperlink for an event. In the app/views/events/index.html.erb template, the hyperlink is generated using

<%= link_to 'Show', event %>

That line of code generates the following HTML, for example:

<a href="/events/1">Show</a>

When this hyperlink is clicked in Safari, it ends up executing Ruby code inside the Rails application. But how does Rails know how to route an incoming URL to the appropriate code that handles the request? The answer: conventions.

It turns out that the HTTP protocol already has conventions for interacting with resources on the web via a simple set of verbs: GET, POST, PUT, DELETE, etc. With every HTTP request comes one of these verbs. The URL identifies the resource (the noun, if you will). For example, clicking the http://localhost:3000/events/1 link issues a GET request for the event living at that web address.

This resource-oriented approach to web design is an example of the REST architectural style, and Rails has whole-heartedly embraced it. Now, REST is a relatively complex (and academic) topic that's beyond the scope of this article. Thankfully you don't have to be a REST expert to use Rails. You just need to tell Rails which resources should be accessible via the RESTful conventions. Rails then takes care of routing incoming HTTP verbs and URLs to those resources.

When we ran the scaffold generator for events, for example, Rails assumed that events are resources we want to expose via RESTful conventions. The same is true for vendors. So if you look in the config/routes.rb file you'll see that the following lines were automatically added:

map.resources :events 
map.resources :vendors

Those two lines of code dynamically add routes for accessing our events and vendors according to the RESTful conventions. To see all the routes, choose the "routes" action from the Action toolbar item.

Let's look at just a few of the routes in the list you'll see to get a glimpse of what's going on behind the scenes:

events GET  /events {:controller => "events", :action => "index"} 
       POST /events {:controller => "events", :action => "create"} 
  
event  GET /events/:id {:controller => "events", :action => "show"} 
       PUT /events/:id {:controller => "events", :action => "update"} 

In a nutshell, the map.resources line generates routes into our application using both the incoming HTTP verb and URL. For example, to list all the events we'd send in the URL /events. The exact same incoming URL is used to create a new event. The only difference is the HTTP verb that is used: GET is a read-only operation that lists the events and POST is a write operation that creates a new event. To show or update an existing event, the URL needs to include the event id (primary key), for example /events/1. Again, the URL is the same, but the HTTP verb is different: GET fetches the event and PUT updates the event.

Controllers

Now let's follow the request back into scaffold-generated MVC code. When Rails receives a GET request for the /events/1 resource, for example, it invokes the show method (called an action) of the EventsController. That action is found in the app/controllers/events_controller.rb file.

class EventsController < ApplicationController
  
  def show
    @event = Event.find(params[:id])
    
    respond_to do |format|
      format.html # renders show.html.erb
      format.xml  { render :xml => @event }
    end
  end
  
  # plus other action methods for listing, creating, 
  # updating, and deleting events
end

The show action uses the event id supplied in params[:id] (1, in this case) to find a matching event in the events database table. It then populates an Event model object with the values corresponding to that database table row. The resulting Event model object is assigned to the @event instance variable. (Ruby identifies instance variables as variables starting with an @ symbol.)

Models

The Event model class itself lives in the app/models/event.rb file.

class Event < 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 ActiveRecord::Base parent class. ActiveRecord is a component of Rails that, among other things, uses reflection and naming conventions to transparently map relational database tables into models. By convention, the Event model encapsulates access to the events table in our database. An instance of an Event model represents a row in the events table, with each table column mapping to an attribute of the Event instance.

For example, without any additional configuration or code, inside of the show action we could get the name of the event using:

@event.name

Database Migrations

Now you may be wondering which database Rails is using and how it got connected. When we created the application, a config/database.yml file was generated. In that 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 (the default environment), so have a look at that particular section of the config/database.yml file.

development:
  adapter: sqlite3
  database: db/development.sqlite3
  timeout: 5000

Rails configures a SQLite3 database by default, which is quite handy given that SQLite3 is already installed on Leopard. When it comes time to deploy this application, we'll switch over to a MySQL database running on Leopard Server.

When we ran the first database migration to create the events table, the db/development.sqlite3 database was automatically created. Here's what the db/migrate/001_create_events.rb file looks like:

class CreateEvents < ActiveRecord::Migration
  def self.up
    create_table :events do |t|
      t.string :name
      t.decimal :budget

      t.timestamps
    end
  end

  def self.down
    drop_table :events
  end
end

Migrations give us a way to evolve our database schema and data over time in a repeatable, automated way. Each migration defines two class-level methods: up and down. The up method moves the migration "forward", in this case by creating the events table and its columns. Note that create_table will implicitly create a primary key column called id by default.

The down method rolls the migration "backward", in this case by dropping the events table. That is, migrations are reversible. For example, if you wanted to drop the events table, you'd type 'rake db:rollback'.

Views

Back in the show action, we've fetched the event from the database and now it's time to show it. The respond_to block decides how to render the event depending on the format requested by the client. Since we're using Safari as the client (and we haven't explicitely specified a format), the format will be HTML. So in this case, the show action renders the template found in the app/views/events/show.html.erb file.

<p>
  <b>Name:</b>
  <%= h @event.name %>
</p>

<p>
  <b>Budget:</b>
  <%= h @event.budget %>
</p>

<%= link_to 'Edit', edit_event_path(@event) %> |
<%= link_to 'Back', events_path %>

Rails view templates are a mix of HTML and Ruby code. Any expression between <%= and %> is evaluated as Ruby code and the result is substituted back into the response. Notice that the show template has access to the @event instance variable that was created in the show action of the EventsController. Using the @event variable, the show template simply outputs the value of each event attribute with its associated column name.

The template then generates hyperlinks to the edit and index actions using the link_to helper. The edit_event_path and events_path methods were generated by the map.resources :events declaration we saw previously. They create URLs according to the RESTful conventions that route back into the application.

At this point we've followed one HTTP request through the scaffold-generated code to generate an HTML page as the response. Now let's start adding some code of our own.

Validating Models

You may have noticed that the scaffold-generated web form used to create and edit events will let you enter anything in the fields, and even let you leave required fields blank. That's no good—we only want valid data in our database.

Maintaining the consistency of the application's data is a model's job. Rails includes a rich set of built-in model validations and it's easy to write your own, as well. So let's add some model validations to ensure an event has a name and a valid budget amount.

Update the app/models/event.rb file as follows:

class Event < ActiveRecord::Base
  validates_presence_of :name
  validates_numericality_of :budget, :greater_than => 0.0
end

Now try to create an event with a blank name or invalid budget amount. You should see something similar to the web form shown in Figure 9.

Validations.jpg

Figure 9: Model validations at work

Linking Models Together

Administering events and vendors is a good start, but we're still missing the ability to track expenditures. An expense is simply an amount charged by a vendor for an event. In database terms that means an expense needs to have foreign keys pointing to its event and vendor. In other words, an expense joins together an event and a vendor. The database schema we're aiming for is shown in Figure 10.

Schema.jpg

Figure 10: Many-to-many association database schema

Just like event and vendor, we want an expense to be a resource. The only difference is we don't need a full scaffold-generated web interface for expenses. Instead, we're going to record expenses using our existing events web interface. However, we still need a migration, a model, and a controller for expenses.

Generate a new expense resource by typing:

 script/generate resource expense event_id:integer vendor_id:integer amount:decimal 

Naming is important here. Rails assumes that the event_id foreign key column references the id column of the events table, and likewise vendor_id references the id column of the vendors table. It's yet another example of how Rails uses naming conventions to keep external configuration at a minimum. (You can always override the default behavior.)

Then apply the migration that was generated in the db/migrate/003_create_expenses.rb file to create the expenses database table by choosing the "db:migrate" action from the Action toolbar item. The results are shown in Figure 11:

Organizer-7.jpg

Figure 11: Applying the expenses database migration

At this point we have an expenses database table with foreign key references to the events and vendors tables. That's one side of the coin. Rails still doesn't know exactly how to use those references in terms of model associations.

Specifically, we want the Expense model to have a many-to-one relationship with the Event model and a many-to-one relationship with the Vendor model. And because an expense knows about both its event and its vendor, the Expense model will implicitly represent a many-to-many relationship between events and vendors.

Declare the associations in the models as follows:

class Expense < ActiveRecord::Base
  belongs_to :event
  belongs_to :vendor
end
class Event < ActiveRecord::Base
  validates_presence_of :name
  validates_numericality_of :budget, :greater_than => 0.0

  has_many :expenses
  has_many :vendors, :through => :expenses
end
class Vendor < ActiveRecord::Base
  has_many :expenses
  has_many :events, :through => :expenses
end

Expense now has a many-to-one relationship with both Event and Vendor. We've also created an indirect relationship between Event and Vendor going "through" the Expense join model. The declarations read quite nicely: An expense belongs_to an event and an event has_many expenses. We can declare the model associations in a concise way and through the power of Ruby those declarations dynamically add methods to the enclosing class for managing the association.

Accessing Models Through the Back Door

Adding just a couple lines of code to the models has given us a bunch of new functionality. Before we start using it in our web application, let's experiment a bit with linking our models together. Type

 script/console

That command loads our Rails application into an interactive environment, then prompts you (shown below as the >> prompt) to enter Ruby code. It shows the value of each expression (shown below after =>) after you press Return.

Start by creating two new vendors in the database:

>> vendor1 = Vendor.create(:name => 'Parties R Us')
=> #<Vendor id: 1, name: "Parties R Us", ...>

>> vendor2 = Vendor.create(:name => 'Fire Department')
=> #<Vendor id: 2, name: "Fire Department", ...>

Next find an event you created earlier in the web interface:

>> event = Event.find_by_name('Chili Cookoff')
=> #<Event id: 1, name: "Chili Cookoff", ...>

Then create two expenses that link the event to each vendor:

>> event.expenses.create(:vendor => vendor1, :amount => 75.00)
=> #<Expense id: 1, event_id: 1, vendor_id: 1, ...>

>> event.expenses.create(:vendor => vendor2, :amount => 25.00)
=> #<Expense id: 2, event_id: 1, vendor_id: 2, ...>

Now you can access all expenses that directly belong to the event and all vendors that indirectly belong to the event through the expenses:

>> event.expenses
=> [#<Expense id: 1, ...>, #<Expense id: 2, ...>]

>> event.vendors
=> [#<Vendor id: 1, ...>, #<Vendor id: 2, ...>]

You can also calculate the total expenses for the event:

>> event.expenses.sum(:amount)
=> #<BigDecimal:1a04960,'0.1E3',4(8)>

Try navigating through the same associations, this time starting with a Vendor object. Then use quit to exit the session.

Adding Business Logic

Now that we can record expenses for a specific event, we can calculate the total expenses to see if we're within budget for the event. Models not only wrap database tables, they also encapsulate business logic such as this. So add the following method to the Event model:

def total_expenses
  expenses.sum(:amount) || BigDecimal("0.0")
end

This instance method uses the expenses relationship so that only those expenses belonging to a specific event are accumulated. We used similar code previously in script/console. Bottling those details up into a well-named method of the Event model helps keep our code clean and reusable. For example, we can also use the total_expenses method to write a new budget_exceeded? method to tell us whether an event is within budget. Add the following method to the Event model:

def budget_exceeded?
  total_expenses > budget
end

Writing Tests

Whenever we add business logic, trivial as it may be, we're wise to test it. Rails makes doing the right things easy. One of those things is testing. When we generated the event scaffolding, Rails went ahead and created a unit test for the Event model and a functional test for the EventsController. All we need to do is write a new unit test to cover the business logic we added to the Event model.

Add the following test method to the test/unit/event_test.rb file:

def test_budget
  event = Event.new(:name => 'Test Event', :budget => 30.00)
  event.expenses.build(:amount => 10.00)
  event.expenses.build(:amount => 20.50)
  assert event.save
  
  assert_equal BigDecimal("30.50"), event.total_expenses
  assert event.budget_exceeded?
end

The test uses the build method to create two expenses (in memory) and add them to the event's expenses association. Then the test saves the event (and its expenses) to the test database and asserts that the save operation returned true. With the event tucked away in the database, the test then checks that the event's total expenses is the sum of the expenses and that the expenses exceed the budget.

Run all the unit tests by choosing the "test:units" action from the Action toolbar item. All the tests should pass with the following output:

....
Finished in 0.1223 seconds.

4 tests, 6 assertions, 0 failures, 0 errors

Remember that Rails has three default runtime environments. When you run tests, Rails switches to the test environment. Among other things, that means the database configured in the test section of the config/database.yml file will be used. In our case it's a SQLite3 database in the db/test.sqlite3 file. The upshot is tests can muck with the database without affecting our development (or production) data.

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. You can also write integration tests to assert expectations across multiple controllers. To run all the unit, functional, and integration tests in one fell swoop, simply run rake. (As it stands, the functional tests fail because we've added validations to the Event model.)

In the second article in this series, Customizing Rails Applications on Mac OS X Leopard, we build on this basic Rails application, including customizing views, working with web forms, adding AJAX support, and supporting an iPhone interface on Mac OS X Leopard.

Updated: 2008-09-29