Action View Form Helpers - LPC-Ltda/Ruby-on-Rails GitHub Wiki

Los formularios en las aplicaciones web son una interfaz esencial para el input del usuario. Sin embargo, el markup de formularios puede volverse tedioso para escribir y mantener debido a la necesidad de manejar los nombres de los controles de formularios y sus numerosos atributos. Rails elimina esta complejidad al proporcionar helpers de visualización para generar el markup de formularios. Sin embargo, dado que estos helpers tienen diferentes casos de uso, los desarrolladores deben conocer las diferencias entre los métodos de ayuda antes de utilizarlos.

Después de leer esta guía, sabrás:

  • Cómo crear formularios de búsqueda y tipos similares de formularios genéricos que no representan ningún modelo específico en su aplicación.
  • Cómo hacer formularios modelo-centrados para crear y editar registros de bases de datos específicas.
  • Cómo generar select boxes para múltiples tipos de datos.
  • ¿Qué fecha y hora provee Rails?
  • Qué hace diferente un formulario de carga de archivos.
  • Cómo publicar formularios de recursos externos y especificar la configuración de un authenticity_token.
  • Cómo construir formas complejas.

1 Tratar con Formularios Básicos

El ayudante de formulario más básico es form_tag.

<%= form_tag do %>
  Form contents
<% end %>

Cuando se llama sin argumentos como este, crea una etiqueta <form> que, cuando se envía, POST a la página actual. Por ejemplo, asumiendo que la página actual es /home/index, el HTML generado se verá así (algunos saltos de línea agregados para facilitar la lectura):

<form accept-charset="UTF-8" action="/" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />
  Form contents
</form>

Notarás que el HTML contiene un elemento input con type=hidden. Este input es importante, porque el formulario no se puede enviar correctamente sin el. El elemento input hidden con el name=utf8 obliga a los navegadores a respetar adecuadamente la codificación de caracteres de su formulario y se genera para todos los formularios, ya sea que su acción sea "GET" o "POST".

1.1 Un formulario de búsqueda genérico

Una de los formularios más simples que ves en la web es un formulario de búsqueda. Este formulario contiene:

  • un elemento formulario con método "GET",
  • una etiqueta para el input,
  • un elemento de input de texto, y
  • un elemento de envío (submit).

Para crear este formulario, utilizará form_tag, label_tag, text_field_tag ​​y submit_tag, respectivamente. De la siguiente forma:

<%= form_tag("/search", method: "get") do %>
  <%= label_tag(:q, "Search for:") %>
  <%= text_field_tag(:q) %>
  <%= submit_tag("Search") %>
<% end %>

Esto generará el siguiente HTML:

<form accept-charset="UTF-8" action="/search" method="get">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <label for="q">Search for:</label>
  <input id="q" name="q" type="text" />
  <input name="commit" type="submit" value="Search" />
</form>

Para cada input de formulario, se genera un atributo de ID a partir de su nombre ("q" en el ejemplo anterior). Estas ID pueden ser muy útiles para el estilo CSS o la manipulación de controles de formulario con JavaScript.

Además de text_field_tag ​​y submit_tag, hay un helper similar para cada control de formulario en HTML.

Siempre use "GET" como método de búsqueda. Esto permite a los usuarios marcar una búsqueda específica y volver a ella. En general, Rails lo alienta a usar el verbo HTTP correcto para una acción.

1.2 Hash múltiples en llamadas a helpers de formulario

El helper form_tag acepta 2 argumentos: la ruta de la acción y un hash de opciones. Este hash especifica el método de envío del formulario y las opciones HTML, como la clase del elemento del formulario.

Al igual que con el helper link_to, el argumento de ruta no tiene que ser una cadena; Puede ser un hash de parámetros de URL reconocibles por el mecanismo de enrutamiento de Rails, que convertirá el hash en una URL válida. Sin embargo, dado que ambos argumentos para form_tag son hashes, puede encontrar fácilmente un problema si desea especificar ambos. Por ejemplo, digamos que escribes esto:

form_tag(controller: "people", action: "search", method: "get", class: "nifty_form")
# => '<form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">'

Aquí, el método y la clase se anexan a la cadena de consulta de la URL generada porque, aunque usted quiere escribir dos hashes, en realidad solo especificó uno. Así que debes decirle a Ruby cuál es cuál al delimitar el primer hash (o ambos) con llaves. Esto generará el HTML que esperas:

form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_form")
# => '<form accept-charset="UTF-8" action="/people/search" method="get" class="nifty_form">'

1.3 Helpers para generar elementos de formulario

Rails proporciona una serie de helpers para generar elementos de formulario como casillas de verificación, campos de texto y botones de radio. Estos helpers básicos, con nombres que terminan en _tag (como text_field_tag ​​y check_box_tag), generan un solo elemento <input>. El primer parámetro para estos es siempre el nombre del input. Cuando se envíe el formulario, el nombre se pasará junto con los datos del formulario y se abrirá paso a los parámetros en el controlador con el valor ingresado por el usuario para ese campo. Por ejemplo, si el formulario contiene <% = text_field_tag ​​(:query)%>, entonces podrá obtener el valor de este campo en el controlador con params[:query].

Al nombrar imputs Rails utiliza ciertas convenciones que hacen posible enviar parámetros con valores no escalables, como matrices o hashes, que también estarán accesibles en parámetros. Puede leer más sobre ellos en el capítulo 7 de esta guía. Para obtener detalles sobre el uso preciso de estos helpers, consulte la documentación de la API.

1.3.1 Checkboxes

