この記事は Ruby Advent Calendar 2015 の 8日目です。
比較的あまり知られていないと思うオススメの Ruby の機能を 7つ紹介します。
Enumerator.new
Enumerator.new
は知ってはいても、あまり使わない人が多いように思います。
私は非常によく使います。
理由は
- スコープを新たに導入したい
- Producer-Consumer パターン。値の生成と消費でフェーズの違いを明確化したい。
- そのメソッド内で1度使いたいだけなのに yield するメソッドを別に作るのはちょっと気が引ける。名前空間を汚したくない。
- 全部、一度配列にしちゃうとメモリが気になる。メモリ消費を節約したい。
- ネストを浅くしたい
- Enumerable モジュールの機能が欲しい
といったところです。
たとえば下記のようなかんじで使います。
# Producer フェーズ
user_ids = Enumerator.new do |y|
open("candidates.txt") do |f|
f.each_line do |line|
next if line.start_with?("#")
user_id = line.chomp
y << user_id
end
end
end
# Consumer フェーズ
user_ids.with_index 1 do |user_id, index|
puts "#{index} #{user_id}"
end
上の例では Consumer フェーズが単純なので、直接書いてもいいのですが、複雑になってくるとネストを浅くできるメリットも活きてきて、Enumerator.new
の良さが分かってきます。
y
というのは慣習的な変数名で、yielder
の頭文字です。Yielder
オブジェクトだから y
ですね。
Object#tap
Object#tap
も多くの人が知っていますが、単にメソッドチェインの中でデバッグする目的で使う人が多いように思います。
私はいろんな状況で Object#tap
をよく使います。
おもな理由は下記のとおりです。
- 変数の初期化とその利用でスコープ、フェーズの分割を明示したい
- 返り値を返すときに、最後にわざわざ
hash
とかオブジェクトを書くのに違和感を感じる。特にmap
などのブロックの場合とか。 - 昔 ActiveSupport にあった
Kernel#returning
のように値を返すときにすっきり書きたい
たとえば下記のように使います。
alphabet_nums = {}.tap do |hash|
("a".."z").each.with_index 1 do |alphabet, index|
hash[alphabet] = index
end
end
こう書くと
alphabet_nums = {}
("a".."z").each.with_index 1 do |alphabet, index|
alphabet_nums[alphabet] = index
end
と書く場合に比べて、tap に囲まれた場所が初期化フェーズであることがよりコード中に明確に表現されていてわかりやすいように感じます。
上記の alphabet_nums
が構築後は変更がない場合など特に、こう書いた方が構築中のブロック内だけ変更があるということが強調されていて、分かりやすいように感じます。
Float::INFINITY
Ruby には無限大を表す Float::INFINITY
という値があります。
Float::INFINITY
を使うとコードをすっきり統一的に書けることがあります。
すべての数を扱うときとある範囲を扱うときの両方がありうる場合を例に説明します。
この場合よく下記のように書きますが、
# to が nil ならどんなに大きな値でもOK! to に値があればその値まで
if to.nil? || x < to
do_something(x)
end
事前に to のデフォルト値を Float::INFINITY
にしておけば、nil
かどうかの判定は不要にできます。
to ||= Float::INFINITY # 事前に nil の場合は無限大を表す値を代入しておくことで
if x < to # スッキリ!!
do_something(x)
end
x < to
のような評価が一度だけであれば嬉しさはあまり伝わらないかもしれませんが、何度も出てくるような場合はスッキリした見た目で分かりやすくなります。
Enumerable#each_slice
Enumerable#each_slice
は何個かずつブロックに渡して順に処理を行えるメソッドです。
このメソッドそのものはよく知られているかと思いますが、似た処理を繰り返すときにすっきり書く方法を実例とともに紹介します。
「電気料金を比較し、最適な電力会社を選ぼう!エネチェンジ」のサイトの開発の中で Enumerable#each_slice
を使った例が以下になります。
require 'selenium-webdriver'
require "webdriver-user-agent"
driver = Webdriver::UserAgent.driver browser: :firefox, agent: :iphone, orientation: :portrait
url = "http://localhost:3000/"
driver.navigate.to url
%w(
order[family_name] 山田
order[given_name] 太郎
order[family_name_kana] ヤマダ
order[given_name_kana] タロウ
order[postcode_1] 100
order[postcode_2] 0001
order[phone_number_1] 012
order[phone_number_2] 3456
order[phone_number_3] 7890
).each_slice(2) do |name, value|
fields = driver.find_elements :name, name
field = fields.first
field.send_keys value
end
Selenium を使った自動操作の例ですが name 属性でエレメントを見つけてそこに自動入力させています。
2要素の配列を要素に持つ配列(配列の配列)を作ってもいいのですが、each_slice
を使うことで、配列のネストを1個減らすことができています。
また、%w()
を使うことで ""
や ,
を書く必要がなくなっているのでかなりすっきりした見た目になっています。
特にロジックに変化が少なくデータの改変が多いときはこのように %w()
で一気にデータ部を記述して、Enumerable#each_slice
で何個かずつ分割する方が分かりやすいように思います。
Array#shelljoin
マイナーですが、Ruby標準ライブラリには shellwords
というライブラリが含まれています。
shellwords
を require
すると sh
等でエスケープが必要な文字は自動的にエスケープしながら文字列を連結してくれる Array#shelljoin
メソッドが追加されます。
require 'shellwords'
["vlc", "Ruby on Rails.mp4"].shelljoin #=> "vlc Ruby\\ on\\ Rails.mp4"
外部コマンドを実行するときは自分でエスケープするよりも Array#shelljoin
を使う方が確実で見通しも良くなります。
SecureRandom.hex
セッションキーなどに用いるランダムな文字列を生成するには securerandom
を require
すると使えるようになる SecureRandom.hex
を使うと便利です。
require 'securerandom'
SecureRandom.hex # "396bc8f76cffce4d3bfea3d07f58b09b"
ランダムなメールアドレスの生成、一時ファイルの命名など、案外使います。
Flip Flop 演算子
ほとんどだれも知らないと思われますが Flip Flop 演算子という記法があります。
条件文を ..
で結んで、最初の条件が満たされてから次の条件が満たされるまで true
を返します。
この日本語ではわからないと思いますので、次の例を見てください。
(1..5).each do |x|
puts x if (x == 2) .. (x == 4)
end
この結果は次のようになります。
2
3
4
この例では嬉しさは伝わりにくいと思いますので、もう一例紹介します。Markdown 形式で書かれているファイルの中で、Ruby のコードスニペットのところだけを処理するコードは下記のように書けます。
ARGF.each_line do |line|
if (line.start_with?("```rb"))..(line.chomp == "```")
do_something(line)
end
end