Rails Dispatch

Rails news delivered fresh

Presented by Engine Yard

In this our first Rails Dispatch content push, we’ve got two great pieces of content for you:

Rails 3 introduces a series of brand new features that will make your Rails applications better and your experience more pleasant. In today’s post, I’m going to cover some of the most important improvements in Rails 3, and show how you can take advantage of those improvements today. This post is a bit technically detailed, and ideally targeted at those with previous Rails experience.

For those of you less familiar with Rails, we’ve also got a screencast, which, as homage to the still-famous “build your blog in 15 minutes with Rails” tutorial, walks you through building your blog in Rails 3. This time, we’re estimating it’ll take more like twenty minutes, but that’s still pretty impressive… Now on to the meat of the post:

The 3.0 release introduces significant improvements to your day-to-day work with Rails, but only deprecates the old APIs. This means that you should have no trouble bringing a Rails 2 app up to speed on Rails 3, trusting deprecation warnings to get things prepared for the final removals to come in Rails 3.1 and 3.2.

New Router

The Rails router has always beaten the pants off manual .htaccess files or Nginx configuration, and Rails 3 makes it even more powerful.

Constrain Your Routes Based on Anything

Rails 2 allowed you to provide regular expression constraints for any route segments. The Rails 3 router allows you to constrain your routes based on anything in the Rack environment, using Rails’ request object.

Myapp::Application.routes.draw do |map|
  get "/posts" => "posts#show", :iphone => true,
    :constraints => {:user_agent => /iPhone/}
end

This will match a GET request to /posts with a user agent string containing the text iPhone. It will route the request to the PostsController’s show action, and add an extra parameter :iphone to the params hash available in the action.

For more advanced constraints, you can create an object to handle it.

class BlacklistConstraint
  def initialize
    @ips = Blacklist.retrieve_ips
    @mutex = Mutex.new
    @last_update = Time.now
  end

  def matches?(request)
    @mutex.synchronize do
      if (Time.now - @last_update) > 3600
        @ips = Blacklist.retrieve_ips
        @last_update = Time.now
      end
    end

    !@ips.include?(request.remote_ip)
  end
end

Myapp::Application.routes.draw do |map|
  constraints BlacklistConstraint.new do
    get "/posts" => "posts#show", :iphone => true
    # more routes
  end
end

This constraint will block users from blacklisted IPs, updating the list of banned IPs every hour.

Optional Segments

You can now specify that some segment in a route is optional. You can even specify nested optional segments. The easiest way to understand it is in terms of the Rails 2 default route.

match "/:controller(/:action(/:id))(.:format)"

This will match /posts, /posts/new, /posts/edit/1, and allow an optional format at the end of each segment. For each optional segment, you can also provide defaults.

match "/:controller(/:action(/:id))(.:format)",
  :defaults => {:action => "index", :format => "html"}

Now you have the same power to design flexible routes as Rails 2 had internally for the default route!

Built in Redirection and Rack

You no longer need to create a controller simply to perform a simple redirect from one URL to another. Imagine you are changing the URL scheme of your site from /stories/2009/02/12/rails-dispatch-announced to simply /rails-dispatch-announced.

match "/stories/:year/:month/:day/:name" => redirect("/%{name}")

You can use the %{param}% syntax to specify a parameter from the parameters hash that Rails should interpolate into the path to redirect to. Of course, you’re not just limited to simple String interpolation. You can also pass a block to the redirect method, which takes the params hash as the first parameter, and the Request itself as an optional second. Coupled with the more powerful constraints, this empowers you to use the router for a whole lot more than you’re used to.

constraints :user_agent => /iPhone/, :subdomain => /^(?!i\.)/ do
  match "*path" => redirect {|params, req| "http://i.myapp.com/#{req.fullpath}" }
end

If you make that your first route, Rails will automatically redirect any request coming from an iPhone to the same path, but prefixed with “i.”

As a closing note on the router, this redirect syntax works because the router always dispatches to a Rack application. The redirect method returns a stub Rack application that takes in the request and returns the proper headers to effect a redirect.

ActiveRecord Overhaul

ActiveRecord has gotten a major overhaul, making it much easier to work with and make ActiveRecord queries.

New Chainable Methods

Instead of one huge Hash, you can now make queries by chaining together one or more of the following methods:

  • select
  • from
  • where
  • joins
  • having
  • group
  • order
  • limit
  • offset
  • includes
  • lock
  • readonly

For instance, you could make a query for the ten most recent posts by DHH in the “Rails 3” category:

Post.where(:author => "dhh", :category => "rails3").limit(10).order("created_at ASC")

The really cool thing about this is that you can create a starting scope in your controller, and refine it later in helpers:

@recent_posts = Post.limit(10).order("created_at ASC")

# Helpers
def posts_by(author)
  @recent_posts.where(:author => author)
