How do I get classes autoloaded in the rails console? - ruby-on-rails

There are obviously some classes that are automatically loaded when I start "irb" using "rails c". So how do I ensure my own classes are loaded? Am I expected to use "require"?
I get "NameError: uninitialized constant" whenever I try to use my classes.

If the classes are included as part of the Rails app though the Gem bundle, or you're require-ing the classes elsewhere in the app, then they should be loaded along with the Rails app into the console.
If they're completely indepent of Rails (e.g. the Rails app doesn't load these classes), then you'll have to require them explicitly.

Related

Rails 7 - autoloading fails for Rails engine RSpec tests with gem

I maintain https://rubygems.org/gems/scimitar. Yesterday, we wanted to upgrade our primary application to Rails 7. This required a corresponding upgrade of Scimitar.
Tests I think are quite straightforward for a Rails engine; you have a dummy Rails app inside your tests, which requires the gem code as usual (require 'scimitar' in application.rb, in this case) and then - well - I guess via the mount in the dummy app's routes.rb, or some other autoloading magic, it thereafter "just works". Classes that are defined in the engine's application components (e.g. /app/models/gemname/foo.rb -> Gemmname::Foo) are autoloaded and available in your dummy application (e.g. /spec/dummy/app/controllers/some_controller.rb can reference Gemname::Foo).
The test suite works fine in Rails 6. If I change the gemspec file to reference Rails 7 and bundle update, tests immediately all fail. None of the constants defined in the engine are visible to the dummy app and since they're referenced by a configuration file in spec/dummy/app/config/initializers/scimitar.rb, the dummy app can't even complete Ruby parsing without raising NameError (uninitialized constant). I also ran a Rails 7 upgrade on that dummy app, but it does kinda nothing and had almost no changes; there were no changes to observed behaviour (tests still failed) and the post-upgrade dummy app ran tests successfully with the gem on Rails 6.
So, it just fails to behave in a recognisably sensible way on Rails 7.
Ruby version is unchanged at 2.7.x (though I would have bump to 3.1 if the Rails 7 update had worked).
I cannot find anything about this in 6->7 upgrade docs; classic autoloader has never been used and there is no specification about it either way; config.load_defaults as 6.0 or 7.0 makes no difference at all; I tried creating a new engine plugin under Rails 7 to play spot-the-difference, but couldn't see what might be wrong and it was obfuscated somewhat since I'm using RSpec but the out-of-box template uses Minitest.
Can anyone please help explain what is going on here?
As it stands in Rails 7.0.1, I found no solution so had to hack around it. Anyone using the Scimitar gem would need to wrap their initializer code (config/initializers/scimitar.rb) with:
Rails.application.config.to_prepare do
...
end
https://api.rubyonrails.org/classes/ActiveSupport/Reloader.html#method-c-to_prepare
Doing this inside the Gem's own engine initializer code and the dummy app initializer code allowed the test suite to run. Likewise, there were quite a few places in our main Rails application beyond just Scimitar that suddenly required this workaround, including some examples of just plain old Ruby gems.

There is a programmatical way to detect Zeitwerk::NameError when upgrading to Rails 6?

