patorashのブログ

方向性はまだない

Rails 5.2でcacheのkeyにTimeWithZoneを渡すと落ちる件

表題の通りなのですが、テストも通ってよっしゃー!stagingにデプロイじゃー!と意気揚々としたものの、キャッシュを使っている箇所の表示がされない不具合が…。

とはいっても一部だけで全部のキャッシュが動かなくなったわけではなさそう。調べてみた。

ローカルでキャッシュを有効にする

Railsのアップデートを真面目にやっていれば、これで開発環境でキャッシュが有効になります。

bin/rails dev:cache

もう一度実行すると、無効になります。

因みにこれが何をやっているのかというと、tmp/caching-dev.txtが作られます。config/environments/development.rbで、このファイルがある場合はキャッシュを有効にするという記述があるはずです。

# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist?
  config.action_controller.perform_caching = true
  config.action_controller.enable_fragment_cache_logging = true
  config.cache_store = :dalli_store
  config.public_file_server.headers = {
    'Cache-Control': "public, max-age=#{2.days.to_i}"
  }
else
  config.action_controller.perform_caching = false
  config.cache_store = :null_store
end

frozen errorで落ちる

フラグメントキャッシュの存在確認で、fragment_exist?([@foo, Time.zone.now.beginning_of_month])みたいなことをしていました。今月分はずっとキャッシュさせる的なやつです。ここで落ちていました。

RubyMineのデバッガを使って調べたところ、instrument_fragment_cacheメソッド内のinstrument_payloadメソッドにTimeWithZone型のデータが渡されると落ちるようです。それでは、instrument_payloadは何をやっているのでしょうか?

rails/caching.rb at master · rails/rails · GitHub

リンクを貼っておきますが、Hashを返しているだけ…。

def instrument_payload(key)
  {
    controller: controller_name,
    action: action_name,
    key: key
  }
end

これでなぜ落ちるのが全く理解できないので、とりあえずこのままにしておきます…。できればissueを書きたいところです。

to_sすれば問題ない

fragment_exist?([@foo, Time.zone.now.beginning_of_month.to_s])すれば問題ないことは確認したので、これで乗り切ることに。Date型でもいけるようだったので、to_dateでもいいでしょう。

余談

TimeWithZone型の値をcacheのkeyに設定すると、/だらけの長いものになりました。別に害はないのですが、RailsガイドのCacheのところを読むと、keyにするには、cache_keyメソッドかto_paramメソッドが定義されている必要がある、とあります。

Rails のキャッシュ: 概要 | Rails ガイド

TimeWithZoneにはto_paramメソッドが定義されています。

Time.zone.now.to_param #=> "2019-01-17 11:19:10 +0900"

しかし、これが使われていません。掘り下げてみたところ、to_paramが呼ばれる前に、to_aメソッドがある場合はto_aメソッドを呼んだ後、再帰的に処理をしていました。

rails/cache.rb at master · rails/rails · GitHub

そして、TimeWithZoneにはto_aメソッドが定義されており、配列に変換されて処理された結果、長い文字列になっていました。

Time.zone.now.to_a #=> [22, 26, 11, 17, 1, 2019, 4, 17, false, "JST"]

うーん、to_paramで処理してほしいような…。