Sprinting with Rails 3


I love Ruby on Rails.  There are few things in life that bring me more joy than opening up Textmate and coding a Rails web app.   That being said, my infatuation has been with Rails 2, and I’ve been itching to get working with Rails 3 – especially with the prospect of a release candidate in the near future.    So, I spent some time playing with the new framework, and I feel like I’ve fallen in love all over again.  The goal of this post is to showcase some of the awesome new features in Rails 3 while building a generic, reusable application skeleton which follows Rails best practices.

1. Generating the app

To start things off, create a new project (I’ll call it “sprinter”) and set up the Git repository.
> rails new sprinter --database=mysql
> cd sprinter/
> rm public/index.html
> rm public/images/rails.png
> git init
> git add .
> git commit -m "Initial creation of the Sprinter app"
> mate .

As you can see, I didn’t even think about a .gitignore file.  Since the Rails community has fully embraced git as the source control system of choice, Rails 3 provides a default .gitignore file for you.

Side note: if you haven’t gotten up to speed on using git yet, you should.  GitHub has a nice crash course.

2. Configuring the app

The first place to start is the Gemfile.  This is the new place to declare all your gem dependencies (it used to be in “config/environment.rb”, which, by the way,  is now “config/application.rb”).    Once you open it up, you’ll see it’s pretty much the same as how you declared gems in environment.rb, except you wont be typing “config” over and over.

Here’s the Gemfile I use for this project:

source 'http://rubygems.org'
gem 'rails', '3.0.0.beta4'
gem 'mysql'
gem 'mongrel'
group :test do
  gem "shoulda"
end

Normally, I would include a few more gems here (specifically: HAML, friendly_id, authlogic, and paperclip), but this is just a quick starter app, so I omitted them for now.  I included “shoulda” in the test group simply to illustrate how to include gems purely for a test environment.  Plus, you should be using shoulda.

3. Bundler

Now that the gems are defined, I can use Rails 3’s new bundler to make sure all required gems are installed.  This is how you can guarantee every system is using the right gems for the project.  It’s similar to requiring gems in environment.rb, but bundler allows you to package the gems in the deliverable.  Also, bundler is far smarter than the previous system.  Previously, gem dependencies were read linearly out of config/environment.rb which had the propensity to cause issues.

For example, consider the following scenario.

  • You have versions 1.2.3 and 1.2.4 of a gem (let’s call it “some-gem”) installed on your system.
  • Your app depends on “some-gem” version “>= 1.2.3″, which is declared in config/environment.rb
  • A different gem required by your app depends upon “some-gem” version “= 1.2.3″

Since you have version “1.2.4″ installed, the “>= 1.2.3″ requirement would use the version “1.2.4″ installed on your system (because it is the latest, greatest version).  However, when it later reads that version “= 1.2.3″ is required, it fails since it already loaded version “1.2.4″.   It’s an edge case, but Bundler will prevent this from happening.

Anyway, I got a bit off subject there.  The next step is to use bundler to make sure all those gems are installed.

> sudo bundle install

You’ll see it fetch the correct versions of all the gems defined in the Gemfile.

4. Using jQuery

One of the best features of Rails 3 is that all the javascript is unobtrusive, thus being fairly framework-agnostic.  Since I’m a big fan of jQuery, and less so of Prototype, let’s swap in jQuery by doing the following:

  1. Delete all .js files (except for “application.js”) in the “public/javascripts” directory.
  2. Grab the “rails.js” file from the src directory of http://github.com/rails/jquery-ujs and place this in the “public/javascripts” directory.

Believe it or not, you now have everything you need to use jQuery.  To make things more seamless though, you should create file “config/initializers/jquery.rb”, and paste the following inside:

module ActionView::Helpers::AssetTagHelper

  remove_const :JAVASCRIPT_DEFAULT_SOURCES

  JAVASCRIPT_DEFAULT_SOURCES = %w(

    http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js 

    rails.js

  )

  reset_javascript_include_default

end

This will include the Google CDN hosted jQuery (saving you some bandwidth and speeding up your pages) when you use the “:defaults” javascript include tag.

Side note: Because all the javascript is unobtrusive,  some of those fun helpers like “link_to_function” no longer exist.  However, “link_to_remote” still exists, but you call it as a regular “link_to” with “:remote => true” as an option.

It’s one thing for the framework to try and enforce unobtrusive javascript, but we should also set up our code to enforce it as well.    At this point, I usually create the following helper methods in “app/helpers/application_helper.rb”:

def stylesheet(*args)
  content_for(:stylesheets) { stylesheet_link_tag(*args) }
end
def javascript(*args)
  content_for(:javascripts) { javascript_include_tag(*args) }
