How to Load Code in Ruby
Tags: ruby
Published: 2023-04-27 10:07:16

There are many ways to load code and access file-related constants in Ruby. We can create a clear architecture by separating and handling concerns into classes and pulling in only the classes we depend on.

Many full-stack frameworks like Rails and Hanami offer a built-in method to access the classes we want, as long as we stick with a certain convention. How does this work?

In this post, we will explore three different options for loading code: using `load`, `require`, and `autoload`. We will also look into the Ruby gem `Zeitwerk`. `Zeitwerk` is the default code-loading mechanism for many new projects, and a lot of established projects are switching to this great gem. Rails and Hanami both utilize `Zeitwerk` as their code-loading tool.

Before we dive into the code loading options on offer in Ruby, let's take a quick look at `$LOAD_PATH` to get to grips with how code loading works.

## `$LOAD_PATH` in Ruby

Before we dive deeper into this topic, let's summarize the `$LOAD_PATH` in Ruby, as it is essential in understanding how loading works.

The global variable `$LOAD_PATH` or shorter `$:` references all directories with Ruby source files registered in an array. So when you prepend `bundle exec` with your program name, Ruby adds all the program gems to the load path.

Then, when we load or require a file, Ruby iterates over this array and searches for the file name. This can add significant time to the booting process, as we might have to search for a given file in many places on the hard disk.

Keeping that in mind, we'll now explore some Ruby code-loading options in detail.

## Options for Loading Code in Ruby

We have three main options for loading code:

- Using the `load` method. This loads and parses a Ruby program in a specified file every time you call the method.
- The `require` method. With this, we load and parse a given file only once.
- The third option is `autoload`. We declare upfront that Ruby should require a specified file when we use a constant that doesn't exist yet.

## `load` a File in Ruby

As mentioned, with `load` we parse and execute a Ruby file.
If the filename we set as an argument is a relative path, i.e., it starts with `./` or `../`, the file will be loaded relative to the current working directory.

Remember: This doesn't mean it's relative to the file where the load is located — it's instead relative to the file that started the Ruby process. If you want to know what the working directory is, run `Dir.pwd`. Otherwise, Ruby searches for a file with the given name in the `$LOAD_PATH`.

Let's see how load works:

```ruby
# foo.rb
$x = 4 # define global Variable

class Foo
  def bar
    puts "I'm doing it now"
  end
end
```

Then we load the file with `load "foo.rb"`.

What happens when we load this file? The global variable `$x` is created and set to the integer 4. The constant `Foo` is also created, and we can instantiate an object from it. So far, this seems just like the more familiar `require` call.

But what happens when we `load` this file again? If we have changed our global value `$x`, it resets to 4.

In older Ruby versions, we also might get a warning that the constant `Foo` is being redefined.

Another problem with `load` is that every reload takes some time to run as the Ruby VM is `eval`-ing the file. `require`, on the other hand, loads and parses a file just once, during the first run.

### Wrapping the File with `load`

Right now, every time we load or require a file, we pollute the global namespace. To avoid this, we can use the `load` method with the wrap parameter set to `true`. This will create an anonymous module where all the constants and methods defined in a loaded file are placed. Here is an example:

```ruby
# file to be loaded foo.rb

class Foo
  puts self.name # => Here we will see where our Foo class is located in
  def bar
    puts "I'm doing it now"
  end
end

###############################
# Now in another file run this:

load "foo.rb", true
```

If you run this, Ruby will output something like this: `#<Module:0x00007fa3430682e8>::Foo`.

Why is this useful? For one thing, we are not polluting the global namespace. But since Ruby creates an anonymous module, we can only access the stuff we load with some trickery.

Nevertheless, wrapping the file is still useful if we want to configure something in our application or set some things up. This way, we can achieve what we want without leaving anything behind.

### Accessing the Wrapped File Constants

There are two ways to access the constants we create in the wrapped file. The first and most obvious is to set a module name as the argument for the wrap parameter.

```ruby
class Foo
  def bar
    puts "I'm doing it now"
  end
end

###############################
# Now in another file run this:

module Parent
end
load "./code_loading/file.rb", Parent

Parent::Foo.new
```

Here is how we can have access to the anonymous Ruby module:

```ruby
class Foo
  def bar
    puts "I'm doing it now"
  end
end

throw :wrapper, Module.nesting.last


############################################
# When we load the file we catch the  error
# and make the module accessible

mod  = catch :wrapper do
  load "./code_loading/file.rb", true
end

mod::Foo.new
```

There is a hidden problem with `wrap`. If the author of the loaded file does something like this, it will break the sandbox:

```ruby
class ::Foo
  # stuff
end
```

Now `Foo` is in the global namespace, and there is no way to prevent this pollution.

## Ruby Code Loading with `require`

