Как вызвать 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
Дайте мне знать, если это полезно!