1 Caché Básico
Esta es una introducción a tres tipos de técnicas de caché: caché de página, caché de acción y caché de fragmento. Por defecto, Rails proporciona caché de fragmento. Para usar caché de página y caché de acción, necesitarás agregar actionpack-page_caching
y actionpack-action_caching
a tu Gemfile
.
Por defecto, el caché de Action Controller solo está habilitado en tu entorno de producción. Puedes experimentar con el caché localmente ejecutando rails dev:cache
, o configurando config.action_controller.perform_caching
a true
en config/environments/development.rb
.
NOTA: Cambiar el valor de config.action_controller.perform_caching
solo tendrá efecto en el caché proporcionado por Action Controller. Por ejemplo, no impactará el caché de bajo nivel, que abordamos más abajo.
1.1 Caché de Página
La caché de página es un mecanismo de Rails que permite que la solicitud de una página generada sea cumplida por el servidor web (es decir, Apache o NGINX) sin tener que pasar por toda la pila de Rails. Aunque esto es súper rápido, no se puede aplicar a todas las situaciones (como páginas que necesitan autenticación). Además, debido a que el servidor web está sirviendo un archivo directamente desde el sistema de archivos, necesitarás implementar la expiración de caché.
La caché de página ha sido eliminada de Rails 4. Consulta la gema actionpack-page_caching.
1.2 Caché de Acción
La caché de página no se puede usar para acciones que tienen filtros previos, por ejemplo, páginas que requieren autenticación. Aquí es donde entra la caché de acción. La caché de acción funciona como la caché de página, excepto que la solicitud web entrante llega a la pila de Rails para que se puedan ejecutar filtros previos antes de que se sirva la caché. Esto permite que se ejecuten autenticaciones y otras restricciones mientras se sigue sirviendo el resultado de la salida de una copia en caché.
La caché de acción ha sido eliminada de Rails 4. Consulta la gema actionpack-action_caching. Consulta la visión general de expiración de caché basada en claves de DHH para el método preferido actualmente.
1.3 Caché de Fragmento
Las aplicaciones web dinámicas generalmente construyen páginas con una variedad de componentes, no todos con las mismas características de caché. Cuando diferentes partes de la página necesitan ser almacenadas en caché y expirar por separado, puedes usar la caché de fragmento.
La caché de fragmento permite que un fragmento de lógica de vista se envuelva en un bloque de caché y se sirva desde el almacén de caché cuando llegue la siguiente solicitud.
Por ejemplo, si quisieras almacenar en caché cada producto en una página, podrías usar este código:
<% @products.each do |product| %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
Cuando tu aplicación recibe su primera solicitud para esta página, Rails escribirá una nueva entrada de caché con una clave única. Una clave se ve algo así:
views/products/index:bea67108094918eeba42cd4a6e786901/products/1
La cadena de caracteres en el medio es un digest de árbol de plantilla. Es un digest hash calculado basado en el contenido del fragmento de vista que estás almacenando en caché. Si cambias el fragmento de vista (por ejemplo, cambia el HTML), el digest cambiará, expirando el archivo existente.
Una versión de caché, derivada del registro del producto, se almacena en la entrada de caché. Cuando se toca el producto, la versión de caché cambia, y cualquier fragmento en caché que contenga la versión anterior se ignora.
Los almacenes de caché como Memcached eliminarán automáticamente los archivos de caché antiguos.
Si deseas almacenar en caché un fragmento bajo ciertas condiciones, puedes usar cache_if
o cache_unless
:
<% cache_if admin?, product do %>
<%= render product %>
<% end %>
1.3.1 Caché de Colección
El helper render
también puede almacenar en caché plantillas individuales renderizadas para una colección. Incluso puede superar el ejemplo anterior con each
leyendo todas las plantillas de caché a la vez en lugar de una por una. Esto se hace pasando cached: true
al renderizar la colección:
<%= render partial: 'products/product', collection: @products, cached: true %>
Todas las plantillas en caché de renderizaciones anteriores se recuperarán a la vez con mucha mayor velocidad. Además, las plantillas que aún no han sido almacenadas en caché se escribirán en caché y se recuperarán en el siguiente renderizado.
La clave de caché puede ser configurada. En el ejemplo a continuación, se prefija con la localización actual para asegurar que diferentes localizaciones de la página del producto no se sobrescriban entre sí:
<%= render partial: 'products/product',
collection: @products,
cached: ->(product) { [I18n.locale, product] } %>
1.4 Caché de Muñeca Rusa
Es posible que desees anidar fragmentos en caché dentro de otros fragmentos en caché. Esto se llama caché de muñeca rusa.
La ventaja de la caché de muñeca rusa es que si se actualiza un solo producto, todos los demás fragmentos internos se pueden reutilizar al regenerar el fragmento externo.
Como se explicó en la sección anterior, un archivo en caché expirará si el valor de updated_at
cambia para un registro del que depende directamente el archivo en caché. Sin embargo, esto no expirará ninguna caché en la que el fragmento esté anidado.
Por ejemplo, considera la siguiente vista:
<% cache product do %>
<%= render product.games %>
<% end %>
Que a su vez renderiza esta vista:
<% cache game do %>
<%= render game %>
<% end %>
Si se cambia algún atributo del juego, el valor de updated_at
se establecerá en la hora actual, expirando así la caché. Sin embargo, como updated_at
no se cambiará para el objeto producto, esa caché no expirará y tu aplicación servirá datos obsoletos. Para solucionar esto, vinculamos los modelos juntos con el método touch
:
class Product < ApplicationRecord
has_many :games
end
class Game < ApplicationRecord
belongs_to :product, touch: true
end
Con touch
establecido en true
, cualquier acción que cambie updated_at
para un registro de juego también lo cambiará para el producto asociado, expirando así la caché.
1.5 Caché de Parcial Compartido
Es posible compartir parciales y caché asociado entre archivos con diferentes tipos MIME. Por ejemplo, el caché de parcial compartido permite a los escritores de plantillas compartir un parcial entre archivos HTML y JavaScript. Cuando las plantillas se recopilan en el archivo de resolución de plantillas, solo incluyen la extensión del lenguaje de plantillas y no el tipo MIME. Debido a esto, las plantillas pueden ser usadas para múltiples tipos MIME. Tanto las solicitudes HTML como JavaScript responderán al siguiente código:
render(partial: 'hotels/hotel', collection: @hotels, cached: true)
Cargará un archivo llamado hotels/hotel.erb
.
Otra opción es incluir el atributo formats
al parcial para renderizar.
render(partial: 'hotels/hotel', collection: @hotels, formats: :html, cached: true)
Cargará un archivo llamado hotels/hotel.html.erb
en cualquier tipo MIME de archivo, por ejemplo, podrías incluir este parcial en un archivo JavaScript.
1.6 Gestión de Dependencias
Para invalidar correctamente la caché, necesitas definir adecuadamente las dependencias de caché. Rails es lo suficientemente inteligente como para manejar casos comunes, por lo que no tienes que especificar nada. Sin embargo, a veces, cuando estás manejando helpers personalizados, por ejemplo, necesitas definirlos explícitamente.
1.6.1 Dependencias Implícitas
La mayoría de las dependencias de plantillas se pueden derivar de llamadas a render
en la propia plantilla. Aquí hay algunos ejemplos de llamadas a render que ActionView::Digestor
sabe cómo decodificar:
render partial: "comments/comment", collection: commentable.comments
render "comments/comments"
render 'comments/comments'
render('comments/comments')
render "header" # se traduce a render("comments/header")
render(@topic) # se traduce a render("topics/topic")
render(topics) # se traduce a render("topics/topic")
render(message.topics) # se traduce a render("topics/topic")
Por otro lado, algunas llamadas necesitan ser cambiadas para que el caché funcione correctamente. Por ejemplo, si estás pasando una colección personalizada, necesitarás cambiar:
render @project.documents.where(published: true)
a:
render partial: "documents/document", collection: @project.documents.where(published: true)
1.6.2 Dependencias Explícitas
A veces tendrás dependencias de plantillas que no se pueden derivar en absoluto. Este es típicamente el caso cuando el renderizado ocurre en helpers. Aquí tienes un ejemplo:
<%= render_sortable_todolists @project.todolists %>
Necesitarás usar un formato especial de comentario para indicarlas:
<%# Template Dependency: todolists/todolist %>
<%= render_sortable_todolists @project.todolists %>
En algunos casos, como una configuración de herencia de tabla única, podrías tener un montón de dependencias explícitas. En lugar de escribir cada plantilla, puedes usar un comodín para que coincida con cualquier plantilla en un directorio:
<%# Template Dependency: events/* %>
<%= render_categorizable_events @person.events %>
En cuanto al caché de colección, si la plantilla parcial no comienza con una llamada de caché limpia, aún puedes beneficiarte del caché de colección agregando un formato especial de comentario en cualquier parte de la plantilla, como:
<%# Template Collection: notification %>
<% my_helper_that_calls_cache(some_arg, notification) do %>
<%= notification.name %>
<% end %>
1.6.3 Dependencias Externas
Si usas un método helper, por ejemplo, dentro de un bloque de caché y luego actualizas ese helper, tendrás que actualizar la caché también. No importa realmente cómo lo hagas, pero el MD5 del archivo de plantilla debe cambiar. Una recomendación es simplemente ser explícito en un comentario, como:
<%# Helper Dependency Updated: Jul 28, 2015 at 7pm %>
<%= some_helper_method(person) %>
1.7 Caché de Bajo Nivel
A veces necesitas almacenar en caché un valor particular o el resultado de una consulta en lugar de almacenar en caché fragmentos de vista. El mecanismo de caché de Rails funciona muy bien para almacenar cualquier información serializable.
La forma más eficiente de implementar caché de bajo nivel es usando el método Rails.cache.fetch
. Este método hace tanto la lectura como la escritura en la caché. Cuando se pasa solo un argumento, la clave se obtiene y el valor de la caché se devuelve. Si se pasa un bloque, ese bloque se ejecutará en caso de que falte la caché. El valor de retorno del bloque se escribirá en la caché bajo la clave de caché dada, y ese valor de retorno se devolverá. En caso de acierto de caché, el valor en caché se devolverá sin ejecutar el bloque.
Considera el siguiente ejemplo. Una aplicación tiene un modelo Product
con un método de instancia que busca el precio del producto en un sitio web competidor. Los datos devueltos por este método serían perfectos para el caché de bajo nivel:
class Product < ApplicationRecord
def competing_price
Rails.cache.fetch("#{cache_key_with_version}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
NOTA: Observa que en este ejemplo usamos el método cache_key_with_version
, por lo que la clave de caché resultante será algo como products/233-20140225082222765838000/competing_price
. cache_key_with_version
genera una cadena basada en el nombre de la clase del modelo, id
y los atributos updated_at
. Esta es una convención común y tiene el beneficio de invalidar la caché cada vez que se actualiza el producto. En general, cuando usas caché de bajo nivel, necesitas generar una clave de caché.
1.7.1 Evitar Almacenar en Caché Instancias de Objetos Active Record
Considera este ejemplo, que almacena en caché una lista de objetos Active Record que representan superusuarios:
# super_admins es una consulta SQL costosa, así que no la ejecutes con demasiada frecuencia
Rails.cache.fetch("super_admin_users", expires_in: 12.hours) do
User.super_admins.to_a
end
Debes evitar este patrón. ¿Por qué? Porque la instancia podría cambiar. En producción, los atributos en ella podrían diferir, o el registro podría ser eliminado. Y en desarrollo, funciona de manera poco confiable con almacenes de caché que recargan el código cuando realizas cambios.
En su lugar, almacena en caché el ID u otro tipo de datos primitivo. Por ejemplo:
# super_admins es una consulta SQL costosa, así que no la ejecutes con demasiada frecuencia
ids = Rails.cache.fetch("super_admin_user_ids", expires_in: 12.hours) do
User.super_admins.pluck(:id)
end
User.where(id: ids).to_a
1.8 Caché de SQL
El caché de consultas es una característica de Rails que almacena en caché el conjunto de resultados devuelto por cada consulta. Si Rails encuentra la misma consulta nuevamente para esa solicitud, usará el conjunto de resultados en caché en lugar de ejecutar la consulta contra la base de datos nuevamente.
Por ejemplo:
class ProductsController < ApplicationController
def index
# Ejecutar una consulta de búsqueda
@products = Product.all
# ...
# Ejecutar la misma consulta nuevamente
@products = Product.all
end
end
La segunda vez que se ejecuta la misma consulta contra la base de datos, en realidad no va a golpear la base de datos. La primera vez que se devuelve el resultado de la consulta, se almacena en el caché de consultas (en memoria) y la segunda vez se extrae de la memoria.
Sin embargo, es importante tener en cuenta que los cachés de consulta se crean al inicio de una acción y se destruyen al final de esa acción, y por lo tanto, solo persisten durante la duración de la acción. Si deseas almacenar resultados de consultas de manera más persistente, puedes hacerlo con caché de bajo nivel.
2 Almacenes de Caché
Rails proporciona diferentes almacenes para los datos en caché (aparte de SQL y caché de página).
2.1 Configuración
Puedes configurar el almacén de caché predeterminado de tu aplicación configurando la opción de configuración config.cache_store
. Otros parámetros se pueden pasar como argumentos al constructor del almacén de caché:
config.cache_store = :memory_store, { size: 64.megabytes }
Alternativamente, puedes configurar ActionController::Base.cache_store
fuera de un bloque de configuración.
Puedes acceder al caché llamando a Rails.cache
.
2.1.1 Opciones de Conexión de Pool
Por defecto, :mem_cache_store
y
:redis_cache_store
están configurados para usar
pool de conexiones. Esto significa que si estás usando Puma, u otro servidor con hilos,
puedes tener múltiples hilos realizando consultas al almacén de caché al mismo tiempo.
Si deseas desactivar el pool de conexiones, configura la opción :pool
a false
al configurar el almacén de caché:
config.cache_store = :mem_cache_store, "cache.example.com", { pool: false }
También puedes anular la configuración predeterminada del pool proporcionando opciones individuales a la opción :pool
:
config.cache_store = :mem_cache_store, "cache.example.com", { pool: { size: 32, timeout: 1 } }
:size
- Esta opción establece el número de conexiones por proceso (por defecto es 5).:timeout
- Esta opción establece el número de segundos para esperar una conexión (por defecto es 5). Si no hay conexión disponible dentro del tiempo de espera, se generará unTimeout::Error
.
2.2 ActiveSupport::Cache::Store
ActiveSupport::Cache::Store
proporciona la base para interactuar con el caché en Rails. Esta es una clase abstracta, y no puedes usarla por sí sola. En su lugar, debes usar una implementación concreta de la clase vinculada a un motor de almacenamiento. Rails viene con varias implementaciones, documentadas a continuación.
Los métodos principales de la API son read
, write
, delete
, exist?
, y fetch
.
Las opciones pasadas al constructor del almacén de caché serán tratadas como opciones predeterminadas para los métodos de API apropiados.
2.3 ActiveSupport::Cache::MemoryStore
ActiveSupport::Cache::MemoryStore
mantiene las entradas en memoria en el mismo proceso Ruby. El almacén de caché tiene un tamaño limitado especificado enviando la opción :size
al inicializador (el valor predeterminado es 32Mb). Cuando el caché supera el tamaño asignado, se producirá una limpieza y se eliminarán las entradas menos recientemente utilizadas.
config.cache_store = :memory_store, { size: 64.megabytes }
Si estás ejecutando múltiples procesos de servidor Ruby on Rails (lo cual es el caso si estás usando Phusion Passenger o el modo de clúster de Puma), entonces tus instancias de procesos de servidor Rails no podrán compartir datos de caché entre sí. Este almacén de caché no es apropiado para implementaciones de aplicaciones grandes. Sin embargo, puede funcionar bien para sitios pequeños y de bajo tráfico con solo un par de procesos de servidor, así como para entornos de desarrollo y prueba.
Los nuevos proyectos de Rails están configurados para usar esta implementación en el entorno de desarrollo por defecto.
NOTA: Dado que los procesos no compartirán datos de caché al usar :memory_store
, no será posible leer, escribir o expirar manualmente el caché a través de la consola de Rails.
2.4 ActiveSupport::Cache::FileStore
ActiveSupport::Cache::FileStore
utiliza el sistema de archivos para almacenar entradas. La ruta al directorio donde se almacenarán los archivos del almacén debe especificarse al inicializar el caché.
config.cache_store = :file_store, "/path/to/cache/directory"
Con este almacén de caché, múltiples procesos de servidor en el mismo host pueden compartir un caché. Este almacén de caché es apropiado para sitios de tráfico bajo a medio que se sirven desde uno o dos hosts. Los procesos de servidor que se ejecutan en diferentes hosts podrían compartir un caché utilizando un sistema de archivos compartido, pero esa configuración no se recomienda.
Dado que el caché crecerá hasta que el disco esté lleno, se recomienda limpiar periódicamente las entradas antiguas.
Esta es la implementación de almacén de caché predeterminada (en "#{root}/tmp/cache/"
) si no se proporciona explícitamente config.cache_store
.
2.5 ActiveSupport::Cache::MemCacheStore
ActiveSupport::Cache::MemCacheStore
utiliza el servidor memcached
de Danga para proporcionar un caché centralizado para tu aplicación. Rails utiliza la gema dalli
incluida por defecto. Este es actualmente el almacén de caché más popular para sitios web de producción. Puede usarse para proporcionar un clúster de caché compartido único con muy alto rendimiento y redundancia.
Al inicializar el caché, debes especificar las direcciones de todos los servidores memcached en tu clúster, o asegurarte de que la variable de entorno MEMCACHE_SERVERS
se haya configurado adecuadamente.
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
Si no se especifican, asumirá que memcached se está ejecutando en localhost en el puerto predeterminado (127.0.0.1:11211
), pero esta no es una configuración ideal para sitios más grandes.
config.cache_store = :mem_cache_store # Caerá en $MEMCACHE_SERVERS, luego 127.0.0.1:11211
Consulta la documentación de Dalli::Client
para conocer los tipos de direcciones compatibles.
El método write
(y fetch
) en este caché acepta opciones adicionales que aprovechan las características específicas de memcached.
2.6 ActiveSupport::Cache::RedisCacheStore
ActiveSupport::Cache::RedisCacheStore
aprovecha el soporte de Redis para la expulsión automática cuando alcanza la memoria máxima, permitiéndole comportarse de manera muy similar a un servidor de caché Memcached.
Nota de implementación: Redis no expira claves por defecto, así que ten cuidado de usar un servidor Redis dedicado para caché. ¡No llenes tu servidor Redis persistente con datos de caché volátiles! Lee la guía de configuración del servidor de caché Redis en detalle.
Para un servidor Redis solo para caché, configura maxmemory-policy
en una de las variantes de allkeys. Redis 4+ soporta la expulsión menos frecuentemente usada (allkeys-lfu
), una excelente opción predeterminada. Redis 3 y versiones anteriores deberían usar la expulsión menos recientemente usada (allkeys-lru
).
Configura los tiempos de espera de lectura y escritura de caché relativamente bajos. Regenerar un valor en caché es a menudo más rápido que esperar más de un segundo para recuperarlo. Tanto los tiempos de espera de lectura como de escritura predeterminados son de 1 segundo, pero pueden configurarse más bajos si tu red es consistentemente de baja latencia.
Por defecto, el almacén de caché intentará reconectarse a Redis una vez si la conexión falla durante una solicitud.
Las lecturas y escrituras de caché nunca generan excepciones; simplemente devuelven nil
, comportándose como si no hubiera nada en el caché. Para saber si tu caché está teniendo excepciones, puedes proporcionar un error_handler
para reportar a un servicio de recopilación de excepciones. Debe aceptar tres argumentos con nombre: method
, el método del almacén de caché que se llamó originalmente; returning
, el valor que se devolvió al usuario, típicamente nil
; y exception
, la excepción que fue rescatada.
Para comenzar, agrega la gema redis a tu Gemfile:
gem "redis"
Finalmente, agrega la configuración en el archivo config/environments/*.rb
relevante:
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] }
Un almacén de caché Redis más complejo y de producción podría verse algo así:
cache_servers = %w(redis://cache-01:6379/0 redis://cache-02:6379/0)
config.cache_store = :redis_cache_store, { url: cache_servers,
connect_timeout: 30, # Predeterminado a 1 segundo
read_timeout: 0.2, # Predeterminado a 1 segundo
write_timeout: 0.2, # Predeterminado a 1 segundo
reconnect_attempts: 2, # Predeterminado a 1
error_handler: -> (method:, returning:, exception:) {
# Reportar errores a Sentry como advertencias
Sentry.capture_exception exception, level: 'warning',
tags: { method: method, returning: returning }
}
}
2.7 ActiveSupport::Cache::NullStore
ActiveSupport::Cache::NullStore
está limitado a cada solicitud web, y borra los valores almacenados al final de una solicitud. Está destinado a ser usado en entornos de desarrollo y prueba. Puede ser muy útil cuando tienes código que interactúa directamente con Rails.cache
pero el caché interfiere con ver los resultados de los cambios de código.
config.cache_store = :null_store
2.8 Almacenes de Caché Personalizados
Puedes crear tu propio almacén de caché personalizado simplemente extendiendo ActiveSupport::Cache::Store
e implementando los métodos apropiados. De esta manera, puedes intercambiar cualquier cantidad de tecnologías de caché en tu aplicación Rails.
Para usar un almacén de caché personalizado, simplemente configura el almacén de caché a una nueva instancia de tu clase personalizada.
config.cache_store = MyCacheStore.new
3 Claves de Caché
Las claves utilizadas en un caché pueden ser cualquier objeto que responda a cache_key
o to_param
. Puedes implementar el método cache_key
en tus clases si necesitas generar claves personalizadas. Active Record generará claves basadas en el nombre de la clase y el id del registro.
Puedes usar Hashes y Arrays de valores como claves de caché.
# Esta es una clave de caché legal
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
Las claves que uses en Rails.cache
no serán las mismas que las utilizadas realmente con el motor de almacenamiento. Pueden ser modificadas con un namespace o alteradas para cumplir con las restricciones del backend tecnológico. Esto significa, por ejemplo, que no puedes guardar valores con Rails.cache
y luego intentar extraerlos con la gema dalli
. Sin embargo, tampoco necesitas preocuparte por exceder el límite de tamaño de memcached o violar las reglas de sintaxis.
4 Soporte para GET Condicional
Los GET condicionales son una característica de la especificación HTTP que proporciona una forma para que los servidores web informen a los navegadores que la respuesta a una solicitud GET no ha cambiado desde la última solicitud y se puede extraer de manera segura del caché del navegador.
Funcionan utilizando los encabezados HTTP_IF_NONE_MATCH
y HTTP_IF_MODIFIED_SINCE
para pasar de ida y vuelta tanto un identificador de contenido único como la marca de tiempo de cuando el contenido fue cambiado por última vez. Si el navegador realiza una solicitud donde el identificador de contenido (ETag) o la marca de tiempo de última modificación coincide con la versión del servidor, entonces el servidor solo necesita enviar una respuesta vacía con un estado no modificado.
Es responsabilidad del servidor (es decir, nuestra) buscar una marca de tiempo de última modificación y el encabezado if-none-match y determinar si enviar o no la respuesta completa. Con el soporte de GET condicional en Rails, esta es una tarea bastante fácil:
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
# Si la solicitud es obsoleta según la marca de tiempo y el valor etag dados
# (es decir, necesita ser procesada nuevamente), entonces ejecuta este bloque
if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
respond_to do |wants|
# ... procesamiento normal de la respuesta
end
end
# Si la solicitud está fresca (es decir, no está modificada), entonces no necesitas hacer
# nada. El render predeterminado verifica esto usando los parámetros
# utilizados en la llamada anterior a stale? y enviará automáticamente un
# :not_modified. Así que eso es todo, has terminado.
end
end
En lugar de un hash de opciones, también puedes simplemente pasar un modelo. Rails usará los métodos updated_at
y cache_key_with_version
para establecer last_modified
y etag
:
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
if stale?(@product)
respond_to do |wants|
# ... procesamiento normal de la respuesta
end
end
end
end
Si no tienes ningún procesamiento especial de respuesta y estás usando el mecanismo de renderizado predeterminado (es decir, no estás usando respond_to
ni llamando a render tú mismo), entonces tienes un helper fácil en fresh_when
:
class ProductsController < ApplicationController
# Esto enviará automáticamente un :not_modified si la solicitud está fresca,
# y renderizará la plantilla predeterminada (product.*) si está obsoleta.
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, etag: @product
end
end
A veces queremos almacenar en caché la respuesta, por ejemplo, una página estática, que nunca se expira. Para lograr esto, podemos usar el helper http_cache_forever
y al hacerlo el navegador y los proxies la almacenarán en caché indefinidamente.
Por defecto, las respuestas en caché serán privadas, almacenadas solo en el navegador web del usuario. Para permitir que los proxies almacenen en caché la respuesta, configura public: true
para indicar que pueden servir la respuesta en caché a todos los usuarios.
Usando este helper, el encabezado last_modified
se establece en Time.new(2011, 1, 1).utc
y el encabezado expires
se establece en 100 años.
ADVERTENCIA: Usa este método con cuidado, ya que el navegador/proxy no podrá invalidar la respuesta en caché a menos que se borre forzosamente la caché del navegador.
class HomeController < ApplicationController
def index
http_cache_forever(public: true) do
render
end
end
end
4.1 ETags Fuertes vs. Débiles
Rails genera ETags débiles por defecto. Los ETags débiles permiten que las respuestas semánticamente equivalentes tengan los mismos ETags, incluso si sus cuerpos no coinciden exactamente. Esto es útil cuando no queremos que la página se regenere por cambios menores en el cuerpo de la respuesta.
Los ETags débiles tienen un prefijo W/
para diferenciarlos de los ETags fuertes.
W/"618bbc92e2d35ea1945008b42799b0e7" → ETag Débil
"618bbc92e2d35ea1945008b42799b0e7" → ETag Fuerte
A diferencia del ETag débil, el ETag fuerte implica que la respuesta debe ser exactamente la misma e idéntica byte por byte. Útil cuando se realizan solicitudes de rango dentro de un archivo grande de video o PDF. Algunos CDNs solo soportan ETags fuertes, como Akamai. Si absolutamente necesitas generar un ETag fuerte, se puede hacer de la siguiente manera.
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, strong_etag: @product
end
end
También puedes establecer el ETag fuerte directamente en la respuesta.
response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"
5 Caché en Desarrollo
Por defecto, el caché está habilitado en modo desarrollo con
:memory_store
.
Rails también proporciona el comando rails dev:cache
para
activar/desactivar fácilmente el caché de Action Controller.
$ bin/rails dev:cache
El modo de desarrollo ahora está siendo almacenado en caché.
$ bin/rails dev:cache
El modo de desarrollo ya no está siendo almacenado en caché.
Para desactivar el caché, configura cache_store
a :null_store
6 Referencias
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.