Las Checkboxes son controles de formulario que le dan al usuario un conjunto de opciones que pueden habilitar o deshabilitar:

<%= check_box_tag(:pet_dog) %>
<%= label_tag(:pet_dog, "I own a dog") %>
<%= check_box_tag(:pet_cat) %>
<%= label_tag(:pet_cat, "I own a cat") %>

Esto genera lo siguiente:

<input id="pet_dog" name="pet_dog" type="checkbox" value="1" />
<label for="pet_dog">I own a dog</label>
<input id="pet_cat" name="pet_cat" type="checkbox" value="1" />
<label for="pet_cat">I own a cat</label>

El primer parámetro para check_box_tag, por supuesto, es el nombre del input. El segundo parámetro, naturalmente, es el valor del input. Este valor se incluirá en los datos del formulario (y estará presente en los parámetros) cuando se marque el checkbox.

1.3.2 Radio Buttons

Los radio buttons, aunque son similares a los checkboxes, son controles que especifican un conjunto de opciones que se excluyen mutuamente (es decir, el usuario solo puede elegir una):

<%= radio_button_tag(:age, "child") %>
<%= label_tag(:age_child, "I am younger than 21") %>
<%= radio_button_tag(:age, "adult") %>
<%= label_tag(:age_adult, "I'm over 21") %>

Salida:

<input id="age_child" name="age" type="radio" value="child" />
<label for="age_child">I am younger than 21</label>
<input id="age_adult" name="age" type="radio" value="adult" />
<label for="age_adult">I'm over 21</label>

Al igual que con check_box_tag, el segundo parámetro para radio_button_tag es el valor del input. Debido a que estos dos botones de radio comparten el mismo nombre (age), el usuario solo podrá seleccionar uno de ellos, y params[:age] contendrán "child" o "adult".

Siempre use etiquetas para checkboxes y radio buttons. Asocian el texto con una opción específica y, al expandir la región en la que se puede hacer clic, facilitan a los usuarios hacer clic en los inputs.

1.4 Otros helpers de interés

Otros controles de formulario que vale la pena mencionar son textareas, campos de contraseña, campos ocultos, campos de búsqueda, campos de teléfono, campos de fecha, campos de color, campos de fecha y hora locales, campos de mes, campos de semana, campos de URL, campos de correo electrónico, campos de número y campos de rango:

<%= text_area_tag(:message, "Hi, nice site", size: "24x6") %>
<%= password_field_tag(:password) %>
<%= hidden_field_tag(:parent_id, "5") %>
<%= search_field(:user, :name) %>
<%= telephone_field(:user, :phone) %>
<%= date_field(:user, :born_on) %>
<%= datetime_local_field(:user, :graduation_day) %>
<%= month_field(:user, :birthday_month) %>
<%= week_field(:user, :birthday_week) %>
<%= url_field(:user, :homepage) %>
<%= email_field(:user, :address) %>
<%= color_field(:user, :favorite_color) %>
<%= time_field(:task, :started_at) %>
<%= number_field(:product, :price, in: 1.0..20.0, step: 0.5) %>
<%= range_field(:product, :discount, in: 1..100) %>

Salida:

<textarea id="message" name="message" cols="24" rows="6">Hi, nice site</textarea>
<input id="password" name="password" type="password" />
<input id="parent_id" name="parent_id" type="hidden" value="5" />
<input id="user_name" name="user[name]" type="search" />
<input id="user_phone" name="user[phone]" type="tel" />
<input id="user_born_on" name="user[born_on]" type="date" />
<input id="user_graduation_day" name="user[graduation_day]" type="datetime-local" />
<input id="user_birthday_month" name="user[birthday_month]" type="month" />
<input id="user_birthday_week" name="user[birthday_week]" type="week" />
<input id="user_homepage" name="user[homepage]" type="url" />
<input id="user_address" name="user[address]" type="email" />
<input id="user_favorite_color" name="user[favorite_color]" type="color" value="#000000" />
<input id="task_started_at" name="task[started_at]" type="time" />
<input id="product_price" max="20.0" min="1.0" name="product[price]" step="0.5" type="number" />
<input id="product_discount" max="100" min="1" name="product[discount]" type="range" />

Los inputs hidden no se muestran al usuario, sino que contienen datos como cualquier entrada de texto. Los valores dentro de ellos pueden ser cambiados con JavaScript.

Las entradas de búsqueda, teléfono, fecha, hora, color, fecha y hora, fecha y hora local, mes, semana, URL, correo electrónico, número y rango son controles de HTML5. Si necesita que su aplicación tenga una experiencia consistente en navegadores antiguos, necesitará un polyfill 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 basada en la presencia de funciones HTML5 detectadas.

Si está utilizando campos de entrada de contraseña (para cualquier propósito), es posible que desee configurar su aplicación para evitar que esos parámetros se registren. Puedes aprender sobre esto en la Guía de seguridad.

2 Tratar con objetos modelo

2.1 Model Object Helpers

Una tarea particularmente común para un formulario es editar o crear un objeto modelo. Si bien los helpers *_tag pueden usarse para esta tarea, son bastante detallados, ya que para cada etiqueta tendría que asegurarse de que se use el nombre de parámetro correcto y establecer el valor predeterminado de la entrada de manera apropiada. Rails proporciona helpers adaptados a esta tarea. Estos helpers carecen del sufijo _tag, por ejemplo, text_field, text_area.

Para estos helpers, el primer argumento es el nombre de una variable de instancia y el segundo es el nombre de un método (generalmente un atributo) para llamar en ese objeto. Rails establecerá el valor del control de entrada al valor de retorno de ese método para el objeto y establecerá un nombre de input apropiado. Si su controlador ha definido @person y el nombre de esa persona es Henry, entonces un formulario que contiene:

<%= text_field(:person, :name) %>

producirá una salida similar a

<input id="person_name" name="person[name]" type="text" value="Henry"/>

Al enviar el formulario, el valor ingresado por el usuario se almacenará en params[:person][:name]. El hash params[:person] es adecuado para pasar a Person.new o, si @person es una instancia de Person, @person.update. Si bien el nombre de un atributo es el segundo parámetro más común para estos helpers, esto no es obligatorio. En el ejemplo anterior, siempre que los objetos person tengan un name y un método nombre=, Rails estará contento.

Debe pasar el nombre de una variable de instancia, es decir :person o "person", no una instancia real de su objeto modelo.

Rails proporciona helpers para mostrar los errores de validación asociados con un objeto modelo. Estos están cubiertos en detalle por la guía de validación de Active Records.

2.2 Binding de un formulario a un objeto

Si bien esto es un aumento en la comodidad, está lejos de ser perfecto. Si la persona tiene muchos atributos para editar, estaremos repitiendo el nombre del objeto editado muchas veces. Lo que queremos hacer es vincular de alguna manera un formulario a un objeto modelo, que es exactamente lo que hace form_for.

Supongamos que tenemos un controlador para tratar con los artículos app/controllers/articles_controller.rb:

def new
  @article = Article.new
end

La vista correspondiente app/views/articles/new.html.erb usando form_for se ve así:

<%= form_for @article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %>
  <%= f.text_field :title %>
  <%= f.text_area :body, size: "60x12" %>
  <%= f.submit "Create" %>
<% end %>

Hay algunas cosas a tener en cuenta aquí:

  • @article es el objeto real que se está editando.
  • Hay un solo hash de opciones. Las opciones de enrutamiento se pasan en el hash :url, las opciones HTML se pasan en el hash :html. También puede proporcionar una opción :namespace para su formulario para garantizar la singularidad de los atributos de identificación en los elementos del formulario. El atributo namespace será prefijado con un guión bajo en la identificación HTML generada.
  • El método form_for produce un objeto generador de formularios (la variable f).
  • Los métodos para crear controles de formulario se invocan en el objeto generador de formularios f.

El HTML resultante es:

<form class="nifty_form" id="new_article" action="/articles" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input type="hidden" name="authenticity_token" value="NRkFyRWxdYNfUg7vYxLOp2SLf93lvnl+QwDWorR42Dp6yZXPhHEb6arhDOIWcqGit8jfnrPwL781/xlrzj63TA==" />
  <input type="text" name="article[title]" id="article_title" />
  <textarea name="article[body]" id="article_body" cols="60" rows="12"></textarea>
  <input type="submit" name="commit" value="Create" data-disable-with="Create" />
</form>

El nombre pasado a form_for controla la clave utilizada en params para acceder a los valores del formulario. Aquí el nombre es article y, por lo tanto, todas las entradas tienen nombres del formulario article[nombre_atributo]. En consecuencia, en la acción create, params[:article] será un hash con claves :title y :body. Puede leer más sobre el significado de los nombres de input en la sección parameter_names.

Los métodos helpers llamados en el generador de formularios son idénticos a los helpers del objeto modelo, excepto que no es necesario especificar qué objeto se está editando, ya que el generador de formularios ya lo administra.

Puede crear un binding similar sin crear realmente etiquetas <form> con el helper fields_for. Esto es útil para editar objetos de modelo adicionales con la misma forma. Por ejemplo, si tuviera un modelo de Person con un modelo de ContactDetail asociado, podría crear un formulario para crear los dos así:

<%= form_for @person, url: {action: "create"} do |person_form| %>
  <%= person_form.text_field :name %>
  <%= fields_for @person.contact_detail do |contact_detail_form| %>
    <%= contact_detail_form.text_field :phone_number %>
  <% end %>
<% end %>

que produce el siguiente resultado:

<form class="new_person" id="new_person" action="/people" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <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 generado por fields_for es un generador de formularios como el generado por form_for (de hecho, form_for llama a fields_for internamente).

2.3 Confiando en 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, debe declararlo como un resource:

resources :articles

Declarar un resource tiene un número de efectos laterales. Vea Rails Routing From the Outside In para más información acerca del seteo y uso de resources.

Al tratar con recursos REST, las llamadas a form_for pueden ser significativamente más fáciles si confía en la identificación de registros. En resumen, puede pasar la instancia del modelo y hacer que Rails descubra el nombre del modelo y el resto:

## Creating a new article
# long-style:
form_for(@article, url: articles_path)
# same thing, short-style (record identification gets used):
form_for(@article)
 
## Editing an existing article
# long-style:
form_for(@article, url: article_path(@article), html: {method: "patch"})
# short-style:
form_for(@article)

Observe cómo el estilo corto para la invocación form_for es convenientemente el mismo, independientemente de que el registro sea nuevo o existente. La identificación del registro es lo suficientemente inteligente como para saber si el registro es nuevo al solicitar record.new_record?. También selecciona la ruta correcta para enviar y el nombre basado en la clase del objeto.

Rails también establecerá automáticamente la class y el id del formulario de forma adecuada: un formulario que crea un article tendría el id y la class new_article. Si estuviera editando el artículo con id 23, la clase se establecería en edit_article y la id en edit_article_23. Estos atributos se omitirán por brevedad en el resto de esta guía.

Cuando utiliza STI (single-table inheritance) con sus modelos, no puede confiar en la identificación de registros en una subclase si solo su clase primaria se declara un recurso. Deberá especificar el nombre del modelo, la :url y el método explícitamente.