end

def posts_in(category)
  @recent_posts.where(:category => category)
end

@recent_posts = posts_by("dhh")
@recent_posts = posts_in("rails3")

Until you actually try to iterate over the collection (using a method like each), ActiveRecord is simply forming the query, so you can make whatever tweaks you want until the last moment.

This also means that you can cheaply create queries in the controller and avoid actually hitting the database if the underlying view is cached.

@recent_posts = Post.where(:author => "dhh", :category => "rails3").
                limit(10).order("created_at ASC")
<% cache do %>
  <% @recent_posts.each do |post| %>
    <h1><%= post.title %></h1>
    <div class="entry"><%= post.body %></div>
  <% end %>
<% end %>

In this case, if the cache is populated, Rails will never hit the database at all, making it even easier to keep Ruby logic out of your views.

Scope Overhaul

In Rails 3, you can use this same API in your models to build reusable scopes.

class Post < ActiveRecord::Base
  scope :recent, limit(10).order("created_at ASC")
end
class PostsController < ApplicationController
  def index
    @recent_posts = Posts.recent.where(:author => params[:author])
  end
end

You can also create several scopes that you can chain together. You might be familiar with the basic shape of this feature from Rails 2, but it’s not souped up to use the same API as you use in normal queries.

Check out a more advanced example, using a dynamic scope.

class Post < ActiveRecord::Base
  scope :recent,   limit(10).order("created_at ASC")
  scope :author,   proc {|author| where(:author => author) }
  scope :category, proc {|category| where(:category => category) }
end
@recent_posts = Post.recent.author("dhh").category("rails3")

You can also use class methods instead of the scope method.

class Post < ActiveRecord::Base
  scope :recent,   limit(10).order("created_at ASC")

  def self.author(author)
    where(:author => author)
  end

  def self.category(category)
    where(:category => category)
  end
end

Because the scoping API is identical to the query API, you should find it very simple to move commonly used queries into class methods or scopes in your models.

ActionMailer

ActionMailer has gotten a long-overdue overhaul. First, ActionMailer now depends on the Mail gem, which was written from the ground up to be a rock-solid mail library. One of the tests it runs through parses every email in the Trec-Corpus and Enron databases, over 2.4Gb of email, all without crashing.

Second, the ActionMailer API has gotten an overhaul, making it feel more like working with ActionController.

The mail method

Inside ActionMailer, the mail method is the new workhorse.

mail(:to => "matz@rubyist.net")

This will look for templates with a name matching the current action, as in ActionController, and render them into a single multipart email.

You can also specify specific templates to render.

mail(:to => "matz@rubyist.net") do |format|
  format.text
  format.html
end

This should look familiar: just like in ActionController, you are using a format block to specify which templates to use. In ActionController, the format block specifies available templates, of which Rails chooses only one to render. In ActionMailer, the block specifies available templates, and Rails combines them all into a single multipart document.

You can also do more custom things inside the individual formats, as in ActionController.

mail(:to => "matz@rubyist.net", :from => "wycats@gmail.com") do |format|
  format.text { render :text => "Hello Matz!" }
  format.html { render :text => "<p>Hello Matz!</p>" }
end

Also in Rails 3, you should use instance variables to make information in the controller available in the view, just like in ActionController.

Defaults

If you send more than one mail, you’ll probably find that you have a lot of common defaults. For instance, you may always send your email from the same address, set a certain reply address, or BCC all messages to a specific address.

For those cases, you can use the defaults method on ActionMailer subclasses to specify defaults for any option you can pass to the mail method.

class AccountNotifier < ActionMailer::Base
  defaults :from => "wycats@gmail.com"

  def welcome(user)
    # @user will be available in templates
    @user = user
    mail(:to => user.email)
  end
end

Gem Management

Rails 3 ships with support for bundler, which manages your application’s dependencies through its entire life, across many machines, systematically and repeatably.

This takes the guesswork out of getting a new developer up and running with an existing Rails app. It also makes the process of ensuring that your server has all the right gems available a standardized affair.

Finally, it makes coming back to a project after a few months trivial, remembering the exact versions of each gem and the exact revision of each git repository you were using the last time you were actively developing the project.

Using Bundler

You don’t need to do anything to take advantage of the basic features of bundler. A newly generated Rails 3 application already comes with a Gemfile, listing out the gems that Rails uses, and you can start up the Rails server or console without any further effort.

If you want to add a dependency, simply add it to the Gemfile.

gem "nokogiri"

To install the gem and its dependencies once you’ve updated your Gemfile, run bundle install. You may also install dependencies manually to your system using the normal gem install command.

After cloning the application on a new system, a single command (bundle install) will make your application ready to go.

You can learn more about using Bundler with Rails 3 at the official Bundler website

Choices, Choices Everywhere

