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 %>
Related
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.
I'm working on a to-do list in rails and I'm getting the following: undefined method items for nil:NilClass in my users_controller.rb.
The program was working to the point where I could delete and create the list and have it take me to the new_list_path. However, after I came back a day later, I got the undefined method.
Currently, the user is logged in and there is no list. I tried to add a list via rails console but that didn't work.
users_controller.rb
class UsersController < ApplicationController
def show
return redirect_to new_list_path unless current_user
#list = current_user.list
#items = #list.items
end
end
I am directing everything to go the View/Users/Show page with some partials:
users/show.html.erb
<h1><%= #list.title %></h1>
<%= link_to "Delete List", #list, method: :delete %>
<h2 class="media-heading"><%= current_user.name %></h2>
<%= render partial: 'items/form'%>
<%= render partial: 'items/item', collection: #items %>
Partials are here
items/_form.html.erb
<%= form_for [#list, #list.items.new] do |f| %>
<div class="form-group">
<h4>Add an Item:</h4><br/>
</div>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control', placeholder: "Add an Item:" %>
</div>
<div class= "form-group">
<%= f.submit "Create Item", class: 'btn btn-success' %>
</div>
<% end %>
items/_item.html.erb
<small class="media-heading">
<p><%= item.name %></p>
<%# time_ago_in_words(item.created_at) %>
</small>
Here are my other two controllers:
lists_controller.rb
class ListsController < ApplicationController
before_action :authenticate_user!
def new
#list = List.new
end
def create
#list = List.new(list_params)
#list.user = current_user
if #list.save
flash[:notice] = "List was saved."
redirect_to current_user
else
flash[:error] = "There was a problem saving your list."
redirect_to user_path(current_user)
end
end
def destroy
#list = List.find(params[:id])
if #list.destroy
redirect_to new_list_path
else
redirect_to current_user
end
end
def edit
end
private
def list_params
params.require(:list).permit(:title)
end
end
items_controller.rb
class ItemsController < ApplicationController
def show
#items = Item.all
end
def create
#list = List.find(params[:list_id])
#item = Item.new(item_params)
#item.list = #list # after initializiation, before saving
if #item.save
flash[:notice] = "Item was saved."
redirect_to current_user
else
flash[:error] = "There was a problem saving your item."
redirect_to current_user
end
end
private
def item_params
params.require(:item).permit(:name)
end
end
I'm wondering how it's broken when it worked previously.
Models are as follows:
list.rb
class List < ActiveRecord::Base
belongs_to :user
has_many :items
end
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_one :list
end
item.rb
class Item < ActiveRecord::Base
belongs_to :list
end
In the users_controller.rb, maybe you should define:
def show
#user = User.find(params[:id])
#list = #user.lists
#items = #list.items
end
You need to find the specific user first so it know what lists to show. I realize you are trying to do that with devise helpers, but it isn't really necessary.
I'm trying to make a comment form on my Collection show page. I'm a bit rusty with Rails and I'm not sure why this form isn't attempting to Create a comment and is instead returning the error
The action 'update' could not be found for CommentsController
The comments system works in console.
Here is my form
<%= form_for [#commentable, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :content %>
<%= f.submit "Comment", class: "btn btn-large btn" %>
<% end %>
My comment model
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
belongs_to :user
end
My collection model
class Collection < ActiveRecord::Base
has_many :comments, as: :commentable
end
My comments_controller
class CommentsController < ApplicationController
def create
#comment = #commentable.comments.new(comment_params)
if #comment.save
flash[:success] = 'Comment posted!'
redirect_to #commentable
else
flash[:notice] = "Error creating comment: #{#comment.errors}"
redirect_to #commentable
end
end
private
def comment_params
params.require(:comment).permit(:content, :commentable_type, :commentable_id, :user_id)
end
end
My collections_controller show action
def show
#collection = Collection.find(params[:id])
#commentable = #collection
#comments = #commentable.comments
#comment = Comment.new if user_signed_in?
end
You're not using #commentable in your form or controller, which may be causing your problem:
<%= form_for [#commentable, #comment] do |f| %>
And your controller action should look like this:
def create
#comment = #commentable.comments.new(comment_params)
Update: Then to load commentable based on resource:
before_filter: load_commentable
def load_commentable
resource, id = request.path.split('/')[1, 2]
#commentable = resource.singularize.classify.constantize.find(id)
end
Method courtesy Ryan Bates: http://railscasts.com/episodes/154-polymorphic-association-revised
Can you show the controller action that renders that forms? I think the problem is that #comment is already persisted/created so it tries to update it.
UPDATE:
I just noticed your show action. Yes the problem is that #comment is already persisted. Just change it to:
#comment = Comment.new if user_signed_in?
I'm using rails 4.0.8. I added a comment section to a model called 'Things', but I keep getting the same error "param is missing or the value is empty: thing" when I press the submit comment button. It says the error is in the Things#Controller. What am I doing wrong?
UPDATE: I removed the url path from the form, but a new error returns "Couldn't find Thing without an ID". The error is in Comments#Controller.
VIEW FOR THING/SHOW
<div id= "thing">
<h1>
<%= #thing.name %>
</h1>
<br>
<div id= "commentsection">
Comments
<div id= "comments">
<br>
<% #thing.comments.each do |c| %>
<%= c.username %>
<br>
<%= c.text %>
<% end %>
<%= form_for #comment, :url => thing_path do |f| %>
<%= f.label :username %>
<%= f.text_field :username %>
<%= f.label :comment %>
<%= f.text_field :text %>
<%= f.submit "Enter", class: "btn btn-small btn-primary" %>
<% end %>
</div>
</div>
</div>
THINGS CONTROLLER
class ThingsController < ApplicationController
def show
#thing = Thing.find(params[:id])
#thing.comments.build
#comment = Comment.new
end
def index
end
def new
#thing = Thing.new
#things = Thing.all
end
def create
#thing = Thing.new(thing_params)
if #thing.save
redirect_to #thing
else
render 'new'
end
end
private
def thing_params
params.require(:thing).permit(:name, :avatar)
end
end
COMMENTS CONTROLLER (I put asterisks around the line where the error is)
class CommentsController < ApplicationController
def show
#comment = Comment.find(params[:id])
end
def new
#comment = Comment.new
#comments = Comment.all
end
def create
****#thing = Thing.find(params[:thing_id])****
#comment = #thing.comments.create(comment_params)
redirect_to thing_path(#thing)
end
end
private
def comment_params
params.require(:comment).permit(:user, :text, :upvotes, :downvotes, :thing_id)
end
end
ROUTES
Website::Application.routes.draw do
get "comments/new"
get "comments/show"
get "things/new"
root 'home_page#home'
get "all/things/new" => 'things#new'
get "all/allthings"
resources :things
resources :good_comments
get "things/show"
get "things/results"
end
You are posting the #comment form to post '/things' path.
<%= form_for #comment, :url => thing_path do |f| %>
It should just be <%= form_for #comment do %> (Rails is smart enough to plug in the comments_path) or if you feel like being more explicit (even though it's not necessary)
<%= form_for #comment, url: :comments_path do %>
Another note though, if you want that Comment to be tied to that specific Thing then in your models it should be
Class Thing
has_many :comments
end
Class Comment
belongs_to :thing
end
Then make sure in your database comment has a thing_id foreign_key field and then your form for comment should actually look like
<%= form_for #thing, #comment do %>
<% end %>
On user's page i have many microposts and i want to add comment form and comments to each micropost.
I have three models: User, Micropost, Comment.
user.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :comments
end
micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
end
comment.rb
class Comment < ActiveRecord::Base
attr_accessible :comment_content
belongs_to :user
belongs_to :micropost
validates :comment_content, presence: true
validates :user_id, presence: true
validates :micropost_id, presence: true
end
comments_controller.rb
class CommentsController < ApplicationController
def create
#comment = current_user.comments.build(params[:comment])
if #comment.save
flash[:success] = "Comment created!"
redirect_to current_user
else
render 'shared/_comment_form'
end
end
end
_micropost.html.erb
<tr>
<td class="micropost">
<span class="content"><%= wrap(micropost.content) %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
</span>
<%= render 'shared/comment_form' %>
</td>
</tr>
Comment form
<%= form_for(#comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :comment_content %>
</div>
<button class="btn" type="submit">
Create
</button>
<% end %>
Every micropost must have its own comments.
In my DB i have comment table with
id / comment_content / user_id / micropost_id
columns.
Comment is not creating because RoR can't understand to which micropost belongs this new comment.
What should i do to have all needed information in my DB?
UPDATE
users_controller
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
#comment = Comment.new
end
microposts_controller
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to current_user
else
render 'shared/_micropost_form'
end
end
SOLUTION!!!
Big thanks to carlosramireziii and Jon! They are both right
comments_controller
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = Comment.new(params[:comment])
#comment.micropost = #micropost
#comment.user = current_user
if #comment.save
flash[:success] = "Comment created!"
redirect_to current_user
else
render 'shared/_comment_form'
end
end
_micropost.html.erb
<%= render 'shared/comment_form', micropost: micropost %>
Comment form
<%= form_for([micropost, #comment]) do |f| %>
routes.rb
resources :microposts do
resources :comments
end
Try passing in the current micropost to the comment partial
<%= render 'shared/comment_form', micropost: micropost %>
Then add the micropost to the comment form_for call
<%= form_for([micropost, #comment]) do |f| %>
Make sure your routes are nested
# in routes.rb
resources :microposts do
resources :comments
end
Then build the comment off of the micropost in the CommentsController
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = #micropost.comments.build(params[:comment])
...
end
I would use nested resources for micropost and comments like this in your routes.rb file:
resources :microposts do
resources :comments
end
Then in your comments controller, which you access through the micropost_comments_path(#micropost) url helper you can do the following to build the association in the create action:
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = Comment.new(params[:comment])
#comment.micropost = #micropost
#comment.user = current_user
if #comment.save
...
end
You could reduce the number of lines of code using the merge method, but I find that this sometimes reduces the readability of your code, and since you'll need the #micropost object if redisplaying the form after validation errors you may as well just fetch the record.