Hashみたいだけどちょっと違うHashie::Mash

Hashie::MashはRubygemsのパッケージで公開されているclassのひとつで,m.key = valueのような書き方でkeyにアクセスできるHashのようなもの.twitterパッケージの内部でも使われている.

GitHub - intridea/hashie: Hashie is a collection of classes and mixins that make hashes more powerful.

使い方

gemコマンドでインストールできる.

$ sudo gem install hashie

利用するにはhashieをrequireする必要がある(Ruby1.8以前の場合は先にrequire "rubygems"しておく).

require "hashie"

以下,コード例.これだけでなんとなく雰囲気は分かると思う.

m = Hashie::Mash.new
m.a = 1
m.b = "hoge"

p m.a #=> 1
p m.b #=> "hoge"

Hashではh[:key]のようにHash#[]で要素にアクセスするのに対し,Mashではメソッド呼び出しのような書き方で要素にアクセスできる.これはmethod_missingを実装することで実現されている.

HashオブジェクトをHashie::Mashにすることもできる.ネストしたHashでもOK.要素にアクセスするためのメソッド名はkeyをto_sしたものになるので,to_sの結果がアレなkeyをもつHashを入れるとアレなことになるので注意.

m = Hashie::Mash.new({:foo => 1, "bar" => {:baz => 2}})

p m.foo #=> 1
p m.bar #=> <#Hashie::Mash baz=2>
p m.bar.baz #=> 2

HashのようにHashie::Mash#[]でも要素にアクセスできる.

m = Hashie::Mash.new({:foo => 1})

p m.foo #=> 1
p m[:foo] #=> 1
p m["foo"] #=> 1

eachやEnumerableのメソッドも使える.

m = Hashie::Mash.new({:foo => 1, "bar" => {:baz => 2}})

m.each{|k, v| p [k, v]}
#=> ["foo", 1]
#=> ["bar", <#Hashie::Mash baz=2>]

twitterパッケージでは,Twitter APIが返したJSONをHashie::Mashに変換してからメソッドの戻り値として返したりするのに使われている(はず).

うまくいかない例

残念ながらHashでは大丈夫でもHashie::Mashだとうまくいかない場合もある.

to_sの結果が同じになるkeyが複数あるとどちらか片方しかアクセスできなくなる.

m = Hashie::Mash.new({"foo" => 1, :foo => 2})

p m.foo #=> 2
p m["foo"] #=> 2
p m[:foo] #=> 2

キーがIntegerの場合は通常のメソッド呼び出しでは要素にアクセスできないなどの問題がある.

m = Hashie::Mash.new({1 => "one"})

p m.1 #=> SyntaxError: no .<digit> floating literal anymore; put 0 before dot
p m[1] #=> "one"
p m["1"] #=> "one"
p m[:1] #=> SyntaxError: syntax error, unexpected tINTEGER, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END
p m[:"1"] #=> "one"

循環参照しているHashはHashie::Mashに変換できない.

h = {}
h[:a] = h
p h #=> {:a=>{...}}

m = Hashie::Mash.new(h) #=> SystemStackError: stack level too deep

以上

手軽に使えそうではあるもののRubygemsのパッケージをインストールする必要があるので,利用する場面はよく考えて選んだ方がいいかも.

Hashie::MashのソースはGitHubで公開されている.結構短いのでmethod_missingを面白く使ったclassの例として読んでみるのにちょうどよさそう.