Ruby on Rails 3 Routing

Ruby on Rails Route (routes.rb)

Route configuration is stored in

/config/routes.rb
Actions to routes.rb Changes to routes.rb
rails generate scaffold Something "resources :somethings" is added to routes.rb automatically
rails generate controller Something action get "controller/action" is added to routes.rb automatically
Add URL pattern manually match "/users/:id" => "users#show"

If more than one rule match an URL pattern, the rule on the top of the file take precedence

Ruby on Rails Route for Multiple Resources

Multiple resources: May contain multiple rows of data. An ID is needed to identify individual resource

http://host/billings/34

Command to generate a scaffold for multiple resources

rails generate scaffold Billing

Changes to config/routes.rb

resources :billings

Automatically mapped:

HTTP method URL action Description
GET /billings index List all billings
GET /billings/new new Display a HTML form for creating a new billing
POST /billings create Create a new billing
GET /billings/:id show Display a bill with the specific id
GET /billings/:id/edit edit Display a HTML form for editing the bill with the specific id
PUT /billings/:id update Update a bill
DELETE /billings/:id destroy Delete the bill with the specify id

Command to find all route information in the same order in routes.rb

rake routes

Route output for accounts mapping a HTTP METHOD/URL to an action of a controller

Helper       METHOD URL     Map To Controller/Action                  Description 
    billings GET    /billings(.:format)                               {:controller=>"billings", :action=>"index"}
             POST   /billings(.:format)                               {:controller=>"billings", :action=>"create"}
 new_billing GET    /billings/new(.:format)                           {:controller=>"billings", :action=>"new"}
edit_billing GET    /billings/:id/edit(.:format)                      {:controller=>"billings", :action=>"edit"}
     billing GET    /billings/:id(.:format)                           {:controller=>"billings", :action=>"show"}
             PUT    /billings/:id(.:format)                           {:controller=>"billings", :action=>"update"}
             DELETE /billings/:id(.:format)                           {:controller=>"billings", :action=>"destroy"}

id can be accessed in the controller by

params[:id]

Rails URL link helper methods

Rails automatic generates helper methods to create url links

When append "_path" to the first column above in the "rake routes", it generates the following URL generation methods:

Method URL generated Description Action
billings_path /billings List all bills index
billing_path(@billing) /billings/:id Detail a bill new
edit_billing_path(@billing) /billings/:id/edit Edit page for billing edit
new_billing_path /billings/new Create page for billing show
<%= link_to 'Listing', billings_path %>
<%= link_to 'Edit', edit_billing_path(@billing) %>
<%= link_to 'New Billing', new_billing_path %>%>
<%= link_to 'Detail', billing_path(@billing) %>
<%= link_to 'Detail', @billing %>

For associate object

<%= link_to 'Billing Comment', [@billing, @comment] %>
/billings/33/comments/3

Each of the helpers has another _url (like billings_url) which returns the host, port and path

www.host.com/billings/22

Adding Query String(s) to link_to

Adding query string

<%= link_to 'Show', billing_path(@bill, :qstring=>"1") %>
/bills/234?qstring=1

The following will NOT work since the last parameter will be treated as HTML option instead

<%= link_to 'Show', @bill, :qstring=>"1" %>

Adding new Actions in a controller

To add a new Rails action "approve" for a resource

http://hostname/billings/1/approve
resources :billings do
  member do
    get 'approve'
  end
end

To add a new Rails action "report" to the collection

http://hostname/billings/report
resources :billings do
  collection do
    get 'report'
  end
end

Ruby on Rails Resources Route - Singular Resources

routes.rb

resource :shop

Use the singular form of resource

Mapping

Helper   METHOD URL         Map To Controller/Action              Description
       shop POST   /shop(.:format)                                   {:controller=>"shops", :action=>"create"}
   new_shop GET    /shop/new(.:format)                               {:controller=>"shops", :action=>"new"}
  edit_shop GET    /shop/edit(.:format)                              {:controller=>"shops", :action=>"edit"}
            GET    /shop(.:format)                                   {:controller=>"shops", :action=>"show"}
            PUT    /shop(.:format)                                   {:controller=>"shops", :action=>"update"}
            DELETE /shop(.:format)                                   {:controller=>"shops", :action=>"destroy"}

To possibly re-use the same controller, singular resources map to plural controllers.

Helper methods to create URL links

Route Helper URL Action Mapped To
shop_path
http://host:port/shop/
show
new_shop_path
http://host:port/shop/new
new
edit_shop_path
http://host:port/shop/edit
edit

Nested Resources

Create an associated model under another model

Generate a controller and a model

rails generate model Contact title:string description:text account:accounts
rails generate controller Contacts

Rails Model

class Account < ActiveRecord::Base
  has_many :contacts
