rails-dev-boost 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|