[Question] Is it possible to add an un-collapsed directory to an already collapsed one?
adrianthedev opened this issue ยท 4 comments
๐ Hey there,
I'm Adrian, author of https://github.com/avo-hq/avo.
TL;DR;
How can we have the following namespacing with this directory structure (in a Rails app)?
app/avo/actions/make_admin.rb
->class Avo::Actions::MakeAdmin
app/avo/resources/user_resource.rb
->class Avo::Resources::UserResource
app/
โโ avo/
โ โโ actions/
โ โ โโ make_admin.rb
โ โโ resources/
โ โ โโ user_resource.rb
โ โโ ...other Avo directories/
โโ controllers/
โโ ... other rails directories/
Long Story
We're looking into refactoring some code, and I'm curious if this is possible with Zeitwerk.
Current setup
We add a few primitives to the Rails app to configure the admin panel. Primitives like Actions, Filters, Resources, Dashboards, Cards, and more.
We add those classes inside the app/avo
directory.
app/
โโ avo/
โ โโ actions/
โ โ โโ make_admin.rb
โ โโ resources/
โ โ โโ user_resource.rb
โ โโ ...other Avo directories/
โโ controllers/
โโ ... other rails directories/
Under the current set up we added those paths to Rails.autoloaders.main
like so:
Rails.autoloaders.main.push_dir(Rails.root.join('app', 'avo', 'resources').to_s)
Rails.autoloaders.main.push_dir(Rails.root.join('app', 'avo', 'actions').to_s)
# ... more directories
That gave us the ability to add un-namespaced classes to the app.
# app/avo/resources/user_resource.rb
class UserResource < Avo::BaseResource
# ... class contents
end
The learnings
We now figured out that it's difficult to organize these un-namespaced classes. If the user doesn't add Action
or Filter
, or Resource
suffix to the class name, it could get more challenging to maintain.
So we figured we'd leave Rails and the default auto-loading scheme to take over and have the classes namespace like so:
- actions ->
Avo::Actions::MakeAdmin
- resources ->
Avo::Resources::User
- etc.
This way, it's implicit what the class/object does.
The problem
We set out to refactor this piece of functionality. We went and removed our own push_dir
operations with the idea that the namespacing would happen automatically.
We soon figured out that all the subdirectories of app
are collapsed (please correct us if we're wrong. We couldn't find the piece of code that adds the collapsing functionality).
So the namespacing now for our files do not contain the top-level Avo
namespace.
- actions ->
Actions::MakeAdmin
- resources ->
Resources::User
The main problem with this namespacing is that we're afraid of conflicts and clashes with other gems or other classes from the parent app
The question
Is there a way to "un-collapse" a directory?
Ideally we'd do something like Rails.autoloaders.main.uncollapse(Rails.root.join('app', 'avo', 'resources').to_s)
. This might be a dumb way of approaching things, so I'm open to any recommendations from you.
I need to mention that we already have an Avo
namespace declared in the gem as the engine namespace.
So, yeah. We're open to suggestions.
Thank you!
Hi Adrian!
Rails automatically adds subdirectories of app
to the autoload paths, with some exceptions like the ones for views or assets. So, what you are seeing is not really collapsing, but that app/avo
is in the autoload paths just like app/models
is. Therefore, app/avo
represents Object
.
In your previous setup, since app/avo/actions
was in the autoload paths too, for example, we had both app/avo
and app/avo/actions
in the autoload paths. This is a setup with nested root directories, and thus app/avo/actions
acted as Object
even if it is a subtree of another root directory.
Now, in the new setup you no longer have nested root directories. What you really need is to say that app/avo
represents Avo
. Zeitwerk has support for that, but Rails does not have API for configuring this. However, reaching the loaders directly does belong to the public interface precisely to be able to customize them in ways that go beyond what the Rails configuration API supports.
The solution would be to have an initializer in the engine like this (untested):
# config/initializers/zeitwerk.rb
ActiveSupport::Dependencies.autoload_paths.delete("#{Rails.root}/app/avo")
Rails.autoloaders.main.push_dir("#{Rails.root}/app/avo", namespace: Avo)
The first line won't be needed in Rails 7.1, but it is necessary now, and won't hurt in Rails 7.1 either if you need to support multiple Rails versions. The argument has to be a string.
As you see, for the second line to be executable, the Avo
module has to be already in memory.
Could you give that a try?
Thank you for the quick response @fxn.
That does make sense (removing the path and adding it under the proper namespace).
I'll give it a try and get back to you.
Reporting back with the initial tests.
It works ๐
Thank you for your quick response and explanation ๐
This gem and your support are exemplary, and I try to keep up the standard with my library.
I'll close this for now and reopen it if necessary.
Awesome, appreciate your words man! โค๏ธ