実装
caches_action
を含んだ以下のようなRails拡張をした。
簡単に言うと、caches_action
でキャッシュしたアクションをno_cache
というパラメータを付けてアクセスすることでキャッシュを強制更新するというもの。
module ControllerConcerns
module Caching
extend ActiveSupport::Concern
module ClassMethods
def clearable_caches_action(*args)
options = args.extract_options!
cache_path = options[:cache_path]
# This approach makes "expire -> read -> write" flow, which is a little time consuming because the read after the expire always return nothing.
# However there's no way to achieve this expect hacking action_controller code.
before_filter if: lambda{ params[:no_cache] == '1' && args.include?(action_name.to_sym) } do
if cache_path
if cache_path.respond_to?(:call)
opt = instance_exec self, &cache_path
else
opt = cache_path
end
else
opt = {}
end
expire_action opt.except(:no_cache)
end
caches_action(*args, options)
end
end
end
end
便利なので同等の機能をRailsにPR出しておいた。
https://github.com/rails/actionpack-action_caching/pull/17
このPRの方が更新のためのキーが指定できるのと、expire -> read -> writeでなく、最初から直接writeになるのでパフォーマンスの面でも良い。
テスト
Anonymous Controllerでcaches_action
を含んだSpecテストはweb上にもあまり例がなかった。
普通にやると
require 'spec_helper'
RSpec.describe ControllerConcerns::Caching, :type => :controller do
controller do
self.perform_caching = true
clearable_caches_action :index
def index
render text: Time.now.to_f.to_s
end
end
before do
@routes.draw { resources :anonymous }
end
context "without no_cache key" do
it "caches the first request" do
get :index
body1 = response.body
get :index
body2 = response.body
expect(body1).to eq body2
end
end
context "with no_cache key" do
it "overwrites the first request" do
get :index
body1 = response.body
get :index, no_cache: 1
body2 = response.body
expect(body1).not_to eq body2
get :index
body3 = response.body
expect(body2).to eq body3
end
end
end
のように書けるが、
ActionController::RoutingError:
No route matches {:format=>nil, :controller=>"anonymous"}
という謎のエラーがcaches_action
内で出てしまった。
どうやらcaches_action
内で呼び出しているurl_for
がエラーを返している。
デバッグにデバッグを重ねweb上で関連情報を探しまわり、やっとのことで解決策を発見。
RSpec.describe ControllerConcerns::Caching, :type => :controller do
controller do
...
end
before do
@routes.draw { resources :anonymous }
###################### ADDED ##########################
allow(controller).to receive(:_routes).and_return(@routes)
#######################################################
end
context "without no_cache key" do
it "caches the first request" do
...
end
end
context "with no_cache key" do
it "overwrites the first request" do
...
end
end
end
要は、controllerがurl_for
で使うroutingはRails Appのroutesしか把握していないので、@routes.draw
でanonymous routesを書いてcontrollerにstubして教えて上げる必要があるというお話。