環境
Ruby 2.6.6
発端
開発中に既存コードでこのような感じのCASE文に出会った。
# 定数(商品タイプ)
FOOD = 'FOOD'
DRINK = 'DRINK'
OTHER = 'OTHER'
# 定数(商品タイプラベル)
FOOD_LABEL = '食べ物'
DRINK_LABEL = '飲み物'
OTHER_LABEL = 'その他'
def get_label(shouhin_type)
case shouhin_type
when FOOD
FOOD_LABEL
when DRINK
DRINK_LABEL
when OTHER
OTHER_LABEL
else
raise
end
end
要はそれぞれの商品タイプに対応したラベルを取得するために、「商品タイプ名」+「_LABEL」という名前の定数を返したいというメソッド。この状態だと、商品タイプが増えるたびにCASEの分岐をメンテしなければならないし、ただ「_LABEL」をつけるだけなのに何とも回りくどさを感じた。そこでRubyには動的にコードを生成して実行する機能があったなと思い検索。
Ruby 3.1 リファレンスマニュアル module function Kernel.#eval を発見
eval
a = nil
b = "ああああ"
eval("a = '#{b}'")
# 実行される → a = ああああ
p a #=> "ああああ"
・引数で渡された文字列をRubyとして実行
・文字列なので変数展開できる
これを使えばできそうなので早速書き換えてみる。
def hoge(shouhin_type)
eval("label = #{shouhin_type}_LABEL")
p label
end
hoge('FOOD')
undefined local variable or method `label' for main:Object (NameError)
あれ、labelなんて変数名はない…?
どうやらeval内で定義した変数は、スコープの関係で外からは呼び出せないみたい。
def hoge(shouhin_type)
label = nil
eval("label = #{shouhin_type}_LABEL")
p label
end
hoge('FOOD')
#=> '食べ物'
修正して無事に動作するようになった。
結果
# CASE文の場合
def get_label(shouhin_type)
case shouhin_type
when FOOD
FOOD_LABEL
when DRINK
DRINK_LABEL
when OTHER
OTHER_LABEL
else
raise
end
end
# evalの場合
def get_label(shouhin_type)
label = nil
eval("label = #{shouhin_type}_LABEL")
label
end
結果、CASE文なら12+α行だったところをevalを使うことで4行にでき、
今後、商品タイプが変更されてもメンテ不要にすることができた。