Nesting Resources in a Ruby on Rails Application
Learn how to nest the post resource under the topic resource to properly organize the Ruby on Rails application.
Guide Tasks
  • Read Tutorial

Integrating nesting in a Ruby on Rails application may need difficult at first, however I think you'll see that the implementation is straightforward if you follow a standard set of processes. Let's review what type of behavior we want from this feature. If we didn't use nesting we'd have routes that looked something like:

Topic

/topics/sports

Post

/posts/123

We want to have a route like this to get to a post:

/topics/sports/posts/123

This is better for search engine optimization since Google looks for keywords inside of the URL. You may notice that we've implemented this type of nesting for DevCamp, below is a sample URL:

http://rails.devcamp.com/trails/ruby-gem-walkthroughs/campsites/api-management-gems/guides/dish-gem

Notice the nesting pattern:

  1. Subdomain => rails
  2. Trail => ruby-gem-walkthroughs
  3. Campsite => api-management-gems
  4. Guide => dish-gem

Having this level of nesting is quite a bit and not recommended for typical applications. Making the decision lead to numerous debates between the development team since you usually only want to nest 1-2 resources at the max. However it was decided it was important for the flow of the application.

Now that you know what we're looking to do, let's get it live in the app.

Implementing Nesting

You should still be on the add-posts git branch, we're still working on posts so we're going to stay on it for the next few lessons. You can quickly find out what branch you're on by typing git branch into the terminal.

As you probably expect we're going to start off by writing some tests. When implementing nested routes having a little experience helps since you're more used to the standardized routing conventions that come with Rails. For example the default route helper for our Topic's index action is:

topics_path

Nested routes follow a similar convention, when we nest posts inside of topics, the route helper should be:

topic_posts_path(topic_id: <topic_id>)

As you probably noticed we need to pass an argument to the route helper method. This makes sense since we need to let the method know which set of posts we want returned. Let's take this step by step, first create a post_spec.rb file in the spec/features directory, and then we'll create a very basic spec to implement the route properly.

# spec/features/post_spec.rb

require 'rails_helper'

describe 'post' do
  before do
    @topic = Topic.create(title: "Sports")
  end

  describe 'nested route' do
    it 'has an index page properly nested under a topic' do
      visit topic_posts_path(topic_id: @topic)
      expect(page.status_code).to eq(200)
    end
  end
end

This spec is creating a topic before each spec since the majority of the tests will need a topic to reference and then it looks for a 200 status code for our nested route. This will fail, let's update the route file:

# config/routes.rb

Rails.application.routes.draw do
  resources :topics do
    scope module: :topics do
      resources :posts
    end
  end
  devise_for :users, controllers: { registrations: 'registrations' }
  root to: 'static#home'
end

I like the Rails syntax here since it's a natural fit for what we're wanting to do, let's go though it line by line:

Inside of the resources :topics do block it first calls:

  • scope module: :topics do - this let's the application know that we're setting up a custom scope inside of topics and to look inside of this block

  • resources :posts - this calls the normal resources method and will return the same available routes, the only difference is now they will be inside of the /topics route path

If you run the specs you'll get a failure saying ActionController::RoutingError: uninitialized constant Topics. This is because since we're performing a full nesting feature we need to also nest our PostsController and our views. Let's do both of those things, run the following commands:

Create a topics directory for the nested controller

mkdir app/controllers/topics

Move the PostsController into the new directory

mv app/controllers/posts_controller.rb app/controllers/topics/posts_controller.rb

Create a posts directory for the nested views

mkdir app/views/topics/posts

Delete the /posts view directory from the root views' directory (be careful anytime you use rf -rf since you could easily delete the entire project if you don't pass the directory you're wanting to remove!)

rm -rf app/views/posts/

This will give us a file structure that looks like this:

large

There's only one remaining item we need to do to implement nesting, update the class name of the PostsController like below:

# app/controllers/topics/posts_controller.rb

class Topics::PostsController < ApplicationController
end

Now if you run the specs you'll see we get the expected error that it can't find the index action, this is good, it means that our nesting implementation is working properly. Let's implement the index code and we'll be ready to implement the rest of the Post feature:

Create an index method in the PostsController

# app/controllers/topics/posts_controller.rb

class Topics::PostsController < ApplicationController
  def index
  end
end

And then create the view template file:

touch app/views/topics/posts/index.html.erb

Running rspec spec/features/post_spec.rb will show that the feature is working, nice work! However we're not quite done. If you run rspec you'll see that we're getting an error uninitialized constant PostsController (NameError), this is because the resource generator created an empty controller spec and now it's failing because it's looking for a non-nested PostsController. To get this working we can either delete the spec since we're not going to be using the controller spec for this app, or we can fix it. Let's fix it:

# spec/controllers/posts_controller_spec.rb

require 'rails_helper'

RSpec.describe Topics::PostsController, type: :controller do

end

I don't like deleting empty spec files until a project is completed since I may eventually want to implement some specialized tests in them, so that's why I prefer to fix issues like this instead of simply removing the file.

Good job on this lesson, you now know how to implement nesting in a Rails application and create a basic spec for it. In the next few lessons we'll go through how to build out the full post feature.

Resources