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.
Related
I am using gem devise for creating users profile
Each user can create a comment. I need to add the user name beside each comment something like this <%= #comment.user.name %>
in user.rb
has_many :comments, dependent: :destroy
in comment.rb
belongs_to :users
in comment controller
before_action :find_comment ,only:[:show,:update,:edit,:destroy]
def new
#user =User.find(params[:id])
#comment = #user.comments.build
end
def create
#user =User.find(params[:id])
#comment = #user.comments.build(comment_params)
#comment.user = current_user
if #comment.save
redirect_to doctor_path(:id => #user.id)
end
end
private
def find_comment
#comment = Comment.find(params[:id])
end
def comment_params
params.require(:comment).permit(:text)
end
user controller
def show
#user = User.find(params[:id])
end
user show.html.erb
<% for item in #user.comments %>
<% if item.text.present? %>
<%= item.text %><br>
<%= #comment.user.name %>
<br><hr>
<% end %>
I got this error
undefined method `user' for nil:NilClass
You could do it the other way around, in your show method:
#comments = Comment.all
in your show view:
<% #comments.each do |comment| %>
<%= comment.text %>
<%= comment.user.name %>
<% end %>
Since your question is not really clear I'll specifiy that if you want to show just the comments posted by the user:
def show
user_id = User.find(params[:id]).id
#comments = Comment.where(user_id: user_id)
end
Just some quick rules to start with
A user has many comments, this will be the relationship between the user and a comment that the user has made.
You already have this
A user has many profile comments, this is the relationship between a user and the comments that have been left for that user on their profile
Now you have that distinction things start to be come clearer.
Start by creating a single xref table to act as the go between users and comments that have been left for a profile and call it profile_comments
this profile_comments table needs a user_id and a comment_id of type integer to store the primary keys from user and comments tables, where the user_id is the id of the user that is having a comment left about them on their profile
You can now setup a profile_comment model that with the following relationships
belongs_to comment
belongs_to user
So now you need to change your user model relationships to the following
user.rb
has_many :comments
has_many :profile_comments, dependent: :destroy
comment.rb
belongs_to :user #not users as you have defined in your question
has_many :profile_comments, dependent: :destroy
and the new profile_comment.rb model needs the two belongs_to clauses for comment and user
profile_comment.rb
belongs_to :user
belongs_to :comment
Now when you create a comment you need to assign to to the user, and also to the profile_comment
So now your comments controller needs to setup these relationships so instead of
before_action :find_comment ,only:[:show,:update,:edit,:destroy]
def new
#user =User.find(params[:id])
#comment = #user.comments.build
end
def create
#user =User.find(params[:id])
#comment = #user.comments.build(comment_params)
#comment.user = current_user
if #comment.save
redirect_to doctor_path(:id => #user.id)
end
end
You need something like this
def create
#user =User.find(params[:id])
#comment = current_user.comments.build(comment_params)
#profile_comment = ProfileComment.new
#user.profile_comment < #profile_comment
#comment.profile_comment < #profile_comment
if #comment.save
redirect_to doctor_path(:id => #user.id)
end
end
Your update action will need to also change accordingly
Now in your view instead of
<% for item in #user.comments %>
<% if item.text.present? %>
<%= item.text %><br>
<%= #comment.user.name %>
<br><hr>
<% end %>
<% end %>
You want this, it's a little complex because you need to get from the profile comment to the comment then to the user that created the comment
<% #user.profile_comments.each do | profile_comment |%>
<%comment = profile_comment.comment%>
<% if comment.text.present? %>
<%= comment.text %><br>
<%if comment.user.blank?%>
No user assigned to this comment
<%else%>
<%= comment.user.name #or email or whatever%>
<%end%>
<br><hr>
<% end %>
<% end %>
Although text is a reserved word and is an actual column type so you might want to change the column name text to something else
Any questions on this feel free to get back to me but I won't be around for the next 24 hours. Hope it's clear and helps you understand what has gone wrong with your initial setup
In the controller and the view I have assumed that the current_user is the person making the comment and #user is the person that is being commented on. Just switch that round if I have that wrong
Hi I'm having trouble by making the update and destroy method in my posts_controller, I'm able to create new Posts but I'm not able to update and I want to know how to destroy the model while destroying all the associations with the other model it.
My models:
Post Model
class Post < ApplicationRecord
has_many :comments, dependent: :destroy
has_many :has_categories
has_many :categories, through: :has_categories
validates :title, presence: true,
length: { minimum: 5 }
after_create :save_categories
def categories=(value)
#categories = value
end
private
def save_categories
#categories.each do |category_id|
HasCategory.create(category_id: category_id, post_id: self.id)
end
end
end
Has_Category model
class HasCategory < ApplicationRecord
belongs_to :post
belongs_to :category
end
Category Model
class Category < ApplicationRecord
validates :name, presence: true
has_many :has_categories
has_many :posts, through: :has_categories
end
So in my partial form for the new and the edit actions is like this
<%= form_with model: #post, local: true do |form| %>
<!--Inputs before the categories-->
<div>
<label>Categories</label>
<% #categories.each do |category| %>
<div>
<%= check_box_tag "categories[]", category.id %> <%= category.name %>
</div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
My posts_controller create and update method
def create
#post = Post.new(post_params)
#post.categories = params[:categories]
if #post.save
redirect_to #post
else
render :new
end
end
def update
#post = Post.find(params[:id])
#post.categories = params[:categories]
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
My create action is working but the update action is just updating the inputs before the check_box_tag.
I know that the save_categories method on my Post model is the one who is taking the array I'm receiving from the form and creating the HasCategory association, How should I make the update action or even the destroy action given the situation that Is a many to many association?
The line has_many :categories, through: :has_categories gives you category_ids for post. So you can change your form:
<%= form_with model: #post, local: true do |form| %>
<!--Inputs before the categories-->
<div>
<label>Categories</label>
<%= f.collection_check_boxes(:category_ids, #categories, :id, :name)
</div>
<div>
<%= form.submit %>
</div>
<% end %>
and controller:
def create
# you need it here for correct rerendering `new` on validation error
#categories = Category.all # or maybe you have here more complicated query
#post = Post.new(post_params)
if #post.save
redirect_to #post
else
render :new
end
end
def update
# you need it here for correct rerendering `edit` on validation error
#categories = Category.all # or maybe you have here more complicated query
#post = Post.find(params[:id])
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
private
def post_params
params.require(:post).permit(:name, :some_other_post_params, category_ids: [])
end
You need to remove callback and categories=(value) method from the Post model. And define #categories in the new and edit actions. If it equals Category.all you can just put it to the form: f.collection_check_boxes(:category_ids, Category.all, :id, :name), without defining #variable
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 %>
I tried to implement a commenting system on my Ruby on Rails website.
I basically followed these guidelines from this thread: Micropost's comments on users page (Ruby on Rails)
However, the comment doesn't show when I post and the text "Asset" is displayed on top of every comment box. Where is this coming from?
Updated with codes:
I am using three models to try to get the comments working as shown in the above link
user.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :comments
micropost.rb
class Micropost < ActiveRecord::Base
attr_accessible :content, :image, :comment_content
belongs_to :user
has_many :comments, dependent: :destroy
validates :user_id, presence: true
validates :content, presence: true
default_scope order: 'microposts.created_at DESC'
def self.from_users_followed_by(user)
followed_user_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
where("user_id IN (#{followed_user_ids}) OR user_id = :user_id",
user_id: user.id)
end
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
class CommentsController < ApplicationController
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = Comment.new(params[:comment])
#comment.micropost = #micropost
#comment.user = current_user
if #comment.save
redirect_to(:back)
else
render 'shared/_comment_form'
end
end
end
micropost controller
class MicropostsController < ApplicationController
before_filter :signed_in_user
before_filter :correct_user, only: :destroy
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.save
flash[:success] = "Posted"
redirect_to root_path
else
#feed_items = []
render 'static_pages/home'
end
end
def destroy
#micropost.destroy
redirect_to root_path
end
private
def correct_user
#micropost = current_user.microposts.find_by_id(params[:id])
redirect_to root_path if #micropost.nil?
end
end
user controller
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
#comment = Comment.new
end
comment form
<%= form_for([micropost, #comment]) do |f| %>
routes.rb
resources :microposts do
resources :comments
end
micropost view
<li>
<span class="content"><%= simple_format(micropost.content) %></span>
<%= image_tag micropost.image_url(:thumb).to_s %><br>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
</span>
<%= render 'shared/comment_form', micropost: micropost %>
<% if current_user?(micropost.user) %>
<%= link_to "delete", micropost, method: :delete,
confirm: "You sure?",
title: micropost.content %><br>
<% end %>
</li>
Maybe you have some static informations in one of your partials or in the form control that render your textarea for the comment?
Check you view for the controller or maybe it is somewhere in the 'static_pages/home'
EDIT:
Just full search your project for the text asset, maybe you find an image alt="assets" tag for an image and the image is not available.
I made a commenting system and I am trying to get it to post under a micropost but I constantly get this routing error. Any suggestions? All help is much appreciated!
Routing Error
No route matches [POST] "/microposts/comments"
Form
<div class="CommentField">
<%= form_for ([#micropost, #micropost.comments.new]) do |f| %>
<%= f.text_area :content, :class => "CommentText", :placeholder => "Write a Comment..." %>
<div class="CommentButtonContainer">
<%= f.submit "Comment", :class => "CommentButton b1" %>
</div>
<% end %>
</div>
comment controller
class CommentsController < ApplicationController
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = #micropost.comments.build(params[:comment])
#comment.user_id = current_user.id
#comment.save
respond_to do |format|
format.html
format.js
end
end
end
routes
resources :microposts do
resources :comments
end
Micropost Model
class Micropost < ActiveRecord::Base
attr_accessible :title, :content, :view_count
acts_as_voteable
belongs_to :user
has_many :comments
has_many :views
accepts_nested_attributes_for :comments
end
User Controller
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#school = School.find(params[:id])
#micropost = Micropost.new
#comment = Comment.new
#comment = #micropost.comments.build(params[:comment])
#microposts = #user.microposts.paginate(:per_page => 10, :page => params[:page])
end
end
The reason you are getting the error is that you are trying to build a form for a comments of a micropost that does not exist yet in the database.
the form, there is a -
form_for ([#micropost, #micropost.comments.new]) do |f|
And in UsersController you have -
#micropost = Micropost.new
comment is a sub-resource of micropost, so a url that creates a comment should look like /micropost/:id/comments where :id is the id of micropost. That is possible only after the micropost is saved.
So I believe your action should assign #micropost to an existing post, or create one right there to have the form working. Something like -
#micropost = Micropost.last || Micropost.create
would at least get rid of the error.
I'll try this again (deleted my other answer since, as Marc Talbot pointed out, was not the correct response to your issue).
Perhaps the issue is as simple as making :microposts be :micropost instead (to reflect your model's name).
resources :micropost do
resources :comments
end