2.3.1 Tratando con Namespaces

Si ha creado rutas con namespaces, form_for tiene una taquigrafía ingeniosa para eso también. Si su aplicación tiene un namespace admin entonces

form_for [:admin, @article]

creará un formulario que se envía al ArticlesController dentro del namespace admin (que se envía a admin_article_path(@article) en el caso de una actualización). Si tiene varios niveles de namespaces, la sintaxis es similar:

form_for [:admin, :management, @article]

Para obtener más información sobre el sistema de enrutamiento de Rails y las convenciones asociadas, consulte la guía de enrutamiento.

2.4 ¿Cómo funcionan los formularios con los métodos PATCH, PUT o DELETE?

El framework Rails fomenta el diseño RESTful de sus aplicaciones, lo que significa que hará muchas solicitudes de "PATCH" y "DELETE" (además de "GET" y "POST"). Sin embargo, la mayoría de los navegadores no admiten otros métodos que no sean "GET" y "POST" cuando se trata de enviar formularios.

Rails resuelve este problema emulando otros métodos sobre POST con un input oculto llamada "_method", que se establece para reflejar el método deseado:

form_tag(search_path, method: "patch")

salida:

<form accept-charset="UTF-8" action="/search" method="post">
  <input name="_method" type="hidden" value="patch" />
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
  ...
</form>

Al analizar los datos de POSTed, Rails tendrá en cuenta el parámetro _method especial y actuará como si el método HTTP fuera el especificado dentro ("PATCH" en este ejemplo).

3 Construyendo Select Boxes con Ease

Los cuadros de selección en HTML requieren una cantidad significativa de markup (un elemento de OPCIÓN para cada opción a elegir), por lo tanto, tiene más sentido que se generen dinámicamente.

Aquí es cómo se vería el markup:

<select name="city_id" id="city_id">
  <option value="1">Lisbon</option>
  <option value="2">Madrid</option>
  ...
  <option value="12">Berlin</option>
</select>

Aquí tiene una lista de ciudades cuyos nombres se presentan al usuario. Internamente, la aplicación solo quiere manejar sus ID para que se utilicen como el atributo de valor de las opciones. Vamos a ver cómo Rails puede ayudar aquí.

3.1 El Select y los tags de Option

El helper más genérico es select_tag, que, como su nombre lo indica, simplemente genera la etiqueta SELECT que encapsula una cadena de opciones:

<%= select_tag(:city_id, '<option value="1">Lisbon</option>...') %>

Esto es un comienzo, pero no crea dinámicamente las etiquetas de opción. Puede generar etiquetas de opción con el helper options_for_select:

<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...]) %>
 
output:
 
<option value="1">Lisbon</option>
<option value="2">Madrid</option>
...

El primer argumento de options_for_select es una matriz anidada donde cada elemento tiene dos elementos: texto de opción (nombre de ciudad) y valor de opción (id de ciudad). El valor de la opción es lo que se enviará a su controlador. A menudo, este será el ID de un objeto de base de datos correspondiente, pero no tiene que ser el caso.

Sabiendo esto, puede combinar select_tag y options_for_select para lograr el markup completo deseado:

<%= select_tag(:city_id, options_for_select(...)) %>

options_for_select le permite preseleccionar una opción pasando su valor.

<%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %>
 
output:
 
<option value="1">Lisbon</option>
<option value="2" selected="selected">Madrid</option>
...

Cuando Rails vea que el valor interno de una opción que se está generando coincide con este valor, agregará el atributo seleccionado a esa opción.

Cuando :include_blank o :prompt no están presentes, :include_blank es forzado verdadero si el atributo de selección required es verdadero, el size de visualización es uno y multiple no es verdadero.

Puedes agregar atributos arbitrarios a las opciones usando hashes:

<%= options_for_select(
  [
    ['Lisbon', 1, { 'data-size' => '2.8 million' }],
    ['Madrid', 2, { 'data-size' => '3.2 million' }]
  ], 2
) %>
 
output:
 
<option value="1" data-size="2.8 million">Lisbon</option>
<option value="2" selected="selected" data-size="3.2 million">Madrid</option>
...

3.2 Select Boxes para tratar con Modelos

En la mayoría de los casos, los controles de formulario estarán vinculados a un modelo de base de datos específico y, como es de esperar, Rails proporciona helpers personalizados para ese propósito. De acuerdo con otros helpers de formularios, cuando se trata de modelos, se elimina el sufijo _tag de select_tag:

# controller:
@person = Person.new(city_id: 2)
# view:
<%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %>

Observe que el tercer parámetro, la matriz de opciones, es el mismo tipo de argumento que pasa a options_for_select. Una ventaja aquí es que no tiene que preocuparse por la preselección de la ciudad correcta si el usuario ya tiene una. Rails lo hará por usted leyendo el atributo @person.city_id.

Al igual que con otros helpers, si utilizara el helper de selección en un generador de formularios con ámbito para el objeto @person, la sintaxis sería:

# select on a form builder
<%= f.select(:city_id, ...) %>

También puede pasar un bloque para el helper select:

<%= f.select(:city_id) do %>
  <% [['Lisbon', 1], ['Madrid', 2]].each do |c| -%>
    <%= content_tag(:option, c.first, value: c.last) %>
  <% end %>
<% end %>

