28 votes

Comment appeler expire_fragment depuis Rails Observer/Model ?

J'ai à peu près tout essayé, mais il semble impossible d'utiliser expire_fragment à partir de modèles ? Je sais que vous n'êtes pas supposé le faire et que c'est non-MVC, mais il doit bien y avoir un moyen de le faire.

J'ai créé un module dans lib/cache_helper.rb avec toutes mes aides d'expiration, dans chacun d'eux, il y a juste un tas d'appels à expire_fragment. J'ai tous mes balayages de cache sous /app/sweepers et j'ai un "include CacheHelper" dans le contrôleur de mon application. l'application lorsqu'il est appelé par les contrôleurs fonctionne bien.

Ensuite, j'ai des démons externes et surtout des tâches cron récurrentes qui appellent une tâche rake qui appelle une certaine méthode. Cette méthode effectue un certain traitement et entre des entrées dans le fichier modèle, après quoi je dois faire expirer le cache.

Quelle est la meilleure façon de procéder, car je ne peux pas spécifier la balayeuse de cache dans le modèle. Les observateurs directs semblent être la meilleure solution, mais ils se plaint du fait que expire_fragment n'est pas défini etc. etc. J'ai même essayé d'inclure les classes de mise en cache d'ActionController dans l'observateur mais cela n'a pas fonctionné. J'aimerais avoir des idées sur la manière de créer une solution pour ce problème. Merci.

38voto

Orion Edwards Points 54939

Clause de non-responsabilité : mes rails sont un peu rouillés, mais ceci ou quelque chose de similaire devrait fonctionner.

ActionController::Base.new.expire_fragment(key, options = nil)

11voto

TheDeadSerious Points 580

La solution fournie par Orion fonctionne parfaitement. En guise d'amélioration et pour des raisons de commodité, j'ai ajouté le code suivant dans le fichier config/initializers/active_record_expire_fragment.rb

class ActiveRecord::Base
  def expire_fragment(*args)
    ActionController::Base.new.expire_fragment(*args)
  end
end

Maintenant, vous pouvez utiliser expire_fragment sur toutes les instances d'ActiveRecord::Base, par exemple. User.first.expire_fragment('user-stats')

7voto

vladr Points 34562

C'est assez facile à faire. Vous pouvez mettre en œuvre la suggestion d'Orion, mais vous pouvez également mettre en œuvre la technique plus large illustrée ci-dessous, qui vous permet d'accéder au contrôleur actuel à partir de n'importe quel modèle et dans n'importe quel but pour lequel vous avez décidé de rompre la séparation MVC (par exemple, pour manipuler le cache des fragments, accéder à current_user la génération de chemins/URL, etc.)

Afin d'avoir accès à le contrôleur de la demande en cours (le cas échéant) de tout modèle ajoutez ce qui suit à environment.rb ou, de préférence, vers un nouveau plugin (par exemple, créer vendor/plugins/controller_from_model/init.rb contenant le code ci-dessous) :

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

Ensuite, 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
  }

  ...

Ensuite, de tout modèle ,

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

Quoi qu'il en soit, dans votre cas particulier, vous pourriez vouloir injecter du code dans vos différents systèmes d'information. ActiveRecord::Base lorsque les classes de contrôleurs concernées sont chargées, de sorte que le code sensible aux contrôleurs réside toujours dans app/controllers/*.rb mais il n'est pas obligatoire de le faire afin d'obtenir quelque chose de fonctionnel (bien que laid et difficile à maintenir).

Amusez-vous bien !

5voto

maurycy Points 1315

Dans un de mes scripts, j'utilise le hack suivant :

  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

Ensuite, une fois que les callbacks ActiveRecord sont appelés, les balayeurs sont en mesure d'appeler expire_fragment.

2voto

Steve Weet Points 15395

Je suis un peu un noob des rails, donc il se peut que ce ne soit pas correct, ou même utile, mais il semble que ce soit une erreur d'essayer d'appeler les actions du contrôleur à partir du modèle.

N'est-il pas possible d'écrire une action dans le contrôleur qui fait ce que vous voulez, puis d'invoquer l'action du contrôleur à partir de votre tâche rake ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X