Thursday, September 20, 2007

Rails: Where do you require?

I was recently looking over a codebase and noticed that several different files contained require statements. I'm not talking about the require statement that each test file has, I'm taking about seeing require 'ostruct', require 'enumerator' or require 'soap4r' in numerous class definitions.

For the last few projects I've been following the convention of putting all the require statements in environment.rb. There are more efficient strategies, but I find this to be the most maintainable of the solutions I've tried.

While following the above strategy I haven't run into any issues with two libraries defining behavior on differing classes that are named the same, but I have heard concerns from team members on the time it takes to require each library. I think that's a valid concern and I was planning on addressing it by writing a method that takes an array of strings and requires them one at a time. The new method would also benchmark each require and print a warning if the require is taking an unacceptable amount of time.

Please drop me a line (or blog it and leave a link) in the comments if you have a strategy that's worked well for you.

7 comments:

  1. Perhaps it would be advantageous to define the requires in a Global hash in the environment.rb file and then do the actual requires closer to where they're actually used but with indexes into the hash?

    Amortizes load time, centralizes dependency management.

    ReplyDelete
  2. I keep my requires in the file(s) where they're needed. I started this in the C world (where I wanted single-file compilations to go faster and also wanted to minimize recompilation of an entire codebase just because I changed one little thing in a shared header file) and carried the habit with me over to Ruby.

    It's a locality of reference thing too. If you later do a rewrite that removes the dependency on the require you're much more likely to remember to remove it if its in the same file. If its in a central location you not only have to remember to edit the other file as well, you also have to check that the require isn't still needed by any other parts of the codebase.

    ReplyDelete
  3. Anonymous5:59 PM

    Ooo... I like that Dan.

    Just did some sloppy benchmarking of about 20 lib's on a local project... and it had a realtime of about 2 seconds.

    So... they do add up if they are relevant libs, but most patch like requires are obviously small.

    Luckily they only load up once, so isn't the end of the world in regards to performance.

    ReplyDelete
  4. I'm with Wincent here - keeping references to dependencies local to where they are needed makes it simpler to load individual parts of an application.

    It also makes it much easier to extract parts of an application and reuse them elsewhere...

    ReplyDelete
  5. I like it in the local file, but hey, that's just personal preference.

    If you are concerned about speed, I've got the perfect solution for you... Kernel#autoload. Purpose built for exactly this, and part of the standard library.

    ReplyDelete
  6. Slight tangent, but I coded up a patch to gems that allows you to pass require an array, and allows you to use symbols instead of strings. I just found it infinitely more intuitive. I never really finished it, but the code is here:

    http://pastie.caboo.se/93540

    It works in most cases and occasionally blows up unexpectedly. If I found the time to fix it, my answer to your question would be, do it once, with an array, and with one statement.

    ReplyDelete
  7. Joe Gutierrez7:22 PM

    #unit_test_suite.rb
    #jgutierrez

    EXPAND_PATH = File.expand_path(File.dirname(__FILE__))
    require EXPAND_PATH + "/helper"

    class TestRunner
    def load(require_file)
    require EXPAND_PATH + "/" + require_file
    end
    end

    test_runner = TestRunner.new

    test_runner.load 'test_customer'
    test_runner.load 'test_customer_list_model'
    test_runner.load 'test_customer_list_presenter'
    test_runner.load 'test_customer_editor_model'
    test_runner.load 'test_customer_editor_presenter'
    test_runner.load 'test_current_customer_coordinator'

    #I like this, it seems IMHO to be more natural for running tests than require '/test_xxxxx'

    ReplyDelete

Note: Only a member of this blog may post a comment.