rack-unreloader 1.7.0 → 1.8.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/MIT-LICENSE +1 -1
- data/README.rdoc +63 -44
- data/Rakefile +1 -1
- data/lib/rack/unreloader/reloader.rb +41 -21
- data/lib/rack/unreloader.rb +10 -5
- data/spec/spec_helper.rb +13 -3
- data/spec/unreloader_spec.rb +52 -8
- metadata +23 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6fefe4d57aa2ea927929699e2a6aba2e8979cea284b0784fee379404b6f1726
|
4
|
+
data.tar.gz: 3aa21b88447194616e29d1ea2cf1181f102f5d58e6a29f4a11696a83d826a506
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a5fbca1692b66ab4daf1f9b1677b15fb704d28bb972b26fdc15d78918fa37bed04657177438ff2351ba8c60a10105f7e138d869a4ab37fbd9e4143188c5685d
|
7
|
+
data.tar.gz: 12d9b2d24730e1f41da6089e5995c219af4b636755738405d1404e0be3f4044c8f935bd073ba53d92cada170761f79bfea82235c5b58d9ca6bc1dac1ee1b647f
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
= 1.8.0 (2021-10-15)
|
2
|
+
|
3
|
+
* Avoid warnings in verbose warning mode on Ruby 3+ (jeremyevans)
|
4
|
+
|
5
|
+
* Check directory timestamps before looking for added or removed files (jeremyevans)
|
6
|
+
|
7
|
+
* Add support for a :delete_hook option to Unreloader#require, called when a file is deleted (jeremyevans)
|
8
|
+
|
9
|
+
* Handle cases where Module#name raises an exception (jeremyevans) (#9)
|
10
|
+
|
1
11
|
= 1.7.0 (2019-03-18)
|
2
12
|
|
3
13
|
* Add :handle_reload_errors option, for returning backtrace with error if there is an exception when reloading (jeremyevans)
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
= Rack::Unreloader
|
2
2
|
|
3
|
-
Rack::Unreloader is a
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
Rack::Unreloader is a code reloader for {Rack}[https://github.com/rack/rack].
|
4
|
+
It speeds up application development by automatically reloading stale code
|
5
|
+
so that you don't have to restart your dev server every time you change a file.
|
6
|
+
|
7
|
+
Unlike most other code loading libraries for Rack,
|
8
|
+
this one ensures that reloads are clean and idempotent
|
9
|
+
by _unloading_ relevant constants first, and it does so incrementally, only
|
10
|
+
reloading the files that are modified.
|
10
11
|
|
11
12
|
== Installation
|
12
13
|
|
@@ -18,54 +19,61 @@ Source code is available on GitHub at https://github.com/jeremyevans/rack-unrelo
|
|
18
19
|
|
19
20
|
== Basic Usage
|
20
21
|
|
21
|
-
|
22
|
+
Before:
|
22
23
|
|
23
|
-
|
24
|
+
# config.ru
|
24
25
|
|
25
|
-
|
26
|
-
route do |r|
|
27
|
-
"Hello world!"
|
28
|
-
end
|
29
|
-
end
|
26
|
+
require './app'
|
30
27
|
|
31
|
-
With a basic +config.ru+ like this:
|
32
|
-
|
33
|
-
require './app.rb'
|
34
28
|
run App
|
35
29
|
|
36
|
-
|
30
|
+
After:
|
31
|
+
|
32
|
+
# config.ru
|
37
33
|
|
38
34
|
require 'rack/unreloader'
|
39
35
|
Unreloader = Rack::Unreloader.new{App}
|
40
|
-
require 'roda'
|
41
36
|
Unreloader.require './app.rb'
|
37
|
+
|
42
38
|
run Unreloader
|
43
39
|
|
44
|
-
|
45
|
-
to use. If you make any changes to +app.rb+, <tt>Rack::Unreloader</tt> will remove any
|
46
|
-
constants defined by requiring +app.rb+, and rerequire the file.
|
40
|
+
Now, +app.rb+ will be monitored for changes on each incoming HTTP request.
|
47
41
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
<tt>Rack::Unreloader</tt>.
|
42
|
+
If changes are detected, +Rack::Unreloader+ will
|
43
|
+
unload all constants defined inside it and then re-require it
|
44
|
+
before proceeding with the request.
|
52
45
|
|
53
|
-
|
54
|
-
practical to tell <tt>Rack::Unreloader</tt> to only unload specific subclasses:
|
46
|
+
== Handling Subclasses
|
55
47
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
48
|
+
By default, +Rack::Unreloader+ unloads *all* constants defined in +app.rb+.
|
49
|
+
That includes third-party libraries, like +Roda+ or +JSON+ in the example below:
|
50
|
+
|
51
|
+
# app.rb
|
52
|
+
|
53
|
+
require 'roda'
|
54
|
+
require 'json'
|
55
|
+
|
56
|
+
class App < Roda
|
57
|
+
...
|
58
|
+
end
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
Unloading these classes/modules isn't just unnecessary, it's dangerous.
|
61
|
+
If your own code depends on them, your app will throw a +NameError+ after
|
62
|
+
reloading when it tries to access them.
|
64
63
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
64
|
+
To reload only *subclasses* of +Roda+ (i.e. +App+), use the +:subclasses+
|
65
|
+
option:
|
66
|
+
|
67
|
+
Rack::Unreloader.new(:subclasses=>%w'Roda'){App}
|
68
|
+
|
69
|
+
== Handling Errors During Reloading
|
70
|
+
|
71
|
+
By default, +Rack::Unreloader+ instances do not handle exceptions raised
|
72
|
+
during reloading, so that it may be rescued elsewhere (e.g. manually or by middleware).
|
73
|
+
You can use the +:handle_reload_errors+ option to send the backtrace directly to the
|
74
|
+
client as the HTTP response:
|
75
|
+
|
76
|
+
Rack::Unreloader.new(handle_reload_errors: true){App}
|
69
77
|
|
70
78
|
== Dependency Handling
|
71
79
|
|
@@ -88,7 +96,7 @@ to using:
|
|
88
96
|
|
89
97
|
Unreloader.require './models.rb'
|
90
98
|
|
91
|
-
The reason that the
|
99
|
+
The reason that the +Rack::Unreloader+ instance is assigned to a constant in
|
92
100
|
+config.ru+ is to make it easy to add reloadable dependencies in this way.
|
93
101
|
|
94
102
|
It's even a better idea to require this dependency manually in +config.ru+,
|
@@ -212,9 +220,20 @@ The advantage for doing this is that new files added to the directory will be
|
|
212
220
|
picked up automatically, and files deleted from the directory will be removed
|
213
221
|
automatically. This applies to files in subdirectories of that directory as well.
|
214
222
|
|
223
|
+
The +require+ method also supports a +:delete_hook+ option. This option sets
|
224
|
+
a hook that is called when the related file is deleted. This is useful if adding
|
225
|
+
a new file or reloading an existing file will handle things correctly, but
|
226
|
+
removing the file will not. One common case for this is when you have a shared data
|
227
|
+
structure that is updated by the files, where adding or reloading the file will
|
228
|
+
update the data structure, but deleting will not, and will leave stale entries in
|
229
|
+
the data structure. You can use the +:delete_hook+ option to remove the entries
|
230
|
+
related to the file in the data structure:
|
231
|
+
|
232
|
+
Unreloader.require 'models', :delete_hook=>proc{|f| SHARED_HASH.delete(f)}
|
233
|
+
|
215
234
|
== Speeding Things Up
|
216
235
|
|
217
|
-
By default,
|
236
|
+
By default, +Rack::Unreloader+ uses +ObjectSpace+ before and after requiring each
|
218
237
|
file that it monitors, to see which classes and modules were defined by the
|
219
238
|
require. This is slow for large numbers of files. In general use it isn't an
|
220
239
|
issue as generally only a single file will be changed at a time, but it can
|
@@ -223,7 +242,7 @@ time.
|
|
223
242
|
|
224
243
|
If you want to speed things up, you can provide a block to Rack::Unreloader#require,
|
225
244
|
which will take the file name, and should return the name of the constants or array
|
226
|
-
of constants to unload. If you do this,
|
245
|
+
of constants to unload. If you do this, +Rack::Unreloader+ will no longer need
|
227
246
|
to use +ObjectSpace+, which substantially speeds up startup. For example, if all of
|
228
247
|
your models just use a capitalized version of the filename:
|
229
248
|
|
@@ -236,7 +255,7 @@ block return the :ObjectSpace symbol.
|
|
236
255
|
|
237
256
|
== chroot Support
|
238
257
|
|
239
|
-
|
258
|
+
+Rack::Unreloader#strip_path_prefix+ exists for supporting reloading in
|
240
259
|
chroot environments, where you chroot an application after it has been fully
|
241
260
|
loaded, but still want to pick up changes to files inside the chroot. Example:
|
242
261
|
|
@@ -248,7 +267,7 @@ $LOADED_FEATURES, as that is necessary for correct operation.
|
|
248
267
|
|
249
268
|
== Usage Outside Rack
|
250
269
|
|
251
|
-
While
|
270
|
+
While +Rack::Unreloader+ is usually in the development of rack applications,
|
252
271
|
it doesn't depend on rack. You can just instantiate an instance of Unreloader and
|
253
272
|
use it to handle reloading in any ruby application, just by using the +require+ and
|
254
273
|
+record_dependency+ to set up the metadata, and calling +reload!+ manually to
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ require 'set'
|
|
3
3
|
module Rack
|
4
4
|
class Unreloader
|
5
5
|
class Reloader
|
6
|
-
|
6
|
+
File = ::File
|
7
7
|
|
8
8
|
# Regexp for valid constant names, to prevent code execution.
|
9
9
|
VALID_CONSTANT_NAME_REGEXP = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.freeze
|
@@ -18,13 +18,15 @@ module Rack
|
|
18
18
|
@classes = opts[:subclasses] ? Array(opts[:subclasses]).map(&:to_s) : %w'Object'
|
19
19
|
|
20
20
|
# Hash of files being monitored for changes, keyed by absolute path of file name,
|
21
|
-
# with values being the last modified time (or nil if the file has
|
21
|
+
# with values being an array containing the last modified time (or nil if the file has
|
22
|
+
# not yet been loaded) and the delete hook.
|
22
23
|
@monitor_files = {}
|
23
24
|
|
24
25
|
# Hash of directories being monitored for changes, keyed by absolute path of directory name,
|
25
|
-
# with values being the an array with
|
26
|
-
# yet been
|
27
|
-
# require_dependency for new files
|
26
|
+
# with values being the an array with a hash of modified times for the directory and
|
27
|
+
# subdirectories (or nil if the directory has not yet been checked), an array of files in
|
28
|
+
# the directory, a block to pass to require_dependency for new files, and the delete_hook
|
29
|
+
# for the files in the directory.
|
28
30
|
@monitor_dirs = {}
|
29
31
|
|
30
32
|
# Hash of procs returning constants defined in files, keyed by absolute path
|
@@ -117,7 +119,7 @@ module Rack
|
|
117
119
|
order.concat(files)
|
118
120
|
order.uniq!
|
119
121
|
|
120
|
-
if
|
122
|
+
if File.directory?(path)
|
121
123
|
(@monitor_files.keys & Unreloader.ruby_files(path)).each do |file|
|
122
124
|
record_dependency(file, files)
|
123
125
|
end
|
@@ -136,25 +138,28 @@ module Rack
|
|
136
138
|
end
|
137
139
|
|
138
140
|
removed_files = []
|
141
|
+
delete_hooks = []
|
139
142
|
|
140
|
-
@monitor_files.to_a.each do |file, time|
|
141
|
-
if
|
143
|
+
@monitor_files.to_a.each do |file, (time, delete_hook)|
|
144
|
+
if File.file?(file)
|
142
145
|
if file_changed?(file, time)
|
143
146
|
changed_files << file
|
144
147
|
end
|
145
148
|
else
|
149
|
+
delete_hooks << [delete_hook, file] if delete_hook
|
146
150
|
removed_files << file
|
147
151
|
end
|
148
152
|
end
|
149
153
|
|
150
154
|
remove_files(removed_files)
|
155
|
+
delete_hooks.each{|hook, file| hook.call(file)}
|
151
156
|
|
152
157
|
return if changed_files.empty?
|
153
158
|
|
154
159
|
unless @dependencies.empty?
|
155
160
|
changed_files = reload_files(changed_files)
|
156
161
|
changed_files.flatten!
|
157
|
-
changed_files.map!{|f|
|
162
|
+
changed_files.map!{|f| File.directory?(f) ? Unreloader.ruby_files(f) : f}
|
158
163
|
changed_files.flatten!
|
159
164
|
changed_files.uniq!
|
160
165
|
|
@@ -164,7 +169,7 @@ module Rack
|
|
164
169
|
end
|
165
170
|
|
166
171
|
unless @skip_reload.empty?
|
167
|
-
skip_reload = @skip_reload.map{|f|
|
172
|
+
skip_reload = @skip_reload.map{|f| File.directory?(f) ? Unreloader.ruby_files(f) : f}
|
168
173
|
skip_reload.flatten!
|
169
174
|
skip_reload.uniq!
|
170
175
|
changed_files -= skip_reload
|
@@ -177,18 +182,19 @@ module Rack
|
|
177
182
|
|
178
183
|
# Require the given dependencies, monitoring them for changes.
|
179
184
|
# Paths should be a file glob or an array of file globs.
|
180
|
-
def require_dependencies(paths, &block)
|
185
|
+
def require_dependencies(paths, opts={}, &block)
|
181
186
|
options = {:cyclic => true}
|
187
|
+
delete_hook = opts[:delete_hook]
|
182
188
|
error = nil
|
183
189
|
|
184
190
|
Unreloader.expand_paths(paths).each do |file|
|
185
|
-
if
|
186
|
-
@monitor_dirs[file] = [nil, [], block]
|
191
|
+
if File.directory?(file)
|
192
|
+
@monitor_dirs[file] = [nil, [], block, delete_hook]
|
187
193
|
check_monitor_dir(file)
|
188
194
|
next
|
189
195
|
else
|
190
196
|
@constants_defined[file] = block
|
191
|
-
@monitor_files[file] = nil
|
197
|
+
@monitor_files[file] = [nil, delete_hook]
|
192
198
|
end
|
193
199
|
|
194
200
|
begin
|
@@ -234,10 +240,21 @@ module Rack
|
|
234
240
|
@logger.info(s) if @logger
|
235
241
|
end
|
236
242
|
|
243
|
+
# A hash of modify times for all subdirectories of the given directory.
|
244
|
+
def subdir_times(dir)
|
245
|
+
h = {}
|
246
|
+
Find.find(dir) do |f|
|
247
|
+
h[f] = modified_at(f) if File.directory?(f)
|
248
|
+
end
|
249
|
+
h
|
250
|
+
end
|
251
|
+
|
237
252
|
# Check a monitored directory for changes, adding new files and removing
|
238
253
|
# deleted files.
|
239
254
|
def check_monitor_dir(dir, changed_files=nil)
|
240
|
-
|
255
|
+
subdir_times, files, block, delete_hook = md = @monitor_dirs[dir]
|
256
|
+
return if subdir_times && subdir_times.all?{|subdir, time| File.directory?(subdir) && modified_at(subdir) == time}
|
257
|
+
md[0] = subdir_times(dir)
|
241
258
|
|
242
259
|
cur_files = Unreloader.ruby_files(dir)
|
243
260
|
return if files == cur_files
|
@@ -250,8 +267,11 @@ module Rack
|
|
250
267
|
end
|
251
268
|
|
252
269
|
remove_files(removed_files)
|
270
|
+
if delete_hook
|
271
|
+
removed_files.each{|f| delete_hook.call(f)}
|
272
|
+
end
|
253
273
|
|
254
|
-
require_dependencies(new_files, &block)
|
274
|
+
require_dependencies(new_files, :delete_hook=>delete_hook, &block)
|
255
275
|
|
256
276
|
new_files.each do |file|
|
257
277
|
if deps = @dependencies[dir]
|
@@ -344,7 +364,7 @@ module Rack
|
|
344
364
|
# Store the currently loaded classes and features, so in case of an error
|
345
365
|
# this state can be rolled back to.
|
346
366
|
def prepare(name)
|
347
|
-
|
367
|
+
remove(name)
|
348
368
|
@old_entries[name] = {:features => monitored_features}
|
349
369
|
if constants = constants_for(name)
|
350
370
|
defs = constants.select{|c| constant_defined?(c)}
|
@@ -383,7 +403,7 @@ module Rack
|
|
383
403
|
|
384
404
|
@files[name] = entry
|
385
405
|
@old_entries.delete(name)
|
386
|
-
@monitor_files[name] = modified_at(name)
|
406
|
+
@monitor_files[name][0] = modified_at(name)
|
387
407
|
|
388
408
|
defs, not_defs = entry[:constants].partition{|c| constant_defined?(c)}
|
389
409
|
unless not_defs.empty?
|
@@ -413,7 +433,7 @@ module Rack
|
|
413
433
|
rs = Set.new
|
414
434
|
|
415
435
|
::ObjectSpace.each_object(Module).each do |mod|
|
416
|
-
if !mod.name.to_s.empty? && monitored_module?(mod)
|
436
|
+
if !(mod.name rescue next).to_s.empty? && monitored_module?(mod)
|
417
437
|
rs << mod
|
418
438
|
end
|
419
439
|
end
|
@@ -466,7 +486,7 @@ module Rack
|
|
466
486
|
end
|
467
487
|
|
468
488
|
# Returns true if the file is new or it's modification time changed.
|
469
|
-
def file_changed?(file, time = @monitor_files[file])
|
489
|
+
def file_changed?(file, time = @monitor_files[file][0])
|
470
490
|
!time || modified_at(file) > time
|
471
491
|
end
|
472
492
|
|
@@ -474,7 +494,7 @@ module Rack
|
|
474
494
|
# to base the reloading on something other than the file's modification
|
475
495
|
# time.
|
476
496
|
def modified_at(file)
|
477
|
-
|
497
|
+
File.mtime(file)
|
478
498
|
end
|
479
499
|
end
|
480
500
|
end
|
data/lib/rack/unreloader.rb
CHANGED
@@ -9,14 +9,14 @@ module Rack
|
|
9
9
|
MUTEX = Monitor.new
|
10
10
|
|
11
11
|
# Reference to ::File as File would return Rack::File by default.
|
12
|
-
|
12
|
+
File = ::File
|
13
13
|
|
14
14
|
# Given the list of paths, find all matching files, or matching ruby files
|
15
15
|
# in subdirecories if given a directory, and return an array of expanded
|
16
16
|
# paths.
|
17
17
|
def self.expand_directory_paths(paths)
|
18
18
|
expand_paths(paths).
|
19
|
-
map{|f|
|
19
|
+
map{|f| File.directory?(f) ? ruby_files(f) : f}.
|
20
20
|
flatten
|
21
21
|
end
|
22
22
|
|
@@ -27,7 +27,7 @@ module Rack
|
|
27
27
|
flatten.
|
28
28
|
map{|path| Dir.glob(path).sort_by{|filename| filename.count('/')}}.
|
29
29
|
flatten.
|
30
|
-
map{|path|
|
30
|
+
map{|path| File.expand_path(path)}.
|
31
31
|
uniq
|
32
32
|
end
|
33
33
|
|
@@ -47,6 +47,8 @@ module Rack
|
|
47
47
|
#
|
48
48
|
# :cooldown :: The number of seconds to wait between checks for changed files.
|
49
49
|
# Defaults to 1. Set to nil/false to not check for changed files.
|
50
|
+
# :handle_reload_errors :: Whether reload to handle reload errors by returning
|
51
|
+
# a 500 plain text response with the backtrace.
|
50
52
|
# :reload :: Set to false to not setup a reloader, and just have require work
|
51
53
|
# directly. Should be set to false in production mode.
|
52
54
|
# :logger :: A Logger instance which will log information related to reloading.
|
@@ -85,9 +87,12 @@ module Rack
|
|
85
87
|
end
|
86
88
|
|
87
89
|
# Add a file glob or array of file globs to monitor for changes.
|
88
|
-
|
90
|
+
# Options:
|
91
|
+
# :delete_hook :: When a file being monitored is deleted, call
|
92
|
+
# this hook with the path of the deleted file.
|
93
|
+
def require(paths, opts={}, &block)
|
89
94
|
if @reloader
|
90
|
-
@reloader.require_dependencies(paths, &block)
|
95
|
+
@reloader.require_dependencies(paths, opts, &block)
|
91
96
|
else
|
92
97
|
Unreloader.expand_directory_paths(paths).each{|f| super(f)}
|
93
98
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
|
|
3
3
|
$: << 'lib'
|
4
4
|
ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
|
5
5
|
gem 'minitest'
|
6
|
-
require 'minitest/autorun'
|
6
|
+
require 'minitest/global_expectations/autorun'
|
7
7
|
require 'minitest/hooks'
|
8
8
|
|
9
9
|
module ModifiedAt
|
@@ -25,14 +25,24 @@ end
|
|
25
25
|
|
26
26
|
class Minitest::Spec
|
27
27
|
def code(i)
|
28
|
-
"class App; def
|
28
|
+
"class App; class << self; def call(env) @a end; alias call call; end; @a ||= []; @a << #{i}; end"
|
29
29
|
end
|
30
30
|
|
31
31
|
def update_app(code, file=@filename)
|
32
|
-
|
32
|
+
if ru.reloader
|
33
|
+
ru.reloader.set_modified_time(File.dirname(file), @i += 1) unless File.file?(file)
|
34
|
+
ru.reloader.set_modified_time(file, @i += 1)
|
35
|
+
end
|
33
36
|
File.open(file, 'wb'){|f| f.write(code)}
|
34
37
|
end
|
35
38
|
|
39
|
+
def file_delete(file)
|
40
|
+
if ru.reloader
|
41
|
+
ru.reloader.set_modified_time(File.dirname(file), @i += 1)
|
42
|
+
end
|
43
|
+
File.delete(file)
|
44
|
+
end
|
45
|
+
|
36
46
|
def logger
|
37
47
|
return @logger if @logger
|
38
48
|
@logger = []
|
data/spec/unreloader_spec.rb
CHANGED
@@ -32,7 +32,7 @@ describe Rack::Unreloader do
|
|
32
32
|
|
33
33
|
it "should stop monitoring file for changes if it is deleted constants contained in file and reload file if file changes" do
|
34
34
|
ru.call({}).must_equal [1]
|
35
|
-
|
35
|
+
file_delete('spec/app.rb')
|
36
36
|
proc{ru.call({})}.must_raise NameError
|
37
37
|
log_match %r{\ALoading.*spec/app\.rb\z},
|
38
38
|
%r{\ANew classes in .*spec/app\.rb: App\z},
|
@@ -131,7 +131,7 @@ describe Rack::Unreloader do
|
|
131
131
|
end
|
132
132
|
|
133
133
|
it "should not unload modules by name if :subclasses option used and module not present" do
|
134
|
-
ru(:subclasses=>'Foo', :code=>"module App; def self.call(env) @a end; @a ||= []; @a << 1; end").call({}).must_equal [1]
|
134
|
+
ru(:subclasses=>'Foo', :code=>"module App; def self.call(env) @a end; class << self; alias call call; end; @a ||= []; @a << 1; end").call({}).must_equal [1]
|
135
135
|
update_app("module App; def self.call(env) @a end; @a ||= []; @a << 2; end")
|
136
136
|
ru.call({}).must_equal [1, 2]
|
137
137
|
log_match %r{\ALoading.*spec/app\.rb\z},
|
@@ -218,7 +218,7 @@ describe Rack::Unreloader do
|
|
218
218
|
|
219
219
|
it "should allow specifying proc for which constants get removed" do
|
220
220
|
base_ru
|
221
|
-
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")
|
221
|
+
update_app("class App; def self.call(env) [@a, App2.a] end; class << self; alias call call; end; @a ||= []; @a << 1; end; class App2; def self.a; @a end; class << self; alias a a; end; @a ||= []; @a << 2; end")
|
222
222
|
@ru.require('spec/app.rb'){|f| File.basename(f).sub(/\.rb/, '').capitalize}
|
223
223
|
ru.call({}).must_equal [[1], [2]]
|
224
224
|
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")
|
@@ -288,6 +288,20 @@ describe Rack::Unreloader do
|
|
288
288
|
ru.call({}).must_equal 4
|
289
289
|
end
|
290
290
|
|
291
|
+
it "should handle modules where name raises an exception" do
|
292
|
+
m = Module.new{def self.name; raise end}
|
293
|
+
ru(:code=>"module App; def self.call(env) @a end; @a ||= []; @a << 1; end").call({}).must_equal [1]
|
294
|
+
update_app("module App; def self.call(env) @a end; @a ||= []; @a << 2; end")
|
295
|
+
ru.call({}).must_equal [2]
|
296
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
297
|
+
%r{\ANew classes in .*spec/app\.rb: App\z},
|
298
|
+
%r{\AUnloading.*spec/app\.rb\z},
|
299
|
+
"Removed constant App",
|
300
|
+
%r{\ALoading.*spec/app\.rb\z},
|
301
|
+
%r{\ANew classes in .*spec/app\.rb: App\z}
|
302
|
+
m
|
303
|
+
end
|
304
|
+
|
291
305
|
describe "with a directory" do
|
292
306
|
include Minitest::Hooks
|
293
307
|
|
@@ -298,7 +312,7 @@ describe Rack::Unreloader do
|
|
298
312
|
end
|
299
313
|
|
300
314
|
after do
|
301
|
-
Dir['spec/dir/**/*.rb'].each{|f|
|
315
|
+
Dir['spec/dir/**/*.rb'].each{|f| file_delete(f)}
|
302
316
|
end
|
303
317
|
|
304
318
|
after(:all) do
|
@@ -353,7 +367,7 @@ describe Rack::Unreloader do
|
|
353
367
|
ru.call({}).must_equal 3
|
354
368
|
update_app("module C; B = 4; end", 'spec/dir/subdir2/app_mod2.rb')
|
355
369
|
ru.call({}).must_equal 4
|
356
|
-
|
370
|
+
file_delete 'spec/dir/subdir/app_mod.rb'
|
357
371
|
ru.call({}).must_equal 0
|
358
372
|
end
|
359
373
|
|
@@ -374,7 +388,7 @@ describe Rack::Unreloader do
|
|
374
388
|
ru.call({}).must_equal 561
|
375
389
|
update_app("class App; end", 'spec/dir/appa.rb')
|
376
390
|
ru.call({}).must_equal 61
|
377
|
-
|
391
|
+
file_delete 'spec/dir/appb.rb'
|
378
392
|
ru.call({}).must_equal 1
|
379
393
|
end
|
380
394
|
|
@@ -440,7 +454,7 @@ describe Rack::Unreloader do
|
|
440
454
|
update_app("App.call[:foo] = 1", 'spec/dir/a.rb')
|
441
455
|
@ru.require('spec/app.rb')
|
442
456
|
ru.call({}).must_equal(:foo=>1)
|
443
|
-
|
457
|
+
file_delete('spec/dir/a.rb')
|
444
458
|
update_app("App.call[:foo] = 2", 'spec/dir/b.rb')
|
445
459
|
ru.call({}).must_equal(:foo=>2)
|
446
460
|
log_match %r{\ALoading.*spec/app\.rb\z},
|
@@ -457,7 +471,7 @@ describe Rack::Unreloader do
|
|
457
471
|
update_app("App.call[:foo] = 1", 'spec/dir/subdir/a.rb')
|
458
472
|
@ru.require('spec/app.rb')
|
459
473
|
ru.call({}).must_equal(:foo=>1)
|
460
|
-
|
474
|
+
file_delete('spec/dir/subdir/a.rb')
|
461
475
|
update_app("App.call[:foo] = 2", 'spec/dir/subdir/b.rb')
|
462
476
|
ru.call({}).must_equal(:foo=>2)
|
463
477
|
log_match %r{\ALoading.*spec/app\.rb\z},
|
@@ -467,5 +481,35 @@ describe Rack::Unreloader do
|
|
467
481
|
%r{\AUnloading .*/spec/dir/subdir/a.rb\z},
|
468
482
|
%r{\ALoading.*spec/dir/subdir/b\.rb\z}
|
469
483
|
end
|
484
|
+
|
485
|
+
it "should call hook when dropping files deleted from the directory" do
|
486
|
+
base_ru
|
487
|
+
deletes = []
|
488
|
+
Object.const_set(:Deletes, deletes)
|
489
|
+
update_app("class App; @a = {}; def self.call(env=nil) @a end; end; RU.require('spec/dir', :delete_hook=>proc{|f| Deletes << f})")
|
490
|
+
update_app("App.call[:foo] = 1", 'spec/dir/a.rb')
|
491
|
+
@ru.require('spec/app.rb', :delete_hook=>proc{|f| deletes << f})
|
492
|
+
ru.call({}).must_equal(:foo=>1)
|
493
|
+
file_delete('spec/dir/a.rb')
|
494
|
+
update_app("App.call[:foo] = 2", 'spec/dir/b.rb')
|
495
|
+
ru.call({}).must_equal(:foo=>2)
|
496
|
+
deletes.must_equal [File.expand_path('spec/dir/a.rb')]
|
497
|
+
file_delete('spec/dir/b.rb')
|
498
|
+
ru.call({}).must_equal(:foo=>2)
|
499
|
+
deletes.must_equal [File.expand_path('spec/dir/a.rb'), File.expand_path('spec/dir/b.rb')]
|
500
|
+
file_delete('spec/app.rb')
|
501
|
+
proc{ru.call({})}.must_raise NameError
|
502
|
+
deletes.must_equal [File.expand_path('spec/dir/a.rb'), File.expand_path('spec/dir/b.rb'), File.expand_path('spec/app.rb')]
|
503
|
+
log_match %r{\ALoading.*spec/app\.rb\z},
|
504
|
+
%r{\ALoading.*spec/dir/a\.rb\z},
|
505
|
+
%r{\ANew classes in .*spec/app\.rb: App\z},
|
506
|
+
%r{\ANew features in .*spec/app\.rb: .*spec/dir/a\.rb\z},
|
507
|
+
%r{\AUnloading .*/spec/dir/a.rb\z},
|
508
|
+
%r{\ALoading.*spec/dir/b\.rb\z},
|
509
|
+
%r{\AUnloading .*/spec/dir/b.rb\z},
|
510
|
+
%r{\AUnloading .*/spec/app.rb\z},
|
511
|
+
%r{\ARemoved constant App\z}
|
512
|
+
Object.send(:remove_const, :Deletes)
|
513
|
+
end
|
470
514
|
end
|
471
515
|
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: 1.
|
4
|
+
version: 1.8.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:
|
11
|
+
date: 2021-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest-global_expectations
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
description: |
|
42
56
|
Rack::Unreloader is a rack middleware that reloads application files when it
|
43
57
|
detects changes, unloading constants defined in those files before reloading.
|
@@ -61,7 +75,11 @@ files:
|
|
61
75
|
homepage: http://github.com/jeremyevans/rack-unreloader
|
62
76
|
licenses:
|
63
77
|
- MIT
|
64
|
-
metadata:
|
78
|
+
metadata:
|
79
|
+
bug_tracker_uri: https://github.com/jeremyevans/rack-unreloader/issues
|
80
|
+
changelog_uri: https://github.com/jeremyevans/rack-unreloader/blob/master/CHANGELOG
|
81
|
+
mailing_list_uri: https://github.com/jeremyevans/rack-unreloader/discussions
|
82
|
+
source_code_uri: https://github.com/jeremyevans/rack-unreloader
|
65
83
|
post_install_message:
|
66
84
|
rdoc_options:
|
67
85
|
- "--quiet"
|
@@ -77,14 +95,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
95
|
requirements:
|
78
96
|
- - ">="
|
79
97
|
- !ruby/object:Gem::Version
|
80
|
-
version:
|
98
|
+
version: 1.8.7
|
81
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
100
|
requirements:
|
83
101
|
- - ">="
|
84
102
|
- !ruby/object:Gem::Version
|
85
103
|
version: '0'
|
86
104
|
requirements: []
|
87
|
-
rubygems_version: 3.
|
105
|
+
rubygems_version: 3.2.22
|
88
106
|
signing_key:
|
89
107
|
specification_version: 4
|
90
108
|
summary: Reload application when files change, unloading constants first
|