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
Related
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
There's a form on the Works show page that allows users to post a comment:
<%= 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 %>
The Works controller is as follows. Note that I'm adding the build comment functionality here so that the form on the Works page functions:
class WorksController < ApplicationController
#before_filter :current_user, only: [:edit, :update]
def index
#works = Work.all
#comment = #work.comments.build(params[:comment])
#comment.user = current_user
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #works }
end
end
def create
#work = current_user.works.create(params[:work])
redirect_to current_user
end
def edit
#work = current_user.works.find(params[:id])
end
def new
#work = current_user.works.new
end
def destroy
#work = current_user.works.find(params[:id]).destroy
flash[:success] = "Work deleted"
redirect_to current_user
end
def update
#work = current_user.works.find(params[:id])
if #work.update_attributes(params[:work])
flash[:success] = "Profile updated"
redirect_to #work
else
render 'edit'
end
end
def show
#work = Work.find(params[:id])
#comment = #work.comments.build
#comment.user = current_user
#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
end
And lastly, here is the Comments controller:
class CommentsController < ApplicationController
before_filter :authenticate_user!
def index
#comments = Comment.all
end
def show
#comment = Comment.find(params[:id])
#activities = PublicActivity::Activity.order("created_at DESC").where(trackable_type: "Comment", trackable_id: #comment).all
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #comment }
end
def update
#comment = current_user.comments.find(params[:id])
if #comment.update_attributes(params[:comment])
flash[:success] = "Comment updated"
redirect_to #comment
end
end
def create
#work = Work.find(params[:id])
#comment = #work.comments.build(params[:comment])
#comment.user = current_user
if #comment.save
#flash[:success] = "Post created!"
redirect_to #work
else
render 'home#index'
end
end
end
end
When I attempt to submit a comment using the comment form on the works show view page, I get the following error:
Activerecord::RecordNotFound in CommentsController#create
Couldn't find Work without an ID
Why can't the application find the Work so that it can associate the comment to it?
EDIT 1:
Thanks to the answers below I edited the comment form:
<%= form_for(#work, #comment) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Post feedback or contribute content
to this work!" %>
</div>
<%= f.submit "Post", class: "btn btn-small btn-primary" %>
<% end %>
I'm still getting the same error after making the change to the form and adding the nested route.
I edited the routes file to include a nest for work comments:
authenticated :user do
root :to => 'activities#index'
end
root :to => "home#index"
devise_for :users
resources :users do
member do
get :following, :followers, :posts, :comments
end
end
resources :works do
resources :comments
end
resources :relationships, only: [:create, :destroy]
resources :posts
resources :activities
resources :comments
Rake routes shows the following for Comments#create:
POST /comments(.:format)
The POST URL (where the error shows up) is appURL/works/1/comments
Doesn't seem right. What do I need to change? Thank you so much for the help so far!!
Your form needs to be form_for([#work, #comment]) so that Rails knows to build a URL like /works/123/comments. Right now it would just be posting to /comments.
Check your rake routes to see the route for your CommentsController#create action. You might also need to tweak the controller to read params[:work_id] instead of params[:id].
The view helper form_for(#comment) will post to '/comments' by default. You can specify a url (see the guides) that includes the :id of the work record. The typical approach is to use form_for([#work, #comment]) and Rails will do this for you so long as you've set up your routes with comments as a nested resource of work.
How can I clean this up using rails 3 features? I have a post that belongs to a group and also a user. The group and user has_many posts. I am using a nested resource
resources :groups do
resources :posts
end
<%= form_for #post, :url => group_posts_path(params[:group_id]) do |f| %>
....
<% end %>
def create
#group = Group.find(1)
#post = #group.posts.build(params[:post])
#post.user_id = current_user.id
respond_to do |format|
if #post.save
.....
end
end
end
Thank you.
Use the accepts_nested_attributes_for method in your model.
If you're not familiar with nested forms check out this railscast and the second part for further information.
In view:
<%= form_for [#group, #group.posts.build] do |f| %>
...
<% end %>
In controller:
class PostsController < ApplicationController
before_filter :find_group
...
def create
#post = #group.posts.build(params[:post])
current_user.posts << #post
end
protected
def find_group
#group = Group.find(params[:group_id])
end
end
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!
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
I have an Issue model and a Comment model. On the issue#show view, I have a comment form. I create the #comment for the form in the issue#show controller action, and then pass it to the comment#create controller action to actually create and save the comment to the db. However, once the #comment params are passed to the comment#create action, I no longer have the issue_id information that I need. How would I pass that information? Here are my files:
<%= form_for #comment do |f| %>
<%= render 'comment_fields', :f => f %>
<%= f.submit "Submit" %>
<% end %>
issue controller:
def show
#issue = Issue.find(params[:id])
#votes = Votership.where(:issue_id => #issue.id)
#current_user_vote = #votes.where(:user_id => current_user.id).first
#comment = Comment.new
end
and the comment controller:
def create
#comment = Comment.new(params[:comment])
#comment.save
redirect_to :back
end
You just need to modify the way you create your #comment in the show action
def show
#issue = Issue.find(params[:id])
#votes = Votership.where(:issue_id => #issue.id)
#current_user_vote = #votes.where(:user_id => current_user.id).first
#comment = #issue.comments.build # assigns issue_id to comment
end
Now when you render the form for #comment, the issue_id should be present in the hidden form inputs
This is unrelated to your question, but I'm also noticing the way you're loading #current_user_vote
#current_user_vote = #votes.where(:user_id => current_user.id).first
You should probably do this as:
#current_user_vote = current_user.votes.first
If I understand well, an issue may have many comments and a comment belongs to an issue?
# config/routes.rb
# Nest the comment under the issue
resources :issues do
resources :comments, only[:create]
end
# app/models/issue.rb
has_many :comments
# app/models/comment.rb
belongs_to :issue
# app/controllers/issues_controller.rb
def show
#issue = Issue.find params[:id]
...
end
# app/views/issues/show.html.erb
<%= form_for [#issue, #issue.comments.build] do |f| %>
....
<% end %>
# app/controllers/comments_controller.rb
def create
#issue = Issue.find params[:issue_id]
#comment = #issue.comments.build params[:comment]
if #comment.save
redirect_to #issue
else
render 'issues/show' # => it will know which issue you are talking about, don't worry
end
end
# or if you don't need validation on comment:
def create
#issue = Issue.find params[:issue_id]
#issue.comments.create params[:comment]
redirect_to #issue
end
issues#show a little bit looks weird.
def show
#issue = Issue.find params[:id]
#votes = #issue.voterships
#current_user_vote = current_user.votes.first
# maybe you want to list all the comments: #comments = #issue.comments
end