jeremyevans/rack-unreloader

Rack Application that reloads application files if changed, unloading constants first

110

stars

114

commits

Ruby

primary language

Aug 26, 2026

updated

README

= Rack::Unreloader

Rack::Unreloader is a code reloader for {Rack}[https://github.com/rack/rack].
It speeds up application development by automatically reloading stale code
so that you don't have to restart your dev server every time you change a file.

Unlike most other code loading libraries for Rack,
this one ensures that reloads are clean and idempotent
by _unloading_ relevant constants first, and it does so incrementally, only
reloading the files that are modified.

== Installation

  gem install rack-unreloader

== Source Code

Source code is available on GitHub at https://github.com/jeremyevans/rack-unreloader

== Basic Usage

Before:

  # config.ru

  require './app'

  run App

After:

  # config.ru

  require 'rack/unreloader'
  Unreloader = Rack::Unreloader.new{App}
  Unreloader.require './app.rb'

  run Unreloader

Now, +app.rb+ will be monitored for changes on each incoming HTTP request.

If changes are detected, +Rack::Unreloader+ will
unload all constants defined inside it and then re-require it
before proceeding with the request.

== Handling Subclasses

By default, +Rack::Unreloader+ unloads *all* constants defined in +app.rb+.
That includes third-party libraries, like +Roda+ or +JSON+ in the example below:

  # app.rb

  require 'roda'
  require 'json'

  class App < Roda
    ...
  end

Unloading these classes/modules isn't just unnecessary, it's dangerous.
If your own code depends on them, your app will throw a +NameError+ after
reloading when it tries to access them.

To reload only *subclasses* of +Roda+ (i.e. +App+), use the +:subclasses+
option:

  Rack::Unreloader.new(:subclasses=>%w'Roda'){App}

== Handling Errors During Reloading

By default, +Rack::Unreloader+ instances do not handle exceptions raised
during reloading, so that it may be rescued elsewhere (e.g. manually or by middleware).
You can use the +:handle_reload_errors+ option to send the backtrace directly to the
client as the HTTP response:

  Rack::Unreloader.new(handle_reload_errors: true){App}

== Dependency Handling

If your +app.rb+ requires a +models.rb+ file that you also want to get reloaded:

  require 'roda'
  require './models.rb'

  class App < Roda
    route do |r|
      "Hello world!"
    end
  end

You can change +app.rb+ from using:

  require './models.rb'

to using:

  Unreloader.require './models.rb'

The reason that the +Rack::Unreloader+ instance is assigned to a constant in
+config.ru+ is to make it easy to add reloadable dependencies in this way.

It's even a better idea to require this dependency manually in +config.ru+,
before requiring +app.rb+:

  require 'rack/unreloader'
  Unreloader = Rack::Unreloader.new(:subclasses=>%w'Roda Sequel::Model'){App}
  Unreloader.require './models.rb'
  Unreloader.require './app.rb'
  run Unreloader

This way, changing your +app.rb+ file will not reload your +models.rb+ file.

== Only Reload in Development Mode

In general, you are only going to want to reload code in development mode.
To simplify things, you can use rack-unreloader both in development and
production, and just not have it reload in production by setting +:reload+
to false if not in development:

  dev = ENV['RACK_ENV'] == 'development'
  require 'rack/unreloader'
  Unreloader = Rack::Unreloader.new(:subclasses=>%w'Roda Sequel::Model', :reload=>dev){App}
  Unreloader.require './models.rb'
  Unreloader.require './app.rb'
  run(dev ? Unreloader : App)

By running the App instead of Unreloader in production mode, there is no
performance penalty.  The advantage of this approach is you can use
Unreloader.require to require files regardless of whether you are using
development or production mode.

== Modules

This reloader also handles modules.  Since modules do not have superclasses,
if you are using the +:subclasses+ option to specify specific subclasses, you
need to specify the module name if you want to reload it:

  Unreloader = Rack::Unreloader.new(:subclasses=>%w'MyModule'){App}

== Dependencies

To correctly handle modules and superclasses, if a change is made to a module
or superclass, you generally want to reload all classes that include the
module or subclass the superclass, so they they pick up the change to the
module or superclass.

You can specify the file dependencies when using rack-unreloader:

  Unreloader.record_dependency('lib/module_file.rb', %w'models/mod1.rb models/mod2.rb')

If lib/module_file.rb is changed, rack-unreloader will reload models/mod1.rb
and models/mod2.rb after reloading lib/module_file.rb.

You can provide directories when requiring dependencies.  For example:

  Unreloader.record_dependency('helpers', %w'app.rb')

will make it so the addition of any ruby files to the helpers directory
will trigger a reload of +app.rb+, and future changes to any of those files
will also trigger of reload of +app.rb+.  Additionally, deleting any ruby files
in the helpers directory will also trigger a reload of +app.rb+.

You can also use a directory as the second argument:

  Unreloader.record_dependency('mod.rb', 'models')

With this, any change to +mod.rb+ will trigger a reload of all ruby files in
the models directory, even if such files are added later.

When using +record_dependencies+ with a directory, you should also call
+require+ with that directory, as opposed to specifically requiring
individual files inside the directory.

== Classes Split Into Multiple Files

Rack::Unreloader handles classes split into multiple files, where there is
a main file for the class that requires the other files that define the
class.  Assuming the main class file is +app.rb+, and other files that
make up the class are in +helpers+:

  # inside config.ru
  Unreloader.require 'app.rb'

  # inside app.rb
  Unreloader.require 'helpers'
  Unreloader.record_split_class(__FILE__, 'helpers')

If +app.rb+ is changed or any of the ruby files in +helpers+ is changed,
it will reload +app.rb+ and all of the files in +helpers+.  This makes
it so if you remove a method from one of the files in +helpers+, it will
reload the entire class so that the method is no longer defined.  Likewise,
if you delete one of the files in helpers, it will reload the class so that
the methods that were defined in that file will no longer be defined on the
class.

== Requiring

Rack::Unreloader#require is a little different than Kernel#require in that it takes
a file glob, not a normal require path.  For that reason, you must specify
the extension when requiring the file, and it will only look in the current
directory by default:

  Unreloader.require 'app.rb'

If you want to require a file in a different directory, you need to provide the
full path:

  Unreloader.require '/path/to/app.rb'

You can use the usual file globbing to load multiple files:

  Unreloader.require 'models/*.rb'

If you want to load all files in a given directory you should just give
the directory path:

  Unreloader.require 'models'

The advantage for doing this is that new files added to the directory will be
picked up automatically, and files deleted from the directory will be removed
automatically. This applies to files in subdirectories of that directory as well.

The +require+ method also supports a +:delete_hook+ option.  This option sets
a hook that is called when the related file is deleted.  This is useful if adding
a new file or reloading an existing file will handle things correctly, but
removing the file will not.  One common case for this is when you have a shared data
structure that is updated by the files, where adding or reloading the file will
update the data structure, but deleting will not, and will leave stale entries in
the data structure.  You can use the +:delete_hook+ option to remove the entries
related to the file in the data structure:

  Unreloader.require 'models', :delete_hook=>proc{|f| SHARED_HASH.delete(f)}

== Speeding Things Up

By default, +Rack::Unreloader+ uses +ObjectSpace+ before and after requiring each
file that it monitors, to see which classes and modules were defined by the
require.  This is slow for large numbers of files.  In general use it isn't an
issue as generally only a single file will be changed at a time, but it can
significantly slow down startup when all files are being loaded at the same
time.

If you want to speed things up, you can provide a block to Rack::Unreloader#require,
which will take the file name, and should return the name of the constants or array
of constants to unload.  If you do this, +Rack::Unreloader+ will no longer need
to use +ObjectSpace+, which substantially speeds up startup.  For example, if all of
your models just use a capitalized version of the filename:

  Unreloader.require('models'){|f| File.basename(f).sub(/\.rb\z/, '').capitalize}

In some cases, you may want to pass a block to require, but inside the block
decide that instead of specifying the constants, ObjectSpace should be used to
automatically determine the constants loaded. You can specify this by having the
block return the :ObjectSpace symbol.

=== Autoload

To further speed things up in development mode, or when only running a subset of
tests, it can be helpful to autoload files instead of require them, so that if
the related constants are not accessed, you don't need to pay the cost of loading
the related files.  To enable autoloading, pass the +:autoload+ option when
creating the reloader:

  Unreloader = Rack::Unreloader.new(autoload: true){App}

Then, you can call +autoload+ instead of +require+:

  Unreloader.autoload('models'){|f| File.basename(f).sub(/\.rb\z/, '').capitalize}

This will monitor the models directory for files, setting up autoloads for each
file.  After the file has been loaded, normal reloading will happen for the
file. Note that for +autoload+, a block is required because the constant names
are needed before loading the file to setup the autoload.

If the <tt>reload: false</tt> option is given when creating the reloader,
autoloads will still be setup by +autoload+, but no reloading will happen. This
can be useful when testing subsets of an application.  When testing subsets of
an application, you don't need reloading, but you can benefit from autoloading,
so parts of the application you are not testing are not loaded.

If you do not pass the +:autoload+ option when creating the reloader, then calls
to +autoload+ will implicitly be transformed to calls to +require+.  This makes
it possible to use the same +autoload+ call in all cases, and handle four
separate scenarios:

1. Autoload then reload: Fast development mode startup, loading the minimum
   number of files, but reloading if those files are changed
2. Autoload without reload: Useful for faster testing of a subset of an
   application, so the untested subsets is not loaded.
3. Require then reload: Slower development mode startup, but have entire
   application loaded before accepting requests
4. Require without reload: Normal production/testing mode with nothing autoloaded
   or reloaded

== Usage Outside Rack

While +Rack::Unreloader+ is usually in the development of rack applications,
it doesn't depend on rack.  You can just instantiate an instance of Unreloader and
use it to handle reloading in any ruby application, just by using the +require+ and
+record_dependency+ to set up the metadata, and calling +reload!+ manually to
reload the application.

== History

Rack::Unreloader was derived from Padrino's reloader.  The Padrino-specific parts
were removed, and it now requires the user manually specify which files to monitor.
It has additional features, improvements, and bug fixes.

== Caveats

Unloading constants and reloading files has a ton of corner cases that this
will not handle correctly.  If it isn't doing what you expect, add a logger:

  Rack::Unreloader.new(:logger=>Logger.new($stdout)){App}

Unloading constants causes issues whenever references to the constant are
cached anywhere instead of looking up the constant by name.  This is fairly
common, and using this library can cause a memory leak or unexpected
behavior in such a case.

Approaches that load a fresh environment for every request (or a fresh
environment anytime there are any changes) are going to be more robust than
this approach, but probably slower.  Be aware that you are trading robustness
for speed when using this library.

== Ruby Version Support

Rack::Unreloader works correctly on Ruby 1.9.2+ and JRuby 9.1+.

== License

MIT

== Maintainer

Jeremy Evans <code@jeremyevans.net>

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

jeremyevans

110 commits

celsworth

3 commits

rlue

1 commits

jeremyevans/rack-unreloader

Rack Application that reloads application files if changed, unloading constants first

110

stars

114

commits

Ruby

primary language

Aug 26, 2026

updated

README

= Rack::Unreloader

Rack::Unreloader is a code reloader for {Rack}[https://github.com/rack/rack].
It speeds up application development by automatically reloading stale code
so that you don't have to restart your dev server every time you change a file.

Unlike most other code loading libraries for Rack,
this one ensures that reloads are clean and idempotent
by _unloading_ relevant constants first, and it does so incrementally, only
reloading the files that are modified.

== Installation

  gem install rack-unreloader

== Source Code

Source code is available on GitHub at https://github.com/jeremyevans/rack-unreloader

== Basic Usage

Before:

  # config.ru

  require './app'

  run App

After:

  # config.ru

  require 'rack/unreloader'
  Unreloader = Rack::Unreloader.new{App}
  Unreloader.require './app.rb'

  run Unreloader

Now, +app.rb+ will be monitored for changes on each incoming HTTP request.

If changes are detected, +Rack::Unreloader+ will
unload all constants defined inside it and then re-require it
before proceeding with the request.

== Handling Subclasses

By default, +Rack::Unreloader+ unloads *all* constants defined in +app.rb+.
That includes third-party libraries, like +Roda+ or +JSON+ in the example below:

  # app.rb

  require 'roda'
  require 'json'

  class App < Roda
    ...
  end

Unloading these classes/modules isn't just unnecessary, it's dangerous.
If your own code depends on them, your app will throw a +NameError+ after
reloading when it tries to access them.

To reload only *subclasses* of +Roda+ (i.e. +App+), use the +:subclasses+
option:

  Rack::Unreloader.new(:subclasses=>%w'Roda'){App}

== Handling Errors During Reloading

By default, +Rack::Unreloader+ instances do not handle exceptions raised
during reloading, so that it may be rescued elsewhere (e.g. manually or by middleware).
You can use the +:handle_reload_errors+ option to send the backtrace directly to the
client as the HTTP response:

  Rack::Unreloader.new(handle_reload_errors: true){App}

== Dependency Handling

If your +app.rb+ requires a +models.rb+ file that you also want to get reloaded:

  require 'roda'
  require './models.rb'

  class App < Roda
    route do |r|
      "Hello world!"
    end
  end

You can change +app.rb+ from using:

  require './models.rb'

to using:

  Unreloader.require './models.rb'

The reason that the +Rack::Unreloader+ instance is assigned to a constant in
+config.ru+ is to make it easy to add reloadable dependencies in this way.

It's even a better idea to require this dependency manually in +config.ru+,
before requiring +app.rb+:

  require 'rack/unreloader'
  Unreloader = Rack::Unreloader.new(:subclasses=>%w'Roda Sequel::Model'){App}
  Unreloader.require './models.rb'
  Unreloader.require './app.rb'
  run Unreloader

This way, changing your +app.rb+ file will not reload your +models.rb+ file.

== Only Reload in Development Mode

In general, you are only going to want to reload code in development mode.
To simplify things, you can use rack-unreloader both in development and
production, and just not have it reload in production by setting +:reload+
to false if not in development:

  dev = ENV['RACK_ENV'] == 'development'
  require 'rack/unreloader'
  Unreloader = Rack::Unreloader.new(:subclasses=>%w'Roda Sequel::Model', :reload=>dev){App}
  Unreloader.require './models.rb'
  Unreloader.require './app.rb'
  run(dev ? Unreloader : App)

By running the App instead of Unreloader in production mode, there is no
performance penalty.  The advantage of this approach is you can use
Unreloader.require to require files regardless of whether you are using
development or production mode.

== Modules

This reloader also handles modules.  Since modules do not have superclasses,
if you are using the +:subclasses+ option to specify specific subclasses, you
need to specify the module name if you want to reload it:

  Unreloader = Rack::Unreloader.new(:subclasses=>%w'MyModule'){App}

== Dependencies

To correctly handle modules and superclasses, if a change is made to a module
or superclass, you generally want to reload all classes that include the
module or subclass the superclass, so they they pick up the change to the
module or superclass.

You can specify the file dependencies when using rack-unreloader:

  Unreloader.record_dependency('lib/module_file.rb', %w'models/mod1.rb models/mod2.rb')

If lib/module_file.rb is changed, rack-unreloader will reload models/mod1.rb
and models/mod2.rb after reloading lib/module_file.rb.

You can provide directories when requiring dependencies.  For example:

  Unreloader.record_dependency('helpers', %w'app.rb')

will make it so the addition of any ruby files to the helpers directory
will trigger a reload of +app.rb+, and future changes to any of those files
will also trigger of reload of +app.rb+.  Additionally, deleting any ruby files
in the helpers directory will also trigger a reload of +app.rb+.

You can also use a directory as the second argument:

  Unreloader.record_dependency('mod.rb', 'models')

With this, any change to +mod.rb+ will trigger a reload of all ruby files in
the models directory, even if such files are added later.

When using +record_dependencies+ with a directory, you should also call
+require+ with that directory, as opposed to specifically requiring
individual files inside the directory.

== Classes Split Into Multiple Files

Rack::Unreloader handles classes split into multiple files, where there is
a main file for the class that requires the other files that define the
class.  Assuming the main class file is +app.rb+, and other files that
make up the class are in +helpers+:

  # inside config.ru
  Unreloader.require 'app.rb'

  # inside app.rb
  Unreloader.require 'helpers'
  Unreloader.record_split_class(__FILE__, 'helpers')

If +app.rb+ is changed or any of the ruby files in +helpers+ is changed,
it will reload +app.rb+ and all of the files in +helpers+.  This makes
it so if you remove a method from one of the files in +helpers+, it will
reload the entire class so that the method is no longer defined.  Likewise,
if you delete one of the files in helpers, it will reload the class so that
the methods that were defined in that file will no longer be defined on the
class.

== Requiring

Rack::Unreloader#require is a little different than Kernel#require in that it takes
a file glob, not a normal require path.  For that reason, you must specify
the extension when requiring the file, and it will only look in the current
directory by default:

  Unreloader.require 'app.rb'

If you want to require a file in a different directory, you need to provide the
full path:

  Unreloader.require '/path/to/app.rb'

You can use the usual file globbing to load multiple files:

  Unreloader.require 'models/*.rb'

If you want to load all files in a given directory you should just give
the directory path:

  Unreloader.require 'models'

The advantage for doing this is that new files added to the directory will be
picked up automatically, and files deleted from the directory will be removed
automatically. This applies to files in subdirectories of that directory as well.

The +require+ method also supports a +:delete_hook+ option.  This option sets
a hook that is called when the related file is deleted.  This is useful if adding
a new file or reloading an existing file will handle things correctly, but
removing the file will not.  One common case for this is when you have a shared data
structure that is updated by the files, where adding or reloading the file will
update the data structure, but deleting will not, and will leave stale entries in
the data structure.  You can use the +:delete_hook+ option to remove the entries
related to the file in the data structure:

  Unreloader.require 'models', :delete_hook=>proc{|f| SHARED_HASH.delete(f)}

== Speeding Things Up

By default, +Rack::Unreloader+ uses +ObjectSpace+ before and after requiring each
file that it monitors, to see which classes and modules were defined by the
require.  This is slow for large numbers of files.  In general use it isn't an
issue as generally only a single file will be changed at a time, but it can
significantly slow down startup when all files are being loaded at the same
time.

If you want to speed things up, you can provide a block to Rack::Unreloader#require,
which will take the file name, and should return the name of the constants or array
of constants to unload.  If you do this, +Rack::Unreloader+ will no longer need
to use +ObjectSpace+, which substantially speeds up startup.  For example, if all of
your models just use a capitalized version of the filename:

  Unreloader.require('models'){|f| File.basename(f).sub(/\.rb\z/, '').capitalize}

In some cases, you may want to pass a block to require, but inside the block
decide that instead of specifying the constants, ObjectSpace should be used to
automatically determine the constants loaded. You can specify this by having the
block return the :ObjectSpace symbol.

=== Autoload

To further speed things up in development mode, or when only running a subset of
tests, it can be helpful to autoload files instead of require them, so that if
the related constants are not accessed, you don't need to pay the cost of loading
the related files.  To enable autoloading, pass the +:autoload+ option when
creating the reloader:

  Unreloader = Rack::Unreloader.new(autoload: true){App}

Then, you can call +autoload+ instead of +require+:

  Unreloader.autoload('models'){|f| File.basename(f).sub(/\.rb\z/, '').capitalize}

This will monitor the models directory for files, setting up autoloads for each
file.  After the file has been loaded, normal reloading will happen for the
file. Note that for +autoload+, a block is required because the constant names
are needed before loading the file to setup the autoload.

If the <tt>reload: false</tt> option is given when creating the reloader,
autoloads will still be setup by +autoload+, but no reloading will happen. This
can be useful when testing subsets of an application.  When testing subsets of
an application, you don't need reloading, but you can benefit from autoloading,
so parts of the application you are not testing are not loaded.

If you do not pass the +:autoload+ option when creating the reloader, then calls
to +autoload+ will implicitly be transformed to calls to +require+.  This makes
it possible to use the same +autoload+ call in all cases, and handle four
separate scenarios:

1. Autoload then reload: Fast development mode startup, loading the minimum
   number of files, but reloading if those files are changed
2. Autoload without reload: Useful for faster testing of a subset of an
   application, so the untested subsets is not loaded.
3. Require then reload: Slower development mode startup, but have entire
   application loaded before accepting requests
4. Require without reload: Normal production/testing mode with nothing autoloaded
   or reloaded

== Usage Outside Rack

While +Rack::Unreloader+ is usually in the development of rack applications,
it doesn't depend on rack.  You can just instantiate an instance of Unreloader and
use it to handle reloading in any ruby application, just by using the +require+ and
+record_dependency+ to set up the metadata, and calling +reload!+ manually to
reload the application.

== History

Rack::Unreloader was derived from Padrino's reloader.  The Padrino-specific parts
were removed, and it now requires the user manually specify which files to monitor.
It has additional features, improvements, and bug fixes.

== Caveats

Unloading constants and reloading files has a ton of corner cases that this
will not handle correctly.  If it isn't doing what you expect, add a logger:

  Rack::Unreloader.new(:logger=>Logger.new($stdout)){App}

Unloading constants causes issues whenever references to the constant are
cached anywhere instead of looking up the constant by name.  This is fairly
common, and using this library can cause a memory leak or unexpected
behavior in such a case.

Approaches that load a fresh environment for every request (or a fresh
environment anytime there are any changes) are going to be more robust than
this approach, but probably slower.  Be aware that you are trading robustness
for speed when using this library.

== Ruby Version Support

Rack::Unreloader works correctly on Ruby 1.9.2+ and JRuby 9.1+.

== License

MIT

== Maintainer

Jeremy Evans <code@jeremyevans.net>

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

jeremyevans

110 commits

celsworth

3 commits

rlue

1 commits

Languages

Ruby

100.0%