fxn/zeitwerk

How to handle path with case sensitivity?

ejstembler opened this issue · 9 comments

I have a gem with a path like mygem/lib/mygem/IO/csv_writer.rb for Mygem::IO::CsvWriter where the IO path is case sensitive on the deployed container. And the module name is too.

However, Zeitwerk fails with this error:

/Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/helpers.rb:135:in 'const_get': uninitialized constant Mygem::Io::CsvWriter (NameError)

    parent.const_get(cname, false)

          ^^^^^^^^^^
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/helpers.rb:135:in cget'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/eager_load.rb:169:in 'block in actual_eager_load_dir'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/helpers.rb:40:in 'block in ls'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/helpers.rb:25:in 'each'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/helpers.rb:25:in 'ls'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/eager_load.rb:164:in 'actual_eager_load_dir'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/eager_load.rb:17:in 'block (2 levels) in eager_load'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/eager_load.rb:16:in 'each'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/eager_load.rb:16:in 'block in eager_load'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/eager_load.rb:10:in 'synchronize'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader/eager_load.rb:10:in 'eager_load'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader.rb:297:in 'block in eager_load_all'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader.rb:295:in 'each'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.7/lib/zeitwerk/loader.rb:295:in 'eager_load_all'

Any way around this?

fxn commented

Hey, have you seen this?

@fxn Thanks for the reply. I did try this:

require 'zeitwerk'

# Setup auto-loading
loader = Zeitwerk::Loader.new
loader.push_dir(Rails.root.join('lib', 'mygem', 'lib'))

loader.inflector.inflect(
  'Mygem::Io::CsvWriter' => 'Mygem::IO::CsvWriter'
)

loader.setup

Though the error still persists. Did I use it correctly?

fxn commented

Should be

loader.inflector.inflect('io' => 'IO')

In this simple API you inflect only the basename, I realize this is not clear enough in the docs, will improve them.

@fxn I appreciate your guidance. Unfortunately, neither of these worked:

loader.inflector.inflect('io' => 'IO')

or

loader.inflector.inflect('Io' => 'IO')

Still that same error:

uninitialized constant Mygem::Io::CsvWriter

Same stacktrace as before.

Incidentally, if I try putting the code in the gem module and use .for_gem and later loader.eager_load after the module, it returns a similar but different error:

/Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/callbacks.rb:25:in 'on_file_autoloaded': expected file /Users/myuser/Projects/mygem/lib/mygem/IO/csv_writer.rb to define constant mygem::Io::CsvWriter, but didn't (Zeitwerk::NameError)

      raise Zeitwerk::NameError.new(msg, cref.last)
      ^^^^^
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/kernel.rb:31:in 'require'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/helpers.rb:135:in 'const_get'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/helpers.rb:135:in 'cget'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/eager_load.rb:169:in 'block in actual_eager_load_dir'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/helpers.rb:40:in 'block in ls'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/helpers.rb:25:in 'each'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/helpers.rb:25:in 'ls'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/eager_load.rb:164:in 'actual_eager_load_dir'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/eager_load.rb:17:in 'block (2 levels) in eager_load'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/eager_load.rb:16:in 'each'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/eager_load.rb:16:in 'block in eager_load'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/eager_load.rb:10:in 'synchronize'
        from /Users/myuser/.asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.8/lib/zeitwerk/loader/eager_load.rb:10:in 'eager_load'
fxn commented

Ahhhh, sorry, I totally missed the name of the directory is "IO", all in capital letters! Please try instead

loader.inflector.inflect('IO' => 'IO')
fxn commented

Let me say that directory name is not conventional, you have csv_writer.rb, and by the same convention you normally have io. In Ruby, file and directory names are normally snake case.

But, if you want it that way, you can with the configuration above.

@fxn Thanks, that worked! loader.inflector.inflect('IO' => 'IO')

I originally had the lower-case path, with upper-case sub-module. That was fine while developing on macOS. Though when deploying to a GCP server, case sensitivity was enforced.. So, I changed to path to upper-case too.

fxn commented

@ejstembler awesome.

I see the documentation could be better in this regard too, the default inflector assumes snake case (and is based on String#capitalize!), but this is not explictly said.

fxn commented

I have improved the docs of the default inflector a bit in 5eca955.