With Rails 3, we have opened up the hooks that we use for our own defaults to alternatives like DataMapper and RSpec.

This means that these libraries can now fully replace ActiveRecord and Test::Unit for normal Rails operations. And this goes far further than simply allowing you to use DataMapper or RSpec as Ruby libraries. Rails 3 implements our own ActiveRecord and Test::Unit support using a fully-public plugin API—the same API that other libraries can use to implement support for Rails.

Of course, you should choose not to use Rails defaults only after reflection, and new users should almost certainly stick with Rails defaults until they get their moorings. That said, as Rails has matured, people have learned to swap out the default template engine, persistence framework, and JavaScript library.

Rails 3 makes those choices seamless and dramatically increases the interoperability of these alternatives with the core framework.

Generators

Rails 3 allows plugins to replace the built-in generators for granular parts of the generation process.

invoke  active_record
  create    db/migrate/20100404095517_create_users.rb
  create    app/models/user.rb
  invoke    test_unit
  create      test/unit/user_test.rb
  create      test/fixtures/users.yml
   route  resources :users
  invoke  scaffold_controller
  create    app/controllers/users_controller.rb
  invoke    erb
  create      app/views/users
  create      app/views/users/index.html.erb
  create      app/views/users/edit.html.erb
  create      app/views/users/show.html.erb
  create      app/views/users/new.html.erb
  create      app/views/users/_form.html.erb
  create      app/views/layouts/users.html.erb
  invoke    test_unit
  create      test/functional/users_controller_test.rb
  invoke    helper
  create      app/helpers/users_helper.rb
  invoke      test_unit
  create        test/unit/helpers/users_helper_test.rb
  invoke  stylesheets
  create    public/stylesheets/scaffold.css

Several times, the generator says it’s “invoking” a particular subsystem. Each of these systems provides an opportunity for plugins to replace provide alternative generators. For instance, DataMapper provides its own generators, so when you run the above command with dm-rails installed in your application, you would see:

invoke  data_mapper
  create    app/models/user.rb
  invoke    test_unit
  create      test/unit/user_test.rb
  create      test/fixtures/users.yml
  ...

The dm-rails plugin has configured that scaffold generator to generate a DataMapper User model, and has also given Rails the information it needs to properly retrieve and persist the DataMapper objects in the controller:

class User
    include DataMapper::Resource

    property :id, Serial
  end
def show
  @user = User.get(params[:id])

  respond_to do |format|
    format.html # show.html.erb
    format.xml  { render :xml => @user }
  end
end

Other plugins, such as Haml or RSpec, could use the same process to replace (or extend) the default Rails generators with customizations for their plugin.

Persisting Without ActiveRecord

Rails 2.0 fully embraced RESTful resources as the conventional way to build Rails applications, providing a number of helpers to take the tedium out of naming.

redirect_to @post
  url_for @post

  head 200, :location => @post
<%= form_for @post do |f| %>
  ...
<% end %>

<%= div_for @post do %>
  ...
<% end %>

<%= link_to @post.name, @post %>

<%= render @post %>
<%= render @posts %>

In Rails 3, all of these examples work exactly the same way with DataMapper, or any other persistence framework that implements the ActiveModel API.

This means that choosing to use something other than ActiveRecord won’t mean tossing off a lot of what made Rails so productive in the first place.

Persistence engines can also hook into the controller’s output logs, providing the same kind of information about the time spent in the “model” layer as ActiveRecord, in the same format.

Other Choices

Rails 3 also makes a number of other choices more pleasant for Rails users.

The JavaScript helpers no longer emit raw Prototype code. Instead they create semantic markup which libraries like Prototype, jQuery, Dojo, or MooTools can wire up correctly. Rails itself ships with a rails.js based on Prototype, and the core team actively maintains a rails.js for jQuery. We expect user of other libraries to release their own versions.

The JSON and XML serializers now support alternative backends correctly, such as the json or yajl libraries for JSON and nokogiri, libxml, or rexml for XML.

Rails 3 continues the long tradition of excellent support for alternative template engines, and the Haml engine already boasts full support for Rails 3. Template engines now also have the ability to provide alternative default templates for scaffolding.

Plugins like New Relic will be happy to learn that Rails 3 ships with built-in instrumentation, eliminating the need for special-purpose hacks to determine time spent in SQL, rendering templates or other parts of the Rails request cycle.

Rails uses generic names for each of its instrumented events (such as sql), so alternatives, such as DataMapper, can emit the same events and seamlessly work with products like New Relic that make use of the new instrumentation system.

Rails 3 has many more improvements to the overall plugin architecture, so keep an eye out for new and innovative plugins that can push the boundaries of what Rails can do.

Learn More

I’ve just scratched the surface of each of these topics. In the weeks to come, a major contributor to each of these features will host the topic, providing a more extensive look at how the feature works, as well as instructions on how to start using the feature right now.