Here's a brand new Rails 5.1.4 app, with a model and a couple of routes and controllers.
A namespaced controller is referencing a top level model:
class AdminArea::WelcomeController < ApplicationController
def index
@user = User.new(name: 'Sergio')
end
end
So far so good. You can check out the master, navigate to http://localhost:3000/admin_area/welcome
and see it work.
BUT if we were to add an empty directory app/presenters/admin_area/user/
*, then things get weird. All of a sudden, User
in that controller is not my model, but a non-existing module!
NoMethodError (undefined method `new' for AdminArea::User:Module):
app/controllers/admin_area/welcome_controller.rb:3:in `index'
Naturally, this module doesn't have any [non-built-in] methods and can't be pinned to a source file on disk.
Question: why adding an empty directory causes rails to mysteriously conjure a module out of thin air instead of correctly resolving name User
to my model?
* actually, if you check out that branch as-is, you'll get a different error.
NameError (uninitialized constant AdminArea::WelcomeController::User)
because git wouldn't let me commit an empty directory, so I added a .keep
file in there. But as soon as you delete that file, you get the behaviour described above.
This a consequence of ruby constant lookup and how Rails resolves autoloading.
The constant User
in the controller is so called "relative reference", which means it should be resolved relative to the namespace within which it occurs.
For this constant, there are three possible variants where the constant can be defined:
AdminArea::WelcomeController::User
AdminArea::User
User
Rails autoloading maps these constants into file names and iterates over the autoload_path
s in order to find the file where the constant is defined. E.g.:
app/assets/admin_area/welcome_controller/user.rb
app/assets/admin_area/welcome_controller/user
app/channels/admin_area/welcome_controller/user.rb
...
app/assets/admin_area/user.rb
app/assets/admin_area/user
...
app/assets/user.rb
...
app/models/user.rb #=> here it is!
When you add the admin_area/user
folder into the presenters directory, you are effectively defining such a constant. Modules in Rails are automagically created, so that you don't actually need to create files where you define these modules that only work as namespaces.
When you added the folder, the folder appeared in the Rails lookup:
...
app/assets/admin_area/user.rb
app/assets/admin_area/user
...
app/presenters/admin_area/user #=> Here Rails finds the folder
and Rails resolves the User
to reference to that module.
However this is quite easy to fix, If you want the User
constant that is used within AdminArea
namespace to reference a top-level constant (and not the AdminArea::User
module), you should change the "relative reference" into an absolute reference by preceding the constant with ::
.
@user = ::User.new(name: 'Sergio')
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