LoadError: Unable to autoload constant in Rails - ruby-on-rails

I am running a Rails application.
app/workers/sample/fetch_book_report_worker.rb
class Sample::FetchBookReportWorker
// body
end
app/workers/sample/fetch_student_report_worker.rb
class Sample::FetchStudentReportWorker
// body
end
app/workers/sample/fetch_teacher_report_worker.rb
class Sample::FetchTeacherReportWorker
// body
end
app/workers/sample/fetch_college_report_worker.rb
class Sample::FetchCollegeReportWorker
// body
end
When I ran the above worker individually, I didn't see an issue. But I ran all the workers at the same time. I got the below error.
LoadError: Unable to autoload constant Sample::FetchStudentReportWorker, expected /home/ubuntu/my-app/app/workers/sample/fetch_student_report_worker.rb to define it
How to solve this?

You can see autoload_paths to type this command.
bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths'
If you can't see .../app/workers/sample, add the autoload_paths in config/application.rb
config.autoload_paths << Rails.root.join("app/workers/sample")
If it could causes problem in production mode, use the 'eager_load_paths'
referece from https://guides.rubyonrails.org/autoloading_and_reloading_constants.html

One likely root cause for the LoadError error is, that as of Rails 5.2.0, there are changes to autoload, that require submodules (namespaces) to (at the least) be defined (as a constant).
In the original post's example, the module Sample should be defined in the app/workers/sample.rb file (as commented by Vasfed above).
Refer to this related SO answer for more details.

Related

Unable to autoload constant A, expected <path> to define it rails

