Reloadable Ruby applications

September 13, 2007

Reloading the source code to your application while it is still running is a tremendous time saver during development. You can tweak formulas, fix typos, even refactor code without losing the program state.

Below is how I implemented this feature for Luz. I’m not convinced it is the most robust implementation, but it has worked well so far.

module Kernel
	$source_file_modification_times ||= {}

	# a new 'require' supporting multiple files
	alias_method :single_require, :require
	def require(*list)
		[*list].each { |file|
			# Was the file successfully 'require'd?
			if single_require(file)
				# Grab latest file name (which now includes the .rb) from $LOADED_FEATURES (list of all 'require'd files)
				file = $LOADED_FEATURES.last

				# Find the full file path that was loaded by searching the path the way Ruby does
				filepath = $LOAD_PATH.find { |path| File.exist?("#{path}/#{file}") } + "/#{file}"

				# Add to list
				$source_file_modification_times[filepath] = File.new(filepath).mtime
			end
		}
	end

	def reload_if_newer(filepath)
		mtime = File.new(filepath).mtime

		# Do we already have the current version?
		return false if mtime == $source_file_modification_times[filepath]

		begin
			load filepath
			$source_file_modification_times[filepath] = mtime
			return true
		rescue Exception => e
			# report exception to user somehow...
			return false
		end
	end

	def reload_modified_source_files
		$source_file_modification_times.each_key { |filepath| reload_if_newer(filepath) }
	end
end

Then simply create a keyboard shortcut (I use Ctrl-Shift-R) that calls reload_modified_source_files. Because it only reloads files that have changed on disk, it’s quite fast.

The exception handling around the “load filepath” statement prevents syntax errors from bringing down the application.

(Note that this also adds multi-file “require”, which I discussed earlier.)