theDrifter theDrifter - 2 months ago 23
Ruby Question

Nested namespaces for controllers in Rails

I refactored my rails app in a way that for every sub resources i create a controller is the according namespace.

api/v1/app/controller/manager.rb
api/v1/app/controller/manager/user.rb
api/v1/app/controller/manager/controller.rb
api/v1/app/controller/admin.rb
api/v1/app/controller/user.rb
api/v1/app/controller/controller.rb


The class definition of the user resource under the manager namespace looks like this

class Api::V1::Manager::UserController < ApplicationController


This controller is reachable through routes.rb:

resources :manager , only: [:show ] do
resources :user, only: [:index], controller: 'manager/user'
end


which generates

/api/v1/manager/:manager_id/user(.:format) api/v1/manager/manager#index {:format=>"json"}


The models are all under

app/models/manager.rb
app/models/user.rb


When i want to access now the
Manager
model inside the
api/v1/app/controller/manager/user.rb
controller or in
api/v1/app/controller/manager.rb
e.g

class Api::V1::ManagerController < ApplicationController
def index
Manager.find(...)
end
end

class Api::V1::Manager::UserController < ApplicationController
def index
Manager.find(...)
end
end


i get these errors

{"error":"uninitialized constant Api::V1::Manager::UserController::Manager"}%
{"error":"uninitialized constant Api::V1::Manager::Manager"}%


The calls are handled by the correct controllers :

Processing by Api::V1::Manager::UserController#index as JSON


The solution is to use the double colon prefix with the call

`::Manager.find(...)`.


I can use all other models
Admin.find(...)
or
Controller.first
normally. Only the
Manager.find(..)
is not working.

Renaming the namespace to
ManagerResource
still produces the same error message.

I would like to be able to group controllers under different namespaces and still access all the models the same way how is that possible?

Update

Created

api/v1/app/controller/api/v1/foo/customer_controller.rb
api/v1/app/controller/api/v1/manager_customer_controller.rb


After starting the server (webrick) all endpoints are working.
Adding
Manager.first
- to any controller- or changing something in a file which uses
Manager...
returns these errors

`uninitialized constant Api::V1::Foo::UserController::Manager`
`uninitialized constant Api::V1::ManagerUserController::Manager`
`uninitialized constant Api::V1::*any_controller*::Manager`


Restarting the server solves this issue.

I am able to use
Controller.first
or any other model in e.g.
api/v1/app/controller/controller.rb
.The the server responds well.

Like @Andrey Deineko pointed out i understand now the module and class names should differ.

What i dont understand is why these errors occur only for a specific model when i substract controllers under a namespace which with a different name than the models?

Update II

I removed all manager related namespaces and controllers. So i am back to the original pre-controller-optimization state.

This error occurs only for the
Manger
model. In the console
Manager.class
shows in any case
Class
.
But in the controller this happens:

module Api
module V1
class Manager < Api::ApiBaseController
def index
puts User.class #=> class
puts Manager.class #=> module
puts ::Manager.class #=> class
puts Controller.class #=> class
...
end
end
end
end


class Api::V1::Manager < Api::ApiBaseController
def index
puts User.class #=> class
puts Manager.class #=> {"error":"uninitialized constant Api::V1::ManagerController::Manager"}
puts ::Manager.class
puts Controller.class
...
end
end


when i change the order so that
::Manager
is first everything works as expected and also the classes then match

class Api::V1::Manager < Api::ApiBaseController
def index
puts User.class #=> class
::puts Manager.class #=> class
puts Manager.class #=> class
puts Controller.class #=> class
...
end
end


The namespace
Api::V1::...
works for every other controller.

Answer

The reason is simple and the answer lies in Ruby's constants resolution mechanism.

Basically it is super bad idea to have a module and class with the same name.

But if you definitely need to have same name for both module and class, be sure to correctly reference each of them.

Meaning, that referencing Manager class with :: is your only solution if you don't want to change the naming.

Rails add some magic to auto/preloading classes in development and production mode, so you could face different issues in different modes.

You may want to read through this official guide on loading constants in Rails.