Si está utilizando select (o helpers similares, como collection_select, select_tag) para establecer una asociación belongs_to usted debe pasar el nombre de la clave foránea (en el ejemplo anterior city_id), no el nombre de la asociación en sí. Si especifica city en lugar de city_id, Active Record generará un error en la línea de ActiveRecord::AssociationTypeMismatch:City(#17815740) expected, Got String(#1138750) al pasar el hash de parámetros a Person.new o update. Otra forma de ver esto es que los helpers de formularios solo editan atributos. También debe tener en cuenta las posibles ramificaciones de seguridad de permitir a los usuarios editar claves externas directamente.

3.3 Option Tags para una Colección de objetos arbitrarios

La generación de etiquetas de opciones con options_for_select requiere que cree una matriz que contenga el texto y el valor de cada opción. Pero, ¿qué pasaría si tuviera un modelo de City (tal vez uno Active Record) y quisiera generar etiquetas de opción a partir de una colección de esos objetos? Una solución sería hacer una matriz anidada iterando sobre ellos:

<% cities_array = City.all.map { |city| [city.name, city.id] } %>
<%= options_for_select(cities_array) %>

Esta es una solución perfectamente válida, pero Rails ofrece una alternativa menos detallada: options_from_collection_for_select. Este ayudante espera una colección de objetos arbitrarios y dos argumentos adicionales: los nombres de los métodos para leer el valor de la opción y el texto de, respectivamente:

<%= options_from_collection_for_select(City.all, :id, :name) %>

Como su nombre lo indica, esto solo genera etiquetas de opción. Para generar un cuadro de selección de trabajo, deberá usarlo junto con select_tag, tal como lo haría con options_for_select. Cuando se trabaja con objetos modelo, al igual que select combina select_tag y options_for_select, collection_select combina select_tag con options_from_collection_for_select.

<%= collection_select(:person, :city_id, City.all, :id, :name) %>

Al igual que con otros helpers, si utilizara el helper collection_select en un generador de formularios con ámbito para el objeto @person, la sintaxis sería:

<%= f.collection_select(:city_id, City.all, :id, :name) %>

Para recapitular, options_from_collection_for_select es a collection_select lo que options_for_select es a select.

Los pares pasados ​​a options_for_select deben tener el nombre primero y el id segundo, sin embargo, con options_from_collection_for_select el primer argumento es el método de valor y el segundo el método de texto.

3.4 Select de Time Zone y Country

Para aprovechar el soporte de zona horaria en Rails, debe preguntar a sus usuarios en qué zona horaria se encuentran. Para ello sería necesario generar opciones de selección de una lista de objetos de Zona horaria predefinidos usando collection_select, pero simplemente puede usar el helper time_zone_select que ya envuelve esto:

<%= time_zone_select(:person, :time_zone) %>

También existe el helper time_zone_options_for_select para una forma más manual (por lo tanto, más personalizable) de hacer esto. Lea la documentación de la API para conocer los posibles argumentos de estos dos métodos.

Rails solían tener un helper country_select para elegir países, pero esto se ha extraído al country_slect_plugin. Al usar esto, tenga en cuenta que la exclusión o inclusión de ciertos nombres de la lista puede ser algo controvertido (y fue la razón por la que esta funcionalidad se extrajo de Rails).

4 Usando helpers de formularios para Date y Time

Puede elegir no usar los helpers de formulario que generan los campos de entrada de fecha y hora de HTML5 y usar los helpers de fecha y hora alternativos. Estos helpers de fecha y hora difieren de los demás ayudantes de formularios en dos aspectos importantes:

  • Las fechas y horas no se pueden representar por un solo elemento de entrada. En su lugar, tiene varios, uno para cada componente (año, mes, día, etc.) y, por lo tanto, no hay un valor único en su hash de params con su fecha u hora.

  • Otros helpers usan el sufijo _tag para indicar si un helper es un helper de barebones o uno que opera en objetos modelo. Con fechas y horas, select_date, select_time y select_datetime son los helpers de barebones, date_select, time_select y datetime_select son los helpers de objetos modelo equivalentes.

Ambas familias de helpers crearán una serie de cuadros de selección para los diferentes componentes (año, mes, día, etc.).

4.1 Helpers Barebones

La familia de ayudantes select_* toma como primer argumento una instancia de Date, Time o DateTime que se usa como el valor seleccionado actualmente. Puede omitir este parámetro, en cuyo caso se utiliza la fecha actual. Por ejemplo:

<%= select_date Date.today, prefix: :start_date %>

salidas (con valores de opción reales omitidos por brevedad)

<select id="start_date_year" name="start_date[year]"> ... </select>
<select id="start_date_month" name="start_date[month]"> ... </select>
<select id="start_date_day" name="start_date[day]"> ... </select>

Las entradas anteriores darían lugar a que params[:start_date] sea un hash con las llaves :year, :month, :day. Para obtener un objeto Date, Time o DateTime real, tendría que extraer estos valores y pasarlos al constructor apropiado, por ejemplo:

Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i)

La opción :prefix es la clave utilizada para recuperar el hash de los componentes de fecha del hash params. Aquí es seteado a start_date, si se omite, se establecerá de forma predeterminada a date.

4.2 Helpers de objetos Modelo

select_date no funciona bien con los formularios que actualizan o crean objetos de Active Record, ya que Active Record espera que cada elemento del hash de parámetros corresponda a un atributo. Los helpers de objetos modelo para fechas y horas envían parámetros con nombres especiales; cuando Active Record ve parámetros con dichos nombres, sabe que deben combinarse con los otros parámetros y entregarse a un constructor apropiado para el tipo de columna. Por ejemplo:

<%= date_select :person, :birth_date %>

salidas (con valores de opción reales omitidos por brevedad)

