バッドノウハウ:Railsで他のアプリケーションとセッションを共有するために

Rails ではセッションにモデルのインスタンスをぶち込んだりできるのですが,例えば複数のアプリケーションでログイン情報を共有するためにセッションを使ったりすると,モデルのクラス定義が存在しないとエラーになってしまいます.

アプリA -> User, Blog, Entry
アプリB -> User, Friend, Photo

in アプリA:
  session[:new_entry] = Entry.new
to アプリB:
  Session contains objects whose class definition isn't available.
  Remember to require the classes for all objects kept in the session.
  (Original exception: ERRORMESSAGE [Entry])\n

本来はこれは正しい動作なのですが,古いアプリなどで仕方なく連携する場合など困る場面もあります1.そこで仕方なく,例外を発生させずに空のクラスを定義することで回避します.

module ActionController
  module Session
    class AbstractStore
      class SessionHash < Hash
        private
        def stale_session_check!
          yield
        rescue ArgumentError => argument_error
          if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
            begin
              # Note that the regexp does not allow $1 to end with a ':'
              $1.constantize
            rescue LoadError, NameError => const_error
              eval(%Q{class #{$1}; def unused; true; end; end}, TOPLEVEL_BINDING)
              #raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
            end

            retry
          else
            raise
          end
        end
      end
    end
  end
end

ただこれだけだといろいろ問題になるので,この処理で定義した空クラスを削除します.app/controllers/application_controller.rb などで before_filter を定義して削除します.

def remove_unused_session
  # セッション読み込み時に生成した class のデータを削除
  session.to_hash.each{|k, d| session[k] = nil if session[k].respond_to?(:unused)}
end

あまり使わない方がいいノウハウですが,システム移行などでどうしてもと言う場合には使えるので,こういう手もあると覚えておくのはいいんじゃないでしょうか.


  1. もちろん Hash や Array など Ruby のクラスだけを使えうように書き換えれば問題ないのですが,そこまで工数割けない場合が多いと思います. [return]
 
comments powered by Disqus