ネストしたモジュールを書く場合の注意点
rubyflow経由で読んだ「How You Nest Modules Matters in Ruby」の内容を紹介する。
ネストしたモジュールを記述する際、シンタックスの違いで挙動が異なることについての記事だ。
Rubyではネストしたモジュール(とクラス)を記述するために2つの異なるシンタックスが用いられる。
# シンタックス#1 module API module V1 end end # シンタックス#2 module API::V1 end
シンタックス#2は既に存在するAPI
モジュールを必要とする。
この2つのシンタックスは等価で、どちらを使用するかは好みの問題だと考えている人もいるが、これは誤りだ。
バージョン管理されたREST APIの例を用いてこれを説明する。
module API class Responder end module V1 class Controller def action Responder.respond_with('Hello, World!') end end end end
#actionが呼び出された時、Rubyは以下の順番でResponder
が定義されているかルックアップする。
API::V1::Controller
API::V1
API
- トップレベル
上記のコードの場合、3.でAPI::Responder
としてルックアップされる。
Module.nesting
メソッドでモジュール/クラスのネスト状態を調べることができる。これを用いてシンタックス#1と#2の違いを捉える。
# シンタックス#1 module API class Responder end module V1 class Controller p Module.nesting #=> [API::V1::Controller, API::V1, API] def action Responder.respond_with('Hello, World!') end end end end
# シンタックス#2 module API class Responder end end module API::V1 class Controller p Module.nesting #=> [API::V1::Controller, API::V1] def action Responder.respond_with('Hello, World!') end end end
シンタックス#2の場合、#actionが読み出されるときNameError
が発生する。
NameError: uninitialized constant API::V1::Controller::Responder
シンタックス#2の場合、ネスト状態の情報が失われてしまうことが原因だ。
API
内にResponder
が定義されているかルックアップを行わないため、例外が発生してしまう。
結論
ネストしたモジュールを記述する場合、シンタックスによる違いを意識しよう。