ruby on rails - tutorial - Cómo llamar a expire_fragment desde Rails Observer/Model?
ruby on rails usos (8)
He intentado prácticamente todo, pero parece imposible usar expire_fragment de los modelos. Sé que se supone que no debes hacerlo y no es MVC, pero seguramente habrá mucho para hacerlo.
Creé un módulo en lib / cache_helper.rb con todos mis ayudantes caducados, dentro de cada uno hay solo un montón de llamadas expira_fragment. Tengo todas mis barredoras de caché configuradas en / app / sweepers y tengo un "include CacheHelper" en mi controlador de aplicación, por lo que el caché que expira en la aplicación cuando se llama a través de los controladores funciona bien.
Entonces las cosas son que tengo algunos daemons externos y especialmente algunas tareas cron recurrentes que llaman una tarea de rake que llama a cierto método. Este método realiza algunas entradas de procesamiento y entradas en el modelo, después de las cuales necesito caducar el caché.
¿Cuál es la mejor manera de hacerlo ya que no puedo especificar la limpieza de caché dentro del modelo? Los observadores directos parecen ser la mejor solución, pero luego se queja de que expire_fragment no está definido, etc., incluso intenté incluir las clases de caché de ActionController en el observador, pero eso no funcionó. Me encantaría algunas ideas de cómo crear una solución para esto. Gracias.
¿No será más fácil y limpio simplemente pasar el controlador actual como argumento de la llamada al método modelo? Como siguiendo:
def delete_cascade(controller)
self.categories.each do |c|
c.delete_cascade(controller)
controller.expire_fragment(%r{article_manager/list/#{c.id}.*})
end
PtSection.delete(self.id)
controller.expire_fragment(%r{category_manager/list/#{self.id}.*})
end
Puede acceder a todos los métodos y propiedades públicos del controlador desde el modelo. Siempre que no modifique el estado del controlador, debería estar bien.
¿Por qué no hacer que sus tareas externas de rake llamen al método de vencimiento en el controlador? Entonces sigues siendo compatible con MVC, no dependes de algún truco de scoping, etc.
Para el caso, ¿por qué no pone toda la funcionalidad daemon / externa en un controlador y tiene rake / cron solo llame eso. Sería mucho más fácil de mantener.
- MarkusQ
Descargo de responsabilidad: Mis rieles están un poco oxidados, pero esto o algo así debería funcionar
ActionController::Base.new.expire_fragment(key, options = nil)
En uno de mis scripts utilizo el siguiente hack:
require ''action_controller/test_process''
sweepers = [ApartmentSweeper]
ActiveRecord::Base.observers = sweepers
ActiveRecord::Base.instantiate_observers
controller = ActionController::Base.new
controller.request = ActionController::TestRequest.new
controller.instance_eval do
@url = ActionController::UrlRewriter.new(request, {})
end
sweepers.each do |sweeper|
sweeper.instance.controller = controller
end
Luego, una vez que se invocan las devoluciones de llamada de ActiveRecord, los sweepers pueden invocar expire_fragment.
Es posible que esto no funcione para lo que está haciendo, pero es posible que pueda definir una devolución de llamada personalizada en su modelo:
class SomeModel < ActiveRecord::Base
define_callback :after_exploded
def explode
... do something that invalidates your cache ...
callback :after_exploded
end
end
Luego puede usar una barredora como lo haría normalmente:
class SomeModelSweeper < ActionController::Caching::Sweeper
observe SomeModel
def after_exploded(model)
... expire your cache
end
end
¡Avíseme si esto es útil!
Esto es bastante fácil de hacer. Puede implementar la sugerencia de Orion, pero también puede implementar la técnica más amplia ilustrada a continuación, que le da acceso al controlador actual desde cualquier modelo y para cualquier propósito que haya decidido romper la separación MVC (por ejemplo, jugar con la memoria caché de fragmentos, acceder al current_user
, generando rutas / URLs, etc.)
Para obtener acceso al controlador de la solicitud actual (si corresponde) de cualquier modelo , agregue lo siguiente a environment.rb
o, muy preferiblemente, a un nuevo complemento (por ejemplo, create vendor/plugins/controller_from_model/init.rb
contenga el siguiente código) )
module ActiveRecord
class Base
protected
def self.thread_safe_current_controller #:nodoc:
Thread.current[:current_controller]
end
def self.thread_safe_current_controller=(controller) #:nodoc:
Thread.current[:current_controller] = controller
end
# pick up the correct current_controller version
# from @@allow_concurrency
if @@allow_concurrency
alias_method :current_controller, :thread_safe_current_controller
alias_method :current_controller=, :thread_safe_current_controller=
else
cattr_accessor :current_controller
end
end
end
Luego, en app/controllers/application.rb
,
class ApplicationController < ActionController::Base
before_filter { |controller|
# all models in this thread/process refer to this controller
# while processing this request
ActiveRecord::Base.current_controller = controller
}
...
Entonces, desde cualquier modelo ,
if controller = ActiveRecord::Base.current_controller
# called from within a user request
else
# no controller is available, didn''t get here from a request - maybe irb?
fi
De todos modos, en su caso particular, es posible que desee insertar código en sus diversos descendientes ActiveRecord::Base
cuando se carguen las clases de controlador relevantes, de modo que el código actual controlado por el controlador todavía resida en la app/controllers/*.rb
, pero no es obligatorio hacerlo para obtener algo funcional (aunque feo y difícil de mantener).
¡Que te diviertas!
La solución provista por Orion funciona perfectamente. Como una mejora y por conveniencia, he puesto el siguiente código en config/initializers/active_record_expire_fragment.rb
class ActiveRecord::Base
def expire_fragment(*args)
ActionController::Base.new.expire_fragment(*args)
end
end
Ahora puede usar expire_fragment en todas las instancias de ActiveRecord :: Base, por ejemplo User.first.expire_fragment(''user-stats'')
Soy un poco novato en los rieles, por lo que puede no ser correcto, o incluso útil, pero parece incorrecto tratar de llamar a las acciones del controlador desde dentro del modelo.
¿No es posible escribir una acción dentro del controlador que hace lo que desea y luego invocar la acción del controlador desde dentro de su tarea de rake?