simpleform simple_form simple rails form association ruby-on-rails ruby-on-rails-3 activerecord cocoon-gem

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.