November 16, 2006

Rate this page del.icio.us  Digg slashdot StumbleUpon

Ruby on Rails on Red Hat

by David Berube


Ruby on Rails is an open source freely available web development framework. It's been quite popular--it won a Jolt "Web Development Tools" award last year, and some prominent Java developers have publically switched to Ruby on Rails. The buzz surrounding Rails is quite impressive--particularly when you consider that Rails had no Fortune 500 company to market it, unlike .NET or Java.

Rails is a Model View Controller (MVC) framework. As you can imagine from the name, applications written using Model View Controller frameworks have three main components: a model, which represents the data and associated logic; the view, which represents how a user interacts with the application; and the controller, which contains all of the business logic that drives the application. This is an artificial distinction, of course, but it is a powerful one.

You'll need Apache 2.0+ and MySQL installed on your Red Hat Linux computer to run these examples.

How do you install Rails?

The first, most basic requirement for running Rails is having the Ruby programming language installed. All recent distributions of Fedora™ Core or Red Hat® Enterprise Linux® include Ruby. You can check to see if Ruby is installed by running the command:

ruby -v

If Ruby is installed, you should get a message that tells you which version you have. If you get a "command not found" error, you don't have Ruby installed. If it is not installed, or if your version is older than 1.8.4, you can install Ruby as follows:


wget ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.8.4.tar.gz
tar -xzvf ruby-1.8.4.tar.gz
cd ruby-1.8.4
./configure
make
make install

Next, you'll need to install the RubyGems development system. RubyGems is a powerful system for managing and installing Ruby code libraries, known as gems. Rails itself is composed of several gems, and once you've successfully installed RubyGems, you can proceed to install Rails. You can install RubyGems as follows:


wget http://rubyforge.org/frs/download.php/11289/rubygems-0.9.0.tgz
tar -xzvf rubygems-0.9.0.tgz
cd rubygems-0.9.0
ruby setup.rb

Now that we have Ruby and RubyGems installed, you can install Rails itself:

gem install -y rails

As you can see, the gem install command works much like yum install command does. The -y option installs all of the required dependences for Rails. You can get more information on what various gem commands do by running gem help.

One more thing: since we'll be using MySQL as our database backend, we'll need to install the MySQL gem. This will let Ruby scripts--such as our application--access MySQL databases. We can do this easily using this command:

gem install mysql

You might wonder why Rails doesn't install the MySQL gem for you; this is because Rails does not require a database backend. Most Rails applications use database access, and a large percentage of those are in MySQL, but you don't need a database adapter for all applications, and you are free to use one that's not MySQL. We'll be using MySQL in this example, where we will design a small Rails application.

Developing a simple Rails application

Our sample application will track entries in a checkbook, showing how quickly you can create real-world applications in Rails. You can add and delete entries, view descriptions, and even see a running total at the bottom of your application.

First, we'll create the skeleton for the application using the Rails command:

rails checkbookapp

This will create a new directory--named checkbookapp--under the current directory, then fill it with the essential files needed for a Rails application.

Most Rails applications will use a database--so will ours. By default, Rails uses a MySQL backend. Most Rails applications will use three different databases--one for development, one for testing, and one for production. We'll only use one: the development database. By default, it will be called checkbook_development--the name of our app, an underscore, and then the word development. Rails does not create the MySQL database for us, nor does it create our tables for us, so we'll need to do that next.

We need to create the database like this:

mysqladmin create checkbookapp_development

We have now have a blank MySQL database, so we need to create the table where we will store our data. To do that, we'll create a "ActiveRecord migration." This is a Rails way of specifying the database schema.

We could create the structure using SQL directly, but migrations have a number of advantages. For one, they are database agnostic; they can be used to deploy on any number of database backends. For another, they are versioned--you can incrementally change your database schema and use the "rake migrate" command to update any database to the most recent version. You can even migrate backwards to an older migration.

We create a blank migration as follows:

ruby script/generate migration InitialSchema

This makes a file for us, "db/migrate/001_initial_schema.rb". Edit that file so that it looks like this:


class InitialSchema < ActiveRecord::Migration
    def self.up
                create_table :entries do |t|
                                t.column :memo, :text
                                t.column :amount, :decimal, :precision => 9, :scale => 2
                                t.column :when, :date
                end
    end
end

def self.down
            drop_table :entries
end

There are two methods to our Migration class: up and down. This code is used by Rails to manage our database schema. The up method creates a table, and the down method destroys the table. The table has three columns: memo, which contains text (a variable length string); amount, a decimal column; and when, which is a date. The amount column has a precision of 9 and a scale of 2--that translate to a MySQL column type of DECIMAL(9,2). This might be different on a different database backend.

For our database, these settings mean that it stores up to $999,999.99. The precision refers to the total number of digits (in decimal), and the scale refers to the number of digits after the decimal point.

Note that Rails automatically adds an additional column for us: id, an artificial primary key that uniquely identifies each row. Although some database developers prefer natural key, this is a Rails convention that helps productivity. If you don't want an artificial primary key, or if your table doesn't require it, you can drop the column afterwards using the drop_column method. (Join tables, for example, wouldn't typically have an id column.)

Now that we've created the migration, we need to activate it. The "rake migrate" command does this for us; the following command will migrate the database for us:

rake migrate

That will run the up method of our migration, and create our entries table. If we created multiple migrations, the Rake migrate command would run only the migrations that haven't been run yet. Since each migration is stored in a separate file, it also stores our schema in any version control system we might use--so this works great for projects with multiple developers.

Our MySQL database now has a entries table. Now we need a model for this table. The model represents the table in code. In other web frameworks, we'd need to specify the table name, primary key, fields, and field types in the model. Rails, though, can figure out this information from the database itself, although you can override behavior if it doesn't fit your needs. You only need to modify the class if you want to add behavior, such as a relationship to another table or a method that performs custom calculations. We can create a model for our table like this;

ruby script/generate model entry

Next, we'll define our user interface using the Rails Scaffolding feature. Scaffolding can rapidly create an interface for a table. It is intended to rapidly create test or administrative user interfaces during development, not for deployment. For our example, though, we'll use the scaffold as our final interface. We can create it as follows:

ruby script/generate scaffold entry

This will create a number of files, including a controller, a number of actions, and a bunch of views. Larger applications would have a number of controllers, but we'll use a single scaffold controller. We need to tell Rails to make this our default controller. First, delete the indx.html file:

rm public/index.html

Next, edit the "config/routes.rb" file. This tells Rails what URL path corresponds to our controller. This means we can change what URLs look like without changing our backend code. Add this line immediately before the end statement at the end of the file:

map.connect '', :controller => "entries"

We now have a working Rails application. We can test it using WEBrick, a small development server intended to run Rails apps during development. Developers can test their app on WEBrick at any time without needing a full server environment. The following command will start WEBrick:

ruby script/server

You can now browse to http://127.0.0.1:3000/ to view the new application. You can add, delete, and edite entries--and if you have over thirty entries, they will be paginated.

Let's add one more feature to our application: a sum at the bottom of the listing. We can create this by adding just one line to the "app/views/entries/list.rhtml" file. Add this line right before the </table> tag:

<tr><th>Total:</th><td> <<span>=@entries.inject(0) { |s,v| s+=v.amount }</span>></td></tr>

You can reload your web browser, and you'll see a new line with a total displayed. The inject function call is a cryptic but powerful Ruby method to calculate the sum of an array--in this case, the entries on the current page of our application. Although this example is trivial, you can see how easy it is to modify a Rails application.

Deploying our application on Apache

We just learned how to test a Rails app using WEBrick, but for a production deployment, you need to use a more powerful solution. A very common and powerful combination for production deployment is Red Hat Enterprise Linux, Apache, and Mongrel. This combination is high performance, easy to maintain, and can be scaled easily to more application and database servers when necessary.

Typically, Apache will serve static files--HTML, CSS, images, and so forth--and the Rails requests will be sent to Mongrel. Mongrel will typically handle either a whole domain/subdomain or a single directory. In this example, we'll serve only Mongrel requests but you could easily have Apache serve other content side-by-side with the Rails content. In this case, we will use mod_proxy_balancer to automatically distribute requests to our Mongrel processes.

First, let's install Mongrel. We can do so as as follows:


gem install mongrel
gem install mongrel_cluster

There's a small caveat: Mongrel comes in two flavors, one for Win32 environments, and one for every other environment, so when you install Mongrel, it'll ask which you want. The Win32 option is for Windows, and the Ruby option is for every other operating system. The difference is that the Win32 package is precompiled since most Windows machines don't have a C compiler. In our case, we can use the Ruby package.

The second gem install command installs the "mongrel_cluster" plugin to Mongrel, which lets us use the Mongrel cluster support.

We're going to create a number of Mongrel instances--five, in our case, although you could use more. Since Rails isn't thread-safe, this will maximize performance--each process can execute only one backend call at a time. Mongrel has built-in tools to manage a number of mongrel instances, called a "cluster." We're going to set up a cluster using the following commands:


mongrel_rails cluster::configure -p 6001 -N 5
mongrel_rails cluster::start

This will create five Mongrel instances on ports 6001 through 6005. We need to configure Apache to send requests to these five instances. We can do this easily using mod_proxy_balancer.

mod_proxy_balancer and the other modules we will require--mod_proxy and mod_proxy_http--are installed with Apache 2.x by default. We can load and configure them as follows:


LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_http_module modules/mod_proxy_http.so


<Proxy balancer://checkbookapp>
    BalancerMember http://127.0.0.1:6001
    BalancerMember http://127.0.0.1:6002
    BalancerMember http://127.0.0.1:6003
    BalancerMember http://127.0.0.1:6004
    BalancerMember http://127.0.0.1:6005
</Proxy>

<VirtualHost *:80>
    ProxyPass / balancer://checkbookapp/
    ProxyPassReverse / balancer://checkbookapp/

</VirtualHost>

The first three lines load the module we need. The <Proxy> block that follows defines the five members of our cluster that will receive the requests--they are running on the same IP address, but different ports. We could use this same mechanism to distribute our requests accros multiple application servers.

The final <VirtualHost> block uses the ProxyPass and ProxyPassReverse directives to forward our requests. The ProxyPass directive forwards requests to the balancing cluster we just defined. The ProxyPassReverse directive adjusts the incoming responses so that they refer to the main server and not the individual clusters. Note that the first argument to each directive is '/', the root of webserver. We could make it a subdirectory, which would place our Rails application there. (You could also place the ProxyPass and ProxyPassReverse inside of a <Location> block, and then you'd only specify the cluster you wanted the <Location> to point to.)

If you start Apache, you should be able to browse to http://localhost:80 and view your application. This approach is fast and easy to maintain--and you can easily add additional application servers by adding new entries to the <Proxy> block.

If things go wrong

If you have problems with your Rails application, there a few steps you'll want to take. First, have you recently upgraded your Rails version? If so, this may have broken your Rails application. You can change the version of Rails your application uses by editing the "config/environment.rb" file and changing the RAILS_GEM_VERSION variable--RubyGems saves your old Gems for that kind of situation.

If you haven't changed your version of Rails, there are a number of log files you can check. You can check your Apache error logs in /var/log/httpd, of course, but you also want to check the log directory in your Rails application. It should contain a number of files, as both Mongrel and WEBrick will log there. If you use WEBrick in debug mode--which is the default--then you will also get more debugging information when an error happens, which is helpful. Note that even if you are in production, full debugging information will be outputted to any local machine--so if you access the web application from the same server it's deployed on, you'll get full debugging information.

Conclusion

As you can see, Rails applications can be developed quickly and efficiently. What's even better is that the fast development speed extends beyond simple applications to the production level deployment that we've only discussed here. With a little work, you can host Rails applications blazingly fast under Linux and Apache - and then scale quickly and efficiently.

About the author

David Berube is a freelance Ruby developer and consultant. His book, Practical Ruby Gems, will be in bookstores everywhere in early '07. Comments can be sent to djberube@berubeconsulting.com.