LoginSignup
7
4

More than 5 years have passed since last update.

RailsのAction Cacheに強制更新機能を追加

Posted at

実装

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して教えて上げる必要があるというお話。

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4