require_dependencyはrequireとどう違うの? というところから色々調べてみたメモです。
require_dependencyとrequireの違いはググったら一発解決しました。
違いは下の通りです。
- requireはロード済のものはキャッシュして再読み込みしないけどrequire_dependencyは何度でも読む
- requireはRubyのメソッドでrequire_dependencyはRailsのメソッド
折角なのでrequire_dependencyはrequireなしでどうやってファイルを呼んでいるか調べてみました。
ファイルの読み込み実装を調べる
require_dependency
はactive_support
のDependencies
モジュールで定義されています。
実装は下の通りです。
ファイルの読み込みはDependencies.depend_onでやっているようなのでそこを追いかけていきます。
def require_dependency(file_name, message = "No such file to load -- %s") file_name = file_name.to_path if file_name.respond_to?(:to_path) unless file_name.is_a?(String) raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}" end Dependencies.depend_on(file_name, message) end
depend_on
の実装は下の通りでした。
更にrequire_or_load
というメソッドを追いかけます。
def depend_on(file_name, message = "No such file to load -- %s.rb") path = search_for_file(file_name) require_or_load(path || file_name) rescue LoadError => load_error if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] load_error.message.replace(message % file_name) load_error.copy_blame!(load_error) end raise end
require_or_load
は下の通りです。
真ん中辺りのload_file
ってメソッドで読み込みしていそうなので更に追いかけてみます。
def require_or_load(file_name, const_path = nil) log_call file_name, const_path file_name = $` if file_name =~ /\.rb\z/ expanded = File.expand_path(file_name) return if loaded.include?(expanded) # Record that we've seen this file *before* loading it to avoid an # infinite loop with mutual dependencies. loaded << expanded loading << expanded begin if load? log "loading #{file_name}" # Enable warnings if this file has not been loaded before and # warnings_on_first_load is set. load_args = ["#{file_name}.rb"] load_args << const_path unless const_path.nil? if !warnings_on_first_load or history.include?(expanded) result = load_file(*load_args) else enable_warnings { result = load_file(*load_args) } end else log "requiring #{file_name}" result = require file_name end rescue Exception loaded.delete expanded raise ensure loading.pop end # Record history *after* loading so first load gets warnings. history << expanded result end
load_file
の実装は下の通りです。
この中にあるKernel.load
で読み込んでいるようですね。
def load_file(path, const_paths = loadable_constants_for_path(path)) log_call path, const_paths const_paths = [const_paths].compact unless const_paths.is_a? Array parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object } result = nil newly_defined_paths = new_constants_in(*parent_paths) do result = Kernel.load path end autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) autoloaded_constants.uniq! log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty? result end
loadとは
loadも詳しく知らないので調査してみました。
これはrequireと似たメソッドで「一度読み込んだファイルでも再読み込みをする」「ファイル名の.rbを省略できない」等の特徴があるようです。
それとloadは何度も読み込む為、相互読み込みで無限ループを起こす事があるようです。
require_or_load
のloading << expanded
はその対策みたいですね。
あとは「ファイル名の.rbを省略できない」という事があるのでrequire_or_load
ではちゃんと.rbを付ける対応をしてくれているようです。
まとめ
- require_dependencyはrequireと違って一度読み込んだファイルも再読み込みしてくれる
- require_dependencyはloadと違って.rbの省略ができたり相互読み込みでの無限ループが対策されていて良い