Learn Rails - Nested Resources

Question Click to View Answer

In this quiz, we will make comments as a nested resource of posts. We will use scaffolding to build posts, but will build comments from scratch. Start by generating a new rails project.

$ rails new nested_resources

Generate the Post model with scaffolding and include title and body attributes.

$ rails generate scaffold Post title:string body:text

Create the posts table in the database.

$ rake db:migrate

Generate an empty migration to create the comments table.

$ rails generate migration CreateComments

Update the comment migration file, so the comments table will have body and post_id attributes.

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.text :body
      t.integer :post_id
      t.timestamps
    end
  end
end

Create the comments table in the database.

$ rake db:migrate

Create the comment model.

Make the comment.rb file in the models directory (the rest of this quiz specifies the file path and it is implied the file should be created if it does not already exist) and add the following code:

class Comment < ActiveRecord::Base
  attr_accessible :body
end

Update the routes file to make comments a nested resource of posts.

# config/routes.rb:
resources :posts do
  resources :comments
end

View the routes that are created by nesting resources.

$ rake routes

Notice that the path for comments will have the post_id in the URL. This will come in handy later.

Associate the posts and comments models.

# models/post.rb:
has_many :comments

# models/comment.rb:
belongs_to :post

Create an empty comments controller.

# controllers/comments_controller.rb:
class CommentsController < ApplicationController
end

Create a form on the posts#show page to make comments on that particular post. Update the controller code so comments can be saved to the database.

# controllers/comments_controller.rb:
def create
  @post = Post.find(params[:post_id])
  @comment = @post.comments.build(params[:comment])
  @comment.save
  redirect_to @post
end

# controllers/posts_controller.rb
def show
  @post = Post.find(params[:id])
  @comment = Comment.new
end

# views/posts/show.html.erb:
<%= render 'comments/form' %>

# views/comments/_form.html.erb:
<%= form_for [@post, @comment] do |f| %>
  <%= f.label :body %><br>
  <%= f.text_area :body %><br>
  <%= f.submit %>
<% end %>

Make a comment on a post. Open the database in terminal and write an SQL query to list all the comments.

$ rails db
select * from comments;

Notice that the post_id is populated in the database even though we did not have a post_id field in the comments form. How did the comment know what post it is associated with?

The post_id is added with this line in the comments controller:

@comment = @post.comments.build(params[:comment])

Create a list of all the comments on the posts/show.html.erb page.

<ul>
<% @post.comments.each do |comment| %>
  <li><%= comment.body %></li>
<% end %>
</ul>

Add links to edit a comment on the posts#show page and include the code required to perform this action.

# controller/comments_controller.rb:
def edit
  @post = Post.find(params[:post_id])
  @comment = @post.comments.find(params[:id])
 end 

def update
  @post = Post.find(params[:post_id])
  @comment = @post.comments.find(params[:id]) 
  if @comment.update_attributes(params[:comment])
    redirect_to post_path(@post)
  else
    render :action => :edit
  end   
end 

# views/comments/edit.html.erb:
<%= render "form" %>

# posts/show.html.erb:
<ul>
<% @post.comments.each do |comment| %>
  <li><%= comment.body %></li>  
  <%= link_to "Edit", edit_post_comment_path(:post_id => @post.id, :id => comment.id) %>
<% end %>
</ul>

Add links to delete a comment on the posts#show page and include the code required to perform this action.

# controller/comments_controller.rb:
def destroy
  @post = Post.find(params[:post_id])
  @comment = @post.comments.find(params[:id]) 
  @comment.destroy
  redirect_to post_path(:id => @post.id)
end

# views/posts/show.html.erb:
<ul>
<% @post.comments.each do |comment| %>
  <li><%= comment.body %></li>  
  <%= link_to "Edit", edit_post_comment_path(:post_id => @post.id, :id => comment.id) %> 
  <%= link_to "Delete", post_comment_path(:post_id => @post.id, :id => comment.id), :method => :delete %>
<% end %>
</ul>