Ruby on Rails 5 Nested comments not being created - ruby-on-rails

I'm a beginner in rails and trying to create a Reddit type blog page. Currently, I'm working on nested comments so the user is able to reply to comments. In my app I have forums, posts and comments. Creating posts and forums has worked perfectly, also posting comments until I tried nesting replies. My code looks like this (Forums and Users models/controllers have been omitted) :
My Comment model:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
belongs_to :user
has_many :comments, as: :commentable
end`
My Post model:
class Post < ApplicationRecord
belongs_to :forum
belongs_to :user
has_many :comments, as: :commentable, dependent: :destroy
end
My posts_controller:
class PostsController < ApplicationController
def index
#posts = Post.all
end
def show
#post = find_post
end
def new
#post = Post.new
end
def edit
#post = find_post
end
def create
#forum = Forum.find(params[:forum_id])
#post = #forum.posts.create(post_params.merge(user_id: current_user.id))
redirect_to forum_path(#forum)
end
def update
#post = find_post
if #post.update(post_params)
redirect_to forum_path
else
render 'show'
end
end
def destroy
#post = find_post
#post.destroy
redirect_to forum_path
end
private
def post_params
params.require(:post).permit(:title, :content)
end
private
def find_post
#forum = Forum.find(params[:forum_id])
#forum.posts.find(params[:id])
end
end
My comments_controller:
class CommentsController < ApplicationController
before_action :find_commentable
def index
#comments = Comment.all
end
def new
#comment = Comment.new
end
def create
#comment = #commentable.comments.create(comment_params.merge(user_id: current_user.id))
if #comment.save
#post = Post.find_by_id(params[:post_id])
redirect_to forum_post_path(#post.forum_id, #post), notice: 'Your comment was successfully posted!'
else
redirect_to forum_post_path(#post.forum_id, #post), notice: 'error :('
end
end
private
def comment_params
params.require(:comment).permit(:content)
end
private
def find_commentable
#commentable = Comment.find_by_id(params[:comment_id]) if params[:comment_id]
#commentable = Post.find_by_id(params[:post_id]) if params[:post_id]
end
end
My views/comments/_comment:
<li>
<%= comment.content %>
<small>Submitted <%= time_ago_in_words(comment.created_at) %> ago</small>
<h2>Add a Reply:</h2>
<%= form_with(model: [ comment, comment.comments.build ], local: true) do |form| %>
<p>
<%= form.text_area :content, placeholder: "Add a Reply" %><br/>
<%= form.submit %>
</p>
<% end %>
<ul>
<%= render partial: 'comments/comment', collection: comment.comments if comment.comments.any? %>
</ul>
<%= link_to 'Back', forum_post_path(#forum, #post) %>
My views/post/show:
<small>Submitted <%= time_ago_in_words(#post.created_at) %> ago</small>
<h3>Comments</h3>
<ul>
<%= render(partial: 'comments/comment', collection: #post.comments) if #post.comments.any?%>
</ul>
<h2>Add a comment:</h2>
<%= form_with(model: [ #post, #post.comments.build ], local: true) do |form| %>
<%= form.text_field :content, placeholder: "Add a comment" %><br/>
<%= form.submit "Add Comment" %>
<% end %>
<%= link_to 'Back', forum_path(#forum) %>
<%= link_to 'All posts', forum_posts_path(#post.forum_id) %>
<%= link_to 'All comments', post_comments_path(#post) %>
</li>
My routes:
resources :posts do
resources :comments
end
resources :comments do
resources :comments
end
The first errors I kept getting were that every time I submitted a comment in view/posts/show the comment wouldn't create. Now, views/comments/_comment lifts the error:
Showing /example/app/views/comments/_comment.html.erb where line #3 raised:
nil can't be converted to a Time value
<small>Submitted <%= time_ago_in_words(comment.created_at) %> ago</small>
after I submit a new comment. Any help regarding this error is appreciated or related to my main goal, I have been circling around this for a long time. Thanks!

The problem here is that new comment is initialized(for the comment which is being rendered) every time the partial is trying to render the comment.
Check out the following byebug output:
(byebug) pp comment.comments
[#<Comment:0x007f35ce11d900
id: 5,
content: "DEF",
commentable_id: 4,
commentable_type: "Comment",
user_id: 1,
created_at: Wed, 30 May 2018 06:53:51 UTC +00:00,
updated_at: Wed, 30 May 2018 06:53:51 UTC +00:00>,
#<Comment:0x007f35ce2a3388
id: nil,
content: nil,
commentable_id: 4,
commentable_type: "Comment",
user_id: nil,
created_at: nil,
updated_at: nil>]
This line is the culprit:
<%= form_with(model: [ comment, comment.comments.build ], local: true) do |form| %>
comment.comments.build initializes a new empty Comment which is your code then tries to render and throws the error as it does not have created_at.
And then the newly initialized comment is being rendered by the code.
Replacing app/views/comments/_comment#13 with the following will resolve the issue:
<%= render(partial: 'comments/comment', collection: comment.comments.where.not(content: nil)) if comment.comments.any? %>

Related

Creating a comment reply won't work while comment works Rails 7

I am trying to allow reply to comments and for some reason when I create a comment it works uneventfully but when I reply the "body" on params comes back empty. The weird part is that I am using the same form. Please take a look:
Note: I am using Action Text rich_text_area, if I use a simple text_area it works just fine.
models/comment.rb
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
belongs_to :parent, class_name: "Comment", optional: true
has_many :comments, class_name: "Comment", foreign_key: :parent_id
has_rich_text :body
validates_presence_of :body
end
routes.rb
resources :posts do
resources :comments, only: [:create, :update, :destroy]
end
comments_controller.rb
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(comment_params)
#comment.user = current_user
respond_to do |format|
format.html do
if #comment.save
flash[:success] = "Comment was successfully created."
else
flash[:danger] = #comment.errors.full_messages.to_sentence
end
redirect_to post_url(#post)
end
end
end
private
def comment_params
params.require(:comment).permit(:body, :parent_id)
end
_comment.rb
// this works (creating a comment without parent)
= render "comments/form", post: #post, comment: #post.comments.build, submit_label: "Comment"
// this won't work (creating a comment with a parent being the current comment)
= render "comments/form", post: #post, comment: #post.comments.build, parent: comment, submit_label: "Reply"
- _form.rb
<%= form_with model: [post, comment], class: "form" do |f| %>
<% if !parent.nil? %>
<%= f.hidden_field :parent_id, value: parent.id %>
<% end %>
<div class="field">
<%= f.rich_text_area :body, placeholder: "What do you think?" %>
</div>
<div class="actions">
<%= f.submit submit_label, class: "btn btn-primary" %>
</div>
<% end %>
What puzzles me the most is that the problem is that the body comes back empty. I cant't see the relation between the body and everything else.
Parameters: {"authenticity_token"=>"[FILTERED]", "comment"=>{"body"=>"", "parent_id"=>"14"}, "commit"=>"Reply", "post_id"=>"4"}
Thanks in advance!

How do I get a destroy method to work for nested resources in Rails?

Currently learning Ruby on Rails and creating a simple blog app with comments. I have a Comment model and an Article model. Comment is polymorphic and both models have many comments.
I'm trying to come up with a destroy method that's able to delete both the comments that belong to Comment and the ones that belong to Article (and that remain as [deleted] without destroying their children, much like in Reddit, although I haven't even gotten to that part).
I have tried different paths but I haven't got it right yet. Nested paths still confuse me a little and I'm not sure on how to pass the params that the path requests when creating the link_to.
These are my files:
routes.rb:
Rails.application.routes.draw do
get 'comments/new'
get 'comments/create'
get 'articles/index'
get 'articles/show'
root 'articles#index'
resources :articles do
resources :comments
end
resources :comments do
resources :comments
end
end
article.rb:
class Article < ApplicationRecord
has_many :comments, as: :commentable
end
comment.rb:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: :true
has_many :comments, as: :commentable
end
comments_controller.rb:
class CommentsController < ApplicationController
before_action :find_commentable
def new
#comment = Comment.new
end
def create
#comment = #commentable.comments.new(comment_params)
if #comment.save
redirect_back(fallback_location: root_path)
else
redirect_back(fallback_location: root_path)
end
end
def destroy
#comment = #commentable.comments.find(params[:id])
#comment.destroy
redirect_back(fallback_location: root_path)
end
private
def comment_params
params.require(:comment).permit(:body)
end
def find_commentable
if params[:article_id]
#commentable = Article.find_by_id(params[:article_id])
elsif params[:comment_id]
#commentable = Comment.find_by_id(params[:comment_id])
end
end
end
show.html.erb, where the form for commments that belong to Article.rb is:
<h1> <%= #article.title %> </h1>
<p> <%= #article.body %> </p>
<small>Submitted <%= time_ago_in_words(#article.created_at) %> ago </small> <br/>
<h3>Comments</h3>
<%= form_for [#article, Comment.new] do |f| %>
<%= f.text_area :body, placeholder: "Say something!" %> <br/>
<%= f.submit "Submit" %>
<% end %>
<ul class="parent-comment">
<%= render partial: 'comments/comment', collection: #article.comments %>
</ul>
<%= link_to "Index", articles_path %>
And the partial _comment.html.erb , which displays the comments that belong to the article as well as those that belong to other comments, and where I'm trying to integrate the link_to:
<p> <%= comment.body %> </p>
<small>Submitted <%= time_ago_in_words(comment.created_at) %> ago </small> <br/>
<%= form_for [comment, Comment.new] do |f| %>
<%= f.text_area :body, placeholder: "Add a reply!" %><br/>
<%= f.submit "Reply" %>
<%= link_to "Delete", comment_path(comment), method: :delete %>
<% end %>
<ul>
<%= render partial: 'comments/comment', collection: comment.comments %>
</ul>
Whenever I do seem to get the path right, NoMethodError in CommentsController#destroy — undefined method `comments' for nil:NilClass comes up. Why would the controller show it as undefined? It worked in the new method, as far as I can see.
Could you give some guidance as to what I should do or what I should fix? I'm not sure how to delete the parent comments, either, and I haven't managed to find information that suits this case. If you know where to point me to, I'm all eyes.
Thank you.
Because of your design model structure.
Your view
<%= link_to "Delete", comment_path(comment), method: :delete %>
Your find_commentable
elsif params[:comment_id]
#commentable = Comment.find_by_id(params[:comment_id])
end
#commentable will be a Comment class, so it won't have .comments methods as your Article class
check carefully to destroy the method
def destroy
#comment = #commentable.comments.find(params[:id])
#comment.destroy
redirect_back(fallback_location: root_path)
end
use #comment = #commentable.comments.find_by(id: params[:id]) and check whether #comment has some value or not?
just add one condition like this and it won't throw the error:
#comment.destroy if #comment
if #comment is nil and trying to destroy then it will throw the error.