end
 
class Contact < ActiveRecord::Base
  belongs_to :account
end

Routing for Nested Resources

Rails route information

router.rb
resources :accounts do
  resources :articles
end

Rails route: rake routes

account_contacts GET    /accounts/:account_id/contacts(.:format)          {:controller=>"contacts", :action=>"index"}
                     POST   /accounts/:account_id/contacts(.:format)          {:controller=>"contacts", :action=>"create"}
 new_account_contact GET    /accounts/:account_id/contacts/new(.:format)      {:controller=>"contacts", :action=>"new"}
edit_account_contact GET    /accounts/:account_id/contacts/:id/edit(.:format) {:controller=>"contacts", :action=>"edit"}
     account_contact GET    /accounts/:account_id/contacts/:id(.:format)      {:controller=>"contacts", :action=>"show"}
                     PUT    /accounts/:account_id/contacts/:id(.:format)      {:controller=>"contacts", :action=>"update"}
                     DELETE /accounts/:account_id/contacts/:id(.:format)      {:controller=>"contacts", :action=>"destroy"}
            accounts GET    /accounts(.:format)                               {:controller=>"accounts", :action=>"index"}
                     POST   /accounts(.:format)                               {:controller=>"accounts", :action=>"create"}
         new_account GET    /accounts/new(.:format)                           {:controller=>"accounts", :action=>"new"}
        edit_account GET    /accounts/:id/edit(.:format)                      {:controller=>"accounts", :action=>"edit"}
             account GET    /accounts/:id(.:format)                           {:controller=>"accounts", :action=>"show"}
                     PUT    /accounts/:id(.:format)                           {:controller=>"accounts", :action=>"update"}
                     DELETE /accounts/:id(.:format)                           {:controller=>"accounts", :action=>"destroy"}

Rails Nested Resources URL Helper Methods

<%= link_to "Contact details", account_contact_path(@account, @contact) %>
<!-- same -->
<%= link_to "Contact details", url_for(@account, @contact) %>
<%= link_to "Contact details", [@account, @contact] %>
<%= link_to "Account details", @account %>

Nested Resources - Controller & View

Controller for Contacts

class ContactsController < ApplicationController
  def create
    @account = Account.find(params[:account_id])
    @contact = @account.contacts.create(params[:id])
    redirect_to account_path(@account)
  end
end

View

<p>Add a contact:</p>
<%= form_for([@account, @account.contacts.build]) do |f| %>
  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :description %><br />
    <%= f.text_area :description %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Controller Namespace

Add prefix namespace to URL

namespace "partner" do
  resources :deals, :billings
end
  • Prepend the /partner/ to the URL
  • Use Partner::DealsController to handle the URL

Rails will create:

partner_deals GET    /partner/deals(.:format)                   {:controller=>"partner/deals", :action=>"index"}
                  POST   /partner/deals(.:format)                   {:controller=>"partner/deals", :action=>"create"}
 new_partner_deal GET    /partner/deals/new(.:format)               {:controller=>"partner/deals", :action=>"new"}
edit_partner_deal GET    /partner/deals/:id/edit(.:format)          {:controller=>"partner/deals", :action=>"edit"}
     partner_deal GET    /partner/deals/:id(.:format)               {:controller=>"partner/deals", :action=>"show"}
                  PUT    /partner/deals/:id(.:format)               {:controller=>"partner/deals", :action=>"update"}
                  DELETE /partner/deals/:id(.:format)               {:controller=>"partner/deals", :action=>"destroy"}

To route URL /partner/deals to DealsController without the Partner:: module prefix

scope "/partner" do
  resources :deals, 
end

To route to the same controller (Partner::DealsController) without the URL /partner prefix

scope :module => "partner" do
  resources :deals
end

Ruby on Rails Non-Resourceful Routes

Define a URL pattern with controller/action name

# URL: /accounts
# URL: /accounts/view      
# URL: /accounts/view/1   params[:id] will set to 1
match ':controller(/:action(/:id))'

Define multiple Hash key for params

# URL /accounts/view/13/345?code='text'
match ':controller/:action/:first_id/:second_id'

Parameters is accessible by

params[:second_id]
params[:code]

Define static text segments

# URL /accounts/view/13/some_static_text/345
match ':controller/:action/:id/some_static_text/:second_id'

Static URL pattern

Mapping a URL pattern to a specific Rails controller and action

match 'accountreview/:id' => 'accounts#report'

Set default value for parameter params[:fee] to "yes"

match 'accountreview/:id' => 'accounts#report', :defaults => { :fee => 'yes' }

Create link helper method

match 'signout' => 'account#signout', :as => :out
  • ":as" create out_path and out_url helper methods automatically. out_path will return /signout

