
Camping apps are generally small and predictable. Many Camping apps are contained within a single file. Larger apps are split into a handful of other Ruby libraries within the same directory.
Since Camping apps (and their dependencies) are loaded with Ruby‘s require method, there is a record of them in $LOADED_FEATURES. Which leaves a perfect space for this class to manage auto-reloading an app if any of its immediate dependencies changes.
Since bin/camping and the Camping::FastCGI class already use the Reloader, you probably don‘t need to hack it on your own. But, if you‘re rolling your own situation, here‘s how.
Rather than this:
require 'yourapp'
Use this:
require 'camping/reloader'
Camping::Reloader.new('/path/to/yourapp.rb')
The reloader will take care of requiring the app and monitoring all files for alterations.

[ show source ]
# File lib/camping/reloader.rb, line 135
135: def conditional_connect
136: # If database models are present, `autoload?` will return nil.
137: unless Camping::Models.autoload? :Base
138: require 'logger'
139: require 'camping/session'
140: Camping::Models::Base.establish_connection @database if @database
141:
142: case @log
143: when Logger
144: Camping::Models::Base.logger = @log
145: when String
146: Camping::Models::Base.logger = Logger.new(@log == "-" ? STDOUT : @log)
147: end
148:
149: begin
150: Camping::Models::Session.create_schema
151: rescue MissingSourceFile
152: puts "** #$0 stopped: SQLite3 not found, please install."
153: puts "** See http://code.whytheluckystiff.net/camping/wiki/BeAlertWhenOnSqlite3 for instructions."
154: exit
155: end
156:
157: if @database and @database[:adapter] == 'sqlite3'
158: begin
159: require 'sqlite3_api'
160: rescue LoadError
161: puts "!! Your SQLite3 adapter isn't a compiled extension."
162: abort "!! Please check out http://code.whytheluckystiff.net/camping/wiki/BeAlertWhenOnSqlite3 for tips."
163: end
164: end
165: end
166: end

[ show source ]
# File lib/camping/reloader.rb, line 129
129: def database=(db)
130: @database = db
131: end

[ show source ]
# File lib/camping/reloader.rb, line 132
132: def log=(log)
133: @log = log
134: end

Creates the reloader, assigns a script to it and initially loads the application. Pass in the full path to the script, otherwise the script will be loaded relative to the current working directory.
[ show source ]
# File lib/camping/reloader.rb, line 36
36: def initialize(script)
37: @script = File.expand_path(script)
38: @mount = File.basename(script, '.rb')
39: @requires = nil
40: load_app
41: end

Find the application, based on the script name.
[ show source ]
# File lib/camping/reloader.rb, line 44
44: def find_app(title)
45: @klass = Object.const_get(Object.constants.grep(/^#{title}$/i)[0]) rescue nil
46: end

Loads (or reloads) the application. The reloader will take care of calling this for you. You can certainly call it yourself if you feel it‘s warranted.
[ show source ]
# File lib/camping/reloader.rb, line 57
57: def load_app
58: title = File.basename(@script)[/^([\w_]+)/,1].gsub /_/,''
59: begin
60: all_requires = $LOADED_FEATURES.dup
61: load @script
62: @requires = ($LOADED_FEATURES - all_requires).select do |req|
63: req.index(File.basename(@script) + "/") == 0 || req.index(title + "/") == 0
64: end
65: rescue Exception => e
66: puts "!! trouble loading #{title}: [#{e.class}] #{e.message}"
67: puts e.backtrace.join("\n")
68: find_app title
69: remove_app
70: return
71: end
72:
73: @mtime = mtime
74: find_app title
75: unless @klass and @klass.const_defined? :C
76: puts "!! trouble loading #{title}: not a Camping app, no #{title.capitalize} module found"
77: remove_app
78: return
79: end
80:
81: Reloader.conditional_connect
82: @klass.create if @klass.respond_to? :create
83: @klass
84: end

The timestamp of the most recently modified app dependency.
[ show source ]
# File lib/camping/reloader.rb, line 87
87: def mtime
88: ((@requires || []) + [@script]).map do |fname|
89: fname = fname.gsub(/^#{Regexp::quote File.dirname(@script)}\//, '')
90: begin
91: File.mtime(File.join(File.dirname(@script), fname))
92: rescue Errno::ENOENT
93: remove_app
94: @mtime
95: end
96: end.max
97: end

Conditional reloading of the app. This gets called on each request and only reloads if the modification times on any of the files is updated.
[ show source ]
# File lib/camping/reloader.rb, line 101
101: def reload_app
102: return if @klass and @mtime and mtime <= @mtime
103:
104: if @requires
105: @requires.each { |req| $LOADED_FEATURES.delete(req) }
106: end
107: k = @klass
108: Object.send :remove_const, k.name if k
109: load_app
110: end

If the file isn‘t found, if we need to remove the app from the global namespace, this will be sure to do so and set @klass to nil.
[ show source ]
# File lib/camping/reloader.rb, line 50
50: def remove_app
51: Object.send :remove_const, @klass.name if @klass
52: @klass = nil
53: end

Conditionally reloads (using reload_app.) Then passes the request through to the wrapped Camping app.
[ show source ]
# File lib/camping/reloader.rb, line 114
114: def run(*a)
115: reload_app
116: if @klass
117: @klass.run(*a)
118: else
119: Camping.run(*a)
120: end
121: end

Returns source code for the main script in the application.
[ show source ]
# File lib/camping/reloader.rb, line 124
124: def view_source
125: File.read(@script)
126: end