Rails 6+, zeitwerk autoloader and namedspaced constants
Asked Answered
A

3

6

The Rails 6+ default autoloader is zeitwerk, which seems like a great improvement over previous approaches.

However, zeitwork follows the convention for Rails projects that anything in app/* is autoloaded and doesn't need to be namespaced.

This works great for app/models/user.rb because you don't have to use Models::User but can just reference User.

However, I added my own app/services directory and I namespace my service objects as Services::Users::Create, which would map to app/services/users/create.rb.

Zeitwork is throwing errors that my class constants don't exist, since it is expecting Users::Create (without the Services:: prefix).

Is there anyway to configure zeitwork to require the Services:: namespace in these instances? In my opinion, it is a much cleaner to read the code as Services::Users::Create and know that you're looking in the app/services/users/create.rb file.

If you just had Users::Create, an average Rails developer would probably look for the app/models/users/create.rb file.

I don't like the approach of naming it Users::CreateService, it just seems very inelegant to me.

I can't be the only one who uses conventions like this; has anyone else come across a solution? I'm still going through all the zeitwerk documentation looking for a solution but haven't found one yet.

Afloat answered 21/11, 2019 at 18:52 Comment(0)
D
5

https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#having-app-in-the-autoload-paths

Some projects want something like app/api/base.rb to define API::Base, and add app to the autoload paths to accomplish that in classic mode. Since Rails adds all subdirectories of app to the autoload paths automatically, we have another situation in which there are nested root directories, so that setup no longer works. Similar principle we explained above with concerns.

If you want to keep that structure, you'll need to delete the subdirectory from the autoload paths in an initializer:

ActiveSupport::Dependencies.autoload_paths.delete("#{Rails.root}/app/api")
Desilva answered 20/12, 2019 at 9:24 Comment(1)
According to docs mutating ActiveSupport::Dependencies.autoload_paths is not recommended, the public interface to change autoload paths is config.autoload_paths guides.rubyonrails.org/…Bareilly
H
0

I needed something similar but for integrations. I also didn't want the class to be called Integrations::Base but instead Integration::Base.

I created a file config/initializers/integrations.rb with the following:

ActiveSupport::Dependencies.autoload_paths.delete("#{Rails.root}/app/integrations")

autoloader = Rails.autoloaders.main
autoloader.push_dir(Rails.root.join("app"))
autoloader.inflector.inflect "integrations" => "Integration"

Then created app/integrations/base.rb with:

module Integration
  class Base
  end
end
Heliogabalus answered 6/11, 2020 at 8:56 Comment(0)
B
0

Little late to the party here but had a very similar situation! Even named the folder the same thing "Services"

This seems to have helped add it to the auto loading path within the application.rb file:

config.autoload_paths += %W(#{config.root}/app/services/)

Then within the sub directory the module and class was defined like this:

#services/sub_folder/base.rb

module Services
  class SubFolder::Base
  end
end

I have a similar answer with more detail here.

Then more general Zeitwerk resources and tips for updating here.

Bryannabryansk answered 15/9, 2021 at 4:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.