9

I am upgrading a Rails 5.2.2 to Rails 6.0.0 that now has Zeitwerk.

Previously I had been extended core ruby classes like Date, Time, String, Float etc as described in this question. Using an initializers file to load all files from the lib/core_ext/* folder. When starting a rails server it now errors an the last line of the stacktrace reads:

/home/username/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/zeitwerk-2.1.10/lib/zeitwerk/loader.rb:351:in `const_get': uninitialized constant CoreExt::Date (NameError)

Unfortunately, Zeitwerk is causing an error where lib/core_ext/date.rb etc is throwing an error that it has already been defined (when using Rails.autoloaders.log! in application.rb. CoreExt::Date

I have since moved the files directly to initializers (previously I just had the initializers directory with a file that loaded each file from the 'lib/core_ext/* folder). This has fixed the issue for now but I'd like to keep core_ext folder and files where they were.

What have I missed here?

lib/core_ext/date.rb

class Date
  def to_sap
    strftime('%d.%m.%Y')
  end
end

I have tried wrapping explicitly in CoreExt but that did not help.

module CoreExt
  class Date
    def to_sap
      strftime('%d.%m.%Y')
    end
  end
end
Jay Killeen
  • 2,832
  • 6
  • 39
  • 66

2 Answers2

5

I have the same structure by extending core functionality and adding files in lib/extensions

I solved this by adding

# config/application.rb
#
class Application < Rails::Application
  ...
  Rails.autoloaders.main.ignore(Rails.root.join('lib/extensions'))
end

And I keep initializing extensions as previously:

Dir[Rails.root.join('lib', 'extensions', '**', '*.rb')].each { |f| require f }

dpaluy
  • 3,537
  • 1
  • 28
  • 42
  • I doubt that this is the "correct" way to solve this issue, but the proposed solution worked. Thank you! – Maris Mar 27 '20 at 18:27
  • 1
    Adding a little more detail to this answer--the initialization line `Dir[Rails.root.join...` should go in a file in `config/initializers`. I used `config/initializers/01_includes.rb` to ensure it would be loaded first. – Jason Machacek Nov 30 '21 at 16:49
  • I've placed this line in the same initializer that loads the `lib/core_ext/*.rb` files. Additional info can be found here: https://github.com/fxn/zeitwerk#ignoring-parts-of-the-project Since we already load the `lib/core_ext/*.rb` files manually in an initializer there is no reason for Zeitwerk to autoload them (they are already loaded) or check them. – 3limin4t0r May 13 '22 at 16:36
4

As is saying on https://github.com/fxn/zeitwerk#file-structure

loader.push_dir(Rails.root.join("app/models"))
loader.push_dir(Rails.root.join("app/controllers"))

And by Zeitwerk author: https://github.com/rails/rails/issues/37835#issuecomment-560563560

The lib folder does not belong to the autoload paths since Rails 3. @pixeltrix's is the recommended modern idiom if you want to autoload. Otherwise, lib belongs to $LOAD_PATH and you can require files in that directory.

We can call push_dir to load the lib subdirectories. My solution:

# config/initializers/zeitwerk.rb

Rails.autoloaders.main.push_dir(Rails.root.join('lib'))

or

# config/application.rb

  ...
  config.autoload_paths += [
    Rails.root.join('lib')
  ]

Then CoreExt::Date can be auto loaded.

Dat Le Tien
  • 1,880
  • 1
  • 9
  • 12
  • 1
    Thank you. I have reviewed those comments you link and that answer looks like the way to do this (having a `lib/core_ext/date.rb` file for example) in Rails with Zeitwerk. I have not tested this code in a new application yet but I don't see any reason why it wouldn't work based on your sources. If others can confirm it would be great but for now I'll accept as the answer. – Jay Killeen Jul 23 '20 at 00:39
  • The comment above requested confirmation, so I thought I'd provide feedback. Installing Ruby extensions via the method described in this answer does not seem to work. There are no error messages, but the extensions don't get applied to the core Ruby classes. It seems like @dpaluy's answer (excluding extensions from Zeitwork and `require`-ing them explicitly may be the best way to apply such extensions. – Jason Machacek Nov 30 '21 at 16:41
  • It should work because zeitwerk enforces a convention. If you had to patch `CoreExt::Date`, down the track it is `push_dir('lib')` then create a date.rb following this lib/core_ext/date.rb with `date.rb` includes the module ```ruby module CoreExt class Date ... end end – Dat Le Tien Jun 05 '22 at 15:43
  • Thanks for the answer Dat Le Tien. Nevertheless It seems that with this solution i can only access my extended methods by explicitly specifying CoreExt::Array. `Array.methods` doesn't show my methods but `CoreExt::Array.methods` do. I can't seem to find a solution where i can make them available without specifying the CoreExt module. – Slth Oct 05 '22 at 09:23