Ktouth Brand. on Web

け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト



[2011年03月10日]

セッション情報が復帰出来ない?

2011年03月11日 09:17更新 筆者:K.Ktouth

新サイトのシステムを実装するにあたり、セッション管理は絶対に要求される機能なわけですが、rails にしろ ramaze にしろ「本格運用はサーバーを建てて」という仕組みになっていてあまり CGI での運用には優しくない(笑)
で、とりあえずレスポンスの早さとかをチェックしようとサーバーでテストしていて気づきました。

セッションが機能していないげー

ローカルで Webrick サーバでテストしている時はメモリ内にセッション情報が保持されるのでぜんぜん問題ないんですが…… CGI は当然ながら毎回プログラムを起動する必要があるわけで。それに合わせたセッション情報の保持システムが準備されているんです。
これの原因究明に少々手間取ったので自分メモ。
先に書いておくと ramaze というか Ramaze::Cache::Sequel (Sequelを使ったキャッシュ機構) 内部での Sequel のエラーでした。ramaze 側で例外を握りつぶしているせいで見つけるのに手間取りました。
多分このエラーは ruby 自体のバージョンで挙動が変化しているために発生したと推測してます。確認してませんが(ぉ
さくらのレンタルサーバはさっさとパッチレベルを上げるべきです。ええ。

(以下、細かい部分)

問題は Sequel の デシリアライザ

サーバ上では以前自分で日記に書いたものを参考にキャッシュ機能に Sequel を使用したものを設定しました。ところがセッションに記録した通知メッセージがページ遷移後に表示されないという状態。
セッションを保持したデータベースファイルを一端ダウンロードし、中身を確認してみたところ……きちんと保持されています。この時点でおかしいわけです。通知メッセージは session.flash プロパティに保存しているので、ページ遷移後は消去されている必要があるわけです。それが消えていない。
つまり、セッション情報の書き込みは成功しているが読込に失敗していると言うことです。もちろんセッションのキャッシュ機能は ramaze 標準の機能をそのまま使っています。さて困った。

デバッグメッセージを仕込んでどこで挙動がおかしくなっているかをチェックしてみたところ……意外なことが判明。
ramaze ではなく Sequel 側でしたぎょ

ramaze 側ではキャッシュ情報を保持するために以下のような モデルクラスを作成しています。

# ... /lib/ramaze/cache/sequel.rb より抜粋
class Table < ::Sequel::Model(:ramaze_cache)
 plugin :schema
 plugin :serialization, :marshal, :value # 問題はここ

 # Define the schema for this model
 set_schema do
  primary_key :id

  String :key,  :null => false, :unique => true
  String :value, :text => true

  Time :expires
 end
end

定義はこれだけ。やっていることはほとんど基本通りの変哲もないシンプルなモデルです。
で、エラーが出ているのは以下の部分。

def cache_fetch(key, default = nil)
 # Retrieve the data and return it
 record = @store[:key => namespaced(key)]
 record.value rescue default # 問題はここ
end

最後の行、value プロパティを参照している際にエラーが出ているのですが、直後の rescue 節で無かったことにされています。
このあたりまで全くお手本通りの利用方法で修正する部分はありません。
で、例外はどこで出ているかというと……

# ... /lib/sequel/plugins/serialization.rb より抜粋
# Add serializated attribute acessor methods to the serialization_module
def define_serialized_attribute_accessor(format, *columns)
 m = self
 include(self.serialization_module ||= Module.new) unless serialization_module
 serialization_module.class_eval do
  columns.each do |column|
   m.serialization_map[column] = format
   define_method(column) do
    if deserialized_values.has_key?(column)
     deserialized_values[column]
    else
     deserialized_values[column] = deserialize_value(column, super()) # 問題はここ
    end
   end
   define_method("#{column}=") do |v|
    changed_columns << column unless changed_columns.include?(column)
    deserialized_values[column] = v
   end
  end
 end
end

エラーメッセージは「スーパークラスにメソッドが見つかりません」、つまり super() の呼び出しでエラーが出ています。
どうも super やクラスメソッドなどの呼び出しあたりで挙動が変化したタイミングがあるのか、バージョンの古い ruby だとこれがうまく行かないものがあるっぽいです。現在さくらのレンタルサーバにある ruby ではこのあたりがうまく行かない模様。
原因も対処すべき部分も Sequel 内部。さてどうするか?

こうしました。

class Ramaze::Cache::Sequel::Table
 def value
  Marshal.load(self[:value].unpack('m')[0]) # Easy FIX
 end
end

本来は「super()」部分を「self[column]」に書き換えたデシリアライズメソッドを準備すべきなんでしょう。
が、今回この機能を利用しているのはこの部分だけなので、簡単に決め打ちしてみました。
バージョン依存しそうなバグっぽいのでもうちょっと検証した後にMLにでも上げて見よう……

本日のリンク元
その他のリンク元
検索