<select id="person_birth_date_1i" name="person[birth_date(1i)]"> ... </select>
<select id="person_birth_date_2i" name="person[birth_date(2i)]"> ... </select>
<select id="person_birth_date_3i" name="person[birth_date(3i)]"> ... </select>

lo que resulta en un hash params como el siguiente:

{'person' => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}}

Cuando esto se pasa a Person.new (o update), Active Record señala que estos parámetros deberían usarse para construir el atributo birth_date y usa la información con sufijo para determinar en qué orden debe pasar estos parámetros a funciones como Date.civil.

4.3 Opciones Comunes

Ambas familias de helpers utilizan el mismo conjunto de funciones básicas para generar las etiquetas de selección individuales y, por lo tanto, ambas aceptan en gran medida las mismas opciones. En particular, por defecto, Rails generará opciones de año 5 años a cada lado del año actual. Si este no es un rango apropiado, las opciones :start_year y :end_year anulan esto. Para obtener una lista exhaustiva de las opciones disponibles, consulte la documentación de la API.

Como regla general, debe usar date_select cuando trabaje con objetos modelo y select_date en otros casos, como un formulario de búsqueda que filtre los resultados por fecha.

En muchos casos, los recolectores de fechas incorporados son torpes, ya que no ayudan al usuario a establecer la relación entre la fecha y el día de la semana.

4.4 Componentes Individuales

Ocasionalmente, debe mostrar solo un componente de fecha, como un año o un mes. Rails proporciona una serie de ayudantes para esto, uno para cada componente select_year, select_month, select_day, select_hour, select_minute, select_second. Estos ayudantes son bastante directos. De forma predeterminada, generarán un campo de entrada con el nombre del componente de tiempo (por ejemplo, "year" para año de selección, "month" para mes de selección, etc.) aunque esto se puede invalidar con la opción :field_name. La opción :prefix funciona de la misma manera que lo hace para select_date y select_time y tiene el mismo valor predeterminado.

El primer parámetro especifica qué valor debe seleccionarse y puede ser una instancia de una Date, Time o DateTime, en cuyo caso se extraerá el componente relevante o un valor numérico. Por ejemplo:

<%= select_year(2009) %>
<%= select_year(Time.now) %>

producirá la misma salida si el año actual es 2009 y el valor elegido por el usuario puede ser recuperado por params[:date][:year].

5 Subiendo Archivos

Una tarea común es subir algún tipo de archivo, ya sea una imagen de una persona o un archivo CSV que contenga datos para procesar. Lo más importante a recordar con las cargas de archivos es que la codificación del formulario renderizado DEBE establecerse en "multipart/form-data". Si usa form_for, esto se hace automáticamente. Si usa form_tag, debe configurarlo usted mismo, según el siguiente ejemplo.

Los dos formularios siguientes suben un archivo.

<%= form_tag({action: :upload}, multipart: true) do %>
  <%= file_field_tag 'picture' %>
<% end %>
 
<%= form_for @person do |f| %>
  <%= f.file_field :picture %>
<% end %>

Rails proporciona el par habitual de helpers: los barebones file_field_tag ​​y el orientado al modelo file_field. La única diferencia con otros ayudantes es que no puede establecer un valor predeterminado para las entradas de archivos, ya que esto no tendría ningún sentido. Como es de esperar en el primer caso, el archivo cargado está en params[:picture] y en el segundo caso en params[:person][:picture].

5.1 Lo que se sube

El objeto en el hash params es una instancia de una subclase de IO. Dependiendo del tamaño del archivo cargado, de hecho puede ser un StringIO o una instancia de un archivo respaldado por un archivo temporal. En ambos casos, el objeto tendrá un atributo original_filename que contiene el nombre que tenía el archivo en la computadora del usuario y un atributo content_type que contiene el tipo MIME del archivo cargado. El siguiente fragmento de código guarda el contenido cargado en #{Rails.root}/public/uploads con el mismo nombre que el archivo original (asumiendo que el formulario fue el del ejemplo anterior).

def upload
  uploaded_io = params[:person][:picture]
  File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file|
    file.write(uploaded_io.read)
  end
end

Una vez que se ha cargado un archivo, hay una multitud de tareas potenciales, desde dónde almacenar los archivos (en el disco, Amazon S3, etc.) y asociarlos con modelos para cambiar el tamaño de los archivos de imagen y generar miniaturas. Las complejidades de esto están más allá del alcance de esta guía, pero hay varias bibliotecas diseñadas para ayudar con esto. Dos de los más conocidos son CarrierWave y Paperclip.

Si el usuario no ha seleccionado un archivo, el parámetro correspondiente será una cadena vacía.

5.2 Tratando con Ajax

A diferencia de otras formas, hacer un formulario de carga asíncrona de archivos no es tan simple como proporcionar form_for con remote: true. Con un formulario Ajax, la serialización se realiza mediante JavaScript que se ejecuta dentro del navegador y, como JavaScript no puede leer archivos de su disco duro, no se puede cargar el archivo. La solución más común es usar un iframe invisible que sirva como objetivo para el envío del formulario.

6 Personalizando Form Builders

Como se mencionó anteriormente, el objeto generado por form_for y fields_for es una instancia de FormBuilder (o una subclase del mismo). Los creadores de formularios encapsulan la noción de mostrar elementos de formulario para un solo objeto. Si bien, por supuesto, puede escribir helpers para sus formularios de la forma habitual, también puede crear una subclase de FormBuilder y agregar los helpers allí. Por ejemplo:

<%= form_for @person do |f| %>
  <%= text_field_with_label f, :first_name %>
<% end %>

puede ser reemplazado con

