21

我目前有一个深度嵌套的复杂表单,我正在使用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 gem 以及 Ryan Bates 的nested_form gem 有关。Cocoon gem 的问题 #91似乎与此问题相同,但 dnagir 建议的解决方法(委托构建对象)在这种情况下并不理想,因为这会导致其他表单出现问题。

4

1 回答 1

30

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

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

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

我有一个演示双嵌套表单的测试项目:一个项目有任务,可以有子任务。

但也许这与你想做的事情关系不够好?

你没有展示你的模型,但如果我理解正确,关系是

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

因此,如果您有一个sale_vehicle可以有. 这就是茧可以做得很好的事情。另一方面,如果您希望在 cocoon 动态创建 a 时也创建 a ,我会看到一些不同的选项。vehiclesale_vehiclesalevehiclesale_vehiclevehicle

利用after_initialize

不能说我是这个的真正粉丝,但在你的after_initialize回调中sale_vehicle,你总是可以构建所需的vehicle模型。

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

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

使用装饰器/演示器

对于用户来说,sale_vehicleandvehicle似乎是一个对象,所以为什么不创建一个由 sale_vehicle 和 vehicle 组成的装饰器,它以一个(嵌套)形式呈现,当保存它时,装饰器知道它需要保存到正确的楷模。

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

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

允许 cocoon 调用自定义build方法

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

编辑:最简单的修复

在您的部分内部,只需构建所需的子对象。这必须在 the 之前发生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,这样您就可以始终确定至少有一个。

我很想听听你的想法。

希望这可以帮助。

于 2012-10-05T23:31:30.053 回答