0

In my rails website I want to make a form with nested resources. More specificly, I want to add a comment section to my blogposts.

The solution I found on several places is creating a form with the rails helper form_for and binding both the @post instance variable and the @comment instance variable to the form like this

<%= form_for[@post, @comment] do |f| %>

Now I was hoping that someone could explain me why it doesn't work to just bind it to @comment, even if you that comment has a foreign key to @post? So in other words I still need to bind the form to both instance variables, even if I defined the following in my show action.

@post = Post.find(params[:id])
@comment = @post.comments.new

Rails is pretty smart, why isn't it able to figure out that @comment belongs to @post?

3
  • It should work. If you do as you suggest, you can use form_for @comment... in your view and you'll get back comment attribute values which you can use to create a comment instance which is bound to the right post. Commented Jul 30, 2015 at 15:31
  • For me it doesn't work, I get an error undefined method 'comments_path' when I don't bind the form to @post Commented Jul 30, 2015 at 15:36
  • Yes as @eiriker mentions below, you would need a resource, but he makes an excellent point that this is not necessarily a good idea. Commented Jul 30, 2015 at 15:46

3 Answers 3

3

@post.comments.new builds a new comment without saving it to the database. This non-persisted comment is then used to construct a form. Once you submit the form, the create action will build then save the comment only with the data submitted by the form. If post_id is not included in that data, Rails has no idea that the first comment had this value, since it wasn't persisted to the database.

As such, you can get around this, if desired, by adding the post_id field to the form:

<%= form_for @comment do |f| %>
  <%= f.hidden_field :post_id %>

This would obviously require a non-nested resources :comments in your routes.rb

However, I don't see a good reason to do this. Using the nested routes, e.g. the form in your question, guarantees that I have a valid post_id attached to the @post in question without having to add an extra field.

To put it another way, lets say I'm a spammer who saw the post_id field in the form. I could easily write code that would submit the form with my spam comment for all possible values of post_id between 1 and 1000000. When you used nested routes, not only is it more difficult to do this, but you can more easily check the validity of the @post object, whether a user can comment on it, etc. Obviously, this isn't a full-featured anti-spam system, but it can make spamming marginally more difficult while making it easier for you to perform the checks you need.

Sign up to request clarification or add additional context in comments.

1 Comment

I'm not sure who to give the accepted answer because all of you informed me well. Thank you for this
1

That is how nested_resources work. When you run rake routes you will find this route

POST   /posts/:post_id/comments(.:format)   comments#create

This means a value for :post_id key should be provided like defining @post = Post.find(params[:post_id])

When you try to give just <%= form_for @comment do |f| %>, Rails will trigger an error No route matches comment_path because it treats it as a non-nested resource

1 Comment

I'm not sure who to give the accepted answer because all of you informed me well. Thank you for this
1

If you really didn't want to pass in both models to the form, you can manually set the action so that it will work. Probably something like this:

<%= form_for @comment, url: post_comments_path(@comment.post) do |f| %>

assuming @comment.post is defined.

But, I can't see the advantage to doing it this way.

As to 'why' Rails can't figure this out without the explicit inclusion of post, I would think it is because comment could possibly be associated with something other than a post and thus Rails cannot make any assumptions (e.g., you could allow comments to be associated with other comments).

1 Comment

I'm not sure who to give the accepted answer because all of you informed me well. Thank you for this

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.