<%= form_for @person, builder: LabellingFormBuilder do |f| %>
  <%= f.text_field :first_name %>
<% end %>

definiendo una clase de LabellingFormBuilder similar a la siguiente:

class LabellingFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(attribute, options={})
    label(attribute) + super
  end
end

Si reutiliza esto con frecuencia, podría definir un helper labeled_form_for que aplique automáticamente el generador: opción LabellingFormBuilder:

def labeled_form_for(record, options = {}, &block)
  options.merge! builder: LabellingFormBuilder
  form_for record, options, &block
end

El generador de formularios utilizado también determina lo que sucede cuando lo haces.

<%= render partial: f %>

Si f es una instancia de FormBuilder, esto hará que el formulario sea parcial, estableciendo el objeto del parcial en el generador de formularios. Si el creador de formularios es de la clase LabellingFormBuilder, en su lugar se procesará el parcial labelling_form.

7 Entendiendo las convenciones de nomenclatura de parámetros

Como ha visto en las secciones anteriores, los valores de formularios pueden estar en el nivel superior del hash params o anidados en otro hash. Por ejemplo, en una acción de creación estándar para un modelo Person, params[:person] normalmente sería un hash de todos los atributos para que la persona cree. El hash params también puede contener matrices, matrices de hashes, etc.

Fundamentalmente, los formularios HTML no conocen ningún tipo de datos estructurados, todo lo que generan son pares nombre-valor, donde los pares son cadenas simples. Las matrices y los hashes que ve en su aplicación son el resultado de algunas convenciones de nombres de parámetros que utiliza Rails.

7.1 Estructuras básicas

Las dos estructuras básicas son matrices y hashes. Hashes refleja 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 hash se pueden anidar tantos niveles como sea necesario, por ejemplo:

<input id="person_address_city" name="person[address][city]" type="text" value="New York"/>

resultará en el hash params

{'person' => {'address' => {'city' => 'New York'}}}

Normalmente Rails ignora los nombres de parámetros duplicados. Si el nombre del parámetro contiene un conjunto vacío de corchetes [], entonces se acumularán en una matriz. Si desea que los usuarios puedan ingresar múltiples números de teléfono, puede 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 daría lugar a que params[:person][:phone_number] sea una matriz que contenga los números de teléfono ingresados.

7.2 Combinándolos

Podemos mezclar y combinar estos dos conceptos. Un elemento de un hash podría ser una matriz como en el ejemplo anterior, o puede tener una matriz de hashes. Por ejemplo, un formulario podría permitirle crear cualquier número de direcciones repitiendo el siguiente fragmento de formulario:

<input name="addresses[][line1]" type="text"/>
<input name="addresses[][line2]" type="text"/>
<input name="addresses[][city]" type="text"/>

Esto daría lugar a que params[:address] sea una matriz de hashes con las teclas line1, line2 y city. Rails decide comenzar a acumular valores en un hash nuevo cada vez que encuentra un nombre de entrada que ya existe en el hash actual.

Sin embargo, hay una restricción, mientras que los hashes se pueden anidar arbitrariamente, solo se permite un nivel de "arrayness". Las matrices generalmente pueden ser reemplazadas por hashes; por ejemplo, en lugar de tener una matriz de objetos modelo, uno puede tener un hash de objetos modelo codificados por su id, un índice de matriz o algún otro parámetro.

Los parámetros de la matriz no juegan bien con el helper check_box. De acuerdo con la especificación HTML, las casillas de verificación sin marcar no envían ningún valor. Sin embargo, a menudo es conveniente que una casilla de verificación envíe siempre un valor. El ayudante check_box falsifica esto creando una entrada auxiliar oculta 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 las dos, pero el valor enviado por la casilla de verificación tiene prioridad. Cuando se trabaja con parámetros de matriz, este envío duplicado confundirá a Rails ya que los nombres de entrada duplicados son cómo decide cuándo iniciar un nuevo elemento de matriz. Es preferible usar check_box_tag o usar hashes en lugar de matrices.

7.3 Usando helpers de formularios

Las secciones anteriores no utilizaron los helpers de formulario Rails en absoluto. Si bien puede crear los nombres de entrada usted mismo y pasarlos directamente a ayudantes como text_field_tag ​​Rails también proporciona soporte de nivel superior. Las dos herramientas a su disposición aquí son el parámetro de nombre para form_for y fields_for y la opción :index que los ayudantes toman.

Es posible que desee representar un formulario con un conjunto de campos de edición para cada una de las direcciones de una persona. Por ejemplo:

<%= form_for @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 %>

Asumiendo que la persona tenía dos direcciones, con los identificadores 23 y 45, esto generaría una salida similar a esta:

<form accept-charset="UTF-8" action="/people/1" class="edit_person" id="edit_person_1" method="post">
  <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>

Esto dará como resultado un hash params parecido a:

{'person' => {'name' => 'Bob', 'address' => {'23' => {'city' => 'Paris'}, '45' => {'city' => 'London'}}}}

Rails sabe que todas estas entradas deben ser parte del hash de person porque usted llamó a fields_for en el primer generador de formularios. Al especificar una opción :index, le está diciendo a Rails que en lugar de nombrar al input person[address][city] debe insertar ese índice rodeado por [] entre la dirección y la ciudad. Esto suele ser útil, ya que es fácil localizar qué registro de dirección se debe modificar. Puede pasar números con algún otro significado, cadenas o incluso nil (lo que resultará en la creación de un parámetro de matriz).

Para crear anidamientos más complejos, puede especificar la primera parte del nombre de entrada (person[address] en el ejemplo anterior) explícitamente:

