Autoloaded graphic

Autoloaded

Travis CI build status Code Climate quality report Code Climate coverage report

Gemnasium build status Inch CI build status RubyGems release

If you like the Module#autoload feature of the Ruby Core library, you may have wished for Autoloaded. It eliminates the drudgery of handcrafting an autoload statement for each Ruby source code file in your project. It also avoids the limitations of rigid convention-driven facilities such as those provided by the ActiveSupport RubyGem.

Autoloaded assumes, but does not enforce, CamelCase-to-snake_case correspondence between the names of constants and source files. You can combine conventions, even putting multiple autoloaded constants in a single source file.

Installation

Install the RubyGem.

$ gem install autoloaded

Use Autoloaded in your RubyGem project by making it a runtime dependency.

# my_awesome_gem.gemspec

Gem::Specification.new do |spec|
  # ...
  spec.add_dependency 'autoloaded', '~> 2'
  # ...
end

Or you may want to make Autoloaded a dependency of your project by using Bundler.

# Gemfile

source 'http://rubygems.org'

gem 'autoloaded', '~> 2'

Usage

Suppose you have the following source files.

lib/
├─ my_awesome_gem/
│  ├─ db/
│  │  ├─ MicroSoft.rb
│  │  ├─ SELF-DESTRUCT!.rb
│  │  ├─ mysql.rb
│  │  ├─ oracle.rb
│  │  └─ postgre_sql.rb
│  ├─ db.rb
│  └─ version.rb
└─ my_awesome_gem.rb

The Autoloaded.module or Autoloaded.class method

The Autoloaded.module and Autoloaded.class method calls below invoke Module#autoload for each source file in the calling module’s corresponding directory. Note that these methods must receive a block, even if it’s an empty block.

The file paths used are abbreviated, if possible, using a directory of the Ruby load path ($:). They are also rendered without their .rb extension.

# lib/my_awesome_gem.rb
module MyAwesomeGem

  Autoloaded.module { }

  # The above is the equivalent of:
  #
  # autoload :Db,      'my_awesome_gem/db'
  # autoload :Version, 'my_awesome_gem/version'

end

# lib/my_awesome_gem/db.rb
module MyAwesomeGem

  class DB

    # The 'class' and 'module' methods are aliases -- use either one.
    Autoloaded.class { }

    # The above is the equivalent of:
    #
    # autoload :MicroSoft,      'my_awesome_gem/db/MicroSoft'
    # autoload :SELF_DESTRUCT_, 'my_awesome_gem/db/SELF-DESTRUCT!'
    # autoload :Mysql,          'my_awesome_gem/db/mysql'
    # autoload :Oracle,         'my_awesome_gem/db/oracle'
    # autoload :PostgreSql,     'my_awesome_gem/db/postgre_sql'

  end

end

The code above is succinct, but it’s not exactly correct. The constants MyAwesomeGem::DB, MyAwesomeGem::DB::MySQL, and others are not set up to autoload properly because they are misspelled (case-sensitively speaking).

MyAwesomeGem.autoload?          :DB  # => nil
MyAwesomeGem.const_defined?     :DB  # => false
MyAwesomeGem.constants.include? :DB  # => false

MyAwesomeGem::DB  # Raises NameError because lib/my_awesome_gem/db.rb does not
                  # get autoloaded!

MyAwesomeGem.autoload?          :Db  # => 'my_awesome_gem/db' (a lie!)
MyAwesomeGem.const_defined?     :Db  # => true (a lie!)
MyAwesomeGem.constants.include? :Db  # => true (a lie!)

MyAwesomeGem::Db  # Raises NameError because lib/my_awesome_gem/db.rb defines
                  # MyAwesomeGem::DB, not MyAwesomeGem::Db!

#################################################################################

require 'my_awesome_gem/db'

MyAwesomeGem::DB.autoload?          :MySQL  # => nil
MyAwesomeGem::DB.const_defined?     :MySQL  # => false
MyAwesomeGem::DB.constants.include? :MySQL  # => false

MyAwesomeGem::DB::MySQL  # Raises NameError because
                         # lib/my_awesome_gem/db/mysql.rb does not get
                         # autoloaded!

MyAwesomeGem::DB.autoload?          :Mysql  # => 'my_awesome_gem/db/mysql' (a lie!)
MyAwesomeGem::DB.const_defined?     :Mysql  # => true (a lie!)
MyAwesomeGem::DB.constants.include? :Mysql  # => true (a lie!)

MyAwesomeGem::DB::Mysql  # Raises NameError because
                         # lib/my_awesome_gem/db/mysql.rb defines
                         # MyAwesomeGem::DB::MySQL, not MyAwesomeGem::DB::Mysql!

The with specification

Autoloaded needs hints from you concerning unpredictable spellings, stylization, and organization of constant names and/or source file names. You can specify with as:

  • A symbol or array of symbols representing constants to autoload
  • A hash of symbols and strings representing constants and the source filename(s) from which to autoload them
  • A combination of the above

