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

时间:2012-10-03 13:17:03

标签: ruby-on-rails ruby-on-rails-3 activerecord cocoon-gem

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

<%= sale.fields_for :sale_vehicles do |sale_vehicles_builder| %>
    <%= render :partial => "sale_vehicles/form", :locals => {:f => sale_vehicles_builder, :form_actions_visible => false} %>    
<% end -%>
<div class="add-field-links">
    <%= link_to_add_association '<i></i> 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' %>
</div>

这对于第一级嵌套非常有效 - sale_vehicle对象由Cocoon正确构建,并且表单按预期呈现。

当存在另一级别的嵌套时会出现问题 - sale_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 -%>

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宝石以及Ryan Bates的nested_form宝石有关。 Cocoon gem的Issue #91似乎与此问题相同,但dnagir建议的解决方法(委托构建对象)在这种情况下并不理想,因为这会导致其他形式的问题。 / p>

1 个答案:

答案 0 :(得分:26)

我可以在你的第二个嵌套表单中看到没有link_to_add_association

在cocoon内部,link_to_add_association会构建一个新元素,以供用户想要动态添加它。

或者,您是否暗示一旦构建sale_vehicle,它应自动包含vehicle?我会假设用户必须选择出售的车辆?

我有一个test-project来演示双嵌套表单:项目有任务,可以有子任务。

但也许这与你想做的事情没有足够的关系?

您不会展示您的模型,但如果我理解正确的关系

sale 
  has_many :sale_vehicles
sale_vehicle
  has_one :vehicle (has_many?)

因此,如果您的sale_vehicle可以有vehicle,那么我认为您的用户会首先将sale_vehicle添加到sale,然后点击链接以添加vehicle。这就是茧能做得非常好的事情。另一方面,如果您希望当cocoon动态创建sale_vehicle时,还会创建vehicle,我会看到一些不同的选项。

使用after_initialize

不能说我真的很喜欢这个,但在你after_initialize的{​​{1}}回调中,你总是可以构建所需的sale_vehicle模型。

我在此假设,由于您的vehicle无效/在没有sale_Vehicle模型的情况下无法存在,因此模型的责任是在构建时立即创建嵌套模型。

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

使用装饰器/演示者

对于用户来说,after_initializesale_vehicle似乎是一个对象,那么为什么不创建一个由sale_vehicle和一个车辆组成的装饰器,它被呈现为一个(嵌套)形式并在保存时这个,装饰者知道它需要保存到正确的模型中。

注意:这有不同的用语。装饰器通常只使用一些视图方法扩展单个类,但它也可以是不同模型的组合。替代术语:演示者,视图模型。

无论如何,装饰器/演示者的功能是为用户抽象出基础数据模型。因此,无论出于何种原因,您需要将单个实体拆分为两个数据库模型(例如,限制列的nr,以保持模型可读,......)但对于用户而言,它仍然是单个实体。所以“呈现”它作为一个。

允许cocoon调用自定义vehicle方法

不确定我是否喜欢这个,但这绝对是可能的。如果“嵌套模型”不是ActiveRecord :: Association,则已经支持它,所以这不应该太难添加。但我对这一补充犹豫不决。所有这些选项都使它变得更加复杂。

编辑:最简单的修复

在你的局部内部,只需构建所需的子对象。这必须在build之前发生,然后你就好了。像

这样的东西
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 -%> 之前构建对象,这样就可以确保至少有一个。

我很想听听你的想法。

希望这会有所帮助。