8

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.

theDrifter
  • 1,631
  • 6
  • 24
  • 40
  • `ManANger` word has a mistake. Is it ok? – Maxim Sep 10 '16 at 19:57
  • aah yes just a typo ;) – theDrifter Sep 10 '16 at 20:00
  • Maybe the Manager module id conflicting with the Manager modelo, try changing the name of Manager module – arieljuod Sep 10 '16 at 20:12
  • @arieljuod yes changing the name from `app/model/manager.rb` into `app/model/manager_foo` lets me work on with `ManagerFoo.find(...) but i not sure i understand what that means – theDrifter Sep 10 '16 at 20:28
  • i also tried to put the manager model under `app/model/mamanger/manager.rb` without success – theDrifter Sep 10 '16 at 20:28
  • @arieljuod i do not use a manager module, i have the manager folder within the controller directory for the namespace – theDrifter Sep 10 '16 at 20:34
  • 1
    The name space works like a module, the problem seems to be that Manager is ambiguous, you need to tell Rails what Manager you want, i'm not sure if there's a namespace wrapping all models, I would go with changing to Api::V1::ManagerApi:: or something else – arieljuod Sep 10 '16 at 20:47
  • Yes thats it! you are right, thats the problem, i renamed the namspace and the routes and now the controller actions work again!! Thanks – theDrifter Sep 12 '16 at 10:42
  • Ah no. It only worked because i had still one ::Manager.find() as soon as i removed the double colons i got the same error message again... – theDrifter Sep 12 '16 at 11:20
  • "The solution is to use the double colon prefix with the call `::Manager.find(...)`." This is correct for this particular case. Are you asking why you must do this to access the `Manager` model? Or are you still having trouble accessing it via `::Manager.find(...)`? – Chris Peters Sep 13 '16 at 19:22
  • @ChrisPeters `::Manager.find` is working but i would like to have a way where i can use all Models directly. I tried different names for the namespace like `ManagerResources` `Managerresources` i imagined writing it in one word prevents ruby from misleading the class and model (namespace) but i get the same error, also renaming it to `resources` did get me the error. My question is how am i able to use all models the same way under namespaced controllers – theDrifter Sep 14 '16 at 10:36

3 Answers3

1

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.

Andrey Deineko
  • 51,333
  • 10
  • 112
  • 145
  • Ok great thanks for the article super interesting ! But i am open for renaming but changing the namespace for manager sub resources to `api/v1/controller/resources/user.rb` gives me the same error in – theDrifter Sep 14 '16 at 11:58
  • 1
    @theDrifter you can easily check it in console: run `Manager.class` - until it returns `Module`, not `Class` - you're still having some module named `Manager`. – Andrey Deineko Sep 14 '16 at 12:00
  • @theDrifter keep me posted if you encounter anything new here – Andrey Deineko Sep 14 '16 at 12:31
  • Hey, so `Manager.class` turns out every time to be a `class`. I tried creating another controller called `manager_user_controller.rb` in the same dir than `manager.rb` with the same error: When i start/restart the server the controller is working, but wadding for example a `Manager.first` in `manager_*_controller.rb the autoload fails and `uninitialized constant Api::V1::Managerresources::UserController::Manager` or `uninitialized constant Api::V1::ManagerUserController::Manager` errors pop up. This behavior also shows up when i rename the name space to `Foo` – theDrifter Sep 15 '16 at 09:23
  • I am able to use `Controller.first` or any other model in e.g. `app/controller/api/v1/controller.rb`.The the server responds well withour restarting, but as soon as i call `Manager.first also in `controller#index` it fails, with the namespace still being `Foo` (`uninitialized constant Api::V1::ControllerController::Manager`) The console shows then `Manager.class` => `class`. – theDrifter Sep 15 '16 at 09:23
  • @theDrifter what if you set `config.reload_classes_only_on_change = false`? – Andrey Deineko Sep 15 '16 at 14:25
  • ok i think i foudn something interesting i ll update the question ..again ;) Thanks for sticking here !!! – theDrifter Sep 16 '16 at 09:52
  • 1
    @theDrifter this is not a trivial stuff you're playing with ;) Check out [this thread](http://stackoverflow.com/questions/34338131/why-doesnt-ruby-find-classes-in-a-higher-scope-when-module-is-specified-using) - Sergio's answer is great. – Andrey Deineko Sep 16 '16 at 10:12
  • oh great that like is super !! – theDrifter Sep 16 '16 at 10:21
  • @theDrifter yeap, this stuff is really good to understand. But I think in the end you'll stick to `::Manager` - just to make sure Rails' autoloading magic don't break your app ;) – Andrey Deineko Sep 16 '16 at 10:28
  • ok i think i found the "bug" i used the rails generators to create the subresources in the first place, so there were some helper files also generated. They defined something `module Api::ManagerHelper` removing them lets me access my Models like normal :) ufff – theDrifter Sep 19 '16 at 16:47
  • @theDrifter glad you've finally found it! – Andrey Deineko Sep 19 '16 at 16:51
0

you have mistake in controller code:

class Api::V1::ManagerController << ApplicationController

must be:

class Api::V1::ManagerController < ApplicationController

not <<, should be <

Oleh Sobchuk
  • 3,612
  • 2
  • 25
  • 41
0
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

Change all this to:

app/controller/api/v1.......

You are doing wrong namespacing.

mrzasa
  • 22,895
  • 11
  • 56
  • 94