読者です 読者をやめる 読者になる 読者になる

Rails の require_dependency とは何か?

rails

Rails Guide を見てみますが、いまいちピンとこないのでソースを見てみました。 http://railsguides.jp/autoloading_and_reloading_constants.html#require-dependency

先にまとめ

require_dependency とは、production環境では require
development環境では Kernel.load する。
Kernel.load は呼ばれるたびにロードされるので、開発時には効率が良い。
通常は、autoload でことが足りそうな気がしますが、 何かautload_pathsに含まれないものをrequireする際には使うと良いでしょう。

ソース見てみるよー

require_dependency

activesupport-5.0.0.1/lib/active_support/dependencies.rb

      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 を呼んでます。

depend_on

    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 を呼んでいます。
 
別件ですが、なんかdefaultのmessageが呼び出し元と若干違いますね。
少し違和感があります(後でもう少し見てみるかも)。

require_or_load 前半

少し長いので分割。

    def require_or_load(file_name, const_path = nil)
      file_name = $` if file_name =~ /\.rb\z/
      expanded = File.expand_path(file_name)
      return if loaded.include?(expanded)

      Dependencies.load_interlock do
        # Maybe it got loaded while we were waiting for our lock:
        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

ActiveSupport::Concurrency::ShareLock を使って安全にロックを掛けつつ loaded に読み込み済みのリスト(Set)を保持しています。

こんな感じ。

#<Set: {"/Users/rochefort/work/rails5_example/app/controllers/articles_controller",
 "/Users/rochefort/work/rails5_example/app/controllers/application_controller",
 "/Users/rochefort/work/rails5_example/app/helpers/application_helper",
 "/Users/rochefort/work/rails5_example/app/helpers/tags_helper",
 "/Users/rochefort/work/rails5_example/app/helpers/user_sessions_helper",
 "/Users/rochefort/work/rails5_example/app/helpers/users_helper",
 "/Users/rochefort/work/rails5_example/app/helpers/articles_helper",
 "/Users/rochefort/work/rails5_example/app/models/article",
 "/Users/rochefort/work/rails5_example/app/models/application_record",
 "/Users/rochefort/work/rails5_example/app/uploaders/image_uploader"}>

require_or_load 後半

begin
  if load?
    # 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
    result = require file_name
  end

load? がtrueの場合は、load_file を呼んでいますが、falseの場合は require しているだけのようです。

load_file

まずは load_file を見てみると Kernel.load しています。

def load_file(path, const_paths = loadable_constants_for_path(path))
  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!
  result
end

load?

続いてload?

def load?
  mechanism == :load
end

mechanism という値を参照しているだけです。

mechanism

こいつはどこから来るのか?

mattr_accessor :mechanism
self.mechanism = ENV['NO_RELOAD'] ? :require : :load

一見、環境変数で出しわけているだけのようですが
おそらくconfigのファイルで出しわけてそうなので、config/production.rb を見てみます。
 
しかし、直接 mechanism を設定している箇所はありません。 ですが、以下が怪しそうです。

config.cache_classes = true

railties

railties-5.0.0.1/lib/rails/application/bootstrap.rb

module Rails
  class Application
    module Bootstrap


      # Sets the dependency loading mechanism.
      initializer :initialize_dependency_mechanism, group: :all do
        ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load
      end

ありました!
cach_classes の値を見て require / load を設定していました。