1 Visión General: Cómo las Piezas Encajan
Esta guía se centra en la interacción entre el Controlador y la Vista en el triángulo Modelo-Vista-Controlador. Como sabes, el Controlador es responsable de orquestar todo el proceso de manejo de una solicitud en Rails, aunque normalmente delega cualquier código pesado al Modelo. Pero luego, cuando es el momento de enviar una respuesta de vuelta al usuario, el Controlador entrega las cosas a la Vista. Esa transferencia es el tema de esta guía.
En términos generales, esto implica decidir qué se debe enviar como respuesta y llamar a un método apropiado para crear esa respuesta. Si la respuesta es una vista completa, Rails también hace un trabajo adicional para envolver la vista en un diseño y posiblemente incluir vistas parciales. Verás todos esos caminos más adelante en esta guía.
2 Creando Respuestas
Desde el punto de vista del controlador, hay tres maneras de crear una respuesta HTTP:
- Llamar a
render
para crear una respuesta completa para enviar de vuelta al navegador. - Llamar a
redirect_to
para enviar un código de estado de redirección HTTP al navegador. - Llamar a
head
para crear una respuesta que consista únicamente en encabezados HTTP para enviar de vuelta al navegador.
2.1 Renderizado por Defecto: Convención sobre Configuración en Acción
Has oído que Rails promueve "convención sobre configuración". El renderizado por defecto es un excelente ejemplo de esto. Por defecto, los controladores en Rails automáticamente renderizan vistas con nombres que corresponden a rutas válidas. Por ejemplo, si tienes este código en tu clase BooksController
:
class BooksController < ApplicationController
end
Y lo siguiente en tu archivo de rutas:
resources :books
Y tienes un archivo de vista app/views/books/index.html.erb
:
<h1>¡Los libros llegarán pronto!</h1>
Rails automáticamente renderizará app/views/books/index.html.erb
cuando navegues a /books
y verás "¡Los libros llegarán pronto!" en tu pantalla.
Sin embargo, una pantalla de "próximamente" es solo mínimamente útil, así que pronto crearás tu modelo Book
y añadirás la acción index a BooksController
:
class BooksController < ApplicationController
def index
@books = Book.all
end
end
Nota que no tenemos un render explícito al final de la acción index de acuerdo con el principio de "convención sobre configuración". La regla es que si no renderizas explícitamente algo al final de una acción del controlador, Rails automáticamente buscará la plantilla action_name.html.erb
en la ruta de vistas del controlador y la renderizará. Así que en este caso, Rails renderizará el archivo app/views/books/index.html.erb
.
Si queremos mostrar las propiedades de todos los libros en nuestra vista, podemos hacerlo con una plantilla ERB como esta:
<h1>Listando Libros</h1>
<table>
<thead>
<tr>
<th>Título</th>
<th>Contenido</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @books.each do |book| %>
<tr>
<td><%= book.title %></td>
<td><%= book.content %></td>
<td><%= link_to "Mostrar", book %></td>
<td><%= link_to "Editar", edit_book_path(book) %></td>
<td><%= link_to "Destruir", book, data: { turbo_method: :delete, turbo_confirm: "¿Estás seguro?" } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to "Nuevo libro", new_book_path %>
NOTA: El renderizado actual es realizado por clases anidadas del módulo ActionView::Template::Handlers
. Esta guía no profundiza en ese proceso, pero es importante saber que la extensión del archivo en tu vista controla la elección del manejador de plantillas.
2.2 Usando render
En la mayoría de los casos, el método render
del controlador realiza el trabajo pesado de renderizar el contenido de tu aplicación para ser usado por un navegador. Hay una variedad de maneras de personalizar el comportamiento de render
. Puedes renderizar la vista por defecto para una plantilla de Rails, o una plantilla específica, o un archivo, o código en línea, o nada en absoluto. Puedes renderizar texto, JSON o XML. También puedes especificar el tipo de contenido o el estado HTTP de la respuesta renderizada.
Si quieres ver los resultados exactos de una llamada a render
sin necesidad de inspeccionarlo en un navegador, puedes llamar a render_to_string
. Este método toma exactamente las mismas opciones que render
, pero devuelve una cadena en lugar de enviar una respuesta de vuelta al navegador.
2.2.1 Renderizando la Vista de una Acción
Si quieres renderizar la vista que corresponde a una plantilla diferente dentro del mismo controlador, puedes usar render
con el nombre de la vista:
def update
@book = Book.find(params[:id])
if @book.update(book_params)
redirect_to(@book)
else
render "edit"
end
end
Si la llamada a update
falla, llamar a la acción update
en este controlador renderizará la plantilla edit.html.erb
perteneciente al mismo controlador.
Si prefieres, puedes usar un símbolo en lugar de una cadena para especificar la acción a renderizar:
def update
@book = Book.find(params[:id])
if @book.update(book_params)
redirect_to(@book)
else
render :edit, status: :unprocessable_entity
end
end
2.2.2 Renderizando la Plantilla de una Acción desde Otro Controlador
¿Qué pasa si quieres renderizar una plantilla de un controlador completamente diferente al que contiene el código de la acción? También puedes hacer eso con render
, que acepta la ruta completa (relativa a app/views
) de la plantilla a renderizar. Por ejemplo, si estás ejecutando código en un AdminProductsController
que vive en app/controllers/admin
, puedes renderizar los resultados de una acción a una plantilla en app/views/products
de esta manera:
render "products/show"
Rails sabe que esta vista pertenece a un controlador diferente debido al carácter de barra incrustado en la cadena. Si quieres ser explícito, puedes usar la opción :template
(que era requerida en Rails 2.2 y versiones anteriores):
render template: "products/show"
2.2.3 Resumiendo
Las dos formas anteriores de renderizar (renderizar la plantilla de otra acción en el mismo controlador y renderizar la plantilla de otra acción en un controlador diferente) son en realidad variantes de la misma operación.
De hecho, en la clase BooksController
, dentro de la acción de actualización donde queremos renderizar la plantilla de edición si el libro no se actualiza con éxito, todas las siguientes llamadas de renderizado renderizarían la plantilla edit.html.erb
en el directorio views/books
:
render :edit
render action: :edit
render "edit"
render action: "edit"
render "books/edit"
render template: "books/edit"
Cuál usas es realmente una cuestión de estilo y convención, pero la regla general es usar la más simple que tenga sentido para el código que estás escribiendo.
2.2.4 Usando render
con :inline
El método render
puede prescindir de una vista por completo, si estás dispuesto a usar la opción :inline
para proporcionar ERB como parte de la llamada al método. Esto es perfectamente válido:
render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"
ADVERTENCIA: Rara vez hay una buena razón para usar esta opción. Mezclar ERB en tus controladores derrota la orientación MVC de Rails y hará más difícil para otros desarrolladores seguir la lógica de tu proyecto. Usa una vista erb separada en su lugar.
Por defecto, el renderizado en línea usa ERB. Puedes forzarlo a usar Builder en su lugar con la opción :type
:
render inline: "xml.p {'¡Práctica de codificación horrible!'}", type: :builder
2.2.5 Renderizando Texto
Puedes enviar texto plano - sin ningún marcado - de vuelta al navegador usando la opción :plain
para render
:
render plain: "OK"
Renderizar texto puro es más útil cuando estás respondiendo a solicitudes Ajax o de servicios web que esperan algo diferente al HTML adecuado.
NOTA: Por defecto, si usas la opción :plain
, el texto se renderiza sin usar el diseño actual. Si quieres que Rails ponga el texto en el diseño actual, necesitas agregar la opción layout: true
y usar la extensión .text.erb
para el archivo de diseño.
2.2.6 Renderizando HTML
Puedes enviar una cadena HTML de vuelta al navegador usando la opción :html
para render
:
render html: helpers.tag.strong('No Encontrado')
Esto es útil cuando estás renderizando un pequeño fragmento de código HTML. Sin embargo, podrías considerar moverlo a un archivo de plantilla si el marcado es complejo.
NOTA: Al usar la opción html:
, las entidades HTML serán escapadas si la cadena no se compone con APIs conscientes de html_safe
.
2.2.7 Renderizando JSON
JSON es un formato de datos de JavaScript utilizado por muchas bibliotecas Ajax. Rails tiene soporte integrado para convertir objetos a JSON y renderizar ese JSON de vuelta al navegador:
render json: @product
No necesitas llamar a to_json
en el objeto que deseas renderizar. Si usas la opción :json
, render
llamará automáticamente a to_json
por ti.
2.2.8 Renderizando XML
Rails también tiene soporte integrado para convertir objetos a XML y renderizar ese XML de vuelta al solicitante:
render xml: @product
No necesitas llamar a to_xml
en el objeto que deseas renderizar. Si usas la opción :xml
, render
llamará automáticamente a to_xml
por ti.
2.2.9 Renderizando JavaScript Puro
Rails puede renderizar JavaScript puro:
render js: "alert('Hola Rails');"
Esto enviará la cadena suministrada al navegador con un tipo MIME de text/javascript
.
2.2.10 Renderizando Cuerpo Crudo
Puedes enviar contenido crudo de vuelta al navegador, sin establecer ningún tipo de contenido, usando la opción :body
para render
:
render body: "raw"
Esta opción solo debe usarse si no te importa el tipo de contenido de la respuesta. Usar :plain
o :html
podría ser más apropiado la mayoría de las veces.
NOTA: A menos que se anule, tu respuesta devuelta de esta opción de renderizado será text/plain
, ya que ese es el tipo de contenido predeterminado de la respuesta de Action Dispatch.
2.2.11 Renderizando Archivo Crudo
Rails puede renderizar un archivo crudo desde una ruta absoluta. Esto es útil para renderizar condicionalmente archivos estáticos como páginas de error.
render file: "#{Rails.root}/public/404.html", layout: false
Esto renderiza el archivo crudo (no soporta ERB u otros manejadores). Por defecto, se renderiza dentro del diseño actual.
ADVERTENCIA: Usar la opción :file
en combinación con la entrada de usuarios puede llevar a problemas de seguridad ya que un atacante podría usar esta acción para acceder a archivos sensibles en tu sistema de archivos.
send_file
es a menudo una opción más rápida y mejor si no se requiere un diseño.
2.2.12 Renderizando Objetos
Rails puede renderizar objetos que respondan a #render_in
. El formato se puede controlar definiendo #format
en el objeto.
class Greeting
def render_in(view_context)
view_context.render html: "Hola, Mundo"
end
def format
:html
end
end
render Greeting.new
# => "Hola Mundo"
Esto llama a render_in
en el objeto proporcionado con el contexto de vista actual. También puedes proporcionar el objeto usando la opción :renderable
para render
:
render renderable: Greeting.new
# => "Hola Mundo"
2.2.13 Opciones para render
Las llamadas al método render
generalmente aceptan seis opciones:
:content_type
:layout
:location
:status
:formats
:variants
2.2.13.1 La Opción :content_type
Por defecto, Rails servirá los resultados de una operación de renderizado con el tipo de contenido MIME de text/html
(o application/json
si usas la opción :json
, o application/xml
para la opción :xml
). Hay ocasiones en las que podrías querer cambiar esto, y puedes hacerlo configurando la opción :content_type
:
render template: "feed", content_type: "application/rss"
2.2.13.2 La Opción :layout
Con la mayoría de las opciones para render
, el contenido renderizado se muestra como parte del diseño actual. Aprenderás más sobre los diseños y cómo usarlos más adelante en esta guía.
Puedes usar la opción :layout
para decirle a Rails que use un archivo específico como el diseño para la acción actual:
render layout: "special_layout"
También puedes decirle a Rails que renderice sin ningún diseño en absoluto:
render layout: false
2.2.13.3 La Opción :location
Puedes usar la opción :location
para establecer el encabezado HTTP Location
:
render xml: photo, location: photo_url(photo)
2.2.13.4 La Opción :status
Rails generará automáticamente una respuesta con el código de estado HTTP correcto (en la mayoría de los casos, esto es 200 OK
). Puedes usar la opción :status
para cambiar esto:
render status: 500
render status: :forbidden
Rails entiende tanto los códigos de estado numéricos como los símbolos correspondientes que se muestran a continuación.
Clase de Respuesta | Código de Estado HTTP | Símbolo |
---|---|---|
Informativo | 100 | :continue |
101 | :switching_protocols | |
102 | :processing | |
Éxito | 200 | :ok |
201 | :created | |
202 | :accepted | |
203 | :non_authoritative_information | |
204 | :no_content | |
205 | :reset_content | |
206 | :partial_content | |
207 | :multi_status | |
208 | :already_reported | |
226 | :im_used | |
Redirección | 300 | :multiple_choices |
301 | :moved_permanently | |
302 | :found | |
303 | :see_other | |
304 | :not_modified | |
305 | :use_proxy | |
307 | :temporary_redirect | |
308 | :permanent_redirect | |
Error del Cliente | 400 | :bad_request |
401 | :unauthorized | |
402 | :payment_required | |
403 | :forbidden | |
404 | :not_found | |
405 | :method_not_allowed | |
406 | :not_acceptable | |
407 | :proxy_authentication_required | |
408 | :request_timeout | |
409 | :conflict | |
410 | :gone | |
411 | :length_required | |
412 | :precondition_failed | |
413 | :payload_too_large | |
414 | :uri_too_long | |
415 | :unsupported_media_type | |
416 | :range_not_satisfiable | |
417 | :expectation_failed | |
421 | :misdirected_request | |
422 | :unprocessable_entity | |
423 | :locked | |
424 | :failed_dependency | |
426 | :upgrade_required | |
428 | :precondition_required | |
429 | :too_many_requests | |
431 | :request_header_fields_too_large | |
451 | :unavailable_for_legal_reasons | |
Error del Servidor | 500 | :internal_server_error |
501 | :not_implemented | |
502 | :bad_gateway | |
503 | :service_unavailable | |
504 | :gateway_timeout | |
505 | :http_version_not_supported | |
506 | :variant_also_negotiates | |
507 | :insufficient_storage | |
508 | :loop_detected | |
510 | :not_extended | |
511 | :network_authentication_required |
NOTA: Si intentas renderizar contenido junto con un código de estado sin contenido (100-199, 204, 205 o 304), se eliminará de la respuesta.
2.2.13.5 La Opción :formats
Rails usa el formato especificado en la solicitud (o :html
por defecto). Puedes cambiar esto pasando la opción :formats
con un símbolo o un array:
render formats: :xml
render formats: [:json, :xml]
Si no existe una plantilla con el formato especificado, se lanzará un error ActionView::MissingTemplate
.
2.2.13.6 La Opción :variants
Esto le dice a Rails que busque variaciones de plantilla del mismo formato. Puedes especificar una lista de variantes pasando la opción :variants
con un símbolo o un array.
Un ejemplo de uso sería este.
# llamado en HomeController#index
render variants: [:mobile, :desktop]
Con este conjunto de variantes, Rails buscará el siguiente conjunto de plantillas y utilizará la primera que exista.
app/views/home/index.html+mobile.erb
app/views/home/index.html+desktop.erb
app/views/home/index.html.erb
Si no existe una plantilla con el formato especificado, se lanzará un error ActionView::MissingTemplate
.
En lugar de establecer la variante en la llamada de renderizado, también puedes establecerla en el objeto de solicitud en tu acción del controlador.
def index
request.variant = determine_variant
end
private
def determine_variant
variant = nil
# algún código para determinar la(s) variante(s) a usar
variant = :mobile if session[:use_mobile]
variant
end
2.2.14 Encontrando Diseños
Para encontrar el diseño actual, Rails primero busca un archivo en app/views/layouts
con el mismo nombre base que el controlador. Por ejemplo, renderizar acciones desde la clase PhotosController
usará app/views/layouts/photos.html.erb
(o app/views/layouts/photos.builder
). Si no hay un diseño específico del controlador, Rails usará app/views/layouts/application.html.erb
o app/views/layouts/application.builder
. Si no hay un diseño .erb
, Rails usará un diseño .builder
si existe uno. Rails también proporciona varias maneras de asignar más precisamente diseños específicos a controladores y acciones individuales.
2.2.14.1 Especificando Diseños para Controladores
Puedes anular las convenciones de diseño predeterminadas en tus controladores usando la declaración layout
. Por ejemplo:
class ProductsController < ApplicationController
layout "inventory"
#...
end
Con esta declaración, todas las vistas renderizadas por el ProductsController
usarán app/views/layouts/inventory.html.erb
como su diseño.
Para asignar un diseño específico para toda la aplicación, usa una declaración layout
en tu clase ApplicationController
:
class ApplicationController < ActionController::Base
layout "main"
#...
end
Con esta declaración, todas las vistas en toda la aplicación usarán app/views/layouts/main.html.erb
para su diseño.
2.2.14.2 Eligiendo Diseños en Tiempo de Ejecución
Puedes usar un símbolo para diferir la elección del diseño hasta que se procese una solicitud:
class ProductsController < ApplicationController
layout :products_layout
def show
@product = Product.find(params[:id])
end
private
def products_layout
@current_user.special? ? "special" : "products"
end
end
Ahora, si el usuario actual es un usuario especial, obtendrán un diseño especial al ver un producto.
Incluso puedes usar un método en línea, como un Proc, para determinar el diseño. Por ejemplo, si pasas un objeto Proc, el bloque que le das al Proc recibirá la instancia del controller
, por lo que el diseño se puede determinar en función de la solicitud actual:
class ProductsController < ApplicationController
layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
end
2.2.14.3 Diseños Condicionales
Los diseños especificados a nivel de controlador admiten las opciones :only
y :except
. Estas opciones toman un nombre de método, o un array de nombres de métodos, que corresponden a nombres de métodos dentro del controlador:
class ProductsController < ApplicationController
layout "product", except: [:index, :rss]
end
Con esta declaración, el diseño product
se usaría para todo excepto los métodos rss
e index
.
2.2.14.4 Herencia de Diseños
Las declaraciones de diseño se propagan hacia abajo en la jerarquía, y las declaraciones de diseño más específicas siempre anulan las más generales. Por ejemplo:
application_controller.rb
class ApplicationController < ActionController::Base layout "main" end
articles_controller.rb
class ArticlesController < ApplicationController end
special_articles_controller.rb
class SpecialArticlesController < ArticlesController layout "special" end
old_articles_controller.rb
class OldArticlesController < SpecialArticlesController layout false def show @article = Article.find(params[:id]) end def index @old_articles = Article.older render layout: "old" end # ... end
En esta aplicación:
- En general, las vistas se renderizarán en el diseño
main
. ArticlesController#index
usará el diseñomain
.SpecialArticlesController#index
usará el diseñospecial
.OldArticlesController#show
no usará ningún diseño en absoluto.OldArticlesController#index
usará el diseñoold
.
2.2.14.5 Herencia de Plantillas
Similar a la lógica de Herencia de Diseños, si no se encuentra una plantilla o parcial en la ruta convencional, el controlador buscará una plantilla o parcial para renderizar en su cadena de herencia. Por ejemplo:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
end
# app/controllers/admin_controller.rb
class AdminController < ApplicationController
end
# app/controllers/admin/products_controller.rb
class Admin::ProductsController < AdminController
def index
end
end
El orden de búsqueda para una acción admin/products#index
será:
app/views/admin/products/
app/views/admin/
app/views/application/
Esto hace que app/views/application/
sea un excelente lugar para tus parciales compartidos, que luego pueden ser renderizados en tu ERB de la siguiente manera:
<%# app/views/admin/products/index.html.erb %>
<%= render @products || "empty_list" %>
<%# app/views/application/_empty_list.html.erb %>
No hay elementos en esta lista <em>aún</em>.
2.2.15 Evitando Errores de Doble Renderizado
Tarde o temprano, la mayoría de los desarrolladores de Rails verán el mensaje de error "Solo se puede renderizar o redirigir una vez por acción". Aunque esto es molesto, es relativamente fácil de solucionar. Por lo general, ocurre debido a un malentendido fundamental sobre cómo funciona render
.
Por ejemplo, aquí hay un código que desencadenará este error:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
render action: "regular_show"
end
Si @book.special?
evalúa a true
, Rails comenzará el proceso de renderizado para volcar la variable @book
en la vista special_show
. Pero esto no detendrá el resto del código en la acción show
de ejecutarse, y cuando Rails llegue al final de la acción, comenzará a renderizar la vista regular_show
- y lanzará un error. La solución es simple: asegúrate de tener solo una llamada a render
o redirect
en un solo camino de código. Algo que puede ayudar es return
. Aquí hay una versión corregida del método:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
return
end
render action: "regular_show"
end
Nota que el renderizado implícito realizado por ActionController detecta si render
ha sido llamado, por lo que lo siguiente funcionará sin errores:
def show
@book = Book.find(params[:id])
if @book.special?
render action: "special_show"
end
end
Esto renderizará un libro con special?
establecido con la plantilla special_show
, mientras que otros libros se renderizarán con la plantilla show
predeterminada.
2.3 Usando redirect_to
Otra forma de manejar la devolución de respuestas a una solicitud HTTP es con redirect_to
. Como has visto, render
le dice a Rails qué vista (u otro recurso) usar para construir una respuesta. El método redirect_to
hace algo completamente diferente: le dice al navegador que envíe una nueva solicitud para una URL diferente. Por ejemplo, podrías redirigir desde donde estés en tu código al índice de fotos en tu aplicación con esta llamada:
redirect_to photos_url
Puedes usar redirect_back
para devolver al usuario a la página de la que acaba de venir. Esta ubicación se extrae del encabezado HTTP_REFERER
, que no está garantizado que sea establecido por el navegador, por lo que debes proporcionar el fallback_location
para usar en este caso.
redirect_back(fallback_location: root_path)
NOTA: redirect_to
y redirect_back
no detienen y retornan inmediatamente de la ejecución del método, sino que simplemente establecen respuestas HTTP. Las declaraciones que ocurren después de ellas en un método serán ejecutadas. Puedes detener por un return
explícito u otro mecanismo de detención, si es necesario.
2.3.1 Obteniendo un Código de Redirección Diferente
Rails usa el código de estado HTTP 302, una redirección temporal, cuando llamas a redirect_to
. Si te gustaría usar un código de estado diferente, tal vez 301, una redirección permanente, puedes usar la opción :status
:
redirect_to photos_path, status: 301
Al igual que la opción :status
para render
, :status
para redirect_to
acepta tanto designaciones de encabezado numéricas como simbólicas.
2.3.2 La Diferencia entre render
y redirect_to
A veces, los desarrolladores inexpertos piensan en redirect_to
como una especie de comando goto
, moviendo la ejecución de un lugar a otro en tu código Rails. Esto es no correcto.
La acción actual se completará, devolviendo una respuesta al navegador. Después de esto, tu código deja de ejecutarse y espera una nueva solicitud, solo sucede que has dicho al navegador qué solicitud debe hacer a continuación enviando de vuelta un código de estado HTTP 302.
Considera estas acciones para ver la diferencia:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
render action: "index"
end
end
Con el código en esta forma, probablemente habrá un problema si la variable @book
es nil
. Recuerda, un render :action
no ejecuta ningún código en la acción de destino, por lo que nada configurará la variable @books
que probablemente requerirá la vista index
. Una forma de solucionar esto es redirigir en lugar de renderizar:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
redirect_to action: :index
end
end
Con este código, el navegador hará una nueva solicitud para la página de índice, el código en el método index
se ejecutará, y todo estará bien.
La única desventaja de este código es que requiere un viaje de ida y vuelta al navegador: el navegador solicitó la acción show con /books/1
y el controlador encuentra que no hay libros, por lo que el controlador envía una respuesta de redirección 302 al navegador diciéndole que vaya a /books/
, el navegador cumple y envía una nueva solicitud de vuelta al controlador pidiendo ahora la acción index
, el controlador luego obtiene todos los libros en la base de datos y renderiza la plantilla de índice, enviándola de vuelta al navegador que luego la muestra en tu pantalla.
Aunque en una aplicación pequeña, esta latencia añadida podría no ser un problema, es algo para pensar si el tiempo de respuesta es una preocupación. Podemos demostrar una forma de manejar esto con un ejemplo inventado:
def index
@books = Book.all
end
def show
@book = Book.find_by(id: params[:id])
if @book.nil?
@books = Book.all
flash.now[:alert] = "Tu libro no fue encontrado"
render "index"
end
end
Esto detectaría que no hay libros con el ID especificado, llenaría la variable de instancia @books
con todos los libros en el modelo, y luego renderizaría directamente la plantilla index.html.erb
, devolviéndola al navegador con un mensaje de alerta flash para decirle al usuario lo que sucedió.
2.4 Usando head
para Construir Respuestas Solo de Encabezado
El método head
se puede usar para enviar respuestas solo con encabezados al navegador. El método head
acepta un número o símbolo (ver tabla de referencia) que representa un código de estado HTTP. El argumento de opciones se interpreta como un hash de nombres y valores de encabezados. Por ejemplo, puedes devolver solo un encabezado de error:
head :bad_request
Esto produciría el siguiente encabezado:
HTTP/1.1 400 Bad Request
Connection: close
Date: Sun, 24 Jan 2010 12:15:53 GMT
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
X-Runtime: 0.013483
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache
O puedes usar otros encabezados HTTP para transmitir otra información:
head :created, location: photo_path(@photo)
Lo que produciría:
HTTP/1.1 201 Created
Connection: close
Date: Sun, 24 Jan 2010 12:16:44 GMT
Transfer-Encoding: chunked
Location: /photos/1
Content-Type: text/html; charset=utf-8
X-Runtime: 0.083496
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache
3 Estructurando Diseños
Cuando Rails renderiza una vista como respuesta, lo hace combinando la vista con el diseño actual, usando las reglas para encontrar el diseño actual que se cubrieron anteriormente en esta guía. Dentro de un diseño, tienes acceso a tres herramientas para combinar diferentes fragmentos de salida para formar la respuesta general:
- Etiquetas de activos
yield
ycontent_for
- Parciales
3.1 Ayudantes de Etiquetas de Activos
Los ayudantes de etiquetas de activos proporcionan métodos para generar HTML que vincula vistas a feeds, JavaScript, hojas de estilo, imágenes, videos y audios. Hay seis ayudantes de etiquetas de activos disponibles en Rails:
Puedes usar estas etiquetas en diseños u otras vistas, aunque las auto_discovery_link_tag
, javascript_include_tag
y stylesheet_link_tag
se usan más comúnmente en la sección <head>
de un diseño.
ADVERTENCIA: Los ayudantes de etiquetas de activos no verifican la existencia de los activos en las ubicaciones especificadas; simplemente asumen que sabes lo que estás haciendo y generan el enlace.
3.1.1 Enlazando a Feeds con el auto_discovery_link_tag
El ayudante auto_discovery_link_tag
construye HTML que la mayoría de los navegadores y lectores de feeds pueden usar para detectar la presencia de feeds RSS, Atom o JSON. Toma el tipo de enlace (:rss
, :atom
o :json
), un hash de opciones que se pasan a través de url_for, y un hash de opciones para la etiqueta:
<%= auto_discovery_link_tag(:rss, {action: "feed"},
{title: "RSS Feed"}) %>
Hay tres opciones de etiqueta disponibles para el auto_discovery_link_tag
:
:rel
especifica el valorrel
en el enlace. El valor predeterminado es "alternate".:type
especifica un tipo MIME explícito. Rails generará automáticamente un tipo MIME apropiado.:title
especifica el título del enlace. El valor predeterminado es el valor:type
en mayúsculas, por ejemplo, "ATOM" o "RSS".
3.1.2 Enlazando a Archivos JavaScript con el javascript_include_tag
El ayudante javascript_include_tag
devuelve una etiqueta HTML script
para cada fuente proporcionada.
Si estás usando Rails con el Pipeline de Activos habilitado, este ayudante generará un enlace a /assets/javascripts/
en lugar de public/javascripts
, que se usaba en versiones anteriores de Rails. Este enlace es luego servido por el pipeline de activos.
Un archivo JavaScript dentro de una aplicación Rails o un motor Rails va en una de tres ubicaciones: app/assets
, lib/assets
o vendor/assets
. Estas ubicaciones se explican en detalle en la sección Organización de Activos en la Guía del Pipeline de Activos.
Puedes especificar una ruta completa relativa a la raíz del documento, o una URL, si lo prefieres. Por ejemplo, para enlazar un archivo JavaScript main.js
que está dentro de uno de app/assets/javascripts
, lib/assets/javascripts
o vendor/assets/javascripts
, harías esto:
<%= javascript_include_tag "main" %>
Rails luego generará una etiqueta script
como esta:
<script src='/assets/main.js'></script>
La solicitud a este activo es luego servida por la gema Sprockets.
Para incluir múltiples archivos como app/assets/javascripts/main.js
y app/assets/javascripts/columns.js
al mismo tiempo:
<%= javascript_include_tag "main", "columns" %>
Para incluir app/assets/javascripts/main.js
y app/assets/javascripts/photos/columns.js
:
<%= javascript_include_tag "main", "/photos/columns" %>
Para incluir http://example.com/main.js
:
<%= javascript_include_tag "http://example.com/main.js" %>
3.1.3 Enlazando a Archivos CSS con el stylesheet_link_tag
El ayudante stylesheet_link_tag
devuelve una etiqueta HTML <link>
para cada fuente proporcionada.
Si estás usando Rails con el "Pipeline de Activos" habilitado, este ayudante generará un enlace a /assets/stylesheets/
. Este enlace es luego procesado por la gema Sprockets. Un archivo de hoja de estilo puede almacenarse en una de tres ubicaciones: app/assets
, lib/assets
o vendor/assets
.
Puedes especificar una ruta completa relativa a la raíz del documento, o una URL. Por ejemplo, para enlazar un archivo de hoja de estilo que está dentro de un directorio llamado stylesheets
dentro de uno de app/assets
, lib/assets
o vendor/assets
, harías esto:
<%= stylesheet_link_tag "main" %>
Para incluir app/assets/stylesheets/main.css
y app/assets/stylesheets/columns.css
:
<%= stylesheet_link_tag "main", "columns" %>
Para incluir app/assets/stylesheets/main.css
y app/assets/stylesheets/photos/columns.css
:
<%= stylesheet_link_tag "main", "photos/columns" %>
Para incluir http://example.com/main.css
:
<%= stylesheet_link_tag "http://example.com/main.css" %>
Por defecto, el stylesheet_link_tag
crea enlaces con rel="stylesheet"
. Puedes anular este valor predeterminado especificando una opción apropiada (:rel
):
<%= stylesheet_link_tag "main_print", media: "print" %>
3.1.4 Enlazando a Imágenes con el image_tag
El ayudante image_tag
construye una etiqueta HTML <img />
al archivo especificado. Por defecto, los archivos se cargan desde public/images
.
ADVERTENCIA: Ten en cuenta que debes especificar la extensión de la imagen.
<%= image_tag "header.png" %>
Puedes proporcionar una ruta a la imagen si lo deseas:
<%= image_tag "icons/delete.gif" %>
Puedes proporcionar un hash de opciones HTML adicionales:
<%= image_tag "icons/delete.gif", {height: 45} %>
Puedes proporcionar texto alternativo para la imagen que se usará si el usuario tiene las imágenes desactivadas en su navegador. Si no especificas un texto alt explícitamente, el valor predeterminado es el nombre del archivo del archivo, en mayúsculas y sin extensión. Por ejemplo, estas dos etiquetas de imagen devolverían el mismo código:
<%= image_tag "home.gif" %>
<%= image_tag "home.gif", alt: "Home" %>
También puedes especificar una etiqueta de tamaño especial, en el formato "{ancho}x{alto}":
<%= image_tag "home.gif", size: "50x20" %>
Además de las etiquetas especiales anteriores, puedes proporcionar un hash final de opciones HTML estándar, como :class
, :id
o :name
:
<%= image_tag "home.gif", alt: "Go Home",
id: "HomeImage",
class: "nav_bar" %>
3.1.5 Enlazando a Videos con el video_tag
El ayudante video_tag
construye una etiqueta HTML5 <video>
al archivo especificado. Por defecto, los archivos se cargan desde public/videos
.
<%= video_tag "movie.ogg" %>
Produce
<video src="/videos/movie.ogg" />
Al igual que una image_tag
, puedes proporcionar una ruta, ya sea absoluta o relativa al directorio public/videos
. Además, puedes especificar la opción size: "#{ancho}x#{alto}"
al igual que una image_tag
. Las etiquetas de video también pueden tener cualquiera de las opciones HTML especificadas al final (id
, class
, etc.).
La etiqueta de video también admite todas las opciones HTML de <video>
a través del hash de opciones HTML, incluyendo:
poster: "image_name.png"
, proporciona una imagen para colocar en lugar del video antes de que comience a reproducirse.autoplay: true
, comienza a reproducir el video al cargar la página.loop: true
, repite el video una vez que llega al final.controls: true
, proporciona controles suministrados por el navegador para que el usuario interactúe con el video.autobuffer: true
, el video precargará el archivo para el usuario al cargar la página.
También puedes especificar múltiples videos para reproducir pasando un array de videos al video_tag
:
<%= video_tag ["trailer.ogg", "movie.ogg"] %>
Esto producirá:
<video>
<source src="/videos/trailer.ogg">
<source src="/videos/movie.ogg">
</video>
3.1.6 Enlazando a Archivos de Audio con el audio_tag
El ayudante audio_tag
construye una etiqueta HTML5 <audio>
al archivo especificado. Por defecto, los archivos se cargan desde public/audios
.
<%= audio_tag "music.mp3" %>
Puedes proporcionar una ruta al archivo de audio si lo deseas:
<%= audio_tag "music/first_song.mp3" %>
También puedes proporcionar un hash de opciones adicionales, como :id
, :class
, etc.
Al igual que el video_tag
, el audio_tag
tiene opciones especiales:
autoplay: true
, comienza a reproducir el audio al cargar la página.controls: true
, proporciona controles suministrados por el navegador para que el usuario interactúe con el audio.autobuffer: true
, el audio precargará el archivo para el usuario al cargar la página.
3.2 Entendiendo yield
Dentro del contexto de un diseño, yield
identifica una sección donde el contenido de la vista debe ser insertado. La forma más simple de usar esto es tener un solo yield
, en el cual se inserta todo el contenido de la vista que se está renderizando:
<html>
<head>
</head>
<body>
<%= yield %>
</body>
</html>
También puedes crear un diseño con múltiples regiones de yield:
<html>
<head>
<%= yield :head %>
</head>
<body>
<%= yield %>
</body>
</html>
El cuerpo principal de la vista siempre se renderizará en el yield
sin nombre. Para renderizar contenido en un yield
con nombre, llama al método content_for
con el mismo argumento que el yield
con nombre.
NOTA: Las aplicaciones recién generadas incluirán <%= yield :head %>
dentro del elemento <head>
de su plantilla app/views/layouts/application.html.erb
.
3.3 Usando el Método content_for
El método content_for
te permite insertar contenido en un bloque yield
con nombre en tu diseño. Por ejemplo, esta vista funcionaría con el diseño que acabas de ver:
<% content_for :head do %>
<title>Una página simple</title>
<% end %>
<p>¡Hola, Rails!</p>
El resultado de renderizar esta página en el diseño suministrado sería este HTML:
<html>
<head>
<title>Una página simple</title>
</head>
<body>
<p>¡Hola, Rails!</p>
</body>
</html>
El método content_for
es muy útil cuando tu diseño contiene regiones distintas, como barras laterales y pies de página, que deben tener sus propios bloques de contenido insertados. También es útil para insertar elementos <script>
JavaScript específicos de la página, elementos <link>
CSS, elementos <meta>
específicos del contexto, o cualquier otro elemento en el <head>
de un diseño genérico.
3.4 Usando Parciales
Las plantillas parciales, generalmente llamadas "parciales", son otro dispositivo para dividir el proceso de renderizado en partes más manejables. Con un parcial, puedes mover el código para renderizar una pieza particular de una respuesta a su propio archivo.
3.4.1 Nombrando Parciales
Para renderizar un parcial como parte de una vista, usas el método render
dentro de la vista:
<%= render "menu" %>
Esto renderizará un archivo llamado _menu.html.erb
en ese punto dentro de la vista que se está renderizando. Nota el carácter de guion bajo inicial: los parciales se nombran con un guion bajo inicial para distinguirlos de las vistas regulares, aunque se refieren a ellos sin el guion bajo. Esto es cierto incluso cuando estás incluyendo un parcial de otra carpeta:
<%= render "application/menu" %>
Dado que los parciales de vista dependen de la misma Herencia de Plantillas que las plantillas y diseños, ese código incluirá el parcial de app/views/application/_menu.html.erb
.
3.4.2 Usando Parciales para Simplificar Vistas
Una forma de usar parciales es tratarlos como el equivalente a subrutinas: como una forma de mover detalles fuera de una vista para que puedas comprender más fácilmente lo que está sucediendo. Por ejemplo, podrías tener una vista que se vea así:
<%= render "application/ad_banner" %>
<h1>Productos</h1>
<p>Aquí hay algunos de nuestros excelentes productos:</p>
<%# ... %>
<%= render "application/footer" %>
Aquí, los parciales _ad_banner.html.erb
y _footer.html.erb
podrían contener contenido que es compartido por muchas páginas en tu aplicación. No necesitas ver los detalles de estas secciones cuando te estás concentrando en una página particular.
Como se vio en las secciones anteriores de esta guía, yield
es una herramienta muy poderosa para limpiar tus diseños. Ten en cuenta que es puro Ruby, por lo que puedes usarlo casi en cualquier lugar. Por ejemplo, podemos usarlo para DRY las definiciones de diseño de formularios para varios recursos similares:
users/index.html.erb
<%= render "application/search_filters", search: @q do |form| %> <p> Nombre contiene: <%= form.text_field :name_contains %> </p> <% end %>
roles/index.html.erb
<%= render "application/search_filters", search: @q do |form| %> <p> Título contiene: <%= form.text_field :title_contains %> </p> <% end %>
application/_search_filters.html.erb
<%= form_with model: search do |form| %> <h1>Formulario de búsqueda:</h1> <fieldset> <%= yield form %> </fieldset> <p> <%= form.submit "Buscar" %> </p> <% end %>
Para contenido que se comparte entre todas las páginas de tu aplicación, puedes usar parciales directamente desde los diseños.
3.4.3 Diseños Parciales
Un parcial puede usar su propio archivo de diseño, al igual que una vista puede usar un diseño. Por ejemplo, podrías llamar a un parcial así:
<%= render partial: "link_area", layout: "graybar" %>
Esto buscaría un parcial llamado _link_area.html.erb
y lo renderizaría usando el diseño _graybar.html.erb
. Ten en cuenta que los diseños para parciales siguen el mismo nombre con guion bajo inicial que los parciales regulares, y se colocan en la misma carpeta con el parcial al que pertenecen (no en la carpeta maestra layouts
).
También ten en cuenta que especificar explícitamente :partial
es necesario cuando se pasan opciones adicionales como :layout
.
3.4.4 Pasando Variables Locales
También puedes pasar variables locales a parciales, haciéndolos aún más poderosos y flexibles. Por ejemplo, puedes usar esta técnica para reducir la duplicación entre páginas nuevas y de edición, mientras mantienes un poco de contenido distinto:
new.html.erb
<h1>Nueva zona</h1> <%= render partial: "form", locals: {zone: @zone} %>
edit.html.erb
<h1>Editando zona</h1> <%= render partial: "form", locals: {zone: @zone} %>
_form.html.erb
<%= form_with model: zone do |form| %> <p> <b>Nombre de la zona</b><br> <%= form.text_field :name %> </p> <p> <%= form.submit %> </p> <% end %>
Aunque el mismo parcial se renderizará en ambas vistas, el ayudante de envío de Action View devolverá "Crear Zona" para la acción nueva y "Actualizar Zona" para la acción de edición.
Para pasar una variable local a un parcial solo en casos específicos, usa el local_assigns
.
index.html.erb
<%= render user.articles %>
show.html.erb
<%= render article, full: true %>
_article.html.erb
<h2><%= article.title %></h2> <% if local_assigns[:full] %> <%= simple_format article.body %> <% else %> <%= truncate article.body %> <% end %>
De esta manera es posible usar el parcial sin necesidad de declarar todas las variables locales.
Cada parcial también tiene una variable local con el mismo nombre que el parcial (menos el guion bajo inicial). Puedes pasar un objeto a esta variable local a través de la opción :object
:
<%= render partial: "customer", object: @new_customer %>
Dentro del parcial customer
, la variable customer
se referirá a @new_customer
de la vista padre.
Si tienes una instancia de un modelo para renderizar en un parcial, puedes usar una sintaxis abreviada:
<%= render @customer %>
Suponiendo que la variable de instancia @customer
contiene una instancia del modelo Customer
, esto usará _customer.html.erb
para renderizarlo y pasará la variable local customer
al parcial que se referirá a la variable de instancia @customer
en la vista padre.
3.4.5 Renderizando Colecciones
Los parciales son muy útiles al renderizar colecciones. Cuando pasas una colección a un parcial a través de la opción :collection
, el parcial se insertará una vez para cada miembro en la colección:
index.html.erb
<h1>Productos</h1> <%= render partial: "product", collection: @products %>
_product.html.erb
<p>Nombre del Producto: <%= product.name %></p>
Cuando un parcial se llama con una colección pluralizada, entonces las instancias individuales del parcial tienen acceso al miembro de la colección que se está renderizando a través de una variable nombrada después del parcial. En este caso, el parcial es _product
, y dentro del parcial _product
, puedes referirte a product
para obtener la instancia que se está renderizando.
También hay una abreviatura para esto. Suponiendo que @products
es una colección de instancias de Product
, simplemente puedes escribir esto en el index.html.erb
para producir el mismo resultado:
<h1>Productos</h1>
<%= render @products %>
Rails determina el nombre del parcial a usar mirando el nombre del modelo en la colección. De hecho, incluso puedes crear una colección heterogénea y renderizarla de esta manera, y Rails elegirá el parcial adecuado para cada miembro de la colección:
index.html.erb
<h1>Contactos</h1> <%= render [customer1, employee1, customer2, employee2] %>
customers/_customer.html.erb
<p>Cliente: <%= customer.name %></p>
employees/_employee.html.erb
<p>Empleado: <%= employee.name %></p>
En este caso, Rails usará los parciales de cliente o empleado según corresponda para cada miembro de la colección.
En el caso de que la colección esté vacía, render
devolverá nil, por lo que debería ser bastante simple proporcionar contenido alternativo.
<h1>Productos</h1>
<%= render(@products) || "No hay productos disponibles." %>
3.4.6 Variables Locales
Para usar un nombre de variable local personalizado dentro del parcial, especifica la opción :as
en la llamada al parcial:
<%= render partial: "product", collection: @products, as: :item %>
Con este cambio, puedes acceder a una instancia de la colección @products
como la variable local item
dentro del parcial.
También puedes pasar variables locales arbitrarias a cualquier parcial que estés renderizando con la opción locals: {}
:
<%= render partial: "product", collection: @products,
as: :item, locals: {title: "Página de Productos"} %>
En este caso, el parcial tendrá acceso a una variable local title
con el valor "Página de Productos".
3.4.7 Variables de Contador
Rails también hace disponible una variable de contador dentro de un parcial llamado por la colección. La variable se nombra después del título del parcial seguido de _counter
. Por ejemplo, al renderizar una colección @products
, el parcial _product.html.erb
puede acceder a la variable product_counter
. La variable indexa el número de veces que el parcial ha sido renderizado dentro de la vista que lo contiene, comenzando con un valor de 0
en el primer renderizado.
# index.html.erb
<%= render partial: "product", collection: @products %>
# _product.html.erb
<%= product_counter %> # 0 para el primer producto, 1 para el segundo producto...
Esto también funciona cuando el nombre de la variable local se cambia usando la opción as:
. Así que si hiciste as: :item
, la variable de contador sería item_counter
.
3.4.8 Plantillas de Espaciador
También puedes especificar un segundo parcial para ser renderizado entre instancias del parcial principal usando la opción :spacer_template
:
<%= render partial: @products, spacer_template: "product_ruler" %>
Rails renderizará el parcial _product_ruler
(sin datos pasados a él) entre cada par de parciales _product
.
3.4.9 Diseños de Parcial de Colección
Al renderizar colecciones, también es posible usar la opción :layout
:
<%= render partial: "product", collection: @products, layout: "special_layout" %>
El diseño se renderizará junto con el parcial para cada elemento en la colección. Las variables current object
y object_counter
estarán disponibles en el diseño también, de la misma manera que lo están dentro del parcial.
3.5 Usando Diseños Anidados
Puedes encontrar que tu aplicación requiere un diseño que difiera ligeramente de tu diseño de aplicación regular para soportar un controlador particular. En lugar de repetir el diseño principal y editarlo, puedes lograr esto usando diseños anidados (a veces llamados sub-plantillas). Aquí tienes un ejemplo:
Supongamos que tienes el siguiente diseño de ApplicationController
:
app/views/layouts/application.html.erb
<html> <head> <title><%= @page_title or "Título de Página" %></title> <%= stylesheet_link_tag "layout" %> <%= yield :head %> </head> <body> <div id="top_menu">Elementos del menú superior aquí</div> <div id="menu">Elementos del menú aquí</div> <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div> </body> </html>
En las páginas generadas por NewsController
, quieres ocultar el menú superior y agregar un menú derecho:
app/views/layouts/news.html.erb
<% content_for :head do %> <style> #top_menu {display: none} #right_menu {float: right; background-color: yellow; color: black} </style> <% end %> <% content_for :content do %> <div id="right_menu">Elementos del menú derecho aquí</div> <%= content_for?(:news_content) ? yield(:news_content) : yield %> <% end %> <%= render template: "layouts/application" %>
Eso es todo. Las vistas de News usarán el nuevo diseño, ocultando el menú superior y agregando un nuevo menú derecho dentro del div "content".
Hay varias maneras de obtener resultados similares con diferentes esquemas de sub-plantillas usando esta técnica. Ten en cuenta que no hay límite en los niveles de anidamiento. Uno puede usar el método ActionView::render
a través de render template: 'layouts/news'
para basar un nuevo diseño en el diseño de News. Si estás seguro de que no subplantillarás el diseño de News
, puedes reemplazar el content_for?(:news_content) ? yield(:news_content) : yield
con simplemente yield
.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.
También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.
Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.