Rails 4.2.x と Oracle の組み合わせでタイムゾーンが適用されず日時検索できない問題への秘伝のタレを Gem にしました。
ざっくり言うと config/initializers/oracle.rb に書くようなモンキーパッチをプラギン化したものになります。もしかしたら誰得だよ的な Gem かもですが、Oracle で Rails な人たちどうしているんだろう?ということで公開してみました。
もちろん依存ライブラリ本体にパッチを送れるのがベストなのですが、手が届かずで思いつく次善策となります (あと Gem としてのテストコードはあとで書きたい気持ち) 。
使い方
Rails プロジェクトであれば、こんな感じに Gemfile に追加して bundle update することになります。
gem 'activerecord-oracle_enhanced-adapter', '~> 1.6.0'
gem 'activerecord-oracle_enhanced-adapter-monky_patch_755'
(snip)
gem 'rails', '~>4.2'
解決する問題
Rails 4.1 系で ActiveRecord::Base.where メソッドの引数に日付オブジェクトを渡すと TO_DATE 関数が適用された SQL が実行されます。
> created_at = Model.first.created_at
> Model.where(created_at: created_at)
Model Load (5.1ms) SELECT "MODELS".* FROM "MODELS" WHERE "MODELS"."CREATED_AT" = TO_DATE('2015-12-28 03:55:04','YYYY-MM-DD HH24:M
I:SS')
=> [#<Model id: ***snip***>]
Rails 4.2 系で同様のコードを実行すると、TO_DATE 関数が適用されずタイムゾーン分の時差 (JST だと UTC+9) が調整されないため期待する結果オブジェクトを得られないという事象がおきてました。
> created_at = Model.first.created_at
> Model.where(created_at: created_at)
Model Load (11.8ms) SELECT "MODELS".* FROM "MODELS" WHERE "MODELS"."CREATED_AT" = :a1 [["created_at", 2015-12-28 03:55:04 UTC]]
=> []
本稿で取り扱っている activerecord-oracle_enhanced-adapter-monky_patch_755 では、本質的な問題解決の仕方ではなく、日時オブジェクトに対して時差分を調整してバインド変数で実行するアプリーチで対応しております (時差分を Time#utc_offset で調整しています) 。
おまけ: 名前の由来
名付けるとき、これは名前が長過ぎてダメなだなあと思ったのですが、逆の考え方ではやく外したい Gem として認知されるにはむしろ悪くないのではと思い「えいやー」とリリースしました。サフィックスになっている 755 の数字は、oracle-enhanced に挙げている ISSUE の番号です。このあたりはかつて Java のライブラリで、JSR 175 から名前を付けた backport175 というライブラリの名前に影響を受けています。
もう少しまとも(?)な意図としては、Gemfile には gem 名を ASCII 順に並べることを前提に、ISSUE 元になっている Gem と隣り合わせの形にした方が後々に意図が伝わるのではと思ってはいます。
余談ですが、Gemfile に書く gem 名を ASCII 順に並べるのは、ASCII 順ということ以外にその並びに意味を持っているのではという考えをさせたくないという意図があります。
おわりに
この問題は、UTC+0 のタイムゾーンに特化しているアプリケーションでないと結構な確率で踏むと思っており、それぞれの Rails アプリケーションで秘伝のタレを持っているか、Rails 4.2.x にアップグレードしていないかなのかな (あるいはもっとミドルウェアに近いレイヤーでうまいことやれる?) と思っており、後者のアップグレードできない問題を抱えている方にはご参考になればなと思っています。