Rails 6 + Zeitwerk, loading files without class - ruby-on-rails

We are upgrading to Rails 6, and have done most of the work to make our application work with Zeitwerk.
We do have two files, which are query objects, existing under app/queries and we don't define a class for them. They exists with methods only.
However, Zeitwerk expects the file to define a constant, but we don't deem it necessary.
Is there any way to tell Zeitwerk that we want to load these files as is?

You can tell the main autoloader to ignore them:
# config/initializers/zeitwerk.rb
Rails.autoloaders.main.ignore(
Rails.root.join("app/queries/foo.rb"),
Rails.root.join("app/queries/bar.rb")
)
And then the application is responsible for loading them using require or whatever.
If everything under app/queries should be ignored, you can ignore the directory itself:
# config/initializers/zeitwerk.rb
Rails.autoloaders.main.ignore(Rails.root.join("app/queries"))

Related

Namespacing service objects in Rails 6 with Zeitwerk autoloader

Rails 6 switched to Zeitwerk as the default autoloader. Zeitwerk will load all files in the /app folder, eliminating the need for namespacing. That means, a TestService service object in app/services/demo/test_service.rb can now be directly called e.g. TestService.new().call.
However, namespacing has been helpful to organize objects in more complex rails apps, e.g. API::UsersController, or for services we use Registration::CreateAccount, Registration::AddDemoData etc.
One solution suggested by the rails guide is to remove the path from the autoloader path in application.rb, e.g. config.autoload_paths -= Dir["#{config.root}/app/services/demo/"]. However, that feels like a monkey patch for shoehorning an old way or organizing objects into the new rails way.
What is the correct way of namespacing objects or a rails 6 way of organizing it without just forcing rails into the old way?
It is not true to say that Zeitwerk eliminates 'the need for namespacing'. Zeitwerk does indeed autoload all the subdirectories of app (except assets, javascripts, and views). Any directories under app are loaded into the 'root' namespace. But, Zeitwerk also 'autovivifies' modules for any directories under those roots. So:
/models/foo.rb => Foo
/services/bar.rb => Bar
/services/registration/add_demo_data.rb => Registration::AddDemoData
If you are already used to loading constants from 'non-standard' directories (by adding to config.autoload_paths), there's usually not much change. There are a couple of cases that do require a bit of tweaking, though. The first is where you are migrating a project that just adds app itself to the autoload path. In classic (pre-Rails 6), this allows you to use app/api/base.rb to contain API::Base, whereas in Zeitwerk it would expect it to contain only Base. That's the case you mention above where the recommendation is to exclude that directory from the autoload path. Another alternative would be to simply add a wrapper directory like app/api/api/base.rb.
The second issue to note is how Zeitwerk infers constants from file names. From the Rails migration guide:
classic mode infers file names from missing constant names
(underscore), whereas zeitwerk mode infers constant names from file
names (camelize). These helpers are not always inverse of each other,
in particular if acronyms are involved. For instance, "FOO".underscore
is "foo", but "foo".camelize is "Foo", not "FOO".
So, /api/api/base.rb actually equates to Api::Base in Zeitwerk, not API::Base.
Zeitwerk includes a rake task to verify autoloading in a project:
% bin/rails zeitwerk:check
Hold on, I am eager loading the application.
expected file app/api/base.rb to define constant Base
EDIT:
As clarified in comments, you actually don't need to add anything to autoload_paths. It's default behaviour for Zeitwerk in Rails when your place your code under some subdirectory in app.
Original answer:
I'm posting separate answer, but actually accepted answer has all the good information. Since my comment was bigger than allowed, I chose to add separate answer for those who are struggling with similar issue.
We have created "components" under app where we separate domain specific namespaces/packages. They co-exists with some "non-component" Rails parts, that are hard to move under components. With classic autoloader, we have added #{config.root}/app in our autoload_paths.
This setup fails for Zeitwerk and removing "#{config.root}/app" from autoload_paths didn't help. rmlockerd suggestion to move app/api/ under /app/api/api moved me thinking in creating separate 'app/components' and moving all components under this directory and add this path to autoload_paths. Zeitwerk likes this.

Mogoid 7.0.4 returns Unable to autoload constant (Mongoid ~6 worked fine)

When I try to upgrade to Mongoid ~7, it throws this error:
Unable to autoload constant User::AuditSession, expected ./testapp/app/models/user/audit_session.rb to define it
I indeed have this file, but it defines AuditSession, not User::AuditSession. I put all the user related models into this directory which worked in Mongoid ~6, but it seems to no longer work in Mongoid ~7.
Is there any workaround I can do to get the old behaviour? - My goal is to avoid moving all my model files or editing all my class names.
Per the Rails conventions, if the model is defined in user/audit_session.rb, the class name should be User::AuditSession.
A workaround is to preload the class so that it is loaded by the time it is needed. Depending on where the class is used from, this could be as simple as placing
require 'user/audit_session'
in the file that uses AuditSession.
Alternatively you could define a top-level audit_session.rb which includes the other file (i.e. just has the above include in it).

Ruby on Rails delay in updating