Ruby on Rails blog and adding comments to posts and editing and deleting comments

I'm working on a ROR blog and have encountered some issues along the way. I'm currently learning Rails and just feel completely lost with connecting all the pieces. I've been working on my comments section for days and was finally able to create comments on posts, but I can't edit or delete them. I also referenced the SO questions below but am still running into problems.
Add Comment to User and Post models (Ruby on Rails)
Here's my layout:
Comment model params:
body \ user_id \ post_id
Model associations:
user.rb
has_many :posts
has_many :comments
post.rb
belongs_to :user
has_many :comments
comment.rb
belongs_to :user
belongs_to :post
routes.rb:
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
get '/' => 'users#index'
get '/posts' => 'posts#index'
post '/posts/create' => 'posts#new'
post '/posts/edit' => 'posts#edit'
get '/signin' => 'sessions#new', as: :new_session
post '/create-session' => 'sessions#create', as: :create_session
get 'signout' => 'sessions#destroy', as: :destroy_session
resources :users
resources :posts
resources :comments
end
comments controller:
class CommentsController < ApplicationController
def index
#comment = Comment.all
end
def new
user = session[:user_id]
#comment = Comment.new(post_id: params[:post_id])
#post = Post.find(params[:post_id])
end
def create
#comment = Comment.new(comment_params)
#comment.user_id = session[:user_id]
#postid = params[:id]
if #comment.save
flash[:notice] = "comment created."
redirect_to '/posts'
else
flash[:error] = "Error creating comment."
redirect_to '/posts'
end
end
def edit
#post = Post.find(params[:id])
end
def update
#comment = Comment.find_by_id(params[:id])
#comment.update(comment_params)
flash[:notice] = "Comment updated."
redirect_to '/posts'
end
def destroy
#comment = Comment.find(params[:comment_id])
#comment.destroy
redirect_to '/posts'
end
private
def comment_params
params.require(:comment).permit(:body, :user_id, :post_id)
end
end
Posts show.html.erb page in views/posts folder:
<%# show all posts %>
<div id="single-post">
<h1>User - <%= #post.user.username %></h1>
<h2>Post - <%= #post.body %> </h2>
<%= link_to("Edit Post", edit_post_path(#post)) %>
</br>
<%= link_to("Delete Post", #post, method: 'delete') %>
</br>
<%= link_to("Add Comment", new_comment_path(post_id: #post.id)) %>
<%#<%= link_to("Edit Comment", edit_comment_path(post_id: #post.id, comment_id: #comment.id))%>
</div>
<h3><% #post.comments.reverse.each do |c| %> </h3>
<div id="single-comment">
<h4>Comment</h4>
<h5>From - <%= c.user.username %></h5>
<h6><%= c.body %> </h6>
</br>
<%= link_to("Edit Comment", edit_comment_path(#post.id)) %>
</br>
<%= link_to("Delete Comment", comment_path(#post.id), method: :delete) %>
</div>
<% end %>
</div>
new.html.erb form in views/comments folder
<div id="comment-form">
<%= form_for #comment do |f| %>
<%= f.label :body %>
<%= f.text_area :body, class: "text-area" %>
<%= f.hidden_field :post_id %>
<%= f.submit %>
<% end %>
</div>
Again I can add comments to posts. When I hover over the edit tag on the comment I'm seeing this: localhost:3000/comments/72/edit
I see this error when I click on edit
When I hover over the delete button I see this: localhost:3000/comments/72
I see this error when I click on delete
I'm at the point where I'm completely lost and feel I have tried everything possible but nothing seems to work. Please help! Here's the GitHub repo as well: https://github.com/angelr1076/rails-blog
The First argument in form cannot contain nil or be empty is telling you that #comment in <%= form_for #comment do |f| %> is nil. This is because in the edit action of your CommentsController you are setting #post instead of #comment.
Change this to be:
def edit
#comment = Comment.find(params[:id])
end
For deleting a comment, the Couldn't find Comment without an ID is telling you that the value you're passing to find is nil. This is because you're trying to use params[:comment_id] instead of params[:id]. Change the destroy action to:
def destroy
#comment = Comment.find(params[:id])
#comment.destroy
redirect_to '/posts'
end
Update:
Also as per your code, you should change edit and delete links to below
<%= link_to("Edit Comment", edit_comment_path(c)) %>
<%= link_to("Delete Comment", comment_path(c), method: :delete)
You are passing #post.id which is an id of post. Instead you should pass id of the comment using the block variable from your comments.each, noticing that the .id isn't needed here because it can be inferred by Rails.

How to show user comments in rails

How can I show all comments in the post#show?
comments_controller
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
#post = Post.find(params[:post_id])
#comment.user_id = current_user.id
#comment.username = current_user.username
#comment.post_id=#post.id
#comment.save
redirect_to post_path(#post)
end
def show
#comment = Comment.find(params[:id])
end
def comment_params
params.require(:comment).permit(:text, :username)
end
end
Models:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
#app/models/post.rb
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
the views/posts/show.html.erb
<ul class="img-list-single">
<li>
<a>
<img src=<%= #post.url %>/>
<span class="text-content"><span><%=#post.title%></span></span>
</a>
<%= #comment.username%>
<%if current_user%>
<%= form_for :comment, :url => post_comments_path(#post) do |f|%>
<%= f.text_field :text %>
<%= f.submit%>
<% end %>
<%if current_user.id==#post.user_id%>
<%= button_to 'Delete', #post, method: :delete, :onclick => " returconfirm('Are you sure you want to delete this post?')"%>
<%end%>
<%end%>
First of all fix your posts controller. The show action should look like this:
class PostsController < ApplicationController
def show
#post = Post.find(params[:id])
#comments = #post.comments.includes(:user)
end
end
Now in app/views/posts/show.html.erb, I would render a partial:
<ul>
<%= render #comments %>
</ul>
Rails will look for a partial in app/views/comments/_comment.html.erb. You can just put all your comment view logic in there and will display all the comments:
# app/views/comments/_comment.html.erb
<li>
<%= comment.text %>
<br>
<%= comment.user.try(:username) %>
</li>
Finally, I would fix your create action in the comments controller:
class CommentsController < ApplicationController
def create
# First get the parent post:
#post = Post.find(params[:post_id])
# Then build the associated model through the parent (this will give it the correct post_id)
#comment = #post.comments.build(comment_params)
# Assign the user directly
#comment.user = current_user
if #comment.save
redirect_to post_path(#post)
else
render :new
end
end
end
It seems to me that you don't need to store the username attribute on the comment model, since that should be available through the comment.user association.
in your post#show action simply do:
#post = Post.find(params[:id])
#comments = #post.comments
then in your view add:
<% #comments.each do |comment| %>
<%= comment.text %>
<br />
<% end %>

Rails - How Do I Nest Comments - Polymorphism

For my application, I have Projects. I have used Polymorphism to build a model called "Newcomment" for comments made on these Projects. I followed this railscast. This works great.
But now, I want to build comments on top of comments. I tried following this tutorial (http://kconrails.com/2010/10/23/nested-comments-in-ruby-on-rails-1-models/) and (http://kconrails.com/2011/01/26/nested-comments-in-ruby-on-rails-controllers-and-views/). I put a form for comments in each comment that I render. I also adjusted the newcomment.rb model, so that newcomment has_many newcomments and the routes.rb file.
Question: Right now, when I make a comment in the form of each comment, it posts as a comment to the project and not as a response to a specific comment. How would I adjust my code so that I can have comments for comments?
newcomment.rb
class Newcomment < ActiveRecord::Base
attr_accessible :content, :user_id
belongs_to :commentable, polymorphic: true
has_many :newcomments, :as => :commentable
belongs_to :user
scope :newest, order("created_at desc")
validates :content, presence: true
end
newcomments_controller.rb
class NewcommentsController < ApplicationController
before_filter :load_commentable
before_filter :authenticate_user!
def create
#newcomment = #commentable.newcomments.new(params[:newcomment])
if #newcomment.save
redirect_to comments_project_path(#commentable), notice: "Comment created."
else
render :new
end
end
def destroy
if current_user.try(:admin?)
#newcomment = Newcomment.find(params[:id])
#commentable = #newcomment.commentable
#newcomment.destroy
if #newcomment.destroy
redirect_to comments_url, notice: "Comment deleted."
end
else
#newcomment = Newcomment.find(params[:id])
#commentable = #newcomment.commentable
#newcomment.destroy
if #newcomment.destroy
redirect_to comments_project_path(#commentable), notice: "Comment deleted."
end
end
end
private
def load_commentable
resource, id = request.path.split('/')[1,2]
#commentable = resource.singularize.classify.constantize.find(id)
end
end
routes.rb
resources :projects do
resources :newcomments do
resources :newcomments
end
end
view/projects/_comments.html.erb
<%= render #newcomments %>
projects_controller.rb
def comments
#commentable = #project
#newcomments = #commentable.newcomments.newest.page(params[:comments_page]).per_page(10)
#newcomment = Newcomment.new
end
view/newcomments/_newcomment.html.erb
<div class="comments">
<%= link_to newcomment.user.name %></strong>
Posted <%= time_ago_in_words(newcomment.created_at) %> ago
<%= newcomment.content %>
</div>
<span class="comment">
<%= form_for [#commentable, #newcomment] do |f| %>
<div class="field">
<%= f.text_area :content, rows: 3, :class => "span8" %>
</div>
<%= f.hidden_field :user_id, :value => current_user.id %>
<div class="actions">
<%= f.submit "Add Comment", :class => "btn btn-header" %>
</div>
<% end %>
<% unless newcomment.newcomments.empty? %>
<%= render #newcomments %>
<% end %>
</span>
All you need, instead of working on it from a scrap, is: https://github.com/elight/acts_as_commentable_with_threading
You should not have the routes like this
resources :newcomments do
resources :newcomments
end
which is a bad smell and we need to fix. In some of our projects, we are also using https://github.com/elight/acts_as_commentable_with_threading and it's good.

Resources