如何通过 Rails 3.2 批量分配提交多个新项目 [英] How to submit multiple NEW items via Rails 3.2 mass-assignment

查看:26
本文介绍了如何通过 Rails 3.2 批量分配提交多个新项目的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

{ "parent" =>{孩子们" =>{0" =>{ ... },1"=>{ ... } } }

当传递一个 new(非持久化)对象时,相同的 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].再一次,Rack barfs.

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

解决方案

正如其他人所提到的,[] 应该包含新记录的键,否则它会将哈希与数组类型混合.您可以使用 fields_for 上的 child_index 选项进行设置.

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

我通常使用 object_id 来执行此操作,以确保在有多个新项目时它是唯一的.

item = Item.newf.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.newid = new_object.object_idfields = f.fields_for(association, new_object, child_index: id) do |builder|渲染(association.to_s.singularize + "_fields", f: builder)结尾link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})结尾

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

<%= link_to_add_fields "添加项目", f, :items %>

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

jQuery ->$('form').on 'click', '.add_fields', (event) ->时间 = 新日期().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.new3.times { @project.items.build }结尾<%= f.fields_for :items do |builder|%>

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

I have a pretty standard use-case. I have a parent object and a list of child objects. I want to have a tabular form where I can edit all the children at once, as rows in the table. I also want to be able to insert one or more new rows, and on submit have them be created as new records.

When I use a fields_for to render a series of sub-forms for nested records related by has-many, rails generates field names e.g. parent[children_attributes][0][fieldname], parent[children_attributes][1][fieldname] and so on.

This causes Rack to parse a params hash that looks like:

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

When passed a new (un-persisted) object, the same fields_for will generate a field name that looks like:

parent[children_attributes][][fieldname]

Note the [] with no index in it.

This cannot be posted in the same form with the fields containing [0], [1], etc. because Rack gets confused and raises

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

"OK", thinks I. "I'll just make sure all the fields use the [] form instead of the [index] form. But I can't figure out how to convince fields_for to do this consistently. Even if I give it an explicit field name prefix and object:

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

So long as child is persisted, it will automatically modify the fieldnames so that they become e.g. parent[children_attributes][0][fieldname], while leaving fieldnames for new records as parent[children_attributes][][fieldname]. Once again, Rack barfs.

I'm at a loss. How the heck do I use standard Rails helpers like fields_for to submit multiple new records, along with existing records, have them be parsed as an array in the params, and have all the records lacking IDs be created as new records in the DB? Am I out of luck and I just have to generate all the field names manually?

解决方案

As others have mentioned, the [] should contain a key for new records because otherwise it is mixing a hash with an array type. You can set this with the child_index option on fields_for.

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

I usually do this using the object_id instead to ensure it is unique in case there are multiple new items.

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

Here's an abstract helper method that does this. This assumes there is a partial with the name of item_fields which it will render.

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

You can use it like this. The arguments are: the name of the link, the parent's form builder, and the name of the association on the parent model.

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

And here is some CoffeeScript to listen to the click event of that link, insert the fields, and update the object id with the current time to give it a unique key.

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()

That code is taken from this RailsCasts Pro episode which requires a paid subscription. However, there is a full working example freely available on GitHub.

Update: I want to point out that inserting a child_index placeholder is not always necessary. If you do not want to use JavaScript to insert new records dynamically, you can build them up ahead of time:

def new
  @project = Project.new
  3.times { @project.items.build }
end

<%= f.fields_for :items do |builder| %>

Rails will automatically insert an index for the new records so it should just work.

这篇关于如何通过 Rails 3.2 批量分配提交多个新项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