views:

91

answers:

1

How can you display the hierarchy of 'require's that take place in a Ruby app?

Some files require files which require additional files.

However, by running an application in debug mode you only trigger a subset of required files - only the ones that are used by whatever subset of functionality your application is using at any given point in time.

How could you display a comprehensive hierarchy of all of the requires in an application as a tree?

+7  A: 

The issue is that in development mode, all files are loaded with load rather than require so that they can be reloaded on each request. In production they are loaded only once. With the exception of some of the framework classes, most files are still only loaded when they are first used. This happens because ActiveSupport overrides const_missing to automatically attempt to load unknown constants from files with the appropriate naming scheme (ConstantName.to_s.underscore would give require 'constant_name'). This of course really muddles up the 'require' hierarchy.

For a trivial case, you can modify the following to meet some of your needs (also check out dependencies in active_support)

  $require_level = []
  alias :orig_require :require
  def require(file)
    puts "#{$require_level.join}#{file}"
    $require_level << "-"
    r = orig_require(file)
    $require_level.pop
    r
  end

  require 'foo'
  require 'baz'


 ben@legba-2:~ $ ruby check_requires.rb 
 foo
 -bar
 baz

Good luck

EDIT: Explanation

What that does is create a global array to store the nesting level of requires. The first puts outputs the required file. Then a dash is added to the nesting level. The file is then actually required. If the loaded file calls require, then this whole process starts again, except that nesting level is 1 deep so "-#{file}" is puts-ed. This process repeats except as the nesting level grows, so do the dashes. After a file and all of its dependencies are loaded, require takes off the dash that it added so that nesting level is in the same state it was when the require started. This keeps the tree structure accurate.

const_missing is similar to method_missing. Basically, just like when you call AnObject.some_unknown_method ruby will call AnObject.method_missing(:some_unknown_method) before raising a NoMethodError, using SomeUnknownConstant triggers a const_missing(:SomeUnknownConstant) before raising a NameError. Rails defines const_missing such that it will search certain specified paths for files that might define the missing constant. It uses a naming convention to facilitate this, e.g. SomeUnknownConstant is expected to be in some_unknown_constant.rb

There is a method to much of this rails madness.

Ben Hughes
Wow. That's a nice little script. Let me see if I can figure out what you're doing. So, require_level is an empty global array. Then you alias the require method as orig_require and redefine require. Not sure what your first puts statement does. Then you append a dash to the array. Then you require the file, pop the last value from the array (why bother? it's just a dash, right? can't you just print "-"?) Then you print the name of the file that you required. It seems to work, I'm just not sure how it all works. Thanks for the script. I'm going to try this now.
monkeyman
Also, in your answer I didn't understand what you meant about const_missing. Would you mind elaborating on that?
monkeyman
Thanks - your script works great!
monkeyman
explanation is now in the answer
Ben Hughes
Thanks, Ben. That's a really nice concise explanation that clears up my confusion. Unfortunately, as I think you implied, it also means that the actual require hierarchy could be quite difficult to fathom except for cases like those handled by your script, in which there is an explicit require. In the case of constants, though, it will be difficult to easily uncover what is being required because of the way const_missing is overridden. At least that's how I'm interpreting your explanation.
monkeyman
definitely. If you have a particularly troubling case (like you just need more info) you should probably dig into the file `rails/activesupport/lib/active_support/dependencies.rb`. depending on exactly what you need, you might be able to creatively add some debugging instrumentation there to find it. A full require hierarchy is just not going to happen (or if it did, would be of dubious value).
Ben Hughes
The overridden const_missing in ActiveSupport does call require if it finds a file to load AND it knows it should use require. So presuming you (or Rails) sets Dependencies.mechanism = :require, it should play nice in the hierarchy.
Sarah Mei