What's new in Ruby on Rails 3, and what has stayed the same? Reuven explores the latest version of this framework and what it means for you.
I still remember an IM conversation I had with a friend several years ago. He asked whether I'd heard of Ruby on Rails, and I replied that although I'd heard of it, I hadn't yet gotten the chance to work with it. Several months later, I had such an opportunity, and it wasn't long before I was hooked on both the Ruby language and the Rails framework.
It turns out that I wasn't the only one to be so smitten with Rails. During the past few years, Rails has changed the face of Web development. It has attracted many people (including myself) to the Ruby language, while also affecting, influencing and inspiring Web development frameworks in other languages.
When people ask why I like to work in Rails, I tell them there's no one answer, because I don't believe any one part of Rails is revolutionary. Rather, the developers have found hundreds, and perhaps thousands, of things that were traditionally difficult or annoying in other Web frameworks and have tried to sand down those sharp edges. For example, database migrations, which let you define and create your database schema in pieces, building it up over time, have saved me enormous headaches. The same is true for Active Record's automatic getter and setter methods for columns in the database. And for controller filters—the list goes on and on.
The answer, I'm happy to say, is that it was, indeed, worthwhile. Ruby on Rails 3 hasn't changed too much from its predecessor; it still looks, feels and acts like the Rails we know and love. Most changes are small and subtle, but where they aren't, you can understand the reasoning behind them and quickly adjust to the changes. I've been surprised to discover just how easily I've adjusted to the Rails 3 way of doing things.
This month, I give a whirlwind tour of Ruby on Rails 3. I describe where things have changed a lot, where they've changed a little, and where they haven't changed at all—at least, for a developer using it. Under the hood, Rails has changed quite a lot, adding a number of new classes, modules and abstractions that have made it into a more modular framework than it was before. This means if you want to implement a new database-storage adapter, for example, Rails 3 will provide you with tools to make its implementation and integration fairly easy.
Before I go into what has changed in Rails 3, it's probably worth saying what's remained the same. First, Rails remains true to the model-view-controller (MVC) paradigm, with an emphasis on “fat” models—that is, the classes you define using models should contain the bulk of the business logic, as well as acting as an ORM and connection to the database. Controllers remain in the role as a go-between, accepting connections from the user and rendering the output in whatever format is appropriate. Views contain a combination of HTML and logic (with as little code as possible) that allow you to display everything from personalized menus and buddy lists, to XML and even PDF output.
Rails 3 certainly is more open to alternatives than was previously the case. When you create an application, you can specify that you want to remove Active Record, Prototype and/or test-unit if you prefer to use something else in their place. But these are options, and it means that for programmers who want to use the same systems they used in Rails 2, no changes will be necessary.
Although I haven't personally benchmarked the performance of Rails 3 in comparison with Rails 2, there's no doubt that it feels faster than Rails 2. This is especially true if you run Rails 3 with Ruby 1.9.2, the latest version of the language significantly faster than Ruby 1.8.7 in many respects. A great deal of attention has been spent on making Rails 3 more modular and much faster than Rails 2, and it shows.
That said, a bunch of little changes will become obvious almost immediately upon starting to work with Rails 3. For starters, Rails no longer has a bunch of different commands in the script directory. Instead, you use the global rails command with a parameter indicating what you want to do. So, to create a new Rails application, you use rails new appname. But, to enter the console, you say rails console or, as a gesture to slow typists, rails c. Similarly, you can use rails generate to create a new controller, model, resource or scaffold (among other things). And, you can use rails dbconsole to open a client connection to the database defined in the current environment.
I must admit, I never had a problem with the old programs in the script directory. At the same time, this means your Rails application directory can be largely empty, because the scripts instead will come from the global Rails installation.
One small but significant change is the way values are displayed in views. In previous versions of Rails, the (default) ERb templating system allowed you to insert values into the resulting HTML as follows:
<h1>Welcome back, <%= current_user.name -%>!</h1>
<h1>Welcome back, <%= raw current_user.name -%>!</h1>
The jewel in the Rails crown continues to be Active Record, an ORM that allows you to think and code with objects, while knowing (somewhere in the back of your mind) that each object instance is being stored to and retrieved from a relational database table.
The biggest change in Active Record is in the syntax you use to specify queries. In Rails 2, you would write something like this:
@people = Person.all(:conditions => "created_at > '2010-jul-14'", :order => "last_name ASC, first_name ASC", :limit => 10)
Rails 3 introduces “Active Relation”, a library used by Active Record, which changes the way you structure these queries. On the surface, the changes might seem annoying and petty. You would rewrite the above query as:
@people = Person.where("created_at => '2010-jul-14'") .order("last_name ASC, first_name ASC") .limit(10) .all
The first thing you'll notice is that the query has been broken up into several methods. The second thing you'll notice is that the .all call comes at the end. This is because until you invoke .all, nothing is executed in the database. This means you can mix and match method calls, adding additional conditions and complexity to your queries, until you finally decide to invoke it. So, you can build up a query over time, pass it as a parameter to a method, return it from a method or add to it conditionally—all before invoking the query on the database.
Aside from this, Active Record seems (again, on the outside) not to have changed very much. Validations now can be invoked with a slightly different syntax, foregrounding the name of the attribute you want to validate, rather than the validation itself. So instead of saying this:
validates_presence_of :email validates_uniqueness_of :email
you now can say this:
validates :email, :presence => true, :uniqueness => true
I'm still not sure which of these syntaxes I prefer. Fortunately, at least for now, the old syntax still works and doesn't raise a deprecation warning.
Under the hood, there have been a bunch of changes to Active Record. Active Relation is probably the biggest and most obvious, but another is the modularization of Active Record, such that validations now are in a separate module. This probably won't affect most people, but it means if you develop a different ORM, or if you're implementing an interface to a non-relational (NoSQL) database, such as MongoDB, you now can use the validation system from Active Record without having to re-invent the wheel.
Overall, Active Record 3 continues to shine. It's one of my favorite reasons for using Rails, and I know I'm not alone in my appreciation for all the things it does to help my applications keep humming along.
Perhaps the biggest change you are likely to encounter in Rails 3 is Bundler. One of the best things about programming in Ruby is the huge library of gems, or downloadable packages, available to the community. This is Ruby's answer to Perl's huge CPAN library, and although Ruby gems aren't even close to the size and scope of CPAN, they are being developed at an impressive pace.
Earlier versions of Rails attempted to handle the inclusion of gems inside the main configuration file, config/environments.rb. In this file (or any of its environment-specific files), you would put a line that looked like this:
Doing so would ensure that the will_paginate gem is available and included. A number of Rake tasks were defined to automate the process of testing for installed gems, installing them on the system and installing them in the application.
Rails 3 changes all of this, in favor of a program called Bundler. The idea is that you create a file, called Gemfile, in the root directory of your Rails application. You then invoke bundle install, which goes through your Gemfile, ensures that all of the gems are indeed installed and creates a file called Gemfile.lock that specifies the precise versions that need to be installed for the application to run. Together, the Gemfile and Gemfile.lock files help ensure that the gems you need are installed on both your development and production machines, either in the system's gem directory or privately in the application.
I must admit that I'm still getting used to Bundler, and I haven't yet become convinced of how wonderful it is or how superior it is to the previous way we configured gems. I'm also more likely than not to forget to type bundle install before running my server.
That said, I do see an advantage to having all gem requirements and configurations, regardless of environment, in a single file. It certainly makes deployment easier on such hosting systems as Heroku, and it avoids some of the confusion I've experienced with various gems being required in different environments. This is perhaps the only feature of Rails 3 that has yet to overwhelm me with its advantages, but the pain of switching to Bundler is relatively small, and I have a feeling I'll become convinced over time that it's useful and even superior.
The last major change in Rails 3 that I cover here is routing. The router is the part of Rails that maps HTTP requests to controller methods. If a user sends a request of GET /people/5, it's up to the router to determine that the people controller's show method has an ID of 5.
For several years now, Rails has tried to simplify routing by encouraging the use of REST (representational state transfer). That is, each URL describes a particular object, and it is the HTTP verb (GET, POST, PUT, DELETE) that describes what you want to do to that object. If I just want to see person #5, I can say:
But, if I want to update person #5, I would say:
along with name-value pairs describing the attributes that should be updated.
In Rails 2, you would describe a resource in the routes file (config/routes.rb) with a line like this:
In Rails 3, things have gotten simpler. For starters, you can declare a resource as follows:
In and of itself, that's not a big change. But, if you need to add additional methods beyond the standard seven (index, show, new, create, edit, update and destroy) Rails provides, you can do so in a block:
resources :people do post "send_feedback", :on => :collection end
This was certainly possible with the older syntax, but it was a bit more convoluted.
If you simply want to add a single, hard-coded route, you can do it with:
Rails 3 is smart enough to know this means you want GET requests for /home/faq to be redirected to the home controller and to invoke the faq method.
I must admit that Rails routing always seemed like a bit of black magic to me, even after years of working with it. The simplification that Rails 3 offers is most welcome, in that I feel I have a much better understanding of what it does and how it accomplishes it. Best of all, my routes.rb file has become less complex and easier to maintain, which is, as I mentioned previously, one of the biggest reasons for working with Rails.
After describing Rails 3 so enthusiastically, you might expect that I'll tell you to go out and upgrade whatever Rails 2 applications you might have written immediately. And, indeed, if the application is simple and small, such an upgrade is not a bad idea.
But, if you're like me and work with some large, complex applications, switching is not likely to be a quick or easy process. A large number of queries will need to be rewritten to use Active Relation. The routing table will need to change, the Gemfile will need to be rewritten, and you'll have to double-check your helpers for XSS attacks. This doesn't mean upgrading is a Herculean task, but it's not something to attempt in one evening.
The first thing I would suggest to anyone considering an upgrade is to ensure that your tests, and especially your integration tests, are in place, passing and providing you with adequate test coverage. Once you have such tests in place, you can start the upgrade process, checking at every stage to see what you might have broken.
A plugin called rails_upgrade is available; it provides a Rake task that looks through your code and configuration, tries to determine places where you might encounter problems and points them out to you. If you have good test coverage and run rails_upgrade, you're likely to know where potential problems lie and whether you've broken anything when you try to align your code with Rails 3.
Finally, upgrading to the latest version of Rails 2 (2.3.10, at the time of this writing) is a good way to prep for an upgrade to Rails 3. If you read through your logs, you'll find a number of deprecation warnings that are meant to nudge (push?) you in the direction of Rails 3 compatibility. Even if you don't plan to upgrade right away, getting your code closer to Rails 3 standards won't be a bad thing.
Rails 3 is a terrific upgrade to an already great Web development framework. While it was under development, I followed the blog postings and explanations with some trepidation, wondering how different Rails 3 would be from previous versions. I needn't have worried. As Rails has improved my experience as a Web developer in many small ways, so too has Rails 3 improved on its predecessor with many small changes. I generally like these changes a great deal and believe this shows that Rails still has a lot going for it. Upgrading might be tricky in some cases, but it's worth working in that direction, even if it takes a while. The application's execution speed will improve as a result, but so will your development speed and the maintainability of your code.