Home > Back-end >  Unable to load classes/modules with Zeitwerk on Ruby project
Unable to load classes/modules with Zeitwerk on Ruby project

Time:01-20

I have built a Ruby project and I want to switch from using a ton of require_relative statements to a smarter autoloader like Zeitwerk.

There are a lot of files/folders in the project, hence I will only present some of them in order to not make my question too bloated with unnecessary details. You can find the whole project on Github.

The structure of the repo is the following

chess/
  bin/
    chess
  lib/ 
    chess.rb
    chess/
      board.rb 
      serialize.rb
      coordinates.rb 
      pieces/
      (other files and folders)
  (other files)

bin/chess is the executable file and contains

#!/usr/bin/env ruby
require "zeitwerk"

loader = Zeitwerk::Loader.for_gem
loader.setup

Chess.new.start  # method that starts the whole game

lib/chess.rb contains the class Chess, which runs the logic of the game.

All the other files inside of lib/ follow the Zeitwerk file structure.

Therefore, lib/chess/serialize.rb contains

class Chess 
  module Serialize 
    ... 
  end 
end 

and lib/chess/pieces/piece.rb contains

class Chess 
  module Pieces 
    module Piece 
    ... 
    end 
  end 
end

When I try to run the script, with bin/chess (or cding into the bin/ folder and running ./chess), the terminal returns

uninitialized constant Chess (NameError)

It seems like the files are not loaded by Zeitwerk. I read all of their documentation but I was not able to discover what's the problem.

edit: As one of the answers pointed out, I removed the for_gem call in bin/chess with

loader = Zeitwerk::Loader.new
loader.tag = File.basename(__FILE__, ".rb")
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
loader.push_dir("#{__dir__}/../lib")
loader.setup

However, now all the classes/modules inside of the Chess namespace are loaded, while all the ones located in deeper directories, like lib/chess/display/chess_display.rb (which is Chess::Display::ChessDisplay) don't get loaded.

CodePudding user response:

According to the Readme, for_gem corresponds to:

# lib/my_gem.rb

require "zeitwerk"
loader = Zeitwerk::Loader.new
loader.tag = File.basename(__FILE__, ".rb")
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
loader.push_dir(__dir__)

note the push_dir(__dir__) and that it's expected to be called from the lib/ directory. You're calling it from a file in the bin/ directory.

I have not used zeitwork like this, but I think you might want to either:

  1. directly require just lib/chess.rb and perform the Zeitwork::Loader.for_gem in that file
  2. Skip for_gem and use the snippet above, but adjust __dir__ to be ../lib.

CodePudding user response:

You use for_gem in lib/chess.rb. That way, the setup is ready no matter how you load the library.

The bin/chess executable then loads the entry point. It should not issue a require_relative, but a regular require 'chess' assuming lib is in the load path.

Same for the test suite, just require 'chess' in the test or spec helper that sets the test suite up.

  • Related