Class Camping::Reloader
In: lib/camping/reloader.rb
Parent: Object

The Camping Reloader

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.

Wrapping Your Apps

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.

Methods

Attributes

klass  [RW] 
mount  [RW] 
mtime  [RW] 
requires  [RW] 

Public Class methods

[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

[Source]

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

[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.

[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

Public Instance methods

Find the application, based on the script name.

[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.

[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.

[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.

[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.

[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.

[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.

[Source]

     # File lib/camping/reloader.rb, line 124
124:     def view_source
125:         File.read(@script)
126:     end

[Validate]