NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.

Visión General del Controlador de Acción

En esta guía, aprenderás cómo funcionan los controladores y cómo encajan en el ciclo de solicitudes de tu aplicación.

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


1 ¿Qué Hace un Controlador?

Action Controller es la C en MVC. Después de que el enrutador ha determinado qué controlador usar para una solicitud, el controlador es responsable de interpretar la solicitud y producir la salida adecuada. Afortunadamente, Action Controller hace la mayor parte del trabajo preliminar por ti y utiliza convenciones inteligentes para hacer esto lo más sencillo posible.

Para la mayoría de las aplicaciones convencionales RESTful, el controlador recibirá la solicitud (esto es invisible para ti como desarrollador), recuperará o guardará datos de un modelo y usará una vista para crear una salida HTML. Si tu controlador necesita hacer las cosas de manera un poco diferente, eso no es un problema, esta es solo la forma más común de que un controlador funcione.

Por lo tanto, se puede pensar en un controlador como un intermediario entre modelos y vistas. Hace que los datos del modelo estén disponibles para la vista, para que pueda mostrar esos datos al usuario, y guarda o actualiza los datos del usuario en el modelo.

NOTA: Para más detalles sobre el proceso de enrutamiento, consulta Enrutamiento de Rails desde el Exterior Hacia Adentro.

2 Convención de Nombres de Controladores

La convención de nomenclatura de controladores en Rails favorece la pluralización de la última palabra en el nombre del controlador, aunque no es estrictamente necesario (por ejemplo, ApplicationController). Por ejemplo, ClientsController es preferible a ClientController, SiteAdminsController es preferible a SiteAdminController o SitesAdminsController, y así sucesivamente.

Seguir esta convención te permitirá usar los generadores de rutas predeterminados (por ejemplo, resources, etc.) sin necesidad de calificar cada :path o :controller, y mantendrá el uso de los ayudantes de rutas nombradas consistente en toda tu aplicación. Consulta Guía de Layouts y Renderizado para más detalles.

NOTA: La convención de nombres de controladores difiere de la convención de nombres de modelos, que se espera que estén nombrados en forma singular.

3 Métodos y Acciones

Un controlador es una clase Ruby que hereda de ApplicationController y tiene métodos como cualquier otra clase. Cuando tu aplicación recibe una solicitud, el enrutamiento determinará qué controlador y acción ejecutar, luego Rails crea una instancia de ese controlador y ejecuta el método con el mismo nombre que la acción.

class ClientsController < ApplicationController
  def new
  end
end

Por ejemplo, si un usuario va a /clients/new en tu aplicación para agregar un nuevo cliente, Rails creará una instancia de ClientsController y llamará a su método new. Ten en cuenta que el método vacío del ejemplo anterior funcionaría bien porque Rails, por defecto, renderizará la vista new.html.erb a menos que la acción indique lo contrario. Al crear un nuevo Client, el método new puede hacer que una variable de instancia @client sea accesible en la vista:

def new
  @client = Client.new
end

La Guía de Layouts y Renderizado explica esto con más detalle.

ApplicationController hereda de ActionController::Base, que define una serie de métodos útiles. Esta guía cubrirá algunos de estos, pero si tienes curiosidad por ver qué hay allí, puedes ver todos ellos en la documentación de la API o en el propio código fuente.

Solo los métodos públicos son invocables como acciones. Es una buena práctica reducir la visibilidad de los métodos (con private o protected) que no están destinados a ser acciones, como métodos auxiliares o filtros.

ADVERTENCIA: Algunos nombres de métodos están reservados por Action Controller. Redefinirlos accidentalmente como acciones, o incluso como métodos auxiliares, podría resultar en un SystemStackError. Si limitas tus controladores solo a acciones de Enrutamiento de Recursos RESTful, no deberías preocuparte por esto.

NOTA: Si debes usar un método reservado como nombre de acción, una solución es usar una ruta personalizada para mapear el nombre del método reservado a tu método de acción no reservado.

4 Parámetros

Probablemente querrás acceder a los datos enviados por el usuario u otros parámetros en tus acciones de controlador. Hay dos tipos de parámetros posibles en una aplicación web. Los primeros son parámetros que se envían como parte de la URL, llamados parámetros de cadena de consulta. La cadena de consulta es todo lo que está después de "?" en la URL. El segundo tipo de parámetro se refiere generalmente como datos POST. Esta información generalmente proviene de un formulario HTML que ha sido completado por el usuario. Se llama datos POST porque solo se puede enviar como parte de una solicitud HTTP POST. Rails no hace distinción entre los parámetros de cadena de consulta y los parámetros POST, y ambos están disponibles en el hash params en tu controlador:

class ClientsController < ApplicationController
  # Esta acción usa parámetros de cadena de consulta porque se ejecuta
  # mediante una solicitud HTTP GET, pero esto no hace ninguna diferencia
  # en cómo se acceden los parámetros. La URL para
  # esta acción se vería así para listar clientes activados:
  # /clients?status=activated
  def index
    if params[:status] == "activated"
      @clients = Client.activated
    else
      @clients = Client.inactivated
    end
  end

  # Esta acción usa parámetros POST. Lo más probable es que provengan
  # de un formulario HTML que el usuario ha enviado. La URL para
  # esta solicitud RESTful será "/clients", y los datos se enviarán
  # como parte del cuerpo de la solicitud.
  def create
    @client = Client.new(params[:client])
    if @client.save
      redirect_to @client
    else
      # Esta línea anula el comportamiento de renderizado predeterminado, que
      # habría sido renderizar la vista "create".
      render "new"
    end
  end
end

4.1 Parámetros Hash y de Array

El hash params no está limitado a claves y valores unidimensionales. Puede contener arrays y hashes anidados. Para enviar un array de valores, agrega un par de corchetes vacíos "[]" al nombre de la clave:

GET /clients?ids[]=1&ids[]=2&ids[]=3

NOTA: La URL real en este ejemplo estará codificada como "/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3" ya que los caracteres "[" y "]" no están permitidos en las URLs. La mayoría de las veces no tienes que preocuparte por esto porque el navegador lo codificará por ti, y Rails lo decodificará automáticamente, pero si alguna vez te encuentras teniendo que enviar esas solicitudes al servidor manualmente, debes tener esto en mente.

El valor de params[:ids] ahora será ["1", "2", "3"]. Ten en cuenta que los valores de los parámetros siempre son cadenas; Rails no intenta adivinar o convertir el tipo.

NOTA: Valores como [nil] o [nil, nil, ...] en params se reemplazan con [] por razones de seguridad por defecto. Consulta la Guía de Seguridad para más información.

Para enviar un hash, incluyes el nombre de la clave dentro de los corchetes:

<form accept-charset="UTF-8" action="/clients" method="post">
  <input type="text" name="client[name]" value="Acme" />
  <input type="text" name="client[phone]" value="12345" />
  <input type="text" name="client[address][postcode]" value="12345" />
  <input type="text" name="client[address][city]" value="Carrot City" />
</form>

Cuando se envía este formulario, el valor de params[:client] será { "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }. Nota el hash anidado en params[:client][:address].

El objeto params actúa como un Hash, pero te permite usar símbolos y cadenas de manera intercambiable como claves.

4.2 Parámetros JSON

Si tu aplicación expone una API, es probable que aceptes parámetros en formato JSON. Si el encabezado "Content-Type" de tu solicitud está configurado como "application/json", Rails cargará automáticamente tus parámetros en el hash params, al cual puedes acceder como lo harías normalmente.

Por ejemplo, si estás enviando este contenido JSON:

{ "company": { "name": "acme", "address": "123 Carrot Street" } }

Tu controlador recibirá params[:company] como { "name" => "acme", "address" => "123 Carrot Street" }.

Además, si has activado config.wrap_parameters en tu inicializador o llamado a wrap_parameters en tu controlador, puedes omitir de manera segura el elemento raíz en el parámetro JSON. En este caso, los parámetros se clonarán y envolverán con una clave elegida basada en el nombre de tu controlador. Así que la solicitud JSON anterior se puede escribir como:

{ "name": "acme", "address": "123 Carrot Street" }

Y, asumiendo que estás enviando los datos a CompaniesController, entonces se envolvería dentro de la clave :company así:

{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }

Puedes personalizar el nombre de la clave o los parámetros específicos que deseas envolver consultando la documentación de la API.

NOTA: El soporte para el análisis de parámetros XML ha sido extraído a una gema llamada actionpack-xml_parser.

4.3 Parámetros de Enrutamiento

El hash params siempre contendrá las claves :controller y :action, pero debes usar los métodos controller_name y action_name en su lugar para acceder a estos valores. Cualquier otro parámetro definido por el enrutamiento, como :id, también estará disponible. Como ejemplo, considera una lista de clientes donde la lista puede mostrar clientes activos o inactivos. Podemos agregar una ruta que capture el parámetro :status en una URL "bonita":

get '/clients/:status', to: 'clients#index', foo: 'bar'

En este caso, cuando un usuario abre la URL /clients/active, params[:status] se establecerá en "active". Cuando se usa esta ruta, params[:foo] también se establecerá en "bar", como si hubiera sido pasado en la cadena de consulta. Tu controlador también recibirá params[:action] como "index" y params[:controller] como "clients".

4.4 Parámetros de Clave Compuesta

Los parámetros de clave compuesta contienen múltiples valores en un solo parámetro. Por esta razón, necesitamos poder extraer cada valor y pasarlos a Active Record. Podemos aprovechar el método extract_value para este caso de uso.

Dado el siguiente controlador:

class BooksController < ApplicationController
  def show
    # Extraer el valor de ID compuesto de los parámetros de URL.
    id = params.extract_value(:id)
    # Encontrar el libro usando el ID compuesto.
    @book = Book.find(id)
    # usar el comportamiento de renderizado predeterminado para renderizar la vista show.
  end
end

Y la siguiente ruta:

get '/books/:id', to: 'books#show'

Cuando un usuario abre la URL /books/4_2, el controlador extraerá el valor de clave compuesta ["4", "2"] y lo pasará a Book.find para renderizar el registro correcto en la vista. El método extract_value puede ser usado para extraer arrays de cualquier parámetro delimitado.

4.5 default_url_options

Puedes establecer parámetros predeterminados globales para la generación de URLs definiendo un método llamado default_url_options en tu controlador. Dicho método debe devolver un hash con los valores predeterminados deseados, cuyas claves deben ser símbolos:

class ApplicationController < ActionController::Base
  def default_url_options
    { locale: I18n.locale }
  end
end

Estas opciones se usarán como punto de partida al generar URLs, por lo que es posible que sean reemplazadas por las opciones pasadas a las llamadas url_for.

Si defines default_url_options en ApplicationController, como en el ejemplo anterior, estos valores predeterminados se usarán para toda la generación de URLs. El método también se puede definir en un controlador específico, en cuyo caso solo afecta a las URLs generadas allí.

En una solicitud dada, el método no se llama realmente para cada URL generada. Por razones de rendimiento, el hash devuelto se almacena en caché, y hay como máximo una invocación por solicitud.

4.6 Parámetros Fuertes

Con los parámetros fuertes, los parámetros del Action Controller están prohibidos para ser usados en asignaciones masivas de Active Model hasta que hayan sido permitidos. Esto significa que tendrás que tomar una decisión consciente sobre qué atributos permitir para la actualización masiva. Esta es una mejor práctica de seguridad para ayudar a prevenir permitir accidentalmente que los usuarios actualicen atributos sensibles del modelo.

Además, los parámetros pueden ser marcados como requeridos y fluirán a través de un flujo de raise/rescue predefinido que resultará en un 400 Bad Request si no se pasan todos los parámetros requeridos.

class PeopleController < ActionController::Base
  # Esto lanzará una excepción ActiveModel::ForbiddenAttributesError
  # porque está usando asignación masiva sin un paso de permiso explícito.
  def create
    Person.create(params[:person])
  end

  # Esto pasará sin problemas siempre y cuando haya una clave person
  # en los parámetros, de lo contrario lanzará una excepción
  # ActionController::ParameterMissing, que será capturada por
  # ActionController::Base y convertida en un error 400 Bad Request.
  def update
    person = current_account.people.find(params[:id])
    person.update!(person_params)
    redirect_to person
  end

  private
    # Usar un método privado para encapsular los parámetros permisibles
    # es solo un buen patrón ya que podrás reutilizar la misma
    # lista de permisos entre create y update. Además, puedes especializar
    # este método con verificación por usuario de atributos permisibles.
    def person_params
      params.require(:person).permit(:name, :age)
    end
end

4.6.1 Valores Escalares Permitidos

Llamar a permit como:

params.permit(:id)

permite la inclusión de la clave especificada (:id) si aparece en params y tiene un valor escalar permitido asociado. De lo contrario, la clave será filtrada, por lo que no se pueden inyectar arrays, hashes u otros objetos.

Los tipos escalares permitidos son String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO, ActionDispatch::Http::UploadedFile, y Rack::Test::UploadedFile.

Para declarar que el valor en params debe ser un array de valores escalares permitidos, mapea la clave a un array vacío:

params.permit(id: [])

A veces no es posible o conveniente declarar las claves válidas de un parámetro hash o su estructura interna. Simplemente mapea a un hash vacío:

params.permit(preferences: {})

pero ten cuidado porque esto abre la puerta a entradas arbitrarias. En este caso, permit asegura que los valores en la estructura devuelta sean escalares permitidos y filtra cualquier otra cosa.

Para permitir un hash completo de parámetros, se puede usar el método permit!:

params.require(:log_entry).permit!

Esto marca el hash de parámetros :log_entry y cualquier sub-hash de él como permitido y no verifica los escalares permitidos, se acepta cualquier cosa. Se debe tener extremo cuidado al usar permit!, ya que permitirá que todos los atributos del modelo presentes y futuros sean asignados masivamente.

4.6.2 Parámetros Anidados

También puedes usar permit en parámetros anidados, como:

params.permit(:name, { emails: [] },
              friends: [ :name,
                         { family: [ :name ], hobbies: [] }])

Esta declaración permite los atributos name, emails, y friends. Se espera que emails sea un array de valores escalares permitidos, y que friends sea un array de recursos con atributos específicos: deben tener un atributo name (se permiten cualquier valor escalar permitido), un atributo hobbies como un array de valores escalares permitidos, y un atributo family que está restringido a tener un name (también se permiten aquí cualquier valor escalar permitido).

4.6.3 Más Ejemplos

Es posible que también desees usar los atributos permitidos en tu acción new. Esto plantea el problema de que no puedes usar require en la clave raíz porque, normalmente, no existe cuando llamas a new:

# usando `fetch` puedes proporcionar un valor predeterminado y usar
# la API de Parámetros Fuertes desde allí.
params.fetch(:blog, {}).permit(:title, :author)

El método de clase del modelo accepts_nested_attributes_for te permite actualizar y destruir registros asociados. Esto se basa en los parámetros id y _destroy:

# permite :id y :_destroy
params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])

Los hashes con claves enteras se tratan de manera diferente, y puedes declarar los atributos como si fueran hijos directos. Obtienes estos tipos de parámetros cuando usas accepts_nested_attributes_for en combinación con una asociación has_many:

# Para permitir los siguientes datos:
# {"book" => {"title" => "Some Book",
#             "chapters_attributes" => { "1" => {"title" => "First Chapter"},
#                                        "2" => {"title" => "Second Chapter"}}}}

params.require(:book).permit(:title, chapters_attributes: [:title])

Imagina un escenario donde tienes parámetros que representan un nombre de producto, y un hash de datos arbitrarios asociados con ese producto, y quieres permitir el atributo de nombre del producto y también todo el hash de datos:

def product_params
  params.require(:product).permit(:name, data: {})
end

4.6.4 Fuera del Alcance de los Parámetros Fuertes

La API de parámetros fuertes fue diseñada con los casos de uso más comunes en mente. No está destinada a ser una solución mágica para manejar todos tus problemas de filtrado de parámetros. Sin embargo, puedes mezclar fácilmente la API con tu propio código para adaptarte a tu situación.

5 Sesión

Tu aplicación tiene una sesión para cada usuario en la que puedes almacenar pequeñas cantidades de datos que se mantendrán entre solicitudes. La sesión solo está disponible en el controlador y la vista y puede usar uno de varios mecanismos de almacenamiento diferentes:

Todos los almacenes de sesión usan una cookie para almacenar un ID único para cada sesión (debes usar una cookie, Rails no te permitirá pasar el ID de sesión en la URL ya que esto es menos seguro).

Para la mayoría de los almacenes, este ID se usa para buscar los datos de la sesión en el servidor, por ejemplo, en una tabla de base de datos. Hay una excepción, y esa es el almacén de sesiones predeterminado y recomendado: el CookieStore, que almacena todos los datos de la sesión en la propia cookie (el ID sigue estando disponible para ti si lo necesitas). Esto tiene la ventaja de ser muy ligero, y no requiere ninguna configuración en una nueva aplicación para usar la sesión. Los datos de la cookie están firmados criptográficamente para hacerlos a prueba de manipulaciones. Y también están encriptados para que cualquiera con acceso a ellos no pueda leer su contenido. (Rails no lo aceptará si ha sido editado).

El CookieStore puede almacenar alrededor de 4 kB de datos, mucho menos que los otros, pero esto suele ser suficiente. Se desaconseja almacenar grandes cantidades de datos en la sesión sin importar qué almacén de sesión use tu aplicación. Debes evitar especialmente almacenar objetos complejos (como instancias de modelo) en la sesión, ya que el servidor podría no ser capaz de reensamblarlos entre solicitudes, lo que resultará en un error.

Si las sesiones de tu usuario no almacenan datos críticos o no necesitan estar disponibles durante largos períodos (por ejemplo, si solo usas el flash para mensajes), puedes considerar usar ActionDispatch::Session::CacheStore. Esto almacenará las sesiones usando la implementación de caché que hayas configurado para tu aplicación. La ventaja de esto es que puedes usar tu infraestructura de caché existente para almacenar sesiones sin requerir ninguna configuración o administración adicional. La desventaja, por supuesto, es que las sesiones serán efímeras y podrían desaparecer en cualquier momento.

Lee más sobre el almacenamiento de sesiones en la Guía de Seguridad.

Si necesitas un mecanismo de almacenamiento de sesión diferente, puedes cambiarlo en un inicializador:

Rails.application.config.session_store :cache_store

Consulta config.session_store en la guía de configuración para más información.

Rails configura una clave de sesión (el nombre de la cookie) al firmar los datos de la sesión. Estos también se pueden cambiar en un inicializador:

# Asegúrate de reiniciar tu servidor cuando modifiques este archivo.
Rails.application.config.session_store :cookie_store, key: '_your_app_session'

También puedes pasar una clave :domain y especificar el nombre de dominio para la cookie:

# Asegúrate de reiniciar tu servidor cuando modifiques este archivo.
Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"

Rails configura (para el CookieStore) una clave secreta utilizada para firmar los datos de la sesión en config/credentials.yml.enc. Esto se puede cambiar con bin/rails credentials:edit.

# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Usado como la clave secreta base para todos los MessageVerifiers en Rails, incluyendo el que protege las cookies.
secret_key_base: 492f...

NOTA: Cambiar el secret_key_base cuando se usa el CookieStore invalidará todas las sesiones existentes.

5.1 Accediendo a la Sesión

En tu controlador, puedes acceder a la sesión a través del método de instancia session.

NOTA: Las sesiones se cargan de manera perezosa. Si no accedes a las sesiones en el código de tu acción, no se cargarán. Por lo tanto, nunca necesitarás deshabilitar las sesiones, simplemente no acceder a ellas hará el trabajo.

Los valores de la sesión se almacenan usando pares clave/valor como un hash:

class ApplicationController < ActionController::Base
  private
    # Encuentra al Usuario con el ID almacenado en la sesión con la clave
    # :current_user_id Esta es una forma común de manejar el inicio de sesión del usuario en
    # una aplicación Rails; iniciar sesión establece el valor de la sesión y
    # cerrar sesión lo elimina.
    def current_user
      @_current_user ||= session[:current_user_id] &&
        User.find_by(id: session[:current_user_id])
    end
end

Para almacenar algo en la sesión, simplemente asígnalo a la clave como un hash:

class LoginsController < ApplicationController
  # "Crear" un inicio de sesión, también conocido como "iniciar sesión al usuario"
  def create
    if user = User.authenticate(params[:username], params[:password])
      # Guarda el ID de usuario en la sesión para que pueda usarse en
      # solicitudes posteriores
      session[:current_user_id] = user.id
      redirect_to root_url
    end
  end
end

Para eliminar algo de la sesión, elimina el par clave/valor:

class LoginsController < ApplicationController
  # "Eliminar" un inicio de sesión, también conocido como "cerrar sesión al usuario"
  def destroy
    # Elimina el id de usuario de la sesión
    session.delete(:current_user_id)
    # Limpia el usuario actual memoizado
    @_current_user = nil
    redirect_to root_url, status: :see_other
  end
end

Para restablecer toda la sesión, usa reset_session.

5.2 El Flash

El flash es una parte especial de la sesión que se borra con cada solicitud. Esto significa que los valores almacenados allí solo estarán disponibles en la siguiente solicitud, lo cual es útil para pasar mensajes de error, etc.

El flash se accede a través del método flash. Al igual que la sesión, el flash se representa como un hash.

Usemos el acto de cerrar sesión como ejemplo. El controlador puede enviar un mensaje que se mostrará al usuario en la siguiente solicitud:

class LoginsController < ApplicationController
  def destroy
    session.delete(:current_user_id)
    flash[:notice] = "Has cerrado sesión exitosamente."
    redirect_to root_url, status: :see_other
  end
end

Nota que también es posible asignar un mensaje flash como parte de la redirección. Puedes asignar :notice, :alert o el :flash de propósito general:

redirect_to root_url, notice: "Has cerrado sesión exitosamente."
redirect_to root_url, alert: "¡Estás atrapado aquí!"
redirect_to root_url, flash: { referral_code: 1234 }

La acción destroy redirige a la root_url de la aplicación, donde se mostrará el mensaje. Nota que depende completamente de la siguiente acción decidir qué, si acaso, hará con lo que la acción anterior puso en el flash. Es convencional mostrar cualquier alerta de error o aviso del flash en el layout de la aplicación:

<html>
  <!-- <head/> -->
  <body>
    <% flash.each do |name, msg| -%>
      <%= content_tag :div, msg, class: name %>
    <% end -%>

    <!-- más contenido -->
  </body>
</html>

De esta manera, si una acción establece un aviso o un mensaje de alerta, el layout lo mostrará automáticamente.

Puedes pasar cualquier cosa que la sesión pueda almacenar; no estás limitado a avisos y alertas:

<% if flash[:just_signed_up] %>
  <p class="welcome">¡Bienvenido a nuestro sitio!</p>
<% end %>

Si deseas que un valor flash se mantenga para otra solicitud, usa flash.keep:

class MainController < ApplicationController
  # Digamos que esta acción corresponde a root_url, pero deseas
  # que todas las solicitudes aquí sean redirigidas a UsersController#index.
  # Si una acción establece el flash y redirige aquí, los valores
  # normalmente se perderían cuando ocurra otra redirección, pero puedes
  # usar 'keep' para hacer que persista para otra solicitud.
  def index
    # Persistirá todos los valores flash.
    flash.keep

    # También puedes usar una clave para mantener solo algún tipo de valor.
    # flash.keep(:notice)
    redirect_to users_url
  end
end

5.2.1 flash.now

Por defecto, agregar valores al flash los hará disponibles para la próxima solicitud, pero a veces puedes querer acceder a esos valores en la misma solicitud. Por ejemplo, si la acción create no logra guardar un recurso, y renderizas la plantilla new directamente, eso no resultará en una nueva solicitud, pero es posible que aún desees mostrar un mensaje usando el flash. Para hacer esto, puedes usar flash.now de la misma manera que usas el flash normal:

class ClientsController < ApplicationController
  def create
    @client = Client.new(client_params)
    if @client.save
      # ...
    else
      flash.now[:error] = "No se pudo guardar el cliente"
      render action: "new"
    end
  end
end

6 Cookies

Tu aplicación puede almacenar pequeñas cantidades de datos en el cliente, llamadas cookies, que se mantendrán entre solicitudes e incluso sesiones. Rails proporciona un acceso fácil a las cookies a través del método cookies, que, al igual que la session, funciona como un hash:

class CommentsController < ApplicationController
  def new
    # Auto-completar el nombre del comentarista si se ha almacenado en una cookie
    @comment = Comment.new(author: cookies[:commenter_name])
  end

  def create
    @comment = Comment.new(comment_params)
    if @comment.save
      flash[:notice] = "¡Gracias por tu comentario!"
      if params[:remember_name]
        # Recordar el nombre del comentarista.
        cookies[:commenter_name] = @comment.author
      else
        # Eliminar la cookie del nombre del comentarista, si la hay.
        cookies.delete(:commenter_name)
      end
      redirect_to @comment.article
    else
      render action: "new"
    end
  end
end

Ten en cuenta que, mientras que para los valores de sesión puedes establecer la clave en nil, para eliminar un valor de cookie debes usar cookies.delete(:key).

Rails también proporciona un tarro de cookies firmado y un tarro de cookies encriptado para almacenar datos sensibles. El tarro de cookies firmado añade una firma criptográfica a los valores de las cookies para proteger su integridad. El tarro de cookies encriptado encripta los valores además de firmarlos, para que no puedan ser leídos por el usuario final. Consulta la documentación de la API para más detalles.

Estos tarros de cookies especiales usan un serializador para serializar los valores asignados en cadenas y deserializarlos en objetos Ruby al leerlos. Puedes especificar qué serializador usar a través de config.action_dispatch.cookies_serializer.

El serializador predeterminado para nuevas aplicaciones es :json. Ten en cuenta que JSON tiene soporte limitado para redondear objetos Ruby. Por ejemplo, los objetos Date, Time y Symbol (incluidas las claves de Hash) se serializarán y deserializarán en Strings:

class CookiesController < ApplicationController
  def set_cookie
    cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014
    redirect_to action: 'read_cookie'
  end

  def read_cookie
    cookies.encrypted[:expiration_date] # => "2014-03-20"
  end
end

Si necesitas almacenar estos o más objetos complejos, es posible que necesites convertir manualmente sus valores al leerlos en solicitudes posteriores.

Si usas el almacén de sesión de cookies, lo anterior también se aplica al hash session y flash.

7 Renderizado

ActionController hace que el renderizado de datos HTML, XML o JSON sea sencillo. Si has generado un controlador usando scaffolding, se vería algo así:

class UsersController < ApplicationController
  def index
    @users = User.all
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render xml: @users }
      format.json { render json: @users }
    end
  end
end

Puedes notar en el código anterior que estamos usando render xml: @users, no render xml: @users.to_xml. Si el objeto no es una cadena, Rails invocará automáticamente to_xml por nosotros.

Puedes aprender más sobre el renderizado en la Guía de Layouts y Renderizado.

8 Callbacks de Acción

Los callbacks de acción son métodos que se ejecutan "antes", "después" o "alrededor" de una acción del controlador.

Los callbacks de acción se heredan, por lo que si configuras uno en un ApplicationController, se ejecutará en cada controlador de tu aplicación.

Los callbacks de acción "antes" se registran a través de before_action. Pueden detener el ciclo de solicitud. Un callback de acción "antes" común es uno que requiere que un usuario esté registrado para que una acción se ejecute. Puedes definir el método de esta manera:

class ApplicationController < ActionController::Base
  before_action :require_login

  private
    def require_login
      unless logged_in?
        flash[:error] = "Debes estar registrado para acceder a esta sección"
        redirect_to new_login_url # detiene el ciclo de solicitud
      end
    end
end

El método simplemente almacena un mensaje de error en el flash y redirige al formulario de inicio de sesión si el usuario no está registrado. Si un callback de acción "antes" renderiza o redirige, la acción del controlador no se ejecutará. Si hay callbacks de acción adicionales programados para ejecutarse después de ese, también se cancelan.

En este ejemplo, el callback de acción se agrega a ApplicationController y, por lo tanto, todos los controladores en la aplicación lo heredan. Esto hará que todo en la aplicación requiera que el usuario esté registrado para usarlo. Por razones obvias (¡el usuario no podría iniciar sesión en primer lugar!), no todos los controladores o acciones deberían requerir esto. Puedes evitar que este callback de acción se ejecute antes de acciones particulares con skip_before_action:

class LoginsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end

Ahora, las acciones new y create de LoginsController funcionarán como antes sin requerir que el usuario esté registrado. La opción :only se usa para omitir este callback de acción solo para estas acciones, y también hay una opción :except que funciona al revés. Estas opciones también se pueden usar al agregar callbacks de acción, por lo que puedes agregar un callback que solo se ejecute para acciones seleccionadas en primer lugar.

NOTA: Llamar al mismo callback de acción varias veces con diferentes opciones no funcionará, ya que la última definición del callback de acción sobrescribirá las anteriores.

8.1 Callbacks de Acción Después y Alrededor

Además del callback de acción "antes", también puedes ejecutar callbacks de acción después de que se haya ejecutado una acción del controlador, o tanto antes como después.

Los callbacks de acción "después" se registran a través de after_action. Son similares a los callbacks de acción "antes", pero debido a que la acción del controlador ya se ha ejecutado, tienen acceso a los datos de respuesta que están a punto de ser enviados al cliente. Obviamente, los callbacks de acción "después" no pueden detener la ejecución de la acción. Ten en cuenta que los callbacks de acción "después" se ejecutan solo después de una acción de controlador exitosa, pero no cuando se produce una excepción en el ciclo de solicitud.

Los callbacks de acción "alrededor" se registran a través de around_action. Son responsables de ejecutar sus acciones asociadas al ceder, similar a cómo funcionan los middlewares de Rack.

Por ejemplo, en un sitio web donde los cambios tienen un flujo de trabajo de aprobación, un administrador podría previsualizarlos fácilmente aplicándolos dentro de una transacción:

class ChangesController < ApplicationController
  around_action :wrap_in_transaction, only: :show

  private
    def wrap_in_transaction
      ActiveRecord::Base.transaction do
        begin
          yield
        ensure
          raise ActiveRecord::Rollback
        end
      end
    end
end

Ten en cuenta que un callback de acción "alrededor" también envuelve la renderización. En particular, en el ejemplo anterior, si la propia vista lee de la base de datos (por ejemplo, a través de un scope), lo hará dentro de la transacción y, por lo tanto, presentará los datos para previsualizar.

Puedes optar por no ceder y construir la respuesta tú mismo, en cuyo caso la acción del controlador no se ejecutará.

8.2 Otras Formas de Usar Callbacks de Acción

Si bien la forma más común de usar callbacks de acción es creando métodos privados y usando before_action, after_action o around_action para agregarlos, hay otras dos formas de hacer lo mismo.

La primera es usar un bloque directamente con los métodos *_action. El bloque recibe el controlador como argumento. El callback de acción require_login del ejemplo anterior podría reescribirse para usar un bloque:

class ApplicationController < ActionController::Base
  before_action do |controller|
    unless controller.send(:logged_in?)
      flash[:error] = "Debes estar registrado para acceder a esta sección"
      redirect_to new_login_url
    end
  end
end

Ten en cuenta que el callback de acción, en este caso, usa send porque el método logged_in? es privado, y el callback de acción no se ejecuta en el ámbito del controlador. Esta no es la forma recomendada de implementar este callback de acción en particular, pero en casos más simples, podría ser útil.

Específicamente para around_action, el bloque también cede en la action:

around_action { |_controller, action| time(&action) }

La segunda forma es usar una clase (en realidad, cualquier objeto que responda a los métodos correctos servirá) para manejar la acción del callback. Esto es útil en casos que son más complejos y no pueden ser implementados de una manera legible y reutilizable usando los otros dos métodos. Como ejemplo, podrías reescribir el callback de acción de inicio de sesión nuevamente para usar una clase:

class ApplicationController < ActionController::Base
  before_action LoginActionCallback
end

class LoginActionCallback
  def self.before(controller)
    unless controller.send(:logged_in?)
      controller.flash[:error] = "Debes estar registrado para acceder a esta sección"
      controller.redirect_to controller.new_login_url
    end
  end
end

Nuevamente, este no es un ejemplo ideal para este callback de acción, porque no se ejecuta en el ámbito del controlador, pero recibe el controlador como argumento. La clase debe implementar un método con el mismo nombre que el callback de acción, por lo que para el callback de acción before_action, la clase debe implementar un método before, y así sucesivamente. El método around debe ceder para ejecutar la acción.

9 Protección Contra Falsificación de Solicitudes

La falsificación de solicitudes entre sitios es un tipo de ataque en el que un sitio engaña a un usuario para que realice solicitudes en otro sitio, posiblemente agregando, modificando o eliminando datos en ese sitio sin el conocimiento o permiso del usuario.

El primer paso para evitar esto es asegurarse de que todas las acciones "destructivas" (crear, actualizar y destruir) solo se puedan acceder con solicitudes que no sean GET. Si estás siguiendo las convenciones RESTful ya estás haciendo esto. Sin embargo, un sitio malicioso aún puede enviar una solicitud que no sea GET a tu sitio con bastante facilidad, y ahí es donde entra la protección contra la falsificación de solicitudes. Como su nombre indica, protege de solicitudes falsificadas.

La forma en que se hace esto es agregar un token no adivinable que solo es conocido por tu servidor a cada solicitud. De esta manera, si llega una solicitud sin el token adecuado, se le negará el acceso.

Si generas un formulario como este:

<%= form_with model: @user do |form| %>
  <%= form.text_field :username %>
  <%= form.text_field :password %>
<% end %>

Verás cómo se agrega el token como un campo oculto:

<form accept-charset="UTF-8" action="/users/1" method="post">
<input type="hidden"
       value="67250ab105eb5ad10851c00a5621854a23af5489"
       name="authenticity_token"/>
<!-- campos -->
</form>

Rails agrega este token a cada formulario que se genera usando los ayudantes de formulario, por lo que la mayoría de las veces no tienes que preocuparte por ello. Si estás escribiendo un formulario manualmente o necesitas agregar el token por otra razón, está disponible a través del método form_authenticity_token:

El form_authenticity_token genera un token de autenticidad válido. Eso es útil en lugares donde Rails no lo agrega automáticamente, como en llamadas Ajax personalizadas.

La Guía de Seguridad tiene más sobre esto, y muchos otros temas relacionados con la seguridad que debes conocer al desarrollar una aplicación web.

