Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 299
theDrifter Avatar asked Sep 10 '16 19:09

theDrifter


1 Answers

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.

like image 179
Andrey Deineko Avatar answered Nov 03 '22 01:11

Andrey Deineko