Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RubyのStringIO徹底解説:仕組みと実践的な使い方

ファイルやソケットなどのIOオブジェクトと同じように動作しながら、文字列のように手軽に操作できるオブジェクトが欲しいと思ったことはありませんか?
そんなときにぴったりなのがStringIOです。
この記事では、具体的な使用例と注意点をわかりやすく紹介します。

基本的な使い方

StringIOオブジェクトは、次のようにして生成します。

io = StringIO.new("abc")

作成したオブジェクトに対しては、getsreadeach_lineといったメソッドを使ってデータを読み込めます。
特に便利なメソッドを表にまとめました。

メソッド 説明
gets 1行分の入力を読み込む
read 指定したバイト数だけ読み込む(デフォルトは全データ)
each_line ブロックを渡すと各行ごとに繰り返し処理を行う
each_char ブロックを渡すと各文字ごとに繰り返し処理を行う
<< データを末尾に追加する
rewind 内部の位置ポインタを先頭に戻す
string StringIOオブジェクトから実際の文字列を取得する

StringIOには位置ポインタがある点に注目してください。
このポインタは、ファイルオブジェクトと同じように、どこまで読み込んだかを記録しています。
そのため、getsreadなどを呼び出すたびに、その分のデータが返されると同時にポインタも前へ進みます。

mapeach_lineのようなEnumerable系のメソッドでもポインタは進むので、この点には注意が必要です。

io.each_line { |line| puts line }

位置ポインタを先頭に戻したい場合は、rewindメソッドを使います。

io.pos
# 45

io.rewind

io.pos
# 0

以上が基本の使い方です。続いて、StringIOの実践的な活用例を見ていきましょう。その前に、もう1つ知っておくべきことを紹介します。

StringScannerとの違いは?

Rubyの標準ライブラリには、StringIO以外にも文字列関連のクラスが用意されています。
それがStringScannerです。

名前もメソッドも似ているため混同しやすいのですが、違いを整理しておきましょう。

重要なポイントは次のとおりです

StringIOオブジェクトは、FileSocketなど他のIOオブジェクトの代わりとして使える一方、StringScannerは主にテキストの解析(パース)用途に適しています。パースとは、テキストをトークンと呼ばれる小さな単位に分解して意味を読み取ることです。

とはいえ、2つのクラスには共通点もあります。「String」という名前を持つだけでなく、どちらも内部に位置ポインタを持っている点です。

標準入力・標準出力の差し替え

たとえば、コマンドラインアプリケーションでKernel#getsメソッドを使ってユーザーからの入力を受け付ける処理を書いたとしましょう。

このコードをテストしようとすると、毎回手動で入力を行う必要が出てきます。
では、自動テストは不可能なのでしょうか?

いいえ、そんなことはありません!

ここでStringIOの出番です。テスト用の入力値でStringIOオブジェクトを初期化し、それを$stdingetsを呼び出した際にRubyがユーザー入力を読み込む対象)に代入すればよいのです。

io     = StringIO.new("input")
$stdin = io

gets
# input

この手法は、putsなどによる出力をキャプチャする場合にも応用できます。
表示系のメソッドは「標準出力」と呼ばれるデフォルトの出力先に出力します。Rubyでは標準出力もIOオブジェクトとして表現されているため、これを自分のStringIOオブジェクトに置き換えることが可能です。

io      = StringIO.new("")
$stdout = io

# $stdout に出力
puts "Jesus Castello is from Spain & likes to help people learn Ruby."

# 元の値を復元
$stdout = STDOUT

io.rewind
io.read
# "Jesus Castello is from Spain & likes to help people learn Ruby."

入力の場合より手順が少し増えていますね。これは、元のSTDOUTオブジェクトを必ず復元することと、出力結果を読み取れるようにStringIOを巻き戻す必要があるからです。

なお、多くのテストフレームワークにはこの処理を自動で行う機能が備わっています(Minitestのassert_outputやRSpecのoutputマッチャーなど)。とはいえ、裏側で何が起きているのかを理解しておくのは非常に有益です。

まとめ

この記事では、実際のIOオブジェクトをエミュレートし、その代替として機能するStringIOクラスについて学びました。
画面への出力やターミナル経由のユーザー入力を扱うクラスのテストにおいて、StringIOは非常に役立ちます。

StringIOの面白い活用例をご存じの方は、ぜひコメント欄でお聞かせください。また、この記事をシェアしていただければ、より多くの人に届きます!

  1. Rubyのエイリアス(別名定義)完全ガイド:aliasキーワードとalias_methodの違い

    Rubyでは、既存のメソッドに別名(エイリアス)を付ける方法が2つあります。 alias(キーワード) alias_method(メソッド) どちらも同じ目的で使えますが、挙動が微妙に異なるため、初心者にとって混乱しやすいトピックです。 本記事では、両者の違いを詳しく掘り下げ、しっかりと理解できるように解説していきます。 aliasキーワードとは まずはaliasから見ていきましょう。aliasはRubyのキーワードの一つです(ifやdef、classなどと同じ扱いです)。 基本的な書き方は以下の通りです。 alias print_something puts print_someth

  2. RubyのStructとOpenStructの使い方を徹底解説!値オブジェクトを簡単に作る方法

    Rubyには、関連する属性をまとめて保持する「値オブジェクト」を手軽に作れるStructという組み込みクラスが用意されています。本記事では、Structの基本的な使い方から注意点、そしてOpenStructとの違いまで詳しく解説します。 そもそもStructとは何か? StructはRubyに組み込まれたクラスで、新しいクラスを動的に生成し、値オブジェクト(Value Object)を作成するために使われます。値オブジェクトとは、関連性のある複数の属性をひとまとめにして扱うためのオブジェクトです。 具体例を挙げてみましょう。 2つの座標(xとy)を持つPointというデータを表したい場合、この