Rails - динамически строить глубоко вложенные объекты (Cocoon/nested_form)

В настоящее время у меня сложная форма с глубоким вложением, и я использую 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

Любая помощь была оценена, чтобы получить эту работу, или предложить другой способ создания этих объектов динамически.

Спасибо!

EDIT: Возможно, стоит упомянуть, что этот вопрос, по-видимому, имеет отношение как к драгоценному камню Cocoon, упомянутому выше, так и к драгоценному камню Райана Бейтса nested_form. Проблема № 91 для драгоценного камня Cocoon представляется такой же проблемой, как и эта, но обходной путь, предложенный dnagir (делегирование здания объектов), не идеален в этом ситуация, так как это вызовет проблемы в других формах.

Ответ 1

Я вижу в вашей второй вложенной форме нет link_to_add_association.

Внутри кокона link_to_add_association создает новый элемент, когда пользователь хочет динамически его добавить.

Или вы подразумеваете, что после создания sale_vehicle он должен автоматически содержать vehicle? Я бы предположил, что пользователь должен будет выбрать продаваемый автомобиль?

У меня есть тестовый проект, который демонстрирует двойные вложенные формы: у проекта есть задачи, которые могут иметь подзадачи.

Но, может быть, это не так хорошо относится к тому, что вы хотите сделать?

Вы не показываете свои модели, но если я правильно понимаю отношения,

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

Итак, если у вас есть sale_vehicle, у которого может быть vehicle, тогда я бы предположил, что ваш пользователь сначала добавит sale_vehicle в sale, а затем нажмите ссылку, чтобы добавить vehicle. Это то, что кокон может делать отлично. Если, с другой стороны, вы хотите, чтобы при динамическом создании кокона sale_vehicle был создан a vehicle, я вижу несколько разных параметров.

Используйте after_initialize

Не могу сказать, что я настоящий фанат этого, но в обратном вызове after_initialize вашего sale_vehicle вы всегда можете создать требуемую модель vehicle.

Я предполагаю, что, так как ваш sale_vehicle недействителен/не может существовать без модели vehicle, то ответственность за возможность создания вложенной модели сразу после сборки будет отвечать.

Обратите внимание, что after_initialize выполняется для каждого создания объекта, поэтому это может быть дорогостоящим. Но это может быть быстрым решением. Если вы отклоняете пустые вложенные модели, это должно работать imho.

Использование Decorator/Presenter

Для пользователя sale_vehicle и vehicle кажутся одним объектом, поэтому почему бы не создать декоратор, состоящий из sell_vehicle и транспортного средства, который представлен в одну (вложенную) форму и при сохранении этого, декоратор знает, что его нужно сохранить в правильные модели.

Примечание: для этого существуют разные термины. Декоратор обычно только расширяет один класс с помощью некоторых методов просмотра, но он может также быть композицией разных моделей. Альтернативные термины: презентатор, модель просмотра.

Во всяком случае, функция декоратора/презентатора - это абстрагирование основной базы данных для ваших пользователей. Таким образом, по какой-то причине вам нужно было разделить один объект на две модели базы данных (например, чтобы ограничить количество столбцов, чтобы сохранить читаемые модели...), но для пользователя он все еще является единственным объектом. Итак, "представляйте" его как единое целое.

Разрешить кокон вызывать пользовательский метод build

Не уверен, что я поклонник этого, но это определенно есть возможность. Он уже поддерживается, если "вложенная модель" не является ActiveRecord:: Association, поэтому это не должно быть слишком сложно добавить. Но я не согласен с этим дополнением. Все эти параметры усложняют работу.

EDIT: простейшее исправление

Внутри частичного просто создайте необходимый дочерний объект. Это должно произойти до 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, таким образом вы всегда уверены, что есть хотя бы один.

Мне интересно услышать ваши мысли.

Надеюсь, это поможет.