Skip to content

Instantly share code, notes, and snippets.

@jamesyang124
Last active October 30, 2015 15:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesyang124/76de5339accfea2bc6f9 to your computer and use it in GitHub Desktop.
Save jamesyang124/76de5339accfea2bc6f9 to your computer and use it in GitHub Desktop.

#Rails Routing

  1. We can define customed url helper by :as

    get "/photos/:id", to: "photos#show", as: :"show_photo"
    
    <%= link_to 'Photo Record', show_photo_path(15) %>
    
    show_photo_path(15)
    # /photos/15

1. Non-Resourceful Routes

1.1 Bound Parameters

  1. you can supply a series of symbols that Rails maps to parts of an incoming HTTP request.

    #####Two of these symbols are special:

    :controller maps to the name of a controller in your application.
    :action maps to the name of an action within that controller.
    # If we have URL: /photos/show/1
    # then it can bound to:
    get ":controller/(:action/:id)"
    
    parmas
    # controller: "photos"
    # action: "show"
    # id: "1"
    
    # dynamic segments :user_id
    get ":controller/(:action/:id)/:user_id"
    
    # /photos/show/1/50
    params
    # user_id: 50

1.2 Defining Defaults

  1. We don't need to explicitly use symbols(:controller, :action ...).

    get 'photos/:id', to: 'photos#show'
    # /photos/5 => photos#show
    
    get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
    # set extra params key-value pairs
    # /photos/5
    
    params
    # format: 'jpg'

1.3 Naming Routes

  1. define named route by :as:

    get ':username', to: 'users#show', as: :user
    
    # user_path

1.4 HTTP Verb Constraints

  1. You can constraint HTTP verbs(GET, POST, PUT, PATCH, and DELETE) with match.

    match "photos", to: "photos#show", via: [:get, :post]
    
    # use :all to allowed all HTTP Verbs
    match "photos", to: "photos#all", via: :all 

1.5 Segment Constraints

  1. You can use the :constraints option to enforce a format for a dynamic segment:

    get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ }
    
    # Terse way to constraint :id
    get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/
    
    # This match paths such as /photos/A12345, but not /photos/893.

1.6 Request-Based Constraints

  1. You can also constrain a route based on any method on the Request object that returns a String.

    get 'photos', to: 'photos#index', constraints: { subdomain: 'admin' }
    
    # Or using block
    
    namespace :admin do
    	constraints subdomain: 'admin' do
    		resources :photos
    	end
    end	
1.7 Advanced Constraints
  1. If you have a more advanced constraint, you can provide an object that responds to matches? that Rails should use.

    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 specify by lambda:

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

1.8 Route Globbing and Wildcard Segments

  1. Use * to match wildcard segments:

    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'.
  2. By requesting '/foo/bar.json', your params[:pages] will be equal to 'foo/bar' with the request format of JSON. If you want the old 3.0.x behavior back, you could supply format: false like this:

    get '*pages', to: 'pages#show', format: false
    
    # or true to make the format segment mandatory.

1.9 Redirection

  1. You can reuse dynamic segments from the match in the path to redirect to:

    get '/stories/:name', to: redirect('/articles/%{name}')
    
    # or block to redirect
    get '/stories/:name', to: redirect do |path_params, req| 
    	"/articles/#{path_params[:name].pluralize}"
    end
    
    get '/stories', to: redirect { |path_params, req| "/articles/#{req.subdomain}" }
  2. Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible.

1.10 Routing to Rack Applications

  1. Instead of a String like 'articles#index', which corresponds to the index action in the ArticlesController, you can specify any Rack application as the endpoint for a matcher:

    match '/application.js', to: Sprockets, via: :all
  2. As long as Sprockets responds to #call and returns a [status, headers, body], the router won't know the difference between the Rack application and an action.

  3. 'articles#index' actually expands out to ArticlesController.action(:index), which returns a valid Rack application.

  4. matcher or mount:

    http://inductor.induktiv.at/blog/2010/05/23/mount-rack-apps-in-rails-3/

    # your rack application should expect 
    # the route to be '/admin':
    match '/admin', to: AdminApp, via: :all
    
    
    # If you would prefer to have your rack application 
    # receive requests at the root path instead use mount:
    mount AdminApp, at: '/admin'
    
    class AdminApp
    	get '/ack' do 
    		# route /admin/ack
    	end
    end

