キーワード引数を委譲するメソッドで ** を付け忘れると ArgumentError になる
受け取ったキーワード引数(**optionsで受けたハッシュ)をそのまま別のメソッドに渡すには、呼び出し側でも**を付けて展開する必要があります。**を付けずに渡すと、ハッシュが1個の位置引数として渡ってしまい、キーワード引数として認識されずArgumentErrorになります。
エラーメッセージの読み方
main.rb:1:in `configure': wrong number of arguments (given 1, expected 0; required keywords: host, port) (ArgumentError)
main.rb- ファイル名
1- 行番号 — 実際にクラッシュした行
configure- 発生場所 — <main> ならトップレベル、メソッド名ならそのメソッドの中
ArgumentError- 例外クラス — 何が起きたか。ここを検索するのが最短です
wrong number of arguments (given 1, expected 0; required keywords: host, port)- 詳細メッセージ — どの値が問題だったか
このエラーが出る典型パターン
パターン1
1 def configure(host:, port:) 2 "#{host}:#{port}" 3 end 4 5 def wrapper(**opts) 6 configure(opts) ^ 7 end 8 9 puts wrapper(host: "localhost", port: 3000)
**を付けずに渡すとハッシュが1個の位置引数として扱われ、キーワード引数として認識されずエラーになります。
直し方: (opts) を (**opts) にします。
パターン2
1 def connect(host:, timeout:) 2 "#{host} (#{timeout}s)" 3 end 4 5 def wrapper(**opts) 6 connect(opts) ^ 7 end 8 9 puts wrapper(host: "db", timeout: 5)
受け取った**optsを次のメソッドに伝えたい場合、呼び出し側でも**で展開する必要があります。
直し方: (opts) を (**opts) にします。
パターン3
1 def setup(mode:, level:) 2 "#{mode}/#{level}" 3 end 4 5 def wrapper(**opts) 6 setup(opts) ^ 7 end 8 9 puts wrapper(mode: "auto", level: 3)
**を付けずに渡すとただのハッシュという1個の引数として扱われ、キーワード引数を期待する側と噛み合いません。
直し方: (opts) を (**opts) にします。
パターン4
1 def build_url(scheme:, path:) 2 "#{scheme}://#{path}" 3 end 4 5 def wrapper(**opts) 6 build_url(opts) ^ 7 end 8 9 puts wrapper(scheme: "https", path: "example.com")
キーワード引数だけを受け取るメソッドに、展開していないハッシュを渡すと引数の形が一致しません。
直し方: (opts) を (**opts) にします。
パターン5
1 def register(name:, plan:) 2 "#{name}/#{plan}" 3 end 4 5 def wrapper(**opts) 6 register(opts) ^ 7 end 8 9 puts wrapper(name: "taro", plan: "pro")
*と**は見た目が似ていますが役割が違います。位置引数の展開とキーワード引数の展開を混同しないよう注意してください。
直し方: (opts) を (**opts) にします。
よくある誤解
「**optionsで受け取った値は、そのまま書けばキーワード引数として次のメソッドに伝わるはず」という思い込みは誤りです。**を付けずに渡すとただのハッシュという位置引数1個として扱われるため、受け取り側がキーワード引数を期待しているとエラーになります。
まとめ
キーワード引数を委譲するメソッドで ** を付け忘れると ArgumentError になるは上級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。