如何通过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
使用插件的注意事项:
-
fields_for调用应该被封装在一个
<fieldset>
,其data-association属性等于模型的复数名称,以及一个类“nested_models”。 -
在调用fields_for之前,应该在视图中构build一个对象。
-
perse的对象字段应该被封装在一个带有“new”类的
<fieldset>
,但是只有当logging是新的(不记得我是否删除了这个需求)。 -
标签内的“_destroy”属性的checkbox必须存在,插件将使用标签文本创build销毁链接。
-
类“add_record”的链接应该存在于fieldset.nested_models中,但是在包含模型字段的fieldset之外。
公寓从这个讨厌它一直在为我工作的奇迹。
检查要点后,这个要求必须更清楚。 请让我知道,如果你改善了代码或如果你使用它:)。
顺便说一句,我的灵感来自瑞安·贝茨第一个嵌套模型的屏幕录像。
长post删除
瑞安有一个插曲: http : //railscasts.com/episodes/196-nested-model-form-revised
它看起来像你需要手动生成唯一的索引。 瑞安用这个object_id
。
我想你可以通过将logging的ID作为隐藏字段来使其工作
有一个叫做蚕茧的gem,我会去做一个更精确的DIY,但它是专门为这种情况build造的。