Ruby オブジェクトをファイル(または文字列)に書き出したり、読み戻したり する機能を提供するモジュール。
Ruby 2.1.0 リファレンスマニュアル | module Marshal
それが Marshal です。
オブジェクトをファイルに書きだすということで、HashをMarshal.dump
して文字列化したものをRedisやMongoといったデータストアに保存するという使い方があるかと思います。
(世間一般ではjsonでやることのほうが多そうなイメージがありますが)
hoge = {
foo: 'bar'
}
p str = Marshal.dump(hoge) # => "\x04\b{\x06:\bfooI\"\bbar\x06:\x06ET"
# strをデータストアに入れる
p Marshal.load(str) # => {:foo=>"bar"}
で、この前ハマったのが以下のパターン。
require 'redis'
require 'date'
conf = {
# ここにはRedisの設定が記述してあります
}
redis = Redis.new(conf)
date = Date.new(1999, 2, 24)
redis.set('key', Marshal.dump(date))
require 'redis'
conf = {
# set.rbと同様のRedisの設定が記述してあります
# 本来はちゃんと共通のmodule化してます。
}
redis = Redis.new(conf)
Marshal.load(redis.get('key'))
# => TypeError: instance of Date needs to have method `marshal_load'
えっ。
Marshal.dump
は
- 名前のついてないオブジェクト
- システムがオブジェクトを保持するもの
などを書きだそうとするとTypeErrorを起こすとマニュアルには記載してあります。
Marshal.dump(Dir.new('/')) # Dirが「システムがオブジェクトを保持するもの」に該当している
# => TypeError: no _dump_data is defined for class Dir
しかしMarshal.load
のタイミングで起きるとはどういうことだ?
と、しばしハマっていました。
で、結論ですが
require 'redis'
require 'date' # <= これ
conf = {
# set.rbと同様のRedisの設定が記述してあります
}
redis = Redis.new(conf)
Marshal.load(redis.get('key'))
# => #<Date: 1993-02-24 ((2449043j,0s,0n),+0s,2299161j)>
Marshal.load
するときに対象のオブジェクトの状態を復元するには同じ環境になっていないといけない、という感じです。(こんな表現で伝わるか不明ですが…)
他のオブジェクトであれば割と分かりやすいエラーです。
require 'redis'
require 'bigdecimal'
conf = {
# ここにはRedisの設定が記述してあります
}
redis = Redis.new(conf)
decimal = BigDecimal.new('0.1234')
redis.set('key', Marshal.dump(decimal))
require 'redis'
conf = {
# decimal_set.rbと同様のRedisの設定が記述してあります
}
redis = Redis.new(conf)
Marshal.load(redis.get('key'))
# => ArgumentError: undefined class/module BigDecimal
これなら「あ、bigdecimalをrequireし忘れてた」となるのですが、DateはrequireせずともDateってオブジェクトがいるんですよね。
p Date # => Date
これのせいで余計な混乱を招いた形です。
こいつがなんなのか、よく分かっていないのですがDate.methods
してもdateライブラリのメソッドが出てこない辺り、require 'date'
をするとDateクラスを再オープンしてるのかなー、とか想像しています。Date.freeze
したあとにrequire 'date'
やったらRuntimeError: can't modify frozen class
になったから多分そう。
ということで、Marshal.load
の実行時には環境を合わせましょうね、というtipsでした。