edge
Más en rubyonrails.org: Más Ruby on Rails

Diseños y Renderizado en Rails

Esta guía cubre las características básicas de diseño de Action Controller y Action View.

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

1 Resumen: Cómo encajan las piezas

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 llega el momento de enviar una respuesta de vuelta al usuario, el Controlador pasa las cosas a la Vista. Esa transferencia es el tema de esta guía.

En líneas generales, esto implica decidir qué se debe enviar como respuesta y llamar a un método adecuado para crear esa respuesta. Si la respuesta es una vista completa, Rails también realiza un trabajo adicional para envolver la vista en un diseño y posiblemente para 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 formas 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 escuchado que Rails promueve la "convención sobre configuración". El renderizado por defecto es un excelente ejemplo de esto. Por defecto, los controladores en Rails renderizan automáticamente 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 están por venir!</h1>

Rails automáticamente renderizará app/views/books/index.html.erb cuando navegues a /books y verás "¡Los libros están por venir!" en tu pantalla.

Sin embargo, una pantalla de próximamente solo es mínimamente útil, así que pronto crearás tu modelo Book y agregarás la acción de índice a BooksController:

class BooksController < ApplicationController
  def index
    @books = Book.all
  end
end

Observa que no tenemos un render explícito al final de la acción de índice 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 de controlador, Rails buscará automáticamente 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>Listado de 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 "Eliminar", 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 real lo realizan clases anidadas del módulo ActionView::Template::Handlers. Esta guía no profundiza en ese proceso, pero es importante saber que la extensión de archivo de 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 se encarga de renderizar el contenido de tu aplicación para su uso por parte de un navegador. Hay varias formas de personalizar el comportamiento de render. Puedes renderizar la vista predeterminada 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.

CONSEJO: Si quieres ver los resultados exactos de una llamada a render sin necesidad de inspeccionarlos 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 lo 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 desde un controlador completamente diferente al que contiene el código de la acción? También puedes hacerlo 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 se encuentra 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 inclinada 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 Conclusión

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 update donde queremos renderizar la plantilla edit si el libro no se actualiza correctamente, todas las siguientes llamadas a render 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 usar es realmente una cuestión de estilo y convención, pero la regla general es usar la forma 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 completamente de una vista, 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 va en contra de la orientación MVC de Rails y dificultará que otros desarrolladores sigan la lógica de tu proyecto. En su lugar, utiliza una vista ERB separada.

Por defecto, la renderización en línea utiliza ERB. Puedes forzar que utilice 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 utilizando la opción :plain en render:

render plain: "OK"

CONSEJO: Renderizar texto puro es más útil cuando estás respondiendo a solicitudes Ajax o de servicios web que esperan algo diferente a HTML correcto.

NOTA: Por defecto, si usas la opción :plain, el texto se renderizará sin utilizar el diseño actual. Si quieres que Rails coloque el texto en el diseño actual, debes 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 utilizando la opción :html en render:

render html: helpers.tag.strong('Not Found')

CONSEJO: Esto es útil cuando estás renderizando un pequeño fragmento de código HTML. Sin embargo, es posible que desees considerar moverlo a un archivo de plantilla si el marcado es complejo.

NOTA: Al usar la opción html:, las entidades HTML se escaparán si la cadena no está compuesta con APIs que sean 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 incorporado para convertir objetos a JSON y renderizar ese JSON de vuelta al navegador:

render json: @product

CONSEJO: No es necesario 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 Renderizado de XML

Rails también tiene soporte incorporado para convertir objetos a XML y renderizar ese XML de vuelta al llamador:

render xml: @product

CONSEJO: No es necesario 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 Renderizado de JavaScript puro

Rails puede renderizar JavaScript puro:

render js: "alert('Hola Rails');"

Esto enviará la cadena proporcionada al navegador con un tipo MIME de text/javascript.

2.2.10 Renderizado de cuerpo sin formato

Puedes enviar un contenido sin formato al navegador, sin establecer ningún tipo de contenido, utilizando la opción :body en render:

render body: "sin formato"

CONSEJO: Esta opción solo debe usarse si no te importa el tipo de contenido de la respuesta. Usar :plain o :html puede ser más apropiado la mayoría de las veces.

NOTA: A menos que se anule, la respuesta devuelta desde 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 Renderizado de archivo sin procesar

Rails puede renderizar un archivo sin procesar 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 sin procesar (no admite ERB u otros manejadores). Por defecto, se renderiza dentro del diseño actual.

ADVERTENCIA: El uso de la opción :file en combinación con la entrada de usuarios puede causar problemas de seguridad, ya que un atacante podría usar esta acción para acceder a archivos sensibles de seguridad en tu sistema de archivos.

CONSEJO: send_file suele ser una opción más rápida y mejor si no se requiere un diseño.

2.2.12 Renderizado de objetos

Rails puede renderizar objetos que responden a :render_in.

render MyRenderable.new

Esto llama a render_in en el objeto proporcionado con el contexto de vista actual.

También puedes proporcionar el objeto utilizando la opción :renderable en render:

render renderable: MyRenderable.new

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 text/html (o application/json si usas la opción :json, o application/xml para la opción :xml). Hay momentos en los que es posible que desees cambiar esto, y puedes hacerlo estableciendo 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 de 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 indicarle a Rails que use un archivo específico como el diseño para la acción actual:

render layout: "special_layout"

También puedes indicarle 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 comprende 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
Informativa 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 intenta renderizar contenido junto con un código de estado que no es de contenido (100-199, 204, 205 o 304), se eliminará de la respuesta.

2.2.13.5 La opción :formats

Rails utiliza el formato especificado en la solicitud (o :html de forma predeterminada). Puede cambiar esto pasando la opción :formats con un símbolo o una matriz:

render formats: :xml
render formats: [:json, :xml]

Si no existe una plantilla con el formato especificado, se generará un error ActionView::MissingTemplate.

2.2.13.6 La opción :variants

Esto le indica a Rails que busque variantes de plantillas del mismo formato. Puede especificar una lista de variantes pasando la opción :variants con un símbolo o una matriz.

Un ejemplo de uso sería el siguiente.

# 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 generará un error ActionView::MissingTemplate.

En lugar de establecer la variante en la llamada de renderizado, también puede establecerla en el objeto de solicitud en la 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 utilizar
      variant = :mobile if session[:use_mobile]

      variant
    end

2.2.14 Encontrar 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, al renderizar acciones desde la clase PhotosController, se utilizará app/views/layouts/photos.html.erb (o app/views/layouts/photos.builder). Si no hay un diseño específico del controlador, Rails utilizará app/views/layouts/application.html.erb o app/views/layouts/application.builder. Si no hay un diseño .erb, Rails utilizará un diseño .builder si existe. Rails también proporciona varias formas de asignar diseños específicos de manera más precisa a controladores y acciones individuales.

2.2.14.1 Especificar diseños para controladores

Puede anular las convenciones de diseño predeterminadas en sus controladores utilizando la declaración layout. Por ejemplo:

class ProductsController < ApplicationController
  layout "inventory"
  #...
end

Con esta declaración, todas las vistas renderizadas por el ProductsController utilizarán app/views/layouts/inventory.html.erb como su diseño.

Para asignar un diseño específico para toda la aplicación, use una declaración de layout en su clase ApplicationController:

class ApplicationController < ActionController::Base
  layout "main"
  #...
end

Con esta declaración, todas las vistas en toda la aplicación utilizarán app/views/layouts/main.html.erb como su diseño.

2.2.14.2 Elegir diseños en tiempo de ejecución

Puede usar un símbolo para posponer 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á un diseño especial al ver un producto.

Incluso puede usar un método en línea, como un Proc, para determinar el diseño. Por ejemplo, si pasa un objeto Proc, el bloque que le dé al Proc se le dará 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 una matriz de nombres de métodos, que corresponden a los 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 utilizaría para todo excepto los métodos rss e index.

2.2.14.4 Herencia de diseños

Las declaraciones de diseño se heredan 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 utilizará el diseño main
  • SpecialArticlesController#index utilizará el diseño special
  • OldArticlesController#show no utilizará ningún diseño
  • OldArticlesController#index utilizará el diseño old ##### Herencia de plantillas

Similar a la lógica de herencia de diseño, 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 lugar ideal para sus parciales compartidos, que luego se pueden renderizar en su 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>todavía</em>.

2.2.15 Evitando errores de renderización duplicada

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 una comprensión errónea fundamental de 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? se evalúa como true, Rails iniciará el proceso de renderización para volcar la variable @book en la vista special_show. Pero esto no detendrá que el resto del código en la acción show se ejecute, 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úrese 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 parcheada del método:

def show
  @book = Book.find(params[:id])
  if @book.special?
    render action: "special_show"
    return
  end
  render action: "regular_show"
end

Tenga en cuenta que la renderización implícita realizada por ActionController detecta si se ha llamado a render, 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 Uso de redirect_to

Otra forma de manejar las 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 cualquier parte de 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 se garantiza que esté configurado por el navegador, por lo que debes proporcionar la fallback_location para usar en este caso.

redirect_back(fallback_location: root_path)

NOTA: redirect_to y redirect_back no detienen ni devuelven inmediatamente la ejecución del método, simplemente establecen respuestas HTTP. Las declaraciones que ocurran después de ellos en un método se ejecutarán. Puedes detener la ejecución mediante un return explícito u otro mecanismo de detención, si es necesario.

2.3.1 Obteniendo un código de estado de redirección diferente

Rails utiliza el código de estado HTTP 302, una redirección temporal, cuando llamas a redirect_to. Si deseas utilizar un código de estado diferente, como el 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, que mueve la ejecución de un lugar a otro en tu código de Rails. Esto es incorrecto. Tu código se detiene y espera una nueva solicitud del navegador. Simplemente has dicho al navegador qué solicitud debe hacer a continuación, enviando 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, es probable que haya un problema si la variable @book es nil. Recuerda, un render :action no ejecuta ningún código en la acción objetivo, por lo que nada configurará la variable @books que probablemente requiera la vista index. Una forma de solucionar esto es redirigir en lugar de renderizar: ```ruby 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, se ejecutará el código en el método index 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 de mostrar con /books/1 y el controlador encuentra que no hay libros, por lo que el controlador envía una respuesta de redireccionamiento 302 al navegador indicá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.

Si bien en una aplicación pequeña, esta latencia adicional puede no ser un problema, es algo a tener en cuenta si el tiempo de respuesta es una preocupación. Podemos demostrar una forma de manejar esto con un ejemplo ficticio:

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 qué sucedió.

2.4 Usando head para construir respuestas solo con encabezados

El método head se puede utilizar 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 encabezado. 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 cual 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, utilizando 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 partes de salida para formar la respuesta general:

3.1 Ayudantes de etiquetas de activos

Los ayudantes de etiquetas de activos proporcionan métodos para generar HTML que vinculan 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 etiquetas auto_discovery_link_tag, javascript_include_tag y stylesheet_link_tag se utilizan 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.

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 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 auto_discovery_link_tag:

  • :rel especifica el valor rel en el enlace. El valor predeterminado es "alternate".
  • :type especifica un tipo MIME explícito. Rails generará automáticamente un tipo MIME adecuado.
  • :title especifica el título del enlace. El valor predeterminado es el valor :type en mayúsculas, por ejemplo, "ATOM" o "RSS". #### Enlazando archivos JavaScript con javascript_include_tag

El ayudante javascript_include_tag devuelve una etiqueta HTML script por cada fuente proporcionada.

Si estás utilizando Rails con el Asset Pipeline habilitado, este ayudante generará un enlace a /assets/javascripts/ en lugar de public/javascripts que se utilizaba en versiones anteriores de Rails. Este enlace es luego servido por el asset pipeline.

Un archivo JavaScript dentro de una aplicación Rails o un motor Rails se coloca 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 Asset Pipeline.

Puedes especificar una ruta completa relativa a la raíz del documento, o una URL, si lo prefieres. Por ejemplo, para enlazar a un archivo JavaScript que está dentro de un directorio llamado javascripts dentro de app/assets, lib/assets o vendor/assets, harías esto:

<%= javascript_include_tag "main" %>

Rails entonces 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 varios 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" %>

El ayudante stylesheet_link_tag devuelve una etiqueta HTML <link> por cada fuente proporcionada.

Si estás utilizando Rails con el "Asset Pipeline" habilitado, este ayudante generará un enlace a /assets/stylesheets/. Este enlace es luego procesado por la gema Sprockets. Un archivo de hoja de estilo se puede almacenar 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 a un archivo de hoja de estilo que está dentro de un directorio llamado stylesheets dentro 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, stylesheet_link_tag crea enlaces con rel="stylesheet". Puedes anular esta opción predeterminada especificando una opción adecuada (:rel):

<%= stylesheet_link_tag "main_print", media: "print" %>

3.1.3 Enlazando a imágenes con image_tag

El ayudante image_tag construye una etiqueta HTML <img /> para el 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 un texto alternativo para la imagen que se utilizará si el usuario tiene las imágenes desactivadas en su navegador. Si no especificas explícitamente un texto alternativo, se utilizará el nombre 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 mencionadas anteriormente, 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.4 Enlazando a videos con video_tag

El ayudante video_tag construye una etiqueta HTML5 <video> para el archivo especificado. Por defecto, los archivos se cargan desde public/videos.

<%= video_tag "movie.ogg" %>

Produce

<video src="/videos/movie.ogg" />

Al igual que con 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 con 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: "nombre_imagen.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 varios videos para reproducir pasando una matriz de videos a video_tag:
<%= video_tag ["trailer.ogg", "movie.ogg"] %>

Esto producirá:

<video>
  <source src="/videos/trailer.ogg">
  <source src="/videos/movie.ogg">
</video>

3.1.5 Enlazando a archivos de audio con audio_tag

El ayudante audio_tag construye una etiqueta HTML5 <audio> para el archivo especificado. De forma predeterminada, 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 video_tag, 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 se debe insertar el contenido de la vista. La forma más sencilla de usar esto es tener un solo yield, en el cual se inserta todo el contenido de la vista que se está renderizando actualmente:

<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, se utiliza el método content_for.

3.3 Uso del 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 proporcionado 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 etiquetas que cargan archivos JavaScript o CSS específicos de la página en la cabecera de un diseño genérico.

3.4 Uso de parciales

Las plantillas parciales, generalmente llamadas "parciales", son otro recurso para dividir el proceso de renderizado en fragmentos más manejables. Con un parcial, puedes mover el código para renderizar una parte específica de una respuesta a su propio archivo.

3.4.1 Nombres de parciales

Para renderizar un parcial como parte de una vista, se utiliza 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. Observa el carácter de guión bajo al principio: los parciales se nombran con un guión bajo al principio para distinguirlos de las vistas regulares, aunque se los menciona sin el guión bajo. Esto también es válido cuando se incluye un parcial desde otra carpeta:

<%= render "shared/menu" %>

Ese código incluirá el parcial desde app/views/shared/_menu.html.erb.

3.4.2 Uso de parciales para simplificar vistas

Una forma de utilizar parciales es tratarlos como el equivalente de subrutinas: como una forma de mover los detalles fuera de una vista para que puedas entender lo que está sucediendo más fácilmente. Por ejemplo, podrías tener una vista que se vea así:

<%= render "shared/ad_banner" %>

<h1>Productos</h1>

<p>Aquí tienes algunos de nuestros excelentes productos:</p>
...

<%= render "shared/footer" %>

Aquí, los parciales _ad_banner.html.erb y _footer.html.erb podrían contener contenido que se comparte en muchas páginas de tu aplicación. No necesitas ver los detalles de estas secciones cuando te estás concentrando en una página en 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 (Don't Repeat Yourself) en la definición del diseño de un formulario para varios recursos similares:

  • users/index.html.erb

    <%= render "shared/search_filters", search: @q do |form| %>
      <p>
        El nombre contiene: <%= form.text_field :name_contains %>
      </p>
    <% end %>
    
  • roles/index.html.erb

    <%= render "shared/search_filters", search: @q do |form| %>
      <p>
        El título contiene: <%= form.text_field :title_contains %>
      </p>
    <% end %>
    
  • shared/_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 %>
    

CONSEJO: Para el contenido que se comparte en todas las páginas de tu aplicación, puedes usar parciales directamente desde los layouts.

3.4.3 Layouts parciales

Un parcial puede usar su propio archivo de layout, al igual que una vista puede usar un layout. Por ejemplo, podrías llamar a un parcial de esta manera:

<%= render partial: "link_area", layout: "graybar" %>

Esto buscaría un parcial llamado _link_area.html.erb y lo renderizaría utilizando el layout _graybar.html.erb. Ten en cuenta que los layouts para parciales siguen la misma convención de nombres con guión bajo al principio que los parciales regulares, y se colocan en la misma carpeta que el parcial al que pertenecen (no en la carpeta principal layouts).

También ten en cuenta que es necesario especificar explícitamente :partial al pasar opciones adicionales como :layout.

3.4.4 Pasando variables locales

También puedes pasar variables locales a los parciales, lo que los hace aún más poderosos y flexibles. Por ejemplo, puedes usar esta técnica para reducir la duplicación entre las páginas de creación y edición, manteniendo al mismo tiempo 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 se renderizará el mismo parcial en ambas vistas, el helper de submit de Action View devolverá "Crear zona" para la acción de creación y "Actualizar zona" para la acción de edición.

Para pasar una variable local a un parcial solo en casos específicos, utiliza 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 utilizar 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 (sin el guión 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 principal.

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 utilizará _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 principal.

3.4.5 Renderizando colecciones

Los parciales son muy útiles para renderizar colecciones. Cuando pasas una colección a un parcial a través de la opción :collection, el parcial se insertará una vez por cada miembro de 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 se llama a un parcial con una colección en plural, las instancias individuales del parcial tienen acceso al miembro de la colección que se está renderizando a través de una variable con el nombre 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 forma abreviada para esto. Suponiendo que @products es una colección de instancias de Product, simplemente puedes escribir esto en index.html.erb para obtener el mismo resultado:

<h1>Productos</h1>
<%= render @products %>

Rails determina el nombre del parcial a utilizar al mirar 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 utilizará los parciales customer o employee según corresponda para cada miembro de la colección. En caso de que la colección esté vacía, render devolverá nil, por lo que debería ser bastante sencillo proporcionar un 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 de la plantilla parcial, especifique la opción :as en la llamada a la 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 de la 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, la parcial tendrá acceso a una variable local title con el valor "Página de Productos".

3.4.7 Variables de contador

Rails también pone a disposición una variable de contador dentro de una parcial llamada por la colección. La variable se llama igual que el nombre de la parcial seguido de _counter. Por ejemplo, al renderizar una colección @products, la parcial _product.html.erb puede acceder a la variable product_counter. La variable indexa el número de veces que la parcial se ha renderizado dentro de la vista que la contiene, comenzando con un valor de 0 en la primera renderización.

# 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 se cambia el nombre de la parcial usando la opción as:. Entonces, si hicieras as: :item, la variable de contador sería item_counter.

3.4.8 Plantillas de separador

También puedes especificar una segunda parcial que se renderizará entre las instancias de la parcial principal usando la opción :spacer_template:

<%= render partial: @products, spacer_template: "product_ruler" %>

Rails renderizará la parcial _product_ruler (sin pasarle datos) entre cada par de parciales _product.

3.4.9 Diseños de parciales de colecciones

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 la parcial para cada elemento de la colección. Las variables de objeto actual y object_counter también estarán disponibles en el diseño de la misma manera que lo están dentro de la parcial.

3.5 Uso de diseños anidados

Es posible que tu aplicación requiera un diseño que difiera ligeramente de tu diseño de aplicación regular para admitir un controlador en particular. En lugar de repetir el diseño principal y editarlo, puedes lograr esto utilizando 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 la página" %></title>
      <%= stylesheet_link_tag "layout" %>
      <style><%= yield :stylesheets %></style>
    </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 :stylesheets do %>
      #top_menu {display: none}
      #right_menu {float: right; background-color: yellow; color: black}
    <% 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 utilizarán el nuevo diseño, ocultando el menú superior y agregando un nuevo menú derecho dentro del div "content".

Hay varias formas de obtener resultados similares con diferentes esquemas de sub-plantillas utilizando esta técnica. Ten en cuenta que no hay límite en los niveles de anidamiento. Se 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 vas a sub-plantillar el diseño de News, puedes reemplazar content_for?(:news_content) ? yield(:news_content) : yield simplemente con yield.

Comentarios

Se te anima a ayudar a mejorar la calidad de esta guía.

Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.

También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.

Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.

Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.