ruk·si

🛤️ Ruby on Rails
Routes

Updated at 2016-04-07 22:28

This guide contains notes tips how to write Ruby on Rails routes. Related to Ruby Guide and Ruby on Rails Guide.

Routes are prioritized according to the order they are defined. The first match is served.

Route basics:

# visitors to /page are served by PageController.hello method
get '/page', to: 'page#hello'

# turns 'id' into param[:id]
get '/patients/:id', to: 'patients#show'

# change route name for generated helpers
# E.g. helper 'clients_path(@patient)' generates '/clients/17' here
get '/patients/:id', to: 'patients#show', as: 'clients'

# ruby on rails can generate the route for you from objects
url_for([@magazine, @ad])
url_for([:edit, @magazine, @ad])

# match multiple HTTP verbs, note that routing get and post to same is not safe
match 'photos', to: 'photos#show', via: [:get, :post]
match 'photos', to: 'photos#show', via: :all

# route GET / to where, place first as it is the most common route
root to: 'pages#main'
root 'pages#main' # shortcut for the above
# inside namespace e.g. /admin
namespace :admin do
  root to: "admin#index"
end

# you can change the default identifier (:id) with param
resources :videos, param: :identifier

To see all paths in development mode:

http://localhost:3000/rails/info/routes
# or...
# bundle exec rake routes

Plural Resources

resources :photos

# generates restful interface with 7 different methods
# GET    /photos          = photos#index
# GET    /photos/new      = photos#new
# POST   /photos          = photos#create
# GET    /photos/:id      = photos#show + param[:id]
# GET    /photos/:id/edit = photos#edit + param[:id]
# PUT    /photos/:id      = photos#update + param[:id]
# DELETE /photos/:id      = photos#destroy + param[:id]

# generates 4 relative path helpers
# photos_path          = /photos
# photos_path(:id)     = /photos/:id
# new_photo_path       = /photos/new
# edit_photo_path(:id) = /photos/:id/edit

# generates 4 absolute url helpers
# photos_url           = http://example.com:3000/photos
# photos_url(:id)      = http://example.com:3000/photos/:id
# new_photo_url        = http://example.com:3000/photos/new
# edit_photo_url(:id)  = http://example.com:3000/photos/:id/edit

# You usually use the PUTs/POSTs with forms and DELETE with a method link.
# Method link uses `jquery_ujs` gem, which is enabled by default.
link_to "Delete", photos_path(auction), method: :delete
resources :photos
resources :books
resources :videos
# is the same as
resources :photos, :books, :videos

You can overwrite singular form of a resource through Inflector.

ActiveSupport::Inflector.inflections do |inflect|
  inflect.irregular 'tooth', 'teeth'
end

Singular Resources

resource :geocoder

# generates restful interface with 7 different methods
# GET    /geocoder/new      = geocoder#new
# POST   /geocoder          = geocoder#create
# GET    /geocoder          = geocoder#show
# GET    /geocoder/edit     = geocoder#edit
# PUT    /geocoder          = geocoder#update
# DELETE /geocoder          = geocoder#destroy

# generates 3 relative path helpers
# geocoder_path      = /geocoder
# new_geocoder_path  = /geocoder/new
# edit_geocoder_path = /geocoder/edit

# generates 3 absolute url helpers
# geocoder_url       = http://example.com:3000/geocoder
# new_geocoder_url   = http://example.com:3000/geocoder/new
# edit_geocoder_url  = http://example.com:3000/geocoder/edit

Partial Resource

resources :comments, only: [:index, :new, :create]

# generates restful interface with 3 different methods
# GET    /comments          = comments#index
# GET    /comments/new      = comments#new
# POST   /comments          = comments#create

# generates 2 relative path helpers
# comments_path           = /comments
# new_comments_path       = /comments/new
# and the url variants

# exclude an action
resources :comments, except: :destroy

Namespaced Resources

Use namespaces to group related resources and actions.

namespace :admin do
  resources :articles
end

# generates restful interface with 7 different methods
# they all direct to Admin::ArticlesController
# GET    /admin/articles          = admin/articles#index
# GET    /admin/articles/new      = admin/articles#new
# POST   /admin/articles          = admin/articles#create
# GET    /admin/articles/:id      = admin/articles#show + param[:id]
# GET    /admin/articles/:id/edit = admin/articles#edit + param[:id]
# PUT    /admin/articles/:id      = admin/articles#update + param[:id]
# DELETE /admin/articles/:id      = admin/articles#destroy + param[:id]

