http://www.developer.com/

Back to article

Rails 3: First Impressions from a Veteran Ruby on Rails Guy


October 4, 2010

This article is based on my recent experiences getting up to speed on Rails 3, starting about four weeks before the final release. While I have used Rails in my consulting business for several years and my last book had two Rails examples, I explored Rails 3 with a fresh perspective because I have spent five months away from Rails development (working full time on statistical natural language processing and text mining).

I divided the article into two parts:

  1. Getting set up for Rails 3 development
  2. A quick overview of new features and how to use them

I cover only the Rails 3 aspects that I have been experimenting with during the past several weeks, so this is not an exhaustive overview by any means. However, you hopefully will have fun with the material and find it a useful jumping off point for learning Rails 3.

Note: The ZIP file containing examples for this article has top-level directories with the same names as the Rails project names that I will create.

I am not converting my old Rails projects to Rails 3, but all of my new projects will use Rails 3. I might decide to update in the future, but I'm not eager to change code that works. I have tried Rails 3 using Ruby 1.9.2, 1.8.7, and Rubinius. I use Ruby 1.9.2 for the examples in this article and that is probably the best choice for you too. That said, I find myself using Rubinius for a lot of my day-to-day Ruby development, so I have also favored it for experimenting with Rails 3.

If you are already a Rails developer, there are very few new things that you need to learn before creating new Rails 3 projects. The rails command is used for all command-line actions. Default routes are not set up for you, but so far I have adjusted quickly to the new system, which definitely is an improvement. Most new features are about control over customization and you can get into customizing your Rails applications as needed over a period of time. This is the approach that I use.

Getting Set Up for Ruby on Rails 3 Development

I assume that you have Ruby 1.9.2 and Rails 3 installed either from the Rails download page instructions or have installed the Ruby Version Manager (RVM) and installed Ruby 1.9.2 and Rails 3:

bash < <( curl http://rvm.beginrescueend.com/releases/rvm-install-head )rvm 1.9.2rvm list # optional  to show installed Ruby versions and which is the version being usedgem install rails

I will not recommend one editor over another, but I use TextMate on OS X, GEdit on Linux, and the e editor on Windows 7.

What's New in Rails 3?

The following sections highlight the most notable new features or updates in Rails 3.

New Router

As powerful as the new routing system is, you can also use it simply for common use cases. For example, if you create a scaffold like this:

rails generate scaffold Post title:string content:stringrake db:migraterm public/index.html

you can set your routes simply by adding this to your routes.rb file (which is generated empty):

  resources :posts

In this simplest form, Rails 3 automatically maps HTTP verbs like GET and PUSH to controller actions like edit and list. You will see another simple routing setup later in the first example. If you are not familiar with Rails routing, here is a quick overview. Consider the URL http://localhost:3000/posts/4 as an example. Then if you define in your routes.rb file:

match "/posts/:id" => "posts#show"

The method show in the PostsController gets called, and the parameter hash params can be checked for the database row ID for the table post (for example,params[:id]>). For the example class Post, you can also generate the proper URI from links using posts_path(@post.id) where I assume that you defined @post using something like @post = Post.find(4).

New Bundler for Storing Dependencies Inside Rails 3 Applications

Before Rails 3 you had several options for managing gems and plugins and they all had problems. Yehuda Katz and Carl Lerche wrote the gem bundler to make the dependencies for each of your Rails applications more transparent and easier to manage.

When you use rails new my_new_project_name to create a new application, the rails command-line utility is run from your Ruby's bin directory. The rails command-line script knows the current Rails version and adds a dependency to the generated GemFile. By default only the rails and sqlite3-ruby gems are configured, but many comments are generated showing other common gems that you might want to add. Ignoring the comments, the generated GemFile will look something like this:

source 'http://rubygems.org'gem 'rails', '3.0.0'gem 'sqlite3-ruby', :require => 'sqlite3'

As you will see in later examples, running the command bundle install will ensure that the required gems are installed.

By using the source method, you can choose one or more repositories to use.

All View Output Is Escaped By Default

This is one change I really like. You no longer have to use the utility Erb method h to escape HTML generated from user input; this is now the default. You can override this default behavior with <%=raw don't escape this text - insecure!%> .

ActionController

Instead of using all of ActionController::Base you can use just the base class ActionController::Metal and add in just the modules you need. When the Merb and Rails projects merged, one of the main goals was to make Rails more modular like Merb. As you would expect from its name, ActionController::Metal is a "bare metal" controller that lets you use just what you need in your application.

For better efficiency, let's start with an example that uses only rendering. This example is initialized using:

rails new meta_render_only --skip-prototype --skip-active-record --skip-test-unitcd meta_render_onlyrails generate controller rubyinform public/index.htmlrails server

Generators have been rewritten for Rails 3. Also notice that you no longer run Rails utilities out of the local script directory. Rather, you pass arguments to the rails command-line script that was installed and placed on your PATH when you installed Rails 3.

So we have something to render, you can create the file app/views/rubyinfo/index.rhtml and add the following text:

 <p>You are running Ruby version <%= ENV['RUBY_VERSION'] %> </p>

If you look at the config/routes.rb file that Rails 3 generated for you, you will notice that there are no routes set, so add this line:

  root :to => "rubyinfo#index"

This makes the default "landing page" for the Web application call method index on the RubyInfo controller. Now if you hit http://localhost:3000 you will see the rendered page. However, we are still bringing in all controller modules, even the old Rails 2.x compatibility support. Change the Rubyinfo controller to use ActionController::Metal and for now, the Web app will be broken:

class RubyinfoController < ActionController::Metalend
A suggestion you may want to follow at a later time:
One of the great things about Rails is that it is quite possible to read through the implementation code. Admittedly, this is not as easy to do as it was 3 or 4 years ago. Still, I would urge you to at least understand where the Rails-specific gems are kept on your file system and get comfortable looking at some of the code. To get started, find where your ruby executable is kept and use the following pattern to find the gems:
markw$ which ruby/Users/markw/.rvm/rubies/ruby-1.9.2-head/bin/rubyls /Users/markw/.rvm/rubies/ruby-1.9.2-head/lib/ruby/gems/1.9/gemsmate /Users/markw/.rvm/rubies/ruby-1.9.2-head/lib/ruby/gems/1.9/gems/*3.0.0*

Figure 1 shows the TextMate editor open to my gem installation directory with Rails 3 gems.

The Rails source code is well documented and the README files in top-level Module directories are useful and interesting. In my recent experiments with Rails 3 I found it useful to spend some time looking at specific code like the base controller code.

So, that is my suggestion, now back to exploring a bare-metal Web application. As I mentioned, after editing the RubyinfoController class to inherit from ActionController::Metal, this example is broken. We can fix it by editing the controller again to make it look like this:

class RubyinfoController < ActionController::Metal  def index    self.response_body = "Test rubyinfo bare-metal controller"  endend

OK, that works, but is it perhaps too close to the metal? Let's add just enough to use RHTML templates. Here is the new controller code followed by the new contents of app/views/rubyinfo/index.rhtml:

class RubyinfoController <ActionController::Metal  include ActionController::Rendering  append_view_path "#{Rails.root}/app/views"  def index    @time = Time.now    render  endend<p>You are running Ruby version <%= ENV['RUBY_VERSION'] %> </p><p><%= @time %>

Since I am also an enthusiastic user of Sinatra and I used to use Merb for projects, the ability to customize Rails 3 -- especially to use only the things specifically required for a project -- is one of my favorite new features of Rails 3. That said, you might want to use the full ActionController::Base that loads everything (even Rails 2.x compatibility code) when you first get started.

You Can Easily Use Alternative Data Stores

You don't have to use ActiveRecord because Datamapper and Sequel are also supported out of the box. You can also use alternative backend datastores. I'll show you an example using the MongoDB data store with the Mongoid object relational mapper (ORM) and also another example using the Datamapper library. The Mongoid developers hook into the Rails 3 bootup process (that uses so-called "railties") to make MongoDB as easy to use as officially supported ORMs.

Using MongoDB with the Mongoid library

I am an extremely enthusiastic user of MongoDB both as a datastore for Web applications and for large-scale data analytics, which usually involves all write operations going to a master Mongo and queries used for analytics getting spread out over one or more read-only slaves. Using both read/write masters and read-only slaves is a useful deployment use case, so I am going to use the Mongoid ORM specifically in this example because it supports automatically sending all writes to the master and all reads to a slave.

If you want to work through this example, you need to install a few gems before building a new Rails project: mongoid, bson, and bson_ext. We will add these to the generated GemFile after creating a new Rails project:

rails new mongoid_example # optional: --skip-activerecordcd mongoid_example

If you add --skip-activerecord then your Rails application will be created without adding in ActiveRecord and its support. In this example I'll show you how to explicitly remove ActiveRecord.

You also need to edit your GemFile and add the following (gem versions may change):

gem "mongoid", "2.0.0.beta.17"gem "bson_ext", "1.0.4"

You can then make sure that all of the bundled gems in your GemFile are installed, and then create a mongoid configuration file:

bundle install rails generate mongoid:config

This generates a config/mongoid.yml file that uses a single mongo on localhost; configuration options for setting up slaves are commented out. Now, if we generate a model, it will use MongoDB. If you don't need a relational database, then use the instructions on the Mongoid Web site for disabling ActiveRecord; i.e. edit the file config/application.rb to comment out the line require 'rails/all' and add the railties that you need:

# require 'rails/allrequire "action_controller/railtie"#require "action_mailer/railtie"require "active_resource/railtie"

We can now generate a scaffold like the last example, but using MongoDB:

rails generate scaffold Post title:string content:stringrake db:migraterm public/index.html

This is not much of an example for using the features of MongoDB but it will get you started.

Tip: You usually will not normalize data in MongoDB storage as you do in a relational database. As a simple example, if your object models include a user and posts created by that user, it is good MongoDB style to have a user document schema that also contains data for the posts for the user. This sometimes leads to duplicated data. This is great for runtime performance because you should configure a mongo service with enough memory to keep all indices in memory, so reading a user's data (including posts) might cost just one disk seek and one read operation. The downside is dealing with updating and deleting replicated data.

Fortunately, with Model classes, it is reasonably easy to put all of the code to create, update and delete data for a model and nested objects in one place.

Using Datamapper

I like to use Datamapper in both Web apps and non-Web apps because of its flexibility to use multiple backends (relational databases, MongoDB, etc.), and you can span object models over multiple backends. While it has always been straightforward to use Datamapper with Rails, it got easier with Rails 3. You start by using a Rails template provided by the Datamapper developers when creating a new project:

rails new datamapper_example -m http://datamapper.org/templates/rails.rb
Note: Check the generated GemFile and make sure that it is using the version of Rails that you want, and edit the version if required. (I had to change the Rails version from 3.0.0.rc1 to 3.0.0.)

As we did in the last example, create a test scaffold, do a data migration. A route for resources :post should have been created for you automatically when generating the scaffold (this was not done when using Mongoid).

Using Controller Responders

By using class-wide responders, you can make controllers that respond with XML, JSON, and so on much more precise. Responders can also be used to factor out common controller code for reuse. Here is a short example that just uses responders built into Rails:

class PostsController < ApplicationController::Base  respond_to :html, :xml, :json  def index    respond_with(@users = User.all)  endend

Developers are starting to write and distribute other responders, for Flash generation for example. If your Web application had to support generation and delivery of custom data formats or data standards like RDF, it would make sense to write a responder to handle new datatypes.

Wrapup

Rails was originally designed to make most decisions for you, offering simple, effective ways to solve common problems. With Rails 3, it certainly has moved away from this philosophy a little because now the platform can be highly customized. In the future I look forward to being able to use just the parts of Rails I need for any given application.

I have often used the Sinatra project for implementing Web services and very simple Web applications. As I get more experienced with the new features of Rails 3, I may start using Rails for very light projects instead of Sinatra (or I might stick with Sinatra).

Code Download

  • Rails3Examples.zip
  • About the Author

    Mark Watson is a consultant specializing in Web applications, text mining, and artificial intelligence development. He is the author of 16 books and writes both a technology blog and an artificial intelligence blog (see www.markwatson.com). He lives in the mountains of Central Arizona with his wife Carol and a very feisty Meyers Parrot.

    Sitemap | Contact Us

    Thanks for your registration, follow us on our social networks to keep up-to-date