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.
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="✓" />
<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".
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="✓" />
<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.
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">'
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.
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.
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.
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.
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.
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 variablef
). - 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="✓" />
<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="✓" />
<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).
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 deresources
.
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.
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.
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="✓" />
<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).
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í.
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ónrequired
es verdadero, elsize
de visualización es uno ymultiple
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>
...
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, comocollection_select
,select_tag
) para establecer una asociaciónbelongs_to
usted debe pasar el nombre de la clave foránea (en el ejemplo anteriorcity_id
), no el nombre de la asociación en sí. Si especificacity
en lugar decity_id
, Active Record generará un error en la línea deActiveRecord::AssociationTypeMismatch:City(#17815740) expected, Got String(#1138750)
al pasar el hash de parámetros aPerson.new
oupdate
. 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.
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, conoptions_from_collection_for_select
el primer argumento es el método de valor y el segundo el método de texto.
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).
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
yselect_datetime
son los helpers de barebones,date_select
,time_select
ydatetime_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.).
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
.
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
.
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.
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]
.
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]
.
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.
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.
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
.
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.
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.
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 ayudantecheck_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 usarcheck_box_tag
o usar hashes en lugar de matrices.
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.
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 %>
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.
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.
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.
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
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
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
.
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.