Currently i am creating Blog using rails,where i want to add comments to post model. I am using acts_as_commentable,its works great on rails console but when i try to implement it in MVC,I got confused !! how can i add comments to Post model.
What should i do ? is there need to create any new controller for handle comments?
I want add comment form below the post->show view,so that user can add comments on the posts#show page.
Sorry for my english !
with acts_as_commentable as Paulo suggested or polymorphic-association
http://asciicasts.com/episodes/154-polymorphic-association
or with PRO account on railscasts: http://railscasts.com/episodes/154-polymorphic-association-revised (repo: https://github.com/railscasts/154-polymorphic-association-revised/tree/master/blog-after)
a little modified code below, this code will let you add comments to Post only as we load #commentable with #commentable = Post.find(params[:id]), if you will go through tutorial you'll be able to add comments to any other models in the app where User and Post share the same Comment model.
I used acts_as_commentable in my app before, nice gem, but I am using polymorphic-association now cause it is much more customizable.
post.rb
attr_accessible :content, :name
has_many :comments, as: :commentable
comment.rb
attr_accessible :content
belongs_to :commentable, polymorphic: true
show.html.erb
<h1>Comments</h1>
<ul id="comments">
<% #comments.each do |comment| %>
<li><%= comment.content %></li>
<% end %>
</ul>
<h2>New Comment</h2>
<%= form_for [#commentable, #comment] do |f| %>
<ol class="formList">
<li>
<%= f.label :content %>
<%= f.text_area :content, :rows => 5 %>
</li>
<li><%= f.submit "Add comment" %></li>
</ol>
<% end %>
posts_controller
def show
#post = Post.find(params[:id])
#commentable = #post
#comments = #commentable.comments
#comment = Comment.new
end
comments_controller
def create
#commentable = Post.find(params[:id])
#comment = #commentable.comments.new(params[:comment])
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
render :new
end
end
routes.rb
resources :posts do
resources :comments
end
As you can see by the acts_as_commentable documentation,
Also make sure you have the migrations to create the database structure.
In your model:
class Post < ActiveRecord::Base
acts_as_commentable
end
By your comment I see you are giving the first steps on Rails. You need to create the controller and views. In you controller you'll need to initialize the variables and call the respecting view.
My best advise for you, is before starting doing your own blog, take a look at this Rails tutorial, which will cover most of the aspects you'll need.
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 have Comment belongs_to Post and Post has_many Comments, the comment model as following:
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
validates :text, presence: true
end
The form which adds new comments is located in Posts show view, as following:
<%= form_with(model: [ #post, #post.comments.build ], local: true) do |form| %>
<% if #comment.errors.any?%>
<div id="error_explanation">
<ul>
<% #comment.errors.messages.values.each do |msg| %>
<%msg.each do |m| %>
<li><%= m %></li>
<%end %>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= form.text_area :text , {placeholder: true}%>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
The Comments create action, as following:
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = Comment.new(comment_params)
#comment.post_id = params[:post_id]
#comment.user_id = current_user.id
if #comment.save
redirect_to post_path(#post)
else
render 'posts/show'
end
end
private
def comment_params
params.require(:comment).permit(:text)
end
end
I need to render the posts/show page to show Comment validation errors, but the problem is I'm in CommentsController controller not PostsController so all objects used in pages/show view will be null.
How can I pass the #comment object to pages/show?
I thought about using flash array, but I'm looking for more conventional way.
Yep, rendering pages/show will just use the view template, but not the action, so anything you define in the pages#show controller action won't be available. #comment will be available though as you define that in the comments#create action. Without seeing the PostsController I don't know what else you're loading in the pages#show action - you could consider moving anything required in both to a method on ApplicationController, then calling from both places. Another option would be to change your commenting process to work via AJAX (remote: true instead of local: true on the form), and responding with JS designed to re-render just the comment form (you can move it into a partial used both in pages/show.html.erb and the comments#create response).
Couple of other notes on your code above - in comments#create, you can use:
#comment = #post.comments.new(comment_params)
to avoid needing to set the post_id on #comment manually.
For the form, I'd be tempted to setup a new comment in pages#show:
#comment = #post.comments.build
And then reference that in the form, it'll make it easier if you do re-use that between pages#show and comments#create:
<%= form_with(model: [ #post, #comment ], local: true) do |form| %>
Hope that helps!
I have been trying to fix an error associated with using the Ancestry gem for comments on my app for Rails 4. I used railscast episode 262 as a guide. However, unlike the episode, my comments model is a nested resource inside another model.Before I go further, I will supply the necessary code for reference. If you like to read the error right away, it is mentioned right after all the code snippets.
The Relevant Models:
class Comment < ActiveRecord::Base
has_ancestry
belongs_to :user
belongs_to :scoreboard
end
class Scoreboard < ActiveRecord::Base
#scoreboard model is like an article page on which users can post comments
belongs_to :user
has_many :teams, dependent: :destroy
has_many :comments, dependent: :destroy
end
Relevant code in the route file:
resources :scoreboards do
resources :comments
resources :teams, only: [:edit, :create, :destroy, :update]
end
The Scoreboards Controller Method for the page on which one can post comments:
def show
#scoreboard = Scoreboard.find_by_id(params[:id])
#team = #scoreboard.teams.build
#comment = #scoreboard.comments.new
end
The Comments Controller:
class CommentsController < ApplicationController
def new
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new(:parent_id => params[:parent_id])
end
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new comment_params
if #comment.save
redirect_to scoreboard_url(#comment.scoreboard_id)
else
render 'new'
end
end
private
def comment_params
params.require(:comment).permit(:body, :parent_id).merge(user_id: current_user.id)
end
end
I will include the migration for the ancestry gem if any mistakes were made on that :
class AddAncestryToComments < ActiveRecord::Migration
def change
add_column :comments, :ancestry, :string
add_index :comments, :ancestry
end
end
The following code shows the view code:
Scoreboard#show View which is giving me the error in the last line:
<div class= "comment-section">
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %> #is it needed to include this here? because this form is for new comments not replies
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
<%= nested_comments #scoreboard.comments.reject(&:new_record?).arrange(:order => :created_at) %>
</div>
The (comments partial)_comment.html.erb View:
<div class=" comment-div">
<p> Posted by <%= link_to "#{comment.user.name}", comment.user %>
<%= time_ago_in_words(comment.created_at) %> ago
</p>
<div class="comment-body">
<%= comment.body %>
<%= link_to "Reply", new_scoreboard_comment_path(#scoreboard, comment, :parent_id => comment) %>
</div>
</div>
The helper method to render comments:
def nested_comments(comments)
comments.map do |comment, sub_comment| #the comments.map also gives me an error if I choose to render the comments without the .arrange ancestry method
render(comment) + content_tag(:div, nested_comments(sub_comment), class: "nested_messages")
end.join.html_safe
end
The new.html.erb for Comments which one is redirected to for the replies form submission:
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %>
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
Upon creating a scoreboard, I am redirected to the show page, where i get the following error:
undefined method `arrange' for []:Array
Even though the array of comments is empty, I get the same error if it wasnt. I have tried .subtree.arrange but that gives me the same error. Also, the ancestry documentation said that .arrange works on scoped classes only. I don't know what that means. I would appreciate some help on making the page work so the comments show properly ordered with the replies after their parent comments. If this is the wrong approach for threaded comments(replies and all), I would appreciate some guidance on what to research next.
.reject(&:new_record?) this will return an array. The error sounds like arrange is a scope on ActiveRecord. So move the reject to the end and it should work.
#scoreboard.comments.arrange(:order => :created_at).reject(&:new_record?)
In regards your comment nesting, I have implemented this before, and found the Railscasts recommendation of a helper to be extremely weak.
Passing parent_id to a comment
Instead, you're better using a partial which becomes recursive depending on the number of children each comment has:
#app/views/scoreboards/show.html.erb
<%= render #comments %>
#app/views/scoreboards/_comment.html.erb
<%= link_to comment.title, comment_path(comment) %>
<div class="nested">
<%= render comment.children if comment.has_children? %>
</div>
I'm learning and trying to add functionality to a simple app. I'm adding a comments ability to each micropost and creating comments works fine, however the comments will not render. No error message, application runs perfectly fine except the comments partials just isn't included in the final view.
I've looked at similar questions here and tried so many solutions that I'm not even sure where the problem might be, any help would be much appreciated.
I have 3 relevant models: Users, Microposts, Comments.
Microposts belong to Users, has_many comments.
Comments belongs to Microposts and Users.
Comments model includes columns for "comment_content", "micropost_id", "user_id" and "created_at".
Micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 1000 }
acts_as_votable
Comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :micropost
validates :comment_content, presence: true
validates :user_id, presence: true
validates :micropost_id, presence: true
User.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :comments
User Controller#show
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
#micropost = Micropost.find(params[:id])
#comment = Comment.new
#comments = Micropost.find(params[:id]).comments.paginate(page: params[:page])
end
Comments Controller#create
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = Comment.new(comment_params)
#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
To render in view from _micropost partial
<ul>
<%= render #comment %>
</ul>
Comments partial _comments
<%= link_to gravatar_for(#micropost.user, size: 15), #micropost.user %>
<%= link_to #micropost.user.name, #micropost.user %>
<% #micropost.comments.each do |comment| %>
<%= comment.comment_content %>
<% end %>
The comments form displays just fine. After logging in and creating a comment I check the console and confirm a comment is created, has the correct content, is associated with the User that made the post and the micropost it was commented on.
So the comment is sitting there in the DB, it just won't come out!
SOLUTION
UsersController:
def show
#user = User.find(params[:id])
end
Views
<% #user.microposts.each do |micropost| %>
<%= link_to gravatar_for(#user, size: 15), #user %>
<%= link_to #user.name, #user %>
<% micropost.comments.each do |comment| %>
<%= comment.comment_content %>
<% end %>
<% end %>
That's basically it, I was overloading my UsersController#show and that was throwing the whole thing off. As the advice below states the Userscontroller was doing the heavy lifting, and I shouldn't have been defining so many variables there.
Can't express enough thanks to #thedanotto and #fylooi. They were both correct. All of the troubleshooting they walked me through taught me so much. It's intimidating learning Rails and I've always refrained from asking stupid questions online, but this experience showed me just how awesome some people can be! I've got a lot to learn, reading through the pragmatic programmers series, I'll do my best to give back to the community!
Your has_many and belongs_to relationships appear to be set up correctly. This means you can rely upon rails to do most of the heavy lifting in your controller and views.
User Controller#show becomes...
def show
#user = User.find(params[:id])
end
views/users/show.html.erb becomes...
<% #user.microposts.each do |micropost| %>
<%= link_to gravatar_for(#user, size: 15), #user %>
<%= link_to #user.name, #user %>
<!-- You have access to micropost here as well, you could still do micropost.user, micropost.user.name instead of #user -->
<% micropost.comments.each do |comment| %>
<%= comment.comment_content %>
<% end %>
<% end %>
this can't work
<%= micropost.comment.comment_content %>
you have a has_many relation in your micropost model.
you could solve this to add a has_one relation to your micropost model with an order by created_at
or temporary (really temporary)
<%= micropost.comments.last.comment_content %>
update:
your partial _comments just need to talk to the comment variable.
so change your micropost.comment.comment_content to comment.comment_content
the name of the user should delegate to the usermodel via
delegate :name, to: :user, prefix: true, allow_nil: true
in your comment model. now you can use comment.user_name to display the name of the user
best
A few things:
You're loading both User and Micropost in your UsersController#show based on ID. Doesn't look right to me, unless your User and Micropost database IDs move in lockstep.
#user = User.find(params[:id])
#micropost = Micropost.find(params[:id])
A Micropost should be shown in a MicropostsController, not a UsersController. Eg. /microposts/1 should return the first Micropost resource, while /users/1 returns the first User resource.
I'm going to assume you're trying to render the user's microposts and their related comments from UsersController#show
Based on that, your variable structure can be arranged as per the following:
# users_controller.rb
class UsersController
def show
#user = User.find(params[:id])
#microposts = #user.microposts
# better practice is to have only one # instance variable in the
# controller, but let's go with this for academic purposes
end
end
# views/users/show.html.erb
<%= render #microposts %>
# this can also be written as
# <% #microposts.each do |micropost|
# <%= render partial: 'micropost', locals: { micropost: micropost }
# <% end %>
# views/users/_micropost.html.erb
<%= link_to gravatar_for(micropost.user, size: 15), micropost.user %>
<%= link_to micropost.user.name, micropost.user %>
<ul>
<%= render micropost.comments %>
</ul>
# views/users/_comment.html.erb
<li>
<%= comment.content
</li>
So I'm creating a blog rails app and I'm trying to create a comment session on the blog. I'm trying to render a form using simple form, but I'm having a hard time getting the simple form to work. For now I have:
<%= simple_form_for ([#user, #post.comments.build]) do |f| %>
<%= f.input :comment %>
<%= f.button :submit %>
<% end %>
but it says that post.comments isn't a defined path.
My comment model:
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
post belongs to user and has_many comments
user has many post and has many comments
Here are my current routes:
resources :posts do
resources :comments
end
Any advice?
Thanks!
Why are you sending #user in simple_form_for?
Use #post in place of #user.
I found a fix to this by generating a migration for the comment. I just had to make sure that everything with an association actually had the columns in the database. After that I just made sure I was rendering the #post.comment instead of comment/comment. Hope this helps anyone that came across the same problem.
remove user from the form.
<%= simple_form_for [#post, #post.comments.build] do |f| %>
<%= f.input :comment %>
<%= f.button :submit %>
<% end %>
and then you will assign the user value in the controller using something like current_user if you are using devise.
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(comment_params)
#comment.user = current_user
#comment.save
redirect_to #post
end
def comment_params
params.require(:comment).permit(:comment)
end
it's a bad idea to have a model named comment and a field into it named comment. I would prefer to call it content