Extending Rails 3 with Railties »

Created at: 07.10.2010 14:49, source: Engine Yard Blog, tagged: Uncategorized Rails 3 Railties

This post comes from guest community contributor and Engine Yard alumni Andre Arko. Andre has been building web applications with Ruby and Rails for five years, and is a member of the Bundler core team. He works for Plex, tweets as @indirect, and blogs at andre.arko.net.

Gem plugins in Rails 3.0

Rails 3.0 is finally released, and with it comes a fantastic new way to extend Rails: Railties. Railties are the basis of the core components of Rails 3, and are the result of months of careful refactoring by Carlhuda. It is easier to extend and expand Rails than it has ever been before, without an alias_method_chain in sight. Unfortunately, while the system for extending and expanding Rails has been completely overhauled, the documentation hasn't been updated yet. The Rails Plugins Guide only covers writing plugins in the old Rails 2 style. Ilya Grigorik wrote a Railtie & Creating Plugins blog post, but just scratched the surface of what is possible with a Railtie plugin. This post covers writing Railtie plugins, hooking into the Rails initialization process, packaging Railtie plugins as gems, and using gem plugins in a Rails 3 application.

Creating Railtie plugins

Creating a Railtie is easy. Just create a class that inherits from ::Rails::Railtie. Every subclass of Railtie is used to initialize your Rails application. Since ActionController, ActionView, and the other Rails components are also Railties, your plugin can function as a first-class member of the Rails application. It will have access to the same methods and context that are used by the official Rails components. Here is a sample minimal Railtie that will be loaded when your Rails application boots.
require 'rails'
class MyCoolRailtie < Rails::Railtie
  # railtie code goes here
end
The Railtie documentation lists all of the methods that are available inside each Railtie class, but doesn't really go into depth about what you can use Railties to do. Here are some example Railties explaining how to use the Railtie methods (in alphabetical order) to customize and extend Rails.

console

The console method allows your Railtie to add code that will be run when a Rails console is started.
console do
  Foo.console_mode!
end

generators

