如何通过Rails 3.2 mass-assignment提交多个NEW项目

我有一个非常标准的用例。 我有一个父对象和一个子对象的列表。 我想要一个表格forms,可以一次编辑所有的孩子,就像表格中的行一样。 我也希望能够插入一个或多个新的行,并提交它们被创build为新的logging。

当我使用fields_for为has-many相关的嵌套logging呈现一系列子表单时,rails会生成字段名,例如parent[children_attributes][0][fieldname]parent[children_attributes][1][fieldname]等上。

这会导致Rackparsing一个参数哈希,如下所示:

 { "parent" => { "children" => { "0" => { ... }, "1" => { ... } } } 

当传递一个新的 (未fields_for )对象时,相同的fields_for将生成一个字段名称,如下所示:

 parent[children_attributes][][fieldname] 

注意其中没有索引的[]

不能与包含[0][1]等的字段以相同的forms发布,因为Rack变得困惑并引发

 TypeError: expected Array (got Rack::Utils::KeySpaceConstrainedParams) 

“OK”,认为我“我会确保所有的字段使用[]forms,而不是[index]forms。但我不知道如何说服fields_for做到这一贯。即使我给它是一个明确的字段名称前缀和对象:

 fields_for 'parent[children_attributes][]', child do |f| ... 

只要child持久,它会自动修改字段名,使他们成为例如parent[children_attributes][0][fieldname] ,同时留下新logging的字段名为parent[children_attributes][][fieldname] 。 再一次,Rack barfs。

我不知所措 我怎么使用像fields_for这样的标准Rails助手来提交多个logging,以及现有的logging,让它们在params中被parsing为一个数组,并且把缺lessID的所​​有logging都创build为数据库中的新logging? 我运气不好,我只需要手动生成所有的字段名称?

正如其他人所提到的那样, []应该包含新logging的关键字,因为否则它会将一个散列与一个数组types混合。 您可以在child_index上使用child_index选项来设置。

 f.fields_for :items, Item.new, child_index: "NEW_ITEM" # ... 

我通常使用object_id来做到这一点,以确保在有多个新项目的情况下它是唯一的。

 item = Item.new f.fields_for :items, item, child_index: item.object_id # ... 

这是一个抽象的帮手方法,这样做。 这假设有一个部分与它将呈现的item_fields的名称。

 def link_to_add_fields(name, f, association) new_object = f.object.send(association).klass.new id = new_object.object_id fields = f.fields_for(association, new_object, child_index: id) do |builder| render(association.to_s.singularize + "_fields", f: builder) end link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")}) end 

你可以像这样使用它。 参数是:链接的名称,父级的表单构build器以及父级模型上关联的名称。

 <%= link_to_add_fields "Add Item", f, :items %> 

这里有一些CoffeeScript来监听该链接的点击事件,插入这些字段,并用当前时间更新对象标识符以给它一个唯一的键值。

 jQuery -> $('form').on 'click', '.add_fields', (event) -> time = new Date().getTime() regexp = new RegExp($(this).data('id'), 'g') $(this).before($(this).data('fields').replace(regexp, time)) event.preventDefault() 

该代码是从这个需要付费订阅的RailsCasts Pro集 。 但是, 在GitHub上可以免费获得一个完整的工作示例。

更新:我想指出,插入一个child_index占位符并不总是必要的。 如果您不想使用JavaScriptdynamic插入新logging,则可以提前构build它们:

 def new @project = Project.new 3.times { @project.items.build } end <%= f.fields_for :items do |builder| %> 

Rails会自动为新logging插入一个索引,所以它应该可以正常工作。

所以,我对我最经常看到的解决scheme并不满意,那就是为服务器端或客户端JS中的新元素生成一个伪索引。 这感觉就像一个混乱,特别是鉴于Rails / Rack完全能够parsing项目列表,只要他们都使用空括号( [] )作为索引。 下面是我所结束的代码的近似值:

 # note that this is NOT f.fields_for. fields_for 'parent[children_attributes][]', child, index: nil do |f| f.label :name f.text_field :name # ... end 

[]结尾字段名称前缀,再加上index: nil选项,将禁用索引生成Rails,以便帮助尝试提供持久对象。 这段代码适用于新的和保存的对象。 得到的表单参数,因为他们一贯使用[] ,被parsing成params的数组:

 params[:parent][:children_attributes] # => [{"name" => "..."}, {...}] 

Parent#children_attributes=方法生成accepts_nested_attributes_for :children处理这个数组很好,更新已更改的logging,添加新的(缺less"id"键),并删除与"_destroy"键集。

我仍然困扰Rails使这如此困难,我不得不恢复到硬编码的字段名称前缀string,而不是使用例如f.fields_for :children, index: nil 。 为了logging,甚至做到以下几点:

 f.fields_for :children, index: nil, child_index: nil do |f| ... 

…无法禁用字段索引生成。

我正在考虑编写一个Rails补丁来使这个更容易,但是我不知道是否有足够的人在意或者甚至会被接受。

编辑:用户@Macario已经告诉我为什么Rails喜欢字段名称中的显式索引:一旦你进入三层嵌套模型,需要有一种方法来区分哪一个第三级属性属于二级模型。

常见的解决scheme是在[]中添加一个占位符,并在将片段插入到表单中时将其replace为唯一的数字。 时间戳大部分时间工作。

也许你应该只是作弊。 把新logging放在一个不同的人为属性,这个属性是实际的装饰器。

 parent[children_attributes][0][fieldname] parent[new_children_attributes][][fieldname] 

这不是很好,但它应该工作。 可能需要额外的努力来支持validation错误的forms的往返。

我在所有的最后一个项目中都遇到过这个用户案例,我期待这个继续下去,正如julian7指出的那样,在[]中提供一个唯一的id是必须的。 在我看来,这通过js更好地完成。 我一直在拖动和改进一个jQuery插件来处理这种情况。 它适用于现有的logging,并添加新的logging,但期望一定的标记,并优雅地退化,inheritance人的代码和一个例子:

https://gist.github.com/3096634

使用插件的注意事项:

  1. fields_for调用应该被封装在一个<fieldset> ,其data-association属性等于模型的复数名称,以及一个类“nested_models”。

  2. 在调用fields_for之前,应该在视图中构build一个对象。

  3. perse的对象字段应该被封装在一个带有“new”类的<fieldset> ,但是只有当logging是新的(不记得我是否删除了这个需求)。

  4. 标签内的“_destroy”属性的checkbox必须存在,插件将使用标签文本创build销毁链接。

  5. 类“add_record”的链接应该存在于fieldset.nested_models中,但是在包含模型字段的fieldset之外。

公寓从这个讨厌它一直在为我工作的奇迹。
检查要点后,这个要求必须更清楚。 请让我知道,如果你改善了代码或如果你使用它:)。
顺便说一句,我的灵感来自瑞安·贝茨第一个嵌套模型的屏幕录像。

长post删除

瑞安有一个插曲: http : //railscasts.com/episodes/196-nested-model-form-revised

它看起来像你需要手动生成唯一的索引。 瑞安用这个object_id

我想你可以通过将logging的ID作为隐藏字段来使其工作

有一个叫做蚕茧的gem,我会去做一个更精确的DIY,但它是专门为这种情况build造的。