ruby on rails - En Rails, un Sweeper no se llama en una configuración de solo modelo
ruby-on-rails observer-pattern (7)
Estoy trabajando en una aplicación de Rails, donde estoy usando el almacenamiento en caché de la página para almacenar resultados html estáticos. El almacenamiento en caché funciona bien. Sin embargo, tengo problemas para caducar los cachés.
Creo que mi problema es, en parte, porque no estoy expirando el caché de mi controlador. Todas las acciones necesarias para esto se están manejando dentro del modelo. Parece que debería ser factible, pero todas las referencias al vencimiento del caché basado en el modelo que estoy encontrando parecen estar desactualizadas o no funcionan.
En mi archivo environment.rb, estoy llamando
config.load_paths += %W( #{RAILS_ROOT}/app/sweepers )
Y tengo, en la carpeta / sweepers, un archivo LinkSweeper:
class LinkSweeper < ActionController::Caching::Sweeper
observe Link
def after_update(link)
clear_links_cache(link)
end
def clear_links_cache(link)
# expire_page :controller => ''links'', :action => ''show'', :md5 => link.md5
expire_page ''/l/''+ link.md5 + ''.html''
end
end
Entonces ... ¿por qué no está borrando la página en caché cuando actualizo el modelo? (Proceso: usando script / consola, estoy seleccionando elementos de la base de datos y guardándolos, pero sus páginas correspondientes no están eliminando de la caché), y también estoy llamando al método específico en el modelo de enlace que normalmente invocaría el barrendero Ninguno de los dos funciona
Si es importante, el archivo en caché es un hash md5 de un valor de clave en la tabla Vínculos. La página en caché se almacena como algo como /l/45ed4aade64d427...99919cba2bd90f.html.
Básicamente, parece que Sweeper no está realmente observando el Enlace. También leí ( aquí ) que podría ser posible simplemente agregar la barredora a config.active_record.observers en environment.rb, pero eso no parecía hacerlo (y no estaba seguro de si el load_path de la aplicación / sweepers en environment.rb obvió eso).
Así que he intentado una serie de enfoques diferentes, para ver qué funciona y qué no.
De nuevo, para resumir la situación: Mi objetivo es caducar las páginas almacenadas en caché cuando un objeto se actualiza, pero expirar sin tener que depender de una acción del Controlador. Las barredoras convencionales usan una línea en el controlador para notificar a la barredora que necesita funcionar. En este caso, no puedo usar una línea en el controlador, ya que la actualización está ocurriendo dentro del modelo. Los tutoriales de sweeper normales no funcionan, ya que suponen que su interacción principal con el objeto de la base de datos es a través del controlador.
Si, al leer esto, ves una forma de reforzar mi código, por favor coméntamelo y házmelo saber.
Primero, veamos las cosas que SI FUNCIONAN, en caso de que también estés atascado en esto y necesites ayuda.
De todas las cosas que probé, lo único que realmente parecía funcionar era declarar un comando after_update en el Observer para el modelo. En ese comando, utilicé el comando explícito para la acción expire_page e incluí una ruta que había sido declarada en routes.rb.
Asi que. Esto funciona:
En config / routes.rb:
map.link ''l/:md5.:format'', :controller => ''links'', :action => ''show''
En la aplicación / modelos / link_observer.rb:
def after_update(link)
ActionController::Base.expire_page(app.link_path(:md5 => link.md5))
end
Tenga en cuenta que ese "md5" es específico de mi aplicación. Es posible que desee utilizar: id o algún otro identificador único.
También encontré que declarar que la línea ActionController :: Base ... del método en el modelo que está haciendo la actualización funcionó. Es decir, dentro de Link.rb, en el método que en realidad está actualizando la base de datos, si simplemente pegué toda esa línea, funcionó. Pero dado que es posible que desee caducar ese caché de página en otros métodos en el futuro, prefiero que se extraiga en el Observer.
Ahora, veamos algunas cosas que NO FUNCIONARON, en caso de que esté buscando en Google para esto.
Llamar a "expire_page (...)" dentro del método after_update (link) dentro de link_observer.rb no funcionó, ya que devolvía un error "método no definido` expire_page ''"
Crear un archivo Sweeper que observó que el Modelo no funcionó. No pude encontrar ningún código de error, pero parecía no darse cuenta de que tenía un trabajo que hacer. Esto fue después de llamar explícitamente "config.load_paths + =% W (# {RAILS_ROOT} / app / sweepers)" dentro de environment.rb. Solo en caso de que toque algo en ese código, aquí está:
class LinkSweeper < ActionController::Caching::Sweeper
observe Link
def after_update(link)
clear_links_cache(link)
end
def clear_links_cache(link)
# DID NOT WORK expire_page :controller => ''links'', :action => ''show'', :md5 => link.md5
# DID NOT WORK expire_page ''/l/''+ link.md5 + ''.html''
# DID NOT WORK ActionController::Base.expire_page(app.link_path(:md5 => link.md5))
end
end
El ejemplo anterior tenía el archivo link_sweeper.rb en un directorio, / app / sweepers. También intenté poner link_sweeper.rb dentro del directorio de la aplicación / modelos e intenté llamarlo con el comando config.active_record.observers en environment.rb:
config.active_record.observers = :link_observer, :link_sweeper
Pero eso tampoco funcionó.
Así que sí. Es muy posible que uno de estos métodos funcione, y que estropeé algo en el código. Pero creo que hice todo por el libro.
En definitiva, para resumir: en lugar de utilizar un Sweeper para caducar el almacenamiento en caché de la página, desea configurar una devolución de llamada after_ en el Observer del modelo. Querrá utilizar la ruta explícita al método Base.expire_page:
def after_update(<model>) # where <model> is the name of the model you''re observing
ActionController::Base.expire_page(app.<model>_path(:id => <model>.id)) # where <model> is the name of the model you''re observing
end
Con suerte, esto ayudará a alguien más en el futuro. Nuevamente, si ve en alguna parte de mi código que no funciona donde debería haber hecho algo diferente, por favor hágamelo saber. Si ves algo en mi código de trabajo que puede ser más estricto, házmelo saber también.
Basado en las respuestas de @moiristo y @ZoogieZork, supongo que esto funcionaría (no probado).
class LinkSweeper < ActiveRecord::Observer
include ActionController::Caching::Pages
# or if you want to expire fragments
#include ActionController::Caching::Fragments
observe Link
def after_update(link)
expire_page( ... )
#expire_fragment( ... )
end
end
Escribí un poco sobre este tema aquí: Rails Cache Sweeper Confusion . Me encantaría escuchar tus opiniones.
Estaba experimentando el mismo problema al intentar hacer un almacenamiento en caché de fragmentos (rieles 3). No pude lograr que la barredora observara, así que me conformé con la solución para convertirlo en un observador de AR como se describe arriba y llamar a ApplicationController.new.expire_fragment(...)
.
He podido hacer que funcione, añadiendo
ActionController::Base.expire_page(app.link_path(:md5 => @link.md5))
al método en el propio modelo que está actualizando la base de datos. Sin embargo, esto parece un poco raro, y me gustaría saber si alguien puede explicar por qué no funciona con la configuración normal de la barredora, y si hay una manera más elegante de manejar esto.
Ese fragmento de código (aparte de las personalizaciones que puse para mi propia aplicación) provino de esta publicación en ruby-forum.com .
Hice que esto funcionara. La única pequeña diferencia en mi configuración es que la barredora es parte de un motor de Rails; que explica pequeñas diferencias (cargar el archivo sweeper con un requerimiento en el init del motor en lugar de agregarlo a la ruta de carga en environment.rb, etc.).
Entonces, la barredora se carga en el init.rb del motor de esta manera:
require File.join(File.dirname(__FILE__), ''app'', ''sweepers'', cached_category_count_sweeper'')
Lo llamé barrendero porque "barre" el caché, pero supongo que es solo un observador en el modelo:
class CachedCategoryCountSweeper < ActiveRecord::Observer
observe CategoryFeature
def before_save(cf)
expire_cache(cf.category_id_was) if cf.category_id_changed?
end
def after_save(cf)
expire_cache(cf.category_id)
end
def after_destroy(cf)
expire_cache(cf.category_id)
end
def expire_cache(c)
ApplicationController.expire_page("/categories/#{c}/counts.xml") if !c.nil?
end
end
Francamente, no me gusta tener que codificar la ruta, pero intenté agregar:
include ActionController:UrlWriter
y luego usando el método path, pero solo funcionó para mí en el desarrollo. No funcionó en producción, porque mi servidor de producción usa una raíz de URL relativa (en lugar de hosts virtuales) y el método interno "page_cache_path" de forma consistente obtenía la ruta del archivo incorrectamente para que no pudiera caducar.
Como este es un observador, agregué al entorno.rb:
config.active_record.observers = :cached_category_count_sweeper
Finalmente, el controlador que usa el caché (no lo vence, eso se hace a través del observador del modelo):
class CachedCategoryCountsController < ApplicationController
caches_page :index
# GET /cached_category_counts.xml
def index
...
end
end
De todos modos, espero que esto ayude.
Andres Montano
Solo una nota: puedes usar cache_sweeper
en ApplicationController.
class ApplicationController < ActionController::Base
cache_sweeper :my_sweeper
end
class MySweeper < ActionController::Caching::Sweeper
observe MyModel
def after_update(my_model)
expire_page(...)
end
end