# generates 4 relative path helpers
# admin_articles_path          = /admin/articles
# admin_article_path(:id)      = /admin/articles/:id
# new_admin_article_path       = /admin/articles/new
# edit_admin_article_path(:id) = /admin/articles/:id/edit
# and the url variants

# to route /articles without the prefix to Admin::ArticlesController
scope module: 'admin' do
  resources :articles
end

# to scope with custom prefixing
scope 'admin', as: 'boss' do
  resources :photos, :accounts
end
resources :photos, :accounts

Nested Routes

class Magazine < ActiveRecord::Base
  has_many :ads
end

class Ad < ActiveRecord::Base
  belongs_to :magazine
end

# routes.rb
resources :magazines do
  resources :ads
end

# generates restful interface with 7 different methods
# GET    /magazines/:magazine_id/ads          = ads#index
# GET    /magazines/:magazine_id/ads/new      = ads#new
# POST   /magazines/:magazine_id/ads          = ads#create
# GET    /magazines/:magazine_id/ads/:id      = ads#show + param[:id]
# GET    /magazines/:magazine_id/ads/:id/edit = ads#edit + param[:id]
# PUT    /magazines/:magazine_id/ads/:id      = ads#update + param[:id]
# DELETE /magazines/:magazine_id/ads/:id      = ads#destroy + param[:id]

# generates 4 relative path helpers
# magazine_ads_path(@magazine)           = /magazines/:id/ads
# magazine_ad_path(@magazine, :id)       = /magazines/:id/ads/:id
# new_magazine_ad_path(@magazine)        = /magazines/:id/ads/new
# edit_magazine_ad_path(@magazine, :id)  = /magazines/:id/ads/:id/edit
# and the related url helpers

Avoid using nested routes. Never nest more than once. Helper methods become too long, they become hard to understand and debug.

Use shallow routes if you go deeper than one level.

resources :magazines, shallow: true do
  resources :ads do
    resources :versions
  end
end

Routing Concerns

Routing concerns allow declaring common routes that can be reused inside other resources, namespaces and routes.

# Define concern.
concern :commentable do
  resources :comments
end
concern :image_attachable do
  resources :images, only: :index
end

# Use with resource.
resources :messages, concerns: :commentable
resources :articles, concerns: [:commentable, :image_attachable]

# This is the same as:
resources :messages do
  resources :comments
end
resources :articles do
  resources :comments
  resources :images, only: :index
end

Additional Actions

Adding action that applies to individual member of a collection.

resources :photos do
  get 'preview', on: :member
end
# or... if you have multiple new actions
resources :photos do
  member do
    get 'preview'
  end
end
# or... silly way to do it
get 'photos/:id/preview'
resources :photos

# generates an additional restful interface method
# GET  /photos/:id/preview   = photos#preview + param[:id]

# generates a relative and absolute path helpers
# preview_photo_path(:id)
# preview_photo_url(:id)

Adding action that applies to the collection.

resources :photos do
  get 'search', on: :collection
end
# or... if you have multiple new actions
resources :photos do
  collection do
    get 'search'
  end
end
# or... silly way to do it
get 'photos/search'
resources :photos

# generates an additional restful interface method
# GET  /photos/search   = photos#search

# generates a relative and absolute path helpers
# search_photos_path
# search_photos_url

You an customize the additional actions.

resources :comments do
  get 'preview', on: :new
end

# generates an additional restful interface method
# GET   /comments/new/preview   = comments#preview

# generates a relative and absolute path helpers
# preview_new_comment_path
# preview_new_comment_url

Bound Route Parameters

:controller and :action are special parameters.

# don't use this though, allows access to all actions
get ':controller(/:action(/:id))'

All other parameters are bound to the request in params[:name]

get 'product/search/:text'

Optional parameters are marked with parentheses ().

get 'product/search(/text/:text)(/order/:order)'

By default, dynamic paths don't accept dots . as dot is used as a separator in formatted routes. Use regexp constraint if you need to allow dots.

get 'photos/:id', to: 'photos#show', constraints: { id: /[^\/]+/ }

Query Strings

Routing doesn't normally care about query strings, the values can be found in params[:key] though.