I'd searched internet on this problem, and the answers say that the most common things are:
Rails naming conventions are begin violated. For example: filename is hello_world.rb and the classname is Hello_World instead of HelloWorld.
In config.autoload_paths in application.rb there is no path required.
None of these cases are mine: I got file under /lib/admin_analytics.rb and module name AdminAnalytics. Also I have config.autoload_paths << Rails.root.join('lib) in application.rb.
But in development mode, when I change the code and refresh the page (making request) the
Unable to autoload constant AdminAnalytics, expected /lib/admin_analytics.rb to define it.
error comes. How to fix this? Thanks in advance!
Ruby version 2.6.0
Rails is 5.2.5
The beginning of the module
module AdminAnalytics
def self.compute_numbers(options = {})
require 'admin/compute_numbers'
# ComputeNumbers.debug = true
ComputeNumbers.send(options[:method], options)
end

Accessing modules in Rails lib folder

There are probably hundreds of these questions on here but I haven't been able to get one to work yet. I'm using Rails 6 and I always have trouble writing custom modules.
I have a file for creating email tokens: lib/account_management/email_token.rb
module AccountManagement
module EmailToken
def create_email_token(user)
....
end
end
end
Then in my controller I have tried all kinds of things:
require 'account_management/email_token'
include AccountManagement::EmailToken
Calling it with: create_and_send_email_token(user)
At one point I added config.eager_load_paths << Rails.root.join('lib/account_management/') but I didn't think you still had to do that.
Whenever I try to call the controller action I get one of a few error messages:
*** NameError Exception: uninitialized constant Accounts::SessionsController::EmailToken
#this happens if I try to manually send through the rails console
(although inside of a byebug I can call require 'account_management/email_token' and it returns
true.
Most commonly I get:
NoMethodError (undefined method `create_email_token' for #<Accounts::SessionsController:0x00007ffe8dea21d8>
Did you mean? create_email):
# this is the name of the controller method and is unrleated.
The simplest way to solve this is by placing your files in app/lib/account_management/email_token.rb. Rails already autoloads any subdirectory of the app folder*.
/lib has not been on the autoload paths since Rails 3. If you want to add it to the autoload paths you need to add /lib not /lib/account_management to the autoload/eager loading paths. In Zeitwerk terms this adds a root where Zeitwerk will index and resolve constants from.
config.autoload_paths += config.root.join('lib')
config.eager_load_paths += config.root.join('lib')
Note that eager_load_paths is only used when eager loading is turned on. Its turned off by default in development to enable code reloading.
/lib is added to $LOAD_PATHso you can also manually require the file with:
require 'account_management/email_token'
See:
Autoloading and Reloading Constants (Zeitwerk Mode)
Rails #37835

Zeitwerk error on devise mailer in production environment

I have Rails 6, my preview class located in
mailer/previews/devise_mailer_preview.rb:
class DeviseMailerPreview < ActionMailer::Preview
...
end
And when I run application locally, everything is going fine, I can see my email previews on http://localhost:3000/rails/mailers/devise_mailer/confirmation_instructions address. But now Im trying to deploy application on server, and found that when I run bundle exec rails c production, I got the error:
/home/deploy/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/zeitwerk-2.3.0/lib/zeitwerk/loader/callbacks.rb:17:in
`on_file_autoloaded': expected file
/home/deploy/project/releases/20200627024908/app/mailer/previews/devise_mailer_preview.rb
to define constant Previews::DeviseMailerPreview, but didn't
(Zeitwerk::NameError)
After that I've checked locally RAILS_ENV=production rails c, and got same.
If I will rename DeviseMailerPreview class to Previews::DeviseMailerPreview, it will be broken and I cannot see emails on development, because Rails 6, accordingly to docs, expect exactly that name.
More of that, I've found in this article, that zeitwerk can be configured with autoload_paths param to avoid ruby's NameError. I found that I have it my config/application.rb:
config.load_defaults 6.0
Anyway I tried to add same row in my config/environments/production.rb file, but it didn't help.
What am I doing wrong and how can I fix it? Thanks in advance!
Add preview_path to the autoload_paths and zeitwerk will expect DeviseMailerPreview constant to be defined.
# development.rb
config.action_mailer.preview_path = Rails.root.join("app/mailers/previews")
# application.rb
config.autoload_paths << Rails.root.join("app/mailers/previews")
Your Mailer preview file is located in mailer/previews/devise_mailer_preview.rb, so I'm assuming it's full path is app/mailer/previews/devise_mailer_preview.rb
The docs says
In the above example, the preview class for UserMailer should be named UserMailerPreview and located in test/mailers/previews/user_mailer_preview.rb
So put your devise_mailer_preview.rb file to test/mailers/previews/devise_mailer_preview.rb
or in your config/application.rb add this line and restart:
config.action_mailer.preview_path = "#{Rails.root}/app/mailers/previews"
Actually the answer was in the docs itself.
In Rails 6, previews are added to the autoload paths only if options.show_previews is true, which is not by default in the production environment. See the source code here.
The reason for this is that previews are supposed to be an aid for development, and they are generally not something you want to be able to look at in production.
However, you can set that flag to true in production if you want.
There's another derivative: By storing the previews under app/mailers, Rails is going to eager load them because app/mailers is in the autoload paths. If app/mailers/previews is not in the autoload paths, eager loading will fail due to the namespace mismatch. Either you have them enabled in all environments, or else is better to have them in a separate location, like the default.

Rails unable to autoload constant from file despite being defined in that file

This is a tricky one to explain. I have a module in another module namespace like so:
# app/models/points/calculator.rb
module Points
module Calculator
def self.included(base)
base.send(:include, CommonMethods)
base.send(:include, "Points::Calculator::#{base}Methods".constantize)
end
end
end
So then in other classes all I need to do is:
class User
include Points::Calculator
end
I've specified this directory in application.rb to be autoloadable...(even though i think rails recurses through models...)
config.autoload_paths += Dir[ Rails.root.join('app', 'models', "points") ]
In development env, everything works fine. When running tests(and production env), I get the following error:
Unable to autoload constant Points::Calculator, expected /Users/pete/work/recognize/app/models/points/calculator.rb to define it (LoadError)
I actually followed the advice here to fix the problem: Stop Rails from unloading a module in development mode by explicitly requiring calculator.rb in application.rb.
However, why is this happening??
I stuck some debug output in ActiveSupport's dependencies.rb file and noticed that this file is being required twice. The first time its required I can see that the constant is indeed loaded.
But the 2nd time its required the constant has been unloaded as far as Rails can tell, but when the actual require is called, ruby returns false because ruby knows its already required it. Then Rails throws the "unable to autoload constant" error because the constant still isn't present and ruby didn't "re-require" the file.
Can anyone shed light on why this might be happening?
Rails augments the constant lookup mechanism of ruby.
Constant lookup in Ruby:
Similar to method missing, a Module#constant-missing is invoked when a reference to a constant fails to be resolved. When we refer to a constant in a given lexical scope, that constant is searched for in:
Each entry in Module.nesting
Each entry in Module.nesting.first.ancestors
Each entry in Object.ancestors if Module.nesting.first is nil or a module.
When we refer to a constant, Ruby first attempts to find it according to this built-in lookup rules.
When ruby fails to find... rails kicks in, and using its own lookup convention and its knowledge about which constants have already been loaded (by ruby), Rails overrides Module#const_missing to load missing constants without the need for explicit require calls by the programmer.
Its own lookup convention?
Contrasting Ruby’s autoload (which requires the location of each autoloaded constant to be specified in advance) rails following a convention that maps constants to file names.
Points::Calculator # =>points/calculator.rb
Now for the constant Points::Calculator, rails searches this file path (ie 'points/calculator.rb') within the autoload paths, defined by the autoload_paths configuration.
In this case, rails searched for file path points/calculator in its autoloaded paths, but fails to find file and hence this error/warning is shown.
This answer is an abstract from this Urbanautomation blog.
Edit:
I wrote a blog about Zeitwerk, the new code reloader in Rails. Check it out at -> https://blog.bigbinary.com/2019/10/08/rails-6-introduces-new-code-loader-called-zeitwerk.html
If someone is having this issue in rails 6 which has zeitwerk autoloader,
Change ruby constant lookup back to classic in your application.rb
# config/application.rb
#...
config.autoloader = :classic
#...
Read more details here Rails Official Guides
Calculator should be a class to be autoloaded correctly
module Points
class Calculator
...
end
end

uninitialized constant in My::Engine after file change

I'm developing a gem/engine. The way I do this is by bundling it in a test RailsApp from source:
# Gemfile
gem 'my-engine', path: '../local/path/to/gem'
This works fine so far.
But, after I change a file in my gem (add a space or a break for example) the Engine is unloaded. Causing the following error:
uninitialized constant My::Engine
This error is thrown by the file that does the first call to My::Engine. ( I need to call that to get the root: My::Engine.root ) If I delete that line, there are no error thrown, but just an empty page is rendered, and this is happening because all my SQL's change and no content is loaded from the database. I think this is because the files in the lib dir are unloaded, because in these files I dynamically create active-record models..
I already checked out the autoload_paths and watchable_dirs:
# engine.rb
module My
class Engine < Rails::Engine
engine_name 'my-engine'
initializer "my-engine.load_config" do |app|
app.config.autoload_paths += %W(#{Engine.root}/lib)
app.config.watchable_dirs["#{Engine.root}/lib"] = [:rb]
end
end
end
I'm not sure if I'm implementing these the right way, but they don't seem to solve my problems the way I'm using them.
I think you may need to require 'my/engine' before calling My::Engine.root, or change the order of your requires so that 'my/engine' is required prior to the file that makes a call to My::Engine.

Resources