I am getting `const_missing': uninitialized constant Disc::Foo1 (NameError) while executing the code
disc.rb
module Disc
MAPPING = {
:new => {
:first => Foo1,
:second => Foo2
},
:old => {
:third => Foo3,
:fourth => Foo4
}
def run
# :new and :first is example but will be any value decided based on the logic dynamically.
cls = MAPPING[:new][:first]
cls.new.execute
end
}
end
foo1.rb
class Foo1
def initialize
end
def execute
puts "f1"
end
end
foo2.rb
class Foo2
def initialize
end
def execute
puts "f2"
end
end
foo3.rb
class Foo3
def initialize
end
def execute
puts "f3"
end
end
foo4.rb
class Foo4
def initialize
end
def execute
puts "f4"
end
end
I am new to ruby. I tried to find the solution and found that I have to add require clause and tried many things but didn't work. Can someone please help me out here?
CodePudding user response:
Ruby can't autoload files — you need to explicitly tell it to load them. Just by sitting in the same directory they will not do anything. This is because, unlike some other languages, there is no predictable set mapping between file names and their contents, so Ruby can't know where the class you wanted is. You have to
require './foo1'
require './foo2'
require './foo3'
require './foo4'
Alternately, you could do it a bit smarter:
%w(./foo1 ./foo2 ./foo3 ./foo4).each do |file|
require file
end
Now that you know about require
... Saying that Ruby can't autoload files was a lie to tell people new to Ruby. Ruby can totally autoload files. But you still have to tell it what they are and where to find them. Just by sitting in the same directory they will not do anything. You have to
autoload(:Foo1, './foo1.rb')
autoload(:Foo2, './foo2.rb')
autoload(:Foo3, './foo3.rb')
autoload(:Foo4, './foo4.rb')
The files will not be loaded, unless you try to access the named classes; when you do so, Ruby will remember you told it where to find those classes, and load the corresponding file. This saves the program start time and memory, as only the actually needed files will be loaded.
You can also do this instead:
# for each `./foo*.rb` file
Dir.glob('./foo*.rb').each do |file|
# turn it into the class name; e.g. `./foo1.rb' -> :Foo1
classname = file[%r{\./(.*)\.rb$}, 1].capitalize.to_sym
# then tell Ruby that that classname can be found in that file
autoload(classname, file)
end
However, since you explicitly have the classes in your MAPPING
, autoloading will not help you — all of the foo files will be autoloaded at the moment MAPPING
is defined, so there is no benefit to using autoload
here. If you stored names instead of class objects themselves, and used Object.const_get
to look up the class object constant, then it would work:
autoload(:Foo1, './foo1.rb')
# `./foo1.rb` is not yet loaded
MAPPING = {
new: {
first: :Foo1,
# ...
},
# ...
}
# `./foo1.rb` is still not loaded:
# we haven't used the `Foo1` constant yet
# (we did use `:Foo1` symbol, but that is a completely different thing)
cls = Object.const_get(MAPPING[:new][:first])
# `./foo1.rb` is now loaded, and `cls` is `Foo1`