はじめに
RailsでRubyのコードを触っていたときのある日、今まで触っていたコードのメソッドを見て
あれ?これってずっとハッシュを引数として渡していたと思ってたけどキーワード引数なのでは?という疑問が生じ、その疑問を解消するために色々調べたので記事にしてまとめてみました。
結論を先に書くと
結論:Rails側(定義元)でなんとかしてくれてるからハッシュかキーワード引数か気にしなくていい
実際にRailsで書いていたコード
以下のコード読みほぐしていく。(turboの説明は省略)
redirect_to root_url, status: :see_other
# controller内に書いていたredirect
validates :title, presence: true, length: { maximum: 100 }
# model内に書いていたvalidation
link_to '削除', todo_path(todo), data: { turbo_method: :delete , turbo_confirm: '本当に削除していいですか?' }, class: 'delete'
# todoアプリのview内の削除ボタンに書いていたコード
ハッシュ
key(キー)とvalue(値)の組み合わせでデータを管理するオブジェクト、データ構造のこと。
{}で括られる。
currencies = {"japan" => "yen", "us" => "dollar"}
ハッシュのキーは文字列よりもシンボルの方が特性上適しているのでよく使われる。
シンボルがキーの場合は=>を用いずに シンボル:値
という記法でハッシュが作成できる。
currencies = {japan: "yen", us: "dollar"}
この時コロンの位置が左から右に変わることに注意(後置コロン)。
キーワード引数
キーワード引数とは、メソッドの引数にキーワードとなる名前を付けて、順序に関係なく対応する引数の記述方法のこと。
ハッシュと同じような記法で書かれるため混同しやすい。
書き方
def メソッド名(キーワード引数1: デフォルト値1, キーワード引数2:デフォルト値2)
# メソッド処理
end
ex) ハンバーガーと飲み物とポテトを買うメソッド(キーワード引数使っていない)
def buy_burger(menu, drink, potato)
# menu のハンバーガー購入
if drink
# drink = true ならドリンク購入
end
if potato
# potato = true ならポテト購入
end
end
メソッド定義を見た後だとこのメソッドがどういう引数を受け取るかわかりやすいが、メソッドの呼び出し側が離れて記述された場合、以下のようになり可読性が下がる。
buy_burger("cheese_burger", true, false) # あれ?何がtrue,falseなんだ?
こういった問題を避けるためにキーワード引数が使われる。
以下のdrink, potatoがキーワード引数。
def buy_burger(menu, drink: true, potato: true)
# 略
end
メソッド呼び出し側
buy_burger("chicken", drink: true, potato: false) # あっドリンクは買うけどポテトは買わないのか
こうすることで呼び出し側の可読性を高めることが出来る。
デフォルト値
キーワード引数にはデフォルト値が設定されているので、引数を省略することが可能。
buy_burger("bigburger") # ex2 デフォルトでdrinkもpotatoもtrueが使われる
またデフォルト値は定義時に省略可能、しかしその際呼び出し時に省略不可。
def buy_burger(menu, drink: , potato: )
# 略
end
buy_burger("cheese", drink: true, potato: false) # ex2と同じように使える
buy_burger("bigburger") # => ArgumentError
位置に依存しない
キーワード引数は呼び出し時に自由に順番を入れ替え可能(キーワード引数間)
buy_burger("fish", potato: false, drink: true) # drinkとpotatoの位置入れ替え
ということは?
link_to '削除', todo_path(todo), data: { turbo_method: :delete , turbo_confirm: '本当に削除していいですか?' }, class: 'delete'
Rubyのルールでメソッドの()は省略可能なので
この上のコードは( ) をつけて
link_to ('削除', todo_path(todo), data: { turbo_method: :delete , turbo_confirm: '本当に削除していいですか?' }, class: 'delete')
となり第一引数にリンク名として'削除'が、第二引数にURLヘルパーメソッド指定、
data:
がキーワード引数でその値にハッシュとして{ turbo_method: :delete , turbo_confirm: '本当に削除していいですか?' }
が入り
class:
もキーワード引数で値が 'delete'
ということで一見落着...と思いきや
Rubyの省略のルールで
最後の引数がハッシュだったらハッシュリテラルの{}を省略出来る
というものがあるので
link_to ('削除', todo_path(todo), {data: { turbo_method: :delete , turbo_confirm: '本当に削除していいですか?' }, class: 'delete'})
として最後の引数をハッシュと考えることも出来る。
あれ?これって結局どうやって見分ければいいんだ...?
キーワード引数 or 引数に{}が省略されたハッシュの判別
Railsのメソッド定義元を確認する必要がある。
実際にRailsのソースコードからメソッドを見てみる
def redirect_to(options = {}, response_options = {})
redirect_toメソッドの引数は通常のハッシュ引数であり、optionsとresponse_optionsがそれぞれデフォルト値なしでハッシュを受け取るように宣言されている。
def validates(*attributes)
*attributesは可変長引数を受け取り、その後の引数は通常の引数として扱われている。
また、defaultsは通常の引数で、attributes.extract_options!によってオプションのハッシュが取り出されている。
def link_to(name = nil, options = nil, html_options = nil, &block)
options(リンクのパスやオプション)
この引数はハッシュであり、通常はリンク先のURLやコントローラーアクションなどが指定される。
Railsのこれらのメソッドはハッシュとして引数を受け取っている。
しかし比較的新しいRailsメソッドを見てみると
insert_all(6.0~) 【GitHubソースコード】
def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
このようにキーワード引数が定義されている。
歴史的経緯
Ruby2.0以前
メソッドのキーワード引数はRuby2.0から導入された機能。
それ以前はメソッドの定義で引数としてハッシュそのものを受け取れるようにしていた。
擬似キーワード引数と呼ばれるテクニックで、ハッシュをキーワード引数っぽく扱っていた。
def buy_burger(menu, options = {})
drink = options[:drink]
potato = options[:potato]
end
buy_burger("cheese", drink: true, potato: true)
これは後方互換性維持のためにRuby3.0でも有効だが新しいコードで使うことは非推奨。
Ruby2.x
Ruby2.0~2.7はハッシュを引数に渡すとそれが自動的にキーワード引数に自動変換されていた。
Ruby 2.7: ハッシュからキーワード引数への自動変換が非推奨に(翻訳)
しかしこの互換性は多くの実に多くのバグやエッジケースの温床となっていたよう。
Ruby3.0からは、自動変換が行われず、エラーとなる仕様に変更になった。
引数の歴史的流れ
ハッシュをキーワード引数として擬似的にみなすテクニックが存在(2.0以前)
→ハッシュとキーワード引数どちらでもokで自動変換(2.x)
→ハッシュとキーワード引数の分離(3.0~)
というRubyのキーワード引数の歴史の流れがあり、
Railsの新しいメソッド定義で引数の受けとり方もその流れに従って変化してきているのかもしれないと勝手に推測。
結局のところ
Rails内部(定義側)で引数がハッシュかキーワード引数か を決めてくれているので、コーディングする上で呼び出し側で記法自体は変化しない。
なのでRails初心者の自分にとってはそこまで気にすることではないという結論に至った。
まとめ
- メソッド定義で引数がハッシュかキーワード引数か決まる
- Rubyのハッシュとキーワード引数には一体化から分離の歴史的経緯がある
- Rails内部で使い分けの判断はしてくれているので呼び出し側でコーディングしている分には気にする必要はない
感想
ふとした疑問を調べただけでRubyの歴史が垣間見れて興味深かったです。引数の扱いも曖昧だったところも明確になった気がします。
Railsチュートリアルでキーワード引数についてではなく、ハッシュの{}の省略についてしっかり書かれていた理由も納得出来ました。
間違っていたら教えてください。
参考文献
チェリー本 5.4, 5.6節
Ruby 2.7: ハッシュからキーワード引数への自動変換が非推奨に(翻訳)
Rubyの:(コロン)の種類
Udemy Ruby on Rails でたくさん出てくる「 : (コロン)」← これ理解できてる!?
Rails チュートリアル 第4章 ハッシュとシンボル
Ruby初学者が戸惑いやすい文法を、例を交えて噛み砕いてみる
Ruby の引数の種類をまとめてみた
nilガード