<%= fields_for 'person[address][primary]', address, index: address do |address_form| %>
  <%= address_form.text_field :city %>
<% end %>

creará entradas como

<input id="person_address_primary_1_city" name="person[address][primary][1][city]" type="text" value="bologna" />

Como regla general, el nombre de entrada final es la concatenación del nombre dado a fields_for/form_for, el valor de índice y el nombre del atributo. También puede pasar una opción :index directamente a los helpers, como text_field, pero generalmente es menos repetitivo especificar esto en el nivel del generador de formularios en lugar de en los controles de entrada individuales.

Como método abreviado, puede agregar [] al nombre y omitir la opción :index. Esto es lo mismo que especificar index: address para

<%= fields_for 'person[address][primary][]', address do |address_form| %>
  <%= address_form.text_field :city %>
<% end %>

produce exactamente la misma salida que el ejemplo anterior.

8 Formularios a Recursos Externos

Los helpers de formularios de Rails también se pueden utilizar para crear un formulario para publicar datos en un recurso externo. Sin embargo, a veces puede ser necesario establecer un identificador de autenticidad para el recurso; Esto se puede hacer pasando un parámetro authenticity_token: 'your_external_token' a las opciones form_tag:

<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token' do %>
  Form contents
<% end %>

A veces, al enviar datos a un recurso externo, como un gateway de pago, los campos que se pueden usar en el formulario están limitados por una API externa y puede ser indeseable generar un identificador de autenticidad. Para no enviar un token, simplemente pase false a la opción: authenticity_token:

<%= form_tag 'http://farfar.away/form', authenticity_token: false do %>
  Form contents
<% end %>

La misma técnica también está disponible para form_for:

<%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
  Form contents
<% end %>

O si no quieres representar un campo de autenticidad:

<%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
  Form contents
<% end %>

9 Formas complejas de construcción

Muchas aplicaciones crecen más allá de formas simples editando un solo objeto. Por ejemplo, al crear una Person, es posible que desee permitir al usuario (en la misma forma) crear múltiples registros de direcciones (hogar, trabajo, etc.). Cuando edite posteriormente a esa persona, el usuario debe poder agregar, eliminar o modificar direcciones según sea necesario.

9.1 Configurando el modelo

Active Record proporciona soporte a nivel de modelo a través del método accept_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 Address_attributes= en Person que le permite crear, actualizar y (opcionalmente) destruir direcciones.

9.2 Formularios anidados

El siguiente formulario le permite a un usuario crear una Persona y sus direcciones asociadas.

<%= form_for @person do |f| %>
  Addresses:
  <ul>
    <%= f.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 procesa su bloque una vez para cada elemento de la asociación. En particular, si una persona no tiene direcciones, no rinde nada. Un patrón común es que el controlador construya uno o más hijos vacíos para que al usuario se le muestre al menos un conjunto de campos. El siguiente ejemplo daría como resultado que se representen 2 conjuntos de campos de dirección en el formulario de persona nueva.

def new
  @person = Person.new
  2.times { @person.addresses.build }
end

El campo fields_for produce un generador de formularios. El nombre de los parámetros será lo que se espera que acepte. 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'
      }
    }
  }
}

Las claves del hash: Address_attributes no son importantes, simplemente necesitan ser diferentes para cada dirección.

Si el objeto asociado ya está guardado, fields_for genera automáticamente una entrada oculta con el ID del registro guardado. Puede deshabilitar esto pasando include_id: false a fields_for. Es posible que desee hacer esto si la entrada generada automáticamente se coloca en una ubicación donde una etiqueta de entrada no es un HTML válido o cuando se usa un ORM donde los niños no tienen una identificación.

9.3 El Controlador

Como es habitual, debe agregar a la lista blanca los parámetros 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

9.4 Eliminando objetos

Puede permitir que los usuarios eliminen objetos asociados pasando allow_destroy: true a accept_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 de 1 o true, entonces el objeto será destruido. Este formulario permite a los usuarios eliminar direcciones:

<%= form_for @person do |f| %>
  Addresses:
  <ul>
    <%= f.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 olvide actualizar los parámetros de la lista blanca en su controlador para incluir también el campo _destroy:

def person_params
  params.require(:person).
    permit(:name, addresses_attributes: [:id, :kind, :street, :_destroy])
end

9.5 Prevención de registros vacíos

A menudo es útil ignorar los conjuntos de campos que el usuario no ha completado. Puede controlar esto pasando un proceso: reject_if a accept_nested_attributes_for. Este proceso se llamará con cada hash de atributos enviados por el formulario. Si el proceso devuelve false, el Registro activo no generará un objeto asociado para ese hash. El siguiente ejemplo solo intenta construir una dirección si se establece el atributo kind.

class Person < ApplicationRecord
  has_many :addresses
  accepts_nested_attributes_for :addresses, reject_if: lambda {|attributes| attributes['kind'].blank?}
end

Para su comodidad, puede pasar el símbolo :all_blank, que creará un proceso que rechazará los registros donde todos los atributos estén en blanco, excluyendo cualquier valor para _destroy.

9.6 Agregando campos sobre la marcha

En lugar de presentar varios conjuntos de campos con anticipación, es posible que desee agregarlos solo cuando un usuario haga clic en el botón 'Agregar nueva dirección'. Rails no proporciona ningún soporte integrado para esto. Al generar nuevos conjuntos de campos, debe asegurarse de que la clave de la matriz asociada sea única: la fecha actual de JavaScript (milisegundos después de la época) es una opción común.

⚠️ **GitHub.com Fallback** ⚠️