しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

require_dependencyの実装を追いかけてみる

require_dependencyはrequireとどう違うの? というところから色々調べてみたメモです。
require_dependencyとrequireの違いはググったら一発解決しました。
違いは下の通りです。

折角なのでrequire_dependencyはrequireなしでどうやってファイルを呼んでいるか調べてみました。

ファイルの読み込み実装を調べる

require_dependencyactive_supportDependenciesモジュールで定義されています。
実装は下の通りです。
ファイルの読み込みは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_loadloading << expandedはその対策みたいですね。
あとは「ファイル名の.rbを省略できない」という事があるのでrequire_or_loadではちゃんと.rbを付ける対応をしてくれているようです。

まとめ

  • require_dependencyはrequireと違って一度読み込んだファイルも再読み込みしてくれる
  • require_dependencyはloadと違って.rbの省略ができたり相互読み込みでの無限ループが対策されていて良い