I am currently migrating an old Rails application to Rails 6.
It seems that some files in the project are not consistent with the classes defined in it.
I don't see this error when running the tests of the application, but after deploy I receive errors like:
Zeitwerk::NameError: expected file /app/my?_/app/lib/multi_io.rb to define constant MultiIo, but didn't
In previous SO questions I could find suggestions to:
Rails 6: Zeitwerk::NameError doesn't load class from module set config.autoloader = :classic
How to ignore a folder in Zeitwerk for Rails 6? ignore a given path
Autoloading and Zeitwerk Mode in Rails 6 tweak config.autoload_path
I am not looking for a workaround so I corrected the file raising the exception.
I would like to find a programmatical way to detect all inconsistent path/class names without running the application in production mode.
So far, three options came to my mind:
A rake task to validate the autoloading (which is not provided by the gem). Does it exists? Would you have a snippet to run from the rails console in development env instead?
A rubocop cop or another static code analyser. There is any?
Force the eager load in the test environment
What it the suggested approach to validate my code without requiring several iterations/deployments?
thank you very much in advance
You can check the autoload compatibility of a project with the zeitwerk::check task:
$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!
It exercises all the autoload paths and will flag issues such as those caused by the change in constant name inference (such as those with acronyms). The task doesn't appear to be widely advertised or documented except in the upgrade guide. But, I actually use it sometimes to just flush out constant naming issues in the project generally (i.e., where the class doesn't match the file).
#rmlockerd Described one of the best tools bin/rails zeitwerk:check for this in his answer here.
The other articles I found informative were:
Understanding Zeitwerk in Rails
9 Tips & Tricks To Help You Enable The Zeitwerk Autoloader In Your
Rails App
Rails doc on autoloading
Rails doc on upgrading from Rails 5.2 to 6.0
The process I took was to run bin/rails zeitwerk:check fix an error, run it again until I saw "All is good". After that worked through errors reported by our spec tests.
One general problem to fix is if a folder is not within the Rails default file structure, Zeitwerk will not be able to find it when autoloading. We had a folder named "services" (app/services) that needed to be added to the autoload path within the application.rb file like:
config.autoload_paths += %W(#{config.root}/app/services/)
Error:
Expected file path/file_name.rb to define constant FileName
Fix:
remove any require or require_relative in other files referencing where the class is defined (Zeitwerk doesn't need them).
Error (class is defined within a module of a sub directory):
NameError: uninitialized constant SubFile::ClassName
Fix:
Detailed in another S/O answer. Whenever method is used within sub folders structure the call as Folder::SubDirectory::Class.method_call
Goodluck!

How to run rails initialization code that relies on all classes being loaded?

I have some crucial application setup code that I want to run once on application spinup that requires iterating through classes of a certain type and calling a gem specific initialize method on them.
According to How can I preload concerns in a rails initializer using Rails 6/Zeitwerk? I should not autoload constants in initializers.
So how can I run code once on application load after classes are loaded
I tried with no luck:
config.after_initialize do
# do stuff
end

Need to initialize a constant before gem initialization ( rails 3 and rails 4)

I am implementing a omniAuth authentication system through a gem and i would need to initialize a constant before the gem gets loaded.
Scenario:
The developer should write that constant in a config file, restart its server and that constant should be initialized before the gem gets loaded.
I tried to put it in a config/initializers/omniauth.rb file but i get a error when loading the server
unitialized constant OmniAuth::Strategies::Xyz::URL.
i am writting it in the gem in this manner:
OmniAuth::Strategies::Xyz::URL= "http://my_account.com"
If you want to set a constant before gems are loaded in a Rails app, you can place it in config/application.rb, just before the Bundler.require statement. However, since no gems have been loaded yet, you might run into trouble setting such a deeply nested constant.
The proper way to do this, is not to rely on constants for configuration. Make a proper configuration object. You can make a Railtie to add a proper configuration option to Rails itself and define the right hooks like to_prepare to start loading your gem's configuration at the right time.

Rails engine constants

I have a Rails engine with the latest rails and ruby.
I have a controller called cms, with a action called update. I use this update action to update different tables. For example I have got a table called setting. This technique works fine in a normal Rails app, but in my Rails Engine it throws this error:
NameError (uninitialized constant Setting):
I've got a model called Setting, why is givin me an error ?
File naming is important for autoloading to work. Naming convention is the same in both apps and engines. In fact, an application is an engine.
So, my_rails_app/app/models/cms/setting.rb is equivalent to my_engine/app/models/cms/setting.rb
If you still have troubles, try accessing constant with explicit namespace Cms::Setting.
You can dynamically get constant from an appropriate namespace by doing
Cms.const_get(table.capitalize)
However, be careful with this approach since a hacker can send you anything and hence access any constant.

Resources