Daniel Viglione
Daniel Viglione

Reputation: 9407

requiring a file within a method

I was looking at the contents of the Ransack ruby gem. Basically, it calls a method called require_constants. And that method itself requires a file:

# ransack.rb
require 'ransack/adapters'
Ransack::Adapters.object_mapper.require_constants

# adapters.rb
module Ransack
  module Adapters

    def self.object_mapper
      @object_mapper ||= instantiate_object_mapper
    end

    def self.instantiate_object_mapper
      if defined?(::ActiveRecord::Base)
        ActiveRecordAdapter.new
      elsif defined?(::Mongoid)
        MongoidAdapter.new
      end
    end

    class ActiveRecordAdapter
      def require_constants
        require 'ransack/adapters/active_record/ransack/constants'
      end
      ...

# constants.rb
module Ransack
  module Constants

The first require copies the content of adapters.rb in ransack.rb, I believe. Hence, we can then reference Ransack::Adapters without an undefined error.

However, when we call require_constants, it appears to copy the contents of Ransack::Constants into the method definition of require_constants.

I find that kind of confusing. We are copying a module inside of a method. What benefit do we get of copying a module inside of a method, rather than just doing it like the other require? Second, I know the module is not a local variable, but I couldn't even define a module in the console when I tried it:

class A
  def a
    module B end
  end
end
SyntaxError: (irb):14: module definition in method body

So what is require doing that does not cause the syntax error?

Upvotes: 1

Views: 787

Answers (2)

Jörg W Mittag
Jörg W Mittag

Reputation: 369420

So what is require doing that does not cause the syntax error?

Simple: it runs the file.

Upvotes: 1

Jordan Running
Jordan Running

Reputation: 106017

"Copy" is the wrong word here. require does not copy anything. It reads the source code in the given file and executes that code in Ruby's global ("main") context (unless the file has already been required; then it does nothing and returns false). To quote the docs (emphasis mine):

Any constants or globals within the loaded source file will be available in the calling program’s global namespace.

require does not behave differently inside a method call, or inside a module, or anywhere else, than it does at the top of a file.

When you see require inside a method call the reason is usually that the module is only needed in a particular scenario, and loading it before that scenario occurs would be wasteful (because, for example, the module takes a long time to load or accesses, during loading, resources that are only available in that scenario—think different database drivers or OSes). It does not mean that the module is being loaded "into" the method or the surrounding code, because that's not what require does.

To demonstrate, suppose we have a module like this:

# baby_module.rb
module BabyModule
  NAME = "Baby"
end

And suppose we run the following program:

module TheCorner
  def self.load_baby_module
    require File.expand_path("baby_module", __dir__)
  end
end

TheCorner.load_baby_module

if defined?(TheCorner::BabyModule)
  puts "#{TheCorner::BabyModule::NAME} is in TheCorner"
elsif defined?(BabyModule)
  puts "Nobody puts #{BabyModule::NAME} in TheCorner"
end

As you have perhaps already guessed, this program's output will be:

Nobody puts Baby in TheCorner

Upvotes: 5

Related Questions