2009年05月29日

Railsでtimestamp with time zone

最近Ruby on Railsの勉強中なのですが、Railsの流儀に逆らわないようにするのは大変ですね。

さて、そんなRailsでPostgreSQLのtimestamp with time zone型を取り扱おうと思ったのですが、どうも意図したとおり動いてくれません。

timestamp with time zone型はDB内部ではグリニッジ標準時(GMT)で値を持っていてクライアントで指定したタイムゾーンで変換して時間を返してしてくれるのですが、config/environment.rb

config.time_zone = 'UTC'
を指定していても、Railsの動作マシンのローカルタイムゾーンでDBから時刻を取得してくれます。

もちろん、

config.time_zone = 'Tokyo'
なんか指定した日にはDBが9時間加算してくれた時間をさらに9時間加算してくれて酷いことになります。

まぁ、config.time_zone自体をコメントアウトすれば、Rails 2.1未満のようにタイムゾーン無しで動いてくれるらしいのですが、昔の仕様に戻して動かすのはRailsの流儀ではないと思い、色々調べた結果下記のように対応することにしました。

まず、アプリケーションのフォルダに lib/postgresql_set_timezone_to_gmt.rb と言う名称で下記の内容のファイルを作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
  # /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/postgresql_adapter.rb
  # line 950
  # Configures the encoding, verbosity, and schema search path of the connection.
  # This is called by #connect and should not be called manually.
  def configure_connection
    execute("SET TIMEZONE TO 'GMT'")
    if @config[:encoding]
      if @connection.respond_to?(:set_client_encoding)
        @connection.set_client_encoding(@config[:encoding])
      else
        execute("SET client_encoding TO '#{@config[:encoding]}'")
      end
    end
    self.client_min_messages = @config[:min_messages] if @config[:min_messages]
    self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
  end
end

(これはRailsがPostgresql DBに接続した際にエンコーディングを設定するメソッドを上書きするためのコードで7行目でクライアントのタイムゾーンをGMTに指定しています)

次に config/environment.rb の末尾にでも

require 'postgresql_set_timezone_to_gmt'
を追加して完了です。

これで、timestamp with time zone型の場合は時刻をGMT(UTC)で取得してくれるので、Railsのタイムゾーン設定が良い感じに動作するようになるんじゃないかと思っています。

まぁ、思いついたばかりでまだテストしてなかったりしますので、ご利用前にテストをお忘れ無く。
(と言うか、そもそもこんなコトしなくてもうまくやれる方法があるのかも知れませんし、github辺りに同様のプラグインなどがコミットされているのかも知れませんが、私には見つかりませんでした。
と言うか、あったら教えてください(^^;)

なお、上記を作成する際に下記のサイトを参考とさせて頂きました。

Posted by Takuchan at 2009年05月29日 01:21 | トラックバック(0)