如何通过 Rails 3.2 批量分配提交多个新项目



我有一个非常标准的用例。我有一个父对象和一个子对象列表。我想要一个表格形式,我可以一次编辑所有子项,作为表中的行。我还希望能够插入一个或多个新行,并在提交时将它们创建为记录。

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

这会导致 Rack 解析如下所示的参数哈希:

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

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

parent[children_attributes][][fieldname]

请注意没有索引的[]

不能与包含[0][1]等的字段以相同的形式发布,因为 Rack 会感到困惑并提出

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

好的,"我想,"我只是确保所有字段都使用[]形式而不是[index]形式。但是我不知道如何说服fields_for始终如一地这样做。即使我给它一个明确的字段名称前缀和对象:

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

只要child持久化,它就会自动修改字段名称,使它们成为例如 parent[children_attributes][0][fieldname],同时将新记录的字段名称保留为 parent[children_attributes][][fieldname] 。再一次,机架吠叫。

我不知所措。我到底如何使用像fields_for这样的标准 Rails 助手来提交多个记录以及现有记录,将它们解析为参数中的数组,并将所有缺少 ID 的记录创建为数据库中的新记录?我运气不好,我只需要手动生成所有字段名称吗?

正如其他人所提到的,[]应该包含新记录的键,否则它会将哈希与数组类型混合在一起。您可以使用fields_for上的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

你可以像这样使用它。参数包括:链接的名称、父模型的窗体生成器以及父模型上的关联名称。

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

这里有一些 CoffeeScript 来侦听该链接的点击事件,插入字段,并使用当前时间更新对象 id,以为其提供唯一键。

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占位符并不总是必要的。如果您不想使用 JavaScript 动态插入新记录,可以提前构建它们:

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

Rails 将自动为新记录插入索引,因此它应该可以正常工作。

因此,我对最常看到的解决方案不满意,即在服务器或客户端 JS 中为新元素生成伪索引。这感觉像是一个笨拙,特别是考虑到Rails/Rack完全有能力解析项目列表,只要它们都使用空括号([](作为索引。这是我最终使用的代码的近似值:

# 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,因此有助于尝试提供持久化对象。此代码段适用于新对象和已保存的对象。生成的表单参数,因为它们始终使用 [] ,在params中被解析为一个数组:

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

accepts_nested_attributes_for :children生成的Parent#children_attributes=方法可以很好地处理此数组,更新更改的记录,添加新记录(缺少"id"键的记录(,并删除具有"_destroy"键集的记录。

我仍然为Rails使这变得如此困难而烦恼,并且我不得不恢复为硬编码的字段名称前缀字符串,而不是使用例如 f.fields_for :children, index: nil .作为记录,即使执行以下操作:

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

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

我正在考虑编写一个 Rails 补丁来简化此操作,但我不知道是否有足够多的人关心它,或者它是否会被接受。

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

常见的解决方案是在 [] 中添加一个占位符,并在将代码段插入表单时将其替换为唯一编号。时间戳大部分时间都有效。

也许你应该作弊。将新记录放在不同的人造属性中,该属性是实际记录的装饰器。

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

它不漂亮,但它应该有效。可能需要一些额外的努力来支持对验证错误的表单进行往返。

我在最后的所有项目中都遇到过这个用户案例,我希望这种情况能继续下去,正如 julian7 指出的那样,有必要在 [] 中提供一个唯一的 id。在我看来,这最好通过 js 来完成。我一直在拖动和改进一个 jquery 插件来处理这种情况。它适用于现有记录和添加新记录,但需要一定的标记并且会优雅地降级,以下是代码和示例:

https://gist.github.com/3096634

使用插件的注意事项:

  1. fields_for调用应包装在数据关联属性等于模型复数名称的<fieldset>和类"nested_models"中。

  2. 应在调用 fields_for 之前在视图中生成对象。

  3. 对象字段 perse 应该包装在类"new"的<fieldset>中,但前提是记录是新的(不记得我是否删除了这个要求(。

  4. 标签内"_destroy"属性的复选框必须存在,插件将使用标签文本来创建销毁链接。

  5. 带有类"add_record"的链接应存在于fieldset.nested_models内,但存在于包含模型字段的字段集之外。

公寓从这个麻烦它一直在为我创造奇迹。
在检查要点后,此要求必须更加清晰。如果您改进了代码或:)使用它,请告诉我。
顺便说一句,我的灵感来自瑞安贝茨的第一个嵌套模型截屏视频。

长帖子已删除

瑞安对此有一集:http://railscasts.com/episodes/196-nested-model-form-revised

看起来您需要手动生成唯一索引。瑞安为此使用了object_id

我认为您可以通过将记录的 id 作为隐藏字段<</p>

div class="one_answers"来使其工作> 有一种

叫做茧的宝石可以做到这一点,我会选择更精简的 mor DIY aproach,但它是专门为这种情况而构建的。

最新更新