Master Routing in Rails [Practical]

Do not miss this exclusive book on Binary Tree Problems. Get it now for free.

In Ruby on Rail, routing is how an application handles HTTP requests and ultimately decides what will be provided to the user through controller actions. In this article, we will go through some routing conventions to follow when building a route.

We will create a project that shows a list of posts, and each post has a link to pdf document and an image which users can view.

Setup Sample Project

First thing we will need to do is to store images in app/assets/images and pdf documents in app/assets/docs The next thing we will do is to create post model and post_file model. Post model will contain the articles for a post and PostFile model will contain the images and pdf docs for the article at OpenGenus.

rails g model post article:text

The model will generate a migration that creates a table posts with article as column for the table.

rails g model PostFile image:text pdf:text

The model will generate a migration that creates a table post_files with image and pdf as columns for the table. Let's add a foreign key for the table;

class CreatePostFiles < ActiveRecord::Migration[7.0]
  def change
    create_table :post_files do |t|
      t.text :image
      t.text :pdf
      t.references :posts, null: false, foreign_key: true
   
      t.timestamps
    end
  end
end

Lets now run the migration so that our tables are created in the db/schema.rb file.

rails db:migrate

Once our table is created we wil now insert data to the tables using rails console, run the following command to open your console;

rails console

The above command will give you a ruby console which we will use to insert data to our table;

 Post.create([{article: "This is my First Post"},{article: "Tis is my second post"},{article: "This is my third post"}])

The code above will insert 3 rows in our table posts. let's insert image and pdf name in our post_files table;

 PostFile.create([{image: "sports.png", pdf: "sports.pdf", posts_id: 1},{image: "dancer.png", pdf: "dancer.pdf", posts_id: 2},{image: "hospital.png", pdf: "hospital.pdf", posts_id: 3}])

Then we will need to create controllers and views that we will use for this project. let's create the controllers;

rails g controller posts index show
rails g controller post_files index show

Creating RESTful Resources

There are two ways of creating routes; explicitly creating each route or using resources.

Routes file

Routing file is located in the config/routes.rb file.

Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Defines the root path route ("/")
  # root "articles#index"
end

Routes declared inside this file will be used to match against specific controller actions.

The root route

When a user first visits an application, they need to be taken to a default page which is the root page. To do this we simply need to declare the root of our application:

Rails.application.routes.draw do
# Defines the root path route ("/")
root to: 'posts#index'
end

Let's talk about the syntax on the route. We use root to: to declare that the following route is the root of our application. Next we have "posts#index", this specific syntax helps us understand what the action is and the specific controller being called. For this route we are calling the posts controller and the index action inside that controller. A great way to visualize this syntax is like so: "controller#action".

Explicitly creating routes

In our project's routes.rb we should have the following;

Rails.application.routes.draw do
root to: 'posts#index'
  get 'posts/index'
  get "/posts/:id", to: "posts#show", as: 'post_show'
end

The code provided above sets up three routes in a Rails application. The first route maps the root URL of the application (/) to the index action of the PostsController, meaning that when a user visits the root URL, they will see a list of all posts. The second route maps the GET request for /posts/index to the same index action of the PostsController, allowing users to access the same list of posts by visiting that URL directly. The third route maps the GET request for /posts/:id to the show action of the PostsController, where :id is a dynamic segment that represents the id of a specific post.

This route generates a named route with the prefix post_show, which can be used in the application's code to generate URLs for individual posts. For example, calling post_show_path(1) would generate the URL /posts/1, which would display the details of the post with an id of 1.

Nested Routes

in your routes.rb file we will add routes for post_files;

Rails.application.routes.draw do
  root to: 'posts#index'
  get 'posts/index'
  get "/posts/:id", to: "posts#show", as: 'post_show'
  
  get 'posts/:post_id/post_files', to: 'post_files#index', as: 'post_files'
  get 'posts/:post_id/post_files/:id', to: 'post_files#show', as: 'post_file'
end

Focus on the last two code of line we just added to route our post_files
The code sets up two nested routes in our application. The first route maps the GET request for /posts/:post_id/post_files to the index action of the PostFilesController, where :post_id is a dynamic segment that represents the id of a specific post. This means that when a user visits a URL like /posts/1/post_files, they will see a list of all files associated with the post that has an id of 1. The second route maps the GET request for /posts/:post_id/post_files/:id to the show action of the PostFilesController, where both :post_id and :id are dynamic segments that represent the id of a specific post and file, respectively. This means that when a user visits a URL like /posts/1/post_files/2, they will see the details of the file with an id of 2 that is associated with the post that has an id of 1. Both routes generate named routes with the prefixes post_files and post_file, respectively, which can be used in the application's code to generate URLs for these nested resources. For example, calling post_files_path(1) would generate the URL /posts/1/post_files, and calling post_file_path(1, 2) would generate the URL /posts/1/post_files/2.