1.11 Using Root

  1. You can specify what Rails should route '/' to with the root method:

    root to: 'pages#main'
    root 'pages#main' # shortcut for the above
  2. The root route only routes GET requests to the action. You can also use root inside namespaces and scopes as well.

    namespace :admin do
    	root to: "admin#index"
    end
    
    root to: "home#index"
  3. You can specify unicode char routes:

    get 'こんにちは', to: 'welcome#index'

2. Resources Routes

  1. resources :photos:

    HTTP Verb Path Controller#Action Named Routes
    GET /photos photos#index photos_path
    GET /photos/new photos#new new_photo_path
    POST /photos photos#create photos_path
    GET /photos/:id photos#show photo_path(:id)
    GET /photos/:id/edit photos#edit edit_photo_path(:id)
    PATCH /PUT /photos/:id photos#update photo_path(:id)
    DELETE /photos/:id photos#destroy photo_path(:id)

2.1 Singular Resource

  1. resource :geocoder:

    HTTP Verb Path Controller#Action Named Routes
    GET /geocoder/new geocoders#new new_geocoder_path
    POST /geocoder geocoders#create geocoder_path
    GET /geocoder geocoders#show geocoder_path
    GET /geocoder/edit geocoders#edit edit_geocoder_path
    PATCH/PUT /geocoder geocoders#update geocoder_path
    DELETE /geocoder geocoders#destroy geocoder_path

2.2 Controller Namespaces and Routing

  1. :namespace prefix the namespace in both url and controller file path:

    namespace :admin do
    	resources :articles, :comments
    end
    HTTP Verb Path Controller#Action Named Routes
    GET /admin/articles admin/articles#index admin_articles_path
    GET /admin/articles/new admin/articles#new new_admin_article_path
    POST /admin/articles admin/articles#create admin_articles_path
    GET /admin/articles/:id admin/articles#show admin_article_path(:id)
    GET /admin/articles/:id/edit admin/articles#edit edit_admin_article_path
    PATCH/PUT /admin/articles/:id admin/articles#update admin_article_path(:id)
    DELETE /admin/articles/:id admin/articles#destroy admin_article_path(:id)
  2. :scope to decide either prefix url path, or controller, the named routes remain the same as if you did not use :scope:

    # /articles, articles_path, Admin::ArticlesController
    scope module: 'mod' do
    	resources :articles, :ments
    end
    
    # Controller file can locate to /app/controllers/Mod/articles_controller.rb
    class ArticlesController < ApplicationController
    	#...
    end
    
    # Or /app/controllers/articles_controller.rb then create class as below:
    module Mod
    	class ArticlesController < ApplicationController
    		#...
    	end
    end
    
    # call it in rails console	
    Mod::ArticlesController
    
    # or single resources
    resources :articles, module: 'admin'
    
    # /admin/comments, comments_path, CommentsController
    scope '/admin' do
    	resources :articles, :comments
    end
    
    # or single resources
    resources :comments, path: '/admin/comments'

    will be:

    | HTTP VERB | PATH | ACTION | NAMED ROUTES | |-----------|------|--------|--------------| | GET | /admin/comments | comments#index | comments_path | POST | /admin/comments |comments#create | | |GET | /admin/comments/new | comments#new | new_comment_path | | GET | /admin/comments/:id/edit |comments#edit | edit_comment_path(:id) | | GET | /admin/comments/:id | comments#show | comment_path(:id) | | GET |/articles | mod/articles#index | articles_path | | GET | /articles/new | mod/articles#new | new_article_path | | GET |/articles/:id/edit | mod/articles#edit |edit_article_path(:id) | | GET |/articles/:id | mod/articles#show |article_path(:id) |

