Zeitwerk Documentation

repository·main·Indexed 24 days ago

https://github.com/fxn/zeitwerk

An efficient and thread-safe code loader for Ruby that automates constant loading based on file structure conventions. It provides features for autoloading, eager loading, and code reloading, eliminating the need for manual require calls. Zeitwerk supports generic loaders, gem integration via Zeitwerk::Loader.for_gem, and gem extensions via Zeitwerk::Loader.for_gem_extension. It maps file paths directly to Ruby constant paths and offers advanced configuration for namespaces, nsfiles, and organizational directory collapsing.

Tokens
9.7K
Snippets
32
Records
61
Agent score
79%

What's inside Zeitwerk

  1. What is Zeitwerk

    main

    Zeitwerk is an efficient and thread-safe code loader for Ruby. It automates the loading of classes and modules based on a conventional file structure, eliminating the need for manual require calls.

    Key features include:

    • Autoloading: Loading constants on demand.
    • Eager Loading: Loading all constants upfront.
    • Code Reloading: Supporting development workflows where code changes need to be reflected without restarting the process.
    • Independent Loaders: Multiple loaders can coexist in a single process, each managing its own project tree, configuration, inflector, and logger independently.
    • Performance: Uses absolute file names for require calls to avoid $LOAD_PATH lookups and performs minimal file system scans.
  2. Beware of circular dependencies

    main
    Zeitwerk cannot resolve top-level circular dependencies. If Class A inherits from Class B, but Class B's body refers to Class A, the loading will fail. This is a limitation of Ruby itself, not just Zeitwerk.
  3. Understand file shadowing in Zeitwerk

    main

    Shadowing occurs when multiple files could define the same constant. Zeitwerk handles this similarly to Ruby's require:

    1. Namespace Shadowing: If foo.rb exists in multiple root directories at the same namespace level, the constant Foo is autoloaded from the first one found. The others are ignored.
    2. Pre-defined Constants: If a constant (e.g., Foo) is already defined (perhaps by a dependency) when Zeitwerk encounters the file intended to define it, that file is ignored.

    Shadowing only applies to Ruby files; namespace definitions can still be spread across multiple directories.

  4. Identify the public interface for end-users

    main

    When using Zeitwerk, only methods that are explicitly documented and not tagged as private should be considered part of the stable public interface.

    To avoid breaking changes, do not rely on the following:

    • Methods tagged with @private.
    • Undocumented public methods (these are considered exploratory and may change or be deleted without warning).
    • Methods starting with two underscores (e.g., __autoloads).

    Note that undocumented methods may be used in the Rails integration, but they are not guaranteed to be stable for general end-user use.

  5. The core idea: File paths match constant paths

    main

    Zeitwerk's primary mechanism is mapping file paths directly to Ruby constant paths. To use Zeitwerk, name your files and directories after the classes and modules they define.

    Mapping Examples:

    • lib/my_gem.rb $\rightarrow$ MyGem
    • lib/my_gem/foo.rb $\rightarrow$ MyGem::Foo
    • lib/my_gem/bar_baz.rb $\rightarrow$ MyGem::BarBaz
    • lib/my_gem/woo/zoo.rb $\rightarrow$ MyGem::Woo::Zoo
  6. Use implicit namespaces for empty modules

    main

    If a directory contains Ruby files but no file exists to define the directory's name as a module, Zeitwerk automatically creates that module for you.

    Example: If you have app/controllers/admin/users_controller.rb but no app/controllers/admin.rb, Zeitwerk will automatically create the Admin module when it is first accessed.

    Requirement: The directory must contain non-ignored .rb files (directly or recursively) for Zeitwerk to recognize it as a namespace.

  7. Distinguish between public and library-public interfaces

    main

    Zeitwerk distinguishes between the interface intended for end-users and the interface intended for internal library use (library-public).

    Methods that are library-public are declared using the internal DSL. These methods are defined as private but are also exposed via a public alias prefixed with two underscores. This allows internal library code to access them while signaling to end-users and tooling that they should not be used.

    Example of internal method definition:

    internal :autoloads

    This creates a private method autoloads and a public alias __autoloads.

  8. Handle nested root directories

    main

    Zeitwerk supports nested root directories (e.g., app/models and app/models/concerns). When a directory is identified as a nested root, Zeitwerk treats it as an independent root rather than a namespace within the parent.

    For example, a file at app/models/concerns/geolocatable.rb should define Geolocatable, not Concerns::Geolocatable.

  9. Zeitwerk Best Practices and Rules of Thumb

    main

    Follow these guidelines to ensure stable autoloading and reloading:

    1. Avoid Overlapping Loaders: Different loaders should manage distinct directory trees. Configuring overlapping root directories in different loaders is an error.
    2. File Existence as Require: Treat the existence of a file as an implicit require. It will be loaded either on-demand (autoload) or upfront (eager load).
    3. First Win Policy: If two loaders manage files that translate to the same constant in the same namespace, the first one encountered wins; the others are ignored.
    4. Namespace Reopening: When reopening a namespace from a dependency, ensure the dependency is loaded (e.g., via require) before calling setup so you are reopening, not defining.
    5. Avoid Stale Constants: Do not cache objects from reloadable constants in non-reloadable places. For example, a non-reloadable class should not subclass a reloadable class or mix in a reloadable module, otherwise, the non-reloadable class will hold a stale reference after a reload.
    6. Single Loader for Reloading: Ideally, a process should have at most one loader with reloading enabled to avoid complexity when loaders refer to each other's constants.
  10. Set up a generic Zeitwerk loader

    main

    To use the generic Zeitwerk API, you must call setup on a Zeitwerk::Loader instance. Customization, such as adding root directories via push_dir, should be performed before calling setup. The setup method is synchronized and idempotent.

    loader = Zeitwerk::Loader.new
    loader.push_dir(File.dirname(__FILE__))
    loader.setup
    loader.push_dir(...)
    loader.push_dir(...)
    loader.setup
  11. Verify file compliance in tests

    main

    To ensure your file structure correctly matches your constant definitions, you can use eager_load in your test suite. If a file is loaded but the expected constant is not defined, Zeitwerk raises Zeitwerk::NameError.

    Testing Pattern:

    begin
      loader.eager_load(force: true)
    rescue Zeitwerk::NameError => e
      flunk e.message
    else
      assert true
    end
    loader.eager_load(force: true)
  12. Enable and use code reloading

    main

    Zeitwerk supports code reloading, but it must be explicitly enabled before calling setup.

    Workflow:

    1. Instantiate the loader.
    2. Call enable_reloading.
    3. Call setup.
    4. Call reload when changes occur.

    Important Notes:

    • Reloading is an instance method; reloading one loader does not affect others.
    • Reloading removes currently loaded classes/modules and resets the loader to pick up the current state of the file system.
    • Enabling reloading after setup raises Zeitwerk::Error.
    • Attempting to reload without enabling it raises Zeitwerk::ReloadingDisabledError.
    • Attempting to reload without calling setup raises Zeitwerk::SetupRequired.
    loader = Zeitwerk::Loader.new
    loader.push_dir(...) 
    loader.enable_reloading # must be before setup
    loader.setup
    ...
    loader.reload