Using Resources to create routes

Rails provides us with route helpers that can help us avoid explicitly declaring the routes like we did above.

resources :posts

The above line of code does exactly the same thing as our previous routes and is the most common way to buld routes in rails. resources is a method provided by Rails that builds the seven necessary routes to perform our CRUD actions on our post resource. If you want to checkout all the routes that you have, on the terminal;

rails routes

Rails will print out all the routes as shown below;

edit_post   GET       /posts/:id/edit(.:format)    posts#edit
            PATCH     /posts/:id(.:format)         posts#update
            PUT       /posts/:id(.:format)         posts#update
            DELETE    /posts/:id(.:format)         posts#update
new_post    GET       /post/new                    posts#new
            POST      /post(.:format)              posts#create
posts       GET       /post(.:format)              posts#index
posts       GET       /post/:id(.:format)          posts#show

Specify the routes needed in resources

We can now specify the paths we need in our project as we only use show and index action and we are also going to make our routes nested for use in our project. Replace your explicit routes for resources as shown in the code below as it is the most common way to declare routes. We will use the resources URL path in this project so take note of that.

Rails.application.routes.draw do
root to: 'posts#index'
  
 resources :posts, only: [:index, :show] do
   resources :post_files, only: [:index, :show]
 end
end   

This code sets up nested resources for posts and post_files, with only the index and show actions defined for each resource. The only option limits the generated routes to only the specified actions. This means that the application will only respond to requests for the index and show actions of both posts and post_files. Any other action (such as new, create, edit, update, or destroy) will return a 404 Not Found error.

Go ahead and run the command rails routes on your terminal, it will give you a list of paths but we are only intrested in this;

 Prefix Verb   URI Pattern
            Controller#Action
 root GET    /
            posts#index
post_post_files GET    /posts/:post_id/post_files(.:format)
            post_files#index
post_post_file GET    /posts/:post_id/post_files/:id(.:format)        
           post_files#show
posts GET    /posts(.:format)
           posts#index
post GET    /posts/:id(.:format)
          posts#show

I need you to take note of the URL for resources routes, it is generated automatically by resources and not explicitly defined as in the explicit routes.

Edit the posts controller file;

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def show
    @post = Post.find(params[:id])
  end
end

Edit controllers/post_files_controller.rb

class PostFilesController < ApplicationController
  def index
    @post_files = PostFile.find(params[:post_id])
    @post = Post.find(params[:post_id])
  end

  def show
    pdf = find_pdf(params[:id])
    send_file pdf, type: 'application/pdf', disposition: 'inline'
  end
  
  private
  
  def find_pdf(id)
    Rails.root.join('app', 'assets','docs', "#{id}.pdf")
  end
end

Next open views/posts/index.html.rb;

<% @posts.each do |post|%>
<h3><%= link_to post.article, post_path(post.id) %></h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt 
   ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco 
</p>
<% end %>

We loop through the Posts variable in order to get individual post. From the code you can see we have link_to post.article, post_path(post.id)
The "post.article" specifies the text that will be displayed as the hyperlink, while "post_path(post.id)" is a Rails helper method that generates the URL for the specific post. The "post.id" specifies the unique identifier for the post, which is used to generate the URL. Run your server;

Next let's make some changes in our views/posts/show.html.erb;

<h1><%= @post.article %></h1>
<%= @post.id %>

<p><%= link_to 'View Files', post_post_files_path(@post.id) %> </p>

If you click on "This is my first post" on the index page it should take you to the /posts/1, it will display the contents of the show action;

Let's also make changes to our views/post_files/index.html.erb

<h1>Post Files</h1>

 <%= image_tag @post_files.image, alt: "user profile" %>

<p><%= link_to 'View pdf file', post_post_file_path(@post.id, @post_files.pdf)  %></p>

Refresh your page then click on the View Files link, it should take you to /posts/1/post_files which is the index action of PostFilesController ;

It will display an image and a link to open the pdf file.

If you click View pdf file it should open your pdf file on this path /posts/1/post_files/sports.pdf

Conclusion

Always use resources when working with routes as it is the most common used and also easy to setup.I hope this helped make rails routes syntax a bit clearer.

Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.