rack-unreloader 0.9.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +10 -0
- data/README.rdoc +32 -5
- data/lib/rack/unreloader.rb +59 -24
- data/spec/unreloader_spec.rb +140 -26
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e78994d15fb3130a66a1b0c4cddd957bbc7ed5a
|
4
|
+
data.tar.gz: 3151916048c0f97a461e6bd1a3e5d06cb5945943
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 184ff5f426d8c587377e045954d2968b562dd8bd3e823c3968efda3556104a33b8df68fafb3aec4269770f7014973e46382302756fddeaf1c99191a39f80dbb9
|
7
|
+
data.tar.gz: 0877f19d6d65db719317c4d52a0cc1acb283f68b121af8a54b296c1e97cf7303d76bf59d33d293e9e3a06101d38add35a70fb4fe99d356877ead32d3bf3dd0b2
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
= 1.0.0 (2014-08-12)
|
2
|
+
|
3
|
+
* Ignore anonymous classes/modules (jeremyevans)
|
4
|
+
|
5
|
+
* Log when attempting to remove a constant that doesn't exist (jeremyevans)
|
6
|
+
|
7
|
+
* Allow specifying constants to remove via a block to require, instead of using ObjectSpace (jeremyevans)
|
8
|
+
|
9
|
+
* Don't unload constants defined in parents when loading dependencies (jeremyevans)
|
10
|
+
|
1
11
|
= 0.9.0 (2014-08-04)
|
2
12
|
|
3
13
|
* Initial public release
|
data/README.rdoc
CHANGED
@@ -35,7 +35,7 @@ With a basic +config.ru+ like this:
|
|
35
35
|
|
36
36
|
Change +config.ru+ to:
|
37
37
|
|
38
|
-
require 'rack
|
38
|
+
require 'rack/unreloader'
|
39
39
|
Unreloader = Rack::Unreloader.new{App}
|
40
40
|
require 'roda'
|
41
41
|
Unreloader.require './app.rb'
|
@@ -53,7 +53,7 @@ code requires the +roda+ library normally before requiring +app.rb+ using
|
|
53
53
|
However, if +app.rb+ requires more than a single file, it is more
|
54
54
|
practical to tell <tt>Rack::Unreloader</tt> to only unload specific subclasses:
|
55
55
|
|
56
|
-
require 'rack
|
56
|
+
require 'rack/unreloader'
|
57
57
|
Unreloader = Rack::Unreloader.new(:subclasses=>%w'Roda'){App}
|
58
58
|
Unreloader.require './app.rb'
|
59
59
|
run Unreloader
|
@@ -89,7 +89,7 @@ The reason that the <tt>Rack::Unreloader</tt> instance is assigned to a constant
|
|
89
89
|
It's even a better idea to require this dependency manually in +config.ru+,
|
90
90
|
before requiring +app.rb+:
|
91
91
|
|
92
|
-
require 'rack
|
92
|
+
require 'rack/unreloader'
|
93
93
|
Unreloader = Rack::Unreloader.new(:subclasses=>%w'Roda Sequel::Model'){App}
|
94
94
|
Unreloader.require './models.rb'
|
95
95
|
Unreloader.require './app.rb'
|
@@ -104,7 +104,7 @@ Assuming you use +RACK_ENV+ to determine development mode, you can change
|
|
104
104
|
+config.ru+ to:
|
105
105
|
|
106
106
|
if ENV['RACK_ENV'] == 'development'
|
107
|
-
require 'rack
|
107
|
+
require 'rack/unreloader'
|
108
108
|
Unreloader = Rack::Unreloader.new{App}
|
109
109
|
Unreloader.require './models.rb'
|
110
110
|
Unreloader.require './app.rb'
|
@@ -128,6 +128,10 @@ need to specify the module name if you want to reload it:
|
|
128
128
|
|
129
129
|
Unreloader = Rack::Unreloader.new(:subclasses=>%w'MyModule'){App}
|
130
130
|
|
131
|
+
Note that if the modules defined are included in any classes, this doesn't
|
132
|
+
uninclude them (ruby doesn't support that), so there won't be a change to
|
133
|
+
that class until that class is reloaded as well.
|
134
|
+
|
131
135
|
== Requiring
|
132
136
|
|
133
137
|
Rack::Unreloader#require is a little different than require in that it takes
|
@@ -146,11 +150,29 @@ You can use the usual file globbing:
|
|
146
150
|
|
147
151
|
Unreloader.require 'models/*.rb'
|
148
152
|
|
153
|
+
== Speeding Things Up
|
154
|
+
|
155
|
+
By default, <tt>Rack::Unreloader</tt> uses +ObjectSpace+ before and after requiring each
|
156
|
+
file that it monitors, to see which classes and modules were defined by the
|
157
|
+
require. This is slow for large numbers of files. In general use it isn't an
|
158
|
+
issue as general only a single file will be changed at a time, but it can
|
159
|
+
significantly slow down startup when all files are being loaded at the same
|
160
|
+
time.
|
161
|
+
|
162
|
+
If you want to speed things up, you can provide a block to Rack::Unreloader#require,
|
163
|
+
which will take the file name, and should return the name of the constants or array
|
164
|
+
of constants to unload. If you do this, <tt>Rack::Unreloader</tt> will no longer need
|
165
|
+
to use +ObjectSpace+, which substantially speeds up startup. For example, if all of
|
166
|
+
your models just use a capitalized version of the filename:
|
167
|
+
|
168
|
+
Unreloader.require('models/*.rb'){|f| File.basename(f).sub(/\.rb\z/, '').capitalize}
|
169
|
+
|
149
170
|
== History
|
150
171
|
|
151
172
|
Rack::Unreloader was derived from Padrino's reloader. It is significantly smaller
|
152
173
|
as it cuts out a lot of Padrino-specific code, and it forces the user to manually
|
153
|
-
specify what files to monitor.
|
174
|
+
specify what files to monitor. It has additional features, improvements, and bug
|
175
|
+
fixes.
|
154
176
|
|
155
177
|
== Caveats
|
156
178
|
|
@@ -168,6 +190,11 @@ environment anytime there are any changes) are going to be more robust than
|
|
168
190
|
this approach, but probably slower. Be aware that you are trading robustness
|
169
191
|
for speed when using this library.
|
170
192
|
|
193
|
+
== Implementation Support
|
194
|
+
|
195
|
+
Rack::Unreloader works correctly on Ruby 1.8.7+ and Rubinius. It only works on
|
196
|
+
JRuby if you use a proc to specify the constants to unload.
|
197
|
+
|
171
198
|
== License
|
172
199
|
|
173
200
|
MIT
|
data/lib/rack/unreloader.rb
CHANGED
@@ -21,6 +21,11 @@ module Rack
|
|
21
21
|
# with values being the last modified time (or nil if the file has not yet been loaded).
|
22
22
|
@monitor_files = {}
|
23
23
|
|
24
|
+
# Hash of procs returning constants defined in files, keyed by absolute path
|
25
|
+
# of file name. If there is no proc, must call ObjectSpace before and after
|
26
|
+
# loading files to detect changes, which is slower.
|
27
|
+
@constants_defined = {}
|
28
|
+
|
24
29
|
# Hash keyed by absolute path of file name, storing constants and other
|
25
30
|
# filenames that the key loads. Values should be hashes with :constants
|
26
31
|
# and :features keys, and arrays of values.
|
@@ -59,7 +64,7 @@ module Rack
|
|
59
64
|
|
60
65
|
# Require the given dependencies, monitoring them for changes.
|
61
66
|
# Paths should be a file glob or an array of file globs.
|
62
|
-
def require_dependencies(paths)
|
67
|
+
def require_dependencies(paths, &block)
|
63
68
|
options = {:cyclic => true}
|
64
69
|
error = nil
|
65
70
|
|
@@ -71,6 +76,7 @@ module Rack
|
|
71
76
|
uniq.
|
72
77
|
each do |file|
|
73
78
|
|
79
|
+
@constants_defined[file] = block
|
74
80
|
@monitor_files[file] = nil
|
75
81
|
begin
|
76
82
|
safe_load(file, options)
|
@@ -94,8 +100,8 @@ module Rack
|
|
94
100
|
return unless @monitor_files.has_key?(file)
|
95
101
|
return unless options[:force] || file_changed?(file)
|
96
102
|
|
97
|
-
prepare(file) # might call #safe_load recursively
|
98
103
|
log "#{@monitor_files[file] ? 'Reloading' : 'Loading'} #{file}"
|
104
|
+
prepare(file) # might call #safe_load recursively
|
99
105
|
begin
|
100
106
|
require(file)
|
101
107
|
commit(file)
|
@@ -115,12 +121,16 @@ module Rack
|
|
115
121
|
base.send :remove_const, object
|
116
122
|
log "Removed constant #{const}"
|
117
123
|
rescue NameError
|
124
|
+
log "Error removing constant: #{const}"
|
118
125
|
end
|
119
126
|
|
120
127
|
# Remove a feature if it is being monitored for reloading, so it
|
121
128
|
# can be required again.
|
122
129
|
def remove_feature(file)
|
123
|
-
|
130
|
+
if @monitor_files.has_key?(file)
|
131
|
+
$LOADED_FEATURES.delete(file)
|
132
|
+
log "Removed feature #{file}"
|
133
|
+
end
|
124
134
|
end
|
125
135
|
|
126
136
|
# Unload all reloadable constants and features, and clear the list
|
@@ -128,7 +138,6 @@ module Rack
|
|
128
138
|
def clear!
|
129
139
|
@files.keys.each do |file|
|
130
140
|
remove(file)
|
131
|
-
remove_feature(file)
|
132
141
|
end
|
133
142
|
@monitor_files = {}
|
134
143
|
@old_entries = {}
|
@@ -138,49 +147,75 @@ module Rack
|
|
138
147
|
# by the file.
|
139
148
|
def remove(name)
|
140
149
|
file = @files[name] || return
|
141
|
-
file[:constants]
|
150
|
+
remove_constants(name){file[:constants]}
|
142
151
|
file[:features].each{|feature| remove_feature(feature)}
|
143
152
|
@files.delete(name)
|
153
|
+
remove_feature(name) if $LOADED_FEATURES.include?(name)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Remove constants defined in file. Uses the stored block if there is
|
157
|
+
# one for the file name, or the given block.
|
158
|
+
def remove_constants(name)
|
159
|
+
constants = if pr = @constants_defined[name]
|
160
|
+
Array(pr.call(name))
|
161
|
+
else
|
162
|
+
yield
|
163
|
+
end
|
164
|
+
|
165
|
+
if constants
|
166
|
+
constants.each{|constant| remove_constant(constant)}
|
167
|
+
end
|
144
168
|
end
|
145
169
|
|
146
170
|
# Store the currently loaded classes and features, so in case of an error
|
147
171
|
# this state can be rolled back to.
|
148
172
|
def prepare(name)
|
149
173
|
file = remove(name)
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
174
|
+
@old_entries[name] = {:features => monitored_features}
|
175
|
+
|
176
|
+
unless @constants_defined[name]
|
177
|
+
@old_entries[name][:constants] = all_classes
|
178
|
+
end
|
155
179
|
end
|
156
180
|
|
157
181
|
# Commit the changed state after requiring the the file, recording the new
|
158
182
|
# classes and features added by the file.
|
159
183
|
def commit(name)
|
160
|
-
entry = {
|
161
|
-
|
162
|
-
:
|
163
|
-
|
184
|
+
entry = {:features => monitored_features - @old_entries[name][:features] - [name]}
|
185
|
+
unless constants_defined = @constants_defined[name]
|
186
|
+
entry[:constants] = new_classes(@old_entries[name][:constants])
|
187
|
+
end
|
188
|
+
|
164
189
|
@files[name] = entry
|
165
190
|
@old_entries.delete(name)
|
166
191
|
@monitor_files[name] = modified_at(name)
|
192
|
+
|
193
|
+
unless constants_defined
|
194
|
+
log("New classes in #{name}: #{entry[:constants].to_a.join(' ')}") unless entry[:constants].empty?
|
195
|
+
end
|
196
|
+
log("New features in #{name}: #{entry[:features].to_a.join(' ')}") unless entry[:features].empty?
|
167
197
|
end
|
168
198
|
|
169
199
|
# Rollback the changes made by requiring the file, restoring the previous state.
|
170
200
|
def rollback(name)
|
171
|
-
new_classes(@old_entries[name][:constants])
|
201
|
+
remove_constants(name){new_classes(@old_entries[name][:constants])}
|
172
202
|
@old_entries.delete(name)
|
173
203
|
end
|
174
204
|
|
175
205
|
private
|
176
206
|
|
207
|
+
# The current loaded features that are being monitored
|
208
|
+
def monitored_features
|
209
|
+
Set.new($LOADED_FEATURES) & @monitor_files.keys
|
210
|
+
end
|
211
|
+
|
177
212
|
# Return a set of all classes in the ObjectSpace.
|
178
213
|
def all_classes
|
179
214
|
rs = Set.new
|
180
215
|
|
181
|
-
ObjectSpace.each_object(Module).each do |
|
182
|
-
if
|
183
|
-
rs <<
|
216
|
+
ObjectSpace.each_object(Module).each do |mod|
|
217
|
+
if !mod.name.to_s.empty? && monitored_module?(mod)
|
218
|
+
rs << mod
|
184
219
|
end
|
185
220
|
end
|
186
221
|
|
@@ -189,20 +224,20 @@ module Rack
|
|
189
224
|
|
190
225
|
# Return whether the given klass is a monitored class that could
|
191
226
|
# be unloaded.
|
192
|
-
def
|
227
|
+
def monitored_module?(mod)
|
193
228
|
@classes.any? do |c|
|
194
229
|
c = constantize(c) rescue false
|
195
230
|
|
196
|
-
if
|
231
|
+
if mod.is_a?(Class)
|
197
232
|
# Reload the class if it is a subclass if the current class
|
198
|
-
(
|
233
|
+
(mod < c) rescue false
|
199
234
|
elsif c == Object
|
200
235
|
# If reloading for all classes, reload for all modules as well
|
201
236
|
true
|
202
237
|
else
|
203
238
|
# Otherwise, reload only if the module matches exactly, since
|
204
239
|
# modules don't have superclasses.
|
205
|
-
|
240
|
+
mod == c
|
206
241
|
end
|
207
242
|
end
|
208
243
|
end
|
@@ -257,8 +292,8 @@ module Rack
|
|
257
292
|
end
|
258
293
|
|
259
294
|
# Add a file glob or array of file globs to monitor for changes.
|
260
|
-
def require(depends)
|
261
|
-
@reloader.require_dependencies(depends)
|
295
|
+
def require(depends, &block)
|
296
|
+
@reloader.require_dependencies(depends, &block)
|
262
297
|
end
|
263
298
|
end
|
264
299
|
end
|
data/spec/unreloader_spec.rb
CHANGED
@@ -35,20 +35,23 @@ describe Rack::Unreloader do
|
|
35
35
|
@logger
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
return @ru if @ru
|
38
|
+
def base_ru(opts={})
|
40
39
|
block = opts[:block] || proc{App}
|
41
40
|
@ru = Rack::Unreloader.new({:logger=>logger, :cooldown=>0}.merge(opts), &block)
|
42
41
|
@ru.reloader.extend ModifiedAt
|
43
42
|
Object.const_set(:RU, @ru)
|
43
|
+
end
|
44
|
+
|
45
|
+
def ru(opts={})
|
46
|
+
return @ru if @ru
|
47
|
+
base_ru(opts)
|
44
48
|
update_app(opts[:code]||code(1))
|
45
|
-
yield if block_given?
|
46
49
|
@ru.require 'spec/app.rb'
|
47
50
|
@ru
|
48
51
|
end
|
49
52
|
|
50
53
|
def log_match(*logs)
|
51
|
-
logs.length == @logger.length
|
54
|
+
logs.length.should == @logger.length
|
52
55
|
logs.zip(@logger).each{|l, log| l.is_a?(String) ? log.should == l : log.should =~ l}
|
53
56
|
end
|
54
57
|
|
@@ -65,26 +68,41 @@ describe Rack::Unreloader do
|
|
65
68
|
Dir['spec/app*.rb'].each{|f| File.delete(f)}
|
66
69
|
end
|
67
70
|
|
68
|
-
it "
|
71
|
+
it "should unload constants contained in file and reload file if file changes" do
|
69
72
|
ru.call({}).should == [1]
|
70
73
|
update_app(code(2))
|
71
74
|
ru.call({}).should == [2]
|
72
|
-
log_match
|
75
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
76
|
+
%r{\ANew classes in .*spec/app\.rb: App\z},
|
77
|
+
%r{\AReloading.*spec/app\.rb\z},
|
78
|
+
"Removed constant App",
|
79
|
+
%r{\ARemoved feature .*/spec/app.rb\z},
|
80
|
+
%r{\ANew classes in .*spec/app\.rb: App\z}
|
73
81
|
end
|
74
82
|
|
75
|
-
it "
|
83
|
+
it "should pickup files added as dependencies" do
|
76
84
|
ru.call({}).should == [1]
|
77
85
|
update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
|
78
86
|
update_app("class App2; def self.call(env) @a end; @a ||= []; @a << 3; end", 'spec/app2.rb')
|
79
87
|
ru.call({}).should == [[2], [3]]
|
80
88
|
update_app("class App2; def self.call(env) @a end; @a ||= []; @a << 4; end", 'spec/app2.rb')
|
81
89
|
ru.call({}).should == [[2], [4]]
|
82
|
-
|
83
|
-
|
84
|
-
%r{\
|
90
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
91
|
+
%r{\ANew classes in .*spec/app\.rb: App\z},
|
92
|
+
%r{\AReloading.*spec/app\.rb\z},
|
93
|
+
"Removed constant App",
|
94
|
+
%r{\ARemoved feature .*/spec/app.rb\z},
|
95
|
+
%r{\ALoading.*spec/app2\.rb\z},
|
96
|
+
%r{\ANew classes in .*spec/app2\.rb: App2\z},
|
97
|
+
%r{\ANew classes in .*spec/app\.rb: (App App2|App2 App)\z},
|
98
|
+
%r{\ANew features in .*spec/app\.rb: .*spec/app2\.rb\z},
|
99
|
+
%r{\AReloading.*spec/app2\.rb\z},
|
100
|
+
"Removed constant App2",
|
101
|
+
%r{\ARemoved feature .*/spec/app2.rb\z},
|
102
|
+
%r{\ANew classes in .*spec/app2\.rb: App2\z}
|
85
103
|
end
|
86
104
|
|
87
|
-
it "
|
105
|
+
it "should support :subclasses option and only unload subclasses of given class" do
|
88
106
|
ru(:subclasses=>'App').call({}).should == [1]
|
89
107
|
update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
|
90
108
|
update_app("class App2 < App; def self.call(env) @a end; @a ||= []; @a << 3; end", 'spec/app2.rb')
|
@@ -92,54 +110,150 @@ describe Rack::Unreloader do
|
|
92
110
|
update_app("class App2 < App; def self.call(env) @a end; @a ||= []; @a << 4; end", 'spec/app2.rb')
|
93
111
|
ru.call({}).should == [[1, 2], [4]]
|
94
112
|
update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
|
95
|
-
log_match
|
96
|
-
%r{\
|
113
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
114
|
+
%r{\AReloading.*spec/app\.rb\z},
|
115
|
+
%r{\ARemoved feature .*/spec/app.rb\z},
|
116
|
+
%r{\ALoading.*spec/app2\.rb\z},
|
117
|
+
%r{\ANew classes in .*spec/app2\.rb: App2\z},
|
118
|
+
%r{\ANew classes in .*spec/app\.rb: App2\z},
|
119
|
+
%r{\ANew features in .*spec/app\.rb: .*spec/app2\.rb\z},
|
120
|
+
%r{\AReloading.*spec/app2\.rb\z},
|
121
|
+
"Removed constant App2",
|
122
|
+
%r{\ARemoved feature .*/spec/app2.rb\z},
|
123
|
+
%r{\ANew classes in .*spec/app2\.rb: App2\z}
|
97
124
|
end
|
98
125
|
|
99
|
-
it "
|
126
|
+
it "should log invalid constant names in :subclasses options" do
|
100
127
|
ru(:subclasses=>%w'1 Object').call({}).should == [1]
|
101
128
|
logger.uniq!
|
102
|
-
log_match
|
129
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
130
|
+
'"1" is not a valid constant name!',
|
131
|
+
%r{\ANew classes in .*spec/app\.rb: App\z}
|
103
132
|
end
|
104
133
|
|
105
|
-
it "
|
134
|
+
it "should unload modules before reloading similar to classes" do
|
106
135
|
ru(:code=>"module App; def self.call(env) @a end; @a ||= []; @a << 1; end").call({}).should == [1]
|
107
136
|
update_app("module App; def self.call(env) @a end; @a ||= []; @a << 2; end")
|
108
137
|
ru.call({}).should == [2]
|
109
|
-
log_match
|
138
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
139
|
+
%r{\ANew classes in .*spec/app\.rb: App\z},
|
140
|
+
%r{\AReloading.*spec/app\.rb\z},
|
141
|
+
"Removed constant App",
|
142
|
+
%r{\ARemoved feature .*/spec/app.rb\z},
|
143
|
+
%r{\ANew classes in .*spec/app\.rb: App\z}
|
110
144
|
end
|
111
145
|
|
112
|
-
it "
|
146
|
+
it "should unload specific modules by name via :subclasses option" do
|
113
147
|
ru(:subclasses=>'App', :code=>"module App; def self.call(env) @a end; @a ||= []; @a << 1; end").call({}).should == [1]
|
114
148
|
update_app("module App; def self.call(env) @a end; @a ||= []; @a << 2; end")
|
115
149
|
ru.call({}).should == [2]
|
116
|
-
log_match
|
150
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
151
|
+
%r{\ANew classes in .*spec/app\.rb: App\z},
|
152
|
+
%r{\AReloading.*spec/app\.rb\z},
|
153
|
+
"Removed constant App",
|
154
|
+
%r{\ARemoved feature .*/spec/app.rb\z},
|
155
|
+
%r{\ANew classes in .*spec/app\.rb: App\z}
|
117
156
|
end
|
118
157
|
|
119
|
-
it "
|
158
|
+
it "should not unload modules by name if :subclasses option used and module not present" do
|
120
159
|
ru(:subclasses=>'Foo', :code=>"module App; def self.call(env) @a end; @a ||= []; @a << 1; end").call({}).should == [1]
|
121
160
|
update_app("module App; def self.call(env) @a end; @a ||= []; @a << 2; end")
|
122
161
|
ru.call({}).should == [1, 2]
|
123
|
-
log_match
|
162
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
163
|
+
%r{\AReloading.*spec/app\.rb\z},
|
164
|
+
%r{\ARemoved feature .*/spec/app.rb\z}
|
124
165
|
end
|
125
166
|
|
126
|
-
it "
|
167
|
+
it "should unload partially loaded modules if loading fails, and allow future loading" do
|
127
168
|
ru.call({}).should == [1]
|
128
169
|
update_app("module App; def self.call(env) @a end; @a ||= []; raise 'foo'; end")
|
129
170
|
proc{ru.call({})}.should raise_error
|
130
171
|
defined?(::App).should == nil
|
131
172
|
update_app(code(2))
|
132
173
|
ru.call({}).should == [2]
|
133
|
-
log_match
|
174
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
175
|
+
%r{\ANew classes in .*spec/app\.rb: App\z},
|
176
|
+
%r{\AReloading.*spec/app\.rb\z},
|
177
|
+
"Removed constant App",
|
178
|
+
%r{\ARemoved feature .*/spec/app.rb\z},
|
134
179
|
%r{\AFailed to load .*spec/app\.rb; removing partially defined constants\z},
|
135
|
-
|
180
|
+
"Removed constant App",
|
181
|
+
%r{\AReloading.*spec/app\.rb\z},
|
182
|
+
%r{\ANew classes in .*spec/app\.rb: App\z}
|
136
183
|
end
|
137
184
|
|
138
|
-
it "
|
185
|
+
it "should unload classes in namespaces" do
|
139
186
|
ru(:code=>"class Array::App; def self.call(env) @a end; @a ||= []; @a << 1; end", :block=>proc{Array::App}).call({}).should == [1]
|
140
187
|
update_app("class Array::App; def self.call(env) @a end; @a ||= []; @a << 2; end")
|
141
188
|
ru.call({}).should == [2]
|
142
|
-
log_match
|
189
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
190
|
+
%r{\ANew classes in .*spec/app\.rb: Array::App\z},
|
191
|
+
%r{\AReloading.*spec/app\.rb\z},
|
192
|
+
"Removed constant Array::App",
|
193
|
+
%r{\ARemoved feature .*/spec/app.rb\z},
|
194
|
+
%r{\ANew classes in .*spec/app\.rb: Array::App\z}
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should not unload class defined in dependency if already defined in parent" do
|
198
|
+
base_ru
|
199
|
+
update_app("class App; def self.call(env) @a end; @a ||= []; @a << 2; RU.require 'spec/app2.rb'; end")
|
200
|
+
update_app("class App; @a << 3 end", 'spec/app2.rb')
|
201
|
+
@ru.require 'spec/app.rb'
|
202
|
+
ru.call({}).should == [2, 3]
|
203
|
+
update_app("class App; @a << 4 end", 'spec/app2.rb')
|
204
|
+
ru.call({}).should == [2, 3, 4]
|
205
|
+
update_app("class App; def self.call(env) @a end; @a ||= []; @a << 2; RU.require 'spec/app2.rb'; end")
|
206
|
+
ru.call({}).should == [2, 4]
|
207
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
208
|
+
%r{\ALoading.*spec/app2\.rb\z},
|
209
|
+
%r{\ANew classes in .*spec/app\.rb: App\z},
|
210
|
+
%r{\ANew features in .*spec/app\.rb: .*spec/app2\.rb\z},
|
211
|
+
%r{\AReloading.*spec/app2\.rb\z},
|
212
|
+
%r{\ARemoved feature .*/spec/app2.rb\z},
|
213
|
+
%r{\AReloading.*spec/app\.rb\z},
|
214
|
+
"Removed constant App",
|
215
|
+
%r{\ARemoved feature .*/spec/app2.rb\z},
|
216
|
+
%r{\ARemoved feature .*/spec/app.rb\z},
|
217
|
+
%r{\ALoading.*spec/app2\.rb\z},
|
218
|
+
%r{\ANew classes in .*spec/app\.rb: App\z},
|
219
|
+
%r{\ANew features in .*spec/app\.rb: .*spec/app2\.rb\z}
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should allow specifying proc for which constants get removed" do
|
223
|
+
base_ru
|
224
|
+
update_app("class App; def self.call(env) [@a, App2.a] end; @a ||= []; @a << 1; end; class App2; def self.a; @a end; @a ||= []; @a << 2; end")
|
225
|
+
@ru.require('spec/app.rb'){|f| File.basename(f).sub(/\.rb/, '').capitalize}
|
226
|
+
ru.call({}).should == [[1], [2]]
|
227
|
+
update_app("class App; def self.call(env) [@a, App2.a] end; @a ||= []; @a << 3; end; class App2; def self.a; @a end; @a ||= []; @a << 4; end")
|
228
|
+
ru.call({}).should == [[3], [2, 4]]
|
229
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
230
|
+
%r{\AReloading.*spec/app\.rb\z},
|
231
|
+
"Removed constant App",
|
232
|
+
%r{\ARemoved feature .*/spec/app.rb\z}
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should handle anonymous classes" do
|
236
|
+
base_ru(:block=>proc{$app})
|
237
|
+
update_app("$app = Class.new do def self.call(env) @a end; @a ||= []; @a << 1; end")
|
238
|
+
@ru.require('spec/app.rb')
|
239
|
+
ru.call({}).should == [1]
|
240
|
+
update_app("$app = Class.new do def self.call(env) @a end; @a ||= []; @a << 2; end")
|
241
|
+
ru.call({}).should == [2]
|
242
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
243
|
+
%r{\AReloading.*spec/app\.rb\z},
|
244
|
+
%r{\ARemoved feature .*/spec/app.rb\z}
|
143
245
|
end
|
144
246
|
|
247
|
+
it "should log when attempting to remove a class that doesn't exist" do
|
248
|
+
base_ru
|
249
|
+
update_app(code(1))
|
250
|
+
@ru.require('spec/app.rb'){|f| 'Foo'}
|
251
|
+
ru.call({}).should == [1]
|
252
|
+
update_app(code(2))
|
253
|
+
ru.call({}).should == [1, 2]
|
254
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
255
|
+
%r{\AReloading.*spec/app\.rb\z},
|
256
|
+
"Error removing constant: Foo",
|
257
|
+
%r{\ARemoved feature .*/spec/app.rb\z}
|
258
|
+
end
|
145
259
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-unreloader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
Rack::Unreloader is a rack middleware that reloads application files when it
|