Как вызвать expire_fragment из Rails Observer/Model?

Я почти все перепробовал, но кажется невозможным использовать expire_fragment из моделей? Я знаю, что вы не должны, и это не MVC, но наверняка есть какой-то способ сделать это.

Я создал модуль в lib/cache_helper.rb со всеми моими помощниками expire, в каждом из которых есть только несколько вызовов expire_fragment. Я установил все мои очистители кэша в /app/sweepers и включил "include CacheHelper" в контроллере приложения, так что срок действия кэша в приложении при вызове через контроллеры работает нормально.

Тогда у меня есть некоторые внешние демоны и особенно некоторые повторяющиеся задачи cron, которые вызывают задачу rake, которая вызывает определенный метод. Этот метод выполняет некоторую обработку и вводит записи в модель, после чего мне нужно завершить кеш.

Каков наилучший способ сделать это, поскольку я не могу указать очиститель кэша в модели. Прямые наблюдатели кажутся лучшим решением, но потом они жалуются на то, что expire_fragment не определен и т. Д. И т. Д. Я даже пытался включить классы кэширования ActionController в наблюдателя, но это не сработало. Я хотел бы некоторые идеи о том, как создать решение для этого. Благодарю.

8 ответов

Решение

Отказ от ответственности: Мои рельсы немного ржавые, но это или что-то вроде этого должно работать

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

Решение, предоставленное Orion, работает отлично. В качестве улучшения и для удобства я поместил следующий код в config/initializers/active_record_expire_fragment.rb

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

Теперь вы можете использовать expire_fragment во всех экземплярах ActiveRecord::Base, например User.first.expire_fragment('user-stats')

Это довольно легко сделать. Вы можете реализовать предложение Ориона, но вы также можете реализовать более широкую технику, показанную ниже, которая дает вам доступ к текущему контроллеру из любой модели, и для какой бы цели вы не решили разделить MVC (например, возиться с фрагментным кешем, получить доступ к current_user, создание путей /URL и т. д.)

Чтобы получить доступ к контроллеру текущего запроса (если есть) из любой модели, добавьте следующее в environment.rb или, что гораздо лучше, к новому плагину (например, создать vendor/plugins/controller_from_model/init.rb содержащий код ниже):

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

Затем в 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
  }

  ...

Тогда из любой модели

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

Во всяком случае, в вашем конкретном случае вы можете ввести код в различные ActiveRecord::Base потомки, когда загружаются соответствующие классы контроллера, так что фактический код с поддержкой контроллера все еще находится в app/controllers/*.rb, но это не обязательно, чтобы получить что-то функциональное (хотя и некрасивое и сложное в обслуживании).

Повеселись!

В одном из моих скриптов я использую следующий хак:

  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

Затем, после вызова обратных вызовов ActiveRecord, подметальные машины могут вызывать expire_fragment.

Не проще ли будет просто передать текущий контроллер в качестве аргумента вызова метода модели? Вроде следующего:

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

Вы можете получить доступ ко всем открытым методам и свойствам контроллера изнутри модели. Пока вы не измените состояние контроллера, все должно быть в порядке.

Я немного любопытен, так что это может быть неправильно или даже полезно, но кажется неправильным пытаться вызывать действия контроллера изнутри модели.

Разве нельзя написать действие в контроллере, которое делает то, что вы хотите, а затем вызвать действие контроллера из вашей задачи rake?

Почему бы не сделать так, чтобы ваши внешние задачи rake вызывали метод expiry на контроллере. Тогда вы все еще поддерживаете MVC, вы не строите в зависимости от какого-то ограниченного взлома и т. Д.

В этом отношении, почему бы вам просто не включить все функции демона / внешнего контроллера и заставить rake / cron просто вызвать это? Это было бы легче поддерживать.

- MarkusQ

Это может не сработать для того, что вы делаете, но вы можете определить собственный обратный вызов для вашей модели:

class SomeModel < ActiveRecord::Base
    define_callback :after_exploded

    def explode
        ... do something that invalidates your cache ...
        callback :after_exploded
    end
end

Затем вы можете использовать уборочную машину, как обычно:

class SomeModelSweeper < ActionController::Caching::Sweeper
  observe SomeModel 

    def after_exploded(model)
      ... expire your cache
    end
end

Дайте мне знать, если это полезно!

Другие вопросы по тегам