配列と範囲はいずれも、ブロックを伴うさまざまなメソッドに対して応答することができます。ブロックは、Rubyの極めて強力な機能であり、かつわかりにくい機能でもあります。
(1..5).each { |i| puts 2 * i }
2
4
6
8
10
=> 1..5
上のコードでは、範囲オブジェクトである(1..5)に対してeachメソッドを呼び出します。メソッドに渡されている{ |i| puts 2 * i }が、ブロックです。|i|では変数名が縦棒「|」に囲まれていますが、これはブロック変数に対して使用するRubyの構文で、ブロックを操作するときに使用する変数を指定します。この場合、範囲オブジェクトのeachメソッドは、iという1つのローカル変数を使用してブロックを操作できます。そして、範囲に含まれるそれぞれの値をこの変数に次々に代入してブロックを実行します。
ブロックであることを示すには波かっこ { } で囲みますが、以下のようにdoとendで囲んで示すこともできます。
(1..5).each do |i|
?> puts 2 * i
end
2
4
6
8
10
=> 1..5
ブロックには複数の行を記述できます (実際ほとんどのブロックは複数行です)。RailsチュートリアルではRuby共通の慣習に従って、短い1行のブロックには波かっこを使用し、長い1行や複数行のブロックにはdo..end記法を使用しています。(1..5).each do |number|
?> puts 2 * number
puts '--'
end2
4
6
8
10
=> 1..5
今度はiの代わりにnumberを使用していることにご注目ください。この変数 (ブロック変数) の名前は固定されていません。
ブロックは見た目よりもずっと奥が深く、ブロックを十分に理解するためには相当なプログラミング経験が必要です。そのためには、ブロックを含むコードをたくさん読みこなすことでブロックの本質を会得する以外に方法はありません8。幸い、人間には個別の事例を一般化する能力というものがあります。ささやかですが、mapメソッドなどを使用したブロックの使用例を参考のためにいくつかご紹介します。
3.times { puts "Betelgeuse!" } # 3.timesではブロックに変数を使用していない
"Betelgeuse!"
"Betelgeuse!"
"Betelgeuse!"
=> 3
(1..5).map { |i| i*2 } # 「*」記法は冪乗 (べき乗)
=> [1, 4, 9, 16, 25]
%w[a b c] # %w で文字列の配列を作成
=> ["a", "b", "c"]
%w[a b c].map { |char| char.upcase }
=> ["A", "B", "C"]
%w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]
上に示したように、mapメソッドは、与えられたブロックを配列や範囲オブジェクトの各要素に対して適用し、その結果を返します。また、後半の2つの例では、mapのブロック内で宣言した引数 (char) に対してメソッドを呼び出しています。こういったケースでは省略記法が一般的で、以下のように書くこともできます。%w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]
%w[A B C].map(&:downcase)
=> "a", "b", "c"。ひとつ面白い話があります。これは実は元々Ruby on Rails独自の記法でした。しかし多くの人がこの記法を好むようになったので、今ではRubyのコア機能として導入されています。
最後のブロックの例として、単体テストにも目を向けてみましょう (リスト4.4)。
test "should get home" do
get :home
assert_response :success
assert_select "title", "Ruby on Rails Tutorial Sample App"
end
ここでは動作をすみずみまで理解する必要はありません (実際、筆者もこのコードをひと目で完璧に把握できるなどとは言いません)。ここで重要なのは、テストコードにdoというキーワードがあることに気付き、そこからテストの本体が「そもそもブロックでできている」ことに気付くことです。すなわち、このtestメソッドは文字列 (説明文) とブロックを引数にとり、テストが実行されるときにブロック内の文が実行される、ということが理解できます。
ところで、1.5.4でランダムなサブドメインを生成するために以下のRubyコードを紹介しましたが、このコードを理解するための準備が整ったので、今こそ読み解いてみましょう。
('a'..'z').to_a.shuffle[0..7].join
順を追ってこのコードを組み立ててみると、動作がよくわかります。
('a'..'z').to_a # 英小文字を列挙した配列を作る
=> "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z".to_a.shuffle # シャッフルする
=> "c", "g", "l", "k", "h", "z", "s", "i", "n", "d", "y", "u", "t", "j", "q",
"b", "r", "o", "f", "e", "w", "v", "m", "a", "x", "p".to_a.shuffle[0..7] # 配列の冒頭7つの要素を取り出す
=> "f", "w", "i", "a", "h", "p", "c", "x".to_a.shuffle[0..7].join # 取り出した要素を結合してひとつの文字列にする
=> "mznpybuj"