ruby on rails - simple_form - Rieles: construye dinĂ¡micamente objetos profundamente anidados(Cocoon/nested_form)
simpleform label (1)
Actualmente tengo una forma compleja con anidación profunda, y estoy usando la gema Cocoon para agregar secciones de forma dinámica según sea necesario (por ejemplo, si un usuario desea agregar otro vehículo al formulario de venta). El código se ve así:
<%= 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>
Esto funciona muy bien para el primer nivel de anidamiento: el objeto sale_vehicle
está construido correctamente por Cocoon, y el formulario se representa como se esperaba.
El problema surge cuando hay otro nivel de anidamiento: el sale_vehicle
parcial tiene este aspecto:
<%= 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 -%>
El parcial para el vehicle
se representa sin campos, porque no se ha sale_vehicle.vehicle
ningún objeto sale_vehicle.vehicle
.
Lo que debo hacer es construir el objeto anidado junto con el objeto principal (Cocoon actualmente no construye ningún objeto anidado), pero ¿cuál es la mejor manera de hacerlo? ¿Hay alguna forma de seleccionar formularios anidados del código auxiliar para que se puedan construir?
Cocoon actualmente construye el objeto principal así:
if instance.collection?
f.object.send(association).build
else
f.object.send("build_#{association}")
end
Si pudiera hacer algo como lo siguiente, sería agradable y simple, pero no estoy seguro de cómo obtener f.children
. ¿Hay alguna forma de acceder a los creadores de formularios anidados desde el creador de formularios principal?
f.children.each do |child|
child.object.build
end
Cualquier ayuda apreciada para hacer que esto funcione, o sugerir otra forma de construir estos objetos dinámicamente.
¡Gracias!
EDITAR: Probablemente vale la pena mencionar que esta pregunta parece ser relevante tanto para la gema Cocoon mencionada anteriormente como para la gema de forma nested_form Ryan Bates. El problema 91 para la gema Cocoon parece ser el mismo problema que este, pero la solución sugerida por dnagir (delegar la construcción de los objetos) no es ideal en esta situación, ya que eso causará problemas en otras formas.
Puedo ver en su segunda forma anidada que no hay link_to_add_association
.
Dentro de cocoon, link_to_add_association
hace la construcción de un nuevo elemento, para cuando un usuario quiere agregarlo dinámicamente.
O, ¿estás insinuando que una vez que se construye un vehicle
sale_vehicle
, debería contener automáticamente un vehicle
? ¿Asumiría que un usuario tendría que seleccionar el vehículo que se vende?
Tengo un test-project que muestra los formularios anidados dobles: un proyecto tiene tareas, que pueden tener subtareas.
Pero tal vez eso no se relaciona lo suficientemente bien con lo que quieres hacer?
Usted no muestra sus modelos, pero si entiendo correctamente las relaciones son
sale
has_many :sale_vehicles
sale_vehicle
has_one :vehicle (has_many?)
Entonces, si tiene un sale_vehicle
que puede tener un vehicle
, asumo que su usuario primero agregaría el sale_vehicle
a la sale
y luego, haga clic en el enlace para agregar el vehicle
. Eso es lo que el capullo puede hacer perfectamente bien. Si, por otro lado, quieres que cuando Cocoon cree dinámicamente un vehicle
sale_vehicle
, también se cree un vehicle
, veo algunas opciones diferentes.
Utilizar after_initialize
No puedo decir que soy un verdadero fanático de esto, pero en la devolución de llamada after_initialize
de su sale_vehicle
de sale_vehicle
, siempre puede construir el modelo de vehicle
requerido.
Estoy asumiendo aquí que dado que su sale_Vehicle
no es válido / no puede existir sin un modelo de vehicle
, es responsabilidad del modelo crear el modelo anidado inmediatamente en la compilación.
Tenga en cuenta que after_initialize
se ejecuta para cada creación de objeto, por lo que esto podría ser costoso. Pero esto podría ser una solución rápida. Si rechaza modelos anidados vacíos, esto debería funcionar imho.
Usar decorador / presentador
Para el usuario, sale_vehicle
y vehicle
parecen un solo objeto, así que ¿por qué no crear un decorador, compuesto por un sale_vehicle y un vehículo, que se presenta en una forma (anidada) y al guardar esto, el decorador sabe que debe guardarse en Los modelos correctos.
Nota: hay diferentes términos para esto. Un decorador generalmente solo extiende una clase con algunos métodos de visualización, pero también podría ser una composición de diferentes modelos. Términos alternativos: presentador, vista-modelo.
De todos modos, la función de un decorador / presentador es abstraer el modelo de datos subyacente para sus usuarios. Entonces, por cualquier motivo, necesitaba dividir una sola entidad en dos modelos de base de datos (por ejemplo, para limitar el número de columnas, para mantener los modelos legibles, ...) pero para el usuario todavía es una entidad única. Así que "presente" como uno.
Permitir a cocoon llamar un método de build
personalizado
No estoy seguro si soy un fan de esto, pero esto definitivamente es una posibilidad. Ya se admite si el "modelo anidado" no es una asociación ActiveRecord ::, por lo que no debería ser demasiado difícil agregarlo. Pero tengo dudas sobre esa adición. Todas esas opciones lo hacen más complicado.
EDITAR: la solución más simple
Dentro de tu parcial solo construye el objeto hijo necesario. Esto tiene que suceder antes de los fields_for
y entonces está fields_for
para fields_for
. Algo como
<% 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 -%>
Conclusión
Personalmente, me gusta mucho el enfoque de decorador, pero podría ser un poco pesado. Simplemente fields_for
el objeto antes de procesar, llame a fields_for
, de esa manera siempre estará seguro de que hay al menos uno.
Me interesa escuchar tus pensamientos.
Espero que esto ayude.