Just like the `load` method, `require` is a method in the `Kernel` module. The big difference between these two methods is how they behave on multiple calls.

While `load` always reevaluates a file, `require` doesn't. Its response is also different. If you call `require` for the first time and the method actually loads something, it returns `true`, otherwise, it returns `false`. `load`, on the other hand, always returns `true`.

Some pseudo-code for `require` would look something like this:

```ruby
def require(file_path)
  if !already_loaded_files? file_path
    eval(File.read(find_file_in_load_path(file_path)))
    mark_file_as_loaded! file_path
    true
  else
    false
  end
end
```

`require` accepts absolute paths for files as well as just a simple name. If we need a simple file, `require` also searches in the `$LOAD_PATH`. This, of course, means that we still have the same disadvantages we had with `load`.

### Simpler File Searching with `require_relative`

`require_relative` always looks for a file that is relative to the current file you are working on. `require_relative` doesn't iterate over a directory, and no real search is involved. Ruby looks into the path specified and tries to load the file. If it isn't there, we get a `LoadError`.

Unfortunately, as of now, `require` doesn't have a `wrap` parameter like `load` does, so `require` always pollutes the global namespace. But there has been [some recent discussion on the Ruby issue tracker](https://bugs.ruby-lang.org/issues/10320) to add something like `wrap` to the `require` method.

## `autoload` Code Loading in Ruby

Our third option for code loading in Ruby is `autoload`. With `autoload`, we tell the Ruby VM upfront what constants might be accessed and where to find and load them.

A big advantage of this approach is that we only load the files we really need. Therefore, the booting process can be really fast as we just pay for what we use. When we actually need the constant, `autoload` requires the file with the method `require`.

Here's how we set up an autoload:

```ruby
### lib/services/foo.rb
module Services
  class Foo
    def bar
      puts "bar"
    end
  end
end

### lib/services.rb
module Services
  autoload :Foo, "#{__dir__}/services/foo.rb"

  # Now we have access to the Foo constant in this module
  # and we will load the file when we actually need it.

  # Foo.new.bar => loads the file and prints "bar"
end
```

The most popular gem for managing loading files is `Zeitwerk`, and it works with `autoload`. Now let's see how Zeitwerk works and how to set it up.

## Code Loading with Zeitwerk

[Zeitwerk](https://github.com/fxn/zeitwerk) takes a directory and makes every file underneath it available to load. The convention is that every new sub-directory is a new module, and every file defines a class with the same name as the file.

Zeitwerk offers an option for code reloading that is useful during development. Rails uses this mode in an active development environment.

How does it work under the hood? Here is some _very, very_ simplified pseudo-code:

```ruby
module Zeitwerk
  class Loader
    def initialize()
      @root_dir = []
    end

    def push_dir(dir)
      @root_dirs << dir
    end

    def setup
      # scan root dirs and define autoloads for each file
      @root_dirs.each do |dir|
        Dir.foreach(dir) do |file|
          next if file == "." || file == ".."

          autoload file.class_name.to_sym, File.expand_path(file, dir)
        end
      end
    end
  end
end

loader = Zeitwerk::Loader.new
loader.push_dir(__dir__)
loader.setup
```

Now, with the last call to `loader.setup`, we get access to every constant defined in one of the files relative to the current file.
Of course, this simplified code doesn't highlight many of the other great features that `Zeitwerk` has, like eager loading and handling namespaces. That's outside of the scope of this post, and I'll leave it to you to dig deeper and explore all `Zeitwerk` has to offer.

### How to Set Up `Zeitwerk` with Roda

Since Rails and Hanami use `Zeitwerk` as their default, let's see how we can configure `Zeitwerk` for Roda:

```ruby
### in config.ru

loader = Zeitwerk::Loader.new
loader.push_dir(__dir__)
loader.push_dir("#{__dir__}/lib")

if ENV['RACK_ENV'] == 'production'
  loader.setup
  Zeitwerk::Loader.eager_load_all
  run App
else
  loader.enable_reloading
  loader.setup
  run ->(env) {
    loader.reload
    App.call(env)
  }
end
```

If we configure our app like this, we have code reloading during development and eager loading in production. This way, the boot time during development is fast, as only the files that are immediately needed are loaded. In production, we load everything upfront and have a faster response time.

## Wrapping Up

In this post, we first took a quick look at `$LOAD_PATH` in Ruby to lay the foundations of how code loading works. We then explored three options to load code in some detail: `load`, `require`, and `autoload` (which works well with the `Zeitwerk` gem).

So, how should we load our code? Most of the time, we should use `require` and `Zeitwerk` to automate that. If you have special requirements and want to put loaded code into a namespace, the `load` method with a wrap parameter is a good option.

Happy code loading!

**P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, [subscribe to our Ruby Magic newsletter and never miss a single post](https://blog.appsignal.com/ruby-magic)!**