Mind Dump, Tech And Life Blog
written by Ivan Alenko
published under license Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)copy! share!
posted at 13. Jan '16

Rails chooses wrong class from wrong namespace

TL;DR Load order problem. Always use namespaces for classes that have the same base name, i.e. don’t use Category and Admin::Category (Admin is also a class), but Front::Category and Admin::Category. Or explicitly set load order if you can. Or change Admin from class to a module.

Did you encountered something like this?

/home/hrdina/lightway/test/controllers/merchant/adverts/images_controller_test.rb:3: warning: toplevel constant Adverts referenced by Merchant::Adverts
/home/hrdina/lightway/test/controllers/merchant/adverts/images_controller_test.rb:5: warning: toplevel constant Adverts referenced by Merchant::Adverts

and tests are failing, right? You say that variables aren’t set. Ok, (after a few hours with messing around) some debugging:

test "gets index" do
  get :index, advert_id: @merchant_advert.id
  assert_response :success

  puts assigns.inspect  # <-- OUR DEBUG CODE
  assert_not_nil assigns(:merchant_advert_images)
end

It prints instance variables

...ode=:sk, @errors=#<ActiveModel::Errors:0x007fdb412f5c28 @base=#<Support::Language:0x007fdb414161c0 ...>, @messages={:code=>[]}>>,
"advert"=>#<Advert id: 3, merchant_id: 3, category_id: 6, ....>,
"advert_images"=>#<ActiveRecord::Association...

Hmm, only advert and advert_images are set, not merchant_advert and merchant_advert_images as they should be - it calls other controller, AdvertsController! Here is some explanation which I copied somewhere on stackoverflow.org:

my run in irb:

irb(main):001:0> Document = 'hello'
=> "hello"
irb(main):002:0> 
irb(main):003:0* class Question
irb(main):004:1> end
=> nil
irb(main):005:0> 
irb(main):006:0* class Animal
irb(main):007:1>   puts Question::Document
irb(main):008:1> end
(irb):7: warning: toplevel constant Document referenced by Question::Document
hello
=> nil
irb(main):009:0> 
irb(main):010:0* class Question
irb(main):011:1>   class Document
irb(main):012:2>   end
irb(main):013:1> end
=> nil

and the next explanation from Andrew White on https://groups.google.com/forum/#!topic/rubyonrails-core/urXofuENLss:

“Is Admin a class or a module? If it’s a class it’s due to the fact that const_missing searches for constants in ancestors which includes Object when Admin is a class. Since SomeController is defined already in Object it will return that constant along with the warning message. If Admin is a module then the ancestor list is just itself.

A couple of ways to workaround the problem, either make Admin a module if possible or nest the public-facing controllers under a different namespace, e.g. Admin::SomeController and Front::SomeController.”

Conclusion

I think the best way to handle this, is to enforce modules by design. It will require to change some application logic and routes, but it is worth of it.

Add Comment