Reputation: 9407
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
Reputation: 369420
So what is
require
doing that does not cause the syntax error?
Simple: it runs the file.
Upvotes: 1
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 require
d; 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