Comments that belong_to Post and belong_to User - ruby-on-rails

I'm trying to add comments to a Post model
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user #should this be has_one :user instead?
....
How do I set up my Comment new and creation actions to get both current_user as well as the current post?
guides.rubyonrails.org suggested
Controller:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment])
redirect_to post_path(#post)
end
View
<%= form_for([#post, #post.comments.build]) do |f| %>
...
However this only seems to be aiming to associate with the post and not also the user. How can I set up both associations?

I assume you have a current_user() method somewhere in your controller.
So this should do it:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
#comment.user = current_user
#comment.save
redirect_to post_path(#post)
end

Deradon answered the question well but I prefer to have that logic within the new comment form itself. For example, instead of calling those variables you can have:
app/views/comments/_form.html.erb:
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.hidden_field :post_id, value: #post.id %>
This assumes of course that your new comment form is embedded within the 'post show' page, so that #post is available:
app/views/posts/show.html.erb:
<body>
<%= render #post %>
<%= render 'comments/_form' %>
</body>
This will add post_id and user_id directly into the db for a new comment. Also, don't forget to make an index for those foreign keys so the database has quicker access. If you don't know how, google it!

Related

Passing validation errors array from one controller to another

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!

undefinded method when I add user's name to comments

#user.name works on my posts, user profile, and everywhere else on my site. But when I add it to user comments, I get undefined method?
<p class="comment_body">
<h2><%= #user.name %></h2>
<%= comment.body %>
</p>
I tried <%= user.name %> and still an error.
I tried <%= current_user.name %> and it worked. However, I don't want to show the current user's name, I want to see the user's name of who posted the comment.
EDIT:
I forgot to associate user to comment, but I'm not really sure how.
Comments controller
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:body))
redirect_to post_path(#post)
end
end
I went into my comments.rb and wrote belongs_to :user and in my user.rb I added has_many :comments.
So, should you set the user and save the comment, shouldn't you? try something like
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:body))
#comment.user = current_user
if #comment.save
redirect_to post_path(#post)
else
# something else
end
end

Need help creating controller variable in Rails

My Rails app allows users to create "connections" that describes their relationship with other users. Users can comment on other users' blog posts (called "works" here) and, for each comment made on a blog post, I want to show the users' relationship to the author. I'm having trouble creating the instance variable in the works controller.
Here's what I have so far in the show action in the works controller:
class WorksController < ApplicationController
def show
#work = Work.find(params[:id])
#workuser = #work.user_id
#connections = Connection.where(user_id: #workuser, otheruser_id: UNKNOWN).all
#comment = #work.comments.build
#comment.user = current_user
#comments = #work.comments.order("created_at DESC").where(work_id: #work).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #work }
end
end
end
I need help with the #connections instance variable, specifically what to assign the otheruser_id: parameter. I know for a fact that this needs to be the user_id of the user who posted a comment. However, I'm stumped as to how to get this id.
Here are the model relationships:
work.rb- belonts_to :user, has_many :comments
user.rb- has_many :works, has_many :comments, has_many :connections
connection.rb- belongs_to :user
Please let me know if I can provide any other information. Any help will be greatly appreciated!
Thanks!!
EDIT: Simplified version of the view code that populates the comments (the user, the relationship to the author, and the comment content):
<% #comments.each do |comment| %>
<%= link_to comment.user.full_name, comment.user if comment.user %>,
<%= #connections.description %>
<%= #comment.content %>
<% end %>
Ill update yo on the instance variable once you answer my comment. But Bachan's answer should do it if its two way.
EDIT:
After what you said about one way relationships I think you should not create #connections instance variable.
Instead define a method in the user.rb model like this:
def get_connection otheruser
Connection.where(:user_id=>self.id,:otheruser_id=>otheruser.id).first
end
Then in the view.....
So you wanna display all the comments like:
Commentator Name
Connection between commentator and work author
comment content
Alright to do that you can do this:
<% #comments.each do |comment| %>
<%= link_to comment.user.full_name, comment.user if comment.user %>
<%= #work.user.get_connection(comment.user).description unless #work.user.get_connection(comment.user).nil? %>
<%= comment.content %>
<% end %>
Controller:
class WorksController < ApplicationController
def show
#work = Work.find(params[:id])
#workuser = #work.user_id
#comment = #work.comments.build
#comment.user = current_user
#comments = #work.comments.order("created_at DESC")
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #work }
end
end
end
Please have a try with
#connections = Connection.where("user_id = ? OR otheruser_id = ?", #workuser, #workuser)

Multiple Foreign Keys for Rails record - Unknown Action

I'm working on an app that allows users to comment on a single "work" (think blog post). The associations in the models are as follows:
class User < ActiveRecord::Base
has_many :works
has_many :comments
class Work < ActiveRecord::Base
belongs_to :user
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
belongs_to :work
In the comments table, a record has the following fields:
id
content
user_id
created_at
updated_at
work_id
In my Comments controller, I have the following Create action:
def create
#work = Work.find(params[:id])
#comment = #work.comments.create(params[:comment])
#comment.user = current_user
if #comment.save
#flash[:success] = "Post created!"
redirect_to root_url
else
render 'activities'
end
end
I'm trying to associate both the user AND the work to the comment but I get the following error message when I try to create a comment:
Unknown action
The action 'update' could not be found for CommentsController
I'm trying to use the following StackOverflow answer as a guide but the solution is not working for me:
Multiple Foreign Keys for a Single Record in Rails 3?
EDIT:
I have a add comment form on the works#show action:
def show
#work = Work.find(params[:id])
#comment = current_user.comments.create(params[:comment])
#activities = PublicActivity::Activity.order("created_at DESC").where(trackable_type: "Work", trackable_id: #work).all
#comments = #work.comments.order("created_at DESC").where(work_id: #work ).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #work }
end
end
The Comment form itself:
<%= form_for(#comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Post a comment!" %>
</div>
<%= f.submit "Post", class: "btn btn-small btn-primary" %>
<% end %>
I also have an update method on the Comments controller:
def update
#comment = current_user.comments.find(params[:id])
if #comment.update_attributes(params[:comment])
flash[:success] = "Comment updated"
redirect_to #comment
end
end
The error message says:
The action 'update' could not be found for CommentsController
So, the issue is that your form is trying to call an update action on the CommentsController. This is unrelated to adding both the User and the Work instance as foreign keys. Your code for that seems right.
For sure if you persist the comment to the database during the show action:
#comment = current_user.comments.create(params[:comment])
Then the form helper will build an update form rather than a create form (because the model already exists):
<%= form_for(#comment) do |f| %>
If the desired action is to POST a comment create from the show page then try building the comment in the show action:
#comment = current_user.comments.build(params[:comment])

How to get the post id in rails?

This is is how I have my models define for a simple blog
def User
has_many :comments, :dependent => :destroy
has_many :posts
end
def Post
belongs_to :user
has_many :comments
end
def Comment
belongs_to :user
belongs_to :post
end
In my Post Controller I have this code so that I can create a comment in the view
def show
#post = Post.find(params[:id])
#comment = Comment.new
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #post }
end
end
Then in my Comment#create I have this
def create
#comment = current_user.comments.create(params[:comment])
if #comment.save
redirect_to home_show_path
else
render 'new'
end
end
How should I make it so that my comment model can receive the post_id? I have done this in my Post show view as a fix but is there another way that is better?
<%= f.hidden_field :post_id, :value => #post.id %>
There's nothing necessarily wrong with setting the post_id via a hidden field in your form - however it does mean that people could potentially associate their comment with any random post.
A better way might be to use nested resources for the comments of posts. To do this, set the following in your routes.rb file:
resources :posts, :shallow => true do
resources :comments
end
Then your form should look like this:
<%= form_for #comment, :url => post_comments_path(#post) do %>
...
<% end %>
Which will mean that the form POSTs to the path /post/[:post_id]/comments - which means in turn that the post_id is available to the controller as a param:
def create
#comment = current_user.comments.new(params[:comment])
#comment.post = Post.find(params[:post_id])
if #comment.save
...
end
end
This has the advantage of doing a select for the Post using the post id, and if the Post isn't found, an error will be raised.
It might also be worth rewriting that controller method slightly, so that the Post.find comes first:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(params[:comment])
#comment.user = current_user
if #comment.save
...
end
end
Hope that helps.
Yes, there is a better way. Use nested resources as described in the official Routing guide or in the Getting Started guide. The Getting Started guide even covers this exact example of posts and comments!
<%= form_for :comment, :url => post_comments_path(#post) do |f| %>
<%= f.text_field :content %>
<%= f.submit%>
<% end %>
In your comment create action you have
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
#comment.user = current_user #if you are using devise or any authentication plugin or you define a current_user helper method
if #comment.save
......
end
if you are using rails 3, in you config/routes.rb do
resources :posts do
resources :comments
end
the first section of the code should be in your post/show.html.erb

Resources