Options for Resources Routing

resources :trades, :controller => "trades"
Options supported Example Description
:controller resources :stocks, :controller => "admin/trades" Specify the controller handling the request
:constraints resources :accounts, :constraints => {:id => /[0-9]+/} :id must be all digits in /accounts/:id
:as resources :accounts, as "users" Use "users" as prefix for path helper method like new_user_path
:path_names resources :accounts, :path_names => { :new => 'create', :edit => 'update' } Use /accounts/make and /accounts/1/update for the new and edit URL
:only resources :accounts, :only => [:index, :show] Only route these actions
:except resources :accounts, :except => [:destroy] Do not route these actions
:path resources :accounts, :path => "vip" use /vip instead of /accounts

Define singular/plural of a word

ActiveSupport::Inflector.inflections do |inflect|
  inflect.irregular 'person', 'people'
end

Apply Constraint to a Route

Limit HTTP method

Apply the URL pattern to HTTP post only

match 'accountreview/:id' => 'accounts#report', :via => :post

Apply the URL pattern to multiple HTTP methods

match 'accountreview/:id' => 'accounts#report', :via => [:get, :post]

Apply Regular Pattern Constraint

Accept URL pattern with id composed of lower case letters only

match 'accountreview/:id' => 'accounts#report', :id => /[a-z]+/

Match URL begins with "RA"

match '/:id' => 'accounts#show', :constraints => { :id => /RA.+/ }

Constraint by Request Object

match "reviews", :constraints => {:subdomain => "partner"}
  • Apply the pattern with request.subdomain returns "partner"
  • Can use any method in the request object as a constraint

Determine a Constraint by the matches? method of an Object

class MyConstraint
  def initialize
    ...
  end

  def matches?(request)
    ...
  end
end
 
MyApp::Application.routes.draw do
  match "/private" => "private#index",
    :constraints => MyConstraint.new
end

Route Globbing

Map URL accounts/11/04/review or accounts/review using wild card

match 'accounts/*rest' => 'accounts#review'
  • params[:rest] set to "11/04/review" and "review" respectively

Use of multiple wild card

match 'accounts/*rest/contact/*cont' => 'accounts#review'

Redirect

Redirect a URL pattern

match "/private" => redirect("/denied")

Use of dynamic segment mapping

match "/private/:name" => redirect("/denied/%{name}")

Using Ruby Code block to determine the redirect target

match "/private/:name" => redirect {|params| "/denied/#{params[:name]}" }
match "/private/:name" => redirect {|params, req| "/denied/#{params[:name]}/#{req.host}" }

Trouble shooting routes

Display the routing in the same order that how a match is found in routes.rb

rake routes

Only display the Rails route for a specific controller

CONTROLLER=accounts rake routes

Unit Testing of Rails Routes

Unit test whether a set of options will generate a specific path

assert_generates "/accounts/1", { :controller => "photos", :action => "show", :id => "1" }
assert_generates "/about", :controller => "pages", :action => "about"

Unit test whether a specific URL pattern will route to the target. (Inverse to assert_generates)

assert_recognizes({ :controller => "accounts", :action => "show", :id => "1" }, "/accounts/1")
assert_recognizes({ :controller => "accounts", :action => "create" }, { :path => "accounts", :method => :post })

Unit test URL link helper method

assert_recognizes new_account_url, { :path => "accounts", :method => :post }

Conduct the unit route testing in both ways

assert_routing({ :path => "accounts", :method => :post }, { :controller => "accounts", :action => "create" })

link_to helper method

Use ActionView helper method

link_to "User", user_path(@user)    # => <a href="/users/123">User</a>

Use a Model instance

link_to "User", @user               # => <a href="/users/123">User</a>

Specify controller, action and Id

link_to "User", :controller => "users", :action => "show", :id => @user     # => <a href="/users/show/1">User</a>
link_to "Users", users_path         # => <a href="/users">Users</a>

Generate the custom HTML code inside the anchor tag

<%= link_to(@user) do %>
  <b><%= @profile.name %></b>
<% end %>
# => <a href="/users/1">
       <b>John</b>
     </a>

Specify a CSS Style

link_to "Users", users_path, :id => "user_id", :class => "user_style"
# => <a href="/users" class="user_style" id="user_id">Articles</a>
link_to "Users", { :controller => "users" }, :id => "user_id", :class => "user_style"
# => <a href="/users" class="user_style" id="user_id">Articles</a>

Add a HTML anchor

link_to "Section A", user_path(@user, :anchor => "a")
# => <a href="/users/1#a">Section A</a>

Add query string

link_to "User", user_path(@user, :q1 => "v1", :q2 => "v2")  # => <a href="/users/123?q1=v1&q2=v2">User</a>