Railsでリクエストごとのタイムスタンプを取得する方法


リクエストを受けるごとに、その時点の時刻を保持して、コントローラ、モデル、ビュー、ヘルパー、(ownなlib配置の)ライブラリのどこからも参照できるようにするには、どうすべきでしょうか。

ぱっと思いつくのは、ApplicationControllerのフィルターでグローバル変数で管理する(あるいは管理するクラスを利用する)という方法ですが、本当にそれで良いのでしょうか。

管理するクラスで処理する場合、  
グローバル変数にアクセスすべきか。  
クラス変数にアクセスすべきか。  
安全かわかりませんがThreadにインスタンス変数を入れて保管すべきか。  
そのあたりの良し悪しがわからないので教えて下さい。




以下はStack Overflowに投稿された質問 [How to get the time at which a request is made from
the request object in rails
3?](https://stackoverflow.com/questions/5661303)に対する[Romanの回答](https://stackoverflow.com/a/5690430)を少し改変したものです

Rack Middlewareを作成し、リクエストにタイムスタンプを付加します

app/middleware/timestamp.rb

[code]

    class Timestamp
      def initialize(app)
        @app = app
      end
    
      def call(env)
        env[:timestamp] = Time.now
        @app.call(env)
      end
    end
    
[/code]

config/application.rbに`config.middleware.use`を追加します

[code]

    config.middleware.use "Timestamp"
    
[/code]

これで、`request.env[:timestamp]`でタイムスタンプが取得できます

* * *

練習がてらgemにしてみました

[code]

    gem 'rack-request_timestamp'
    
[/code]

してbundle installでインストールした後、config/application.rbに

[code]

    config.middleware.use Rack::RequestTimestamp
    
[/code]

を追加してください。

[code]

    request.env[:timestamp]
    
[/code]

でタイムスタンプが取れます。




## TL;DR

[steveklabnik/request_store](https://github.com/steveklabnik/request_store)
gemを使い、`ApplicationController`のフィルターで値を設定するとよいのではないでしょうか。

[code]

    class ApplicationController < ActionController::Base
      before_action :set_timestamp
    
      def set_timestamp
       RequestStore.store[:timestamp] = Time.now
      end
    end
    
    # コントローラ/モデル/ビュー/ヘルパーなどで
    RequestStore.store[:timestamp]
    
[/code]

`RequestStore`はグローバルに参照できて、スレッドセーフなどにまつわる問題の面倒を見てくれます。

## 理由

グローバル変数やクラス変数はスレッドセーフではないのでお勧めできません。

[Thread#[]](http://docs.ruby-
lang.org/ja/2.2.0/method/Thread/i/=5b=5d.html)や[Thread#[]=](http://docs.ruby-
lang.org/ja/2.2.0/method/Thread/i/=5b=5d=3d.html)を使うとスレッド固有のデータを格納することが出来ます。

`Thread.current[:foo] = :bar`のように値をいれれば、コントローラ/モデル/ビュー/ヘルパーどこからでも参照できます。

単純にリクエストごとに毎回値を設定/参照するだけなら以下のようなミドルウェアを書くといいと思います。

[code]

    class Timestamp
      def initialize(app)
        @app = app
      end
    
      def call(env)
        Thread.current[:timestamp] = Time.now
        @app.call(env)
      end
    
      def self.current
        Thread.current[:timestamp]
      end
    end
    
    # コントローラ/モデル/ビュー/ヘルパーなどで
    Timestamp.current
    
[/code]

しかし、同じスレッドで何度もリクエストが処理されるような場合、スレッドに設定されたデータが残っていて問題が起こる可能性があるので、以下のようにリクエストごとに設定した値を消す処理が必要になってくると思います。

[code]

      def call(env)
        Thread.current[:timestamp] = Time.now
        @app.call(env)
      ensure
        Thread.current[:timestamp] = nil
      end
    
[/code]

`RequestStore`を使うとそのあたりの面倒を見てくれるので楽です。

参考: [Rails - リクエスト単位でグローバルな参照を持たせてAuditログをスッキリ実装したい -
Qiita](http://qiita.com/ainame/items/823b396d560d82194d48)

