Class | Camping::Reloader |
In: |
lib/camping/reloader.rb
|
Parent: | Object |
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.
klass | [RW] | |
mount | [RW] | |
mtime | [RW] | |
requires | [RW] |
# 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
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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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.
# 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