A symbol provided to with signifies the name of a constant, and a string signifies the name of a source file.

Specifying with does not filter the source files; it maps the source files to different constants, or tweaks the names of constants.

You can specify with multiple times, and its effects are cumulative.

# lib/my_awesome_gem.rb
module MyAwesomeGem

  Autoloaded.module do |autoloading|
    autoloading.with :DB, :VERSION
    # Or:
    # autoloading.with :DB
    # autoloading.with :VERSION
    # Or:
    # autoloading.with DB: 'db', VERSION: 'version'
    # Or:
    # autoloading.with DB:      'db'
    # autoloading.with VERSION: 'version'
    # Or:
    # autoloading.with 'db' => :DB, 'version' => :VERSION
    # Or:
    # autoloading.with 'db'      => :DB
    # autoloading.with 'version' => :VERSION
  end

  # The above is the equivalent of:
  #
  # autoload :DB,      'my_awesome_gem/db'
  # autoload :VERSION, 'my_awesome_gem/version'

end

# lib/my_awesome_gem/db.rb
module MyAwesomeGem

  class DB

    Autoloaded.class do |autoloading|
      autoloading.with :MySQL, :PostgreSQL, [:Access, :SQLServer] => 'MicroSoft'
      # Or:
      # autoloading.with :MySQL,
      #                  :PostgreSQL,
      #                  Access:    'MicroSoft',
      #                  SQLServer: 'MicroSoft'
      # Or:
      # autoloading.with :MySQL,
      #                  :PostgreSQL,
      #                  'MicroSoft' => [:Access, :SQLServer]
      # Or:
      # autoloading.with :MySQL,
      #                  :PostgreSQL,
      #                  'MicroSoft' => :Access,
      #                  'MicroSoft' => :SQLServer
      # Or ...
    end

    # The above is the equivalent of:
    #
    # autoload :Access,         'my_awesome_gem/db/MicroSoft'
    # autoload :SQLServer,      'my_awesome_gem/db/MicroSoft'
    # autoload :SELF_DESTRUCT_, 'my_awesome_gem/db/SELF-DESTRUCT!'
    # autoload :MySQL,          'my_awesome_gem/db/mysql'
    # autoload :Oracle,         'my_awesome_gem/db/oracle'
    # autoload :PostgreSQL,     'my_awesome_gem/db/postgre_sql'

  end

end

Now you’re autoloading all the constants you want to be autoloading.

MyAwesomeGem.autoload?          :DB  # => 'my_awesome_gem/db'
MyAwesomeGem.const_defined?     :DB  # => true
MyAwesomeGem.constants.include? :DB  # => true

MyAwesomeGem::DB  # => MyAwesomeGem::DB

MyAwesomeGem.autoload?          :Db  # => nil
MyAwesomeGem.const_defined?     :Db  # => false
MyAwesomeGem.constants.include? :Db  # => false

MyAwesomeGem::Db  # Raises NameError as expected.

#################################################################################

MyAwesomeGem::DB.autoload?          :MySQL  # => 'my_awesome_gem/db/mysql'
MyAwesomeGem::DB.const_defined?     :MySQL  # => true
MyAwesomeGem::DB.constants.include? :MySQL  # => true

MyAwesomeGem::DB::MySQL  # => MyAwesomeGem::DB::MySQL

MyAwesomeGem::DB.autoload?          :Mysql  # => nil
MyAwesomeGem::DB.const_defined?     :Mysql  # => false
MyAwesomeGem::DB.constants.include? :Mysql  # => false

MyAwesomeGem::DB::Mysql  # Raises NameError as expected.

The except and only specifications

What about source files you don’t want to be autoloaded?

MyAwesomeGem::DB::SELF_DESTRUCT_  # Loading this file does something bad, so
                                  # let's not.

If you really want to avoid loading lib/my_awesome_gem/db/SELF-DESTRUCT!.rb, so much so that you don’t want an autoload statement made for it, specify except.

# lib/my_awesome_gem/db.rb

module MyAwesomeGem

  class DB

    Autoloaded.class do |autoloading|
      autoloading.with :MySQL, :PostgreSQL, [:Access, :SQLServer] => 'MicroSoft'

      autoloading.except 'SELF-DESTRUCT!'
      # Or:
      # autoloading.except :SELF_DESTRUCT_
      # Or ...
    end

    # The above is the equivalent of:
    #
    # autoload :Access,     'my_awesome_gem/db/MicroSoft'
    # autoload :SQLServer,  'my_awesome_gem/db/MicroSoft'
    # autoload :MySQL,      'my_awesome_gem/db/mysql'
    # autoload :Oracle,     'my_awesome_gem/db/oracle'
    # autoload :PostgreSQL, 'my_awesome_gem/db/postgre_sql'

  end

end
MyAwesomeGem::DB.autoload?          :SELF_DESTRUCT_  # => nil
MyAwesomeGem::DB.const_defined?     :SELF_DESTRUCT_  # => false
MyAwesomeGem::DB.constants.include? :SELF_DESTRUCT_  # => false

