Donato Donato - 2 months ago 7
Ruby Question

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?

Answer

"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 up a lot of memory or takes a long time to load). 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

Comments