2.3 Nested Resources

  1. Caveat: Resources should never be nested more than 1 level deep.

    resources :users do
    	resources :member
    end

    will be:

    | HTTP VERB | PATH | ACTION | NAMED ROUTES | |-----------|------|--------|--------------| | GET | /users | users#index | users_path | | POST | /users | users#create | | | GET | /users/new | users#new | new_user_path | | GET | /users/:id/edit | users#edit | edit_user_path(:id) | | GET | /users/:id | users#show | user_path(:id) | | PUT/UPDATE | /users/:id | users#update | | | DELETE | /users/:id | users#destroy | | | GET | /users/:user_id/members | members#index | user_member_index_path | | POST | /users/:user_id/members | members#create | | | GET | /users/:user_id/members/new | members#new | new_user_member_path | | GET | /users/:user_id/members/:id/edit | memberss#edit | edit_user_member_path(:id) | | GET | /users/:user_id/members/:id | users#show | user_member_path(:id) | | PUT/UPDATE | /users/5 | users#update | | | DELETE | /users/5 | users#destroy | |

2.4 Shallow Nesting

  1. One way to avoid deep nesting (as recommended above) is to generate the collection actions scoped under the parent, so as to get a sense of the hierarchy, but to not nest the member actions.

    # collection actions
    resources :articles do
    	resources :comments, only: [:index, :new, :create]
    end
    
    # member actions
    resources :comments, only: [:show, :destroy, :update, :edit]

    Above code is equialent to:

    resources :articles do
    	resources :comments, shallow: true
    end
  2. You can also specify the :shallow option in the parent resource, in which case all of the nested resources will be shallow:

    resources :articles, shallow: true do
    	resources :comments, :quotes, :drafts
    end

    or use DSL:

    shallow do
    	resources :articles do
    		resources :comments
    		resources :quotes
    		resources :drafts
    	end
    end
  3. There has two options to change the named routes or URL paths in :scope:

    ####Add "sekret" to shallow URL path

    scope shallow_path: "sekret" do
    	resources :articles do
    		resources :comments, shallow: true
    	end
    end

    will be:

    |HTTP Verb | Path | Controller#Action | Named Routes | |----------|-------|-------------------|--------------| |GET | /articles/:article_id/comments | comments#index | article_comments_path| |POST | /articles/:article_id/comments | comments#create | article_comments_path| |GET |/articles/:article_id/comments/new | comments#new | new_article_comment_path | |GET |/sekret/comments/:id/edit | comments#edit | edit_comment_path | |GET |/sekret/comments/:id | comments#show| comment_path | |PATCH/PUT |/sekret/comments/:id | comments#update | comment_path | |DELETE |/sekret/comments/:id | comments#destroy | comment_path |

    ####Add "skeret" to shallow named routes

    scope shallow_prefix: "sekret" do
    	resources :articles do
    		resources :comments, shallow: true
    	end
    end

    will be:

    HTTP Verb Path Controller#Action Named Routes

|GET | /articles/:article_id/comments | comments#index | article_comments_path | |POST | /articles/:article_id/comments | comments#create | article_comments_path | |GET |/articles/:article_id/comments/new | comments#new | new_article_comment_path | |GET | /comments/:id/edit | comments#edit | edit_sekret_comment_path(:id) | |GET | /comments/:id | comments#show | sekret_comment_path(:id) | |PATCH/PUT | /comments/:id | comments#update | sekret_comment_path(:id) | |DELETE | /comments/:id | comments#destroy | sekret_comment_path(:id) |

2.5 Routing Concerns

  1. We can group resources and reused inside other resources by :concerns:

    concern :commentable do
    	resources :comments
    end
    
    concern :image_attachable do
    	resources :images, only: :index
    end
    
    # then call it from other resources
    resources :messages, concerns: :commentable
    
    resources :articles, concerns: [:commentable, :image_attachable]
    
    # It is equivalent to:
    resources :messages do
    	resources :comments
    end
    
    resources :articles do
    	resources :comments
    	resources :images, only: :index
    end
  2. You can also put concerns to the :namespace or :scope.

    namespace :articles do
    	concerns :commentable
    end
    
    # articles_comments_path  		=> index
    # new_articles_comment_path 	=> new
    # edit_articles_comment_path 	=> edit
    # articles_comment_path  		=> show

2.6 Creating Paths and URLs From Objects

  1. In addition to using the routing helpers, Rails can also create paths and URLs from an array of parameters.

  2. For instance, when using magazine_ad_path, you can pass in instances of Magazine and Ad instead of the numeric IDs:

    <%= link_to 'Ad details', magazine_ad_path(@magazine, @ad) %>
    
    # or use url_for with array of parameters, Rails will find path for you
    <%= link_to 'Ad details', url_for([@magazine, @ad]) %>
    
    # In helpers like link_to, 
    # you can specify just the object in place of the full url_for call:
    <%= link_to 'Ad details', [@magazine, @ad] %>
    
    # For other actions, you just need to insert the action name as 
    # the first element of the array:
    <%= link_to 'Edit Ad', [:edit, @magazine, @ad] %>

