I am still a newbie so please forgive this silly question. I got 3 models:
- User (generated by devise), Comment and Post
User has many posts and comments
Comment belongs to both post and user
Post has many comments and belongs to user
My routes.rb
resources :users do
resources :posts do
resources :comments
end
end
My form code:
<%= form_for([#user,#post,#comment]) do |f| %>
...
<% end %>
I want to generate to user_post_comments_path but the above form_for generate to post_comments_path. Why? Did I misunderstand something. Thanks alot
The Rails routing and Form handling is very confusing and I still get it wrong all the time...
I think that your problem is that some of your variables is not set (nil) and so rails can not determine what you are actually up to.
I would also like to recommend to you that you don't nest your routes like that unless you have to for the sake of the urls.
It's usually enough to nest one level deep and use the current_user to assign to models when creating them. This also reduces the security risk involved when posting ids of other users:
def create
#post = current_user.posts.build(post_params)
[...]
end
def create
#comment = current_user.comments.build(comment_params)
#comment.post = Post.find params[:post_id]
[...]
end
Related
I am making this form for a reply function in a blog-like app with the same recipe as the comment in which it should nest. (Comment recipe)
I get the following error when I try to view my app in the browser:
No route matches {:action=>"index", :post_id=>"10", :controller=>"replies", :comment_id=>nil} missing required keys: [:comment_id]
This is my reply view file for my replies/_form.html.haml
%h5 Reply
= form_for [ #post, #comment, #reply ] do |f|
%p
= f.label :name
%br
= f.text_field :name
%p
= f.label :talk
%br
= f.text_area :talk
%p
= f.submit 'Submit'
This is my replies controller getting a hold on the comments_id much like the recipe said I should do between the comments and the post_id:
class RepliesController < ApplicationController
def create
#reply = Reply.new(reply_params)
#reply.comment_id = params[:comment_id]
#reply.save
redirect_to post_path(#reply.comment.post)
end
end
And this is my id passing in the comments show controller as it is similar in the post show controller. Or should I add something more to the post show controller now?
def show
#reply = Reply.new
#reply.comment_id = #comment.id
end
I tried adding replies though the rails console. They show up neatly, so I think my routes file works. Something with the id's and the handling of the collections isn't going great though. The form part is not working.
I don't like adding gems if I don't have to, I want to understand my app.
EDIT: I should probably add that my app has a view that looks like a indexing-form system in a indexing-form system.
Below the post there are comments - with a form, and below these there are replies - with a form.
EDIT 2: First argument in form cannot contain nil or be empty is what I get now al the time.
EDIT 3: I still can't make my forms under the comments. This is my routes file, maybe it clarifies.
resources :posts do
resources :comments do
resources :replies do
end
end
end
I am debugging now by making print outs and found out that in my _form haml file for a reply rails can find the #post, but not the comment nor replies (but they have to be created of course with the form), while I can get almost exactly the same structure to work in my _form for a comment.
Is it even possible in Rails to have multiple forms printed out on the same page?
Still all help is appreciated!
EDIT 4: I have gotten a little further. Now Rails says:
No route matches {:action=>"index", :post_id=>"2", :controller=>"replies", :comment_id=>nil} missing required keys: [:comment_id]
As a direct effect of changing my show action in the post controller:
def show
#post = Post.find(params[:id])
## create a blank comment
#comment = Phase.new
#comment.post_id = #post.id
##
## The same for a blank reply
#reply = Reply.new
#reply.comment_id = #comment.id
##
end
The last line of this action seems to not make any difference. Now I just need to be able to grab this comment_id in the reply form and then I am done. Nearly a week of struggle.
I believe you may not be getting a #comment.id because you are doing Comment.new(reply_params) and objects only get id fields whens you use .create OR .save after instantiating with new. Let me know if this helps!
Because of this you are getting the nil error for #comment in the form.
EDIT
It seems like you are looking to get the comment_id from the form, another solution would be to get it from the URL by having this happen under comments path. You'd have to nest your resources so it's something like comments/:id/reply/:reply_id or comments/:id/reply/new.
Here's some great examples for nested attributes: http://guides.rubyonrails.org/routing.html#nested-resources
SECOND EDIT
Okay, I was a bit confused because typically you would have the form in new not in show. Either way I would be looking at the instance variables being passed in. How do your associations look?
if it's Post has_many :comments and Comment has_many :replies, then you can do something like this in your show actions:
def new
#post = Post.find(params[:post_id]
#comment = #post.comments.find(params[:comment_id]) #scopes your search to only the comments belonging to this post
#reply = #comment.replies.new
end
The more important point to get out of this is that each instance variable needs to be passed in. that being said, I'm not sure which ones are currently working for you
As far as create you should be doing something similar, creating through the associations. This rails magic handles all the foreign keys and assigning the id's etc:
def create
#post = Post.find(params[:post_id]
#comment = Comment.find(params[:comment_id])
#reply = #comment.replies.new(reply_params)
if #reply.save
# some logic
redirect_to wherever_path
else
# other logic
render :new
end
end
The more important point to get out of this is that each instance variable needs to be passed in. that being said, I'm not sure which ones are currently working for you
Let me know how this works!
I have a double nested resource comments belongs to answer, answer belongs to question. I'm trying to create a comment with
def create
#answer = Answer.find(params[:answer_id])
#comment = #answer.comments.create(params[:comment])
redirect_to question_path(#question)
end
From the form like this:
form_for([#answer, #answer.comments.build])
However, I'm getting an undefined method error for render #answer.comments. Is my create action wrong?
EDIT: I have the associations such as, answer has_many comments, and comment belongs_to answer and the nested routing file for resources..
You probably want to refactor so you're not doing the logic of building the comments in the view.
Build the comment in the controller#new action for the form, then
form_for [#answer, #comment]
First thing first.
In order to use #answer.comments you need to have
has_many :comments
in your answer.rb (model)
Say I have the prototypical blog app in Rails. I would have a Post model which has many Comments. My routes.rb might look like this:
resources :post do
resources :comment
end
This means that for instance the edit path for a comment looks like this: /post/21/comment/42/edit.
It appears to make sense when we have a has many/belongs to relationship between two models.
However, once you notice that the id of the post is not really needed to find the comment (or even the post), it starts to make less sense.
To see what I mean, consider these two equivalent implementations of the edit action in the controller:
# Nested resource version
def edit
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
# ...
end
vs.
# Un-nested resource version
def edit
#comment = Comment.find(params[:id])
#post = #comment.post
# ...
end
My question is: Is there a use case for this I haven't considered? Or are nested resources only good for making pretty URLs?
for example, there's a Question model that has_many :answers
I'd like to show a form for new answer on the questions#show ( just like stackoverflow does )
routes would be something like:
resources :questions do
resources :answers
end
What is the best way to show the errors for an invalid record under these circumstances?
The problem is that I can't render an action I need from within AnswersController ( since it would be questions#show ), the only way seem to be redirect_to and store errors in flash hash.
It just looks like a pretty much common scenario, I believe there should be some better way
This may be one of a few cases where it's actually justified to add a new resourceful member route to your QuestionsController:
resources :questions do
post 'answer', :on => :member
end
which would recognize question/:id/answer with a POST request routed to questions#answer, allowing you keep all the logic in one controller:
class QuestionsController < ApplicationController
...
def show
#question = Question.find(params[:id])
end
def answer
#question = Question.find(params[:id])
#answer = #question.answers.build(params[:question][:answer])
if #answer.save
# show question with newly posted answer at url /question/:id
redirect_to #question
else
# show question with invalid editable answer at url /question/:id/answer
render 'show'
end
end
...
end
Explanation: In my opinion, the decision to handle the logic in one controller as opposed to two comes down to what you consider to be the resource of interest. Normally, you would consider each model to represent a distinct resource and thus create a separate controller to handle actions related to each resource. However, when there are multiple deeply coupled models for which multiple actions (e.g. show, new, create) are handled in a single view, it might be cleaner to think of the models as forming a single resource.
In this example, I think of the resource as a collective one consisting of both the question and its answers. Since this collective resource is uniquely identified by the question itself, I would let the question controller handle it. The show action in the questions controller already involves retrieving the collective question-answers resource, so you might think of the answer action (and potentially unanswer and reanswer actions) as the analogue of update to that collective resource.
Thinking of the resources this way is largely a matter of design preference, and there will be trade-offs depending on the requirements.
You can render questions#show from the AnswersController like this:
render :template => 'questions/show'
If you want to jump to a specific anchor on the page, you have to define that in your answer form:
<%= form_for(#answer, :url => question_answers_url(#question, :anchor => 'answer_form')) do |f| -%>
Have a partial which has the form which posts data to the answers_controller#create action.
So, in your questions#show page - show.html.erb, render a partial
<%= render :partial => "answers/answer_form" %>
In the _answer_form.html.erb, have a form which will post data to answers#create
<% form_for #answer do |f| %>
# have a text area
<% end %>
As far as the flash is concerned
In the AsnwersController
def create
#question = Question.find(params[:question_id])
#answer = Answer.build(params)
if #answer.save
flash[:notice] = "Answer was posted successfully"
else
flash[:error] = "There were a few errors please try again"
end
redirect_to question_path(#question)
end
I am trying to get the user_id of the user that is logged in. So a user will create a blog post and it will capture the id of the user when creating the blog post.
I know how to do this using nested resources but how can I get the user_id when creating a new blog post without nested resources?
the routes would look like this
resources :blogs
I also tried adding hidden field to blog form _blog.html.erb
<%= f.hidden_field :site_id, :value => #blog.site_id %>
The association is set up correctly I am just trying to see how I can get the user_id without nested resources. Any suggestions?
Pull the user id off the currently logged in user in the BlogsController#create method.
Example (Note that how you access the user_id will depend on how you're doing authentication)
class BlogsController < ApplicationController
def create
#blog = Blog.new params[:blog]
#blog.user_id = current_user.id
if #blog.save
...
end
end
end
if you are using devise/authlogic for authentication then you can easily get user_id using current_user method provided