rack-unreloader 0.9.0 → 1.0.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
  SHA1:
3
- metadata.gz: 7665e2203b2b2f7ac74d05e6c0b5b2c60d7d89d8
4
- data.tar.gz: 19cd1b3fcf725bdaf58db40ba67426c38b3823d1
3
+ metadata.gz: 4e78994d15fb3130a66a1b0c4cddd957bbc7ed5a
4
+ data.tar.gz: 3151916048c0f97a461e6bd1a3e5d06cb5945943
5
5
  SHA512:
6
- metadata.gz: 7810e9036260c1a517f544783e755997deeb759fcb56e05fdc2ee16109de4c74c907f94a019fe42c503610255797d4f04f76060ff8dde732037eb15df52b90a2
7
- data.tar.gz: 17a0ea4463d61cfb959e72ce724c28fecba47dc7ac098ab0b01559c17f8e90026436b982da342335610d281f4fd652b3f13bfd5b1d1b10555d8ec590410036df
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-unreloader'
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-unreloader'
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-unreloader'
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-unreloader'
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
@@ -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
- $LOADED_FEATURES.delete(file) if @monitor_files.has_key?(file)
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].each{|constant| remove_constant(constant)}
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
- old_features = Set.new($LOADED_FEATURES)
151
- @old_entries[name] = {:constants => all_classes, :features => old_features}
152
- features = file && file[:features] || []
153
- features.each{|feature| safe_load(feature, :force => true)}
154
- remove_feature(name) if old_features.include?(name)
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
- :constants => new_classes(@old_entries[name][:constants]),
162
- :features => Set.new($LOADED_FEATURES) - @old_entries[name][:features] - [name]
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]).each{|klass| remove_constant(klass)}
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 |klass|
182
- if monitored_class?(klass)
183
- rs << klass
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 monitored_class?(klass)
227
+ def monitored_module?(mod)
193
228
  @classes.any? do |c|
194
229
  c = constantize(c) rescue false
195
230
 
196
- if klass.is_a?(Class)
231
+ if mod.is_a?(Class)
197
232
  # Reload the class if it is a subclass if the current class
198
- (klass < c) rescue false
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
- klass == c
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
@@ -35,20 +35,23 @@ describe Rack::Unreloader do
35
35
  @logger
36
36
  end
37
37
 
38
- def ru(opts={})
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 "it should unload constants contained in file and reload file if file changes" do
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(%r{\ALoading.*spec/app\.rb\z}, "Removed constant App", %r{\AReloading.*spec/app\.rb\z})
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 "it should pickup files added as dependencies" do
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
- update_app("RU.require 'spec/app2.rb'; class App; def self.call(env) [@a, App2.call(env)] end; @a ||= []; @a << 2; end")
83
- log_match(%r{\ALoading.*spec/app\.rb\z}, "Removed constant App", %r{\AReloading.*spec/app\.rb\z},
84
- %r{\ALoading.*spec/app2\.rb\z}, "Removed constant App2", %r{\AReloading.*spec/app2\.rb\z})
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 "it should support :subclasses option and only unload subclasses of given class" do
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(%r{\ALoading.*spec/app\.rb\z}, %r{\AReloading.*spec/app\.rb\z},
96
- %r{\ALoading.*spec/app2\.rb\z}, "Removed constant App2", %r{\AReloading.*spec/app2\.rb\z})
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 "it log invalid constant names in :subclasses options" do
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('"1" is not a valid constant name!', %r{\ALoading.*spec/app\.rb\z})
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 "it should unload modules before reloading similar to classes" do
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(%r{\ALoading.*spec/app\.rb\z}, "Removed constant App", %r{\AReloading.*spec/app\.rb\z})
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 "it should unload specific modules by name via :subclasses option" do
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(%r{\ALoading.*spec/app\.rb\z}, "Removed constant App", %r{\AReloading.*spec/app\.rb\z})
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 "it should not unload modules by name if :subclasses option used and module not present" do
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(%r{\ALoading.*spec/app\.rb\z}, %r{\AReloading.*spec/app\.rb\z})
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 "it unload partially loaded modules if loading fails, and allow future loading" do
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(%r{\ALoading.*spec/app\.rb\z}, "Removed constant App", %r{\AReloading.*spec/app\.rb\z},
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
- "Removed constant App", %r{\AReloading.*spec/app\.rb\z})
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 "it should unload classes in namespaces" do
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(%r{\ALoading.*spec/app\.rb\z}, "Removed constant Array::App", %r{\AReloading.*spec/app\.rb\z})
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.9.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-04 00:00:00.000000000 Z
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