Skip to content

Instantly share code, notes, and snippets.

@justinfrench
Created July 15, 2025 06:01
Show Gist options
  • Save justinfrench/9b541269762f62f6d109b4a3a255558e to your computer and use it in GitHub Desktop.
Save justinfrench/9b541269762f62f6d109b4a3a255558e to your computer and use it in GitHub Desktop.
Develop a Ruby Gem locally without having to restart the Rails server

This is the result of a fair bit of testing and back-and-forth with ChatGPT to help me:

  • Do local development on a gem for use with Rails apps
  • Add the gem to a local Rails app I'm working on for testing
  • Make changes to the gem
  • See those changes reflected in the Rails app without having to restart the server each time

The solution is in two parts:

  • Integrate Zeitwerk into the gem (not just making the filenaming compatible)
  • Integrate the gem into the host Rails app's reloaders (but only if the Rails app is in development mode, and if the gem was added to the Gemfile with a path: to the local copy of the gem

As an added bonus, the developer experience for the gem is pretty simple too:

  • No need to manually require all the files in your gem (they're autoloaded when called)
  • Classes and Modules are autoloaded in things like bin/console
  • Classes and Modules are autoloaded in your spec or test environment
# my_rails_app/Gemfile
source "https://rubygems.org"
# ...
# Load the gem from disk in the same direction as my_rails_app
gem "my_gem", path: "../my_gem"
# my_gem/my_gem.gemspec
require_relative "lib/my_gem/version"
Gem::Specification.new do |spec|
# ...
# Add Rails and Zeitwerk as dependenices
spec.add_runtime_dependency "rails", ">= 7.2"
spec.add_runtime_dependency "zeitwerk", "~> 2.6"
end
# my_gem/lib/my_gem.rb
require "zeitwerk"
module MyGem
Loader = Zeitwerk::Loader.for_gem
Loader.enable_reloading
Loader.setup
end
require "my_gem/railtie" if defined?(Rails::Railtie)
# my_gem/lib/my_gem/railtie.rb
require "rails/railtie"
require "bundler"
module MyGem
# This code exists purely to support local development of the MyGem gem
# without having to restart the host Rails app's server after each change.
#
# When the host app is in development *and* MyGem was loaded from a local
# `path:` source, push the gem’s lib/my_gem directory into Rails’ main
# autoloader so the gem is autoloaded and hot-reloaded like app/ code; skip
# this hook for packaged (rubygems/git) installs and for prod/test envs.
class Railtie < Rails::Railtie
initializer "my_gem.reloader", if: :hot_reload_needed? do |app|
# Ensure the gem loader is reload-capable
MyGem::Loader.enable_reloading
# Watch every dir managed by the gem loader
watch_dirs = MyGem::Loader.dirs.to_h { |d| [d.to_s, %w[rb]] }
reloader = app.config.file_watcher.new([], watch_dirs) do
MyGem::Loader.reload
end
# Let Rails run the checker before each request
app.reloaders << reloader
ActiveSupport::Reloader.to_prepare { reloader.execute_if_updated }
end
# Returns true in environments with enable_reloading (e.g. development),
# and the MyGem gem has been loaded in the Gemfile with a local `path:`.
def self.hot_reload_needed?(app = Rails.application)
app.config.enable_reloading &&
Gem.loaded_specs.key?("my_gem") &&
Gem.loaded_specs["my_gem"].source.is_a?(Bundler::Source::Path)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment