Tutorial :form_for with nested resources



Question:

I have a two-part question about form_for and nested resources. Let's say I'm writing a blog engine and I want to relate a comment to an article. I've defined a nested resource as follows:

map.resources :articles do |articles|      articles.resources :comments  end  

The comment form is in the show.html.erb view for articles, underneath the article itself, for instance like this:

<%= render :partial => "articles/article" %>  <% form_for([ :article, @comment]) do |f| %>      <%= f.text_area :text %>      <%= submit_tag "Submit" %>  <%  end %>  

This gives an error, "Called id for nil, which would mistakenly etc." I've also tried

<% form_for @article, @comment do |f| %>  

Which renders correctly but relates f.text_area to the article's 'text' field instead of the comment's, and presents the html for the article.text attribute in that text area. So I seem to have this wrong as well. What I want is a form whose 'submit' will call the create action on CommentsController, with an article_id in the params, for instance a post request to /articles/1/comments.

The second part to my question is, what's the best way to create the comment instance to begin with? I'm creating a @comment in the show action of the ArticlesController, so a comment object will be in scope for the form_for helper. Then in the create action of the CommentsController, I create new @comment using the params passed in from the form_for.

Thanks!


Solution:1

Travis R is correct. (I wish I could upvote ya.) I just got this working myself. With these routes:

resources :articles do    resources :comments  end  

You get paths like:

/articles/42  /articles/42/comments/99  

routed to controllers at

app/controllers/articles_controller.rb  app/controllers/comments_controller.rb  

just as it says at http://guides.rubyonrails.org/routing.html#nested-resources, with no special namespaces.

But partials and forms become tricky. Note the square brackets:

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

Most important, if you want a URI, you may need something like this:

article_comment_path(@article, @comment)  

Alternatively:

[@article, @comment]  

as described at http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects

For example, inside a collections partial with comment_item supplied for iteration,

<%= link_to "delete", article_comment_path(@article, comment_item),        :method => :delete, :confirm => "Really?" %>  

What jamuraa says may work in the context of Article, but it did not work for me in various other ways.

There is a lot of discussion related to nested resources, e.g. http://weblog.jamisbuck.org/2007/2/5/nesting-resources

Interestingly, I just learned that most people's unit-tests are not actually testing all paths. When people follow jamisbuck's suggestion, they end up with two ways to get at nested resources. Their unit-tests will generally get/post to the simplest:

# POST /comments  post :create, :comment => {:article_id=>42, ...}  

In order to test the route that they may prefer, they need to do it this way:

# POST /articles/42/comments  post :create, :article_id => 42, :comment => {...}  

I learned this because my unit-tests started failing when I switched from this:

resources :comments  resources :articles do    resources :comments  end  

to this:

resources :comments, :only => [:destroy, :show, :edit, :update]  resources :articles do    resources :comments, :only => [:create, :index, :new]  end  

I guess it's ok to have duplicate routes, and to miss a few unit-tests. (Why test? Because even if the user never sees the duplicates, your forms may refer to them, either implicitly or via named routes.) Still, to minimize needless duplication, I recommend this:

resources :comments  resources :articles do    resources :comments, :only => [:create, :index, :new]  end  

Sorry for the long answer. Not many people are aware of the subtleties, I think.


Solution:2

Be sure to have both objects created in controller: @post and @comment for the post, eg:

@post = Post.find params[:post_id]  @comment = Comment.new(:post=>@post)  

Then in view:

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

Be sure to explicitly define the array in the form_for, not just comma separated like you have above.


Solution:3

You don't need to do special things in the form. You just build the comment correctly in the show action:

class ArticlesController < ActionController::Base    ....    def show      @article = Article.find(params[:id])      @new_comment = @article.comments.build    end    ....  end  

and then make a form for it in the article view:

<% form_for @new_comment do |f| %>     <%= f.text_area :text %>     <%= f.submit "Post Comment" %>  <% end %>  

by default, this comment will go to the create action of CommentsController, which you will then probably want to put redirect :back into so you're routed back to the Article page.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »