Home > Blockchain >  `const_missing': uninitialized constant (NameError) - Require
`const_missing': uninitialized constant (NameError) - Require

Time:07-28

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`
  • Related