🛤️ Ruby on Rails - Routes
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)`