NOTA: Esta guía no pretende ser una documentación completa de los ayudantes de formularios disponibles y sus argumentos. Por favor, visita la documentación de la API de Rails para obtener una referencia completa de todos los ayudantes disponibles.
1 Trabajando con Formularios Básicos
El principal ayudante de formularios es form_with
.
<%= form_with do |form| %>
Contenido del formulario
<% end %>
Cuando se llama sin argumentos de esta manera, crea una etiqueta de formulario que, al enviarse, realizará una solicitud POST a la página actual. Por ejemplo, suponiendo que la página actual es una página de inicio, el HTML generado se verá así:
<form accept-charset="UTF-8" action="/" method="post">
<input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />
Contenido del formulario
</form>
Observarás que el HTML contiene un elemento input
con el tipo hidden
. Este input
es importante, porque los formularios que no son GET no se pueden enviar correctamente sin él. El elemento input
oculto con el nombre authenticity_token
es una característica de seguridad de Rails llamada protección contra falsificación de solicitudes entre sitios, y los ayudantes de formularios lo generan para cada formulario que no es GET (siempre que esta característica de seguridad esté habilitada). Puedes obtener más información al respecto en la guía Securing Rails Applications (en inglés).
1.1 Un Formulario de Búsqueda Genérico
Uno de los formularios más básicos que se ven en la web es un formulario de búsqueda. Este formulario contiene:
- un elemento de formulario con método "GET",
- una etiqueta para la entrada,
- un elemento de entrada de texto, y
- un elemento de envío.
Para crear este formulario, utilizarás form_with
y el objeto constructor de formularios que genera. De esta manera:
<%= form_with url: "/search", method: :get do |form| %>
<%= form.label :query, "Buscar:" %>
<%= form.text_field :query %>
<%= form.submit "Buscar" %>
<% end %>
Esto generará el siguiente HTML:
<form action="/search" method="get" accept-charset="UTF-8" >
<label for="query">Buscar:</label>
<input id="query" name="query" type="text" />
<input name="commit" type="submit" value="Buscar" data-disable-with="Buscar" />
</form>
CONSEJO: Pasar url: my_specified_path
a form_with
indica al formulario dónde realizar la solicitud. Sin embargo, como se explica a continuación, también puedes pasar objetos Active Record al formulario.
CONSEJO: Para cada entrada de formulario, se genera un atributo ID a partir de su nombre ("query"
en el ejemplo anterior). Estos ID pueden ser muy útiles para aplicar estilos CSS o manipular los controles del formulario con JavaScript.
IMPORTANTE: Utiliza "GET" como método para los formularios de búsqueda. Esto permite a los usuarios marcar una búsqueda específica y volver a ella. En general, Rails te anima a utilizar el verbo HTTP correcto para una acción.
1.2 Ayudantes para Generar Elementos de Formulario
El objeto constructor de formularios generado por form_with
proporciona numerosos métodos de ayuda para generar elementos de formulario como campos de texto, casillas de verificación y botones de radio. El primer parámetro de estos métodos siempre es el nombre de la entrada. Cuando se envía el formulario, el nombre se enviará junto con los datos del formulario y llegará a params
en el controlador con el valor ingresado por el usuario para ese campo. Por ejemplo, si el formulario contiene <%= form.text_field :query %>
, entonces podrías obtener el valor de este campo en el controlador con params[:query]
.
Cuando se nombran las entradas, Rails utiliza ciertas convenciones que permiten enviar parámetros con valores no escalares, como matrices o hashes, que también serán accesibles en params
. Puedes obtener más información al respecto en la sección Understanding Parameter Naming Conventions de esta guía. Para obtener detalles sobre el uso preciso de estos ayudantes, consulta la documentación de la API.
1.2.1 Casillas de verificación
Las casillas de verificación son controles de formulario que permiten al usuario seleccionar o deseleccionar un conjunto de opciones:
<%= form.check_box :pet_dog %>
<%= form.label :pet_dog, "Tengo un perro" %>
<%= form.check_box :pet_cat %>
<%= form.label :pet_cat, "Tengo un gato" %>
Esto genera lo siguiente:
<input type="checkbox" id="pet_dog" name="pet_dog" value="1" />
<label for="pet_dog">Tengo un perro</label>
<input type="checkbox" id="pet_cat" name="pet_cat" value="1" />
<label for="pet_cat">Tengo un gato</label>
El primer parámetro de check_box
es el nombre del campo de entrada. Los valores de las casillas de verificación (los valores que aparecerán en params
) se pueden especificar opcionalmente utilizando los terceros y cuartos parámetros. Consulta la documentación de la API para obtener más detalles.
1.2.2 Botones de opción
Los botones de opción, aunque similares a las casillas de verificación, son controles que especifican un conjunto de opciones en las que son mutuamente excluyentes (es decir, el usuario solo puede seleccionar una):
<%= form.radio_button :age, "child" %>
<%= form.label :age_child, "Soy menor de 21 años" %>
<%= form.radio_button :age, "adult" %>
<%= form.label :age_adult, "Soy mayor de 21 años" %>
Salida:
<input type="radio" id="age_child" name="age" value="child" />
<label for="age_child">Soy menor de 21 años</label>
<input type="radio" id="age_adult" name="age" value="adult" />
<label for="age_adult">Soy mayor de 21 años</label>
El segundo parámetro de radio_button
es el valor del campo de entrada. Debido a que estos dos botones de opción comparten el mismo nombre (age
), el usuario solo podrá seleccionar uno de ellos, y params[:age]
contendrá "child"
o "adult"
.
NOTA: Siempre utiliza etiquetas para las casillas de verificación y los botones de opción. Asocian texto con una opción específica y, al expandir la región clickeable, facilitan que los usuarios hagan clic en los campos de entrada.
1.3 Otros ayudantes de interés
Otros controles de formulario que vale la pena mencionar son las áreas de texto, los campos ocultos, los campos de contraseña, los campos numéricos, los campos de fecha y hora, y muchos más:
<%= form.text_area :message, size: "70x5" %>
<%= form.hidden_field :parent_id, value: "foo" %>
<%= form.password_field :password %>
<%= form.number_field :price, in: 1.0..20.0, step: 0.5 %>
<%= form.range_field :discount, in: 1..100 %>
<%= form.date_field :born_on %>
<%= form.time_field :started_at %>
<%= form.datetime_local_field :graduation_day %>
<%= form.month_field :birthday_month %>
<%= form.week_field :birthday_week %>
<%= form.search_field :name %>
<%= form.email_field :address %>
<%= form.telephone_field :phone %>
<%= form.url_field :homepage %>
<%= form.color_field :favorite_color %>
Salida:
<textarea name="message" id="message" cols="70" rows="5"></textarea>
<input type="hidden" name="parent_id" id="parent_id" value="foo" />
<input type="password" name="password" id="password" />
<input type="number" name="price" id="price" step="0.5" min="1.0" max="20.0" />
<input type="range" name="discount" id="discount" min="1" max="100" />
<input type="date" name="born_on" id="born_on" />
<input type="time" name="started_at" id="started_at" />
<input type="datetime-local" name="graduation_day" id="graduation_day" />
<input type="month" name="birthday_month" id="birthday_month" />
<input type="week" name="birthday_week" id="birthday_week" />
<input type="search" name="name" id="name" />
<input type="email" name="address" id="address" />
<input type="tel" name="phone" id="phone" />
<input type="url" name="homepage" id="homepage" />
<input type="color" name="favorite_color" id="favorite_color" value="#000000" />
Los campos ocultos no se muestran al usuario, sino que almacenan datos como cualquier campo de texto. Los valores dentro de ellos se pueden cambiar con JavaScript.
IMPORTANTE: Los campos de búsqueda, teléfono, fecha, hora, color, fecha y hora, mes, semana, URL, correo electrónico, número y rango son controles HTML5. Si deseas que tu aplicación tenga una experiencia consistente en navegadores antiguos, necesitarás un polyfill de HTML5 (proporcionado por CSS y/o JavaScript). Definitivamente no hay escasez de soluciones para esto, aunque una herramienta popular en este momento es Modernizr, que proporciona una forma sencilla de agregar funcionalidad en función de la presencia de características HTML5 detectadas.
CONSEJO: Si estás utilizando campos de entrada de contraseña (para cualquier propósito), es posible que desees configurar tu aplicación para evitar que esos parámetros se registren. Puedes obtener más información al respecto en la guía Securing Rails Applications.
2 Trabajando con objetos de modelo
2.1 Vinculando un formulario a un objeto
El argumento :model
de form_with
nos permite vincular el objeto del generador de formularios a un objeto de modelo. Esto significa que el formulario estará enfocado en ese objeto de modelo y los campos del formulario se llenarán con los valores de ese objeto de modelo.
Por ejemplo, si tenemos un objeto de modelo @article
como:
@article = Article.find(42)
# => #<Article id: 42, title: "Mi título", body: "Mi cuerpo">
El siguiente formulario:
<%= form_with model: @article do |form| %>
<%= form.text_field :title %>
<%= form.text_area :body, size: "60x10" %>
<%= form.submit %>
<% end %>
Genera:
<form action="/articles/42" method="post" accept-charset="UTF-8" >
<input name="authenticity_token" type="hidden" value="..." />
<input type="text" name="article[title]" id="article_title" value="Mi título" />
<textarea name="article[body]" id="article_body" cols="60" rows="10">
Mi cuerpo
</textarea>
<input type="submit" name="commit" value="Actualizar artículo" data-disable-with="Actualizar artículo">
</form>
Aquí hay varias cosas a tener en cuenta:
- El atributo
action
del formulario se completa automáticamente con un valor apropiado para@article
. - Los campos del formulario se completan automáticamente con los valores correspondientes de
@article
. - Los nombres de los campos del formulario están delimitados con
article[...]
. Esto significa queparams[:article]
será un hash que contiene los valores de todos estos campos. Puedes obtener más información sobre la importancia de los nombres de entrada en el capítulo Understanding Parameter Naming Conventions de esta guía. - El botón de envío se le asigna automáticamente un texto apropiado.
CONSEJO: Convencionalmente, tus entradas reflejarán los atributos del modelo. Sin embargo, ¡no tienen que hacerlo! Si necesitas otra información, puedes incluirla en tu formulario de la misma manera que con los atributos y acceder a ella a través de params[:article][:my_nifty_non_attribute_input]
.
2.1.1 El ayudante fields_for
El ayudante fields_for
crea un enlace similar pero sin renderizar una etiqueta <form>
. Esto se puede utilizar para renderizar campos para objetos de modelo adicionales dentro del mismo formulario. Por ejemplo, si tienes un modelo Person
con un modelo asociado ContactDetail
, puedes crear un solo formulario para ambos de la siguiente manera:
<%= form_with model: @person do |person_form| %>
<%= person_form.text_field :name %>
<%= fields_for :contact_detail, @person.contact_detail do |contact_detail_form| %>
<%= contact_detail_form.text_field :phone_number %>
<% end %>
<% end %>
Lo cual produce la siguiente salida:
<form action="/people" accept-charset="UTF-8" method="post">
<input type="hidden" name="authenticity_token" value="bL13x72pldyDD8bgtkjKQakJCpd4A8JdXGbfksxBDHdf1uC0kCMqe2tvVdUYfidJt0fj3ihC4NxiVHv8GVYxJA==" />
<input type="text" name="person[name]" id="person_name" />
<input type="text" name="contact_detail[phone_number]" id="contact_detail_phone_number" />
</form>
El objeto devuelto por fields_for
es un constructor de formularios similar al devuelto por form_with
.
2.2 Dependiendo de la identificación del registro
El modelo Article está directamente disponible para los usuarios de la aplicación, por lo que, siguiendo las mejores prácticas para desarrollar con Rails, deberías declararlo un recurso:
resources :articles
CONSEJO: Declarar un recurso tiene varios efectos secundarios. Consulta la guía Rails Routing from the Outside In para obtener más información sobre cómo configurar y utilizar recursos.
Cuando se trata de recursos RESTful, las llamadas a form_with
pueden ser mucho más fáciles si te basas en la identificación del registro. En resumen, puedes pasar la instancia del modelo y Rails se encargará de determinar el nombre del modelo y el resto. En ambos ejemplos, el estilo largo y el estilo corto tienen el mismo resultado:
## Crear un nuevo artículo
# estilo largo:
form_with(model: @article, url: articles_path)
# estilo corto:
form_with(model: @article)
## Editar un artículo existente
# estilo largo:
form_with(model: @article, url: article_path(@article), method: "patch")
# estilo corto:
form_with(model: @article)
Observa cómo la invocación de form_with
en estilo corto es conveniente y es la misma, independientemente de si el registro es nuevo o existente. La identificación del registro es lo suficientemente inteligente como para determinar si el registro es nuevo preguntando record.persisted?
. También selecciona la ruta correcta para enviar los datos y el nombre basado en la clase del objeto.
Si tienes un recurso singular, deberás llamar a resource
y resolve
para que funcione con form_with
:
resource :geocoder
resolve('Geocoder') { [:geocoder] }
ADVERTENCIA: Cuando estás utilizando STI (herencia de tabla única) con tus modelos, no puedes depender de la identificación del registro en una subclase si solo su clase principal está declarada como recurso. Deberás especificar :url
y :scope
(el nombre del modelo) explícitamente.
2.2.1 Tratando con espacios de nombres
Si has creado rutas con espacios de nombres, form_with
también tiene una forma abreviada para eso. Si tu aplicación tiene un espacio de nombres de administrador, entonces
form_with model: [:admin, @article]
creará un formulario que envía los datos al ArticlesController
dentro del espacio de nombres de administrador (enviando a admin_article_path(@article)
en caso de una actualización). Si tienes varios niveles de espacios de nombres, la sintaxis es similar:
form_with model: [:admin, :management, @article]
Para obtener más información sobre el sistema de enrutamiento de Rails y las convenciones asociadas, consulta la guía Rails Routing from the Outside In.
2.3 ¿Cómo funcionan los formularios con los métodos PATCH, PUT o DELETE?
El framework de Rails fomenta el diseño RESTful de tus aplicaciones, lo que significa que realizarás muchas solicitudes "PATCH", "PUT" y "DELETE" (además de "GET" y "POST"). Sin embargo, la mayoría de los navegadores no admiten métodos distintos de "GET" y "POST" al enviar formularios.
Rails soluciona este problema emulando otros métodos a través de POST con un campo oculto llamado "_method"
, que se establece para reflejar el método deseado:
form_with(url: search_path, method: "patch")
Salida:
<form accept-charset="UTF-8" action="/search" method="post">
<input name="_method" type="hidden" value="patch" />
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
<!-- ... -->
</form>
Al analizar los datos enviados mediante POST, Rails tendrá en cuenta el parámetro especial _method
y actuará como si el método HTTP fuera el especificado en él ("PATCH" en este ejemplo).
Al renderizar un formulario, los botones de envío pueden anular el atributo method
declarado a través de la palabra clave formmethod:
:
<%= form_with url: "/posts/1", method: :patch do |form| %>
<%= form.button "Eliminar", formmethod: :delete, data: { confirm: "¿Estás seguro?" } %>
<%= form.button "Actualizar" %>
<% end %>
Similar a los elementos <form>
, la mayoría de los navegadores no admiten la anulación de los métodos de formulario declarados a través de formmethod que no sean "GET" y "POST".
Rails soluciona este problema emulando otros métodos sobre POST mediante una combinación de formmethod, value y name:
<form accept-charset="UTF-8" action="/posts/1" method="post">
<input name="_method" type="hidden" value="patch" />
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
<!-- ... -->
<button type="submit" formmethod="post" name="_method" value="delete" data-confirm="¿Estás seguro?">Eliminar</button>
<button type="submit" name="button">Actualizar</button>
</form>
3 Crear cuadros de selección fácilmente
Los cuadros de selección en HTML requieren una cantidad significativa de marcado, un elemento <option>
por cada opción para elegir. Por lo tanto, Rails proporciona métodos auxiliares para reducir esta carga.
Por ejemplo, supongamos que tenemos una lista de ciudades para que el usuario elija. Podemos usar el ayudante select
de la siguiente manera:
<%= form.select :city, ["Berlín", "Chicago", "Madrid"] %>
Salida:
<select name="city" id="city">
<option value="Berlín">Berlín</option>
<option value="Chicago">Chicago</option>
<option value="Madrid">Madrid</option>
</select>
También podemos designar valores <option>
que difieren de sus etiquetas:
<%= form.select :city, [["Berlín", "BE"], ["Chicago", "CHI"], ["Madrid", "MD"]] %>
Salida:
<select name="city" id="city">
<option value="BE">Berlín</option>
<option value="CHI">Chicago</option>
<option value="MD">Madrid</option>
</select>
De esta manera, el usuario verá el nombre completo de la ciudad, pero params[:city]
será uno de "BE"
, "CHI"
o "MD"
.
Por último, podemos especificar una opción predeterminada para el cuadro de selección con el argumento :selected
:
<%= form.select :city, [["Berlín", "BE"], ["Chicago", "CHI"], ["Madrid", "MD"]], selected: "CHI" %>
Salida:
<select name="city" id="city">
<option value="BE">Berlín</option>
<option value="CHI" selected="selected">Chicago</option>
<option value="MD">Madrid</option>
</select>
3.1 Grupos de opciones
En algunos casos, es posible que deseemos mejorar la experiencia del usuario agrupando opciones relacionadas. Podemos hacerlo pasando un Hash
(o un Array
comparable) a select
:
<%= form.select :city,
{
"Europa" => [ ["Berlín", "BE"], ["Madrid", "MD"] ],
"América del Norte" => [ ["Chicago", "CHI"] ],
},
selected: "CHI" %>
Salida:
<select name="city" id="city">
<optgroup label="Europa">
<option value="BE">Berlín</option>
<option value="MD">Madrid</option>
</optgroup>
<optgroup label="América del Norte">
<option value="CHI" selected="selected">Chicago</option>
</optgroup>
</select>
3.2 Cuadros de selección y objetos de modelo
Al igual que otros controles de formulario, un cuadro de selección puede estar vinculado a un atributo del modelo. Por ejemplo, si tenemos un objeto de modelo @person
como:
@person = Person.new(city: "MD")
El siguiente formulario:
<%= form_with model: @person do |form| %>
<%= form.select :city, [["Berlín", "BE"], ["Chicago", "CHI"], ["Madrid", "MD"]] %>
<% end %>
Genera un cuadro de selección como:
<select name="person[city]" id="person_city">
<option value="BE">Berlín</option>
<option value="CHI">Chicago</option>
<option value="MD" selected="selected">Madrid</option>
</select>
Observa que la opción adecuada se marcó automáticamente como selected="selected"
. ¡Dado que este cuadro de selección estaba vinculado a un modelo, no fue necesario especificar un argumento :selected
!
3.3 Selección de zona horaria y país
Para aprovechar el soporte de zona horaria en Rails, debes preguntar a tus usuarios en qué zona horaria se encuentran. Hacerlo requeriría generar opciones de selección a partir de una lista de objetos predefinidos ActiveSupport::TimeZone
, pero puedes usar simplemente el ayudante time_zone_select
que ya lo envuelve:
<%= form.time_zone_select :time_zone %>
Rails solía tener un ayudante country_select
para elegir países, pero esto se ha extraído al complemento country_select.
4 Uso de ayudantes de formulario de fecha y hora
Si no deseas utilizar las entradas de fecha y hora de HTML5, Rails proporciona ayudantes de formulario alternativos para fecha y hora que generan cuadros de selección simples. Estos ayudantes generan un cuadro de selección para cada componente temporal (por ejemplo, año, mes, día, etc.). Por ejemplo, si tenemos un objeto de modelo @person
como:
@person = Person.new(birth_date: Date.new(1995, 12, 21))
El siguiente formulario:
<%= form_with model: @person do |form| %>
<%= form.date_select :birth_date %>
<% end %>
Genera cuadros de selección como:
<select name="person[birth_date(1i)]" id="person_birth_date_1i">
<option value="1990">1990</option>
<option value="1991">1991</option>
<option value="1992">1992</option>
<option value="1993">1993</option>
<option value="1994">1994</option>
<option value="1995" selected="selected">1995</option>
<option value="1996">1996</option>
<option value="1997">1997</option>
<option value="1998">1998</option>
<option value="1999">1999</option>
<option value="2000">2000</option>
</select>
<select name="person[birth_date(2i)]" id="person_birth_date_2i">
<option value="1">enero</option>
<option value="2">febrero</option>
<option value="3">marzo</option>
<option value="4">abril</option>
<option value="5">mayo</option>
<option value="6">junio</option>
<option value="7">julio</option>
<option value="8">agosto</option>
<option value="9">septiembre</option>
<option value="10">octubre</option>
<option value="11">noviembre</option>
<option value="12" selected="selected">diciembre</option>
</select>
<select name="person[birth_date(3i)]" id="person_birth_date_3i">
<option value="1">1</option>
...
<option value="21" selected="selected">21</option>
...
<option value="31">31</option>
</select>
Ten en cuenta que, cuando se envía el formulario, no habrá un solo valor en el hash params
que contenga la fecha completa. En su lugar, habrá varios valores con nombres especiales como "birth_date(1i)"
. Active Record sabe cómo ensamblar estos valores con nombres especiales en una fecha o hora completa, según el tipo declarado del atributo del modelo. Por lo tanto, podemos pasar params[:person]
a Person.new
o Person#update
como lo haríamos si el formulario usara un solo campo para representar la fecha completa.
Además del ayudante date_select
, Rails proporciona time_select
y datetime_select
.
4.1 Cuadros de selección para componentes temporales individuales
Rails también proporciona ayudantes para renderizar cuadros de selección para componentes temporales individuales: select_year
, select_month
, select_day
, select_hour
, select_minute
y select_second
. Estos ayudantes son métodos "desnudos", lo que significa que no se llaman en una instancia de constructor de formularios. Por ejemplo:
<%= select_year 1999, prefix: "party" %>
Genera un cuadro de selección como:
<select name="party[year]" id="party_year">
<option value="1994">1994</option>
<option value="1995">1995</option>
<option value="1996">1996</option>
<option value="1997">1997</option>
<option value="1998">1998</option>
<option value="1999" selected="selected">1999</option>
<option value="2000">2000</option>
<option value="2001">2001</option>
<option value="2002">2002</option>
<option value="2003">2003</option>
<option value="2004">2004</option>
</select>
Para cada uno de estos ayudantes, puedes especificar un objeto de fecha o hora en lugar de un número como valor predeterminado, y se extraerá y utilizará el componente temporal correspondiente.
5 Elecciones de una colección de objetos arbitrarios
A veces, queremos generar un conjunto de opciones a partir de una colección de objetos arbitrarios. Por ejemplo, si tenemos un modelo City
y una asociación correspondiente belongs_to :city
:
class City < ApplicationRecord
end
class Person < ApplicationRecord
belongs_to :city
end
City.order(:name).map { |city| [city.name, city.id] }
# => [["Berlin", 3], ["Chicago", 1], ["Madrid", 2]]
Entonces podemos permitir al usuario elegir una ciudad de la base de datos con el siguiente formulario:
<%= form_with model: @person do |form| %>
<%= form.select :city_id, City.order(:name).map { |city| [city.name, city.id] } %>
<% end %>
NOTA: Al renderizar un campo para una asociación belongs_to
, debes especificar el nombre de la clave externa (city_id
en el ejemplo anterior), en lugar del nombre de la asociación en sí.
Sin embargo, Rails proporciona ayudantes que generan opciones a partir de una colección sin tener que iterar explícitamente sobre ella. Estos ayudantes determinan el valor y la etiqueta de texto de cada opción llamando a métodos especificados en cada objeto de la colección.
5.1 El ayudante collection_select
Para generar un cuadro de selección, podemos usar collection_select
:
<%= form.collection_select :city_id, City.order(:name), :id, :name %>
Salida:
<select name="person[city_id]" id="person_city_id">
<option value="3">Berlin</option>
<option value="1">Chicago</option>
<option value="2">Madrid</option>
</select>
NOTA: Con collection_select
especificamos primero el método de valor (:id
en el ejemplo anterior) y luego el método de etiqueta de texto (:name
en el ejemplo anterior). Esto es contrario al orden utilizado al especificar opciones para el ayudante select
, donde la etiqueta de texto viene primero y el valor segundo.
5.2 El ayudante collection_radio_buttons
Para generar un conjunto de botones de radio, podemos usar collection_radio_buttons
:
<%= form.collection_radio_buttons :city_id, City.order(:name), :id, :name %>
Salida:
<input type="radio" name="person[city_id]" value="3" id="person_city_id_3">
<label for="person_city_id_3">Berlin</label>
<input type="radio" name="person[city_id]" value="1" id="person_city_id_1">
<label for="person_city_id_1">Chicago</label>
<input type="radio" name="person[city_id]" value="2" id="person_city_id_2">
<label for="person_city_id_2">Madrid</label>
5.3 El ayudante collection_check_boxes
Para generar un conjunto de casillas de verificación, por ejemplo, para admitir una asociación has_and_belongs_to_many
, podemos usar collection_check_boxes
:
<%= form.collection_check_boxes :interest_ids, Interest.order(:name), :id, :name %>
Salida:
<input type="checkbox" name="person[interest_id][]" value="3" id="person_interest_id_3">
<label for="person_interest_id_3">Engineering</label>
<input type="checkbox" name="person[interest_id][]" value="4" id="person_interest_id_4">
<label for="person_interest_id_4">Math</label>
<input type="checkbox" name="person[interest_id][]" value="1" id="person_interest_id_1">
<label for="person_interest_id_1">Science</label>
<input type="checkbox" name="person[interest_id][]" value="2" id="person_interest_id_2">
<label for="person_interest_id_2">Technology</label>
6 Carga de archivos
Una tarea común es cargar algún tipo de archivo, ya sea una imagen de una persona o un archivo CSV que contiene datos para procesar. Los campos de carga de archivos se pueden renderizar con el ayudante file_field
.
<%= form_with model: @person do |form| %>
<%= form.file_field :picture %>
<% end %>
Lo más importante a recordar con las cargas de archivos es que el atributo enctype
del formulario renderizado debe establecerse en "multipart/form-data". Esto se hace automáticamente si usas un file_field
dentro de un form_with
. También puedes establecer el atributo manualmente:
<%= form_with url: "/uploads", multipart: true do |form| %>
<%= file_field_tag :picture %>
<% end %>
Ten en cuenta que, de acuerdo con las convenciones de form_with
, los nombres de los campos en los dos formularios anteriores también serán diferentes. Es decir, el nombre del campo en el primer formulario será person[picture]
(accesible a través de params[:person][:picture]
), y el nombre del campo en el segundo formulario será simplemente picture
(accesible a través de params[:picture]
).
6.1 Qué se carga
El objeto en el hash params
es una instancia de ActionDispatch::Http::UploadedFile
. El siguiente fragmento guarda el archivo cargado en #{Rails.root}/public/uploads
con el mismo nombre que el archivo original.
def upload
uploaded_file = params[:picture]
File.open(Rails.root.join('public', 'uploads', uploaded_file.original_filename), 'wb') do |file|
file.write(uploaded_file.read)
end
end
Una vez que se ha cargado un archivo, hay una multitud de tareas potenciales, que van desde dónde almacenar los archivos (en disco, Amazon S3, etc.), asociarlos con modelos, redimensionar archivos de imagen y generar miniaturas, etc. Active Storage está diseñado para ayudar con estas tareas.
7 Personalización de los constructores de formularios
El objeto devuelto por form_with
y fields_for
es una instancia de ActionView::Helpers::FormBuilder
. Los constructores de formularios encapsulan la noción de mostrar elementos de formulario para un solo objeto. Si bien puedes escribir helpers para tus formularios de la manera habitual, también puedes crear una subclase de ActionView::Helpers::FormBuilder
y agregar los helpers allí. Por ejemplo,
<%= form_with model: @person do |form| %>
<%= text_field_with_label form, :first_name %>
<% end %>
se puede reemplazar por
<%= form_with model: @person, builder: LabellingFormBuilder do |form| %>
<%= form.text_field :first_name %>
<% end %>
definiendo una clase LabellingFormBuilder
similar a la siguiente:
class LabellingFormBuilder < ActionView::Helpers::FormBuilder
def text_field(attribute, options = {})
label(attribute) + super
end
end
Si reutilizas esto con frecuencia, puedes definir un helper labeled_form_with
que aplique automáticamente la opción builder: LabellingFormBuilder
:
def labeled_form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
options[:builder] = LabellingFormBuilder
form_with model: model, scope: scope, url: url, format: format, **options, &block
end
El constructor de formularios utilizado también determina qué sucede cuando haces:
<%= render partial: f %>
Si f
es una instancia de ActionView::Helpers::FormBuilder
, esto renderizará el partial form
, estableciendo el objeto del partial en el constructor de formularios. Si el constructor de formularios es de la clase LabellingFormBuilder
, entonces se renderizará el partial labelling_form
en su lugar.
8 Entendiendo las convenciones de nomenclatura de parámetros
Los valores de los formularios pueden estar en el nivel superior del hash params
o anidados en otro hash. Por ejemplo, en una acción create
estándar para un modelo Person, params[:person]
generalmente sería un hash de todos los atributos de la persona a crear. El hash params
también puede contener arrays, arrays de hashes, y así sucesivamente.
Fundamentalmente, los formularios HTML no conocen ningún tipo de datos estructurados, todo lo que generan son pares de nombre-valor, donde los pares son simplemente cadenas simples. Los arrays y hashes que ves en tu aplicación son el resultado de algunas convenciones de nomenclatura de parámetros que Rails utiliza.
8.1 Estructuras básicas
Las dos estructuras básicas son arrays y hashes. Los hashes reflejan la sintaxis utilizada para acceder al valor en params
. Por ejemplo, si un formulario contiene:
<input id="person_name" name="person[name]" type="text" value="Henry"/>
el hash params
contendrá
{ 'person' => { 'name' => 'Henry' } }
y params[:person][:name]
recuperará el valor enviado en el controlador.
Los hashes pueden estar anidados tantos niveles como se requiera, por ejemplo:
<input id="person_address_city" name="person[address][city]" type="text" value="New York"/>
resultará en el hash params
siendo
{ 'person' => { 'address' => { 'city' => 'New York' } } }
Normalmente, Rails ignora los nombres de parámetros duplicados. Si el nombre del parámetro termina con un conjunto vacío de corchetes []
, se acumularán en un array. Si deseas que los usuarios puedan ingresar múltiples números de teléfono, puedes colocar esto en el formulario:
<input name="person[phone_number][]" type="text"/>
<input name="person[phone_number][]" type="text"/>
<input name="person[phone_number][]" type="text"/>
Esto hará que params[:person][:phone_number]
sea un array que contiene los números de teléfono ingresados.
8.2 Combinándolos
Podemos mezclar y combinar estos dos conceptos. Un elemento de un hash puede ser un array como en el ejemplo anterior, o puedes tener un array de hashes. Por ejemplo, un formulario podría permitirte crear cualquier número de direcciones repitiendo el siguiente fragmento de formulario:
<input name="person[addresses][][line1]" type="text"/>
<input name="person[addresses][][line2]" type="text"/>
<input name="person[addresses][][city]" type="text"/>
<input name="person[addresses][][line1]" type="text"/>
<input name="person[addresses][][line2]" type="text"/>
<input name="person[addresses][][city]" type="text"/>
Esto hará que params[:person][:addresses]
sea un array de hashes con claves line1
, line2
y city
.
Sin embargo, hay una restricción: aunque los hashes pueden estar anidados arbitrariamente, solo se permite un nivel de "arrayness". Los arrays generalmente se pueden reemplazar por hashes; por ejemplo, en lugar de tener un array de objetos de modelo, se puede tener un hash de objetos de modelo indexados por su id, un índice de array u otro parámetro.
ADVERTENCIA: Los parámetros de matriz no funcionan bien con el ayudante check_box
. Según la especificación HTML, las casillas de verificación no marcadas no envían ningún valor. Sin embargo, a menudo es conveniente que una casilla de verificación siempre envíe un valor. El ayudante check_box
simula esto creando una entrada oculta auxiliar con el mismo nombre. Si la casilla de verificación no está marcada, solo se envía la entrada oculta y si está marcada, se envían ambas, pero el valor enviado por la casilla de verificación tiene prioridad.
8.3 La opción :index
del ayudante fields_for
Digamos que queremos renderizar un formulario con un conjunto de campos para cada una de las direcciones de una persona. El ayudante fields_for
con su opción :index
puede ayudar:
<%= form_with model: @person do |person_form| %>
<%= person_form.text_field :name %>
<% @person.addresses.each do |address| %>
<%= person_form.fields_for address, index: address.id do |address_form| %>
<%= address_form.text_field :city %>
<% end %>
<% end %>
<% end %>
Suponiendo que la persona tiene dos direcciones con los IDs 23 y 45, el formulario anterior renderizaría una salida similar a:
<form accept-charset="UTF-8" action="/people/1" method="post">
<input name="_method" type="hidden" value="patch" />
<input id="person_name" name="person[name]" type="text" />
<input id="person_address_23_city" name="person[address][23][city]" type="text" />
<input id="person_address_45_city" name="person[address][45][city]" type="text" />
</form>
Lo cual resultará en un hash params
que se ve así:
{
"person" => {
"name" => "Bob",
"address" => {
"23" => {
"city" => "Paris"
},
"45" => {
"city" => "London"
}
}
}
}
Todos los campos de entrada del formulario se asignan al hash "person"
porque llamamos a fields_for
en el constructor de formularios person_form
. Además, al especificar index: address.id
, renderizamos el atributo name
de cada campo de ciudad como person[address][#{address.id}][city]
en lugar de person[address][city]
. De esta manera, podemos determinar qué registros de dirección deben modificarse al procesar el hash params
.
Puede pasar otros números o cadenas de importancia a través de la opción :index
. Incluso puede pasar nil
, lo que producirá un parámetro de matriz.
Para crear anidaciones más complejas, puede especificar la parte inicial del nombre de entrada explícitamente. Por ejemplo:
<%= fields_for 'person[address][primary]', address, index: address.id do |address_form| %>
<%= address_form.text_field :city %>
<% end %>
creará campos de entrada como:
<input id="person_address_primary_23_city" name="person[address][primary][23][city]" type="text" value="Paris" />
También puede pasar una opción :index
directamente a ayudantes como text_field
, pero generalmente es menos repetitivo especificarlo en el nivel del constructor de formularios que en campos de entrada individuales.
Hablando en general, el nombre final de la entrada será una concatenación del nombre dado a fields_for
/ form_with
, el valor de la opción :index
y el nombre del atributo.
Por último, como atajo, en lugar de especificar un ID para :index
(por ejemplo, index: address.id
), puede agregar "[]"
al nombre dado. Por ejemplo:
<%= fields_for 'person[address][primary][]', address do |address_form| %>
<%= address_form.text_field :city %>
<% end %>
produce exactamente la misma salida que nuestro ejemplo original.
9 Formularios para recursos externos
Los ayudantes de formularios de Rails también se pueden utilizar para construir un formulario para enviar datos a un recurso externo. Sin embargo, a veces puede ser necesario establecer un authenticity_token
para el recurso; esto se puede hacer pasando un parámetro authenticity_token: 'your_external_token'
a las opciones de form_with
:
<%= form_with url: 'http://farfar.away/form', authenticity_token: 'external_token' do %>
Contenido del formulario
<% end %>
A veces, al enviar datos a un recurso externo, como una pasarela de pago, los campos que se pueden utilizar en el formulario están limitados por una API externa y puede ser indeseable generar un authenticity_token
. Para no enviar un token, simplemente pase false
a la opción :authenticity_token
:
<%= form_with url: 'http://farfar.away/form', authenticity_token: false do %>
Contenido del formulario
<% end %>
10 Construyendo formularios complejos
Muchas aplicaciones van más allá de los formularios simples que editan un solo objeto. Por ejemplo, al crear una Persona
, es posible que desee permitir al usuario (en el mismo formulario) crear varios registros de dirección (casa, trabajo, etc.). Al editar posteriormente esa persona, el usuario debería poder agregar, eliminar o modificar direcciones según sea necesario.
10.1 Configurando el modelo
Active Record proporciona soporte a nivel de modelo a través del método accepts_nested_attributes_for
:
class Person < ApplicationRecord
has_many :addresses, inverse_of: :person
accepts_nested_attributes_for :addresses
end
class Address < ApplicationRecord
belongs_to :person
end
Esto crea un método addresses_attributes=
en Person
que le permite crear, actualizar y (opcionalmente) destruir direcciones.
10.2 Formularios anidados
El siguiente formulario permite a un usuario crear una Persona
y sus direcciones asociadas.
<%= form_with model: @person do |form| %>
Direcciones:
<ul>
<%= form.fields_for :addresses do |addresses_form| %>
<li>
<%= addresses_form.label :kind %>
<%= addresses_form.text_field :kind %>
<%= addresses_form.label :street %>
<%= addresses_form.text_field :street %>
...
</li>
<% end %>
</ul>
<% end %>
Cuando una asociación acepta atributos anidados, fields_for
renderiza su bloque una vez por cada elemento de la asociación. En particular, si una persona no tiene direcciones, no renderiza nada. Un patrón común es que el controlador construya uno o más hijos vacíos para que al menos se muestre un conjunto de campos al usuario. El siguiente ejemplo resultaría en 2 conjuntos de campos de dirección que se renderizarían en el formulario de nueva persona.
def new
@person = Person.new
2.times { @person.addresses.build }
end
fields_for
proporciona un generador de formularios. El nombre de los parámetros será lo que accepts_nested_attributes_for
espera. Por ejemplo, al crear un usuario con 2 direcciones, los parámetros enviados se verían así:
{
'person' => {
'name' => 'John Doe',
'addresses_attributes' => {
'0' => {
'kind' => 'Home',
'street' => '221b Baker Street'
},
'1' => {
'kind' => 'Office',
'street' => '31 Spooner Street'
}
}
}
}
Los valores reales de las claves en el hash :addresses_attributes
no son importantes; sin embargo, deben ser cadenas de enteros y diferentes para cada dirección.
Si el objeto asociado ya está guardado, fields_for
genera automáticamente un campo oculto con el id
del registro guardado. Puedes desactivar esto pasando include_id: false
a fields_for
.
10.3 El controlador
Como de costumbre, debes declarar los parámetros permitidos en el controlador antes de pasarlos al modelo:
def create
@person = Person.new(person_params)
# ...
end
private
def person_params
params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street])
end
10.4 Eliminación de objetos
Puedes permitir a los usuarios eliminar objetos asociados pasando allow_destroy: true
a accepts_nested_attributes_for
class Person < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses, allow_destroy: true
end
Si el hash de atributos para un objeto contiene la clave _destroy
con un valor que se evalúa como true
(por ejemplo, 1, '1', true o 'true'), entonces el objeto se eliminará. Este formulario permite a los usuarios eliminar direcciones:
<%= form_with model: @person do |form| %>
Direcciones:
<ul>
<%= form.fields_for :addresses do |addresses_form| %>
<li>
<%= addresses_form.check_box :_destroy %>
<%= addresses_form.label :kind %>
<%= addresses_form.text_field :kind %>
...
</li>
<% end %>
</ul>
<% end %>
No olvides actualizar los parámetros permitidos en tu controlador para incluir también el campo _destroy
:
def person_params
params.require(:person).
permit(:name, addresses_attributes: [:id, :kind, :street, :_destroy])
end
10.5 Evitar registros vacíos
A menudo es útil ignorar conjuntos de campos que el usuario no ha completado. Puedes controlar esto pasando un bloque :reject_if
a accepts_nested_attributes_for
. Este bloque se llamará con cada hash de atributos enviado por el formulario. Si el bloque devuelve true
, entonces Active Record no construirá un objeto asociado para ese hash. El siguiente ejemplo solo intentará construir una dirección si el atributo kind
está establecido.
class Person < ApplicationRecord
has_many :addresses
accepts_nested_attributes_for :addresses, reject_if: lambda { |attributes| attributes['kind'].blank? }
end
Como conveniencia, también puedes pasar el símbolo :all_blank
, que creará un bloque que rechazará registros donde todos los atributos estén en blanco, excluyendo cualquier valor para _destroy
.
10.6 Añadir campos sobre la marcha
En lugar de renderizar varios conjuntos de campos de antemano, es posible que desees agregarlos solo cuando un usuario haga clic en un botón "Agregar nueva dirección". Rails no proporciona soporte incorporado para esto. Al generar nuevos conjuntos de campos, debes asegurarte de que la clave del array asociado sea única; la fecha actual en JavaScript (milisegundos desde la época) es una elección común.
11 Uso de Tag Helpers sin un generador de formularios
En caso de que necesites renderizar campos de formulario fuera del contexto de un generador de formularios, Rails proporciona ayudantes de etiquetas para elementos de formulario comunes. Por ejemplo, check_box_tag
:
<%= check_box_tag "accept" %>
Salida:
<input type="checkbox" name="accept" id="accept" value="1" />
Generalmente, estos ayudantes tienen el mismo nombre que sus contrapartes de generador de formularios, pero con un sufijo _tag
. Para obtener una lista completa, consulta la documentación de la API de FormTagHelper
.
12 Uso de form_tag
y form_for
Antes de que se introdujera form_with
en Rails 5.1, su funcionalidad solía estar dividida entre form_tag
y form_for
. Ambos están ahora en desuso suave. La documentación sobre su uso se puede encontrar en versiones anteriores de esta guía.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.
También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.
Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.
Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.