【问题标题】:Rails - Dynamically build deeply nested objects (Cocoon / nested_form)Rails - 动态构建深度嵌套的对象 (Cocoon / nested_form)
【发布时间】:2012-10-03 13:17:02
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    我可以在您的第二个嵌套表单中看到没有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

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

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

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

    使用装饰器/演示器

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

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

    无论如何,装饰者/演示者的功能是为您的用户抽象出底层数据模型。因此,无论出于何种原因,您都需要将单个实体拆分为两个数据库模型(例如,限制列的 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,这样您就可以始终确定至少有一个。

    我很想听听你的想法。

    希望这会有所帮助。

    【讨论】:

    • 哇,非常棒且非常详细的答案,正是我所希望的。我周末不在,但周一会做一些测试。装饰器和简单的解决方案似乎都是不错的选择。
    • 周末愉快!很想知道你周一的测试是如何进行的 :)
    • 最简单的解决方案(在视图中构建对象)效果非常好——我将它包裹在一个 if 循环中以确保如果车辆已经构建(例如在编辑表单上)没有问题,并且它完全按预期工作。很高兴就这么简单!
    • 我在运行 Ruby 2.3 和 Rails 4.2.6 时遇到了同样的问题。在fields_for 之前构建对象对我不起作用。添加f.fields_for :resource_name, (f.object.build_resource_name unless f.object_resource_name) do |resource|是我成功使用的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-31
    • 1970-01-01
    • 1970-01-01
    • 2019-03-15
    • 1970-01-01
    相关资源
    最近更新 更多