Jon
Jon

Reputation: 1259

Clean way to require all files in non-Rails Ruby app?

I'm trying to write a game in Ruby (not Rails) as a way to teach it to myself better. (Meaning, I'd like to do this right, but if I'm trying to shoehorn something that just won't work into Ruby, I'll switch languages.) I'm running into a problem with require order, and I'm wondering if there's a clean way to do the following.

Here's my structure so far:

game
  Gemfile
  src
    models
      character.rb
      game_object.rb
  init.rb

Instead of listing each file individually, init.rb requires files like this:

  Dir['./src/**/*.rb'].each do |app|
    require app
  end

game_object.rb is, so far, very simple, but character.rb looks like this:

module Game
  class Character < Game::GameObject
    attr_accessor :name

    def initialize(name)
      @name = name
      end
  end
end

Unfortunately, if I do that, I get "uninitialized constant Game::GameObject (NameError)", unless I explicitly require game_object before other files.

It seems to me that I have a few options here:

  1. Load game_object (and other superclassees) in init.rb before others.
  2. Require game_object in character.rb, which seems problematic because depending on which path I use, my understanding is that it may load the file multiple times.
  3. Load each file individually and manage the order completely manually so I have full control.

These all seem to be more complicated than it should be. Is there a cleaner way?

Upvotes: 4

Views: 771

Answers (2)

d11wtq
d11wtq

Reputation: 35308

I haven't done it before, but you could add your own autoloading logic to Module#const_missing.

class Module
  def const_missing_with_autoload(c)
    components = []
    (self.to_s.split("::") + c.to_s.split("::")).reverse.each do |comp|
      components.unshift(comp.gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase)
      begin
        require File.join(*components)
        return const_get(c)
      rescue LoadError
      end
    end
    const_missing_without_autoload(c)
  end

  alias_method :const_missing_without_autoload, :const_missing
  alias_method :const_missing, :const_missing_with_autoload
end

I assumed this would work without the method swizzling, but I couldn't get that working. I'd love somebody to post a clean solution that achieves what I'm trying to do. FWIW, just adding ActiveSupport to your project would probably be useful anyway.

Upvotes: 0

dnch
dnch

Reputation: 9605

Just putting it out there: more code does not mean "dirty", just as often as less code does not mean "clean."

Requiring your files manually will give you less headache in the long run—it solves load order issues and prevents you from requiring files you don't actually want. Plus, anyone looking at your code later on has a nice, clean manifest of what's being loaded in your app.

My suggestions:

  1. Drop the src folder, it's redundant—your Ruby app is all source.
  2. Map your namespaces to folders
  3. Use explicit requires (this becomes easier when you use #2:

Example:

game/
  bin/
    gamerunner
  Gemfile
  lib/
    game.rb
    game/
      game_object.rb
  models/
    character.rb
  spec/
    spec_helper.rb
    models/
      character_spec.rb

You are writing specs / tests, aren't you?

Then, in lib/game.rb:

require 'game/game_object.rb'
# require the rest of your library as you build it

module Game
end

And in your init:

require 'game'
require 'models/character.rb'

Much cleaner, much easier to extract from later on and should solve your problem.

Upvotes: 5

Related Questions