Rails is a Ruby-based framework for building Model View Controller (MVC) applications. Ruby itself is a language that has generated a lot of interest in the last few years, not least because of Rails. Rails was initially created from work done by David Heinemeier Hansson for a company called 37signals. DHH (as he is known) was working on a number of web sites and Rails emerged as a framework from the internal work done building web applications. Internally Rails supports many of the principles of agile software development such as Test Driven Development (TDD), “You Aint’ Going to Need It” (YAGNI) and “Do not Repeat Yourself” (DRY), and is said by many in the Rails community to be ‘opinionated software’, i.e. it has its own opinion about how things should be done, and if you don’t like it, tough!
In this article I’ll introduce Rails and look at certain aspects of using it, such as its support for TDD and database access. Mac OSX seems to be the system of choice for building Rails applications and that’s what I’ll use for this article, during which I’ll talk about starting development of a blogging application (RBlog), which is currently a work in progress.
Start at the beginning Go on to the end and then stop, said the Red Queen
Rails tries to make many common tasks simple. To help do this Rails has a set of scripts that can be run to generate code. The scripts can generate the initial structure of the application and the classes and software needed in the application as we go along. These scripts are run from within a console window.
To start a Rails app I need to run the code generator to create the application structure. To do this I’ll start up a terminal and, making sure I’m in the right directory, call:
This script will create a new Rails application for me. Running the script gives me the well known (well known if you do Rails!) directory structure of:
app config db doc lib log public script test tmp vendor
The above structure is common to all Rails applications and is part of the opinionated nature of Rails. For now I care about the following directories:
- “app” contains the application code, in this case the code for the Models, Views and Controllers that make up the MVC part of the application, there’s other code in here as well to support MVC
- “db” contains the database ‘migrations’, this is code used to create, manage and update the DDL for the database, this is executable Ruby code rather than SQL and there’s more on this later “public” is where the public web files are stored
- “config” is where various set-up files live The first thing I want to do once I’ve created the application is to execute it to see what we have. So once the application has been created I can fire up another terminal (I usually have at least two terminals open and sometimes three, one for my commands, one for the server and another to see the log file). In the new terminal window change the project directory and run the server with the command ‘script/server’. This starts the server on port 3000, the default. The server I’m using here is Mongrel which is the default server built into the Rails download, there are other servers you can use such as WEBrick and if course it’s possible to run production servers behind Apache. There’s plenty of discussion on these servers out on the web so I won’t go into the differences here.
Once the server is running you can point your browser at:
http://localhost:3000 …and you should see something like
This is good. We have a running application. It doesn’t look like it does a lot but it does at least work. The next step is to create the database. For the database I’m going to use MySql, which has long been the database of choice for Rails applications, although the default database in the current release is SQLite, which is fine for development and testing, but not for production. Rails, like other environments, abstracts away the database access. The first step to doing this is to configure rails to use the specific database engine in use. This configuration is in the database.yml (yml files are YAML, which stands for YAML Ain’t a Markup Language) file in the config directory. I edit the file to look like this:
development: adapter: mysql encoding: utf8 database: rblog_development username: root password: socket: /tmp/mysql.sock
This points the application at a database called rblog_development using the root user with no password. I can then add similar entries for the test and production databases. Once that is done I create the development database by running:
mysqladmin -u root create rblog_development
This creates the “rblog_development” database.
Now that all the necessary structure is in place I can create the first controllers and models. What do I know about the blog? I know it’s going to have users, users can have blogs and blogs will have entries and comments. There may be more things eventually, such as tags, categories, pingbacks etc., but for now that’s enough. This means that at the moment I need to create models for users, blogs and blogentries; I also need to create controllers for these resources.
For the users model the user will have an email address and password, the password will be stored as a hash and will be salted (this is stolen more or less directly from the Agile Web Development With Rails book written by Dave Thomas et al, published by the Pragmatic Programmers). A user will have a name that can be displayed on comments or on a blog and users may also own blogs. For now I’m going to limit this to one blog per user, but in the future this may expand to multiple blogs.
As I mentioned above, Rails has a bunch of scripts that will generate the base code for me. In this case I need to generate a ‘scaffold’. The scaffold gives me the outline of the resources I want to use. To create the scaffold I need to run the ‘generate’ script, like so:
script/generate scaffold user email:string hashed_password:string name:string blog_id:integer
This will create the model, the controller and the view for the User resource as well as the database migration. I’ll take a look at the details in a moment. I can then do the same for blogs and blogentries:
script/generate scaffold blog title:string sub_title:string owner_id:integer admin:boolean script/generate scaffold blog_entry title:string entry:text author_id:integer
Notice that I’m using singular names for the model components, so user, blog and blog_entry. This is a standard Rails naming convention, the singular names map onto plural database table names. So a User model maps onto a Users table (each row in the table will contain a single User, so the naming convention makes sense).
The generated blog entry code looks like this:
class BlogEntry < ActiveRecord::Base end
class BlogEntriesController < ApplicationController # GET /blog_entries # GET /blog_entries.xml def index end # GET /blog_entries/1 # GET /blog_entries/1.xml def show etc...
class CreateBlogEntries < ActiveRecord::Migration def self.up create_table :blog_entries do |t| t.string :title t.text :entry t.integer :author_id t.timestamps end end def self.down drop_table :blog_entries end end
Notice that the BlogEntry model contains no data members, they are defined in the migration. The migration contains the code that will set-up and tear down the database, in this case creating a Blog_Entries table with title, entry and author_id column. The table will also contain a created_at and updated_at column, added by the t.timestamps entry. And finally the table will contain an Id column that will be the primary key for this table. So I get some things for free (the Id), and again this points up the opinionated nature of Rails. There are also a number of view files created for the Blog_Entries type which I won’t show here. The generated code for the other resources follows a similar pattern.
ActiveRecord and database access
One of the driving forces of Rails is to make things easier for developers. It does this partly by taking decisions out of developers’ hands. It’s an ‘opinionated’ framework after all, and one of the opinions it has is on the pattern to use for database access. Its choice in this case is the ‘active record’ pattern. Within Ruby, ActiveRecord is all about providing database access for our application.
Rails has an ActiveRecord module and the model classes all derive from ActiveRecord::Base, for example:
BlogEntry < ActiveRecord::Base
It is this module that provides the active record support for the framework. Like much of the rest of Rails, ActiveRecord follows naming conventions. Here for example the BlogEntry class represents a row in the blog_entries table. How does this happen?
Previously I ran this script:
script/generate scaffold blog_entry ...
This script created two files, the file with the model class BlogEntry definition and a “migration”. The migrations are “scripts” that help create and mange the database definitions, essentially they are DDL for Rails. Migrations are used to both set up and tear down databases. The files contain class definitions that specify the steps to take when managing the database. Migrations are timestamped so that it is easy to apply migrations in the correct order and to rollback those migrations in reverse order if needs be. The migrations live in the db/migrate directory. Currently there are four migrations in there:
20080925064318_create_sessions.rb 20080925065056_create_blogs.rb 20080925064319_create_users.rb 20080925065210_create_blog_entries.rb
The first is a fairly standard Rails migration that creates the session tables (run rake db:sessions:create to create this), the others are specific to this application. Each migration has a date-time as part of the file name and it’s this name that determines the order in which the migrations are run. The 20080925065210_create_blog_entries.rb looks like this
class CreateBlogEntries < ActiveRecord::Migration def self.up create_table :blog_entries do |t| t.string :title, :null => false t.text :entry, :null => false t.integer :author_id, :null => false t.timestamps end enddef self.down drop_table :blog_entries end end
This is similar to the code shown previously but with constraints added to three of the columns to prevent them taking on NULL values. I added these constraints by hand editing the file.
So the migration is a class that derives from ActiveRecord::Migration and provides two class methods (static methods to C#/C++ folks), up and down (it’s the “self” that indicates that these are class methods and not instance methods). You can run the migration from the command line by using the Rake command:
rake db:migrate Rake is Ruby’s equivalent of make, ant, nant or MSBuild.
This runs any migrations that have not yet been run. How does it know which migrations to run? There is a database table that holds the information about the migrations that have been run. I can see this table in MySQL by running the following commands:
$ mysql -u root mysql> use rblog_development mysql> show tables; …which show something like: +-----------------------------+ | Tables_in_rblog_development | +-----------------------------+ | blog_entries | | blogs | | schema_migrations | | sessions | | users | +-----------------------------+ …and running: mysql> select * from schema_migrations; +----------------+ | version | +----------------+ | 20080923152418 | | 20080923152427 | | 20080923152435 | | 20080925064318 | | 20080925064319 | | 20080925065056 | | 20080925065210 | +----------------+
…shows the above rows on my machine as I write this article. Notice that the last entry in the table matches the datetime portion of the name of the last migration file.
When a migration is run (assuming it has not yet been added to the database), then the self.up method is executed. This method creates or modifies database entries. In the case of the blog_entries migration it creates the table and adds the six columns defined in the migration, remember that the id and timestamps are also added.
Again, running mysql shows this:
mysql> show create table blog_entries; +--------------+---------------------+ | Table | Create Table | +--------------+---------------------+ | blog_entries | CREATE TABLE 'blog_entries' ( 'id' int(11) NOT NULL auto_increment, 'title' varchar(255) NOT NULL, 'entry' text NOT NULL, 'author_id' int(11) NOT NULL, 'created_at' datetime default NULL, 'updated_at' datetime default NULL, PRIMARY KEY ('id') ) ENGINE=InnoDB DEFAULT CHARSET=latin1 | +--------------+--------------------+
Notice that timestamps turns into two columns, and that an id column has been added as a primary key.
A migration can also be rolled back. Running rake db:migrate rollback will rollback the last migration, or a specific version can be specified. For example rake db:migrate VERSION=20080925065056 Running the migrations this way runs them in reverse order up to the specified migration, on the way the self.down method of each migration is called. For the blog_entries migration would drop the table. The down method should undo whatever the up method did!
One of the interesting (and sometimes frustrating) things about Rails is the way the migrations and the models work together. Running the script/generate scaffold blog_entry creates two files, the migration and the model. Looking in the model file there is… nothing, just the class definition. The knowledge about the members of this class is in the migrations. This takes DRY (Do Not Repeat Yourself) to the limit but it can mean looking in several files (there maybe more than one migration per model) to find everything that the class uses. If the migrations get too “spread out”, i.e. there are three or more migrations with modifications to one table, then it is worth amalgamating those migrations into one file.
Once you have the database in place it is a trivial exercise to use them, and again Rails comes into its own here. ActiveRecord provides finder methods that can be used to find entries in a table based on the primary key, and ActiveRecord also extends the base find methods available dynamically. I can explore this behaviour outside of a browser, which is a great way to do testing.
From a terminal I can open a Ruby console like this:
$ script/console Loading development environment (Rails 2.1.0) >>
…from here I can then execute Ruby code. I can create a new blog_entry and add it to the database, something like this:
>> be = BlogEntry.new => #<BlogEntry id: nil, title: nil, entry: nil, author_id: nil, created_at: nil, updated_at: nil> >> be.title='Hello World' => "Hello World" >> be.entry='My first entry' => "My first entry" >> be.author_id=1 => 1 >> be.save! => true
In this case I create a new BlogEntry object, set its fields then save it to the database. The call to save! throws an exception if something goes wrong. In this case the code is written entirely in the console, but the same code would be run from the within the application itself.
Barely scratching the surface
In this article I’ve barely scratched the surface of Ruby or Rails. I haven’t touched on the controller’s support for REST, how resources are created and destroyed, how tests are run. In a follow-up article I’ll look at these topics.