はじめに
メソッドの引数に関するRuby特有の挙動がわからずハマったため、備忘録としてまとめます。
なおこの記事におけるRubyのバージョンは3.3です。
問題
以下のようなコードを見かけました。
class A
attr_reader :arg1, :arg2, :arg3
def initialize(arg1:, arg2:, arg3:)
@arg1 = arg1
@arg2 = arg2
@arg3 = arg3
end
def execute
# キーワード引数で3つの引数を渡しているつもり
B.execute(param1: arg1, param2: arg2, param3: arg3)
end
end
class B
# 引数は1つだけ、キーワード引数でもない
def self.execute(params)
params.each do |key, value|
puts "#{key}: #{value}"
end
end
end
a = A.new(arg1: 'a', arg2: 'b', arg3: 'c')
a.execute
#=> param1: a
#=> param2: b
#=> param3: c
Aクラスのexecute
メソッドでBクラスのexecute
メソッドを呼び出しています。
メソッド呼び出し側ではキーワード引数で3つの引数を指定しているのに対し、Bクラス内のexecute
メソッドで指定されている引数はparams
1つだけです。
キーワード引数にもなっていません。
こんな状態なのにも関わらず正常に動いていたため、疑問が湧きました。
解決策
Rubyではメソッドに引数を渡す際、最後の引数がハッシュであれば、波括弧{}
を省略できます。
# メソッドの定義
def print_person_info(name, age, options = {})
puts "名前: #{name}"
puts "年齢: #{age}"
options.each do |key, value|
puts "#{key}: #{value}"
end
end
# 通常のハッシュ表記
print_person_info("山田太郎", 30, {職業: "エンジニア", 趣味: "読書"})
#=> 名前: 山田太郎
#=> 年齢: 30
#=> 職業: エンジニア
#=> 趣味: 読書
# 最後の引数がハッシュの場合、波括弧を省略できる
print_person_info("佐藤花子", 25, 職業: "デザイナー", 趣味: "絵画")
#=> 名前: 佐藤花子
#=> 年齢: 25
#=> 職業: デザイナー
#=> 趣味: 絵画
冒頭のサンプルコードにもこの波括弧の省略が適用されています。
キーワード引数を3つ指定したつもりでしたが、実際には1つのハッシュとみなされていたのです。
つまり、次のように記述したのと同じ扱いになっていました。
class A
attr_reader :arg1, :arg2, :arg3
def initialize(arg1:, arg2:, arg3:)
@arg1 = arg1
@arg2 = arg2
@arg3 = arg3
end
def execute
# 3つの引数を渡しているように見えて、実際は1つのハッシュを渡している扱いとなる
B.execute({ param1: arg1, param2: arg2, param3: arg3 })
end
end
class B
# 引数は1つだけ
def self.execute(params)
# ...
end
end
これで謎が解けました。
おわりに
最後の引数がハッシュであれば省略可能なことは知っていたものの、3つの引数が1つのハッシュとみなされてるとは気づきませんでした。
Rubyの柔軟な挙動は良い時もある反面、コードリーディングをする際は苦しめられることもあるなと感じました。