When a Rails project grows, I often notice the need to refactor domain logic into a separate module, isolated from Rails framework.

The isolated module will still stay in the main Rails app codebase, but can be easily packaged as a Ruby gem, tested separately, and used in other related applications.


During the refactoring, we want to:

  1. Use require to load the module like a normal gem. If possible, no dependency on Rails autoload features such asrequire_dependency and autoload_paths.

  2. Edit the module without restarting server during development. In other words, we need to find a way to reload the module on every request.

Reload require files

After some research, I found a working solution by Timothy Cardenas:

ActionDispatch::Callbacks.to_prepare do
  if Object.const_defined?("Module1")
    Object.send(:remove_const, "Module1")
  $".delete_if {|s| s.include?('module1') }
  require 'module1'

What it does is, before each request,

  1. Unload the top-level module.
  2. Un-require all required files from the module.
  3. Re-require the top-level module.

An extra step in Rails 3.2

In Rails 3.2, ActionDispatch::Callbacks.to_prepare has a slightly different behavior. It will run before a request only if a watchable file is modified.

You need to specify your own watchable files:

# watch all .rb files recursively under modules/module1/ dir
config.watchable_dirs['modules/module1'] = [:rb]

Introduce require_reloader gem

Before bundling the solution into a gem, I did a search on RubyGems.org and found Colin Young’s gem_reloader. It’s based on Timy’s solution as well. I forked it and started playing around.

In the end, I made some major changes to include Rails 3.2 support, some fixes and new features.

So I decided to release it as a new Ruby gem: require_reloader.

# config/environments/development.rb
YourApp::Application.configure do
  RequireReloader.watch :module1,
    path: 'modules/module1'

Currently it supports Rails 3, including 3.1 and 3.2.

If you are working on something similar, looking forward for your feedbacks and pull requests. It is not tested on Rails 2 yet, so pull requests are definitely welcomed!