Un truco muy simple en ruby on rails 4 (también aplica a rails 3) que puede ahorrarnos mucho trabajo es la capacidad de guardar un objeto junto con sus objetos hijos simultáneamente en el mismo formulario, de esta forma podemos guardar una pregunta con sus respuestas, o un post con su categoría, simúltaneamente sin necesidad de llamados ajax adicionales, o sea únicamente utilizando los atributos anidados de Ruby on Rails.
Atributos anidados en el modelo con Ruby on Rails
Para lograr esto necesitamos al menos dos modelos, el padre (father) y el hijo (child), donde un padre puede tener n hijos y el hijo tiene un padre.
#models/father.rb
class Father < ActiveRecord::Base
has_many :childs
accepts_nested_attributes_for :childs
end
#models/child.rb
class Father < ActiveRecord::Base
belongs_to :father
end
La línea que marca la diferencia es accepts_nested_attributes_for :childs, la cual es justamente la que permite aceptar atributos anidados, o sea que permite guardar el modelo junto con el modelo hijo.
Agregando los atributos anidados al white list de los strong parameters
otra modificación que tenemos que hacer, en el controller de la clase padre tenemos que agregar a los strong_parameters.
def father_params params.require(:father).permit(:name, childs_attributes: [:id, :name]) end
Dentro del arreglo child_attributes tenemos que incorporar todos los campos del objeto hijo que queremos pasar, en este ejemplo únicamente estoy pasando el id y el nombre pero más campos podrían llegar a ser necesarios.
La vista del formulario con atributos anidados
Ahora nos está faltando agregar al formulario para crear un objeto del tipo padre los campos del hijo, este es un ejemplo de un formulario tipo padre creado con scaffold y modificado para aceptar el campo nombre del hijo.
<%= form_for(@father) do |f| %>
<% if @father.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@father.errors.count, "error") %> prohibited this father from being saved:</h2>
<ul>
<% @father.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= f.fields_for :childs, @childs do |c| %>
<%= c.text_field :name %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Como ven, la única diferencia radica en el f.field_for
Agregando hijos al formulario
ahora esto va a tirar un error, puesto que @childs no está definido en ninguna parte, para eso tenemos que volver al controlador de la clase padre y dejarla de la siguiente forma:
def new
@father = Father.new
@childs = @father.childs.build
end
Y con eso ya tendríamos un formulario junto a su formulario hijo funcionando.
Desafío propuesto
Si quisiéramos que tuviese más hijos por defecto simplemente podemos agregar más hijos al arreglo @childs.
Un ejercicio interesante es crear más de estos formularios por javascript (o coffeescript) y aceptar sólo los que no están en blanco.