rails-dev-boost 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.markdown +21 -3
- data/VERSION +1 -1
- data/lib/rails_development_boost.rb +11 -2
- data/lib/rails_development_boost/async.rb +64 -22
- data/lib/rails_development_boost/async/reactor.rb +195 -0
- data/lib/rails_development_boost/dependencies_patch.rb +142 -51
- data/lib/rails_development_boost/dependencies_patch/instrumentation_patch.rb +37 -22
- data/lib/rails_development_boost/descendants_tracker_patch.rb +9 -1
- data/lib/rails_development_boost/loadable_patch.rb +3 -4
- data/lib/rails_development_boost/loaded_file.rb +82 -21
- data/lib/rails_development_boost/reloader.rb +44 -1
- metadata +37 -57
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3c6be77bb649766e7ae7bfe30a7d45f848c62ec3
|
4
|
+
data.tar.gz: 380d7b77ab210ea426ee7a4369e46102e317bb5b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e69c5b78346a397e7bc99069e323db29ea92dd3ced9cd2da58d3083e7fb625be6075c3482e85bccece82390bf681cf24e3615dee5b0c2b211a8cfcf86ec650e7
|
7
|
+
data.tar.gz: e4d005306b5d46d9f668cc241d8a9dfc735a51447da2f13cf6c6aaad97ab1f19580b997b0db78fe6308bc64af60f8ea80a002305153198d7c5562ae29fc37435
|
data/README.markdown
CHANGED
@@ -8,7 +8,7 @@ Alternative to Robert Pankowecki's [`active_reload`](https://github.com/paneq/ac
|
|
8
8
|
|
9
9
|
## Branches
|
10
10
|
|
11
|
-
If you are using **Rails 3**: [`rails-dev-boost/master`](http://github.com/thedarkone/rails-dev-boost/tree/master) branch.
|
11
|
+
If you are using **Rails 3 and newer**: [`rails-dev-boost/master`](http://github.com/thedarkone/rails-dev-boost/tree/master) branch.
|
12
12
|
|
13
13
|
If you are using **Rails 2.3**: [`rails-dev-boost/rails-2-3`](http://github.com/thedarkone/rails-dev-boost/tree/rails-2-3) branch.
|
14
14
|
|
@@ -29,7 +29,7 @@ I'm very interested in making the plugin as robust as possible and will work wit
|
|
29
29
|
|
30
30
|
There is built-in debug mode in `rails-dev-boost` that can be enabled by putting this line a Rails initializer file:
|
31
31
|
|
32
|
-
RailsDevelopmentBoost.debug!
|
32
|
+
RailsDevelopmentBoost.debug! if defined?(RailsDevelopmentBoost)
|
33
33
|
|
34
34
|
After restarting your server `rails-dev-boost` will start to spewing detailed tracing information about its actions into your `development.log` file.
|
35
35
|
|
@@ -47,7 +47,7 @@ Usage through `Gemfile`:
|
|
47
47
|
|
48
48
|
```ruby
|
49
49
|
group :development do
|
50
|
-
gem 'rails-dev-boost', :git => 'git://github.com/thedarkone/rails-dev-boost.git'
|
50
|
+
gem 'rails-dev-boost', :git => 'git://github.com/thedarkone/rails-dev-boost.git'
|
51
51
|
end
|
52
52
|
```
|
53
53
|
|
@@ -279,6 +279,24 @@ class Blog < ActiveRecord::Base
|
|
279
279
|
end
|
280
280
|
```
|
281
281
|
|
282
|
+
## Asynchronous mode
|
283
|
+
|
284
|
+
By default `rails-dev-boost` now runs in an "async" mode, watching and unloading modified files in a separate thread. This allows for an even faster development mode because there is no longer a need to do a `File.mtime` check of all the `.rb` files at the beginning of the request.
|
285
|
+
|
286
|
+
To disable the async mode put the following code in a Rails initializer file (these are found in `config/initializers` directory):
|
287
|
+
```ruby
|
288
|
+
RailsDevelopmentBoost.async = false
|
289
|
+
```
|
290
|
+
|
291
|
+
## `routes.rb` potentially not reloading
|
292
|
+
|
293
|
+
Since Rails 4.0 `ActiveSupport` now by default reloads `routes.rb` file **if any other auto-loaded `.rb` has changed**. This behavior is different from all previous Rails versions, where `routes.rb` had been reloaded **only if the `routes.rb` file itself had been changed**. This now results in `routes.rb` being reloading on all requests in which any other unrelated `.rb` has been changed, it is in my opinion an unnecessary slowdown, thus `rails-dev-boost` by default reverts Rails to the pre Rails 4.0 behavior.
|
294
|
+
|
295
|
+
To disable this patch and revert to the default Rails 4.0 behavior - put the following code in a Rails initializer file (these are found in `config/initializers` directory):
|
296
|
+
```ruby
|
297
|
+
RailsDevelopmentBoost.reload_routes_on_any_change = true
|
298
|
+
```
|
299
|
+
|
282
300
|
## FAQ
|
283
301
|
|
284
302
|
### Q: Since the plugin uses its special "unloading mechanism" won't everything break down?
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
@@ -13,6 +13,8 @@ module RailsDevelopmentBoost
|
|
13
13
|
else
|
14
14
|
ActionDispatch::Callbacks.before(:prepend => true) { ActiveSupport::Dependencies.unload_modified_files! }
|
15
15
|
end
|
16
|
+
|
17
|
+
DependenciesPatch.enable_async_mode_by_default!
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
@@ -35,6 +37,8 @@ module RailsDevelopmentBoost
|
|
35
37
|
else
|
36
38
|
ActiveSupport.on_load(:action_controller) { ViewHelpersPatch.apply! }
|
37
39
|
end
|
40
|
+
|
41
|
+
app.config.middleware.use 'RailsDevelopmentBoost::Async::Middleware'
|
38
42
|
end
|
39
43
|
end
|
40
44
|
end
|
@@ -55,7 +59,12 @@ module RailsDevelopmentBoost
|
|
55
59
|
DependenciesPatch.debug!
|
56
60
|
end
|
57
61
|
|
58
|
-
def self.async
|
59
|
-
DependenciesPatch.async
|
62
|
+
def self.async=(new_value)
|
63
|
+
DependenciesPatch.async = new_value
|
64
|
+
end
|
65
|
+
|
66
|
+
class << self
|
67
|
+
attr_accessor :reload_routes_on_any_change
|
60
68
|
end
|
69
|
+
self.reload_routes_on_any_change = false
|
61
70
|
end
|
@@ -1,15 +1,29 @@
|
|
1
|
-
require 'rb-fsevent'
|
2
1
|
require 'monitor'
|
3
2
|
|
4
3
|
module RailsDevelopmentBoost
|
5
4
|
module Async
|
5
|
+
class Middleware
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
if DependenciesPatch.applied? && DependenciesPatch.async?
|
12
|
+
Async.synchronize { @app.call(env) }
|
13
|
+
else
|
14
|
+
@app.call(env)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
6
19
|
extend self
|
20
|
+
autoload :Reactor, 'rails_development_boost/async/reactor'
|
7
21
|
|
8
22
|
MONITOR = Monitor.new
|
9
23
|
|
10
24
|
def heartbeat_check!
|
11
25
|
if @reactor
|
12
|
-
unless @reactor.
|
26
|
+
unless @reactor.alive_and_watching?(ActiveSupport::Dependencies.autoload_paths)
|
13
27
|
@reactor.stop
|
14
28
|
@reactor = nil
|
15
29
|
start!
|
@@ -25,13 +39,56 @@ module RailsDevelopmentBoost
|
|
25
39
|
MONITOR.synchronize { yield }
|
26
40
|
end
|
27
41
|
|
42
|
+
def usable?
|
43
|
+
Reactor.implementation
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_new_async_value(new_value)
|
47
|
+
if new_value
|
48
|
+
if !Async.usable?
|
49
|
+
msg = 'Unable to start rails-dev-boost in an asynchronous mode. '
|
50
|
+
if gem_error = Reactor.gem_load_error
|
51
|
+
msg << "Please install the missing gem for even faster rails-dev-boost experience:\n#{gem_error}\n" <<
|
52
|
+
"If you can't use the suggest gem version please let me know at https://github.com/thedarkone/rails-dev-boost/issues !\n"
|
53
|
+
else
|
54
|
+
msg << "Are you running on a OS that is 'async-unsupported' by rails-dev-boost? Please open an issue on github: https://github.com/thedarkone/rails-dev-boost/issues !"
|
55
|
+
end
|
56
|
+
msg << "\nTo get rid of this message disable the rails-dev-boost's async mode by putting the following code " +
|
57
|
+
"in a Rails initializer file (these are found in config/initializers directory):\n" +
|
58
|
+
"\n\tRailsDevelopmentBoost.async = false if defined?(RailsDevelopmentBoost)\n\n"
|
59
|
+
async_warning(msg)
|
60
|
+
new_value = false
|
61
|
+
elsif in_console?
|
62
|
+
async_warning('Warning: using asynchronous mode in Rails console mode might result in surprising behaviour and is not recommended.')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
new_value
|
66
|
+
end
|
67
|
+
|
68
|
+
def enable_by_default!(user_provided_value = false)
|
69
|
+
unless user_provided_value
|
70
|
+
DependenciesPatch.async = true unless usable? # trigger the warning message, unless there is a user supplied `async` setting
|
71
|
+
DependenciesPatch.async = !in_console?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
28
75
|
private
|
76
|
+
def in_console?
|
77
|
+
defined?(IRB) || defined?(Pry)
|
78
|
+
end
|
79
|
+
|
80
|
+
def async_warning(msg)
|
81
|
+
msg = msg.gsub(/^/, '[RAILS-DEV-BOOST] ')
|
82
|
+
Kernel.warn(msg)
|
83
|
+
Rails.logger.info(msg)
|
84
|
+
end
|
29
85
|
|
30
86
|
def start!
|
31
|
-
@reactor = Reactor.
|
32
|
-
|
33
|
-
|
34
|
-
|
87
|
+
if @reactor = Reactor.get
|
88
|
+
@reactor.watch(ActiveSupport::Dependencies.autoload_paths) {|changed_dirs| unload_affected(changed_dirs)}
|
89
|
+
@reactor.start!
|
90
|
+
self.unloaded_something = LoadedFile.unload_modified! # don't miss-out on any of the file changes as the async thread hasn't been started as of yet
|
91
|
+
end
|
35
92
|
end
|
36
93
|
|
37
94
|
def re_raise_unload_error_if_any
|
@@ -45,9 +102,7 @@ module RailsDevelopmentBoost
|
|
45
102
|
changed_dirs = changed_dirs.map {|changed_dir| File.expand_path(changed_dir).chomp(File::SEPARATOR)}
|
46
103
|
|
47
104
|
synchronize do
|
48
|
-
self.unloaded_something = LoadedFile::LOADED.
|
49
|
-
changed_dirs.any? {|changed_dir| file.path.starts_with?(changed_dir)} && file.changed?
|
50
|
-
end
|
105
|
+
self.unloaded_something = LoadedFile::LOADED.unload_modified!(changed_dirs)
|
51
106
|
end
|
52
107
|
rescue Exception => e
|
53
108
|
@unload_error ||= e
|
@@ -56,18 +111,5 @@ module RailsDevelopmentBoost
|
|
56
111
|
def unloaded_something=(value)
|
57
112
|
@unloaded_something ||= value
|
58
113
|
end
|
59
|
-
|
60
|
-
class Reactor
|
61
|
-
delegate :alive?, :to => '@thread'
|
62
|
-
delegate :watch, :stop, :to => '@watcher'
|
63
|
-
|
64
|
-
def initialize
|
65
|
-
@watcher = FSEvent.new
|
66
|
-
end
|
67
|
-
|
68
|
-
def start!
|
69
|
-
@thread = Thread.new { @watcher.run }
|
70
|
-
end
|
71
|
-
end
|
72
114
|
end
|
73
115
|
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module RailsDevelopmentBoost
|
5
|
+
module Async
|
6
|
+
# Not using Listen gem directly, because I don't want to be storing/checking .rb files' SHA contents and would like to rely on mtime values exclusively.
|
7
|
+
module Reactor
|
8
|
+
class MissingNativeGem < StandardError
|
9
|
+
def initialize(gem_name, version)
|
10
|
+
gem_version_msg = indented_code("gem '#{gem_name}', '#{version}'")
|
11
|
+
super("by adding the following to your Gemfile:\n#{gem_version_msg}\n\nThis can go into the same :development group if (you are using one):\n" <<
|
12
|
+
indented_code("group :development do\n\tgem 'rails-dev-boost', :github => 'thedarkone/rails-dev-boost'#{gem_version_msg}\nend\n"))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def indented_code(msg)
|
17
|
+
"\n\t" << msg.gsub("\n", "\n\t")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
extend self
|
22
|
+
attr_reader :gem_load_error
|
23
|
+
|
24
|
+
def get
|
25
|
+
if impl = implementation
|
26
|
+
impl.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def implementation
|
31
|
+
defined?(@implementation) ? @implementation : (@implementation = find_usable_implementation)
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_usable_implementation
|
35
|
+
[Darwin, Linux, Windows].find(&:usable?)
|
36
|
+
rescue MissingNativeGem => e
|
37
|
+
@gem_load_error ||= e.message
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
class Base
|
42
|
+
def initialize
|
43
|
+
@watcher = create_watcher
|
44
|
+
@directories = Set.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def watch(directories, &block)
|
48
|
+
@directories.merge(directories)
|
49
|
+
watch_internal(directories, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def start!
|
53
|
+
@thread = Thread.new { start_watcher! }
|
54
|
+
end
|
55
|
+
|
56
|
+
def stop
|
57
|
+
@watcher.stop
|
58
|
+
stop_thread
|
59
|
+
end
|
60
|
+
|
61
|
+
def alive_and_watching?(directories)
|
62
|
+
@thread.alive? && directories.all? {|directory| @directories.include?(directory)}
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def watch_internal(directories, &block)
|
67
|
+
@watcher.watch(directories, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def stop_thread
|
71
|
+
@thread.join if @thread
|
72
|
+
end
|
73
|
+
|
74
|
+
def start_watcher!
|
75
|
+
@watcher.run
|
76
|
+
end
|
77
|
+
|
78
|
+
class << self
|
79
|
+
def usable?
|
80
|
+
gem_check! if platform_match?
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def platform_match?
|
85
|
+
require 'rbconfig'
|
86
|
+
RbConfig::CONFIG['target_os'] =~ self::TARGET_OS_REGEX
|
87
|
+
end
|
88
|
+
|
89
|
+
def gem_check!
|
90
|
+
defined?(@gem_loaded) ? @gem_loaded : @gem_loaded = load_gem!
|
91
|
+
end
|
92
|
+
|
93
|
+
def load_gem!
|
94
|
+
gem(self::GEM_NAME, self::GEM_VERSION)
|
95
|
+
require(self::GEM_NAME)
|
96
|
+
true
|
97
|
+
rescue Gem::LoadError
|
98
|
+
raise MissingNativeGem.new(self::GEM_NAME, self::GEM_VERSION)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Darwin < Base
|
104
|
+
TARGET_OS_REGEX = /darwin(1.+)?$/i
|
105
|
+
GEM_NAME = 'rb-fsevent'
|
106
|
+
GEM_VERSION = '>= 0.9.1'
|
107
|
+
|
108
|
+
private
|
109
|
+
def create_watcher
|
110
|
+
FSEvent.new
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Errors, comments and other gotchas taken from Listen gem (https://github.com/guard/listen)
|
115
|
+
class Linux < Base
|
116
|
+
TARGET_OS_REGEX = /linux/i
|
117
|
+
GEM_NAME = 'rb-inotify'
|
118
|
+
GEM_VERSION = '>= 0.8.8'
|
119
|
+
|
120
|
+
EVENTS = [:recursive, :attrib, :create, :delete, :move, :close_write]
|
121
|
+
|
122
|
+
# The message to show when the limit of inotify watchers is not enough
|
123
|
+
#
|
124
|
+
INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '')
|
125
|
+
Listen error: unable to monitor directories for changes.
|
126
|
+
|
127
|
+
Please head to https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers
|
128
|
+
for information on how to solve this issue.
|
129
|
+
EOS
|
130
|
+
|
131
|
+
private
|
132
|
+
def watch_internal(directories)
|
133
|
+
directories.each do |directory|
|
134
|
+
@watcher.watch(directory, *EVENTS) do |event|
|
135
|
+
unless root?(event) || file_event_on_a_dir?(event)
|
136
|
+
if File.file?(absolute_name = event.absolute_name)
|
137
|
+
yield [File.dirname(absolute_name)]
|
138
|
+
elsif File.directory?(absolute_name)
|
139
|
+
yield [absolute_name]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
rescue Errno::ENOSPC
|
145
|
+
abort(INOTIFY_LIMIT_MESSAGE)
|
146
|
+
end
|
147
|
+
|
148
|
+
def root?(event) # Event on root directory
|
149
|
+
event.name.empty? # same as event.name == ""
|
150
|
+
end
|
151
|
+
|
152
|
+
# INotify reports changes to files inside directories as events
|
153
|
+
# on the directories themselves too.
|
154
|
+
#
|
155
|
+
# @see http://linux.die.net/man/7/inotify
|
156
|
+
def file_event_on_a_dir?(event)
|
157
|
+
# event.flags.include?(:isdir) and event.flags & [:close, :modify] != []
|
158
|
+
flags = event.flags
|
159
|
+
flags.include?(:isdir) && (flags.include?(:close) || flags.include?(:modify))
|
160
|
+
end
|
161
|
+
|
162
|
+
def create_watcher
|
163
|
+
INotify::Notifier.new
|
164
|
+
end
|
165
|
+
|
166
|
+
def stop_thread
|
167
|
+
Thread.kill(@thread) if @thread
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class Windows < Base
|
172
|
+
TARGET_OS_REGEX = /mswin|mingw|cygwin/i
|
173
|
+
GEM_NAME = 'wdm'
|
174
|
+
GEM_VERSION = '>= 0.0.3'
|
175
|
+
|
176
|
+
private
|
177
|
+
def watch_internal(directories)
|
178
|
+
directories.each do |directory|
|
179
|
+
@watcher.watch_recursively(directory) do |change|
|
180
|
+
yield [File.dirname(change.path)]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def create_watcher
|
186
|
+
WDM::Monitor.new
|
187
|
+
end
|
188
|
+
|
189
|
+
def start_watcher!
|
190
|
+
@watcher.run!
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -18,13 +18,22 @@ module RailsDevelopmentBoost
|
|
18
18
|
|
19
19
|
patch = self
|
20
20
|
ActiveSupport::Dependencies.module_eval do
|
21
|
-
|
21
|
+
unless method_defined?(:local_const_defined?) # pre 4da45060 compatibility
|
22
|
+
if method_defined?(:uninherited_const_defined?)
|
23
|
+
alias_method :local_const_defined?, :uninherited_const_defined?
|
24
|
+
else # post 4.0 compat
|
25
|
+
def local_const_defined?(mod, const_name)
|
26
|
+
mod.const_defined?(const_name, false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
22
30
|
remove_possible_method :remove_unloadable_constants!
|
23
31
|
remove_possible_method :clear
|
24
32
|
include patch
|
25
33
|
alias_method_chain :load_file, 'constant_tracking'
|
26
34
|
alias_method_chain :remove_constant, 'handling_of_connections'
|
27
35
|
extend patch
|
36
|
+
@routes_path_loading = nil
|
28
37
|
end
|
29
38
|
|
30
39
|
ActiveSupport::Dependencies::Loadable.module_eval do
|
@@ -45,23 +54,24 @@ module RailsDevelopmentBoost
|
|
45
54
|
end
|
46
55
|
end
|
47
56
|
|
48
|
-
def self.async
|
49
|
-
@async =
|
57
|
+
def self.async=(new_value)
|
58
|
+
@async = Async.process_new_async_value(new_value)
|
50
59
|
end
|
51
60
|
|
52
61
|
def self.async?
|
53
62
|
@async
|
54
63
|
end
|
55
64
|
|
65
|
+
def self.enable_async_mode_by_default!
|
66
|
+
Async.enable_by_default!(defined?(@async))
|
67
|
+
end
|
68
|
+
|
56
69
|
def self.applied?
|
57
70
|
ActiveSupport::Dependencies < self
|
58
71
|
end
|
59
72
|
|
60
73
|
autoload :InstrumentationPatch, 'rails_development_boost/dependencies_patch/instrumentation_patch'
|
61
74
|
|
62
|
-
mattr_accessor :constants_being_removed
|
63
|
-
self.constants_being_removed = []
|
64
|
-
|
65
75
|
mattr_accessor :explicit_dependencies
|
66
76
|
self.explicit_dependencies = {}
|
67
77
|
|
@@ -91,6 +101,12 @@ module RailsDevelopmentBoost
|
|
91
101
|
end while const_name.sub!(/::[^:]+\Z/, NOTHING)
|
92
102
|
false
|
93
103
|
end
|
104
|
+
|
105
|
+
def load_path_to_real_path(path)
|
106
|
+
expanded_path = File.expand_path(path)
|
107
|
+
expanded_path << '.rb' unless path =~ /\.r(?:b|ake)\Z/
|
108
|
+
expanded_path
|
109
|
+
end
|
94
110
|
end
|
95
111
|
|
96
112
|
class ModuleCache
|
@@ -154,12 +170,50 @@ module RailsDevelopmentBoost
|
|
154
170
|
end
|
155
171
|
end
|
156
172
|
|
173
|
+
class ConstantsHeap < Array
|
174
|
+
NAMESPACE_SEPARATOR = '::'
|
175
|
+
|
176
|
+
def initialize(*args)
|
177
|
+
super
|
178
|
+
@seen = Set.new
|
179
|
+
end
|
180
|
+
|
181
|
+
def add_const?(const_name, force_insert = false)
|
182
|
+
if @seen.add?(const_name) || force_insert
|
183
|
+
(self[const_name.count(NAMESPACE_SEPARATOR)] ||= []) << const_name
|
184
|
+
true
|
185
|
+
else
|
186
|
+
false
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def pop_next_const
|
191
|
+
reverse_each do |slot|
|
192
|
+
if const_name = slot.try(:pop)
|
193
|
+
return const_name
|
194
|
+
end
|
195
|
+
end
|
196
|
+
nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def seen?(const_name)
|
200
|
+
@seen.include?(const_name)
|
201
|
+
end
|
202
|
+
|
203
|
+
def clear_seen
|
204
|
+
@seen.clear
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
mattr_accessor :constants_to_remove
|
209
|
+
self.constants_to_remove = ConstantsHeap.new
|
210
|
+
|
157
211
|
def unload_modified_files!
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
212
|
+
async_synchronize do
|
213
|
+
unloaded_something = unload_modified_files_internal!
|
214
|
+
load_failure = clear_load_failure
|
215
|
+
unloaded_something || load_failure
|
216
|
+
end
|
163
217
|
end
|
164
218
|
|
165
219
|
def remove_explicitely_unloadable_constants!
|
@@ -173,7 +227,10 @@ module RailsDevelopmentBoost
|
|
173
227
|
# Augmented `load_file'.
|
174
228
|
def load_file_with_constant_tracking(path, *args)
|
175
229
|
async_synchronize do
|
176
|
-
@module_cache
|
230
|
+
if @module_cache
|
231
|
+
@module_cache = nil
|
232
|
+
constants_to_remove.clear_seen
|
233
|
+
end
|
177
234
|
load_file_with_constant_tracking_internal(path, args)
|
178
235
|
end
|
179
236
|
end
|
@@ -187,6 +244,14 @@ module RailsDevelopmentBoost
|
|
187
244
|
currently_loading.pop
|
188
245
|
end
|
189
246
|
|
247
|
+
def loading_routes_file(path)
|
248
|
+
prev_value = @routes_path_loading
|
249
|
+
@routes_path_loading = Util.load_path_to_real_path(path)
|
250
|
+
yield
|
251
|
+
ensure
|
252
|
+
@routes_path_loading = prev_value
|
253
|
+
end
|
254
|
+
|
190
255
|
def associate_constants_to_file(constants, file_path)
|
191
256
|
# freezing strings before using them as Hash keys is slightly more memory efficient
|
192
257
|
constants.map!(&:freeze)
|
@@ -195,13 +260,32 @@ module RailsDevelopmentBoost
|
|
195
260
|
LoadedFile.for(file_path).add_constants(constants)
|
196
261
|
end
|
197
262
|
|
263
|
+
def schedule_const_for_unloading(const_name)
|
264
|
+
if constants_to_remove.add_const?(const_name)
|
265
|
+
if qualified_const_defined?(const_name) && object = const_name.constantize
|
266
|
+
@module_cache ||= ModuleCache.new # make sure module_cache has been created
|
267
|
+
schedule_dependent_constants_for_removal(const_name, object)
|
268
|
+
end
|
269
|
+
true
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def process_consts_scheduled_for_removal
|
274
|
+
unless @now_removing_const
|
275
|
+
@now_removing_const = true
|
276
|
+
begin
|
277
|
+
process_consts_scheduled_for_removal_internal
|
278
|
+
ensure
|
279
|
+
@now_removing_const = nil
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
198
284
|
# Augmented `remove_constant'.
|
199
285
|
def remove_constant_with_handling_of_connections(const_name)
|
200
286
|
async_synchronize do
|
201
|
-
|
202
|
-
|
203
|
-
unprotected_remove_constant(const_name)
|
204
|
-
end
|
287
|
+
schedule_const_for_unloading(const_name)
|
288
|
+
process_consts_scheduled_for_removal
|
205
289
|
end
|
206
290
|
end
|
207
291
|
|
@@ -247,6 +331,20 @@ module RailsDevelopmentBoost
|
|
247
331
|
end
|
248
332
|
|
249
333
|
private
|
334
|
+
def process_consts_scheduled_for_removal_internal
|
335
|
+
heap = constants_to_remove
|
336
|
+
result = nil
|
337
|
+
while const_to_remove = heap.pop_next_const
|
338
|
+
begin
|
339
|
+
result = unprotected_remove_constant(const_to_remove, qualified_const_defined?(const_to_remove) && const_to_remove.constantize)
|
340
|
+
rescue Exception
|
341
|
+
heap.add_const?(const_to_remove, true)
|
342
|
+
raise
|
343
|
+
end
|
344
|
+
end
|
345
|
+
result
|
346
|
+
end
|
347
|
+
|
250
348
|
def unload_modified_files_internal!
|
251
349
|
log_call
|
252
350
|
if DependenciesPatch.async?
|
@@ -270,6 +368,9 @@ module RailsDevelopmentBoost
|
|
270
368
|
|
271
369
|
# Associate newly loaded constants to the file just loaded
|
272
370
|
associate_constants_to_file(new_constants, path)
|
371
|
+
if routes_path_loading = @routes_path_loading
|
372
|
+
RoutesLoadedFile.for(routes_path_loading).associate_with(LoadedFile.for(path))
|
373
|
+
end
|
273
374
|
end
|
274
375
|
|
275
376
|
result
|
@@ -282,16 +383,17 @@ module RailsDevelopmentBoost
|
|
282
383
|
yield
|
283
384
|
end
|
284
385
|
end
|
285
|
-
|
286
|
-
def
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
remove_child_module_constants(object, const_name)
|
293
|
-
end
|
386
|
+
|
387
|
+
def schedule_dependent_constants_for_removal(const_name, object)
|
388
|
+
handle_connected_constants(object, const_name)
|
389
|
+
LoadedFile.schedule_for_unloading_files_with_const!(const_name)
|
390
|
+
if object.kind_of?(Module)
|
391
|
+
remove_parent_modules_if_autoloaded(object)
|
392
|
+
remove_child_module_constants(object, const_name)
|
294
393
|
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def unprotected_remove_constant(const_name, object)
|
295
397
|
result = remove_constant_without_handling_of_connections(const_name)
|
296
398
|
clear_tracks_of_removed_const(const_name, object)
|
297
399
|
result
|
@@ -299,17 +401,19 @@ module RailsDevelopmentBoost
|
|
299
401
|
|
300
402
|
def error_loading_file(file_path, e)
|
301
403
|
LoadedFile.for(file_path).stale! if LoadedFile.loaded?(file_path)
|
302
|
-
|
404
|
+
# only the errors that blow through the full stack are load failures, this lets user code handle failed load failures by rescuing raised exceptions without triggering a full dependecies reload
|
405
|
+
@load_failure = true if currently_loading.size == 1
|
303
406
|
raise e
|
304
407
|
end
|
305
408
|
|
306
409
|
def handle_connected_constants(object, const_name)
|
307
410
|
return unless Module === object && qualified_const_defined?(const_name)
|
411
|
+
remove_nested_constants(const_name)
|
308
412
|
remove_explicit_dependencies_of(const_name)
|
309
413
|
remove_dependent_modules(object)
|
414
|
+
# TODO move these into the cleanup phase
|
310
415
|
update_activerecord_related_references(object)
|
311
416
|
update_mongoid_related_references(object)
|
312
|
-
remove_nested_constants(const_name)
|
313
417
|
end
|
314
418
|
|
315
419
|
def remove_nested_constants(const_name)
|
@@ -317,7 +421,7 @@ module RailsDevelopmentBoost
|
|
317
421
|
end
|
318
422
|
|
319
423
|
def remove_nested_constant(parent_const, child_const)
|
320
|
-
|
424
|
+
schedule_const_for_unloading(child_const)
|
321
425
|
end
|
322
426
|
|
323
427
|
# AS::Dependencies doesn't track same-file nested constants, so we need to look out for them on our own.
|
@@ -339,7 +443,7 @@ module RailsDevelopmentBoost
|
|
339
443
|
end
|
340
444
|
|
341
445
|
def remove_autoloaded_parent_module(initial_object, parent_object)
|
342
|
-
|
446
|
+
schedule_const_for_unloading(parent_object._mod_name)
|
343
447
|
end
|
344
448
|
|
345
449
|
def autoloaded_object?(object) # faster than going through Dependencies.autoloaded?
|
@@ -373,7 +477,7 @@ module RailsDevelopmentBoost
|
|
373
477
|
end
|
374
478
|
|
375
479
|
def remove_child_module_constant(parent_object, full_child_const_name)
|
376
|
-
|
480
|
+
schedule_const_for_unloading(full_child_const_name)
|
377
481
|
end
|
378
482
|
|
379
483
|
def remove_explicit_dependencies_of(const_name)
|
@@ -385,21 +489,23 @@ module RailsDevelopmentBoost
|
|
385
489
|
end
|
386
490
|
|
387
491
|
def remove_explicit_dependency(const_name, depending_const)
|
388
|
-
|
492
|
+
schedule_const_for_unloading(depending_const)
|
389
493
|
end
|
390
494
|
|
391
495
|
def clear_tracks_of_removed_const(const_name, object = nil)
|
392
496
|
autoloaded_constants.delete(const_name)
|
393
|
-
@module_cache
|
497
|
+
# @module_cache might be nil if remove_constant has been called with a non-existent constant, ie: it hasn't been checked with `qualified_const_defined?`. Because AS::Dep doesn't blow, neither
|
498
|
+
# should we.
|
499
|
+
@module_cache.remove_const(const_name, object) if @module_cache
|
394
500
|
LoadedFile.const_unloaded(const_name)
|
395
501
|
end
|
396
502
|
|
397
503
|
def remove_dependent_modules(mod)
|
398
|
-
module_cache.each_dependent_on(mod) {|other| remove_dependent_constant(mod, other)}
|
504
|
+
@module_cache.each_dependent_on(mod) {|other| remove_dependent_constant(mod, other)}
|
399
505
|
end
|
400
506
|
|
401
507
|
def remove_dependent_constant(original_module, dependent_module)
|
402
|
-
|
508
|
+
schedule_const_for_unloading(dependent_module._mod_name)
|
403
509
|
end
|
404
510
|
|
405
511
|
AR_REFLECTION_CACHES = [:@klass]
|
@@ -419,10 +525,10 @@ module RailsDevelopmentBoost
|
|
419
525
|
def update_mongoid_related_references(klass)
|
420
526
|
if defined?(Mongoid::Document) && klass < Mongoid::Document
|
421
527
|
while (superclass = Util.first_non_anonymous_superclass(superclass || klass)) != Object && superclass < Mongoid::Document
|
422
|
-
|
528
|
+
schedule_const_for_unloading(superclass._mod_name) # this is necessary to nuke the @_types caches
|
423
529
|
end
|
424
530
|
|
425
|
-
module_cache.each_dependent_on(Mongoid::Document) do |model|
|
531
|
+
@module_cache.each_dependent_on(Mongoid::Document) do |model|
|
426
532
|
clean_up_relation_caches(model.relations, klass, MONGOID_RELATION_CACHES)
|
427
533
|
end
|
428
534
|
end
|
@@ -435,20 +541,5 @@ module RailsDevelopmentBoost
|
|
435
541
|
end
|
436
542
|
end
|
437
543
|
end
|
438
|
-
|
439
|
-
def module_cache
|
440
|
-
@module_cache ||= ModuleCache.new
|
441
|
-
end
|
442
|
-
|
443
|
-
def prevent_further_removal_of(const_name)
|
444
|
-
return if constants_being_removed.include?(const_name)
|
445
|
-
|
446
|
-
constants_being_removed << const_name
|
447
|
-
begin
|
448
|
-
yield
|
449
|
-
ensure
|
450
|
-
constants_being_removed.delete(const_name)
|
451
|
-
end
|
452
|
-
end
|
453
544
|
end
|
454
545
|
end
|
@@ -2,7 +2,7 @@ module RailsDevelopmentBoost
|
|
2
2
|
module DependenciesPatch
|
3
3
|
module InstrumentationPatch
|
4
4
|
module Instrumenter
|
5
|
-
delegate :boost_log, :to => 'ActiveSupport::Dependencies'
|
5
|
+
delegate :boost_log, :boost_log_nested, :boost_log_schedule_const_removal, :to => 'ActiveSupport::Dependencies'
|
6
6
|
|
7
7
|
def self.included(mod)
|
8
8
|
mod.extend(ClassMethods)
|
@@ -38,9 +38,14 @@ module RailsDevelopmentBoost
|
|
38
38
|
add_constants_without_instrumentation(new_constants)
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
42
|
-
boost_log('
|
43
|
-
|
41
|
+
def dependent_file_schedule_for_unloading_with_instrumentation!(dependent_file)
|
42
|
+
boost_log('SCHEDULE_DEPENDENT', "#{boost_inspect}: #{dependent_file.boost_inspect}")
|
43
|
+
dependent_file_schedule_for_unloading_without_instrumentation!(dependent_file)
|
44
|
+
end
|
45
|
+
|
46
|
+
def schedule_const_for_unloading_with_instrumentation(const_name)
|
47
|
+
boost_log_schedule_const_removal('SCHEDULE_REMOVAL', const_name, const_name)
|
48
|
+
schedule_const_for_unloading_without_instrumentation(const_name)
|
44
49
|
end
|
45
50
|
|
46
51
|
private
|
@@ -69,9 +74,9 @@ module RailsDevelopmentBoost
|
|
69
74
|
end
|
70
75
|
end
|
71
76
|
|
72
|
-
def
|
73
|
-
boost_log('
|
74
|
-
|
77
|
+
def schedule_containing_file_with_instrumentation(const_name, file)
|
78
|
+
boost_log('SCHEDULE_CONTAINING_FILE', "#{const_name} -> #{file.boost_inspect}")
|
79
|
+
schedule_containing_file_without_instrumentation(const_name, file)
|
75
80
|
end
|
76
81
|
end
|
77
82
|
end
|
@@ -79,14 +84,14 @@ module RailsDevelopmentBoost
|
|
79
84
|
module Files
|
80
85
|
include Instrumenter
|
81
86
|
|
82
|
-
def
|
87
|
+
def schedule_modified_file_with_instrumentation(file)
|
83
88
|
boost_log('CHANGED', "#{file.boost_inspect}")
|
84
|
-
|
89
|
+
boost_log_nested { schedule_modified_file_without_instrumentation(file) }
|
85
90
|
end
|
86
91
|
|
87
|
-
def
|
88
|
-
boost_log('
|
89
|
-
|
92
|
+
def schedule_decorator_file_with_instrumentation(file)
|
93
|
+
boost_log('SCHEDULE_DECORATOR_FILE', "#{file.boost_inspect}")
|
94
|
+
schedule_decorator_file_without_instrumentation(file)
|
90
95
|
end
|
91
96
|
end
|
92
97
|
|
@@ -123,15 +128,23 @@ module RailsDevelopmentBoost
|
|
123
128
|
raw_boost_log("#{ "[#{action}] " if action}#{msg}")
|
124
129
|
end
|
125
130
|
|
126
|
-
|
127
|
-
|
128
|
-
boost_log(
|
131
|
+
def boost_log_schedule_const_removal(action, msg, const_name)
|
132
|
+
action = "#{action} | #{ActiveSupport::Dependencies.constants_to_remove.seen?(const_name) ? 'SKIP' : 'SCHEDULED'}"
|
133
|
+
boost_log(action, msg)
|
134
|
+
end
|
135
|
+
|
136
|
+
def boost_log_nested
|
129
137
|
@removal_nesting = (@removal_nesting || 0) + 1
|
130
|
-
|
138
|
+
yield
|
131
139
|
ensure
|
132
140
|
@removal_nesting -= 1
|
133
141
|
end
|
134
142
|
|
143
|
+
private
|
144
|
+
def schedule_dependent_constants_for_removal(const_name, object)
|
145
|
+
boost_log_nested { super }
|
146
|
+
end
|
147
|
+
|
135
148
|
def error_loading_file(file_path, e)
|
136
149
|
description = RailsDevelopmentBoost::LoadedFile.loaded?(file_path) ? RailsDevelopmentBoost::LoadedFile.for(file_path).boost_inspect : file_path
|
137
150
|
boost_log('ERROR_WHILE_LOADING', "#{description}: #{e.inspect}")
|
@@ -139,32 +152,34 @@ module RailsDevelopmentBoost
|
|
139
152
|
end
|
140
153
|
|
141
154
|
def remove_explicit_dependency(const_name, depending_const)
|
142
|
-
|
155
|
+
boost_log_schedule_const_removal('EXPLICIT_DEPENDENCY', "#{const_name} -> #{depending_const}", depending_const)
|
143
156
|
super
|
144
157
|
end
|
145
158
|
|
146
159
|
def remove_dependent_constant(original_module, dependent_module)
|
147
|
-
|
160
|
+
const_name = dependent_module._mod_name
|
161
|
+
boost_log_schedule_const_removal('DEPENDENT_MODULE', "#{original_module._mod_name} -> #{const_name}", const_name)
|
148
162
|
super
|
149
163
|
end
|
150
164
|
|
151
165
|
def remove_autoloaded_parent_module(initial_object, parent_object)
|
152
|
-
|
166
|
+
const_name = parent_object._mod_name
|
167
|
+
boost_log_schedule_const_removal('REMOVE_PARENT', "#{initial_object._mod_name} -> #{const_name}", const_name)
|
153
168
|
super
|
154
169
|
end
|
155
170
|
|
156
171
|
def remove_child_module_constant(parent_object, full_child_const_name)
|
157
|
-
|
172
|
+
boost_log_schedule_const_removal('REMOVE_CHILD', "#{parent_object._mod_name} -> #{full_child_const_name}", full_child_const_name)
|
158
173
|
super
|
159
174
|
end
|
160
175
|
|
161
176
|
def remove_nested_constant(parent_const, child_const)
|
162
|
-
|
177
|
+
boost_log_schedule_const_removal('REMOVE_NESTED', "#{parent_const} :: #{child_const.sub(/\A#{parent_const}::/, '')}", child_const)
|
163
178
|
super
|
164
179
|
end
|
165
180
|
|
166
181
|
def raw_boost_log(msg)
|
167
|
-
Rails.logger.info("[DEV-BOOST] #{"
|
182
|
+
Rails.logger.info("[DEV-BOOST] #{" " * (@removal_nesting || 0)}#{msg}")
|
168
183
|
end
|
169
184
|
end
|
170
185
|
end
|
@@ -3,8 +3,16 @@ require 'active_support/descendants_tracker'
|
|
3
3
|
module RailsDevelopmentBoost
|
4
4
|
module DescendantsTrackerPatch
|
5
5
|
def self.apply!
|
6
|
+
# removing the .clear method across all Rails/Ruby versions
|
7
|
+
begin
|
8
|
+
ActiveSupport::DescendantsTracker.send(:remove_method, :clear)
|
9
|
+
rescue NameError
|
10
|
+
end
|
11
|
+
begin
|
12
|
+
ActiveSupport::DescendantsTracker.singleton_class.send(:remove_method, :clear)
|
13
|
+
rescue NameError
|
14
|
+
end
|
6
15
|
ActiveSupport::DescendantsTracker.extend self
|
7
|
-
ActiveSupport::DescendantsTracker.singleton_class.remove_possible_method :clear
|
8
16
|
end
|
9
17
|
|
10
18
|
def delete(klass)
|
@@ -5,11 +5,10 @@ module RailsDevelopmentBoost
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def load(file, wrap = false)
|
8
|
-
|
8
|
+
real_path = DependenciesPatch::Util.load_path_to_real_path(file)
|
9
9
|
# force the manual #load calls for autoloadable files to go through the AS::Dep stack
|
10
|
-
if ActiveSupport::Dependencies.in_autoload_path?(
|
11
|
-
|
12
|
-
ActiveSupport::Dependencies.load_file_from_explicit_load(expanded_path)
|
10
|
+
if ActiveSupport::Dependencies.in_autoload_path?(real_path)
|
11
|
+
ActiveSupport::Dependencies.load_file_from_explicit_load(real_path)
|
13
12
|
else
|
14
13
|
super
|
15
14
|
end
|
@@ -1,40 +1,69 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module RailsDevelopmentBoost
|
2
4
|
class LoadedFile
|
3
5
|
class Files < Hash
|
4
6
|
def initialize(*args)
|
7
|
+
@directories = {}
|
5
8
|
super {|hash, file_path| hash[file_path] = LoadedFile.new(file_path)}
|
6
9
|
end
|
7
10
|
|
8
|
-
def
|
9
|
-
|
11
|
+
def []=(file_path, loaded_file)
|
12
|
+
(@directories[loaded_file.dirname] ||= Set.new) << loaded_file
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(file_path)
|
17
|
+
if loaded_file = super
|
18
|
+
dirname = loaded_file.dirname
|
19
|
+
if @directories[dirname].delete(loaded_file).empty?
|
20
|
+
@directories.delete(dirname)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
loaded_file
|
10
24
|
end
|
11
25
|
|
12
|
-
def
|
26
|
+
def unload_modified!(filter_directories = nil)
|
13
27
|
unloaded_something = false
|
14
|
-
|
15
|
-
if
|
16
|
-
|
28
|
+
find_files_in(filter_directories).each do |file|
|
29
|
+
if file.changed?
|
30
|
+
schedule_modified_file(file)
|
17
31
|
unloaded_something = true
|
18
32
|
end
|
19
33
|
end
|
20
34
|
if unloaded_something
|
21
35
|
values.each do |file|
|
22
|
-
|
36
|
+
schedule_decorator_file(file) if file.decorator_like?
|
23
37
|
end
|
24
38
|
end
|
39
|
+
ActiveSupport::Dependencies.process_consts_scheduled_for_removal
|
25
40
|
unloaded_something
|
26
41
|
end
|
27
42
|
|
28
|
-
def
|
29
|
-
|
43
|
+
def find_files_in(filter_directories = nil)
|
44
|
+
if filter_directories
|
45
|
+
arr = []
|
46
|
+
@directories.each_pair do |dirname, files|
|
47
|
+
arr.concat(files.to_a) if filter_directories.any? {|filter_directory| dirname.starts_with?(filter_directory)}
|
48
|
+
end
|
49
|
+
arr
|
50
|
+
else
|
51
|
+
values
|
52
|
+
end
|
30
53
|
end
|
31
54
|
|
32
|
-
def
|
33
|
-
file.
|
55
|
+
def schedule_modified_file(file)
|
56
|
+
file.schedule_consts_for_unloading!
|
57
|
+
end
|
58
|
+
|
59
|
+
def schedule_decorator_file(file)
|
60
|
+
file.schedule_consts_for_unloading!
|
34
61
|
end
|
35
62
|
|
36
63
|
def constants
|
37
|
-
|
64
|
+
arr = []
|
65
|
+
each_value {|file| arr.concat(file.constants)}
|
66
|
+
arr
|
38
67
|
end
|
39
68
|
|
40
69
|
def stored?(file)
|
@@ -110,16 +139,20 @@ module RailsDevelopmentBoost
|
|
110
139
|
@constants |= new_constants
|
111
140
|
end
|
112
141
|
|
142
|
+
def dirname
|
143
|
+
File.dirname(@path)
|
144
|
+
end
|
145
|
+
|
113
146
|
# "decorator" files are popular with certain Rails frameworks (spree/refinerycms etc.) they don't define their own constants, instead
|
114
147
|
# they are usually used for adding methods to other classes via Model.class_eval { def meth; end }
|
115
148
|
def decorator_like?
|
116
149
|
@constants.empty? && !INTERDEPENDENCIES[self]
|
117
150
|
end
|
118
151
|
|
119
|
-
def
|
152
|
+
def schedule_consts_for_unloading!
|
120
153
|
guard_double_unloading do
|
121
|
-
INTERDEPENDENCIES.each_dependent_on(self) {|dependent_file|
|
122
|
-
@constants.dup.each {|const|
|
154
|
+
INTERDEPENDENCIES.each_dependent_on(self) {|dependent_file| dependent_file_schedule_for_unloading!(dependent_file)}
|
155
|
+
@constants.dup.each {|const| schedule_const_for_unloading(const)}
|
123
156
|
clean_up_if_necessary
|
124
157
|
end
|
125
158
|
end
|
@@ -166,8 +199,8 @@ module RailsDevelopmentBoost
|
|
166
199
|
@path.eql?(other)
|
167
200
|
end
|
168
201
|
|
169
|
-
def
|
170
|
-
dependent_file.
|
202
|
+
def dependent_file_schedule_for_unloading!(dependent_file)
|
203
|
+
dependent_file.schedule_consts_for_unloading!
|
171
204
|
end
|
172
205
|
|
173
206
|
def guard_double_unloading
|
@@ -206,6 +239,10 @@ module RailsDevelopmentBoost
|
|
206
239
|
INTERDEPENDENCIES.each_dependent_on(self, &:stale!)
|
207
240
|
end
|
208
241
|
|
242
|
+
def schedule_const_for_unloading(const_name)
|
243
|
+
ActiveSupport::Dependencies.schedule_const_for_unloading(const_name)
|
244
|
+
end
|
245
|
+
|
209
246
|
class << self
|
210
247
|
def unload_modified!
|
211
248
|
LOADED.unload_modified!
|
@@ -227,12 +264,12 @@ module RailsDevelopmentBoost
|
|
227
264
|
CONSTANTS_TO_FILES[const_name]
|
228
265
|
end
|
229
266
|
|
230
|
-
def
|
231
|
-
CONSTANTS_TO_FILES.each_file_with_const(const_name) {|file|
|
267
|
+
def schedule_for_unloading_files_with_const!(const_name)
|
268
|
+
CONSTANTS_TO_FILES.each_file_with_const(const_name) {|file| schedule_containing_file(const_name, file)}
|
232
269
|
end
|
233
270
|
|
234
|
-
def
|
235
|
-
file.
|
271
|
+
def schedule_containing_file(const_name, file)
|
272
|
+
file.schedule_consts_for_unloading!
|
236
273
|
end
|
237
274
|
|
238
275
|
def const_unloaded(const_name)
|
@@ -250,4 +287,28 @@ module RailsDevelopmentBoost
|
|
250
287
|
File.mtime(@path).to_i rescue nil
|
251
288
|
end
|
252
289
|
end
|
290
|
+
|
291
|
+
class RoutesLoadedFile < LoadedFile
|
292
|
+
def decorator_like?
|
293
|
+
false
|
294
|
+
end
|
295
|
+
|
296
|
+
def changed?
|
297
|
+
false
|
298
|
+
end
|
299
|
+
|
300
|
+
def schedule_consts_for_unloading!
|
301
|
+
Reloader.routes_reloader.force_execute!
|
302
|
+
end
|
303
|
+
|
304
|
+
def add_constants(new_constants)
|
305
|
+
end
|
306
|
+
|
307
|
+
def clean_up_if_necessary
|
308
|
+
end
|
309
|
+
|
310
|
+
def self.for(file_path)
|
311
|
+
LOADED.loaded?(file_path) ? LOADED[file_path] : LOADED[file_path] = new(file_path)
|
312
|
+
end
|
313
|
+
end
|
253
314
|
end
|
@@ -1,10 +1,41 @@
|
|
1
1
|
module RailsDevelopmentBoost
|
2
2
|
module Reloader # replacement for the Rails' post fa1d9a file_update_checker
|
3
|
+
module RoutesReloaderPatch
|
4
|
+
def force_execute!
|
5
|
+
@force_execute = true
|
6
|
+
end
|
7
|
+
|
8
|
+
def updated?
|
9
|
+
@force_execute = true if result = super
|
10
|
+
result
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
if RailsDevelopmentBoost.reload_routes_on_any_change || @in_execute_if_updated || @force_execute || updated?
|
15
|
+
@force_execute = false
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load(file, wrap = false)
|
21
|
+
ActiveSupport::Dependencies.loading_routes_file(file) { super }
|
22
|
+
end
|
23
|
+
|
24
|
+
def execute_if_updated
|
25
|
+
old_in_execute_if_updated = @in_execute_if_updated
|
26
|
+
@in_execute_if_updated = true
|
27
|
+
super
|
28
|
+
ensure
|
29
|
+
@in_execute_if_updated = old_in_execute_if_updated
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
3
33
|
extend self
|
4
34
|
|
5
35
|
def hook_in!
|
6
36
|
Rails.application.reloaders.unshift(self)
|
7
37
|
ActionDispatch::Reloader.to_prepare(:prepend => true) { RailsDevelopmentBoost::Reloader.execute_if_updated }
|
38
|
+
patch_routes_reloader! if Rails::VERSION::MAJOR >= 4
|
8
39
|
end
|
9
40
|
|
10
41
|
def execute
|
@@ -20,7 +51,19 @@ module RailsDevelopmentBoost
|
|
20
51
|
|
21
52
|
alias_method :updated?, :execute
|
22
53
|
|
54
|
+
def routes_reloader
|
55
|
+
Rails.application.respond_to?(:routes_reloader) && Rails.application.routes_reloader
|
56
|
+
end
|
57
|
+
|
23
58
|
private
|
59
|
+
# Rails 4.0+ calls routes_reloader.execute instead of routes_reloader.execute_if_updated because an autoloaded Rails::Engine might be mounted via routes.rb, therefore if any constants
|
60
|
+
# are unloaded this triggers the reloading of routes.rb, we would like to avoid that.
|
61
|
+
def patch_routes_reloader!
|
62
|
+
if reloader = routes_reloader
|
63
|
+
reloader.extend(RoutesReloaderPatch)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
24
67
|
def init
|
25
68
|
Rails.application.reloaders.delete_if do |reloader|
|
26
69
|
if rails_file_checker?(reloader)
|
@@ -43,7 +86,7 @@ module RailsDevelopmentBoost
|
|
43
86
|
if (dir_glob = reloader.instance_variable_get(:@glob)).kind_of?(String)
|
44
87
|
autoload_paths = ActiveSupport::Dependencies.autoload_paths
|
45
88
|
dir_glob.sub(/\A\{/, '').sub(/\}\Z/, '').split(',').all? do |glob_path|
|
46
|
-
autoload_paths.any? {|autoload_path| glob_path.starts_with?(autoload_path)}
|
89
|
+
autoload_paths.any? {|autoload_path| glob_path.starts_with?(autoload_path.to_s)}
|
47
90
|
end
|
48
91
|
end
|
49
92
|
end
|
metadata
CHANGED
@@ -1,50 +1,40 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-dev-boost
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 1
|
10
|
-
version: 0.2.1
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Roman Le Negrate
|
14
8
|
- thedarkone
|
15
9
|
autorequire:
|
16
10
|
bindir: bin
|
17
11
|
cert_chain: []
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
requirements:
|
28
|
-
- - ">="
|
29
|
-
- !ruby/object:Gem::Version
|
30
|
-
hash: 7
|
31
|
-
segments:
|
32
|
-
- 3
|
33
|
-
- 0
|
34
|
-
version: "3.0"
|
12
|
+
date: 2014-04-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: railties
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '3.0'
|
35
21
|
type: :runtime
|
36
|
-
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '3.0'
|
37
28
|
description: Make your Rails app 10 times faster in development mode
|
38
29
|
email: roman.lenegrate@gmail.com
|
39
30
|
executables: []
|
40
|
-
|
41
31
|
extensions: []
|
42
|
-
|
43
|
-
extra_rdoc_files:
|
32
|
+
extra_rdoc_files:
|
44
33
|
- LICENSE
|
45
34
|
- README.markdown
|
46
|
-
files:
|
35
|
+
files:
|
47
36
|
- lib/rails-dev-boost.rb
|
37
|
+
- lib/rails_development_boost/async/reactor.rb
|
48
38
|
- lib/rails_development_boost/async.rb
|
49
39
|
- lib/rails_development_boost/dependencies_patch/instrumentation_patch.rb
|
50
40
|
- lib/rails_development_boost/dependencies_patch.rb
|
@@ -92,41 +82,31 @@ files:
|
|
92
82
|
- LICENSE
|
93
83
|
- README.markdown
|
94
84
|
- VERSION
|
95
|
-
has_rdoc: true
|
96
85
|
homepage: http://github.com/thedarkone/rails-dev-boost
|
97
86
|
licenses: []
|
98
|
-
|
87
|
+
metadata: {}
|
99
88
|
post_install_message:
|
100
|
-
rdoc_options:
|
89
|
+
rdoc_options:
|
101
90
|
- --charset=UTF-8
|
102
|
-
require_paths:
|
91
|
+
require_paths:
|
103
92
|
- lib
|
104
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
none: false
|
115
|
-
requirements:
|
116
|
-
- - ">="
|
117
|
-
- !ruby/object:Gem::Version
|
118
|
-
hash: 3
|
119
|
-
segments:
|
120
|
-
- 0
|
121
|
-
version: "0"
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
122
103
|
requirements: []
|
123
|
-
|
124
104
|
rubyforge_project:
|
125
|
-
rubygems_version:
|
105
|
+
rubygems_version: 2.0.14
|
126
106
|
signing_key:
|
127
107
|
specification_version: 3
|
128
108
|
summary: Speeds up Rails development mode
|
129
|
-
test_files:
|
109
|
+
test_files:
|
130
110
|
- test/constants/active_record/comment.rb
|
131
111
|
- test/constants/active_record/message.rb
|
132
112
|
- test/constants/active_record/other.rb
|