モデルのtimestamp(created_at,updated_at)は通常、TimeWithZoneクラスのインスタンスですが、これがTimeクラスになる現象が発生しました。
Timeクラスになるとどうなるかと言うと、railsが時刻を扱う際には通常、DBにはUTCで格納し、入出力時にTimeZoneを加えるようになっていますが、これが行われないのでUTCのまま扱われ、viewにも当然UTCで出力されるようになります。
原因
せっかちな人のために結論を書くと、利用していた論理削除gemrails4_acts_as_paranoidが原因でした。
これをparanoiaに置き換えたところ解決しました。
以下、原因を調べた方法について書きます。
調査
まず、timestampがTimeWithZoneを返す正常なrailsアプリを用意し、DBを比較してみたところ、DBには差がないことが分かりました。
つまりActiveRecordがTimeZoneを付加するところをスキップしているということです。
というわけでActiveRecordのソースを見ます。
timestamp.rbの先頭のコメントに以下のような記述があります。
# If your attributes are time zone aware and you desire to skip time zone conversion to the current Time.zone
# when reading certain attributes then you can do following:
#
# class Topic < ActiveRecord::Base
# self.skip_time_zone_conversion_for_attributes = [:written_on]
# end
どうやら意図せずskip_time_zone_conversion_for_attributesを定義してしまっていたようです。
プロダクトコードを検索しましたが定義している様子はないので、gemのソースを検索してみると、rails4_acts_as_paranoidのソースを発見しました。
def self.extended(base)
base.define_callbacks :recover
base.skip_time_zone_conversion_for_attributes =[base.paranoid_column, :created_at, :updated_at]
end
モジュールがincludeされると自動的にparanoid_column(デフォルトはdeleted_at)とcreated_at,updated_atがTimeZoneを解除されています。論理削除の際にTimeZoneに起因するバグが発生するのを避けたのかも知れません。
と言うわけでこのgemが犯人と分かったので、paranoiaに置き換えて事なきを得ました。
分かってしまえば原因は単純でしたが、トラブルシュートの良い経験になりました。