Rails will require any generators defined in lib/generators/*.rb automatically. If you ship Rails::Generators with your Railtie in some other directory, you can require them using this method.
generators do
  require 'path/to/generator'
end

rake_tasks

If you ship rake tasks for apps with your Railtie, load them using this method.
rake_tasks do
  require 'path/to/railtie.tasks'
end

initializer

The initializer method provides Railties with a lot of power. They create initializers that will be run during the Rails boot process, like the files put into config/initializers in the app directory. The initializer method takes two options, :after or :before, if there are specific initializers that you want to run before or after yours.
initializer "my_cool_railtie.boot_foo" do
  Foo.boot(Bar)
end

initializer "my_cool_railtie.boot_bar",
  :before => "my_cool_railtie.boot_foo" do
    Bar.boot!
end

Rails configuration hooks

The biggest extension hook that Railties provide is somewhat unassuming: the config method. That method returns the instance of Railtie::Configuration that belongs to the application being booted. This opens up all sorts of interesting possibilities, since the config object is the same one that is made available inside a Rails application's environment.rb file. Here are some annotated examples of using config to change how a Rails application is initialized and configured.

after_initialize

This method takes a block that will be run after Rails is is completely initialized, and all of the application's initializers have run.

app_middleware

This method exposes the MiddlewareStack that will be used to handle requests to your Rails application. You can use any of the methods defined on MiddlewareStack, including use and swap, to manage the Rails application's Rack middlewares. For example, if your Railtie included the Rack middleware MyRailtie::Middleware, you could add it to the Rails application middleware stack like this:
config.middlewares.use MyRailtie::Middleware

before_configuration

Code passed in a block to this method will be run immediately before the application configuration block inside application.rb is run. This is usually the best place to set default options that users of your plugin should be able to override themselves, as in the jquery-rails example below.

before_eager_load

The block passed to before_eager_load will be run before Rails requires the application’s classes. Eager load is never run in development mode. However, if you need to run code after Rails loads, but before any application code loads, this is the place to put it.
config.before_eager_load do
  SomeClass.set_important_value = "RoboCop"
end

before_initialize

This method takes a block to be run before the Rails initialization process happens -- this is basically equivalent to creating an initializer, and setting it to run before the first initializer the app has.

generators

This object holds the configuration for the generators that are invoked when you run the rails generate command.
config.generators do |g|
  g.orm             :datamapper, :migration => true
  g.template_engine :haml
  g.test_framework  :rspec
end
You can also use it to disable colorized logging in the console.
config.generators.colorize_logging = false

to_prepare

Last, but quite importantly, to_prepare allows you to do one-time setup. The block you pass to this method will run for every request in development mode, but only once in production. Use it when you need to set something up once before the app starts serving requests.

Examples

At this point, you're probably thinking "why would I actually want to do any of that stuff?". So, here are a few select examples of Railtie plugins packaged as gems.

rspec-rails

The rspec-rails plugin ships with a set of rake tasks and generators that integrate the RSpec gem with Rails.
module RSpec
  module Rails
    class Railtie < ::Rails::Railtie
      config.generators.integration_tool :rspec
      config.generators.test_framework   :rspec

      rake_tasks do
        load "rspec/rails/tasks/rspec.rake"
      end
    end
  end
end
This Railtie just does three things: First, it sets the generators that will be used for integration tests via the integration_tool method. Next, it sets the generators that will be used to generate model, controller, and view tests (via the test_framework method). Last, it loads the RSpec rake tasks to run RSpec tests instead of test-unit tests.

jquery-rails

The jquery-rails plugin ships with a generator that downloads and installs jQuery, the jquery-ujs script that enables Rails helpers with jQuery, and optionally installs jQueryUI as well.
module Jquery
  module Rails
    class Railtie < ::Rails::Railtie
      config.before_configuration do
        if ::Rails.root.join('public/javascripts/jquery-ui.min.js').exist?
          config.action_view.javascript_expansions[:defaults] =
            %w(jquery.min jquery-ui.min rails)
        else
          config.action_view.javascript_expansions[:defaults] =
            %w(jquery.min rails)
        end
      end
    end
  end
end
This Railtie only sets one setting, but checks for the jQueryUI library to determine the value to set. By using the config.before_configuration hook, it runs right before the application.rb config block runs. That means it has access to the Rails.root, which is needed to check for jQueryUI, and it means that users can still override javascript_expansion[:defaults] in their application.rb if they want something different than the new defaults that the plugin provides.

haml-rails

The haml-rails gem provides generators for views written in Haml instead of the default generated views that are written in ERB.
module Haml
  module Rails
    class Railtie < ::Rails::Railtie
      config.generators.template_engine :haml

      config.before_initialize do
        Haml.init_rails(binding)
        Haml::Template.options[:format] = :html5
      end
    end
  end
end
This Railtie simply changes the template engine that Rails invokes when you run rails generate, and then initializes Haml for Rails, and sets the Haml output format to HTML5.

Packaging up gem plugins

Railtie plugins are easy to turn into gem plugins for Rails. This makes them easy to distribute, manage, and upgrade. The first thing you need is a gem. If you don't have a gem yet, you can create a new gem easily using Bundler. Just run bundle gem my_new_gem and Bundler will generate a skeleton gem and gemspec that follow gem best practices. Once you have a gem, just make sure that your Railtie subclass is defined when lib/my_new_gem.rb is loaded. You can define the Railtie in a separate file and require that file, or define it directly. Last, add a dependency on the Rails gem (~>3.0) to your gemspec. If your gem is also a plain Ruby library, and you don't want to depend on the Rails gem, then you can put your Railtie in a separate file, and conditionally require that file inside your main library file.
# lib/my_new_gem/my_cool_railtie.rb
module MyNewGem
  class MyCoolRailtie < ::Rails::Railtie
    # Railtie code here
  end
end
# lib/my_new_gem.rb
require 'my_new_gem/my_cool_railtie.rb' if defined?(Rails)
This ensures that your gem can be loaded (without the Railtie) if it is loaded outside the context of a Rails application. Now that your gem has a Railtie, you can build it and release it to Gemcutter. Once your gem is on Gemcutter, using it with Rails 3 applications is extremely easy -- just add the gem to your Gemfile. Bundler will download and install your gem when you run bundle install, Rails will load it, and the Rails::Railtie class takes care of the rest!


more »

Rails Has Great Documentation »

Created at: 28.08.2010 21:08, source: Riding Rails - home, tagged: Activism Documentation Documentation Rails 3 Rails 3

To this day I still hear people complain that Rails has poor documentation. From where I’m sitting this seems far from the truth. Let me lay out the evidence piece by piece:

RailsTutorial.org

To learn Rails from scratch Michael Hartl recently finished his book Ruby on Rails Tutorial: Learn Rails by Example. The book teaches Rails 3 from the ground up and it’s available for FREE online. If you’d rather have a PDF or a book you can grab that as well (and he’s even working on some screencasts).

The source for the finalized book will be pushed to GitHub and released under a Creative Commons License shortly after Rails 3 is done. If you’d like to help translate the book to your language of choice, feel free to contact Michael and he’ll get in touch when it’s time to make it happen.

Rails Guides

If you’re not a Rails newbie don’t forget about the Rails Guides, which have been updated for Rails 3.

Rails API Docs

There are two main websites I use to do API lookups. The first is Rails Searchable API Doc, which has online and offline searchable documentation. The second is APIdock which is online only, but has the ability to comment and easily compare different versions of documentation.

Rails 3 Free Screencasts

If you’re more of a visual learner (like me) then there are plenty of free screencasts to teach you about Rails 3. About 2 months ago I produced the Rails 3 Screencasts, which will get you started.

Ryan Bates has also produced an incredible amount of Rails 3 screencasts over on Railscasts.com. Ryan has been producing Railscasts for over 3 1/2 years, isn’t that crazy?

There’s also a few good free screencasts over on Teach me to Code by Charles Max Wood.

Keeping on the Edge

If you find yourself wondering how to keep up with all of the newest features / libraries for Rails 3, both the Ruby5 Podcast and the Ruby Show are going strong. Don’t listen to audio? It doesn’t matter, just subscribe to the Ruby5 RSS feed and get links with descriptions to all the newest libraries, tutorials, and more. You might also want to checkout Peter Cooper’s new Ruby Weekly, a Ruby email newsletter.

Need to upgrade a big app to Rails 3?

Jeremy McAnally’s Rails 3 Upgrade Handbook PDF is just $12. There’s also a few paid screencasts for the upgrade over on Thinkcode.tv and BDDCasts.

Need a Book?

There’s a bunch of books that will be coming out after the release, most of which you can start reading now. The Rails 3 Way by Obie Fernandez, Rails 3 In Action by Ryan Bigg and Yehuda Katz, Beginning Rails by Cloves Carneiro Jr and Rida Al Barazi, and of course the Agile Web Development with Rails:fourth edition by Sam Ruby, Dave Thomas, and David Heinemeier Hansson.

In conclusion

No more complaining about lack of good documentation! Seriously. If you want even more Rails 3 content, check out the blog post by Kevin Faustino on 34 Ruby on Rails 3 resources to get you started.


more »

Announcing Rails Dispatch! »

Created at: 07.04.2010 20:00, source: Engine Yard Blog, tagged: News rails Rails 3

At Engine Yard, we’re always looking for new and fun ways to give back and help grow the community. Most recently, we organized the Ruby Summer of Code, designed to help foster student participation in open source development. Today, as the next project on our long list, we’re launching RailsDispatch.com.

null

Rails Dispatch will provide timely releases of up-to-date educational content, in the form of blog posts, tutorial videos, screencasts and more. We’ll be working with Engine Yard and community developers to put together the resources, and aiming for weekly content pushes from now until RailsConf. Until then the bulk of the content, in the spirit of the times, will focus on Rails 3, and the changes and new features it brings. As the content library grows, we’ll also be releasing new, interactive elements and community resources, so you definitely want to hop off the RSS feed and on to the site every little bit to check out what’s new.

Check out today’s release for the technical details on how Rails 3 makes life better, and an update to the famous Rails ‘blog in 15 minutes’ screencast.

As always, leave your feedback here, and check out Rails Dispatch!


more »

The Lowdown on Routes in Rails 3 »

Created at: 30.03.2010 21:00, source: Engine Yard Blog, tagged: Technology Rails 3 routes

Today’s blog post is by a guest community contributor, Rizwan Reza. Rizwan is an active member of the Rails community, and as of late, has been working to clean up the overgrown Lighthouse queue.

Stop! I’d like to tell you something important, but it may be a bit shocking, so you should probably have a seat. Here goes: everything you knew about working with routes in Rails 2… is history! With Rails 3, you’ve got to roll up your sleeves, unlearn what you learned, and route the new way around. And this time, it’s faster, cleaner and a lot more Ruby-like.

In this post, we’ll walk through the underpinnings of Routes in Rails 3. They’ve been rewritten—for good reason—and after we get through the explanation, I’m confident you’ll agree.

Let’s start by looking at some code; here’s the new DSL, in its full glory:

resources :products do
  resource :category
 
  member do
    post :short
  end
 
  collection do
    get :long
  end
end
 
match "/posts/github" => redirect("http://github.com/rails.atom")

Now check out the old way of doing it:

map.resources :products, :member => {:short => :post}, :collection => {:long => :get} do |products|
  products.resource :category
end

As you can see, the example from Rails 3 is much cleaner and more Rubyish. So let’s jump right in and walk through a quick overview of how you’d define different types of routes in Rails 3.

Default Route

The default route in Rails 3, match '/:controller(/:action(/:id))', is much more explicit, as the parenthesis denote optional parameters.

Regular Routes

Rather than defining different keys for controller and action, you just have catalog#view, which is pretty awesome.

match 'products/:id', :to => 'catalog#view'

In Rails 2, you would’ve done:

map.connect 'products/:id', :controller => 'products', :action => 'view'

Named Routes

Named Routes generate helpers like posts_url and posts_path, rather than manually defining the hash to action and controller in helper methods like link_to:

match 'logout', :to => 'sessions#destroy', :as => "logout"

The key :as specifies a name to generate helpers. In Rails 2, you would have done:

map.logout '/logout', :controller =&gt; 'sessions', :action =&gt; 'destroy'

Empty Route

The root of the web site is the empty route. Whereas Rails 2 added a nice shortcut to it, Rails 3 simplifies things even further:

# Rails 3
root :to => 'welcome#show'
 
# Rails 2
map.root :controller => "welcome", :action => 'show'

Shorthands

The revamped routes in Rails 3 sport some nice shortcuts to commonly used routes. There are two types of shorthands. First, the :to shorthand allows you to skip the :to key and directly designate the route to the matcher:

match "/account" => "account#index"
match "/info" => "projects#info", :as => "info"

Second, the match shorhand allows you to define a path and controller with its action at the same time:

match "account/overview"
 
# identical to
 
match "account/overview", :to => "account#overview"

Verb Routes

While you can limit a route to an HTTP request through :via, it’s a nice added convenience to have Verb routes. Adding sugar on top, you can even use shorthands with them:

get "account/overview"
 
# identical to
 
match "account/overview", :to => "account#overview", :via => "get"

Keys

The match method (as well as the verb shorthands) take a number of optional keys.

:as

The :as key names the route. You can then use named route helpers wherever url_for is available (such as controllers, tests, and mailers). Resource routes (using the resources helper) automatically create named routes, as in Rails 2.3.

match "account/overview/:id", :as => "overview"
 
# in your controller
 
overview_path(12) #=> "/account/overview/12"

:via

Allows you to specify a set of verbs, so only those HTTP requests are accepted for a route.

match "account/setup", :via => [:get, :post]

Rack

Rack is a sweet interface to web servers that provides unified API to Ruby frameworks. Most if not all Ruby frameworks are built on top of Rack these days. The recent built-in support for Rack means your application is not bound to being Rails specific. You can have parts of your application handled by any Rack supported framework, be it Sinatra, Cramp or something else. You can skip the Rails stack altogether and pass on the request to a Rack app.

Here’s an example of a Sinatra app:

class HomeApp < Sinatra::Base
  get "/" do
    "Hello World!"
  end
end
 
Rizwan::Application.routes do
  match "/home", :to => HomeApp
end

And here’s an example of a Rack app:

match "/foo", :to => proc {|env| [200, {}, ["Hello world"]] }
 
match 'rocketeer.js' => ::TestRoutingMapper::RocketeerApp
 
RocketeerApp = lambda { |env|
  [200, {"Content-Type" => "text/html"}, ["javascripts"]]
}

Resourceful Routes

Since Rails 1.2, resourceful routes have been the preferred way to use the router. Recognizing this fact, the Rails core team has added some nice improvements. Take a look at this typical RESTful route in Rails 3:

resources :products

This would generate all the neat helpers we have come to love and would also route the URLs accordingly. Just like before, you can also add multiple resources in a single line:

resources :products, :posts, :categories

More RESTful Actions

As you know, you’re not limited to the seven actions that RESTful architecture provides, but can also define more actions in a resource. Having said that, you might want to keep an eye open if you’re defining lots of actions in a single resource, as they can be turned into separate resources.

We can add RESTful actions to this resource in a couple of ways. Here’s a few collection RESTful actions inside a block:

resources :products do
  collection do
    get  :sold
    post :on_offer
  end
end

And take a look at this inline member RESTful action:

resources :products do
  get :sold, :on => :member
end

Not only that, but you can also redefine to extend the scope of the default seven RESTful actions:

resources :session do
  collection do
    get :create
  end
end

create actions, which usually only accepts POST requests, can now accept GET requests as well:

resource :session do
  get :create
end

Nested Resources

In Rails 2, nested resources were defined by a block or by using a :has_many or :has_one key. Both of these have been superseded by a block, giving them a more Rubyish interface to defining associated resources.

Here’s a route for a project that has many tasks and people:

resources :projects do
  resources :tasks, :people
end

Namespaced Resources

These are especially useful when defining resources in a folder; it doesn’t get much cleaner than this:

namespace :admin do
  resources :projects
end

Renaming Resources

You can also rename resources through the :as key. This code uses :as in resourceful routes to change the products path to devices:

namespace :forum do
  resources :products, :as => 'devices' do
    resources :questions
  end
end

Restricting Resources

Resources can be restricted to only specified actions.

resources :posts, :except => [:index]
 
resources :posts, :only => [:new, :create]

Altering Path Names

You can define a different path name for a particular REST action. This helps you customize your RESTful routes. This code will route /projects/1/cambiar to the edit action.

resources :projects, :path_names => { :edit => 'cambiar' }

The Redirect Method

The newly added redirect method in Rails 3 provides a level of convenience not present before. For example, it can redirect to any path given and eventually, can also pass on to a full-blown URI, something previously accomplished by Rails plugins like redirect_routing.

Moreover, the redirect method also introduces generic actions. Unique to Rails 3, generic actions are a simple way to provide the same action to complex paths, depending on what’s passed to redirect.

This code will redirect /foo/1 to /bar/1s:

match "/foo/:id", :to => redirect("/bar/%{id}s")

This code will redirect /account/proc/john to /johns.

match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" }

Note that redirect cannot be used in a block as opposed to other constraints and scopes.

The Constraints Method

Constraints allow you to specify requirements for path segments in routes. Besides that, they also allow you to use a handful of methods to verify whether or not something matches a given criteria. Like a route that checks if the request is AJAX or not, for example.

In this code, we’re using a regular expression, and the route has been restricted to only allow one digit IDs to pass through:

match "/posts/show/:id", :to => "posts#index", :constraints => {:id => /\d/}

Scope

When the scope method is passed along a symbol, it assumes the symbol is a controller. When the argument is a string, the scope method prepends that string to the beginning of the path.

Scope can also have path_segments, which can be constrained, giving us greater flexibility in routes.

controller :articles do
  scope '/articles', :name_prefix => 'article' do
    scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do
      match '/:id', :to => :with_id
    end
  end
end
 
scope :posts, :name_prefix => "posts" do
  match "/:action", :as => "action"
end
 
scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do # See constraint here
  resources :rooms
end

As you can see, when scope is given a string as its argument, it prepends to the path, something that was accomplished through path_prefix in Rails 2. name_prefix is essentially the same as before.

Optional Segments

Unlike all the previous versions of Rails, path segments can now be optional in routes. Optional segments don’t necessarily have to be path segments, which are passed as parameters to the action. The default route is a good example of optional segments in use. Here, both /posts/new and /posts will be redirected to the create action in posts controller, but /posts/edit will not work:

match 'posts(/new)', :to => 'posts#create'

This is an optional path scope that allows to have a prepended path before a resource:

scope '(:locale)', :locale => /en|pl/ do
  resources :descriptions
end

Pervasive Blocks

As evident from the examples, routes in Rails 3 exhibit pervasive blocks for almost all the methods you’d normally want to pass a block to, helping you achieve DRY in routes.rb.

controller :posts do
  match 'export', :to => :new, :as => :export_request
  match '/:action'
end

Right now, most Rails developers wouldn’t use all of these methods, but when the need arises—when you need to define more complex routes—you might appreciate having the above information handy. You don’t need to use a plugin or a hack when you know it’s built right in. With Rails 3, Routes rock at a whole new level.

Questions and comments welcome!


more »

Rails and Merb Merge: Rack (Part 6 of 6) »

Created at: 16.03.2010 19:00, source: Engine Yard Blog, tagged: Technology rails Rails 3

In this final segment, I’ll talk about how Rails 3 takes advantage of the Rack standard, leveraging it in ways that make sharing code between Ruby web applications easier. There are a ton of good blog posts and presentations on Rack itself, so I won’t go into too much detail beyond a brief summary.

What is Rack?

Rack defines a standard for interaction between web servers and Ruby web applications. It’s based on the CGI standard, but stripped of CGI’s global characteristics (such as its use of environment variables and standard output).

Servers send requests to Rack applications by calling their #call method with an environment Hash containing information as a number of standardized keys. For instance, it provides the requested path in the PATH_INFO variable. Applications send responses back to the server as an Array of three elements: the status code, a Hash of the headers, and the body. Applications may return any object that responds to #each as the body, and each iteration must return a String object.

Here are a couple of simple Rack applications:

# A Proc responds to #call
proc do |env|
  [200, {"Content-Type" => "text/html"}, ["Hello world"]]
end
 
# Other objects that respond to #call work too
class MyApp
  def self.call(env)
    [
      200,
      {"Content-Type" => "text/html"},
      ["Hello world"]
    ]
  end
end

Rails 2.3

In Rails 2.3, Rails became a fully valid Rack application, leveraging Rack in a number of places:

  • ActionController::Dispatcher.new is the top-level Rack application
  • The parameter parser is implemented as middleware
  • The router is a Rack application that dispatches to controllers
  • Each controller is a Rack application

This was a modest step toward leveraging Rack, and made Rails applications work anywhere Rack worked. However, the internals of some of these pieces were messy, and an overhaul would be needed to take full advantage of the Rack ecosystem and the power of Rack’s architecture.

Enter Rails 3: The Application

First, rather than having a global Dispatcher object pointing at a global router, Rails 3 introduces the idea of the Rails Application. The Application, an object holding the router and other configuration, is itself a Rack application.

The following is a valid config.ru.

# Set up load paths as desired
 
require "action_controller/railtie"
 
class FooController < ActionController::Base
  def bar
    self.response_body = "HELLO"
  end
end
 
class MyApp < Rails::Application
   config.session_store :disabled
 
   routes.draw do
     match "/foo" => "foo#bar"
  end
end
 
run MyApp

As you can see, a Rails application is a valid Rack application. The above config.ru file will run with rackup, Passenger, Glassfish, or any other Rack server.

ActionDispatch and Rack Middleware

Rails 3 introduces a new framework called ActionDispatch, a library extending the features of the Rack library for Rails’ use. This library can also be used standalone in any Rack application.

The new ActionDispatch::Request and ActionDispatch::Response objects now inherit from Rack::Request and Rack::Response. Integration tests now use Rack::Test and can now be used to test any Rack application.

Rails now includes a number of new general purpose middlewares:

  • A middleware to run preparation callbacks, which Rails runs once in production mode, or once per request in development mode
  • A middleware to set up and write cookies
  • A middleware to clean up flash notices
  • A middleware to handle HEAD requests
  • A middleware to handle IP spoof checking
  • A middleware to serve static assets
  • Middlewares to handle rescuing exceptions down the stack
  • Middlewares for various session stores

Rails also uses a number of middlewares that ship with the Rack library:

  • A middleware to synchronize non-threadsafe requests
  • A middleware to measure and set the runtime of requests
  • A middleware to implement Send-File semantics in various web servers
  • A middleware to handle PUT and DELETE requests coming in via POST requests

The middleware stack object, available as config.middleware, supports reordering these middlewares, or inserting new middleware relative to default ones.

One nice thing about implementing the functionality as middleware is that it forces them to be simple, standalone objects, and understanding, patching or even replacing them becomes much easier than trying to modify a monolithic controller or request object.

The Router

The Rails 3 router was written from the ground up by Josh Peek as the Rack::Mount project, which provides a general-purpose Rack router. Rack::Mount recognizes URLs and dispatches them to any Rack application. As a result, the router now natively supports matching any URL to any arbitrary Rack application–even a Sintra application.

Rails.application.routes.draw do
  match "/blog" => Rack::Blog # Someone should write it!
end

The router is itself a Rack application, so it’s possible to mount a router inside the main router. Carl Lerche’s Astaire project uses this to provide the Sinatra routing DSL inside a Rails controller. The following is a valid config.ru:

$:.unshift File.expand_path('../../lib', __FILE__)
 
require 'rubygems'
require 'astaire'
 
class OmgController < ActionController::Base
  include Astaire::DSL
  append_view_paths File.expand_path('../views', __FILE__)
 
  get "/goodbye" do
    render :text => "goodbye"
  end
 
  get "/hello" do
    render "hello"
  end
end
 
run OmgController

Astaire uses ActionDispatch::Routing::RouteSet internally, a Rails wrapper around Rack::Mount.

Actions

Because the router dispatches directly to Rack applications, each controller action is its own mini-application. To get a valid Rack application for a controller’s action, use the ArticlesController.action(:index) syntax.

The Rails router uses the syntax match "/articles" => "articles#index" to match a URL to a controller/action pair. That syntax is identical to match "/articles" => ArticlesController.action(:index).

Because actions are their own Rack applications, you can add middleware between the router and controller actions:

class ArticlesController < ApplicationController
  use MyMiddleware
end

This will insert the middleware between the router and the dispatching of an action. The following is a valid config.ru:

class ArticlesController < ActionController::Base
  append_view_path "/path/to/views"
 
  def index
    render
  end
 
  # implicit actions, such as "articles/show" would work here
end
 
run ArticlesController.action(:index)

This endpoint would behave exactly like a normal action, including callbacks and rendering. In fact, it’s roughly identical to what happens internally inside the router.

You can also call into an action using this syntax:

env = Rack::MockRequest.env_for("/articles")
status, headers, body = ArticlesController.action(:index).call(env)
 
# Create a response object, if you want one
response = ActionDispatch::Response.new(status, headers, body)

You could also use Rack::Test:

class TestArticles < Test::Unit::TestCase
  include Rack::Test::Methods
 
  def app
    ArticlesController.action(:index)
  end
 
  def test_get
    get "/articles"
    assert_equal "Hello world", last_response.body
  end
end
 
# This would include the application's middleware,
# which you probably want as it includes sessions
# and cookies
class TestApplication < Test::Unit::TestCase
  include Rack::Test::Methods
 
  def app
    MyApplication
  end
 
  def test_get
    get "/articles"
    assert_equal "Hello world", last_response.body
  end
end

You would probably use Rails integration tests, which wrap Rack::Test with some additional conveniences, in practice. It’s nice to know that there’s very little magic going on, and that all of the individual pieces of Rails are just standard Rack.

Conclusion

While Rails 2 had started down the path of supporting Rack, Rails 3 took a few dozen steps forward, deeply integrating the framework with the Rack standard. In addition to improving the general interaction with servers, Rails 3 takes much more advantage of the Rack library, exposes its own functionality as reusable Rack components, and wires up the stack using the Rack architecture. This means that Rails itself integrates well with tools that work with Rack applications, and can also incorporate other applications or middleware following the Rack standard.

Stay tuned for more Rails 3 content to come—the series may be over, but there’s plenty left to talk about.


more »