0.03
Low commit activity in last 3 years
A long-lived project that still receives updates
Rack::Unreloader is a rack middleware that reloads application files when it detects changes, unloading constants defined in those files before reloading.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
 Dependencies
 Project Readme

Rack::Unreloader¶ ↑

Rack::Unreloader is a code reloader for 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 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 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.

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>