TL;DR (長い3行で)
・同じスコープに同じ定義値が復数あると警告がでる
・直接requireとシンボリックリンクのrequireが同居しているのが原因だと修正できない
・C/C++でおなじみのインクルードガード相当のことをRubyでもやる
困りごと
同じRubyファイルを直接requireする子と,シンボリックリンク経由でrequireする子が同居すると,↓ のような警告がでます.
warning: already initialized constant
本質的には同じものをrequireしているので,警告がでても困ることはさして無いのですが,Console上に出てくるのでちょっと目障り.
C/C++であるようなインクルードガードがRubyでも欲しい.
まずは問題の再現
↓ 用意するのは3ファイル.
main.rb → requireをcallする側
required.rb → requireされる側
required_link.rb → required.rbへのシンボリックリンク
↓ requireされる側のファイル
TOP_LEVEL_DEF = "top level def"
puts "## require DONE"
↓ requireする側のファイル
# 直接require
require_relative './required.rb'
# シンボリックリンク経由でrequire
require_relative './required_linked.rb'
puts "## TOP_LEVEL_DEF = #{top level def}"
ここでmain.rbを実行すると,↓ のような結果になります.
## require DONE
required_linked.rb:1: warning: already initialized constant TOP_LEVEL_DEF
required.rb:1: warning: previous definition of TOP_LEVEL_DEF was here
## require DONE
## TOP_LEVEL_DEF = top level def
required.rbが2回読み込まれ,TOP_LEVEL_DEFの2回目の初期化のときに警告が出ていることがわかります.
Ruby版インクルードガード
C/C++のように#ifndefが使えれば良いのですが,定数が宣言されているかどうか,を調べるような関数は見つけられませんでした.
ただ,カレントスコープでアクセス可能な定義値の一覧を取得する関数はあるようです.
Module.constants
https://docs.ruby-lang.org/ja/latest/method/Module/s/constants.html
これを使えば,定数の未宣言/宣言済の判定ができそうです.
先ほどのrequired.rbを ↓ のように改造します.
if !Module.constants.include?(:IS_REQUIRED)
IS_REQUIRED = true # 値は特に意味なし
TOP_LEVEL_DEF = "top level def"
puts "## require DONE"
end
この状態でmain.rbを実行すると,
## require DONE
## TOP_LEVEL_DEF = top level def
という出力になり,警告が出ないようになりました.
required.rbが1回しか読み込まれていないことが確認できますし,そのあとの定義値の使用も問題ありません.
まとめると
if !Module.constants.include?(:IS_REQUIRED)
IS_REQUIRED = true
# Do something.
end
という構文でRubyでもC/C++のようなインクルードガードが実現できそうです.
あまり使う機会は無さそうですが,直接とシンボリックリンク経由で同一ファイルをrequireすることがあったので調べてみました.
この半端ではない車輪の再発明感
追記 (2017/11/22)
コメントでdefined?関数を教えていただきました.
こちらを使うほうが短くて直感的な書き方にできるのでよさそうです.
if !defined? IS_REQUIRED
IS_REQUIRED = true
# Do something.
end
---///