2.7 Adding More RESTful routes

  1. You can also add routes to memeber routes(:show, :edit, :update, :destroy) or collection routes(:index, :new, :create).

    ####Add route/aciton to member routes:

    resources :photos do
    	member do
    		get 'preview'
    	end
    end
    
    # Or 1 liner
    resource :photos do
    	get "preview", on: :member
    end 
    
    # This result:
    # preview_photo_path 
    # GET /photos/:id/preview(.:format)         
    # photos#preview

    ####Add route/action to collection routes:

    resource :photos do
    	collection do
    		get 'search'
    	end
    	
    	get 'search', on: :collection
    end
    
    # This result:
    # search_photos_path
    # GET  
    # /photos/search(.:format) 
    # photos#search

    ####Add route/aciotn to new action

    resource :photos do
    	get 'sear', on: :new
    end
    
    # This result:
    # sear_new_photos_path
    # GET   
    # /photos/new/sear(.:format) 
    # photos#sear

3. Customizing Resourceful Routes

  1. The :controller option lets you explicitly specify a controller to use for the resource.

    resources :photos, controller: 'images'

    will be:

    |HTTP Verb | Path | Controller#Action | Named Helper | |----------|-------|-------------------|--------------| |GET | /photos | images#index | photos_path | |GET | /photos/new | images#new | new_photo_path | |POST | /photos | images#create | photos_path | |GET | /photos/:id | images#show | photo_path(:id) | |GET | /photos/:id/edit | images#edit | edit_photo_path(:id) | |PATCH/PUT | /photos/:id | images#update | photo_path(:id) | |DELETE | /photos/:id | images#destroy | photo_path(:id) |

  2. For namespaced controllers you can use the directory notation.

    resources :user_permissions, controller: 'admin/user_permissions'
    
    # This will route to the Admin::UserPermissions controller.
  3. You can use the :constraints option to specify a required format on the implicit id. The router would no longer match /photos/1 to this route. Instead, /photos/RR27 would match.

    resources :photos, constraints: { id: /[A-Z][A-Z][0-9]+/ }
  4. You can specify a single constraint to apply to a number of routes by using the block form:

    constraints(id: /[A-Z][A-Z][0-9]+/) do
    	resources :photos
    	resources :accounts
    end

3.1 Overriding the Named Helpers

  1. The :as option lets you override the normal naming for the named route helpers. For example:

    resources :photos, as: 'images'

    will be:

    | HTTP Verb | Path | Controller#Action | Named Helper | |-----------|------|-------------------|--------------| | GET | /photos | photos#index | images_path | | GET | /photos/new | photos#new | new_image_path | | POST | /photos | photos#create | images_path | | GET | /photos/:id | photos#show | image_path(:id) | | GET | /photos/:id/edit | photos#edit | edit_image_path(:id) | | PATCH/PUT | /photos/:id | photos#update | image_path(:id) | | DELETE | /photos/:id | photos#destroy | image_path(:id) |

3.2 Overriding the new and edit Segments

  1. The :path_names option lets you override the automatically-generated new and edit segments in paths:

    resources :photos, path_names: { new: 'make', edit: 'change' }
    
    # routing path for new and edit:
    /photos/make
    /photos/1/change
  2. If you find yourself wanting to change this option uniformly for all of your routes, you can use a scope.

    scope path_names: { new: 'make' } do
    	# rest of your routes
    end

3.3 Prefixing the Named Route Helpers

  1. You can use the :as option to prefix the named route helpers that Rails generates for a route. Use this option to prevent name collisions between routes using a path scope.

    scope 'admin' do
    	resources :photos, as: 'admin_photos'
    end
    
    resources :photos
    
    # admin_photos_path, new_admin_photo_path
  2. To prefix a group of route helpers, use :as with scope:

    scope 'admin', as: 'admin' do
    	resources :photos, :accounts
    end
    
    resources :photos, :accounts
    
    # generate routes such as admin_photos_path and admin_accounts_path 
    # which map to /admin/photos and /admin/accounts respectively.
  3. The namespace scope will automatically add :as as well as :module and :path prefixes.

  4. You can prefix routes with a named parameter also:

    scope ':username' do
    	resources :articles
    end
    
    # /bob/articles/1 
    # will allow you to reference the username part of 
    # the path as params[:username] in controllers, helpers and views.
    params[:username] == "bob"

