rack-unreloader 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 513c06d3763473095bfcfb9b22a55d5bff7e5ede857a957934923062dd72473f
4
- data.tar.gz: 03bc800248c21507ac209104593019e3fc69a398f32a02b7e4e3371d4824d162
3
+ metadata.gz: f6fefe4d57aa2ea927929699e2a6aba2e8979cea284b0784fee379404b6f1726
4
+ data.tar.gz: 3aa21b88447194616e29d1ea2cf1181f102f5d58e6a29f4a11696a83d826a506
5
5
  SHA512:
6
- metadata.gz: 83c6ad5ee6aa7650bf5de240949a20c54b4560c1ef943f3ebc8599a22788cd88c96214ee0f26d540f17eb16f26fe282357f52c1cd4b415980250bc18281675d7
7
- data.tar.gz: 96a012e44fc868f4a3a2aa1395c5710156ef9302f36e23a3718af364e03e4f5bd411959c8fa2cf590eede2201208117ab4a47a36def1c6e95a5bb87c0c045978
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2015 Jeremy Evans
1
+ Copyright (c) 2014-2021 Jeremy Evans
2
2
  Copyright (c) 2011 Padrino
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining
data/README.rdoc CHANGED
@@ -1,12 +1,13 @@
1
1
  = Rack::Unreloader
2
2
 
3
- Rack::Unreloader is a rack library that reloads application files when it
4
- detects changes, unloading constants defined in those files before reloading.
5
- Like other rack libraries for reloading, this can make application development
6
- much faster, as you don't need to restart the whole application when you change
7
- a single file. Unlike most other rack libraries for reloading, this unloads
8
- constants before requiring files, avoiding issues when loading a file is not
9
- idempotent.
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
- Assuming a basic web application stored in +app.rb+:
22
+ Before:
22
23
 
23
- require 'roda'
24
+ # config.ru
24
25
 
25
- class App < Roda
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
- Change +config.ru+ to:
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
- The block you pass to Rack::Unreloader.new should return the rack application
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
- Note that this causes problems if +app.rb+ loads any new libraries that define
49
- constants, as it will unload those constants first. This is why the example
50
- code requires the +roda+ library normally before requiring +app.rb+ using
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
- However, if +app.rb+ requires more than a single file, it is more
54
- practical to tell <tt>Rack::Unreloader</tt> to only unload specific subclasses:
46
+ == Handling Subclasses
55
47
 
56
- require 'rack/unreloader'
57
- Unreloader = Rack::Unreloader.new(:subclasses=>%w'Roda'){App}
58
- Unreloader.require './app.rb'
59
- run Unreloader
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
- When the +:subclasses+ option is given, only subclasses of the given classes
62
- will be unloaded before reloading the file. It is recommended that
63
- you use a +:subclasses+ option when using <tt>Rack::Unreloader</tt>.
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
- When the +:handle_reload_errors+ option is given, most exceptions raised during
66
- reloading will cause the backtrace to be returned, instead of raising the error
67
- externally. This can be useful if no middleware that wrap the Unreloader are
68
- rescuing exceptions.
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 <tt>Rack::Unreloader</tt> instance is assigned to a constant in
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, <tt>Rack::Unreloader</tt> uses +ObjectSpace+ before and after requiring each
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, <tt>Rack::Unreloader</tt> will no longer need
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
- <tt>Rack::Unreloader#strip_path_prefix</tt> exists for supporting reloading in
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 <tt>Rack::Unreloader</tt> is usually in the development of rack applications,
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
@@ -12,7 +12,7 @@ end
12
12
 
13
13
  desc "Run specs"
14
14
  task :spec do
15
- sh "#{FileUtils::RUBY} spec/unreloader_spec.rb"
15
+ sh "#{FileUtils::RUBY} #{'-w ' if RUBY_VERSION >= '3'}spec/unreloader_spec.rb"
16
16
  end
17
17
 
18
18
  task :default => :spec
@@ -3,7 +3,7 @@ require 'set'
3
3
  module Rack
4
4
  class Unreloader
5
5
  class Reloader
6
- F = ::File
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 not yet been loaded).
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 the last modified time (or nil if the directory has not
26
- # yet been loaded), an array of files in the directory, and a block to pass to
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 F.directory?(path)
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 F.file?(file)
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| F.directory?(f) ? Unreloader.ruby_files(f) : 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| F.directory?(f) ? Unreloader.ruby_files(f) : 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 F.directory?(file)
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
- time, files, block = @monitor_dirs[dir]
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
- file = remove(name)
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
- F.mtime(file)
497
+ File.mtime(file)
478
498
  end
479
499
  end
480
500
  end
@@ -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
- F = ::File
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| F.directory?(f) ? ruby_files(f) : 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| F.expand_path(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
- def require(paths, &block)
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 self.call(env) @a end; @a ||= []; @a << #{i}; end"
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
- ru.reloader.set_modified_time(file, @i += 1) if ru.reloader
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 = []
@@ -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
- File.delete('spec/app.rb')
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| File.delete(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
- File.delete 'spec/dir/subdir/app_mod.rb'
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
- File.delete 'spec/dir/appb.rb'
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
- File.delete('spec/dir/a.rb')
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
- File.delete('spec/dir/subdir/a.rb')
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.7.0
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: 2019-03-18 00:00:00.000000000 Z
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: '0'
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.0.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