- 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:
- Subdomain =>
rails
- Trail =>
ruby-gem-walkthroughs
- Campsite =>
api-management-gems
- 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 oftopics
and to look inside of this blockresources :posts
- this calls the normalresources
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:
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.