Rails – 动态构建深层嵌套对象(Cocoon / nested_form)

我目前有一个具有深度嵌套的复杂forms,我正在使用Cocoon gem来根据需要动态添加部分(例如,如果用户想要在销售表单中添加其他车辆)。 代码如下所示:

  "sale_vehicles/form", :locals => {:f => sale_vehicles_builder, :form_actions_visible => false} %>   

这对于第一级嵌套非常有效 – 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?) 

因此,如果您有可以拥有vehiclesale_vehicle ,那么我会假设您的用户首先将sale_vehicle添加到sale ,然后单击链接添加vehicle 。 这就是茧能做得非常好的事情。 另一方面,如果您想要在茧动态创建sale_vehicle同时创建vehicle ,我会看到一些不同的选项。

使用after_initialize

不能说我真的很喜欢这个,但是在你的sale_vehicleafter_initialize回调中,你总能建立所需的vehicle

我在这里假设,因为您的sale_Vehicle无效/不能在没有vehicle模型的情况下存在,所以模型的责任是在构建时立即创建嵌套模型。

请注意,为每个对象创建执行after_initialize ,因此这可能会很昂贵。 但这可能是一个快速解决方案。 如果你拒绝空的嵌套模型,这应该适用于imho。

使用装饰器/演示者

对于用户来说, sale_vehiclevehicle似乎是一个对象,所以为什么不创建一个由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之前构建对象,这样您始终确定至少有一个。

我很想听听你的想法。

希望这可以帮助。