end

These functions help me group all the stylesheet and javascript includes together.  With that, I then update the application layout file like so:

<!DOCTYPE html>
<html>
<head>
  <title>Sprinter</title>
  <% stylesheet :all %>
  <% javascript :defaults %>
  <%= yield(:stylesheets) %>
  <%= csrf_meta_tag %>
</head>
<body>
  <%= yield %>
  <%= yield(:javascripts) %>
</body>
</html>

Another side note:  Rails 3 spits out pure HTML5.

Moving the javascript include tags to the bottom does several things:

  1. Any inline-javascript that depends on jQuery or any of our other methods will cause an error.  This forces you to use an external file for it (like you should).
  2. There is no browser blocking when loading the javascript files, thus causing the page to load faster.

5. Rails 3 Routes

Now that all the required gems are installed, we’re ready to roll.  I like to start by creating a simple “Welcome” controller for the app.  One thing to note is that all the Rails 2 “script/” commands are now part of the “rails” command.

> rails generate controller Welcome index

Followed by an update to the “config/routes.rb” file to set the “welcome” controller as the root of the project.

root :to => "welcome#index"

In Rails 2, you would have declared the same thing with something like:

map.root :controller => 'welcome', :action => 'index'

Rails 3 routes are built to be  more extendable and require a lot less typing (notice the “controller#action” naming convention).  Let’s make sure everything’s working:

> rake db:create
> rails server

The site should now be visible at “http://localhost:3000″.  When you’re satisfied, stop the server with Ctrl+C.

6. Scaffolding

So far, I think this is a great starting point for quickly getting up and running on a new Rails 3 project.  However,it really doesn’t do anything yet.    So, let’s make it into a simple blog.

> rails generate scaffold Post title:string body:text published:boolean

This looks pretty standard, but a few key things have changed for Rails 3.  One, the scaffold generates a “_form.html.erb” partial instead of repeating the form in the “new” and “edit” views, making things much DRYer.

This also sets up the routes like so:

resources :posts

Just to close the discussion on routes, consider if posts belonged to a user and also featured a “comment” action.  We could set the routes up like so:

resources :users do
  resources :posts do
    member do
      get :comment
    end
  end
end

Pretty clean, isn’t it?

Anyway, back to the app.  Let’s see how things are working.  Run:

> rake db:migrate

Starting the server and checking out “/posts” gives you the standard Rails scaffolding page.  All is good here.

7. And finally, Arel

Now I can introduce my favorite part of Rails 3: Arel.

In a nutshell, Arel is an ORM-agnostic framework based on Relational Algebra (what the SQL language is based on).  I’m sure I’ll be blogging about it in far more detail shortly, but it essentially allows you to make all your Rails queries chainable (much like “named_scope” in Rails 2 – in fact, it was written by the same guy).

Open up app/controllers/posts_controller.rb and we’ll give it a shot.

In the “index” action, you can see that it grabs the blog posts in the standard Rails way:

@posts = Post.all

Normally, to only show the latest 25 published posts, we’d change this to be:

@posts = Post.all(:conditions => { :published => true }, :limit => 25, : order => "posts.created_at DESC")

With Arel, we can make this awesome by writing it as:

@posts = Post.where(:published => true).order("posts.created_at DESC").limit(25)

You should notice that, apart from a new naming convention, there is one major difference:  in Rails 3, we don’t call “.all”.  This is by choice.  We could call .all on that line and everything would work as expected (and in some cases, you want to call it).  However, calling “.all” forces the query to be run immediately.  But, thanks to Arel, if we leave it off, the query is only run when we actually use the data.

So, our query is only run when the view hits:

<% @posts.each do |post| %>
...
<% end %>

I don’t know about you, but I think that’s awesome.  It’s cleaner, and far more extendable.  For example, say we wanted to have a toggle switch for published and unpublished posts.  Our “index” action could be something like:

@posts = Post.order("posts.created_at DESC").limit(25)
if(params[:draft])  
  @posts = @posts.where(:published => false)
else  
  @posts = @posts.where(:published => true)
end

Gorgeous, isn’t it?

Well, that’s it for now.  I hope this post helps you hit the ground running with Rails 3.  I’ll be sure to post more about Rails 3 as I discover it, but so far, I’m extremely excited about it.  I hope you feel the same way.  Leave your thoughts/gripes in the comments.

Information and Links

Join the fray by commenting, tracking what others have to say, or linking to it from your blog.


Other Posts

Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

[...] one version of Ruby (old faithful, 1.8.7), I use multiple versions of Rails – you know, since I love Rails 3 but am still tethered to Rails 2.  I thought I’d give it a try to see if it could help [...]