MyAwesomeGem::DB::SELF_DESTRUCT_  # Raises NameError as expected.

You can specify except as:

  • A symbol or array of symbols representing constants to avoid autoloading
  • A string or array of strings representing source filenames to avoid autoloading
  • A hash of symbols and/or strings representing constants and/or source filenames to avoid autoloading
  • A combination of the above

The only specification is like except but it has the opposite effect, namely, that only specified constants and/or source files will be autoloaded.

You can specify only as:

  • A symbol or array of symbols representing constants to autoload
  • A string or array of strings representing source filenames to autoload
  • A hash of symbols and/or strings representing constants and the source filename(s) from which to autoload them
  • A combination of the above

A symbol provided to except or only signifies the name of a constant, and a string signifies the name of a source file.

You can specify except and only multiple times, and their effects are cumulative.

The from specification

It’s recommended that you call Autoloaded.module or Autoloaded.class from within the source file where your module or class is defined. This practice allows Autoloaded to assume that the source files to be autoloaded are in a directory of the same name (and in the same location) as the module’s defining source file.

There are circumstances, however, in which you cannot rely on the computed directory for autoloading. Perhaps the directory has a different name from the module’s defining source file. Or perhaps you are autoloading a library that you didn’t author. In these situations you can specify from with the path from which source files should be autoloaded.

# somewhere_else.rb

module MyAwesomeGem

  Autoloaded.module do |autoloading|
    # The following code is not actually very useful since the installed location
    # of a RubyGem varies with the operating system and user preferences. How to
    # compute the path properly is outside the scope of this readme and is left
    # as an exercise for the reader.
    autoloading.from '/absolute/path/to/my_awesome_gem'
  end

end

A path provided to from cannot be relative; it must start with the filesystem root.

If you specify from multiple times in an Autoloaded block, only the last one takes effect.

The Autoloaded.warn method

There are two circumstances under which Autoloaded by default will write warnings to stderr:

  • Overriding an established autoload
  • Establishing an autoload for a defined constant

You can silence these warnings by passing false to Autoloaded.warn. (Passing true turns warnings on if they are off.)

# lib/my_awesome_gem/db.rb

module MyAwesomeGem

  class DB

    Autoloaded.warn false  # Turn off Autoloaded warnings.

    autoload :SQLServer, 'my_awesome_gem/db/MicroSoft'

    class Oracle; end

    Autoloaded.class { }  # This duplicates the 'autoload' statement and class
                          # definition above, but no Autoloaded warnings will be
                          # displayed.

    Autoloaded.warn true  # Turn on Autoloaded warnings again.

  end

end

Use the block form if you want to ensure warnings get toggled on or off for a series of statements.

# lib/my_awesome_gem/db.rb

module MyAwesomeGem

  class DB

    autoload :SQLServer, 'my_awesome_gem/db/MicroSoft'

    class Oracle; end

    Autoloaded.warn false do
      Autoloaded.class { }  # This duplicates the 'autoload' statement and class
                            # definition above, but no Autoloaded warnings will
                            # be displayed.
    end

    # Autoloaded warnings are turned on again automatically.

  end

end

How to debug autoloading

The Autoloaded.module or Autoloaded.class method returns an ordered list of arguments it has passed to autoload.

# lib/my_awesome_gem/db.rb

module MyAwesomeGem

  class DB

    results = Autoloaded.class do |autoloading|
      autoloading.with   :MySQL,
                         :PostgreSQL,
                         [:Access, :SQLServer] => 'MicroSoft'
      autoloading.except 'SELF-DESTRUCT!'
    end
    STDOUT.puts results.inspect  # See output below.

  end

end

# [[:Access,     'my_awesome_gem/db/MicroSoft'  ],
#  [:SQLServer,  'my_awesome_gem/db/MicroSoft'  ],
#  [:MySQL,      'my_awesome_gem/db/mysql'      ],
#  [:Oracle,     'my_awesome_gem/db/oracle'     ],
#  [:PostgreSQL, 'my_awesome_gem/db/postgre_sql']]

You can also hook Module#autoload and Kernel#autoload via monkeypatching or other means in order to see what’s happening.

Source filenames are relative to the from specification

You may have noticed that source filenames in the above examples are not absolute. They are relative to the Autoloaded block’s from specification (which I recommend that you allow to be computed for you — see above).

Recursive autoloading not supported

Autoloaded does not perform deep autoloading of nested namespaces and directories. This is by design.

Contributing

  1. Fork the official repository.
  2. Create your feature branch: git checkout -b my-new-feature.
  3. Commit your changes: git commit -am 'Add some feature'.
  4. Push to the branch: git push origin my-new-feature.
  5. Create a new pull request.

Development

After cloning the repository, bin/setup to install dependencies. Then rake to run the tests. You can also bin/console to get an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, bundle exec rake install. To release a new version, update the version number in lib/autoloaded/version.rb, and then bundle exec rake release, which will create a Git tag for the version, push Git commits and tags, and push the .gem file to RubyGems.org.

License

Released under the MIT License.