発端
とある機能を開発していたところ、以下のエラーが出てwebsocketサーバーが起動しなくなりました。
NameError: uninitialized constant Concerns::XXXable
エラーの発生箇所は、とあるController内の include Concerns::XXXable
でした。
Concerns::XXXable
自体は定義してあり、そのあたりの修正もしていなかったため困惑しましたが、どうやら調べていくとquerlyというgemをインストールしてから発生していることがわかりました。
なぜgemをインストールしただけでエラーが発生するようになったか、調査に結構時間がかかったので原因と対策をまとめました。
結論
原因: gem内でトップレベルにConcernsというnamespaceが切られていたこと
ざっくり説明すると、querly内のトップレベルのnamespaceにConcernsというnamespaceが切られていたため、その中で Concerns::XXXable
を探そうとして、上記エラーが発生していました。
※すでに当該コードは下記PRで修正されています。
[https://github.com/soutaro/querly/pull/34:embed:cite]
対策: 定数を参照するときは完全修飾名を使うようにする
標題の通り、 Site::Concerns::XXXable
のように完全修飾名を使うことで予期せぬバグは発生しなくなります。
そもそも Concerns::Site::XXXable
の方が良くないか?っていう話はいったん置いておきます。
詳しい原因
原因を詳しく説明すると、こんな感じです。
うちのアプリのwebsocketサーバーは、起動時に Rails.application.eager_load!
を実行していました。
Rails.application.egaer_load!を実行すると
- autoload_pathsを先頭から探索
- const_missingが発生したmoduleからトップレベルの名前空間につくまで順に親を探索
の順に定数を探索しにいきます。
querlyをインストールしていない状態で include Concerns::XXXable
をしているControllerを読み込むと
- Concernsが見つからない
- autload_pathsを探索してもConcerns::XXXableが見つからない
- 上記Controller内から順に親namespaceを探索し、Site::Concerns::XXXableが見つかる
という風にちゃんと読み込めます。
一方、querlyをインストールしている状態で include Concerns::XXXable
をしているControllerを読み込むと
Rails起動時にquerlyが読み込まれquerlyのConcernsが読み込まれてしまうため、
上記2の段階でquerlyのConcernsを参照してしまい、定数が見つからないというエラーが発生していました。
(全然わかりやすく説明できない...)
トップレベルにConcernsみたいなnamespaceが切られているgemをインストールしていなくても、Concerns::XXXable
を読み込みにいく前に自分でConcerns(Rails標準のapp/controller/concernsなど)を読み込んでしまうと同様のエラーがおきました。
まとめ
定数を参照するときは完全修飾名を使うようにしよう!という話でした。
説明がぜんぜんうまくできませんでした...!