From: Matthew Moss Date: 2008-09-04T22:45:01+09:00 Subject: [SUMMARY] Where the Required Things Are (#175) Great job on this quiz, everyone who submitted. I know I'll be adding at least one of these submissions to my toolbox. Ten solutions were provided this week. I downloaded them all and tested each one in the manner requested in the extra credit. (For anyone who did not attempt the extra credit, make sure to look at the other solutions; adding this capability is only a small amount of extra code.) To test, I used the sample file provided by Michal Suchanek, shown here. require 'date' require 'readline' require 'matrix' require 'rubygems' require 'time' require 'hpricot' require 'rubyforge' require 'hoe' require 'curses' I made all modules were installed before testing. (Actually, some tests were done with missing modules, with mostly expected results. In the end, it was more interesting to see which solutions worked with a set of requires that did exist.) A more extensive set of tests would be appropriate to bullet-proof any solutions, but this is sufficient for this quiz. Ten solutions were provided from nine users (Suchanek providing two solutions). Of those ten, three failed with errors (Shelly, Stevens, Suchanek #2). Two solutions stumbled a bit when rubygems got involved. The first (Bilkovich) failed to find any of the gems: `hpricot`, `rubyforge` and `hoe`. The second (Reitinger) found `rubygems` but failed on the next (non-gem) require, `time`; since `rubygems` does its own thing to `require`, this is a possible source of problems to be addressed. It's possible these problems might be caused by platform differences; I am running ruby 1.8.6 (MacPorts install) on Mac OS X 10.4.11. I didn't have time to dig into these errors, but if you care to know the specific error, let me know. The next problem I found was that three solutions (Morin, Phillips, Wille) did not report the locations of the `readline` and `etc` modules. The likely cause here is, again, platform differences. These two modules in Mac OS X are ".bundle" files. One solution (Wille) reported the modules as not found, while the other two (Morin, Phillips) simply reported nothing for those two modules. Interestingly, the "cheat" provided by Wille, `gem which`, was also unable to locate `readline` and `etc`. That leaves us with two "winners" for this week: _Jesse Merriman_ and _Michal Suchanek_ (his first solution). Both solutions found all the required modules, recursively, including the .bundle files, and didn't get confused by `rubygems`. I'm going to look here at Jesse's solution. module Kernel alias :require_orig :require def require mod if success = require_orig(mod) file = $".last $:.each do |dir| path = "#{dir}/#{file}" if File.readable?(path) puts "require #{mod} => #{path}" break end end end success end end `require` is a `Kernel` method, so the first thing Jesse does is open the `Kernel` module and keep track of the original `require` method via an alias. Next, the new `require` method is defined, and the first thing it does is to call the original method, which is why the alias was needed. There are three possible results. First, if `require_orig` throws an exception... well, nothing gets done here. The exception throws out of this method, reproducing the standard behavior. Second, if `require_orig` returns false (and sets `success` accordingly), the the body is skipped and false is returned. Again, standard behavior has been maintained. The good stuff is what happens when `require_orig` returns true. Jesse makes use of two predefined global variables. `$"` is an array containing module names loaded by `require`; calling `last` on that array gets the most recently loaded module. `$:` is also an array, but contains the search paths for modules. So once a file is successfully loaded via a call to `require_orig`, Jesse checks through the module search paths, and builds a full path for the loaded module, checking that path against `File.readable?`. If so, the information is printed to standard output and the loop ends. Finally, most of you should recognize the standard technique for knowing when a script is run rather than loaded via `require`: if __FILE__ == $0 ARGV.each { |mod| require mod } end Make sure to look at Michal Suchanek's solution as well. The use of module `rbconfig` and method binding were new to me. One final note: my machine is running Mac OS X, so of course, all my testing has been using that. This has two implications: some programs may not have worked (pehaps some that I mentioned above), and also that the two "complete" solutions (Merriman, Suchanek) could quite possibly be broken on other platforms. I don't know. (I'm a bit occupied this week.:) A 100% solid solution should account for all these things. -- Matthew Moss