Rails: Routing Error, comment model to micropost model - ruby-on-rails

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

Related

Adding comments to article in Rails 5 (deeply nested resources)

I have a Rails app that has three main models: Qn, Ans, and Comments. I've been doing okay with 1-level-deep nested resources, but these three resources are nested deeply (Comments is shallowly nested) and they are all displayed in one single view, which makes it very confusing.
In a url like: http://localhost:3000/questions/2, the user can see all #question.answers displayed using a loop. In each of those answers, the user can see the answer.comments displayed using a loop. Below each answer, the user can also submit a new comment.
But after attempting several times to implement a 1) loop displaying all comments and 2) form for new comment, I always get some error along the lines of:
undefined method `model_name' for {:url=>"/questions/4/answers/2/comments/new"}:Hash
So I tried to pass in params #commentable instead of the answer, or point to the specific controller and action and so on, but none of these methods worked. I am guessing that I have an issue with my controllers to begin with, but I cannot seem to figure out what.
routes.rb (top ommited)
# Resources
resources :sessions
resources :users
resources :bookmarks # to be implemented later
resources :questions do
resources :answers do
resources :comments, shallow: true
end
end
Question model
class Question < ApplicationRecord
has_many :answers
has_many :bookmarks #later
end
Answer model:
class Answer < ApplicationRecord
belongs_to :question
has_many :comments, as: :commentable
has_many :likes, as: :likeable
validates :answercontent, length: {minimum: 50}
end
Comment model:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
The show.html.erb (of QuestionsController)
<% #question.answers.each do |answer| %>
// ommited
<!-- Comments -->
<% answer.comments.each do |comment| %>
<%= comment.content %>
<br>
<% end %>
<!-- Submit new comment -->
<%= form_for(url: new_question_answer_comment_path, comment: {answer_id: answer.id, question_id: #question.id}) do |f| %>
<%= f.text_area :content %>
<%= f.submit "Submit" %>
<% end %>
<% end %>
QuestionsController (new, create, destroy ommited for brevity)
class QuestionsController < ApplicationController
def index
#questions = Question.all
end
def show
#question = Question.find(params[:id])
#answers = Answer.all
# Delete only appears when num_ans is 0
#deletable = (current_user== User.find(#question.user_id)) && (#question.answers.all.size==0)
end
private
def question_params
params.require(:question).permit(:picture_url, :country, :educational_level, :topic)
end
end
AnswersController (edit, update, destroy ommited for brevity)
class AnswersController < ApplicationController
def create
#question = Question.find(params[:question_id])
#answer = #question.answers.create(answer_params)
#answer.question_id = #question.id
#answer.user_id = current_user.id
if #answer.save
redirect_to #question
else
render :new
end
end
private
def answer_params
params.require(:answer).permit(:user_id, :question_id, :answercontent)
end
end
CommentsController
class CommentsController < ApplicationController
before_filter: load_commentable
def index
#commentable = Answer.find(params[:answer_id])
#comments = #commentable.comments
end
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(params[:comment])
if #comment.save
redirect_to #commentable
else
render :new
end
end
# From RailsCast ep.154
private
def load_commentable
resource, id = request.path.split('/')[1,2]
#commentable = resource.singularize.classify.constantize.find(id)
end
end
The routes
are quite messy right now so I will just post where the comments are:
question_answer_comments GET /questions/:question_id/answers/:answer_id/comments(.:format) comments#index
POST /questions/:question_id/answers/:answer_id/comments(.:format) comments#create
new_question_answer_comment GET /questions/:question_id/answers/:answer_id/comments/new(.:format) comments#new
edit_comment GET /comments/:id/edit(.:format) comments#edit
comment GET /comments/:id(.:format) comments#show
PATCH /comments/:id(.:format) comments#update
PUT /comments/:id(.:format) comments#update
DELETE /comments/:id(.:format) comments#destroy
Thanks in advance for the help.
Update:
To give you more info on what solutions I attempted:
1. Passing in two params like:
<%= form_for([answer, #comment], url: new_question_answer_comment_path(answer.id, #question.id)) do |f| %>
Gave me:
First argument in form cannot contain nil or be empty
Using #commentable (which is basically the answer) gives me an error saying that 'the id in #commentable.id doesn't exist as #commentable is nil'.
So I think the problem is that answer or #commentable is nil. But I specified it in the loop and in the controller too. So what else may I try?
form_for expects record as first argument, in your case it should be a comment instance. Also new_question_answer_comment_path expects values for question_id and answer_id keys, as you are creating a new comment, the route should be question_answer_comments not new_question_answer_comment so your form_for should be
<%= form_for Comment.new,url: question_answer_comments_path(#question,answer) do |f| %>
<%= f.text_area :content %>
<%= f.submit "Submit" %>
<% end %>
or just
<%= form_for [Comment.new,#question,answer] do |f| %>
<%= f.text_area :content %>
<%= f.submit "Submit" %>
<% end %>

Can't mass assign a nested attribute?

I'm trying to assign comments which belongs_to answers (and answers has_many comments) and get a can't assign mass protected attributes: answer.
Here is my commentsController
class CommentsController < ApplicationController
def create
#answer = Answer.find(params[:answer_id])
#comment = #answer.comments.new(params[:comment])
#comment.save
redirect_to question_path(#answer)
end
end
Here is my answers controller for the page I am using
def show
#answer = Answer.find(params[:id])
#comment = #answer.comments.new(params[:comment])
end
My answer model accepts_nested_attributes for comments.
And this is my form,
<%= form_for([#answer, #comment]) do |f| %>
<p>
</p>
<p>
<%= f.label :comment %>
<%= f.text_area :answer, :cols => "50", :rows => "30"%>
</p>
<p>
<%= f.submit "Submit Comment" %>
</p>
Any ideas?
you should permit params before using it in your comments controller, in rails4 u can do this
class CommentsController < ApplicationController
def create
#answer = Answer.find(params[:answer_id])
#comment = #answer.comments.new(params.require(:comment).permit(:answer))
#comment.save
redirect_to question_path(#answer)
end
end
or u can also define a method in comments controller for permitting params as
def comment_params
params.require(:comment).permit(:answer)
end
then in your controller create a new record using this function as-
#comment = #answer.comments.new(comment_params)
#comment.save
as u had said u have nested attrs for comments in answer something like
class Answer < ActiveRecord::Base
has_many :comments, dependent: :destroy
accepts_nested_attributes_for :comments
end
and u are also using attr_accessible so to permit params in nested form u have to do
attr_accessible :comments_attributes along with your answer attrs in your answer.rb

rails 4 no data is being transferred to db

I'm trying to do user reviews, when user can write review to another user, i create tables review with :content, user_reviews with :for_user_id, and by_user_id,
my routes
devise_for :users
resources :users, :only => [:show] do
resources :reviews
end
class Review < ActiveRecord::Base
belongs_to :user
has_many :users, :through => :users_reviews
end
class User < ActiveRecord::Base
has_many :users_review
has_many :reviews, :through => :users_review
end
class UsersReview < ActiveRecord::Base
belongs_to :user
belongs_to :review
end
class ReviewsController < ApplicationController
def new
#user = User.find(params[:user_id])
#review = #user.reviews.new(params[:for_user_id])
end
def create
#user = User.find(params[:id])
#review = current_user.reviews.build(review_params)
redirect_to root_path
end
def show
end
def index
#user = User.find(params[:for_user_id])
#reviews = Review.all
end
private
def review_params
params.require(:review).permit(:user_id, :user_id, :content)
end
end
and my view
<%= form_for([#user, #user.reviews.build]) do |f| %>
<%= f.text_area :content, placeholder: "Your review" %>
<%= f.submit "Go", class: "btn btn-large btn-primary" %>
<% end %>
all work, but no data send to the db :\ what i doing wrong?
You're not saving anything in the create method, therefore nothing is going to persist.
You'll want something like:
def create
#user = User.find(params[:id])
#review = #user.reviews.build(review_params)
if #user.save && #review.save
redirect_to root_path
else
(handle bad data)
end
end
I would also tend to agree with #marzapower - If you want to use current_user, you don't need the line above #review. My method above has this change included.
You are missing a call to the save method in your create action:
def create
#user = User.find(params[:id])
#review = current_user.reviews.build(review_params)
#review.save
redirect_to root_path
end
This post explains the difference between build and create.
I think you're missing two things. The User and Review classes both need to reference the UserReview class that they employ in the through relationship, e.g.,
class User < ActiveRecord::Base
...
has_many :user_reviews_received, class_name: 'UserReview', foreign_key: :for_user_id
has_many :reviews_received, through: :user_reviews_received, class_name: 'Review'
has_many :user_reviews_written, class_name: 'UserReview', foreign_key: :by_user_id
has_many :reviews_written, through: :user_reviews_written, class_name: 'Review'
...
end
As noted above, the create action seems a bit confused. Generally speaking the params should be the same for building up an instance in the new and create actions but you've got some differences. You could resolve this in one of two ways. One option would be to resolve it in the controller.
class ReviewsController < ActionController::Base
def new
#user = User.find(params[:user_id])
#review = #user.reviews_received.new(by_user_id: current_user.id)
end
def create
#review = #user.reviews_received.create(params[:review].merge(by_user_id: current_user.id))
redirect_to root_path
end
end
The second option would be to resolve it in the view (new first line in the form).
<%= form_for([#user, #user.reviews_received.build(by_user_id: current_user.id)]) do |f| %>
<%= f.hidden_field :by_user_id %>
<%= f.text_area :content, placeholder: "Your review" %>
<%= f.submit "Go", class: "btn btn-large btn-primary" %>
<% end %>
I would tend towards something like the first option
There's an error in the controller, I think:
def create
#user = User.find(params[:id])
#review = #user.reviews.build(review_params)
#review.save
redirect_to root_path
end
You were creating new Reviews for current_user instead of #user.

Micropost's comments on users page (Ruby on Rails)

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.

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