3.4 Restricting the Routes Created

  1. Use only or except:

    resources :photos, only: [:index, :show]
    
    resources :photos, except: :destroy
  2. Using scope, we can alter path names generated by resources:

    scope(path_names: { new: 'neu', edit: 'bearbeiten' }) do
    	# instead /categories, paths now: /kategorien
    	resources :categories, path: 'kategorien'
    end

    will be:

    |HTTP Verb | Path | Controller#Action | Named Helper | |----------|------|-------------------|--------------| |GET | /kategorien | categories#index | categories_path | |GET | /kategorien/neu | categories#new | new_category_path | |POST | /kategorien | categories#create | categories_path | |GET | /kategorien/:id | categories#show | category_path(:id) | |GET | /kategorien/:id/bearbeiten | categories#edit | edit_category_path(:id) | |PATCH/PUT | /kategorien/:id | categories#update | category_path(:id)| |DELETE | /kategorien/:id | categories#destroy | category_path(:id) |

3.5 Overriding the Singular Form

  1. If you want to define the singular form of a resource, you should add additional rules to the Inflector:

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

3.6 Using :as in Nested Resources

  1. The :as option overrides the automatically-generated name for the resource in nested route helpers. For example:

    resources :magazines do
    	resources :ads, as: 'periodical_ads'
    end
    
    # named route change to:
    # magazine_periodical_ads_url 
    # edit_magazine_periodical_ad_path

3.7 Overriding Named Route Parameters

  1. The :param option overrides the default resource identifier :id (name of the dynamic segment used to generate the routes). You can access that segment from your controller using params[<:param>].

    resources :videos, param: :identifier
    
    # Video model using identifier attribute as their id
    Video.find_by(identifier: params[:identifier])

    will be:

    |videos | GET |/videos(.:format) |videos#index| |---------|------|-----------------------------------|------------| | |POST |/videos(.:format) |videos#create| |new_videos |GET |/videos/new(.:format) |videos#new| |edit_videos |GET |/videos/:identifier/edit(.:format) |videos#edit|

4. Inspecting and Testing Routes

  1. List routes in http://localhost:3000/rails/info/routes under development mode.

  2. You may restrict the listing to the routes that map to a particular controller setting the CONTROLLER environment variable:

```sh
$ CONTROLLER=welcome bin/rake routes

Prefix 		Verb URI Pattern              Controller#Action
  root 		GET  /                        welcome#index
       		POST /                        welcome#create

clients POST /clients(.:format) welcome#client_create GET /index/:status(.:format) welcome#get_index {:foo=>"bar"} show_photo GET /photos/:id(.:format) welcome#index ```

> [Setting an environment variable before a command in bash](http://stackoverflow.com/questions/10856129/setting-an-environment-variable-before-a-command-in-bash-not-working-for-second#)

4.1 Tesing Routes

  1. Routes should be included in your testing strategy (just like the rest of your application). Rails offers three built-in assertions designed to make testing routes simpler.

    ####https://whatdoitest.com/rails-assertions-cheat-sheet

    assert_generates
    assert_recognizes
    assert_routing	
    
    
    assert_generates '/photos/1', { controller: 'photos', action: 'show', id: '1' }
    assert_generates '/about', controller: 'pages', action: 'about'
  2. assert_generates asserts that a particular set of options generate a particular path and can be used with default routes or custom routes.

  3. assert_recognizes is the inverse of assert_generates. It asserts that a given path is recognized and routes it to a particular spot in your application.

    assert_recognizes({ controller: 'photos', action: 'create' }, { path: 'photos', method: :post })
  4. The assert_routing assertion checks the route both ways: it tests that the path generates the options, and that the options generate the path. Thus, it combines the functions of assert_generates and assert_recognizes.

    assert_routing({ path: 'photos', method: :post }, 
    		{ controller: 'photos', action: 'create' })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment