Rails Autoloading Hell

Suppose you want to have two controllers on the one namespaced model, call it Foo::Bar . Suppose one is an admin controller and you want to namespace it as Admin::Foo::BarsController. Any reason that shouldn’t work? Well, it sure doesn’t work, at least in Rails 3.2.x!

The problem I ran into is how the Rails determines the corresponding model for a controller.

  1. When Rails loads the controller, it will determine the model. I don’t see any way to disable that.
  2. If the controller is named Admin::Foo::BarsController, then the following are looked up, in this order:
    1. Admin::Foo::Bar
    2. Admin::Bar
    3. ::Bar
  3. The 3rd result is found and that will find the file at /app/models/foo/bar.rb. But, the dependency loader is expecting to find ::Bar declared in this file, and then Foo::Bar is found. The stack trace is below.

The fix I’m using is to change the order of the modules for the controller. So I’m using the Foo::Admin::BarsController

Is that the best fix?

Here’s an article that somewhat partially explains the issue:

Rails autoloading — how it works, and when it doesn’t

Stack Dump of two controllers on same one namespaced model

/.rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:503:in `load_missing_constant': Expected /app/models/foo/bar.rb to define Bar (LoadError)
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:192:in `block in const_missing'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:190:in `each'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:190:in `const_missing'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:514:in `load_missing_constant'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:192:in `block in const_missing'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:190:in `each'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:190:in `const_missing'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:514:in `load_missing_constant'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:192:in `block in const_missing'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:190:in `each'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/dependencies.rb:190:in `const_missing'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/inflector/methods.rb:230:in `block in constantize'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/inflector/methods.rb:229:in `each'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/inflector/methods.rb:229:in `constantize'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/inflector/methods.rb:260:in `safe_constantize'
	from .rvm/gems/ruby-2.1.5/gems/activesupport-3.2.16/lib/active_support/core_ext/string/inflections.rb:66:in `safe_constantize'
	from .rvm/gems/ruby-2.1.5/gems/actionpack-3.2.16/lib/action_controller/metal/params_wrapper.rb:152:in `_default_wrap_model'
	from .rvm/gems/ruby-2.1.5/gems/actionpack-3.2.16/lib/action_controller/metal/params_wrapper.rb:169:in `_set_wrapper_defaults'
	from .rvm/gems/ruby-2.1.5/gems/actionpack-3.2.16/lib/action_controller/metal/params_wrapper.rb:133:in `inherited'
	from .rvm/gems/ruby-2.1.5/gems/actionpack-3.2.16/lib/abstract_controller/railties/routes_helpers.rb:7:in `block (2 levels) in with'
	from .rvm/gems/ruby-2.1.5/gems/actionpack-3.2.16/lib/action_controller/railties/paths.rb:7:in `block (2 levels) in with'
	from /app/controllers/admin/foo/bars_controller.rb:61:in `<module:Foo>'
	from /app/controllers/admin/foo/bars_controller.rb:12:in `<module:Admin>'
	from /app/controllers/admin/foo/bars_controller.rb:11:in `<top (required)>' 

@justin - I use namespaced models and controllers all the time. But I don’t think I ever use a pattern like:

Foo::Bar::Baz
Bar::Baz
Baz

Instead I organize like:
Baz::Bar::Foo
Baz::Bar
Baz

and always use ‘full pathnames’ when referencing a class. if you can structure your app to fit this organization, I think you’ll find it works OK.

Yeah, just reference the model with the full path?

Admin::Foo::BarsController

def show
  @bar = ::Foo::Bar.find(params[:id])
end

Yes - that should work !

Hi @andyl and @zts, the problem I had was specific to not being able to specify the model for a nested resource. Basically, Rails has to infer the model from the controller name.

You could just explicitly require the models (like in a normal Ruby app ¯\_(ツ)_/¯). Of course with a standard require you won’t get the benefit of Rails’ automatic reloading when the file changes. I found Rails’ require_dependency solves this problem pretty well.

@ridiculous That works in many cases. My issue was the auto-inference of the model name from the controller.