You can define the default query string.

get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
# params[:format] is 'jpg' if not found in URL query string

You can define query strings that cannot be changed based on URL.

get 'photos', to: 'photos#show', id: 'special'
# But usually an extra route is more clean:
get 'photos/special' => 'photos#special'

Constraints

resources :photos, constraints: { id: /[A-Z][A-Z][0-9]+/ }

# apply a constraint to multiple resources
constraints(id: /[A-Z][A-Z][0-9]+/) do
  resources :photos
  resources :accounts
end

Rails implicitly anchors regex to start and end.

get 'people/show/:id' => :show, constraints: { :id => /\d+/ }
# Matches '123' but not 'foo32bar'

Request Constraints

You can also constrain by any value in Response Object.

get 'photos', to: 'photos#index', constraints: { subdomain: 'api' }
# http://api.example.com/photos

You can also define constraint in a block.

namespace :admin do
  constraints subdomain: 'api' do
    resources :photos
  end
end
# http://api.example.com/admin/photos

Advanced Constraints

If you have more complex constraint, you can bind the route to an object that offers matches? method that takes the request and returns boolean if the constraint is satisfied.

class BlacklistConstraint
  def initialize
    @ips = Blacklist.retrieve_ips
  end

  def matches?(request)
    @ips.include?(request.remote_ip)
  end
end

Rails.application.routes.draw do
  get '*path', to: 'blacklist#index',
    constraints: BlacklistConstraint.new
end

# Or as a lambda...
Rails.application.routes.draw do
  get '*path', to: 'blacklist#index',
    constraints: lambda { |request|
      Blacklist.retrieve_ips.include?(request.remote_ip)
    }
end

Wildcard Segments

get 'photos/*other', to: 'photos#unknown'
# Matches photos/12 and /photos/long/path/to/12
# First one's params[:other] is 12
# Second one's params[:other] is long/path/to/12

# widlcards can be anywhere in a route
get 'books/*section/:title', to: 'books#show'
# some/section/last-words-a-memoir
# params[:section] = some/section
# params[:title] = last-words-a-memoir

get '*a/foo/*b', to: 'test#index'
# zoo/woo/foo/bar/baz
# params[:a] = zoo/woo
# params[:b] = bar/baz

Never use the legacy wild controller route. This route will make all actions in every controller accessible via GET requests.

# very bad
match ':controller(/:action(/:id(.:format)))'

Renaming Segments

resources :photos, path_names: { new: 'make', edit: 'change' }
# or...
scope path_names: { new: 'make', edit: 'change' } do
  resources :photos
end

# /photos/make
# /photos/1/change
# note that the actual action names don't change

Redirecting

# Note that these are Moved Permanently (301) redirects by default,
# so other web servers might cache the redirect and forget the original URL.
get '/stories', to: redirect('/articles')
get '/stories/:name', to: redirect('/articles/%{name}')
get '/stories/:name', to: redirect do |path_params, request|
  "/articles/#{path_params[:name].pluralize}"
end
get '/stories', to: redirect do |path_params, request|
    "/articles/#{request.subdomain}"
end

Change Resource Controller

# paths and helpers are all photos but the controller is e.g. images#index
resources :photos, controller: 'images'

Translated Paths

scope(path_names: { new: 'neu', edit: 'bearbeiten' }) do
  resources :categories, path: 'kategorien'
end

# generates restful interface with 7 different methods
# GET    /kategorien                 = categories#index
# GET    /kategorien/neu             = categories#new
# POST   /kategorien                 = categories#create
# GET    /kategorien/:id             = categories#show + param[:id]
# GET    /kategorien/:id/bearbeiten  = categories#edit + param[:id]
# PUT    /kategorien/:id             = categories#update + param[:id]
# DELETE /kategorien/:id             = categories#destroy + param[:id]

# generates 4 relative path helpers
# categories_path
# new_category_path
# categories_path
# category_path(:id)
# edit_category_path(:id)
# category_path(:id)
# category_path(:id)
# and the related url helpers

Named Routes

Naming a route using :as creates helpers that can be used in views.

get 'help' => 'help# index', as: 'help'
# Now `help_path` and `help_url` helpers will work in views.

get 'help/:id' => 'help#show', as: 'help'
# Parameters can also be added to URL helpers e.g. `help_path(id: 10)`

Sources