Rails2とRails3でセッションを共有する

サブドメインが異なるRails2アプリとRails3アプリでセッションを共有したい場合、Cookie Session Storeに互換性が無いみたいでそのまま共有しようとすると問題が発生します。

具体的にどういう問題があるかというと、

  • Rails2はCookieのKeyをSymbolとして扱う。しかしRails3はStringとして扱っている。
  • Flash周りは何とマーシャルしてCookieに保存されている。Rails2とRails3でモジュール・クラス名が異なっているのでアンマーシャル時にuninitialized constantが発生する。

まず1つ目の問題ですが、幸いなことにRails3はKeyがSymbolでも読み込みが可能です。ただし一度でも読み込むとStringに変換されてRails2からは読めなくなります。ということは、Rails2でStringなKeyを認識出来るようにすれば、この問題は解決出来そうです。

2つ目の問題は、双方に存在しないモジュール・クラスを予め定義しておけばエラーは出ないはずです。

以上を踏まえてRails2、Rails3にモンキーパッチを当てます。

Rails2側

config/initializersにaccept_rails3_session.rbなど適当に名前をつけて下記ソースをコピペします。

module ActionDispatch
  module Flash
    class FlashHash < Hash
      def method_missing(m, *a, &b)
      end
    end
  end
end
 
module ActionController
  module Session
    class CookieStore
      private
      def unmarshal(cookie)
        if cookie
          data = persistent_session_id!(@verifier.verify(cookie))
          data.symbolize_keys!
        end
      rescue ActiveSupport::MessageVerifier::InvalidSignature
        nil
      end
 
      def requires_session_id?(data)
        if data
          data.respond_to?(:key?) && !data.key?(:session_id) && !data.key?("session_id")
        else
          true
        end
      end
    end
  end
end

config/initializers/session_store.rbを開き、:key、:secret、:domainを設定します。この3つはRails3側も同じにする必要があります。

ActionController::Base.session = {
  :key    => '_session',
  :secret => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  :domain => '.example.com'
}

Rails3側

config/initializersにaccept_rails2_session.rbなど適当に名前をつけて下記ソースをコピペします。

module ActionController
  module Flash
    class FlashHash < Hash
      def method_missing(m, *a, &b)
      end
    end
  end
end

config/initializers/session_store.rbを開き、:keyと:domainをRails2側で設定した値と同じ値にします。

AppName::Application.config.session_store :cookie_store,
                                          :key    => '_session',
                                          :domain => '.example.com'

config/initializers/secret_token.rbを開き、Rails2側で設定した:secretと同じ値にします。

AppName::Application.config.secret_token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

以上です!これでRails2とRails3でセッションを共有出来るようになります。

まだ残る問題点

  • Rails2側がRuby1.8.xでRails3側がRuby1.9.2の構成で、Rails2で作成したFlash MessageをRails3側で読み出すとincompatible character encodingsになります。
  • Rails2側で作成したFlash MessageをRails3側で呼び出すとFlashが消えてくれません。

Flash周りはもう少し考えないと厳しいかもしれません。。。