Routes and Resources

Basic Routing Conventions

Let's take a look at our routes as they are now, using rake routes:

$ rake routes

       Prefix Verb URI Pattern                                               Controller#Action
   list_posts GET  /list_posts(.:format)                                     application#list_posts
              GET  /show_post/:id(.:format)                                  application#show_post
     new_post GET  /new_post(.:format)                                       application#new_post
  create_post POST /create_post(.:format)                                    application#create_post
              GET  /edit_post/:id(.:format)                                  application#edit_post
              POST /update_post/:id(.:format)                                application#update_post
              POST /delete_post/:id(.:format)                                application#delete_post
              POST /create_comment_for_post/:post_id(.:format)               application#create_comment
              POST /list_posts/:post_id/delete_comment/:comment_id(.:format) application#delete_comment
list_comments GET  /list_comments(.:format)                                  application#list_comments

One of the key insights for Rails conventions is that data-centric apps (or CRUDy apps) tend to have very similar ways for letting users interact with the application, which are:

  • a page to view a list of something
  • view something
  • show a form to create a new instance of something
  • actually create something
  • show a form to edit an instance of something
  • actually update something
  • delete something

Those seven "interaction patterns" are so common, that Rails provides a strong set of conventions from routing to controllers, views and forms to help make those workflows simpler. We're going to focus on the routing part now.

For routing, the convention is that instead of us having to come up with the URL patterns, Rails recommends that we structure the URLs and corresponding controller actions in the following way:

Workflow HTTP VERB PATH Controller Action
Show a list of posts GET /posts posts#index
Show a post GET /posts/:id posts#show
Show the page to create post GET /posts/new posts#new
Create a post POST /posts posts#create
Show the page to edit a post GET /posts/:id/edit posts#edit
Update a post PUT/PATCH /posts/:id posts#update
Delete a post DELETE /posts/:id posts#destroy

We can change the post part of our current routing to follow this convention:

# posts
GET  /list_posts        ->   GET       /posts           posts#index
GET  /show_post/:id     ->   GET       /posts/:id       posts#show
GET  /new_post          ->   GET       /posts/new       posts#new
POST /create_post       ->   POST      /posts           posts#create
GET  /edit_post/:id     ->   GET       /posts/:id/edit  posts#edit
POST /update_post/:id   ->   PUT/PATCH /posts/:id       posts#update
POST /delete_post/:id   ->   DELETE    /posts/:id       posts#destroy

And in the routes.rb file,

Rails.application.routes.draw do

### posts ###

  get    '/posts'          => 'posts#index'
  get    '/posts/new'      => 'posts#new'
  get    '/posts/:id'      => 'posts#show'
  post   '/posts'          => 'posts#create'
  get    '/posts/:id/edit' => 'posts#edit'
  patch  '/posts/:id'      => 'posts#update'
  put    '/posts/:id'      => 'posts#update'
  delete '/posts/:id'      => 'posts#destroy'

# comments later..
  ...

end

Notice that with this new pattern, there's a lot less verbs in the URL itself, but we're relying on the HTTP verbs (GET/POST/PUT/PATCH/DELETE) to supply the semantics for otherwise same URL patterns, for example /posts/:id.

Note that PUT had been used for updating resources until recently, when PATCH was determined to be a better semantic match for this action. Since then, Rails has moved to preferring PATCH, but allowing both verbs for updates.

Another change we'll see later on is that we will be routing all URLs related to posts to a PostsController instead of handling everything in ApplicationController. The PostController's action names

  • new
  • index
  • show
  • create
  • edit
  • update
  • destroy

are also conventions corresponding to the 7 interaction patterns.

This is the strongest set of conventions that Rails imposes on you as an application developer. We'll ask you to make an effort to memorize it. We'll revisit this plenty of times throughout the book to help you do that as well.

Routing For Multiple Resources

Oftentimes we need to have multiple resources present in the URL. For example for our blog app whenever we need to have a route to create a comment, it's very important that we know which post this comment is created on.

We're currently doing this:

post 'create_comment_for_post/:post_id' => 'application#create_comment'

The Rails convention of a URL with multiple associated resources is to start with the "containing" resource all the way to the "innermost" resource. For example, our routes for comments would change to:

post   '/posts/:id/comments' => 'comments#create'
delete '/posts/:post_id/comments/:id' => 'comments#destroy'

get    '/comments'                => 'comments#index'

The semantics of those URLs represent: "Create a comment under a specific post" and "delete a comment under a specific post". The last route, "show all comments in the system", doesn't need to be "scoped" under a post, so the URL doesn't have to lead with "/posts".

If we need to have multiple resources in the URL with their IDs, the convention is such that the last resource will use the placeholder :id and all the rest will be :resource_id such as :post_id.

Restful

REST stands for "Representational State Transfer". It's a software architecture style and guideline to create scalable web services. REST, as a way to structure web applications, has many facets to it, but let's just look at how it applies to the interface that we use to interact with web apps, the URLs.

To see how REST simplifies the semantics of URLs, let's walk through an example. Let's say we're building an online pizza ordering service. A naïve implementation of the interfaces of the service (URLs) would be:

/check_order?order_id=2
/place_new_order
/pizza_details?type=1
/pay_for_my_order?order_id=12
/cancel_order?order_id=3
/expedite_order?order_id=13

A RESTful interface design would look like this:

GET       /orders/2
POST      /orders
GET       /pizzas/1
POST      /orders/12/payments
DELETE    /orders/3
POST      /orders/13/expeditions

You can see, RESTful interfaces center around "nouns" or "resources", removing "verbs" from the interfaces but instead relying on the standardized HTTP verbs (GET/POST/PUT/DELETE) to supply CRUD semantics for the actions. Verbs such as check, place, cancel are mapped directly to HTTP verbs of GET to retrieve, POST to create, and DELETE to destroy resource order. pay and expedite are structured as creation (HTTP POST) of sub resources of payments and expeditions under resource orders.

RESTful interfaces standardized web application interaction patterns to make them easier to understand and program against. Because the interfaces are centered around manipulating resources, the responses are much more predictable. An analogy of how the RESTful architecture pattern simplifies interaction with web services would be how the Relational Database and SQL language simplifies data storage. Before Relational Database and SQL, data was typically stored in proprietary systems with specific logic to interact with them, this meant that on every project you'd have to learn a different set of instructions to interact with data. Relational Databases and SQL gave a common data structure (data records as rows and columns stored in tables) and a set of common interfaces - four simple verbs of SELECT, INSERT, UPDATE and DELETE that can support all types of applications.

Adopting a RESTful interface for your web app will also streamline your application to align with how the backend data is stored in databases, and makes development easier. We'll see that in the following chapters.

Resource Notation

We have talked about Rails' routing conventions, and RESTful URL patterns. Now our routes.rb file would look like the following:

### config/routes.rb ###

Rails.application.routes.draw do
### posts ###

  get    '/posts'          => 'posts#index'
  get    '/posts/new'      => 'posts#new'
  get    '/posts/:id'      => 'posts#show'
  post   '/posts'          => 'posts#create'
  get    '/posts/:id/edit' => 'posts#edit'
  patch  '/posts/:id'      => 'posts#update'
  put    '/posts/:id'      => 'posts#update'
  delete '/posts/:id'      => 'posts#destroy'


### comments ###

  get    '/comments'                    => 'comments#index'
  post   '/posts/:id/comments'     => 'comments#create'
  delete '/posts/:post_id/comments/:id' => 'comments#destroy'

end

In fact, the routes like what we have in the posts section are very common, so Rails provides a special resources notation that helps generate those for you.

We can simply do:

resources :posts

This line will automatically generate the 8 lines we had before. Note that for the update action, Rails allows both PUT and PATCH verb, for compatibility reasons.

You can run bundle exec rake routes in your command line environment to verify that it generates exactly the same set of routes.

Note that this single line will generate 8 routes. In our case we happen to need all the routes here, but if you only need a subset of the routes, you can define them more explicitly as:

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

This line above would only generate 3 routes for you - type bundle exec rake routes to check it out.

Nested Resources

Rails also allows us to nest resources to create URL patterns with multiple resources, see below:

### config/routes.rb ###

Rails.application.routes.draw do
  resources :posts do
    resources :comments, only: [:create, :destroy]
  end

  resources :comments, only: :index
end

Run bundle exec rake routes and see the output - it should be the same as what we had before.

Path Helpers

When you run bundle exec rake routes, notice the Prefix column for some of these routes. These are here to help us know how to use Rails' provided path helpers for our routes. We can use these prefixes followed by _path in our controllers and views to easily build paths.

For example:

posts_path # => '/posts'

post_id = 123
post_comments_path(post_id)
# => '/posts/123/comments'

What's the advantage of using URL helpers, rather than hard coding the paths such as /posts/123/comments directly in your code? (for example, in your controller action?) The reason you want to use path helpers is that if you want to change the URL patterns in your app, it's a lot easier to use the URL helpers to return a different path - let's look at an example:

get '/register', to: 'users#new', as: 'register'

When we add the "as" clause to this route, if you run bundle exec rake routes, you'll see the Prefix column with register, and you can use register_path to get /register the path. Now, if we want to change the path to /login, all we have to do is just to:

get '/login', to: 'users#new', as: 'register'

Now our register_path gives us /login. We don't have to change our code in the Rails app at all.

More Routing Conventions

Before we move on, let's take a quick look at a couple variations of what we've already seen, as well as some other features available to us in Rails routing:

### config/routes.rb ###

Rails.application.routes.draw do

# pointing our homepage (root path) to posts#index
  root to: 'posts#index'

# `match` & `via:` allow us to define one route and use several HTTP verbs
# `as:` lets us define the name of the route prefix

  match '/authors/:id' => 'authors#update',
    via: [:put, :patch],
    as:   :update_author
# update_author  PUT|PATCH /authors/:id(.:format)  authors#update

  resources :posts do
# define extra params to pass for requests to a route
    get 'popular', on: :collection, action: :index, popular: true
# popular_posts  GET /posts/popular(.:format)  posts#index {:popular=>true}

    get 'preview', on: :member

    # ...
  end

# we can even use routes to redirect
  get '/home', to: redirect('/')
end

Let's break down all the features listed above one at a time.

  • root: root is used to specify what action maps to "/"(the top-level URL of our site which has no path). The root URL of your website/app is the most commonly used, because of this root should be specified at the top of the routes file.

  • match + via: match is used to match URL patterns to one or more routes. We can also specify which HTTP verb may be used for matching to a URL with this pattern. We do this by passing in a hash to the match method. The key for this hash is via, and the value is an array of HTTP verbs. match is a more general purpose form of some more commonly used HTTP routing methods, such as get, post, and delete. It may take all of the same options as those methods, but, compared to those methods, it gives a bit more flexibility.
    For instance, by using match we may specify a URL that matches for two different routes, each corresponding to two different HTTP verbs. Normally, doing this would require two commands, instead of one. We can see this from our example above, where we match the URL pattern '/authors/:id' to the action 'authors#update'. We then specify what HTTP verbs may be used to issue the request for that URL with via: [:put, :patch]. Always specify an HTTP method when using match, not doing so may have negative implications on your application's security.
    match presents another option for routing to certain actions in Rails. In general, it is best to stick with the more commonly used HttpHelpers methods, such as get and post.

  • as: We mentioned a bit earlier that the option as: may be used with our route declaration to change the prefix for our URL helpers. This can be used in various ways. We can use as: to make our URL helpers better match custom URLs. Another use is to change the current URL path helper to something more intuitive or something that matches up better with the resources within an application.

  • collection route: See how above we nest our /popular route under the posts resource. We then use on: :collection to specify what part of a resource we are nesting this route under. We have many posts so that is considered a collection. By specifying on: :collection we are saying, "Match a URL with path /posts/popular." If we didn't add on: :collection, Rails will assume that this route corresponds to another resource associated with a single member of our posts collection. In that case our route would become /posts/:id/popular. We can also specify multiple collection routes by using a block format.

collection do
  get 'popular'
end
  • passing an extra parameter: We can specify a default parameter that always gets passed in with our params hash when we are matched with certain URLs. There are two ways to specify this; one way is the same way we do above:
get 'popular', on: :collection, action: :index, popular: true

popular: true will be passed to our action that the popular route matches with via the params hash. It will have a key of :popular and a value of true. We can also use more explicit syntax by passing a hash to our route method get, where the key is defaults: and the value is another hash containing the name of the parameter we want to pass to our action.

get 'popular', on: :collection, action: :index, defaults: { popular: true}
  • member route: We can use on: member to specify that our route matches a member of a collection. If we use this the dynamic segment will show up as :id. A URL helper will also be created as preview_post. By specifying this route with on: member, we are telling Rails that this is a member of this particular resource. That it isn't just another resource nested under posts, but more closely linked or related to our posts in this application. Multiple member routes may be defined using a block format; this format uses the same syntax as the block format for defining multiple collection routes, just replace collection with member.

  • redirect: There is one other concept to talk about that is displayed in our example above, and that is redirection. Rails gives us the capability to redirect from one path to another by using a redirect helper in conjunction with a route. get '/home', to: redirect('/'). If someone tries to access the path /home, they will immediately be redirected to the root path /. This isn't restricted to just one URL path of course; we could also have something like this: get '/home', to: redirect('/travel').

Review

Rails routes stand in the front of an application. A route interprets an incoming HTTP request and:

  • matches a request to a controller action based on the combination of a HTTP verb and the request URL pattern
  • captures data in the URL to be available in params in controller actions
  • Rails encourages developers to use RESTful URL patterns when setting up routes, with conceptualization of manipulating resources with HTTP verbs.
  • You can use the resources macro to generate RESTful routes very quickly.