lasciva blog

開発して得た知見やwebビジネスのストック

Rails Jbuilderのcacheのキーの挙動の調査メモ

概要

RailsでjsonAPIを提供しているプロジェクトで、Jbuilderを使ったときのcacheのkeyの挙動を調べた際のメモ。

github.com

Jbuilderでは以下のようにcacheを使うことができる。
この引数のkeyには、どのようなオブジェクトが入ってもいいようになっている。

json.cache!(key, expires_in: 5.minutes) do
  json.hoge :foo
end

一部でActionController::Parametersインスタンスが用いられていた箇所があったが、
ActionController::ParametersはRails5から to_unsafe_hpermitを使わないと、中身が無視されるような仕様になったので、
この場合どういう挙動をするのかがわからなかったので調べた。

結果としては、ActionController::Parametersをそのまま使っても問題なかった。

※ 注

  • リクエストパラメータがコントロールできない環境では、そもそも ActionController::Parametersを直接使うべきではない。
  • 今回は内部のアプリのAPIで、リクエストパラメータが十分制限される環境なので、使っていた。

詳細

環境

実装

実装としては、以下のようになっており ActiveSupport::Cache.expand_cache_key内の返り値が使われていた。 https://github.com/rails/jbuilder/blob/91c4eeed484abd28cada2628af1f45f3de0cb0f5/lib/jbuilder/jbuilder_template.rb#L150

  def _cache_key(key, options)
    name_options = options.slice(:skip_digest, :virtual_path)
    key = _fragment_name_with_digest(key, name_options)

    if @context.respond_to?(:combined_fragment_cache_key)
      key = @context.combined_fragment_cache_key(key)
    else
      key = url_for(key).split('://', 2).last if ::Hash === key
    end

    ::ActiveSupport::Cache.expand_cache_key(key, :jbuilder)
  end

  def _fragment_name_with_digest(key, options)
    if @context.respond_to?(:cache_fragment_name)
      # Current compatibility, fragment_name_with_digest is private again and cache_fragment_name
      # should be used instead.
      @context.cache_fragment_name(key, options)
    elsif @context.respond_to?(:fragment_name_with_digest)
      # Backwards compatibility for period of time when fragment_name_with_digest was made public.
      @context.fragment_name_with_digest(key)
    else
      key
    end                                                                                                                                                                                                                                                               end

ActiveSupport::Cache.expand_cache_key内の処理は以下のようになっていた。 https://github.com/rails/rails/blob/master/activesupport/lib/active_support/cache.rb#L80

module ActiveSupport
  module Cache
    class << self
      def expand_cache_key(key, namespace = nil)
        expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
        if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
          expanded_cache_key << "#{prefix}/"
        end

        expanded_cache_key << retrieve_cache_key(key)
        expanded_cache_key
      end

      private
        def retrieve_cache_key(key)
          case
          when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
          when key.respond_to?(:cache_key)              then key.cache_key
          when key.is_a?(Array)                         then key.map { |element| retrieve_cache_key(element) }.to_param
          when key.respond_to?(:to_a)                   then retrieve_cache_key(key.to_a)
          else                                               key.to_param
          end.to_s
        end
    end
  end
end

to_aした後に、 to_paramされて、以下のようになる。

params # { hoge: :aaa, foo: :bbb }
ActiveSupport::Cache.expand_cache_key(params) # => "hoge/aaa/foo/bbb"