带有嵌套资源的form_for


125

我有一个关于form_for和嵌套资源的两部分问题。假设我正在编写博客引擎,并且想将评论与文章相关联。我定义了一个嵌套资源,如下所示:

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

评论表单位于文章本身的show.html.erb视图中,位于文章本身下方,例如:

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

这给出一个错误,“将id称为nil,这会错误地等”。我也尝试过

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

可以正确呈现,但将f.text_area与文章的“文本”字段而不是评论的字段相关联,并在该文本区域中显示article.text属性的html。所以我似乎也有这个错误。我想要的是一种表单,其“提交”将调用CommentsController上的create动作,在params中带有article_id,例如对/ articles / 1 / comments的发布请求。

我的问题的第二部分是,创建注释实例的最佳方法是什么?我在ArticlesController的show动作中创建一个@comment,因此comment对象将在form_for帮助器的范围内。然后,在CommentsController的create动作中,我使用从form_for传入的参数创建新的@comment。

谢谢!

Answers:


228

Travis R是正确的。(我希望我能投票赞成。)我只是自己完成了这项工作。使用这些路线:

resources :articles do
  resources :comments
end

您会得到如下路径:

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

路由到控制器

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

就像在http://guides.rubyonrails.org/routing.html#nested-resources上所说的那样,没有特殊的名称空间。

但是部分和形式变得棘手。请注意方括号:

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

最重要的是,如果您需要URI,则可能需要以下内容:

article_comment_path(@article, @comment)

或者:

[@article, @comment]

http://edgeguides.rubyonrails.org/routing.html#creating-paths-and-urls-from-objects中所述

例如,在comment_item提供了用于迭代的部分集合中,

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

jamuraa所说的话可能在Article的背景下起作用,但它对我而言没有其他多种作用。

关于嵌套资源的讨论很多,例如http://weblog.jamisbuck.org/2007/2/5/nesting-resources

有趣的是,我刚刚了解到,大多数人的单元测试实际上并未测试所有路径。当人们遵循jamisbuck的建议时,他们最终会以两种方式获取嵌套资源。他们的单元测试通常将最简单地获得/发布:

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

为了测试他们喜欢的路线,他们需要这样做:

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

我之所以学到这是因为,当我从此切换时,我的单元测试开始失败了:

resources :comments
resources :articles do
  resources :comments
end

对此:

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

我想可以有重复的路线,也可以错过一些单元测试。(为什么要测试?因为即使用户从未看到过重复项,您的表单也可能隐式或通过命名路径引用它们。)但是,为了尽量减少不必要的重复项,我建议这样做:

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

很抱歉,答案很长。我认为没有多少人知道这些细微之处。


这是工作,但我不得不像jamuraa所说的那样修改控制器。
Marcus Becker

Jam的方法行得通,但是您最终可能会得到一些您可能不知道的额外路线。最好是明确的。
cdunn2001 2014年

我在@course内嵌套了资源@result。虽然[@result, @course]有效,但form_for(@result, url: { action: "create" }) 也有效。这仅需要最后一个模型名称和方法名称。
Anwar

@ cdunn2001请您解释一下为什么我们必须像这样在这里提到“ @article”,这意味着什么?以下语法有什么作用?:<%= form_for [@article,@comment]做| f | %>
Arpit Agarwal

1
Travis / @ cdunn2001正确。当您使用没有重复项的嵌套路由时,不要同时设置父项和资源,否则它将认为所有操作都是嵌套的。同样,如果您嵌套了所有内容,则始终设置AT.parent。另外,如果您有一个带有带有部分嵌套路线的“取消”按钮的通用表单,则请使用以下路径,以便无论您设置了哪个路径(注意子项的复数形式):<%= link_to'Cancel',parent_children_path(AT.parent || AT.child.parent)%>
iheggie

54

确保在controller:@post@comment帖子中都创建了两个对象,例如:

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

然后来看:

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

确保在form_for中明确定义数组,而不仅仅是像上面一样用逗号分隔。


特拉维斯(Travis)的答案有些老套,但我认为它对于Rails 3.2.X是最正确的。如果要让表单构建器的所有元素填充“注释”字段,只需使用一个数组,则不需要URL帮助器。
卡尔,

1
仅设置嵌套操作的父对象。如果您仅部分嵌套资源(例如,按照示例),那么设置父对象将导致form_for失败(刚刚在rails 5.1上再次确认)
iheggie

35

您不需要在表单中做特殊的事情。您只需在show操作中正确构建注释即可:

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

然后在文章视图中为其创建表格:

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

默认情况下,此注释将转到的create操作CommentsController,您可能要将该注释redirect :back放入,以便将您路由到该Article页面。


10
我必须使用form_for([@article, @new_comment])格式。我认为这是因为我comments#new没有展示的观点article#new_comment。我认为article#new_commentRails足够聪明,可以计算出嵌套在其中的注释对象,这样就不必指定它了吗?
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.