I'm learning rails and I've come across a little quirk that I can't seem to find the answer to anywhere:
Since I'm learning rails, I'll make a few tweaks to the code while the localhost is running (rails s) and then just refresh the browser to see if the change I wanted to make was accurate. This works for changes to the views, css, html, routing, etc.
But now I'm making changes to a controller file that is calling another ruby class that I wrote and when I make changes to the ruby class, they don't show up right away. The way I know this is that I use a variety of printf functions in the Ruby class to show the current state of things and if I add one and re-run, it won't show unless I shut the server down and restart it.
Any thoughts? Is this a known issue?
You must autoload the folder which contains your custom files:
# in config/application.rb:
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/extras)
There you write the folder which you want to be autoloaded.
WARNING: the naming is very important: files in there must be named as the class/modules they define (like models, controllers, etc):
foo.rb must define Foo costant
foo/bar.rb must define Foo:Bar costant
and you cannot autoload files which do not have this naming convention. The reason is linked to the autoload working: when in your code call f.e. the Foo constant, and the constant is missing, Rails tries to see if in its autoload paths there is a file that follows this naming convention, and if there is it loads it.

Converting Rails 2 plugin to Rails 3 gem

So there's this great plugin I've gotten used to using in my Rails 2 projects called Bootstrapper. It essentially duplicates the functionality of the seeds.rb file, but I like it because it lets you break up your bootstrap process into concise chunks.
Anyway, I've gone so far as to fork the project and attempt to turn it into a Rails 3 gem. I've been able to get the gem to initialize and register the rake tasks and generators OK. However, I'm running into a problem with the Bootstrapper class itself. It won't load in the Rails project unless it's in a module.
That is, if I place the Bootstrapper class in a file by itself and require that file in my Railtie, then in my Rails app, it can't find the Bootstrapper class. If I put the class in a module and call Bootstrapper::Bootstrapper everything is peachy.
The code that actually requires the Bootstrapper class is this:
ActiveSupport.on_load :active_record do
require 'bootstrapper/bootstrapper'
end
The source is available here:
http://github.com/jrmehle/bootstrapper/tree/make_gem
Autoload paths actually has an annoying feature of following filesystem paths. For example in your lib or extras (depending on what you autoload) you might have the following file structure:
lib/bootstrapper/bootstrapper.rb
# in this case, Bootstrapper::Bootstrapper.class = Class in rails c
# ie: you don't get a NameError exception
More specifically,
lib/bootstrappers/bootstrapper.rb
# Bootstrapper::Bootstrapper => NameError
# Bootstrappers::Bootstrapper => works
If you really want the other way, you can move everything into your lib/bootstrapper.rb source file but meh, I don't like doing that, that's not how gems are organized. In rails3, you'll find the autoloading pretty nice once you use modules everywhere (which can be painful).
Rails3 uses /extras instead of /lib but it's not required, it's just the default (commented out) from rails new. To switch, you just autoload extras instead of lib.

How can you track the full sequence & order of 'require's in a Ruby app as a tree?

How can you display the hierarchy of 'require's that take place in a Ruby app?
Some files require files which require additional files.
However, by running an application in debug mode you only trigger a subset of required files - only the ones that are used by whatever subset of functionality your application is using at any given point in time.
How could you display a comprehensive hierarchy of all of the requires in an application as a tree?
The issue is that in development mode, all files are loaded with load rather than require so that they can be reloaded on each request. In production they are loaded only once. With the exception of some of the framework classes, most files are still only loaded when they are first used. This happens because ActiveSupport overrides const_missing to automatically attempt to load unknown constants from files with the appropriate naming scheme (ConstantName.to_s.underscore would give require 'constant_name'). This of course really muddles up the 'require' hierarchy.
For a trivial case, you can modify the following to meet some of your needs (also check out dependencies in active_support)
$require_level = []
alias :orig_require :require
def require(file)
puts "#{$require_level.join}#{file}"
$require_level << "-"
r = orig_require(file)
$require_level.pop
r
end
require 'foo'
require 'baz'
ben#legba-2:~ $ ruby check_requires.rb
foo
-bar
baz
Good luck
EDIT: Explanation
What that does is create a global array to store the nesting level of requires. The first puts outputs the required file. Then a dash is added to the nesting level. The file is then actually required. If the loaded file calls require, then this whole process starts again, except that nesting level is 1 deep so "-#{file}" is puts-ed. This process repeats except as the nesting level grows, so do the dashes. After a file and all of its dependencies are loaded, require takes off the dash that it added so that nesting level is in the same state it was when the require started. This keeps the tree structure accurate.
const_missing is similar to method_missing. Basically, just like when you call AnObject.some_unknown_method ruby will call AnObject.method_missing(:some_unknown_method) before raising a NoMethodError, using SomeUnknownConstant triggers a const_missing(:SomeUnknownConstant) before raising a NameError. Rails defines const_missing such that it will search certain specified paths for files that might define the missing constant. It uses a naming convention to facilitate this, e.g. SomeUnknownConstant is expected to be in some_unknown_constant.rb
There is a method to much of this rails madness.

Resources