書いた人:西山
Ruby には便利な標準添付ライブラリがたくさんありますが、なかなか知られていないのが現状です。そこで、この連載では Ruby の標準添付ライブラリを紹介していきます。
今回は、データを外部ファイルに保存するためのクラス PStore と PStore を継承したクラスについて紹介します。
PStore を使うと Ruby のオブジェクトを手軽に外部ファイルに保存することが出来ます。 保存されるデータファイルの内容は Marshal されたバイナリになります。
使い方は基本的には Hash のように PStore#[]= を使って値を保存して、PStore#[] を使って値を取り出します。 PStore#roots は Hash#keys に、PStore#root? は Hash#key に相当します。
ただし、transaction 中でしかデータを操作することは出来ません。 データを参照するだけの transaction の場合は引数に true を指定することで、読み込み専用の transaction にすることが出来ます。
require 'pstore'
db = PStore.new('/tmp/foo')
db.transaction do
p db.roots
ary = db['root'] = [1,2,3,4] # 配列を db に設定
ary[0] = [1,1.5] # 破壊的に変更
end # 保存は transaction を抜けるときなので変更された結果が保存される
db.transaction(true) do
p db.root?('root')
p db['root']
end
begin
db.transaction(true) do
db['root'] = 'hoge' # 書き込もうとすると PStore::Error
end
rescue PStore::Error
p $!
end
実行例 (1 回目):
[]
true
[[1, 1.5], 2, 3, 4]
#<PStore::Error: in read-only transaction>
実行例 (2 回目以降):
["root"]
true
[[1, 1.5], 2, 3, 4]
#<PStore::Error: in read-only transaction>
他に PStore#fetch と PStore#delete が Hash の同名のメソッドと同じ機能を持っています。
require 'pstore'
db = PStore.new('/tmp/foo')
db.transaction do
ary = db.fetch(:ary, [])
p ary
ary.push(0, 1, 2)
db[:ary] = ary
end
db.transaction do
ary = db.fetch(:ary, [])
p ary
db.delete(:ary)
end
実行例:
[]
[0, 1, 2]
PStore#abort と PStore#commit で、その transaction での PStore への変更を破棄したり、即座に変更を反映して transaction を抜けたり出来ます。 transaction の中で例外が発生した場合も abort と同様に変更は保存されずに transaction を抜けます。
PStore#path は、PStore のデータファイルのパスを返します。
require 'pstore'
db = PStore.new('/tmp/foo')
db.transaction do
db['foo'] = 'bar'
p [1, db['foo']]
db.abort
p [2, db['foo']] # abort したので実行されない
end
db.transaction do
db.delete('foo')
p [3, db['foo']]
db.commit
p [4, db['foo']] # commit したので実行されない
db['foo'] = 'bar' # 実行されないので delete されたまま
end
db.transaction(true) do
p [5, db['foo']]
end
p db.path
実行例:
[1, "bar"]
[3, nil]
[5, nil]
"/tmp/foo"
内部で Marshal を使っているため、以下の特徴があります。
ruby 1.8.5 以前の pstore.rb にはバイナリモードに関するバグがあるため、以下のスクリプトを実行して「found pstore.rb bug!」と表示された場合は修正済みの pstore.rb をダウンロードして使ってください。
require 'pstore'
db = PStore.new('bugcheck')
begin
db.transaction do
db['bugcheck'] = "\x1a"
end
db.transaction do
if db['bugcheck'] == "\x1a"
puts "OK"
end
end
rescue ArgumentError
puts "found pstore.rb bug!"
end
YAML::Store は PStore と同じように Ruby のオブジェクトを YAML 形式で外部ファイルに保存します。 保存されるデータファイルの内容は YAML 形式のテキストで基本的に UTF-8N (BOM なしの UTF-8) になります。
YAML::Store の使い方は基本的に PStore と全く同じです。 プログラマーのための YAML 入門 (中級編) も参考にしてください。
require 'yaml/store'
db = YAML::Store.new('/tmp/foo.yaml')
db.transaction do
db['foo'] = 'bar'
end
db.transaction(true) do
p db['foo']
end
実行例:
"bar"
実行後の /tmp/foo.yaml の例:
---
foo: bar
YAML::Store には Hash でオプションを指定することも出来ます。
オプションの詳細は http://yaml4r.sourceforge.net/doc/page/the_options_hash.htm を参照してください。
require 'yaml/store'
db = YAML::Store.new('/tmp/foo.yaml', :SortKeys => true)
db.transaction do
db['foo'] = 'bar'
db['hoge'] = 'fuga'
end
db.transaction(true) do
p db.roots
end
実行例:
["hoge", "foo"]
実行後の /tmp/foo.yaml の例:
---
hoge: fuga
foo: bar
内部で YAML を使っているため、以下の特徴があります。
Proc のように YAML.dump が出来るのに YAML.load が出来ないオブジェクトを格納してしまうと、以下の例のように transaction に入ることが出来なくなるので注意しましょう。
require 'yaml/store'
db = YAML::Store.new('/tmp/proc.yaml')
begin
db.transaction do
db["proc"] = proc{}
end
db.transaction(true) {}
rescue TypeError
p $!
end
実行例:
#<TypeError: allocator undefined for Proc>
実行後の /tmp/proc.yaml の例:
---
proc: !ruby/object:Proc {}
Marshal や YAML のように dump と load が出来る機能があれば、PStore を継承して他の形式の Store を簡単に作成することが出来ます。
最低限以下のメソッドをオーバーライドすれば独自形式の Store を作ることが出来ます。 継承により transaction の処理などは PStore の機能をそのまま使うことが出来ます。
ここでは例として、SOAP::Marshal を使って作った XMLStore を紹介します。
内容は短いのでここに全文を載せます。
#
# XMLStore
#
# Copyright (c) 2005 Kazuhiro NISHIYAMA.
# You can redistribute it and/or modify it under the same terms as Ruby.
#
# $Id: xmlstore.rb,v 1.1 2005/03/09 13:58:25 znz Exp $
require 'pstore'
require 'soap/marshal'
class XMLStore < PStore
def initialize(filename)
super(filename)
end
def dump(table)
SOAP::Marshal.dump(table)
end
def load(content)
SOAP::Marshal.load(content)
end
def load_file(file)
SOAP::Marshal.load(File.open(file, "rb"){|f| f.read})
end
end
使い方は PStore や YAML::Store と同じです。
require 'xmlstore'
db = XMLStore.new('/tmp/foo.xml')
db.transaction do
db['foo'] = 'bar'
db['hoge'] = 'fuga'
end
db.transaction(true) do
p db.roots
end
実行例:
["hoge", "foo"]
実行後の /tmp/foo.xml の例:
<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Body>
<Hash xmlns:n1="http://xml.apache.org/xml-soap"
xsi:type="n1:Map"
env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<item>
<key xsi:type="xsd:string">hoge</key>
<value xsi:type="xsd:string">fuga</value>
</item>
<item>
<key xsi:type="xsd:string">foo</key>
<value xsi:type="xsd:string">bar</value>
</item>
</Hash>
</env:Body>
</env:Envelope>
もう一つの例として、ActiveSupport の to_json を使って作ってみた JsonStore を紹介します。 これも短いので全体を載せておきます。rubygems で ActiveSupport がインストールされていることを想定しています。 YAML is JSON に書いてあるように、JSON は YAML の制限をきつくしたものと見なせるので、load には YAML.load を使っています。
require 'pstore'
require 'yaml'
require 'rubygems'
require 'active_support'
class JsonStore < PStore
def initialize(filename)
super(filename)
end
def dump(table)
table.to_json
end
def load(content)
YAML.load(content)
end
end
to_json は Symbol が文字列になるなど、JavaScript の仕様にあわせているため、load しても元通りにならないので、JsonStore は実用には向かないと思いますが、このように簡単に独自形式の Store が作れるという例として参考にしてください。
今回は PStore を中心に PStore を継承して独自形式の Store を作成する方法までを紹介しました。 自作のアプリケーションでの手軽なデータ保存に活用していただければ幸いです。
西山和広。 Ruby hotlinks 五月雨版や 現在の Ruby リファレンスマニュアルのメンテナをやっています。 Ruby リファレンスマニュアルはいつでも執筆者募集中です。 何かあれば、マニュアル執筆編集に関する議論をするためのメーリングリスト rubyist@freeml.com (参加方法) へどうぞ。
Ruby リファレンスマニュアルは現在青木さんによる新システムに移行準備中です。 手伝っていただける方は ruby-reference-manual ML に入ってください。