Question | Click to View Answer |
In this test we will created nested comments using polymorphism. There will be an articles model that has many comments and comments can be responded to by other users and should be displayed in a nested format (similar to Reddit or Hacker News). Start by creating a new rails project called nested_comments_polymorphism. |
$ rails new nested_comments_polymorphism
|
Create the Article MVC with scaffolding. The Article resource should have body and title attributes. |
$ rails generate scaffold Article title:string body:text
|
Create the Comment MVC template with the generate resource command. The comment resource should have a body attribute. |
$ rails generate resource Comment body:text
|
Create the articles and comments tables in the database. |
$ rake db:migrate
|
Update the routes so that comments can be made on articles and other comments (we will add a reply link to comments, so comments can be made on other comments). |
config/routes.rb
root :to => 'articles#index'
resources :articles do
resources :comments
end
resources :comments do
resources :comments
end
|
View the routes that this routes.rb file generates. |
$ rake routes
|
Update the comments table so it will support a polymorphic relationship. |
$ rails generate migration AddColumnsToComments
# Update the migration with the following changes:
class AddColumnsToComments < ActiveRecord::Migration
def change
add_column :comments, :commentable_id, :integer
add_column :comments, :commentable_type, :string
end
end
|
Update the Article model, so it supports a polymorphic relationship. |
class Article < ActiveRecord::Base
attr_accessible :body, :title
has_many :comments, :as => :commentable, :dependent => :destroy
end
|
Update the Comment model, so it supports a polymorphic relationship. |
class Comment < ActiveRecord::Base
attr_accessible :body
belongs_to :commentable, :polymorphic => true
has_many :comments, :as => :commentable
end
|
Put a form on the article#show page to make new comments. |
# views/articles/show.html.erb
<%= render 'comments/form' %>
# views/comments/_form.html.erb
<%= form_for [@article, @comment = Comment.new] do |f| %>
<%= f.text_area :body %> <br>
<%= f.submit %>
<% end %>
comments_controller.rb
def create
@parent = Article.find(params[:article_id]) if params[:article_id]
@parent = Comment.find(params[:comment_id]) if params[:comment_id]
@comment = @parent.comments.new(params[:comment])
if @comment.save
redirect_to article_path(params[:article_id])
else
render 'new'
end
end
The create action determines if the parent for a given comment is an article or another comment. The @comment = @parent.comments.new(params[:comment]) associates the comment with its parent. |
Display all comments on the articles#show page. |
# views/articles/show.html.erb
<%= render @article.comments %>
# views/comments/_comment.html.erb
<%= comment.body %><br />
The render @article.comments in the show page automatically knows to load a file called views/comments/_comment.html.erb. This is Rails magic, so just memorize this. |
Make some comments in the form on the articles#show page and see how they are stored in the database. |
$ rails db
>> select * from comments;
Notice that all of the comments have a commentable_type of Article. All the comments currently have an Article parent because we have not added the functionality to reply to a comment and have a Comment as the parent - we will add this function later. |
After each comment on the articles#show page, add a reply link to create a new child comment. |
# views/articles/show.html.erb
<%= render 'comments/form' %>
# views/comments/_form.html.erb
<%= form_for [@article, @comment = Comment.new] do |f| %>
<%= f.text_area :body %> <br>
<%= f.submit %>
<% end %>
# comments_controller.rb
def new
@parent_comment = Comment.find(params[:comment_id])
@comment = @parent_comment.comments.new
end
|
Show all the nested comments with the child comments more indented than the parent comments. |
# comments/_comment.html.erb
<div class="comment"> <%= render :partial => comment.comments %></div>
# stylesheets/comments.css.scss
.comment {
margin-left: 30px;
}
|
Add a link to delete comments and all comments nested under that comment. |
# views/comments/_comment.html.erb
<%= link_to "Destroy", article_comment_path(comment.commentable, comment), :method => :delete %>
# comments_controller.rb
def destroy
@comment = Comment.find(params[:id])
@comment.destroy
redirect_to articles_path
end
|