Expirar cachés sin pasar por el Control ler


#1

Hola gente,

Estoy usando Sweepers para expirar determinadas cachés cuando se
guarda/actualiza/borra determinado modelo.

Todo bien.

Lo que ocurre que ahora me surge que estoy insertando modelos en la BD
desde un script/rake/cron … y sorprendido de mí veo que las
cachésno se expiran.

El problema está en que los Sweepers no están activados puesto que se
declaran en el application.rb con el cache_sweeper.

Leyendo esto:

http://codelevy.com/articles/2008/03/04/rails-caching-sweepers-controllers-and-models

Veo que no hay mucha solución, pero me gustaría preguntar si os ha
pasado alguna vez y cómo lo habéis solucionado.

O si tenéis alguna sugerencia.

Gracias
f.


#2

No utilices cache, eso es de maricas :slight_smile:
Bromas a parte, ni idea. Uf como me afecta el calor.

El 31 de julio de 2008 17:04, Fernando G.removed_email_address@domain.invalid
escribió:


#3

Usando observers no te funcionaria?

El observer mira directamente el modelo, así que podrias decirle que
desde allí se expiren las caches …


#4

El día 1 de agosto de 2008 11:00, Francesc E.
removed_email_address@domain.invalid
escribió:> Usando observers no te funcionaria?

Sip.

Es lo que me han sugerido pero no me gusta salirme de el modo en que
rails espera que se hagan las cosas.

Por otro lado, no sé si desde los observes voy a tener fácil el acceso
a métodos como:

  • expire_action

Y al reconocimiento de rutas como:

forums_topic_show_url(
:forum_nicetitle => record.forums_forum.nicetitle,
:category_nicetitle => record.forums_category.nicetitle,
:topic_nicetitle => record.nicetitle
)

No sé…

Gracias
f.


#5

On Fri, Aug 1, 2008 at 12:01, Fernando G. removed_email_address@domain.invalid
wrote:

a métodos como:

No sé…

Gracias
f.

Ha sido difícil pero lo he conseguido (creo).

Necesitas un Sweeper
http://rails-doc.org/rails/ActionController/Caching/Sweeping, no un
Observer normal y corriente
http://rails-doc.org/rails/ActiveRecord/Observer, en otro caso no
podrás hacer uso de las funciones expire_*.

Yo tengo un modelo y un sweeper que le observa:

— app/models/product.rb —
class Product
end
— eof —
— app/models/product_sweeper.rb —
class ProductSweeper < ActionController::Caching::Sweeper
observe Product

def after_save(product)
product.logger.info(“Producto creado y <#{products_url}”>)
end
end
— eof —

Vale, desde el script/console si hago lo siguiente:

ActiveRecord::Base.observers = [ProductSweeper]
ActiveRecord::Base.instantiate_observers
ProductSweeper.instance.controller = ActionController.Base.new
ProductSweeper.instance.controller.request = TestRequest.new
p = Product.create :name => ‘test’

En el log aparece “Producto creado y http://test.host/products”, que
creo que demuestra que se podría hacer funcionar los expire_* con los
métodos hash_for_products_path y similares.

Suerte.


#6

A mi esto me da un error …

ProductSweeper.instance.controller.request = TestRequest.new

No existe la clase TestRequest.

2008/8/1 Daniel R. Troitiño removed_email_address@domain.invalid:


#7

2008/8/1 Daniel R. Troitiño removed_email_address@domain.invalid:

ActionController::TestRequest.

Suerte.

De hecho la línea justo superior también está mal:
ActionController::Base (dobles dos puntos en vez de punto).

Lo siento.


#8

2008/8/1 Francesc E. removed_email_address@domain.invalid:

A mi esto me da un error …

ProductSweeper.instance.controller.request = TestRequest.new

No existe la clase TestRequest.

Ups. Mira que he repasado los pasos una y otra vez. Pues esa línea
siempre la escribía mal. La clase correcta es
ActionController::TestRequest.

Suerte.


#9

2008/8/2 Fernando G. removed_email_address@domain.invalid:

Las 2 rutas están bien definidas en el routes.rb. :
from /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:617:in
`send’
@url.rewrite(rewrite_options(options))
def initialize_current_url

Primera cosa, no se si es por el ejemplo, pero ruta_guapa y
ruta_guapa_con_parametro se pueden reducir así:

map.ruta_guapa ‘ruta/guapa/:parametro’, :controller => ‘develop’,
:action => ‘simulate_login_form’, :parametro => nil

Cuando no reciba parámetro Rails pasará nil (que por como es el
ejemplo tú debes ya tener cuidado en simulate_login_form) y además, de
gratis, quita la barra final de “ruta/guapa”, con lo que quedan las
rutas que tú tenías.

Y lo siguiente, la “solución” (sigo con mi ejemplo, si no te importa):

ActiveRecord::Base.observers = [ProductSweeper]
ActiveRecord::Base.instantiate_observers
ProductSweeper.instance.controller = ActionController::Base.new
tr = ActionController::TestRequest.new
ProductSweeper.instance.controller.request = tr
ProductSweeper.instance.controller.instance_eval(’@url =
ActionController::UrlRewriter.new(tr, {})’)
Product.create :name => ‘Name 22’

En el after_save del swepper:

product.logger.info(“ruta guapa 1 #{ruta_guapa_path}”)
product.logger.info(“ruta guapa 2 #{ruta_guapa_path(‘X’)}”)
product.logger.info(“ruta guapa 3 #{ruta_guapa_path(:parametro =>
‘X’)}”)

En el log:

ruta guapa 1 /ruta/guapa
ruta guapa 2 /ruta/guapa/X
ruta guapa 3 /ruta/guapa/X

Es un asco tener que utilizar instance_eval, pero en Rails 2.1.0
funciona. Digo que es un asco porque al no ser un API pública tendrás
que comprobar en cada versión de Rails si va a seguir funcionando. La
única forma que he visto posible utilizando solo el API pública es
realizar un request de verdad, mediante el método process, pero eso
implicaría tener un controlador de verdad (mientras que en la solución
que utilizamos no necesitamos realizar un request y no necesitamos un
controlador).

Rails con las rutas es un desastre de cuidado. Debería ser mucho más
abierto con su utilización.

Suerte.


#10

El día 1 de agosto de 2008 17:17, Daniel R.
Troitiñoremoved_email_address@domain.invalid
escribió:>

end

En el log aparece “Producto creado y http://test.host/products”, que
creo que demuestra que se podría hacer funcionar los expire_* con los
métodos hash_for_products_path y similares.

Daniel… está genial lo que has conseguido :D.

Pero se me ha vuelto a liar.

He conseguido que el ejemplo que me pasaste funcionara, pero si
intento generar urls con parámetros en forma de Hash, que es la forma
que más uso, la he cagado:

Ejemplo:

class ForumsCommentSweeper < ActionController::Caching::Sweeper
observe ForumsComment

def after_save( record )
p( “ruta: #{ruta_guapa_url}” )
p( “ruta: #{ruta_guapa_con_parametro_url( ‘X’ )}” )
p( “ruta: #{ruta_guapa_con_parametro_url( :parametro => ‘X’ )}” )
end
end

Las 2 rutas están bien definidas en el routes.rb. :

map.ruta_guapa ‘ruta/guapa’, :controller =>
‘develop’, :action => ‘simulate_login_form’
map.ruta_guapa_con_parametro ‘ruta/guapa/:parametro’, :controller =>
‘develop’, :action => ‘simulate_login_form’

La tercera peta con un:

NoMethodError: You have a nil object when you didn’t expect it!
The error occurred while evaluating nil.rewrite
from
/Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/base.rb:617:in
url_for' from (eval):17:inruta_guapa_con_parametro_url’
from
/Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:665:in
send!' from /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:665:inmethod_missing’
from
/Users/fguillen/Documents/develop-ror/lcforums/app/sweepers/forums_comment_sweeper.rb:14
<<el número de línea no coincide porque el paste está resumido>> :in
after_save' from /Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/observer.rb:157:insend’

He intentado revisar el código de cachin.rb y de base.rb y veo que el
problema está en base.rb justo en este método:

def url_for(options = nil) #:doc:
case options || {}
when String
options
when Hash
@url.rewrite(rewrite_options(options))
else
polymorphic_url(options)
end
end

Si entra en el ‘when Hash’ estamos jodidos resulta que ‘@url’ es nil y
sacabó.
He intentado ver como ser carga este ‘@url’ y parece que se hace en el
método del mismo fichero:

def initialize_current_url
@url = UrlRewriter.new(request, params.clone)
end

He intentado invocarlo desde la consola para que se inicializara con
esto:

ForumsCommentSweeper.instance.controller.initialize_current_url

Pero es privado.

También desde consola he probado:

ForumsCommentSweeper.instance.controller.url =
ActionController::UrlRewriter.new(
ForumsCommentSweeper.instance.controller.request, nil )

Pero no le gustaba:

NoMethodError: undefined method `url=’ for
#ActionController::Base:0x2129d58

Vamos que estoy viendo que nos estamos metiendo un plato de
espaguettis del que no vamos a salir.

Si sale alguna sugerencia al rescate guay…

Sino intentaré borrar la cache de alguna manera cutre rollo borrar el
fichero o cualquier barbaridad.

Saludos, y muchas gracias.
f.

PD: a ver si no me he cargado nada de las gemas que he puesto log por
todas partes… :slight_smile:


#11

2008/8/4 Fernando G. removed_email_address@domain.invalid:

en vez de localhost:3000:
de estos 2 entornos que me pierdo…
ForumsCommentSweeper.instance.controller.request = tr
Como ves es muy parecido al tuyo.
You might have expected an instance of ActiveRecord::Base.
from /Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/observer.rb:157:in
Entonces… ¿qué diferencia hay entre script/console y script/runner
para que me falle esto? … gracias.

f.

Es ¿imposible? que te suceda eso.

He reproducido tu ejemplo lo mejor que he podido: utilizo una
aplicación Rails 2.0.2, tengo un ForumsComment (con los atributos que
tu mostrabas) y un ForumsCommentSweeper con un after_save que hace
unos logger.info de las tres rutas de ejemplo que pusiste en un correo
anterior (tengo la ruta que yo te propuse en el routes.rb). Tu ejemplo
en la consola funciona perfectamente, tu ejemplo en un archivo
etc/prueba.rb (dentro del directorio de la aplicación Rails, me
imagino) funciona sin protestas (aparecen en el log los mensajes
esperados).

Lo único que he tenido que hacer que no aparece explicitamente en tus
instrucciones es un “require ‘action_controller/test_process’” al
principio de prueba.rb.

Digo que es imposible porque al construir TestRequest se crea una
variable @env que es la que te está diciendo a tí que es nil (cuando
se inicializa invariablemente a un hash vacio).

Te debe faltar algo por ahí o algo, o el ejemplo que pones no es
suficiente para reproducir el problema al completo. Lo siento.

Suerte.


#12

Llego muy tarde al hilo: sólo avisar que convendría usar la misma
versión de Rails para depurar el problema.
Fernando: ¿cuesta mucho esfuerzo replicar el problema en una
aplicación nueva? Si es más o menos rápido, quizá podrías colgarla en
algún sitio para hacer alguna prueba sobre el mismo ejemplo (es un
poco jaleo pero por lo que parece el problema lleva tiempo dando
guerra).


#13

El día 3 de agosto de 2008 2:23, Daniel R.
Troitiñoremoved_email_address@domain.invalid
escribió:> 2008/8/2 Fernando G. removed_email_address@domain.invalid:

Primera cosa, no se si es por el ejemplo, pero ruta_guapa y
ruta_guapa_con_parametro se pueden reducir así:

map.ruta_guapa ‘ruta/guapa/:parametro’, :controller => ‘develop’,
:action => ‘simulate_login_form’, :parametro => nil

Cuando no reciba parámetro Rails pasará nil (que por como es el
ejemplo tú debes ya tener cuidado en simulate_login_form) y además, de
gratis, quita la barra final de “ruta/guapa”, con lo que quedan las
rutas que tú tenías.

Oki… apuntado… gracias.

Product.create :name => ‘Name 22’

Funciona perfectamente… cargar las urls y expira la caché… al
principio me estaba volviendo loco porque no parecía estar expirando
las cachés pero luego ví en el log que al ejecutarse desde consola se
expiraban las cachés de test.host:

  • Expired fragment:
    test.host/topics/este-es-foro-1/esta-es-categoria-1/ver/nuevo-tema,
    lang = [“en”] (0.00007)

en vez de localhost:3000:

  • Expired fragment:
    localhost.3000/topics/este-es-foro-1/esta-es-categoria-1/ver/nuevo-tema,
    lang = [“en”] (0.00005)

Ahora, Daniel, tengo otro problema y es que no consigo dar 2 pasos
seguidos en este punto …

Resulta que todo funciona bien en script/consola … pero me está
fallando en script/runner… seguro que hay algo del modo de funcionar
de estos 2 entornos que me pierdo…

Este es el código que me funciona en consola:


ActiveRecord::Base.observers = [ForumsCommentSweeper]
ActiveRecord::Base.instantiate_observers

ForumsCommentSweeper.instance.controller = ActionController::Base.new

tr = ActionController::TestRequest.new
ForumsCommentSweeper.instance.controller.request = tr
ForumsCommentSweeper.instance.controller.instance_eval( ‘@url =
ActionController::UrlRewriter.new(tr, {})’ )

forums_comment_new = ForumsComment.new()
forums_comment_new.body = “body”
forums_comment_new.user_id = 1000
forums_comment_new.forums_topic_id = 5
forums_comment_new.save

Como ves es muy parecido al tuyo.

Si lo ejecuto en con script/runner (lo meto en un fichero
etc/prueba.rb y le doy a script/runner etc/prueba.rb) me dá un error
al intentar resolver la url:


/Library/Ruby/Gems/1.8/gems/rails-2.0.2/lib/commands/runner.rb:45:
/Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/request.rb:168:in
ssl?': You have a nil object when you didn't expect it! (NoMethodError) You might have expected an instance of ActiveRecord::Base. The error occurred while evaluating nil.[] from /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/request.rb:163:inprotocol’
from (eval):2:in ruta_guapa_url' from /Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:664:insend!’
from
/Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:664:in
method_missing' from /Users/fguillen/Documents/develop-ror/lcforums/app/sweepers/forums_comment_sweeper.rb:13:inafter_save’
from
/Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/observer.rb:157:in
`send’

Llega hasta el sweeper perfectamente pero al intentar resolver la url
peta… en esta línea:

p( "ruta1: #{ruta_guapa_url}" )

:confused:

Entonces… ¿qué diferencia hay entre script/console y script/runner
para que me falle esto? … gracias.

f.


#14

Jajaja, esto me pasa por llegar tarde… XD Enhorabuena a ambos! :smiley:


#15

El día 4 de agosto de 2008 11:54, Daniel R.
Troitiñoremoved_email_address@domain.invalid
escribió:> etc/prueba.rb (dentro del directorio de la aplicación Rails, me

Te debe faltar algo por ahí o algo, o el ejemplo que pones no es
suficiente para reproducir el problema al completo. Lo siento.

Daniel… ahora sí que funciona todo :slight_smile:

Era el:


require ‘action_controller/test_process’

Con esa línea ya funciona desde script/runner.

Muchas gracias por todo. Voy a intentar escribir un post con todo esto
referenciándote sino te importa. (si tienes una web o algo pásamela)

Gracias otra vez.
f.


#16

El día 4 de agosto de 2008 12:17, Raul M. removed_email_address@domain.invalid
escribió:> Llego muy tarde al hilo: sólo avisar que convendría usar la misma

versión de Rails para depurar el problema.
Fernando: ¿cuesta mucho esfuerzo replicar el problema en una
aplicación nueva? Si es más o menos rápido, quizá podrías colgarla en
algún sitio para hacer alguna prueba sobre el mismo ejemplo (es un
poco jaleo pero por lo que parece el problema lleva tiempo dando
guerra).

Si hubiéramos seguido con el poltergeist está claro que habría que
haber hecho una aplicacioncita compartida para agilizar las pruebas.

Sí que es jaleo, pero es como se deben hacer las cosas.

Saludos
f.