11: HELPERS: Todo acerca de los Helpers - LPC-Ltda/Ruby-on-Rails GitHub Wiki
Gracias por ayudar a los Helpers a ayudar a los sin ayuda. Su ayuda fue muy ayudadora -Mrs. Doung in the movie The Weekenders
A través del libro, ya hemos cubierto algunos métodos helper provistos por Rails para ayudarle a ensamblar la interface de usuario de su aplicación web. Este capítulo lista y explica todos los módulos helper y sus métodos, seguido de instrucciones para crear sus propios helpers.
Note: Este capítulo es esencialmente material de referencia. Aunque se han hecho todos los esfuerzos para que sea directamente legible, usted notará que la cobertura de los módulos helpers de Action View han sido organizados alfabéticamente, comenzando con
ActiveModelHelper
y terminando conUrlHelper
. Dentro de cada sección del módulo, los métodos han sido separados en grupos lógicos si es apropiado.
Este capítulo es publicado sobre la licencia Creative Commons Attribution-ShareAlike 4.0, http://creativecommons.org/licenses/by-sa/4.0/.
##11.1 ActiveModelHelper
El módulo ActiveModelHelper
contiene métodos helper para rápidamente crear formularios a partir de los objetos que siguen las convenciones de Active Model, partiendo con los modelos Active Record. El método form
es capaz de crear un formulario completo para todos los tipos de contenido básicos de un registro dado. Sin embargo, no sabe como como ensamblar los componentes de la interface del usuario para manipular asociaciones. La mayoría de los desarrolladores Rails ensamblan sus propios formularios desde cero usando métodos desde FormHelper
en lugar de usar este módulo. Sin embargo este módulo contiene algunos helpers útiles para reportar la validación de errores en sus formularios que usted usará regularmente.
Note que desde Rails 3, usted debe agregar la siguiente gema a su Gemfile
para poder usar este módulo.
gem 'dynamic_form'
###11.1.1 Reportando la validación de errores
Los métodos error_message_on
y error_messages_for
le ayudan a agregar información de validación de errores formateada a sus templates en un estilo consistente.
11.1.1.1 error_message_on(object, method, *options)
Retorna un tag div
que contiene el mensaje de error adjunto al método específico sobre el objeto, si existe uno. Esto es útil para mostrar errores de validación en la línea a continuación del campo del form correspondiente. El argumento object
del método puede ser una referencia a un objeto actual o un símbolo correspondiente al nombre de una instancia variable. El method
puede ser un símbolo correspondiente al nombre del atributo sobre el objeto para el cual rendereamos un mensaje de error.
El contenido puede ser especializado con opciones para texto pre y post y clases CSS personalizadas.
prepend_text: string Fragmento para anteponer al mensaje de error generado.
append_text: string Fragmento para agregar al mensaje de error generado.
css_class: class_name Nombre de la clase CSS para el div
generado que envuelve el mensaje de error. Por defecto es formError
.
El uso de este método es común cuando los requerimientos de interface de usuario especifican mensajes de validación individual por campo de ingreso de un formulario, como en el siguiente ejemplo de la vida real:
.form_field
.field_label
%span.required *
%label First Name
.textual
= form.text_field :first_name
= form.error_message_on :first_name
Como en el ejemplo, el helper error_message_on
en en su mayoría accesado vía la variable del block form
de form_for
y sus variantes. Cuando es usado vía la variable form
, usted deja el primer argumento (que especifica el objeto) ya que está implicito.
11.1.1.2 error_messages_for(*params)
Retorna un tag div
que contiene los mensajes de error para todos los objetos contenidos en las variables de instancias identificadas como parámetros.
= form_for @person do |form|
= form.error_messages
.text-field
= form.label :name, "Name"
= form.text_field :name
Como en el ejemplo, el helper error_message_for
es comúnmente accesado vía la variable de bloque form
de form_for
y sus variantes. Cuando se usa vía la variable form
, se llamado como error_messages
y usted no necesita el primer argumento ya que está implícito.
Este método fue usado por el scaffolding de Rails antes de la versión 3 pero rara vez en aplicaciones de producción reales, La documentación API de Rails le aconseja usar la implementación de este método como inspiración para encontrar sus propios reperimientos:
Esta es una presentación empaquetada de los errores con strings embuidos y una cierta estructura HTML. Si lo que usted necesita es diferenciarse de la presentación por defecto, hace sentido que acceda a la instancia de objeto de errores y la setee usted mismo. Vea la fuente de este método para que vea lo fácil que es.
Seguiremos adelante y reproduciremos la fuente del método aquí con la advertencia de que usted no debe intentar usarlo como inspiración a menos que tenga un buen conocimiento de Ruby! Por otro lado, si usted tiene tiempo para estudiar la forma en que este método fue implementado, le enseñará definitivamente mucho acerca de la forma en que Rails está implementado, lo cual es el sabor distintivo de Ruby.
def error_messages_for(*params)
options = params.extract_options!.symbolize_keys
objects = Array.wrap(options.delete(:object)||params).map do |object|
object = instance_variable_get("@#{object}") unless object.respond_to?(:to_model)
object = convert_to_model(object)
if object.class.respond_to?(:model_name)
options[:object_name] ||= object.class.model_name.human.downcase
end
object
end
objects.compact!
count = objects.inject(0) { |sum, object| sum + object.errors.count }
unless count.zero?
html = {}
[:id, :class].each do |key|
if options.include?(key)
value = options[key]
html[key] = value unless value.blank?
else
html[key] = 'error_explanation'
end
end
options[:object_name] ||= params.first
I18n.with_options locale: options[:locale],
scope: [:activerecord, :errors, :template] do |locale|
header_message = if options.include?(:header_message)
options[:header_message]
else
locale.t :header, count: count, model: options[:object_name].to_s.gsub('_', ' ')
end
message=options.include?(:message) ? options[:message] :locale.t(:body)
error_messages = objects.sum do |object|
object.errors.full_messages.map do |msg|
content_tag(:li, msg)
end
end.join.html_safe
contents = ''
contents << content_tag(options[:header_tag]) ||
:h2, header_message) unless header_message.blank?
contents << content_tag(:p, message) unless message.blank?
contents << content_tag(:ul, error_messages)
content_tag(:div, contents.html_safe, html)
end
else
''
end
end
Después en este capítulo hablaremos en extenso de la construcción de sus propios métodos helper.
###11.1.2 Creación automática de formularios
El siguiente par de métodos son usados para la creación de campos automáticos. Usted intentar usarlos también, pero sospecho que su utilidad es limitada en alguna forma en las aplicaciones reales.
11.1.2.1 form(name, options)
Retorna un formulario completo con tags de ingreso y todo para un objeto de modelo nombrado. Aquí esta el código ejemplo dado en la documentación API del formulario dinámico, que usa un objeto Post
hipotético de una aplicación pizarra de anuncios como un ejemplo:
form("post")
# => <form action='/post/create' method='post'>
# <p>
# <label for="post_title">Title</label><br />
# <input id="post_title" name="post[title]" size="30" value="Hello World" />
# </p>
# <p>
# <label for="post_body">Body</label><br />
# <textarea cols="40" id="post_body" name="post[body]" rows="20"></textarea>
# </p>
# <input name="commit" type="submit" value="Create" />
# </form>
Internamente, el método llama a record.persisted? para inferir si la acción del formulario debe ser
createo
update. Es posible explicitar específicamente la acción del formulario (y el valor del botón submit además) usando la opción
:action`.
Si usted necesita que el formulario tenga su enctype
seteado a multipart
, útil para la carga de archivos, setee options[:multipart]
a true
.
Usted puede pasar también una opción :input_block
, usando el idioma Proc.new
de Ruby para crear un nuevo bloque de código anónimo. El block que usted entrega será invocado para cada columna de contenido de su modelo, y retorna su valor retornado será insertado dentro del formulario.
form("Entry", action: "sign", input_block: Proc.new {|record, column|
"#{column.human_name}: #{input(record, column.name)}<br />"})
# => <form action="/entries/sign" method="post">
# Message:
# <input id="entry_message" name="entry[message]" size="30" type="text" <br />
# <input name="commit" type="submit" value="Sign" />
# </form>
Ese constructor de block del ejemplo, como es mencionado en la documentación API del formulario dinámico, usa el método helper input
, el cual también es parte de este módulo y es cubierto en la siguiente sección de este capítulo.
Finalmente, también es posible agregar contenido adicional al formulario al darle un bloque a la llamada form
, como en el siguiente fragmento:
form("entry", action: "sign") do |form|
form << content_tag("b", "Department")
form << collection_select("department", "id", @department, "id", "name")
end
El block usa (yield) un acumulador de string (llamado form
en el ejemplo), al cual le agrega cualquier contenido adicional que usted desea aparezca entre el contenido principal y el tag submit.
11.1.2.2 input(name, method, options)
El método apropiadamente llamado input
toma algo de información de identificación e inmediatamente genera un tag input HTML basado en un atributo de un modelo Active Record. Volviendo al ejemplo Post
usado en la explicación de form
, aquí está el fragmento de código dado en la documentación del API Rails:
input("post", "title")
# => <input id="post_title" name="post[title]" size="30" type "text" value="Hello World" />
Para mostrarle rápidamente los tipos de campos input generados por este método, simplemente reproduciré una porción desde el módulo mismo:
def to_tag(options = {})
case column_type
when :string
field_type = @method_name.include?("password") ? "password" : "text"
to_input_field_tag(field_type, options)
when :text
to_text_area_tag(options)
when :integer, :float, :decimal
to_input_field_tag("text", options)
when :date
to_date_select_tag(options)
when :datetime, :timestamp
to_datetime_select_tag(options)
when :time
to_time_select_tag(options)
when :boolean
to_boolean_select_tag(options).html_safe
end
end
###11.1.3 Personalizar la forma en que los errores de validación se destacan
Por defecto, cuando Rails marca un campo en su formulario que falló el chequeo de validación, lo hace envolviendo el campo en un elemento div
con el nombre de clase field_with_errors
. Este comportamiento es personalizable, ya que se logra vía un objeto Proc
almacenado como una propiedad de configuración de la clase ActionView::Base
:
module ActionView
class Base
cattr_accessor :field_error_proc
@@field_error_proc = Proc.new{ |html_tag, instance|
"<div class=\"field_with_errors\">#{html_tag}</div>".html_safe
}
end
Armado con este conocimiento, cambiar el comportamiento de validación de errores es tan simple como sobre-escribir el atributo field_error_proc
de Action View con su propio Proc
personalizado. Yo sugiero que lo hagan en un archivo inicializador.
En el Listado 11.1 cambiamos el seteo para que los campos input con errores de validación sean prefijados con el mensaje ERROR
.
Listado 11.1 Despliegue de error de validación personalizado
ActionView::Base.field_error_proc =
Porc.new do |html_tag, instance|
%(<div style="color:red">ERROR</div>) + html_tag
end
Se ha sugerido por muchas personas que habría sido una mejor solución por defecto simplemente agregar una clase field_with_errors CSS al tag input mismo en lugar de envolverlo con un tag div
extra. En efecto, esto habría simplificado mucho nuestras vidas, ya que un div
extra frecuentemente rompe layouts perfectos al pixel. Sin embargo, ya que html_tag
es ya construido en el momento en que field_error_proc
es invocado, no es trivial modificar su contenido.
##11.2 AsseTagHelper
De acuerdo a la documentación del API Rails, este módulo
Provee métodos para generar HTML que linkea vistas con assets tales como imagenes, javascripts, stylesheets, y feeds. Estos métodos no verifican si los assets existen antes de linkearlos.
El módulo AssetTagHelper inlcuye algunos métodos que puede usar a diario durante el desarrollo Rails activo, en particular
image_tag`.
###11.2.1 Helpers del head
Algunos de los métodos helper en este módulo le ayudan a agregar contenido al elemento head
de sus documentos HTML.
11.2.1.1 auto_discovery_link_tag(type = :rss, url_options = {}, tag_option = {})
Retorna un tag link que los browser y lectores de noticias pueden usar para autodetectar un feed RSS o ATOM. El tipo puede ser :rss
(por defecto) o :atom
. Controle las opciones del link en formato url_for
usando el url_options
.
Usted pueds modificar el tag link
mismo usando el parámetro tag_options
:
:rel La relación de este link. Por defecto es "alternate"
.
:type Sobreescribe el tipo MIME (tal como "application/atom+xml"
) que de otra forma Rails genera automáticamente para usted.
:title El título del link, Por defecto del tipo mayúsculas.
Aquí hay ejemplos del uso de auto_discovery_link_tag
como se muestra en la documentación del API Rails:
auto_discovery_link_tag
# => <link rel="alternate" type="application/rss+xml" title="RSS"
# href="http://www.currenthost.com/controller/action" />
auto_discovery_link_tag(:atom)
# => <link rel="alternate" type="application/atom+xml" title="ATOM"
# href="http://www.currenthost.com/controller/action" />
auto_discovery_link_tag( :rss, {action: "feed"} )
# => <link rel="alternate" type="application/rss+xml" title="RSS"
# href="http://www.currenthost.com/controller/feed" />
auto_discovery_link_tag( :rss, {action: "feed"}, {title: "My RSS"} )
# => <link rel="alternate" type="application/rss+xml" title="My RSS"
# href="http://www.currenthost.com/controller/feed" />
11.2.1.2 favicon_link_tag(source='favicon.ico', options={})
Retorna un link cargando un archivo favicon. Por defecto, Rails seteará el ícono a favicon.ico
. Usted puede setar un archivo diferente en el primer argumento.
El helper favicon_link
acepta un hash de opciones opcional que acepta lo siguiente:
:rel Especifica la relación de este link, Por defecto es "shortcut icon".
:type Sobrescribe el tipo MIME autogenerado. Por defecto es "image/vnd.microsoft.icon"
favicon_link_tag '/myicon.ico'
# => <link href="/assets/favicon.ico" rel="shortcut icon"
# type="image/vnd.microsoft.icon" />
11.2.1.3 javascript_include_tag(*sources)
Retorna un tag script
para cada una de las sources provistas. Usted puede pasar el nombre del archivo (la extensión .js
es opcional) JavaScript que existe en su directorio app/assets/javascript
para su inclusión dentro de la página actual, o usted puede pasar su ruta completa relativa a su documento root.
javascript_include_tag "xmlhr"
# => <script src="/assets/xmlhr.js?1284139606"></script>
javascript_include_tag "comon", "/elsewhere/cools"
# => <script src="/assets/common.js?1284139606"></script>
# <script src="/elsewhere/cools.js?1423139606"></script>
Cuando el Asset Pipeline está activado, al pasar un archivo manifiesto como un source incluirá todos los archivos JavaScript o CoffeeScript especificados dentro del manifiesto.
javascript_include_tag "application"
Por defecto, no incluir la extensión
.js
a las sources JavaScript resultará en que.js
será puesto como sufijo al nombre del archivo. Sin embargo, esto no funciona bien con los lenguajes de templates de JavaScript pues ellos tienen sus propias extensiones. Para rectificar esto, como en Rails 4.1, setear la opción:extname
afalse
resultará en que el helperjavascript_inlcude_tag
no agregará.js
a la source entregada.
javascript_inlcude_tag 'templates.jst', extname: false
# => <script src="/javascript/templates.jst"></script>
11.2.1.4 javascript_path(source, options = {})
Computa la ruta a los assets JavaScript en el directorio app/assets/javascripts
. Si el parámetro source es entregado sin una extensión, Rails agregará .js
al final. Una ruta completa (desde el documento root) puede también ser agregada y usada internamente por el javascript_include_tag
para construir la salida de la ruta script a su markup.
11.2.1.5 stylesheet_link_tag(*sources)
Retorna un tag link styesheet para las sources especificadas como argumentos. Si usted no especifica una extensión, .css
será agregada automáticamente, Como con otros métodos helpers que toman un número variable de argumentos más opciones, usted puede pasar un hash de opciones como último argumento y ellos serán agregados como atributos al tag.
stylesheet_link_tag "style"
# => <link href="/stylesheets/style.css" media="screen"
# rel="Stylesheet" type="text/css" />
stylesheet_link_tag "style", media: "all"
# => <link href="/stylesheets/style.css" media="all"
# rel="Stylesheet" type="text/css" />
stylesheet_link_tag "random.styles", "/css/stylish"
# => <link href="/stylesheets/random.styles" media="screen"
# rel="Stylesheet" type="text/css" />
# <link href="/css/stylish" media="screen"
# rel="Stylesheet" type="text/css" />
**11.2.1.6 `stylesheet_path(source)
Computa la ruta a los assets stylesheet en el directorio app/assets/stylesheets
. Si el archivo source no tiene extensión, .css
será agregado. Rutas completas desde el documento root pueden ser pasadas. Usado internamente por stylesheet_link_tag
para construir la ruta stylesheet.
###11.2.2 Helpers de assets
Este módulo también contiene una serie de métodos helper que generan markup relacionados con assets. Es importante generar tags de assets dinámicamente, porque frecuentemente los assets son o empaquetados juntos o, servidos desde un servidor fuente diferente al de su contenido regular. Los métodos helper de asset también ponen un timestamp su URL fuente del asset para prevenir problemas del cache del browser.
11.2.2.1 audio_path(source)
Computa la ruta a un asset de audio en el directorio public/audios
, el cual usted deberá agregar por usted mismo a su proyecto Rails ya que no es generado por defecto. Rutas completas desde el documento root seran pasadas. Usado internamente por audio_tag
para construir la ruta de audio.
11.2.2.2 audio_tag(source, options = {})
Retorna un tag de audio HTML5 basado en el argumento source
.
audio_tag("sound")
# => <audio src="/audios/sound" />
audio_tag("sound.wav")
# => <audio src="/audios/sound.wav" />
audio_tag("sound.wav", autoplay: true, controls: true)
# => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
11.2.2.3 font_path(source, options = {})
Computa la ruta a un asset font en el directorio /assets/fonts
, el cual usted tiene que agregar por usted mismo a su proyecto Rails ya que no es generado por defecto. Rutas completas desde el documento root (que comienza con "/") se pueden pasar.
font_path("font.ttf") # => /assets/font.ttf
font_path("dir/font.ttf") # => /assets/dir/font.ttf
font_path("/dir/font.ttf") # => /dir/font.ttf
11.2.2.4 image_path(source)
Computa la ruta a un asset image en el directorio app/assets/images
. Las rutas completas desde el documento root (comienza con "/") pueden ser pasadas. Este método es usado internamente por image_tag
para construir la ruta a la imagen.
image_tag("edit.png") # => /assets/edit.png
image_tag("icons/edit.png") # => /assets/icons/edit.png
image_tag("/icons/edit.png") # => /icons/edit.png
El método
image_tag
hace uso del métodoimage_path
que cubriremos después en en este capítulo. Este método útil determina la ruta a usar en el tag. Usted puede llamar un controlador "image" y tenerlo trabajando como un recurso, a pesar del nombre aparentemente contradictorio, porque para su uso interno, ActionView usa el aliaspath_to_image
para el método.
11.2.2.5 image_tag(source, option = {})
Retorna un tag img
para usarlo en un template. El parámetro source
puede ser una ruta completa o un archivo que exista en su directorio de imágenes. Usted puede agregar atributos arbitrarios adicionales al tag img
usando el parámetro options. Las siguientes dos opciones son tratadas especialmente:
:alt Si no es proporcionado un texto alternativo, se usa la parte de la fuente que corresponde al nombre del archivo sin la extensión y en mayúsculas.
:size Entregado como anchoxalto
así "30x45" se transforma en los atributos width="30"
y height="45"
. La opción :size
puede fallar en silencio si el valor no está en el formato correcto.
image_tag("icon.png")
# => <img src="/assets/icon.png" alt="Icon" />
image_tag("icon.png", size: "16x10", alt: "Edit Entry")
# => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
image_tag("/photos/dog.jpg", class: 'icon')
# => <img src="/photos/dog.jpg" alt="Dog" class="icon" />
11.2.2.6 video_path
Computa la ruta a un asset video en el directorio public/videos
, el cual debe ser creado por usted mismo ya que Rails no lo genera por defecto. Las rutas completas desde el documento root pueden ser pasadas. Usado internamente por video_tag
para construir el atributo src
del video.
11.2.2.7 video_tag(sources, options = {})
Retorna un tag video
de HTML5 para las sources. Si sources
es un string, un tag simple de video será retornado. Si sources
es un arreglo, un tag video
con tags source
anidados para cada source será retornado. El sources
pueden ser rutas completas o archivos que existan en su directorio de videos públicos.
Usted puede agregar un elemento de video HTML normal usando el hash options
. Las options
soportan dos claves adicionales para conveniencia y conformancia:
:poster Setea una imagen (como una captura de pantalla) para ser mostrada antes de que el vídeo cargue. La ruta es calculada usando image_path
.
:size Entregado como anchoxalto
en la misma forma que image_tag
. La opción :size
puede aceptar también un campo string numérico, el cual setea ambos, width
y height
al valor entregado.
video_tag("trailer")
# => <video src="/videos/trailer" />
video_tag("trailer.ogg")
# => <video src="/videos/trailer.ogg" />
video_tag("trail.ogg", controls: true, autobuffer: true)
# => <video autobuffer="autobuffer" controls="controls" src="/videos/trail.ogg" />
video_tag("trail.m4v", size="16x10", poster: "screenshot.png")
# => <video src="/videos/trail.m4v" width="16" height="10" poster="images/screenshot.png" />
video_tag(["trailer.ogg", "trailer.flv"])
# => <video>
# <source src="trailer.ogg" />
# <source src="trailer.flv" />
# </video>
###11.2.3 Usando host de assets
Por defecto, Rails linkea a los activos en el host actual en la carpeta public, pero usted puede redirecionar Rails para linkear assets desde un host de assets dedicado seteando ActionController:Base.asset_host
en el archivo de configuración, el que está típicamente en config/environments/production.rb
para que no afecte su ambiente de desarrollo. Por ejemplo, usted define assets.example.com
para ser su host de assets en esta forma:
config.action_controller.asset_host = "assets.example.com"
Los helpers que hemos cubierto toman esto en cuenta cuando generan sus markups:
image_tag("rails.png")
# => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" />
stylesheet_link_tag("application")
# => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
Los browser abren como mucho dos conexiones simultaneas a un único host. En el escenario del peor caso, con muchos archivos asset, sólo dos archivos a la vez pueden ser bajados. Usted puede aliviar este cuello de botella usando un wildcard %d
en el seteo de su asset_host
. Por ejemplo, "asset%d.example.com"
. Si este wildcard está presente, Rails distribuye los requerimientos de assets en los correspondientes cuatro hosts: "assets0.example.com"
, "assets1.example.com"
, .... Con este truco, los browser pueden abrir ocho conexiones simultáneas en lugar de dos.
image_tag("rails.png")
# => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
stylesheet_link_tag("application")
# => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
Para tener esta técnica funcionando en producción, usted debe ya sea setear cuatro hosts actuales con el mismo contenido de asset o usar el wildcard DNS en CNAME el wildcard a un host de assets único. Usted puede leer más acerca del seteo de sus registro DNS CNAME de su proveedor de hosting. Note que esta técnica es puramente una optimización del desempeño del browser y no está relacionada con el balance de carga del servidor.
Alternativamente, usted puede ejercer más control sobre el host de assets seteando asset_host
a un proc como en el código siguiente:
config.action_controller.asset_host = Proc.new {|source| "http://assets#rand(2) + 1"}.example.com
El ejemplo genera http://assets1.example.com
y http://assets2.example.com
aleatoriamente. Esta opción es útil, por ejemplo, si usted necesita menos/mas de cuatro hosts, optimizando los nosmbres de host, ay así. Como usted puede ver, el proc toma un parámetro source
. Esto es un string con la ruta absoluta del asset con alguna extensión y timestamp en su lugar -por ejemplo, /images/rails.png?1230601161
.
config.action_controller.asset_host = Proc.new {|source|
if source.starts_with?('/images')
"http://images.example.com"
else
"http://assets.example.com"
end
}
image_tag("rails.png")
# => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" />
stylesheet_link_tag("application")
# => <link href="http://assets.example.com/stylesheet/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
Alternativamente, usted puede preguntar por un segundo parámetro request
, el cual es particularmente útil para servir assets desde una página protegido por SSL. Los siguientes ejemplos deshabilitan el hosting de asset para conexiones HTTPS mientras que aún envían assets para requerimeintos HTTP planos desde los host de assets. Si usted no tiene certificados SSL para cada uno de sus host de assets esta técnica le permite eliminar advertencias en el cliente acerca de la mezcla de medios.
ActionController::Base.asset_host = Proc.new {|source|
if request.ssl?
"#{request.protocol}#{request.host_with_port}"
else
"#{request.protocol}.assets.example.com"
end
}
Para pruebas y reutiliación simple, usted puede implementar un host de asset personalizado que responda a call
y uno o dos parámetros justo comoel proc.
config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
"http://asset%d.example.com", "https://asset1.example.com"
)
###11.2.4 Sólo para plugins
Un puñado de métodos de clase en AssetTagHelper
relacionados con la configuración están destinados para el uso en plugins.
- `register_javascript_expansion
- `register_stylesheet_expansion
##11.3 AtomFeedHelper
Provee un helper atom_feed
para ayudar a la generación de feeds ATOM en la construcción de templates.
atom_feed do |feed|
feed.title("My great blog")
feed.updated(@posts.first.created_at)
@posts.each do |post|
feed.entry(post) do |entry|
entry.title(post.title)
entry.content(post.body, type: 'html')
entry.author do |author|
author.name("DHH")
end
end
end
end
Las opciones para atom_feed
son las siguientes:
:language Por defecto en "en-US"
:root_url El HTML alternativo que este feed está duplicando. Por defecto es "/"
sobre el host actual.
:url La URL para este feed. Por defecto el la URL actual.
:id El id para este feed. Por defecto es tag:#{request.host},#{options}:#{request.fullpath.split(".")}
.
:schema_date La fecha en que el esquema tag para el feed fue usado por primera vez. Un buen valor por defecto es el año en que usted creó el feed. Ver http://feedvalidator.org/docs/error/InvalidTAG.html para más información. Si no está especificado, 2005 es usado (como un valor de "no me importa").
:instruct Es un hash de instrucciones procesadas en el formulario de {target => {attribute => value, ...}} or {target => [{attribute => value, ...}, ]}
.
Otros namespaces pueden ser agregados al elemento root:
atom_feed(
'xmlns:app' => 'http://www.w3.org/2007/app',
'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'
) do |feed|
feed.title("My great blog!")
feed.updated((@posts.first.created_at))
feed.tag!(openSearch:totalResults,10)
@posts.each do |post|
feed.entry(post) do |entry|
entry.title(post.title)
entry.content(post.body, type: 'html')
entry.tag!('app:edited', Time.now)
entry.author do |author|
author.name("DHH")
end
end
end
end
Las spec de ATOM definen cinco elementos que pueden contener directamente cotenido XHTML si type: 'xhtml'
es especificado como un atributo:
content
rights
title
subtitle
summary
Si alguno de estos elementos tiene contenido XHTML, este helper se hará cargo de los divs de encierre necesarios y una declaración de XHTML namespace.
entry.summary type: 'xhtml' do |xhtml|
xhtml.p pluralize(order.line_items.count, "line item")
xhtml.p "Shipped to #{order.address}"
xhtml.p "Paid by #{order.pay_type}"
end
El método atom_feed
produce una instancia de AtomFeedBuilder
. Los objetos anidados también producen instancias AtomBuilder
.
##11.4 CacheHelper
Este módulo contiene métodos helper relacionados con hace chache de fragmentos de vistas. Hacer cache de fragmentos es útil cuando ciertos elementos de una acción cambian frecuentemente o dependen de un estado compilado, mientras que otras partes raramente cambian o pueden ser compartidos entre múltiples partes. Los límies de un fragmento que será puesto en cache son definidos dentro de un template view usando el método helper cache
. Este tópico es cubierto en detalle en la sección del Capítulo 17, "Poner en cache y Desempeño".
##11.5 CaptureHelper
Una de las grandes caracterísicas de las vistas de Rails es que usted no está limitado a renderear un único flujo de contenido. A lo largo del camino, usted puede definir bloques de código template que debe ser insertado dentro de otras partes de la página durante el rendereo usando yield
. La técnica es lograda vía un par de métodos del módulo CapturaHelper
.
11.5.0.1 capture(&block)
El método capture
le permite capturar parte de la salida del template (dentro de un bloque) y asignarla a una variable instancia. El valor de esa variable puede ser usada en cualquier otro lugar en el template.
- message_html = capture do
%div
This is a message
Pienso que el método capture
no es útil por si mismo en un template, es más útil cuando usted lo usa en su propio método helper. Le da la habilidad de escribir sus propios helpers que obtienen contenido de template envuelto usando un block. Cubrimos esta técnica despues en este capítulo en la sección "Escribiendo sus propios helpers de vistas".
11.5.0.2 content_for(name, &block)
Mencionamos el método content_for
en el Capítulo 10, "Action View", en la sección "Produciendo Contenido" (Yielding Content). Esto le permite designar una parte de su template como contenido otra parte de la página. Esto funciona en forma similar a su método hermano capture
(de hecho usa capture
). En lugar de retornar el contenido del bloque que se le entrega, almacena el contenido para ser recuperado usando yield
en cualquier otro lugar del template (o, más frecuentemente, en el layuot que lo rodea).
Un ejemplo común es insertar contenido lateral dentro del layout. En el siguiente ejemplo, el link no aparecerá en el flujo del template de la vista. Aparecerá en otro lugar del taplate, donde quiera que :navigation_sidebar
aparezca.
- content_for :navigation_sidebar do
= link_to 'Detail Page', item_detail_path(item)
11.5.0.3 content_for(name)?
Usando este método usted puede chequear si el template produjo (yield) ultimamente algún contenido bajo un nombre particular usando el método content_for
para que usted pueda tomar desiciones antes en el template. El siguiente ejemplo ilustra claramente el uso de este método alterando la clase del elemento body
dinámicamente:
%body{class: content_for?(:right_col) ? 'one-column' : 'two-column' }
= yield
= yield :right_col
11.5.0.4 provide(name, content = nil, &block)
El método helper provide
funciona en la misma forma que content_for
, excepto cuando es usado con streaming. Cuando hacemos un streaming, provide
vacía el buffer actual hacia el layout y se detiene a mirar por más contenidos.
Si usted desea concatenar múltiples veces al mismo buffer cuando renderea un template dado, usted debe usar content_for
en su lugar.
##11.6 CsrfHelper
El módulo CsrfHelper
sólo contiene un método, llamado csrf_meta_tags
. Incluido en la sección <head>
de su template emitirá tags meta "csrf-param"
y "csrf-token"
con el nombre del parámetro y token para la protección contra la falsificación de requerimientos cross-site, respectivamente.
%head
= csrf_meta_tags
Los tags meta "csrf-param"
y "csrf-token"
son usados por Rails para generar formularios dinámicos que implementan links no remotos con :method
.
##11.7 DateHelper
El módulo DateHelper
es usado primariamente para creat tags select
de HTML para diferentes tipos de data de calendarios. Cuenta también con uno de los métodos de nombre más largo, una bestia peculiar de Rails, llamada distance_of_time_in_words_to_now
.
Supongo que el nombre del método helper era demasiado para un sólo bocado, ya que en algún punto se le creo un alias
time_ago_in_words
.
###11.7.1 Los helpers de selección de fecha y hora
Los siguientes métodos lo ayudarán a crear tags de ingreso de campos a formularios que traten con data de fecha y hora. Todos ellos están preparados para asignación multi-parámetro a un objeto Active Record. Esta es una forma elegante de decir que incluso si ellos aparecen en un formulario HTML como campos de ingreso separados, cuando sean posteados de vuelta al servidor, se entiende que todos ellos se refieren a un único atributo del modelo. Esto es algo de la magia de Rails para usted.
11.7.1.1 date_select(object_name, method, options = {}, html_options = {})
Retorna tres tags select
(uno para año, mes y día) preseleccionado para acceder a un atributo del tipo fecha específico (identificado por el parámetro method
) sobre un objeto asignado al template (identificado por object_name
).
Es posible manejar el select a través del hash options
, el cual acepta todas las claves que cada uno de los select individuales acepta (como :use_month_numbers
para select_month
).
El método date_select
también toma las opciones :discard_year
, discard_month
y discard_day
, las cuales sacan el tag select
correspondiente del conjunto de tres. El sentido común dicta que sacar el select de, mes automáticamente sacará el select del día. Si el día es omitido pero no el mes, Rails asumirá que el día será el primero del mes.
También es posible setear el orden de los tags usando la opción :order
con un arreglo de símbolos :year
, :month
y :day
en el órden deseado. Los símbolos pueden ser omitidos y el tag respectivo no será incluido.
Al pasar disabled: true
como parte de las opciones hará al elemento inaccesible para cambios (ver Listado 11.2)
Listado 11.2 Ejemplos de
date_select
date_select("post", "written_on")
date_select("post", "written_on, start_year: 1995, use_month_numbers: true, discard_day: true, include_blank: true)
date_select("post", "written_on", order: [:day, :month, :year])
date_select("user", "birthday", order: [:month, :day])
Si cualquier cosa es pasada en el hash html_options
, será aplicado a todos los tags select en el conjunto.
11.7.1.2 datetime_select(object_name, method, options = {}, html_options = {})
FUnciona exactamente como date_select
excepto por la inclusión de tags para hora y minutos. Los segundos pueden ser incluidos con la opción :include_seconds
. Junto con la inclusión de información de la hora, vienen opciones de eliminación adicionales: :discard_hour
, discard_minute
y discard_seconds
.
Seteando la opción ampm
a true
retorna horas en el formato AM/PM.
datetime_select("post", "written_on")
datetime_select("post", "written_on", ampm: true)
11.7.1.3 time_select(object_name, method, options = {}, html_options = {})
Retorna un conjunto de tags select
(hora, minuto, y opcionalmente segundo) preseleccionados para acceder a un atributo específico del tipo time (identificado por method
) sobre un objeto asignado al template (identificado por object_name
). Usted puede incluir los segundos seteando la opción :include_seconds
a true
.
Como con datetime_select
, setear :ampm
a true
resulta en horas en formato AM/PM.
time_select("post", "sunrise")
time_select("post", "written_on", include_seconds: true)
time_select("game", "written_on", ampm: true)
###11.7.2 Los helpers select individuales de fecha y hora
En ocasiones usted necesita sólo un elemento particular de la fecha u hora, y Rails proporciona un ampilo conjunto de helpers individuales de fecha y hora. En contraste con los helpers que ya hemos visto, los siguientes helpers no están limitados a una variable de instancia sobre la página. A cambio, ellos toman un objeto date o time de Ruby como su primer parámetro. (Todos estos métodos tienen un conjunto de opciones comunes cubiertas en las subsecciones siguientes.)
11.7.2.1 select_date(date = Date.current, options = {}, html_options = {})
Retorna un conjunto de tags select
(año, mes y día) preseleccionado con la fecha provista (o la fecha actual). Es posible explicitar el orden de los tags usando la opción :order
con un arreglo de símbolos :year
, :month
y :day
en el orden deseado.
select_date(started_at, order: [:year, :month, :day])
11.7.2.2 select_datetime(datetime = Time.current, options = {}, html_options = {})
Retorna un conjunto de tags select
(año, mes, día, hora y minuto), preseleccionado con el datetime. Opcionalmente, al setear la opción include_seconds: true
agregamos el campo segundos. Es posible setear explícitamente el orden de los tags usando la opción :order
con un arreglo de símbolos :year
, :month
, :day
, :hour
, :minute
y :seconds
en el orden deseado. Usted puede también agregar un valor de caracteres para las opciones :date_separator
y :time_separator
y controlar el despliegue de los elementos (por defecto son "/"
y ":"
).
11.7.2.3 select_day(date, options = {}, html_options = {})
Retorna un tag select
con opciones para cada uno de los días del 1 al 31 con el día actual seleccionado. La fecha puede ser sustituida por un valor de día dentro del rango de 1 a 31. Si desplegar días con un cero para días de un dígito es su preferencia, setear la opción use_two_digit_numbers
a true cumplirá con ello.
select_day(started_at)
select_day(10)
select_day(5, use_two_digit_numbers: true)
Por defecto, el nombre del campo es por defecto day
, pero puede ser sobrescrito usando la opción :field_name
.
11.7.2.4 select_hour(datetime, options = {}, html_options = {})
Retorna un tag select
con las opciones para las horas entre 0 y 23, con la hora actual seleccionada. El parámetro datetime
puede ser sustituido con un número de hora entre 0 y 23. Seteando la opción :ampm
a true
la hora será desplegada en formato AM/PM. Por defecto el nombre del campo es hour
pero puede ser sobrescrito usando la opción :field_name
.
11.7.2.5 select_minute(datetime, options = {}, html_options = {})
Retorna un tag select
con opciones para cada uno de los minutos de 0 a 59, con el minuto actual seleccionado. También puede retornar un tag select
con opciones por minute_step
desde 0 a 59 con el minuto 00 seleccionado. El parámetro datetime
puede ser sustituido por un valor de segundos entre 0 y 59. Por defecto el nombre del campo es por defecto minute
pero puede ser sobrescrito usando la opción :field_name
.
11.7.2.6 select_month(date, options = {}, html_options = {})
Retorna un tag select
con opciones para cada uno de los meses desde January hasta December con el mes actual seleccionado. Por defecto, los nombres de meses son presentados como opciones del usuario en una selección drop-down y los números de los meses (1 al 12) son enviados al servidor.
También es posible usar números de meses para la presentación en lugar de nombres al setear use_month_numbers: true
. Para desplegar los números de mes con ceros, setee la opción use_two_digit_numbers: true
. Si sucede que desea ambos, números y nombres, setee add_month_numbers: true
. Si prefiere presentar la abreviación de los nombres, setee use_short_month: true
. Finalmente, si desea usar sus propios nombres de mes, setee el valor de :use_month_names
a un arreglo de 12 nombres de mes.
# Will use keys like "January", "March"
select_month(Date.today)
# Will use keys like "1", "3"
select_month(Date.today, use_month_numbers: true)
# Will use keys like "1 - January", "3 - March"
select_month(Date.today, add_month_numbers: true)
# Will use keys like "Jan", "Mar"
select_month(Date.today, use_short_month: true)
# Will use keys like "January", "Enero", "Marzo
select_month(Date.today, use_month_names: %w(Enero Febrero Marzo ...))
Por defecto el nombre del campo es month
pero puede ser sobrescrito usando la opción :field_name
.
11.7.2.7 select_second(datetime, options = {}, html_options = {})
Retorna un tag select
con opciones para cada segundo (0 a 59) con el segundo actual seleccionado. El parámetro datetime
puede ser un objeto Datetime
o un segundo dado como número. Por defecto, el nombre del campo es second
pero puede ser sobrescrito usando la opción field_name
.
11.7.2.8 select_time(datetime = Time.current, options = {}, html_options = {})
Retorna un conjunto de tags select
de HTML (hora y minuto), Usted puede setear la opción :time_separator
para formatear la salida. Es posible agregar un ingreso seteando include_seconds: true
.
select_time(some_time, time_separator: ':', include_seconds: true)
11.7.2.9 select_year(date, options = {}, html_options = {})
Retorna un tag select
con opciones para cada uno de los cinco años antes y después del año actual, el cual es selecionado. El radio de cinco años puede ser cambiado usando las opciones :start_year
y end_year
. Listas ascendentes y descendentes son soportadas al hacer que start_year
sea menor o mayor que end_year
. El parámetro date puede ser un objeto Date
o un año entregado como número.
# ascending year values
select_year(Date.today, start_year: 1992, end_year: 2007)
# descending year values
select_year(Date.today, start_year: 2005, end_year: 1900)
Por defecto, el nombre del campo es year
pero puede ser sobrescrito usando la opción :field_name
.
###11.2.3 Opciones comunes para los helpers de selección de fecha
Todos los métodos del tipo select comparten un número de opciones comunes que son los siguientes:
:discard_type Seteelo a true
si desea eliminar la parte tipo del nombre del select. Si es seteado a true
el método select_month
usará date
en lugar de date[month]
.
:field:name Le permite sobrescribir el nombre natural del tag select
(desde day, minute, ...).
:inlcude_blank Seteado a true
permitirá ingresar una fecha en blanco.
:prefix Sobrescribe el prefijo por defecto date
usado para los nombres de los tags select
. Especificando birthday
resultará en un lugar birthday[month]
en lugar de date[month]
cuando es pasado al método select_month
.
:use_hidden Seteado a true
para insertar el valor del datetime dentro de la página como un input hidden (oculto) de HYML en lugar de un tag select
.
:disabled Seteado a true
si desea mostrar el campo select
como deshabilitado.
:prompt Seteado a true
(para un prompt generico), un prompt o un hash de string prompts para :year
, :month
, :day
, :hour
, :minute
y :second
.
###11.7.4 Métodos distance_in_time
con nombres descriptivos complejos
Algunos métodos distance_in_time
son realmente largos, con nombres descriptivos complejos que nadie puede siquiera recordar sin mirarlos -bueno, a menos la primera docena de veces.
Encuentro que los siguientes métodos son un ejemplo perfecto de la forma en que Rails hace el diseño de su API. En lugar de ir por una alternativa mas corta y necesariamente más críptica, el autor del framework decidió mantener los nombres largos y descriptivos. Este es una de esos casos en los que un no-programador puede mirar el código y comprender lo que está haciendo. Bueno, probablemente.
Yo encuentro estos métodos destacables porque son parte de la razón por las personas consideran Rails parte del fenómeno Web 2.0. Qué otros framework de web incluirían formas de humanizar el despliegue de los timestamps?
11.7.4.1 distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
Reporta la distancia aproximada en tiempo entre dos objetos Time
, Datetime
o Date
o enteros como segundos. Setee el parámetro include_seconds: true
si usted desea aproximaciones más detalladas cuando la distancia es menor a un minuto. La mejor forma de mostrar este método es vía ejemplos:
>> from_time = Time.current
>> helper.distance_of_time_in_words(from_time, from_time + 50.minutes)
=> about 1 hour
>> helper.distance_of_time_in_words(from_time, from_time + 50.seconds)
=> less than a minute
>> helper.distance_of_time_in_words(from_time, from_time + 50.seconds, include_seconds: true)
=> less than 20 seconds
>> helper.distance_of_time_in_words(from_time, from_time + 3.years.from_now)
=> about 3 years
La documentación del API de Rails le hace notar que Rails calcula 1 año como 365.25 días.
11.7.4.2 distance_of_time_in_words_to_now(from_time, include_seconds_or_options = {})
Funciona exactamente como distance_of_time_in_words
excepto que el to_time
es reemplazado por la hora actual. Usualmente invocado sobre los atributos created_at
y updated_at
de su modelo, seguidos por el string ago
en su template, como en el siguiente ejemplo:
%strong= comment.user.name
%br
%small= "#{distance_of_time_in_words_to_now(review.created_at)} ago"
Note que este método tiene el alias time_ago_in_words
para quienes prefieran los nombres de métodos cortos.
###11.7.5 time_tag(date_ot_time, *args, &block)
**
Introducido en Rails 3.1, el time_tag
retorna un elemento time HTML5 para una fecha u hora dada. Usando el helper semántico time_tag
se asegura que su fecha u hora en el markup esté en un formato legible para máquinas. Seteando la opción pubdate: true
agregará el atributo al tag indicando que la fecha u hora es una fecha de publicación. Los siguientes ejemplos muestran la salida que podemos esperar cuando los usamos:
time_tag(Date.current)
# => <time datetime="2013-08-13">August 13, 2013</time>
time_tag(Time.current)
# => <time datetime="2013-08-13T14:58:29Z">August 13, 2013 14:58</time>
time_tag(Time.current, pubdate: true)
# => <time datetime="2013-08-13T15:02:56Z" pubdate="pubdate">August 13, 2013 15:02</time>
= time_tag(Date.current) do
%strong Once upon a time
# => <time datetime="2013-08-13"><strong>Once upon a time</strong></time>
##11.8 DebugHelper
El módulo DebugHelper
sólo contiene un método, llamado debug
. Con salida en su template, pásele un objeto que desee vaciar a YAML y desplegar en el browser dentro de tags PRE. Util para la depuración durante el desarrollo pero para nada más.
##11.9 FormHelper
El módulo FormHelper
provee un conjunto de métodos para trabajar con formularios HTML, especialmente cuando estos están relacionados a objetos de modelos Active Record asignados al template. Sus métodos corresponden a cada uno de los tipos de campos de input HTML (text, password, etc) disponibles. Cuando el formulario es emitido, el valor de los campos input son empaquetados dentro del params
que es pasado al controlador.
Hay dos tipos de métodos helper de formularios. Los encontrados en este módulo son para trabajar específicamente con atributos de modelos Active Record, y las versiones que se llaman similarmente dentro del módulo FormTagHelper
no.
Nota: los métodos helper de formularios de esta sección pueden también ser usados con modelos no Active Record, así el modelo pase el test Active Model Lint que se encuentra en el módulo
ActiveModel::Lint::Tests
. La forma simple de hacer esto es incluir el módulo mixinActiveModel::Model
a su clase.
###11.9.1 Creando formularios y modelos
El metodo núcleo de este helper es llamado form_for
, y lo cubrimos en parte en el Capítulo 3, "REST, recursos, y Rails". El método helper produce un objeto form
, sobre el cual usted puede invocar métodos helper de input, omitiendo su primer argumento. El uso de form_for
nos lleva a un uso sucinto del código del formulario:
= form_for offer do |f|
= f.label :version, 'Version'
=f.text_field :version
%br
= f.label :author, 'Author'
= f.text_field :author
El bloque argumento form_for
es un objeto constructor de formularios que contiene el modelo. Entonces la idea es que
= f.text_field :first_name
Se expanda a
= text_field :person, :first_name
Si usted desea que el hash params resultante posteado a su controlador se nombre basado en algo distinto que el nombre de la clase del objeto que usted pasa a form_for
, usted puede pasar un símbolo arbitrario a la opción :as
:
= form_for person, as :client do |f|
En este caso la llamada a text_field
= f.text_field :first_name
Será expandida a
= text_field :client, :first_name, object: person
11.9.1.1 Opciones de form_for
En cualquiera de sus variantes, el correcto argumento para form_for
es un hash de opciones opcional:
:url La URL a la que es emitido el formulario. Toma los mismos campos que usted pasa a url_for
o link_to
. En particular usted puede pasar aquí una ruta nombrada directamente también. Por defecto va a la acción actual.
:namespace Un namespace que será prefijado con un underscore sobre el id HTML del formulario generado.
:html Atributos HTML opcionales para el tag form
.
:builder Clase constructora de formulario opcional (en lugar de ActionView::Helpers::FormBuilder
).
11.9.1.2 Estilo orientado al recurso
La forma preferida de usar form_for
es descansar en la identificación de recursos automatizada, la cual usará la convención y rutas nombradas de esa aproximación en lugar de configurar manualmente la opción :url
.
Por ejemplo, si post
es un registro existente para ser editado, entonces el estilo orientado al recurso
= form_for post do |f|
es equivalente a
= form_for post, as: :post, url: post_path(post), method: :patch, html: {class: "edit_post", id: "edit_post_45"} do |f|
El método form_for
también reconoce registros nuevos al llamar a new?
sobre los objetos que usted le pasa.
= form_for(Post.new) do |f|
Se expande a
= form_for post, as: :post, url: post_path. html: {class: "new_post", id: "new_post"} do |f|
Las convenciones individuales pueden ser sobrescritas entregando un objeto argumento más las opciones :url
, :method
y/o :html
.
= form_for(post, url: super_post_path(post)) do |f|
Usted puede crear formularios con rutas con namespace pasando un arreglo como el primer argumento, como en el siguiente ejemplo, el cual puede mapear a admin_post_url
:
= form_for([:admin, post]) do |f|
El ejemplo siguiente es la versión equivalente (vieja escuela) de form_tag
, el cual no usa un objeto formulario yielded y los nombres de los objetos explicitos son usados en los campos input:
= form_tag people_path do
.field
= label :person, :first_name
= text_field :person, :first_name
.field
= label :person, :last_name
= text_field :person, :last_name
.buttons
= submit_tag 'Create'
La primera versión tiene un poco menos de repetición (recuerde su principio DRY) y casi seimpre será más conveniente cuando usted esté rendereando objetos de Active Record.
11.9.1.3 Las variables son opcionales
Si usted especifica explícitamente el parametro nombre del objeto para sus campos input en lugar de dejar que los suministre el formulario, tenga en mente que esto no deben coincidir con la instancia de un objeto vivo al alcance de su template. Rails no se queja si el objeto no está ahí. Simplemente pone valores en blanco en el formulario resultante.
11.9.1.4 Convenciones de los formularios generados por Rails
El HTML generado por la invocación de form_for
en el ejemplo precedente es característico de los formularios de Rails y sigue convenciones de nombre específico.
En el caso de que usted se lo esté preguntando, el campo oculto authenticity_token
con una algarabía cerca de la cima del formulario tiene que ver con la protección contra ataques de falsificación de requerimientos cross-site.
<form accept-charset="UTF-8" action="/people" method="post">
<div style="margin:0;padding:0;display:inline">
<input name="utf8" type="hidden" value="✓" />
<input name="authenticity_token" type="hidden" value="afl+6u3J/2meoHtve69q+tD9gPc3/QUsHCqPh85Z4WU=" />
</div>
<div class='field'>
<label for="person_first_name">First name</label>
<input id="person_first_name" name="person[first_name]" type="text" />
</div>
<div class='field'>
<label for="person_last_name">Last name</label>
<input id="person_last_name" name="person[last_name]" type="text" />
</div>
<div class='buttons'>
<input name="commit" type="submit" value="Create" />
</div>
</form>
Cuando este formulario es enviado, el hash params
lucirá como el siguiente ejemplo (usando el formato reflejado en el log de development para cada request):
Parameters: {"utf8"=>"7",
"authenticity_token"=>"afl+6u3J/2meoHtve69q+tD9gPc3/QUsHCqPh85Z4WU=",
"person"=>{"first_name"=>"william", "last_name"=>"Smith"},
"commit"=>"Create"}
Como usted puede ver, el hash params
tiene un valor person
anidado, el cual es accesado usando params[:person]
en el controlador. Esto es un conocimiento de Rails muy fundamental, y estaría sorprendido si usted no lo supiera ya. Le prometo que no veremos más cosas básicas.
11.9.1.5 Desplegando valores existentes
Si usted editó una instancia existente de Person
, esos valores de los atributos del objeto deberán estar llenos en el formulario. Esto también es conocimiento fundamental de Rails. Que pasa si usted desea editar una nueva instancia de un objeto modelo, prepoblado con ciertos valores? Tendré que pasar los valores como opciones a los métodos helper de input? No, Ya que los helpers de formulario despliegan los valores de los atributos del modelo, es solo la preocupación de inicializar el objeto con los valores deseados en el controlador, como lo siguiente:
# Using the gem decent exposure
expose(:person) do
if person_id = (params[:person_id]) || params[:id])
Person.find(person_id)
else
# Set default values that you want to appear in the form
Person.new(first_name: 'First', last_name: 'Last')
end
end
Ya que usted está sólo usando new
, ningún registro sera grabado en la base de datos, y sus valores por defecto mágicamente aparecerán en los campos input.
###11.9.2 Cómo los helpers de formularios obtienen sus valores
Una importante lección que aprender acerca de los métodos de formularios de Rails es que el valor que despliegan viene directamente de la base de datos antes de ser manipulada por el desarrollador. A menos que usted sepa lo que está haciendo, usted puede obtener algunos resultados inesperados si trata de sobrescribir los valores que son desplegados en el formulario.
Ilustremos con un modelo simple LineItem
, el cual tiene un atributo decimal rate
(a propósito de una columna rate
en su base de datos). Sobrescribiremos su accesor rate con otro nuestro:
class LineItem < ActiveRecord::Base
def rate
"A RATE"
end
end
En situaciones normales, el accesor sobrescrito está ocultando el acceso al atributo rate real, como podemos ilustrar usando la consola.
>> li = LineItem.new
=> #<LineItem ...>
>> li.rate
=> "A RATE"
Sin embargo, supongamos que hemos compuesto un formulario para editar lines items usando los helpers de formulario:
= form_for line_item do |f|
= f.text_field :rate
Usted encontrará que este funciona normalmente, como si el accesor rate
sobrescrito no existiera. El hecho es que los helpers de formularios de Rails usan un método especial llamado attribute_before_type_cast
(el cual fue cubierto en el Capítulo 5, "Trabajando con Active Record"). El ejemplo precedente usa el método rate_before_type_cast
y baypasea el método sobrescrito que definimos.
###11.9.3 Integrando objetos adicionales en un formulario
El método helper field_for
crea un scope entorno a un objeto de modelo específico igual que form_for
pero no crea los tags del formulario. Tampoco tiene una representación HTML actual como div
o fieldset
. El método field_for
es adecuado para especificar objetos del modelo adicionales en el mismo formulario, en particular asociaciones del objeto principal que está representado en el formulario.
11.9.3.1 Ejemplos genérico
El siguiente ejemplo simple representa una persona y sus permisos asociados.
= form_for person do |f|
First name:
= f.text_field :first_name
Last Name
= f.text_field :last_name
.permissions
=field_for person.permission do |permission_fields|
Admin?
= permission_fields.check_box :admin
11.9.3.2 Ejemplos de atributos anidados
Cuando un objeto perteneciente al scope actual tiene un atributo write anidado para un cierto atributo, field_for
producirá un nuevo scope para ese atributo. Esto le permite crear formularios que setean o cambian atributos de un objeto padre y sus asociaciones de una vez.
Los atributos writer anidados son métodos setter normales nombrados despues de una asociación. La forma más común para definir estos writters es ya sea declarando accepts_nested_attributes_for
en la definición de un modelo o definiendo un método con un nombre apropiado. Por ejemplo, el atributo writer para la asociación :address
es llamada address_attributes=
.
Si un formulario estilo uno-a-uno o uno uno-a-munchos será producido depende de si el método reader normal retorna un objeto único o un array
de objetos. Considere un clase de Ruby Person
simple que retorna una Address
única desde su método reader address
y responde al método writer address_attributes=
:
class Person
def address
@address
end
def address_attributes=(attributes)
# Process the attributes hash
end
end
Este modelo puede ahora ser usado con un field_for
anidado, como el siguiente:
= form_for person do |f|
= f.field_for :address do |address_fields|
Street
= address_fields.text_field :street
Zip code
= address_fields.text_field :zip_code
Cuando address
es una asociación sobre una Person
, usted puede usar accepts_nested_attributes_for
para definir el método writer para usted, como lo siguiente:
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
Si usted desea destruir el modelo asociado a través del formulario, usted debe habilitarlo primero usando la opción :allow_destroy
para accepts_nested_attributes_for
, como en lo siguiente:
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address, allow_destroy: true
end
Ahora cuando usted use un elemento de formulario check box especialmente llamado _destroy
con un valor de true
, la lógica generada por accepts_nested_attributes_for
destruirá el modelo asociado. (Esta es una técnica muy útil para ventanas de listas que permiten el borrado de múltiples registros de una vez usando check boxes).
= form_for person do |f|
= f.field_for :address do |address_fields|
Delete this address
= address_fields.check_box :_destroy
11.9.3.3 fields_for
con asociaciones uno-a- uno
Considere una clase Person
que retorna un arreglo de instancias Proyect
desde el método reader projects
y responde al método writer projects_attribute=
:
class Person < ActiveRecord::Base
def projects
[@project1, @project2]
end
def projects_attributes=(attributes)
# Process the attributes hash
end
end
Este modelo puede ahora ser usado con un método helper field_for
anidado en un formulario. El block dado en la llamada a fields_for
anidada será repetida para cada instancia en la colección automáticamente:
= form_for person do |f|
= f.field_for :projects do |project_field|
.project
Name:
= project_fields.text_field :name
También es posible especificar la instancia que será usada para hacer la iteración. El símbolo pasado a fields_for
refiere al método reader para el objeto padre del formulario, pero el segundo argumento contiene el objeto actual para ser usado por los campos:
= form_for person do |f|
- person.projects.select(&:active?).each do |project|
= f.field_for :projects, project do |project_fields|
.project
Name:
= project_fields.text_field :name
Ya que fields_for
también comprende una colección como su segundo argumento en esa situación, usted puede encoger el último ejemplo al siguiente código. Sólo ponga en una línea la colección de proyectos:
= form_for person do |f|
= f.fields_for :projects, projects.selects(&:active?) do |project_fields|
.project
Name:
= project_fields.text_field :name
Si en nuestro ejemplo Person
era un modelo Active Record y projects
era una de sus asociaciones has_many
, entonces usted puede usar accepts_nested_attributes_for
para definir el método writer para usted:
class Person < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects
end
Como cuando usamos accepts_nested_attributes_for
con asociaciones belongs_to
, si usted desea destruir cualquiera de los modelos asociados a través del formulario, usted tiene que habilitarlo primero usando la opción :allow_destroy
.
class Person < ActiveRecord::Base
has_many :projects
accepts_nested_attributes_for :projects, allow_dstroy: true
end
Esto le permite especificar cual modelo destruir en el hash de atributos agregando un elemento de formulario booleano llamado _destroy
.
= form_for person do |form|
= form.fields_for : projects do |project_fields|
Delete this project
= project_fields.check_box :_destroy
11.9.3.4 Grabando atributos anidados
Los registros anidados son actualizados al grabar, incluso si el registro padre no ha sido cambiado. Por ejemplo, considere el siguiente código de modelo:
class Project < ActiveRecord::Base
has_many :tasks
accepts_nested_attributes_for :tasks
end
class Task < ActiveRecord::Base
belongs_to :project
has_many :assignments
accepts_nested_attributes_for :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :task
end
El siguiente fragmento de spec ilustra el grabado anidado:
# Setup project, task, and assignment objects...
project.update(name: project.name,
task_attributes: [{id: task.id, name: task.name,
assignments_attributes: [{id: assignment.id, name: 'Paul'}]
}]
)
assignment.reload
expect(assignment.name).to eq('Paul')
###11.9.4 Constructores (builders) de formularios personalizados
Bajo la cubierta, el método form_for
usa una clase llamada ActionView::Helpers::FormBuilder
. Una instancia de ella es yielded al bloque del formulario. Convenientemente, usted puede hacer uan subclase de ella para para sobrescribirla o para definir helpers de formularios adicionales.
Por ejemplo, digamos que usted construye una clase constructora para agregar automáticamente etiquetas a los inputs del formulario cuando text_field
es llamado. Usted lo habilitará con la opción :builder
como en el siguiente código:
= form_for person, builder: LabelingFormBuilder do |f|
Instrucciones acerca de cómo hacer clases de constructores de formularios personalizados podría llenar su propio capítulo, pero pero uno puede ver los fuentes de algunos constructores de formularios Rails populares como SimpleForm (https://github.com/plataformatec/simple_form) y formtastic (https://github.com/justinfrench/formtastic) para aprender más.
###11.9.5 Inputs de formularios
Para cada uno de estos métodos, hay un método constructor de formulario similarmente llamado que omite el parámetro object_name
.
11.9.5.1 check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
Este helper le da un campo input oculto extra para asegurarse que un valor falso es pasado aún su el check box no ha sido chequeado.
check_box('timesheet', 'approved')
# => <input name="timesheet[approved]" type="hidden" value="0" />
# <input checked="checked" type="checkbox" id=""timesheet_approved" name="timesheet[approved]" value="1" />
11.9.5.2 color_field(object_name, method, options = {})
Crea un campo input de color que le permite setear un color a través de su valor hexadecimal. El valor por defecto de un color_field
es #000000
.
color_field(:car, :paint_color)
# => <input id="car_paint_color" name="car[paint_color]" type="color"
# value="#000000" />
Este método es en caso contrario idéntico a text_field
.
11.9.5.3 date_field(object_name, method, options = {})
Crea un campo input de fecha. Si un objeto es entregado al helper, este llama a to_date
sobre él e intenta setear el valor por defecto.
date_field(:person, :birthday)
# => <input id="person_birthday" name="person[birthday]" type="date" />
Para sobrescribir el valor por defecto, pase un string en el formato YYYY-MM-DD a la opción :value
. Este método es en caso contrario idéntico a text_field
.
11.9.5.4 datetime_field(object_name, method, options = {})
Crea un campo input deltipo "datetime", el cual acepta horas en UTC. Si una instancia DateTime
o ActiveSupport::TimeWithZone
es entregada al helper, este llama a strftime
con "%Y-%m-%dT%T.%L%z" sobre el valor del objeto para intentar setear un valor por defecto.
datetime_field(:post, :publish_at)
# => <input id="post_publish_at" name="post[publish_at]" type="datetime" />
El datetime_field
acepta las opciones :min
y :max
, las cuales le permiten setear valores mínimos y máximos aceptables, espectivamente.
datetime_field(:invoice, :invoiced_on,
min: Time.current.beginning_of_year,
max: Time.current.end_of_year)
# => <input id="invoice_invoiced_on" max="2013-12-31T23:59:59.999+0000"
min="2013-01-01T00:00:00.000+0000" name="invoice[invoiced_on]"
type="datetime" />
Para sobrescribir el valor por defecto, pase un string en el formato "%Y-%m-%dT%T.%L%z" a la opción :value
. Este método es en caso contrario idéntico a text_field
.
11.9.5.5 datetime_local_field(object_name, method, options = {})
Crea un campo input del tipo "datetime-local". Este método es en caso contrario idéntico a datetime_field
, excepto que el valor usado es local sobre UTC. Si se le entrega una instancia DateTime
o ActiveSuppor::TimeWithZone
al helper, éste llama strftime
con "%Y-%m-%dT%T.%L%z" sobre el valor del objeto para intentar setear un valor por defecto.
11.9.5.6 email_field(object_name, method, options = {})
Crea un campo input email. Este método es en caso contrario idéntico a text_field
.
11.9.5.7 file_field(object_name, method, options = {})
Crea un campo para subir un archivo y automáticamente agrega multipart: true
al formulario que lo encierra. Ver file_field_tag
para detalles.
11.9.5.8 hidden_field(object_name, method, options = {})
Crea un campo oculto, con parámetros similar a text_field
.
11.9.5.9 label(object_name, method, content_or_options = nil, options = nil, &block)
Crea un tag label con el atributo for
apuntando a un campo input específico.
label('timesheet', 'approved')
# => <label for="timesheet_approved">Approved</label>
label('timesheet', 'approved', 'Approved?')
# => <label for="timesheet_approved">Approved?</label>
A muchos de nosotros nos gusta linkear labels a campos input en forma anidada. (Muchos podrían decir que este es el uso correcto de las etiquetas). A partir de Rails 3, el helper label
acepta un block así el anidado es posible y funciona como esperamos. Como resultado, en lugar de hacer
= f.label :terms, "<span>Accept #{link_to 'Terms', terms_path}</span>"
puede hacer esto que es más elegante y mentenible.
= f.label :terms do
%span Accept #{link_to "Terms", terms_path}
11.9.5.10 month_field(object_name, method, options = {})
Crea un campo input del tipo "month" sin información de la zona horaria. Un mes es representado por cuatro dígitos para el año seguido por un guión y finalizado con dos dígitos para el mes (por ejemplo, 2013-08).
Si se le entrega una instancia de DateTime
o ActiveSupport::TimeWithZone
al helper, éste llama a strftime
con "%Y-%m" sobre el valor del objeto para intentar setear un valor por defecto.
month_field(:user, :born_on)
# => <input id="user_born_on" name="user[born_on]" type="month" />
Para sobrescribir el valor por defecto, pase un string en el formato "%Y-%m" a la opción :value
. Este método es en caso contrario idéntico a datetime_field
.
11.9.5.11 number_field(object_name, method, options = {})
Crea un campo input number. Este método es en caso contrario idéntico a text_field
con las siguientes opciones adicionales:
:min El valor mínimo aceptable
:max El valor máximo aceptable
:in Un rango que especifica valores :min
y :max
:step El valor de granularidad aceptable
11.9.5.12 password_field(object_name, method, options = {})
Crea un campo input password. Este método es en caso contrario idéntico a text_field
pero renderea con un valor nil por defecto por razones de seguridad. Si usted desea pre-llenar la password del usuario debe hacer algo como lo siguiente:
password_field(:user, :password, value: user.password)
11.9.5.13 radio_button(object_name, method, tag_value, options = {})
Crea un campo input radio button. Asegurese de darle a todas sus opciones de radio button el mismo name
para que el browser pueda considerarlas linkeadas.
= radio_button(:post, :category, :rails)
= radio_button(:post, :category, :ruby)
11.9.5.14 range_field(object_name, method, options = {})
Crea un campo input range. Este método es en caso contrario idéntico a number_field
.
11.9.5.15 search_field(object_name, method, options = {})
Crea un campo input search. Este método es en caso contrario idéntico a text_field
.
11.9.5.16 telephone_field(object_name, method, options = {})
Crea un campo input telephone. Este método es en caso contrario idéntico a text_field
y su alias es phone_field
.
11.9.5.17 submit(value = nil, options = {})
Crea un botón submit con el text valor como 'caption'. La opción disable_with
puede ser usada para proveer un nombre para versiones deshabilitadas del botón submit.
11.9.5.18 text_area(object_name, method, options = {})
Crea un campo input de texto multilínea (el tag textarea
). La opción :size
le permite especificar fácilmente las dimensiones del área de texto en lugar de tener que setear las opciones :rows
y :cols
.
text_area(:comment, :body, size: "25x10")
# => <textarea name="comment[body]" id="comment_body" cols="25" rows="10"></textarea>
11.9.5.19 text_field(object_name, method, options = {})
Crea un campo input de texto estándar.
11.9.5.20 time_field(object_name, method, options = {})
Crea un campo input del tipo "time". Si se le entrega una instancia DateTime
o ActiveSupport::TimeWithZone
al helper, este llama a strftime
con "%T.%L" sobre el valor del objeto para intentar setear un valor por defecto.
time_field(:task, :started_at)
# => <input id="task_started_at" name="task[started_at]" type="time" />
Para sobrescribir el valor por defecto pase un string en el formato "%T.%L" a la opción :value
. Este método es en caso contrario idéntico a datetime_field
.
11.9.5.21 url_field(object_name, method, options = {})
Crea un campo input del tipo "url". Este método es en caso contrario idéntico a text_field
.
11.9.5.22 week_field(object_name, method, options = {})
Crea un campo input del tipo "week". Si se le entrega una instancia DateTime
o ActiveSupport::TimeWithZone
al helper, éste llama a strftime
con "$Y-W%W" sobre el valor del objeto para intentar setear un valor por defecto.
week_field(:task, :started_at)
# => <input id="task_started_at" name="task[started_at]" type_"week" />
Para sobrescribir el valor por defecto, pase un string con formato "$Y-W%W" a la opción :value
. Este método es en caso contrario idéntico a datetime_field
.
##11.10 FormOptionsHelper
Los métodos en el módulo FormOptionsHelper
son todos para ayudarle en su trabajo con elementos select
de HTML entregándole formas de volcar colecciones de objetos en tags options
.
###11.10.1 Helpers de select`
Los siguientes métodos le ayudan a crear tags select
basado en un par de identificadores object
y attribute
.
11.10.1.1 collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
Retorna los tags select
y option
para un object
dado y el method
usando options_from_collection_for_select
(también en este módulo) para generar la lista de tags option
desde el parámetro collection
11.10.1.2 grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
Retorna los tags select
, optgroup
y option
para un object
dado y el method
usando la opción option_groups_from_collection_for_select
(cubierto después en este capítulo).
11.10.1.3 select(object, method, collection, value_method, text_method, options = {}, html_options = {})
Crea un tag select y una serie de tags option
contenidos para el object
y atributo entregado. El valor del atributo actualmente contenido en el objeto (si hay alguno) será seleccionado, siempre que el objeto esté disponible (no sea nil). Ver la sección
options_for_select` para el formato requerido por los parámetros elegidos.
Aquí esta un pequeño ejemplo donde el valor de @post.person_id
es 1
:
= select(:post, :person_id,
Person.all.collect { |p| [ p.name, p.id ] },
{ inlcude_blank: true } )
Ejecutando este código helper generará la siguiente salida HTML:
<select id="post_person_id" name="post[person_id]">
<option value=""></option>
<option value="1" selected="selected">David</option>
<option value="2">Sam</option>
<option value="3">Tobias</option>
</select>
Si es necesario, especifique selected: value
para explicitar la selección o selected: nil
si no quiere dejar todas las opciones sin seleccionar. La opción include_blank: true
inserta un tag option
en blanco al comienzo de la lista para que no haya un valor preseleccionado. También, uno puede deshabilitar valores específicos seteando un valor único o un arreglo de valores a la opción :disabled
.
11.10.1.4 time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
Retorna los tags select
y option
para un objeto dado y el método, usando time_zone_options_for_select
para generar la lista de tags option
.
Adicionalmente a la opción :include_blank
documentada en la sección anterior, este método también soporta una opción :model
, la cual usa por defecto ActiveSupport::TimeZone
. Esto puede ser usado para especificar diferentes zonas horarias prioritarias sobre cualquier otra.
time_zone_select(:user, :time_zone, [
ActiveSupport::TimeZone['Eastern Time (US & Canada)'],
ActiveSupport::TimeZone['Pacific Time (US & Canada)']
])
# => <select id="user_time_zone" name="user[time_zone]">
# <option value="Eastern Time (US & Canada)">
# (GMT-05:00) Eastern Time (US & Canada)
# </option>
# <option value="Pacific Time (US & Canada)">
# (GMT-05:00) Pacific Time (US & Canada)
# </option>
# <option disabled="disabled" value="">--------------------------</option>
# <option value="American Samoa">(GMT-11:00) American Samoa</option>
# ...
Finalmente, seteando la opción :default
a una instancia de ActiveSupport::TimeZone
seteamos el valor seleccionado por defecto si ninguno fue seteado.
###11.10.2 Helpers de Check box / Radio
Los siguientes métodos crean tags input
del tipo "checkbox" o "radio" basado en una colección.
11.10.2.1 collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
El helper de formulario collection_check_boxes
crea una colección de check boxes y sus etiquetas asociadas basado en una colección.
Para ilustrar, asumamos que tenemos un modelo Post
que tiene múltiples categorías, usando el helper collection_check_boxes
, podemos agregar la habilidad de setear los category_ids
del post:
collection_check_boxes(:post, :category_ids, Category.all, :id, :name)
# => <input id="post_category_ids_1" name="post[category_ids][]" type="checkbox" value="1" />
<label for="post_category_ids_1">Ruby on Rails</label>
<input id="post_category_ids_2" name="post[category_ids][]" type="checkbox" value="2" />
<label for="post_category_ids_2">Ruby</label>
Si uno busca cambiar la forma en que las etiquetas y check boxes son rendereados, al pasar un bloque se producirá (yield) un constructor:
collection_check_boxes(:post, :category_ids, Category.all, :id, :name) do |item|
item.label(class: 'check-box') { item.check_box(class: 'check-box' }
end
El constructor también tiene acceso a los métodos object
, text
y value
del ítem actual que está siendo rendereado.
11.10.2.2 collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {})
El helper de formulario collection_radio_buttons
crea una colección de radio buttons y las etiquetas asociadas basado en una colección. Esto es predominantemente usado para setear un valor individual, como la relación belongs_to
sobre un modelo.
Use
collection_radio_buttons
con una colección que sólo tenga un puñado de ítems a menos que usted desee que su página se llene de radio buttons. Vuelva acollection_select
para colecciones grandes.
collection_radio_buttons(:post, :author_id, Author.all, :id, :name)
# => <input id="post_author_1" name="post[author_id][]" type="radio" value="1" />
# <label for="post_author_1">Obie</label>
# => <input id="post_author_2" name="post[author_id][]" type="radio" value="2" />
# <label for="post_author_1">Kevin</label>
# ...
Similarmente al helper collection_check_boxes
, si uno busca cambiar la forma en que las etiquetas y los radio buttons son rendereados, pasar un block producirá (yield) un contructor:
collection_radio_buttons(:post, :author_id, Author.all, :id, :name) do |item|
item.label(class: 'radio-button') { item.radio_button(class: 'radio-button') }
end
El constructor también tiene acceso a los métodos object
, text
y value
del ítem que está siendo rendereado.
###11.10.3 Helpers de opciones
Para todos los métodos siguientes, sólo tags option
son retornados, así que usted debe invocarlos dentro de un helper select o envueltos en un tag select
.
11.10.3.1 grouped_options_for_select(grouped_options, selected_key = nil, options = {})
Retorna un string tags option
, como options_for_select
, pero rodeadas con tags optgroup
.
11.10.3.1 option_group_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_mehod, selected_key = nil)