Rails – 动态构建深层嵌套对象(Cocoon / nested_form)
我目前有一个具有深度嵌套的复杂forms,我正在使用Cocoon gem来根据需要动态添加部分(例如,如果用户想要在销售表单中添加其他车辆)。 代码如下所示:
"sale_vehicles/form", :locals => {:f => sale_vehicles_builder, :form_actions_visible => false} %> <%= link_to_add_association ' Add Vehicle'.html_safe, sale, :sale_vehicles, :partial => 'sale_vehicles/form', :render_options => {:locals => {:form_actions_visible => 'false', :show_features => true, :fieldset_label => 'Vehicle Details'}}, :class => 'btn' %>
这对于第一级嵌套非常有效 – sale_vehicle
对象由Cocoon正确构建,并且表单按预期呈现。
当存在另一级嵌套时出现问题 – sale_vehicle
partial看起来像这样:
"vehicles/form", :locals => {:f => vehicle_builder, :f_parent => f, :form_actions_visible => false, :show_features => true, :fieldset_label => 'Vehicle Details'} %>
vehicle
的部分没有字段,因为没有sale_vehicle.vehicle
对象。
我需要做的是构建嵌套对象和主对象(Cocoon目前不构建任何嵌套对象),但是如何最好地做到这一点? 有没有办法从帮助程序代码中选择嵌套表单,以便可以构建这些表单?
Cocoon目前构建这样的主要对象:
if instance.collection? f.object.send(association).build else f.object.send("build_#{association}") end
如果我可以做类似下面这样的事情,它会让事情变得简单明了,但我不确定如何获得f.children
– 有没有办法从父表单构建器访问嵌套表单构建器?
f.children.each do |child| child.object.build end
任何帮助,以使这个工作,或建议另一种动态构建这些对象的方法。
谢谢!
编辑:可能值得一提的是,这个问题似乎与上面提到的Cocoon gem以及Ryan Bates的nested_form gem都有关。 Cocoon gem的问题#91似乎与此问题相同,但dnagir建议的解决方法(委托对象的构建)在这种情况下并不理想,因为这会导致其他forms的问题。
我可以在你的第二个嵌套表单中看到没有link_to_add_association
。
在内部cocoon中, link_to_add_association
用于构建新元素,以便用户想要动态添加它。
或者,您是否暗示一旦建立了sale_vehicle
,它应该自动包含vehicle
? 我会假设用户必须选择出售的车辆?
我有一个测试项目 ,演示了双嵌套表单:项目有任务,可以有子任务。
但也许这与你想做的事情没有足够的关系?
你没有展示你的模型,但如果我理解正确的关系是
sale has_many :sale_vehicles sale_vehicle has_one :vehicle (has_many?)
因此,如果您有可以拥有vehicle
的sale_vehicle
,那么我会假设您的用户首先将sale_vehicle
添加到sale
,然后单击链接添加vehicle
。 这就是茧能做得非常好的事情。 另一方面,如果您想要在茧动态创建sale_vehicle
同时创建vehicle
,我会看到一些不同的选项。
使用after_initialize
不能说我真的很喜欢这个,但是在你的sale_vehicle
的after_initialize
回调中,你总能建立所需的vehicle
。
我在这里假设,因为您的sale_Vehicle
无效/不能在没有vehicle
模型的情况下存在,所以模型的责任是在构建时立即创建嵌套模型。
请注意,为每个对象创建执行after_initialize
,因此这可能会很昂贵。 但这可能是一个快速解决方案。 如果你拒绝空的嵌套模型,这应该适用于imho。
使用装饰器/演示者
对于用户来说, sale_vehicle
和vehicle
似乎是一个对象,所以为什么不创建一个由sale_vehicle和一个车辆组成的装饰器,它被呈现为一个(嵌套)forms,当保存它时,装饰者知道它需要保存到正确的型号。
注意:对此有不同的用语。 装饰器通常只使用一些视图方法扩展单个类,但它也可以是不同模型的组合。 替代术语:演示者,视图模型。
无论如何,装饰器/演示者的function是为用户抽象出基础数据模型。 因此,无论出于何种原因,您需要将单个实体拆分为两个数据库模型(例如,限制列的nr,以保持模型可读,……)但对于用户而言,它仍然是单个实体。 所以“呈现”它作为一个。
允许cocoon调用自定义build
方法
我不确定我是否喜欢这个,但这绝对是可能的。 如果“嵌套模型”不是ActiveRecord :: Association,则已经支持它,所以这不应该太难添加。 但我对这一补充犹豫不决。 所有这些选项使其更加复杂。
编辑:最简单的修复
在你的局部内部只需构建所需的子对象。 这必须发生在fields_for
之前,然后你就好了。 就像是
<% f.object.build_vehicle %> <%= f.fields_for :vehicle do |vehicle_builder| %> <%= render :partial => "vehicles/form", :locals => {:f => vehicle_builder, :f_parent => f, :form_actions_visible => false, :show_features => true, :fieldset_label => 'Vehicle Details'} %> <% end -%>
结论
我个人非常喜欢装饰器方法,但它可能有点沉重。 只需在渲染调用fields_for
之前构建对象,这样您始终确定至少有一个。
我很想听听你的想法。
希望这可以帮助。