42

I have four models (Document, Question, Question::Document, and Answer). In my Answer model I have

validates :text,
  presence: { :unless => Proc.new{ |a| a.question.is_a? Question::Document } }

This gives me the the warning

warning: toplevel constant Document referenced by Question::Document

How do I prevent this warning from happening (without renaming my classes)?

Kyle Decot
  • 20,715
  • 39
  • 142
  • 263

7 Answers7

42

Your folder/file structure should look as follows:

app/
  models/
    question/
      document.rb
    answer.rb
    document.rb
    question.rb

And then rails will automatically find the correct models (it will translate the model name to a filename, and namespaces are translated to folders).

Make sure that inside your question/document.rb the class definition looks as one of the following alternatives:

class Question::Document
end

or

class Question
  class Document
  end
end

If you write just class Document you are redefining the toplevel constant Document.

Note that if the global Document is defined first, this will also trigger this error. This depends on the code path, so the best way to resolve that, is to add a require_dependency where needed. See here and here for more background.

E.g. something like

require_dependency 'question/document' 

class Answer < ActiveRecord::Base

end  

If you put the file in a different place, where rails cannot automatically find it, you will have to explicitly require it, so rails knows Question::Document exists.

If for instance, you define Question::Document inside the Question model, which is a reasonable place, you will have to explicitly state the dependency to the Question model in your Answer model.

So, in that case, in your answer.rb you will write

require_dependency 'question'

class Answer < ActiveRecord::Base
  # ..
end

While plain require works, it is preferred to use require_dependency instead as it will work with auto-loading, which means: behaves as expected during development.

nathanvda
  • 49,707
  • 13
  • 117
  • 139
  • 2
    As of the most recent version of rails, this does not actually work if there's anything that loads the top-level Document first. If that happens, Rails will simply throw an error and refuse to look for the correct file if you try to access Question::Document after it. – GlyphGryph Jul 15 '15 at 20:22
  • 1
    Which version of rails did you test with? That would be really weird, and imho a bug. I want to verify that. – nathanvda Jul 15 '15 at 20:52
  • 1
    I actually just checked at the Rails github repo and it looks like it's a known issue, but it was closed as its just apparently the way Rails works with namespaces due to using const_missing. Don't know the details, but it actually works that way for every Rails ever apparently. Seems like the only solution is to manually require_dependency the files in the correct order whenever they might be called. Maybe it's even a Ruby thing? https://github.com/rails/rails/issues/6931 Is one of the issues bringing it up. – GlyphGryph Jul 15 '15 at 21:09
  • Nice response time to a feedback to a question you answered two years ago, though! – GlyphGryph Jul 15 '15 at 21:14
  • 1
    Why do we need to `require 'question'` in `answer.rb` – hiveer Aug 11 '15 at 02:24
  • I am not sure what is unclear, because I would now just repeat the line from my answer. Let me try: if the `Question::Document` class is defined inside the `Question` class, meaning, inside the `question.rb` file, rails has no way of finding that file (and thus definition), since it will go looking for a `question/document.rb` file, so you will have to explicitly require the file. Is that clearer? – nathanvda Aug 11 '15 at 13:51
  • Updated my answer to prefer `require_dependency` over `require` and actually best to always add the explicit `require_depency` where needed, as suggested in the documentation. – nathanvda Jul 08 '16 at 12:47
  • best answer. should be green – Tim Kretschmer Dec 26 '16 at 10:59
  • I'm facing the same problem right now and I was wondering if you could also just put `require_dependency 'question/document'` at the top of `question.rb` file? Because when you reference `Question::Answer` later in the code Rails would first load `Question` from `question.rb` which would also load `Question::Answer` because of `require_dependency`. – schneikai Nov 14 '18 at 17:20
21

In Rails, you are not supposed to use "require" as it messes up autoloading.

One solution to this is to append a require_dependency to the end of the file that defines the outer constant.

app/models/question.rb

class Question
  # ...
end

require_dependency 'question/document'

app/models/question/document.rb

class Question
  class Document
    # ...
  end
end

This forces the Question::Document class to be loaded after the Question constant is found. Normally, if Rails already knows about the top-level Document constant, it won't attempt to load Question::Document if it's not already known.

References:

Steve
  • 6,618
  • 3
  • 44
  • 42
5

You need to have Question::Document defined before referencing offending Document reference. Otherwise Ruby will start traversing namespaces up to find Document. Your answer.rb should have

require 'question/document'

on top of it, assuming that's the path where Question::Document is defined.

Tero Tilus
  • 571
  • 1
  • 3
  • 11
1

You might be seeing the warning like this

/path/to/app/models/answer.rb:4: warning: toplevel constant Document referenced by Question::Document

Just require the class which was referenced , on the top file which is throwing this warning.

In your case the below line will go in app/model/answer.rb

require Rails.root.join('app/models/question/document.rb')

And, after restarting the rails server you won't see such ugly warning.

You can read detail explanation here

Community
  • 1
  • 1
Paritosh Piplewar
  • 7,982
  • 5
  • 26
  • 41
0

Put the various class definitions in order so that Question::Document is defined before you reference it. Otherwise ruby goes looking in the toplevel, as 7stud showed.

test.rb

class Document
end

class Question
end

class Question
  class Document
  end
end

class Answer
  puts Question::Document.class
end

The result

$ ruby test.rb
Class
Fred
  • 8,582
  • 1
  • 21
  • 27
  • Theoretically this is correct. But in rails you generally do not put models in the same file (because then rails cannot deduce filenames automatically based on the class name). What your answer shows is that requiring the correct file with the correct class should work. – nathanvda Sep 12 '13 at 08:50
  • but it doesn't work (three levels) with Rails (4.1.5) properly, the long term solution is renaming one of the classes, unless you want to mess around with require order - especially in Minitest. – Ivan Stana Aug 25 '14 at 19:48
0

I've wrote a gem which introduce alternative to require_dependency solution: heavy_control

It explicitly resolves given constant names on initalization via constantize (before other constants are loaded). Also it happens each reload in development.

mr_ffloyd
  • 103
  • 1
  • 8
-1

You mean like this:

Document = 'hello'

class Question
end

class Animal
  puts Question::Document
end

class Question
  class Document
  end
end

--output:--
1.rb:7: warning: toplevel constant Document referenced by Question::Document
hello

It looks like ruby searches enclosing scopes for a constant when it isn't found in the specified scope. I think the warning is a penalty for being unable to think up more than two variable names.

7stud
  • 46,922
  • 14
  • 101
  • 127
  • It's not that I **can't** name it something else but it makes sense to name them similarly because they are related to each other in that `Question::Document` is a question that has Document attached to it as the answer. I would rather not name the differently if at all possible because it's easier to tell the relationship/link between the two – Kyle Decot Aug 29 '13 at 17:09
  • This code is not really valid. The `puts Question::Document` is executed when parsing the file, and at that time the class `Question::Document` is just not yet defined. So change the order, or put the `puts` in a function, and it will work. So also: your conclusion is wrong: "penalty for being unable to think up more than two variable names" ???? WTF? It is perfectly possible to use the same classname in a different scope. – nathanvda Sep 12 '13 at 08:45
  • @nathanvda, _So change the order, or put the puts in a function, and it will work._ The point of the example was to use all the names mentioned by the op in some code that produces the same error message--not produce working code. _WTF? It is perfectly possible to use the same classname in a different scope._ And it is perfectly possible to use the variable names xxxyxx, xxyxxx, xyxxx, xxxyx in the same scope. So what? The goal isn't just to write legal syntax. Some of us try to set the bar a little higher. – 7stud Sep 12 '13 at 17:59