Name Clash with Top Level constant when autoloading with Rails
Asked Answered
R

1

3

I'm having a class Dog in app/models/dog.rb and another class Services::My::Deeply::Nested::Dog in app/services/my/deeply/nested/dog.rb.

Now in Services::My (app/services/my.rb), I have a reference to one of the following (no need to distinguish, since the behaviour is exaclty the same in all situations):

  • Deeply::Nested::Dog
  • Services::My::Deeply::Nested::Dog
  • ::Services::My::Deeply::Nested::Dog

No matter which of the above I choose, I always get the following error message:

services/my.rb: warning: toplevel constant Dog referenced by Services::My::Deeply::Nested::Dog

Also, my deeply nested Dog never even gets seen by the interpreter (I found out by using puts and syntax errors, nothing happens).

Possible workarounds (which I all don't like) are:

  • Rename one of the Dog classes
  • Run with RAILS_ENV=production in order to disable autoloading and use eager loading instead
  • require_dependency that points to the deeply nested dog

As a computer scientist, I find none of the above workarounds satisfactory. I would like to understand why my deeply nested Dog is not found without workaround and find out what the state-of-the-art Ruby / Rails solution would be for this problem.

Thanks a lot in advance.

Roughhouse answered 2/12, 2016 at 9:21 Comment(4)
If you temporarily remove/rename app/models/dog.rb, is your app/services/my/deeply/nested/dog.rb autoloaded when you invoke Dog?Moneychanger
Just curious, where did you get the idea about app/services? https://mcmap.net/q/751890/-way-to-load-folder-as-module-constant-in-rails-app-directory had the same confusion just yesterday.Moneychanger
Unfortunately I cannot rename or remove ::Dog - hell would break loose since there are thousands of calls to it elsewhere in the code (it's not really named Dog, I simplified and anonymized the code of a large Rails App before posting it here).Roughhouse
Concerning the app/services, someone I worked for used it as a convention.Roughhouse
M
1

app/services/my/deeply/nested/dog.rb should define My::Deeply::Nested::Dog and not Services::My::Deeply::Nested::Dog, for the same reason that app/models/dog.rb doesn't define Models::Dog but just Dog.

With :

#app/services/my/deeply/nested/dog.rb                                                       
module My
  module Deeply
    module Nested
      class Dog
        puts "I'm in %s" % __FILE__
        puts Module.nesting.inspect
      end
    end
  end
end

and

# app/models/dog.rb
class Dog
  puts "I'm in %s" % __FILE__
  puts Module.nesting.inspect
end

and

# app/services/my.rb
module My
  puts "I'm in %s" % __FILE__
  puts Module.nesting.inspect
  Dog
  My::Deeply::Nested::Dog
end

You get :

Loading development environment (Rails 5.0.0.1)
2.3.1 :001 > My
I'm in /home/ricou/www/new_rails_5_app/app/services/my.rb
[My]
I'm in /home/ricou/www/new_rails_5_app/app/models/dog.rb
[Dog]
I'm in /home/ricou/www/new_rails_5_app/app/services/my/deeply/nested/dog.rb
[My::Deeply::Nested::Dog, My::Deeply::Nested, My::Deeply, My]
 => My 
Moneychanger answered 2/12, 2016 at 10:57 Comment(5)
Thank you for your reply. Unfortunately, it doesn't solve the problem: 1. Doing any changes in the nested dog file has no effect, since the file never gets read. 2. Removing Services:: from the path to the nested dog in my.rb causes a NameError.Roughhouse
Here's a list of what I tried in my.rb and what happened: Deeply::Nested::Dog => warning: toplevel constant Dog referenced by Services::My::Deeply::Nested::Dog, My::Deeply::Nested::Dog => NameError: uninitialized constant Services::My::My, ::My::Deeply::Nested::Dog => NameError: uninitialized constant My, ::Services::My::Deeply::Nested::Dog => app/services/my.rb: warning: toplevel constant Dog referenced by Services::My::Deeply::Nested::DogRoughhouse
Either remove the module Services in every rb file inside app/services or move app/services to app/services/services. The given my.rb example works with Deeply::Nested::Dog, My::Deeply::Nested::Dog and ::My::Deeply::Nested::DogMoneychanger
Another possibility : config.autoload_paths << Rails.root.join('app') in your config/application.rb, and leaving Services as namespace.Moneychanger
Thanks a lot. I think your reply is correct, but it cannot work in our environment because of our "very special" configuration which we have to stick to for historical reasons (you know the kind of problem I guess ;-) ) So if anyone else comes across this, if there is no more way out, require_dependency 'path/to/missed/file' is the easiest workaround. We tried until today and we think we have no other alternative. Thank you for your effort @EricDuminilRoughhouse

© 2022 - 2024 — McMap. All rights reserved.