10 Los Objetos de Solicitud y Respuesta

En cada controlador, hay dos métodos de acceso que apuntan a los objetos de solicitud y respuesta asociados con el ciclo de solicitud que se está ejecutando actualmente. El método request contiene una instancia de ActionDispatch::Request y el método response devuelve un objeto de respuesta que representa lo que se va a enviar de vuelta al cliente.

10.1 El Objeto request

El objeto de solicitud contiene mucha información útil sobre la solicitud que viene del cliente. Para obtener una lista completa de los métodos disponibles, consulta la documentación de la API de Rails y la Documentación de Rack. Entre las propiedades a las que puedes acceder en este objeto están:

Propiedad de request Propósito
host El nombre de host utilizado para esta solicitud.
domain(n=2) Los primeros n segmentos del nombre de host, comenzando desde la derecha (el TLD).
format El tipo de contenido solicitado por el cliente.
method El método HTTP utilizado para la solicitud.
get?, post?, patch?, put?, delete?, head? Devuelve verdadero si el método HTTP es GET/POST/PATCH/PUT/DELETE/HEAD.
headers Devuelve un hash que contiene los encabezados asociados con la solicitud.
port El número de puerto (entero) utilizado para la solicitud.
protocol Devuelve una cadena que contiene el protocolo utilizado más "://", por ejemplo "http://".
query_string La parte de la cadena de consulta de la URL, es decir, todo lo que está después de "?".
remote_ip La dirección IP del cliente.
url La URL completa utilizada para la solicitud.

10.1.1 path_parameters, query_parameters, y request_parameters

Rails recopila todos los parámetros enviados junto con la solicitud en el hash params, ya sea que se envíen como parte de la cadena de consulta o del cuerpo de la publicación. El objeto de solicitud tiene tres métodos de acceso que te dan acceso a estos parámetros dependiendo de dónde provienen. El hash query_parameters contiene parámetros que se enviaron como parte de la cadena de consulta, mientras que el hash request_parameters contiene parámetros enviados como parte del cuerpo de la publicación. El hash path_parameters contiene parámetros que fueron reconocidos por el enrutamiento como parte de la ruta que lleva a este controlador y acción en particular.

10.2 El Objeto response

El objeto de respuesta no se usa generalmente directamente, pero se construye durante la ejecución de la acción y la renderización de los datos que se están enviando de vuelta al usuario, pero a veces, como en un callback de acción posterior, puede ser útil acceder a la respuesta directamente. Algunos de estos métodos de acceso también tienen setters, lo que te permite cambiar sus valores. Para obtener una lista completa de los métodos disponibles, consulta la documentación de la API de Rails y la Documentación de Rack.

Propiedad de response Propósito
body Esta es la cadena de datos que se envía de vuelta al cliente. Generalmente es HTML.
status El código de estado HTTP para la respuesta, como 200 para una solicitud exitosa o 404 para archivo no encontrado.
location La URL a la que se está redirigiendo al cliente, si la hay.
content_type El tipo de contenido de la respuesta.
charset El conjunto de caracteres que se está utilizando para la respuesta. El valor predeterminado es "utf-8".
headers Encabezados utilizados para la respuesta.

10.2.1 Estableciendo Encabezados Personalizados

Si deseas establecer encabezados personalizados para una respuesta, entonces response.headers es el lugar para hacerlo. El atributo headers es un hash que mapea nombres de encabezados a sus valores, y Rails configurará algunos de ellos automáticamente. Si deseas agregar o cambiar un encabezado, simplemente asígnalo a response.headers de esta manera:

response.headers["Content-Type"] = "application/pdf"

NOTA: En el caso anterior, tendría más sentido usar directamente el setter content_type.

11 Autenticaciones HTTP

Rails viene con tres mecanismos de autenticación HTTP incorporados:

  • Autenticación Básica
  • Autenticación Digest
  • Autenticación de Token

11.1 Autenticación Básica HTTP

La autenticación básica HTTP es un esquema de autenticación que es compatible con la mayoría de los navegadores y otros clientes HTTP. Como ejemplo, considera una sección de administración que solo estará disponible ingresando un nombre de usuario y una contraseña en la ventana de diálogo básica HTTP del navegador. Usar la autenticación incorporada solo requiere que uses un método, http_basic_authenticate_with.

class AdminsController < ApplicationController
  http_basic_authenticate_with name: "humbaba", password: "5baa61e4"
end

Con esto en su lugar, puedes crear controladores con espacio de nombres que hereden de AdminsController. El callback de acción se ejecutará así para todas las acciones en esos controladores, protegiéndolos con autenticación básica HTTP.

11.2 Autenticación Digest HTTP

La autenticación digest HTTP es superior a la autenticación básica ya que no requiere que el cliente envíe una contraseña sin cifrar a través de la red (aunque la autenticación básica HTTP es segura a través de HTTPS). Usar la autenticación digest con Rails solo requiere usar un método, authenticate_or_request_with_http_digest.

class AdminsController < ApplicationController
  USERS = { "lifo" => "world" }

  before_action :authenticate

  private
    def authenticate
      authenticate_or_request_with_http_digest do |username|
        USERS[username]
      end
    end
end

Como se ve en el ejemplo anterior, el bloque authenticate_or_request_with_http_digest toma solo un argumento: el nombre de usuario. Y el bloque devuelve la contraseña. Devolver false o nil de authenticate_or_request_with_http_digest causará un fallo en la autenticación.

11.3 Autenticación de Token HTTP

La autenticación de token HTTP es un esquema para habilitar el uso de tokens Bearer en el encabezado HTTP Authorization. Existen muchos formatos de token disponibles y describirlos está fuera del alcance de este documento.

Como ejemplo, supongamos que deseas usar un token de autenticación que ha sido emitido de antemano para realizar autenticación y acceso. Implementar la autenticación de token con Rails solo requiere usar un método, authenticate_or_request_with_http_token.

class PostsController < ApplicationController
  TOKEN = "secret"

  before_action :authenticate

  private
    def authenticate
      authenticate_or_request_with_http_token do |token, options|
        ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
      end
    end
end

Como se ve en el ejemplo anterior, el bloque authenticate_or_request_with_http_token toma dos argumentos: el token y un Hash que contiene las opciones que fueron analizadas del encabezado HTTP Authorization. El bloque debe devolver true si la autenticación es exitosa. Devolver false o nil en él causará un fallo en la autenticación.

12 Transmisión y Descarga de Archivos

A veces puedes querer enviar un archivo al usuario en lugar de renderizar una página HTML. Todos los controladores en Rails tienen los métodos send_data y send_file, que transmitirán datos al cliente. send_file es un método de conveniencia que te permite proporcionar el nombre de un archivo en el disco, y transmitirá el contenido de ese archivo por ti.

Para transmitir datos al cliente, usa send_data:

require "prawn"
class ClientsController < ApplicationController
  # Genera un documento PDF con información sobre el cliente y
  # lo devuelve. El usuario obtendrá el PDF como una descarga de archivo.
  def download_pdf
    client = Client.find(params[:id])
    send_data generate_pdf(client),
              filename: "#{client.name}.pdf",
              type: "application/pdf"
  end

  private
    def generate_pdf(client)
      Prawn::Document.new do
        text client.name, align: :center
        text "Dirección: #{client.address}"
        text "Email: #{client.email}"
      end.render
    end
end

La acción download_pdf en el ejemplo anterior llamará a un método privado que realmente genera el documento PDF y lo devuelve como una cadena. Esta cadena luego se transmitirá al cliente como una descarga de archivo, y se sugerirá un nombre de archivo al usuario. A veces, cuando transmites archivos al usuario, es posible que no desees que descarguen el archivo. Toma las imágenes, por ejemplo, que pueden ser incrustadas en páginas HTML. Para decirle al navegador que un archivo no está destinado a ser descargado, puedes establecer la opción :disposition en "inline". El valor opuesto y predeterminado para esta opción es "attachment".

12.1 Enviando Archivos

Si deseas enviar un archivo que ya existe en el disco, usa el método send_file.

class ClientsController < ApplicationController
  # Transmite un archivo que ya ha sido generado y almacenado en el disco.
  def download_pdf
    client = Client.find(params[:id])
    send_file("#{Rails.root}/files/clients/#{client.id}.pdf",
              filename: "#{client.name}.pdf",
              type: "application/pdf")
  end
end

Esto leerá y transmitirá el archivo 4 kB a la vez, evitando cargar todo el archivo en la memoria de una vez. Puedes desactivar la transmisión con la opción :stream o ajustar el tamaño del bloque con la opción :buffer_size.

Si no se especifica :type, se adivinará a partir de la extensión del archivo especificada en :filename. Si el tipo de contenido no está registrado para la extensión, se usará application/octet-stream.

ADVERTENCIA: Ten cuidado al usar datos provenientes del cliente (params, cookies, etc.) para ubicar el archivo en el disco, ya que esto es un riesgo de seguridad que podría permitir a alguien acceder a archivos a los que no deberían acceder.

CONSEJO: No se recomienda que transmitas archivos estáticos a través de Rails si puedes mantenerlos en una carpeta pública en tu servidor web. Es mucho más eficiente permitir que el usuario descargue el archivo directamente usando Apache u otro servidor web, evitando que la solicitud pase innecesariamente por toda la pila de Rails.

12.2 Descargas RESTful

Aunque send_data funciona bien, si estás creando una aplicación RESTful, generalmente no es necesario tener acciones separadas para descargas de archivos. En la terminología REST, el archivo PDF del ejemplo anterior puede considerarse solo otra representación del recurso cliente. Rails proporciona una forma elegante de hacer descargas "RESTful". Aquí está cómo puedes reescribir el ejemplo para que la descarga del PDF sea parte de la acción show, sin ninguna transmisión:

class ClientsController < ApplicationController
  # El usuario puede solicitar recibir este recurso como HTML o PDF.
  def show
    @client = Client.find(params[:id])

    respond_to do |format|
      format.html
      format.pdf { render pdf: generate_pdf(@client) }
    end
  end
end

Para que este ejemplo funcione, debes agregar el tipo MIME PDF a Rails. Esto se puede hacer agregando la siguiente línea al archivo config/initializers/mime_types.rb:

Mime::Type.register "application/pdf", :pdf

NOTA: Los archivos de configuración no se recargan en cada solicitud, por lo que debes reiniciar el servidor para que sus cambios surtan efecto.

Ahora el usuario puede solicitar obtener una versión en PDF de un cliente simplemente agregando ".pdf" a la URL:

GET /clients/1.pdf

12.3 Transmisión en Vivo de Datos Arbitrarios

Rails te permite transmitir más que solo archivos. De hecho, puedes transmitir cualquier cosa que desees en un objeto de respuesta. El módulo ActionController::Live te permite crear una conexión persistente con un navegador. Usando este módulo, podrás enviar datos arbitrarios al navegador en puntos específicos en el tiempo.

12.3.1 Incorporando Transmisión en Vivo

Incluir ActionController::Live dentro de tu clase de controlador proporcionará a todas las acciones dentro del controlador la capacidad de transmitir datos. Puedes mezclar el módulo de esta manera:

class MyController < ActionController::Base
  include ActionController::Live

  def stream
    response.headers['Content-Type'] = 'text/event-stream'
    100.times {
      response.stream.write "hello world\n"
      sleep 1
    }
  ensure
    response.stream.close
  end
end

El código anterior mantendrá una conexión persistente con el navegador y enviará 100 mensajes de "hello world\n", cada uno con un segundo de diferencia.

Hay un par de cosas a notar en el ejemplo anterior. Necesitamos asegurarnos de cerrar el flujo de respuesta. Olvidar cerrar el flujo dejará el socket abierto para siempre. También tenemos que establecer el tipo de contenido en text/event-stream antes de escribir en el flujo de respuesta. Esto se debe a que los encabezados no pueden ser escritos después de que la respuesta ha sido comprometida (cuando response.committed? devuelve un valor verdadero), lo cual ocurre cuando escribes o comprometes el flujo de respuesta.

12.3.2 Ejemplo de Uso

Supongamos que estás haciendo una máquina de Karaoke, y un usuario quiere obtener la letra de una canción en particular. Cada Song tiene un número particular de líneas y cada línea toma tiempo num_beats para terminar de cantar.

Si quisiéramos devolver las letras en estilo Karaoke (solo enviando la línea cuando el cantante ha terminado la línea anterior), entonces podríamos usar ActionController::Live de la siguiente manera:

class LyricsController < ActionController::Base
  include ActionController::Live

  def show
    response.headers['Content-Type'] = 'text/event-stream'
    song = Song.find(params[:id])

    song.each do |line|
      response.stream.write line.lyrics
      sleep line.num_beats
    end
  ensure
    response.stream.close
  end
end

El código anterior envía la siguiente línea solo después de que el cantante ha completado la línea anterior.

12.3.3 Consideraciones de Transmisión

La transmisión de datos arbitrarios es una herramienta extremadamente poderosa. Como se muestra en los ejemplos anteriores, puedes elegir cuándo y qué enviar a través de un flujo de respuesta. Sin embargo, también debes tener en cuenta las siguientes cosas:

  • Cada flujo de respuesta crea un nuevo hilo y copia las variables locales del hilo del hilo original. Tener demasiadas variables locales del hilo puede impactar negativamente en el rendimiento. De manera similar, un gran número de hilos también puede afectar el rendimiento.
  • No cerrar el flujo de respuesta dejará el socket correspondiente abierto para siempre. Asegúrate de llamar a close siempre que estés usando un flujo de respuesta.
  • Los servidores WEBrick almacenan en búfer todas las respuestas, y por lo tanto incluir ActionController::Live no funcionará. Debes usar un servidor web que no almacene en búfer automáticamente las respuestas.

13 Filtrado de Registro

Rails mantiene un archivo de registro para cada entorno en la carpeta log. Estos son extremadamente útiles al depurar lo que realmente está sucediendo en tu aplicación, pero en una aplicación en vivo es posible que no desees que cada bit de información se almacene en el archivo de registro.

13.1 Filtrado de Parámetros

Puedes filtrar parámetros de solicitud sensibles de tus archivos de registro agregándolos a config.filter_parameters en la configuración de la aplicación. Estos parámetros serán marcados como [FILTERED] en el registro.

config.filter_parameters << :password

NOTA: Los parámetros proporcionados serán filtrados por expresión regular de coincidencia parcial. Rails agrega una lista de filtros predeterminados, incluidos :passw, :secret, y :token, en el inicializador apropiado (initializers/filter_parameter_logging.rb) para manejar parámetros típicos de la aplicación como password, password_confirmation y my_token.

13.2 Filtrado de Redirecciones

A veces es deseable filtrar de los archivos de registro algunas ubicaciones sensibles a las que tu aplicación se está redirigiendo. Puedes hacer eso usando la opción de configuración config.filter_redirect:

config.filter_redirect << 's3.amazonaws.com'

Puedes configurarlo como una Cadena, una Expresión Regular o un array de ambas.

config.filter_redirect.concat ['s3.amazonaws.com', /private_path/]

Las URLs coincidentes serán reemplazadas con '[FILTERED]'. Sin embargo, si solo deseas filtrar los parámetros, no las URLs completas, por favor consulta Filtrado de Parámetros.

14 Rescate

Lo más probable es que tu aplicación contenga errores o de otro modo arroje una excepción que necesita ser manejada. Por ejemplo, si el usuario sigue un enlace a un recurso que ya no existe en la base de datos, Active Record lanzará la excepción ActiveRecord::RecordNotFound.

El manejo de excepciones predeterminado de Rails muestra un mensaje de "500 Error del Servidor" para todas las excepciones. Si la solicitud se realizó localmente, se muestra un buen seguimiento de la pila y se agrega información adicional, para que puedas averiguar qué salió mal y solucionarlo. Si la solicitud fue remota, Rails simplemente mostrará un mensaje simple de "500 Error del Servidor" al usuario, o un "404 No Encontrado" si hubo un error de enrutamiento, o no se pudo encontrar un registro. A veces es posible que desees personalizar cómo se capturan estos errores y cómo se muestran al usuario. Hay varios niveles de manejo de excepciones disponibles en una aplicación Rails:

14.1 Las Plantillas Predeterminadas 500 y 404

Por defecto, en el entorno de producción la aplicación renderizará un mensaje de error 404 o 500. En el entorno de desarrollo, todas las excepciones no manejadas simplemente se lanzan. Estos mensajes están contenidos en archivos HTML estáticos en la carpeta pública, en 404.html y 500.html respectivamente. Puedes personalizar estos archivos para agregar información y estilo adicional, pero recuerda que son HTML estático; es decir, no puedes usar ERB, SCSS, CoffeeScript o layouts para ellos.

14.2 rescue_from

Si deseas hacer algo un poco más elaborado al capturar errores, puedes usar rescue_from, que maneja excepciones de un cierto tipo (o múltiples tipos) en un controlador completo y sus subclases.

Cuando ocurre una excepción que es capturada por una directiva rescue_from, el objeto de excepción se pasa al manejador. El manejador puede ser un método o un objeto Proc pasado a la opción :with. También puedes usar un bloque directamente en lugar de un objeto Proc explícito.

Aquí está cómo puedes usar rescue_from para interceptar todos los errores ActiveRecord::RecordNotFound y hacer algo con ellos.

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

  private
    def record_not_found
      render plain: "404 Not Found", status: 404
    end
end

Por supuesto, este ejemplo es cualquier cosa menos elaborado y no mejora en absoluto el manejo de excepciones predeterminado, pero una vez que puedes capturar todas esas excepciones, eres libre de hacer lo que quieras con ellas. Por ejemplo, podrías crear clases de excepción personalizadas que se lanzarán cuando un usuario no tenga acceso a una cierta sección de tu aplicación:

class ApplicationController < ActionController::Base
  rescue_from User::NotAuthorized, with: :user_not_authorized

  private
    def user_not_authorized
      flash[:error] = "No tienes acceso a esta sección."
      redirect_back(fallback_location: root_path)
    end
end

class ClientsController < ApplicationController
  # Verifica que el usuario tenga la autorización correcta para acceder a los clientes.
  before_action :check_authorization

  # Nota cómo las acciones no tienen que preocuparse por todo el asunto de la autenticación.
  def edit
    @client = Client.find(params[:id])
  end

  private
    # Si el usuario no está autorizado, simplemente lanza la excepción.
    def check_authorization
      raise User::NotAuthorized unless current_user.admin?
    end
end

ADVERTENCIA: Usar rescue_from con Exception o StandardError podría causar efectos secundarios graves, ya que evita que Rails maneje las excepciones correctamente. Por lo tanto, no se recomienda hacerlo a menos que haya una razón de peso.

NOTA: Cuando se ejecuta en el entorno de producción, todos los errores ActiveRecord::RecordNotFound renderizan la página de error 404. A menos que necesites un comportamiento personalizado, no necesitas manejar esto.

NOTA: Ciertas excepciones solo son rescatables desde la clase ApplicationController, ya que se lanzan antes de que el controlador se inicialice y la acción se ejecute.

15 Forzar el Protocolo HTTPS

Si deseas asegurarte de que la comunicación con tu controlador solo sea posible a través de HTTPS, debes hacerlo habilitando el middleware ActionDispatch::SSL a través de config.force_ssl en la configuración de tu entorno.

16 Punto Final de Verificación de Salud Incorporado

Rails también viene con un punto final de verificación de salud incorporado que es accesible en la ruta /up. Este punto final devolverá un código de estado 200 si la aplicación se ha iniciado sin excepciones, y un código de estado 500 de lo contrario.

En producción, muchas aplicaciones están obligadas a informar su estado a aguas arriba, ya sea a un monitor de tiempo de actividad que notificará a un ingeniero cuando las cosas vayan mal, o a un equilibrador de carga o controlador de Kubernetes utilizado para determinar la salud de un pod. Esta verificación de salud está diseñada para ser un ajuste único que funcionará en muchas situaciones.

Mientras que cualquier aplicación Rails recién generada tendrá la verificación de salud en /up, puedes configurar la ruta para que sea cualquier cosa que desees en tu "config/routes.rb":

Rails.application.routes.draw do
  get "healthz" => "rails/health#show", as: :rails_health_check
end

La verificación de salud ahora será accesible a través de la ruta /healthz.

NOTA: Este punto final no refleja el estado de todas las dependencias de tu aplicación, como la base de datos o el clúster de redis. Reemplaza "rails/health#show" con tu propia acción de controlador si tienes necesidades específicas de la aplicación.

Piensa cuidadosamente sobre lo que deseas verificar, ya que puede llevar a situaciones en las que tu aplicación se reinicia debido a que un servicio de terceros falla. Idealmente, deberías diseñar tu aplicación para manejar esas interrupciones de manera elegante.


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.