I have a file which looks like this
#app/services/account/authenticate/base.rb
module Account
module Authenticate
AuthenticateError = Class.new(StandardError)
class Base < ::Account::Base
def self.call(*attrs)
raise NotImplementedError
end
end
end
end
Now when I will run the code from rails c
I have an error
> ::Account::Authenticate::AuthenticateError
=> NameError (uninitialized constant Account::Authenticate::AuthenticateError)
> ::Account::Authenticate.constants
=> [:Base, :ViaToken]
So rails doesn't see AuthenticateError class. But when I will create a nested class from this folder like
=> Account::Authenticate::ViaToken
> ::Account::Authenticate.constants
=> [:Base, :AuthenticateError, :ViaToken]
AuthenticateError class is now visible
> ::Account::Authenticate::AuthenticateError
=> Account::Authenticate::AuthenticateError
The solution for this problem is to create a separate file authenticate_error.rb which will work from the beginning but this solution is not ideal for me. Is there any solution to preload all classes or smth?
(Ruby 2.6 with Rails 6.0.0.rc2)
I experienced this same issue when deploying a Rails 6.0.2 application to an Ubuntu 18.04 server.
Unable to load application: Zeitwerk::NameError: expected file /home/deploy/myapp/app/models/concerns/designation.rb to define constant Designation, but didn't
I found out that the issue was with zeitwerk. Zeitwerk is the new code loader engine used in Rails 6. It’s meant to be the new default for all Rails 6+ projects replacing the old classic engine. Zeitwerk provides the features of code autoloading, eager loading, and reloading.
Here's how I solved it:
Navigate to the config/application.rb
file on your project.
Add this line within your application module to switch to classic
mode for autoloading:
config.autoloader = :classic
Here's an example:
module MyApp
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.0
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
config.autoloader = :classic
end
end
You can read up more on zeitwerk in this article: Understanding Zeitwerk in Rails 6
That's all.
I hope this helps
updating a Rails app from 5.2 to 6.0 and also hit the Zeitwerk bump!
If you want to continue to use the autoloading mode you're currently using, avoiding Zeitwerk, then add this line to your application.rb file (@PromisePreston answer and Rails doc)
config.autoloader = :classic
If you want to upgrade to Zeitwerk then a command to use is bin/rails zeitwerk:check
(From this guide article).
Scenario we hit closest to this particular question was where we had a file within a subfolder like this:
#presenters/submission_files/base.rb
module Presenters
module SubmissionFiles
class Base < Showtime::Presenter
def method_call
#code_here
end
end
end
end
Removing an extra modules to have:
#presenters/submission_files/base.rb
module Presenters
class SubmissionFiles::Base < Showtime::Presenter
def method_call
#code_here
end
end
end
Then, when calling the method in other ruby files in the app use: Presenters::SubmissionFiles::Base.method_call
zeitwerk
follow conventional file structure
which is able to load your project's classes and modules on demand (autoloading) as long as you follow it's rule.
# service/account/authenticate/base.rb
module Account
module Authenticate
puts "load service ....."
AuthenticateError = Class.new(StandardError)
class Base
end
end
end
::Account::Authenticate::AuthenticateError # uninitialized constant
::Account::Authenticate::Base # load service ....
::Account::Authenticate::AuthenticateError # OK
as you can see, the first time you attempt to reach the constant AuthenticateError
, the log load service ...
does not show, that because you don't play zeitwerk
rule:
whenever it gets a request to load the const ::Account::Authenticate::AuthenticateError
, first it'll check and return if that constant already loaded, otherwise it'll look for the file /account/authenticate/authenticate_error.rb
which corresponding to the constant ::Account::Authenticate::AuthenticateError
to find that constant definition, but it could not find it.
on step 2, when you call ::Account::Authenticate::Base
, it could find the file /account/authenticate/base.rb
and load it, during this time, it's also load the constant AuthenticateError
which is defined on that file, now we have the constant ::Account::Authenticate::AuthenticateError
, and of course, it's OK on step 3.
now let try to play with the zeitwerk
rule, i create a file /account/authenticate/authenticate_error.rb
as below
# service/account/authenticate/authenticate_error.rb
module Account
module Authenticate
puts "load error ....."
AuthenticateError = Class.new(StandardError)
end
end
and try to attempt that constant at the step 1
$ spring stop
$ rails c
> ::Account::Authenticate::AuthenticateError
load error .....
=> Account::Authenticate::AuthenticateError
it worked since zeitwerk
found the file account/authenticate/authenticate_error.rb
. (note that the file name /____authenticate_error.rb
still work)
my thought: i think you could work safely with the constant AuthenticateError
inside the module ::Account::Authenticate
, in case of you want to expose those error constants to outside, you could create file /account/authenticate/error.rb
# service/account/authenticate/error.rb
module Account
module Authenticate
module Error
AuthenticateError = Class.new(StandardError)
end
end
end
then you could access ::Account::Authenticate::Error::AuthenticateError
, in my opinion, it even clearer than put AuthenticateError
inside base.rb
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With