Ruby Forum Rails-ES > Expirar cachés sin pasar por el Control ler

Posted by Fernando Guillen (Guest)
on 31.07.2008 17:05
(Received via mailing list)
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.
Posted by Emili Parreño (Guest)
on 31.07.2008 17:40
(Received via mailing list)
No utilices cache, eso es de maricas :)
Bromas a parte, ni idea. Uf como me afecta el calor.

El 31 de julio de 2008 17:04, Fernando Guillen<fguillen.mail@gmail.com>
escribió:
Posted by Francesc Esplugas (fesplugas)
on 01.08.2008 11:00
(Received via mailing list)
Usando observers no te funcionaria?

El observer mira directamente el modelo, así que podrias decirle que
desde allí se expiren las caches ...
Posted by Fernando Guillen (Guest)
on 01.08.2008 12:02
(Received via mailing list)
El día 1 de agosto de 2008 11:00, Francesc Esplugas
<francesc.esplugas@gmail.com> 
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.
Posted by Daniel Rodriguez Troitiño (Guest)
on 01.08.2008 17:18
(Received via mailing list)
On Fri, Aug 1, 2008 at 12:01, Fernando Guillen <fguillen.mail@gmail.com> 
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.
Posted by Francesc Esplugas (fesplugas)
on 01.08.2008 20:04
(Received via mailing list)
A mi esto me da un error ...

    ProductSweeper.instance.controller.request = TestRequest.new

No existe la clase TestRequest.

2008/8/1 Daniel Rodriguez Troitiño <notzcoolx@yahoo.es>:
Posted by Daniel Rodriguez Troitiño (Guest)
on 01.08.2008 20:36
(Received via mailing list)
2008/8/1 Francesc Esplugas <francesc.esplugas@gmail.com>:
> 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.
Posted by Daniel Rodriguez Troitiño (Guest)
on 01.08.2008 20:38
(Received via mailing list)
2008/8/1 Daniel Rodriguez Troitiño <notzcoolx@yahoo.es>:
> 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.
Posted by Fernando Guillen (Guest)
on 02.08.2008 18:17
(Received via mailing list)
El día 1 de agosto de 2008 17:17, Daniel Rodriguez 
Troitiño<notzcoolx@yahoo.es> 
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:in `ruta_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:in
`method_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:in
`send'


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.. :)
Posted by Daniel Rodriguez Troitiño (Guest)
on 03.08.2008 02:24
(Received via mailing list)
2008/8/2 Fernando Guillen <fguillen.mail@gmail.com>:
>
> 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.
Posted by Fernando Guillen (Guest)
on 04.08.2008 09:31
(Received via mailing list)
El día 3 de agosto de 2008 2:23, Daniel Rodriguez 
Troitiño<notzcoolx@yahoo.es> 
escribió:> 2008/8/2 Fernando Guillen <fguillen.mail@gmail.com>:
> 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:in
`protocol'
  from (eval):2:in `ruta_guapa_url'
  from 
/Library/Ruby/Gems/1.8/gems/actionpack-2.0.2/lib/action_controller/caching.rb:664:in
`send!'
  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:in
`after_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}" )

:/

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

f.
Posted by Daniel Rodriguez Troitiño (Guest)
on 04.08.2008 11:57
(Received via mailing list)
2008/8/4 Fernando Guillen <fguillen.mail@gmail.com>:
> 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.
Posted by Raul Murciano (raul)
on 04.08.2008 12:17
(Received via mailing list)
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).
Posted by Fernando Guillen (Guest)
on 04.08.2008 12:19
(Received via mailing list)
El día 4 de agosto de 2008 11:54, Daniel Rodriguez 
Troitiño<notzcoolx@yahoo.es> 
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 :)

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.
Posted by Raul Murciano (raul)
on 04.08.2008 12:20
(Received via mailing list)
Jajaja, esto me pasa por llegar tarde... XD Enhorabuena a ambos! :D
Posted by Fernando Guillen (Guest)
on 04.08.2008 12:21
(Received via mailing list)
El día 4 de agosto de 2008 12:17, Raul Murciano <raul@murciano.net> 
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.