TL;DR
Rubyにおいて、キーワード引数を取るメソッドには、to_hash
メソッドを実装しているオブジェクトを渡すことができて、キーワード引数として展開して解釈される。
キーワード引数とは
def log(msg, level: "ERROR", time: Time.now)
puts "#{ time.ctime } [#{ level }] #{ msg }"
end
log("Hello!", level: "INFO") #=> Mon Feb 18 01:46:22 2013 [INFO] Hello!
(https://magazine.rubyist.net/articles/0041/0041-200Special-kwarg.html より引用)
このlog
メソッドのlevel
やtime
みたいな感じで、引数にデフォルト値を指定することができて、また名前を指定して引数を渡せるという便利機能です。
Hashはキーワード引数に展開される
キーワード引数にはHashを渡すことができて、その場合はHashの中身が展開されます
# 普通の使い方
log('aaa', level: 'AAA') # => Wed Nov 6 09:16:36 2019 [AAA] aaa
# Hashも渡せる
log('aaa', {level: 'AAA'}) # => Wed Nov 6 09:16:44 2019 [AAA] aaa
このようなHash => keyword引数の変換はRuby 2.7ではdeprecatedになったので注意してください 1
Hash以外もキーワード引数にわたす
そして、キーワード引数に渡して展開されるのはHashだけではありません。「to_hash
を実装しており、かつその返り値のkeyがsymbolであるオブジェクト」ならなんでも展開されます。
# to_hashを実装しているクラス
class HogeWithToHash
def to_hash
{ level: 'AAA' }
end
end
# キーワード引数として展開される
log('aaa', HogeWithToHash.new) # => Wed Nov 6 09:27:25 2019 [AAA] aaa
# to_hashを実装しているが返り値のHashのkeyが文字列であるクラス
class HogeWithToHashStringKey
def to_hash
{ 'level' => 'AAA' }
end
end
# キーワード引数として正しく展開されない
log('aaa', HogeWithToHashStringKey.new) # => ArgumentError (wrong number of arguments (given 2, expected 1))
キーワード引数に展開されるオブジェクトの例: Rake::TaskArguments
このような条件を満たすオブジェクトとして、Rakeタスクを定義するときに使われる Task::Arguments
が挙げられます。したがって、以下のRakefileは正しく動きます:
def greet(first_name:, last_name:)
puts "Hello, #{first_name} #{last_name}"
end
task :greet, [:first_name, :last_name] do |t, args|
greet(args)
end
$ rake "greet[Ichiro,Tanaka]"
Hello, Ichiro Tanaka
以上