rails-dev-boost 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/LICENSE +19 -0
- data/README.markdown +105 -0
- data/Rakefile +52 -0
- data/TODO.txt +2 -0
- data/VERSION +1 -0
- data/init.rb +3 -0
- data/lib/rails_development_boost.rb +32 -0
- data/lib/rails_development_boost/dependencies_patch.rb +332 -0
- data/lib/rails_development_boost/descendants_tracker_patch.rb +18 -0
- data/lib/rails_development_boost/loaded_file.rb +76 -0
- data/lib/rails_development_boost/reference_patch.rb +24 -0
- data/lib/rails_development_boost/view_helpers_patch.rb +17 -0
- data/rails-dev-boost.gemspec +116 -0
- data/test/constants/active_record/comment.rb +2 -0
- data/test/constants/active_record/message.rb +2 -0
- data/test/constants/active_record/other.rb +2 -0
- data/test/constants/active_record/post.rb +3 -0
- data/test/constants/deep_nesting/a.rb +2 -0
- data/test/constants/deep_nesting/a/b.rb +2 -0
- data/test/constants/deep_nesting/a/b/c.rb +2 -0
- data/test/constants/deep_nesting/a/b/c/d.rb +2 -0
- data/test/constants/double_removal/ns.rb +3 -0
- data/test/constants/double_removal/ns/c.rb +3 -0
- data/test/constants/double_removal/ns/m.rb +2 -0
- data/test/constants/mixins/client.rb +3 -0
- data/test/constants/mixins/mixin.rb +4 -0
- data/test/constants/mixins/update/mixin.rb +4 -0
- data/test/constants/nested_mixins/b.rb +2 -0
- data/test/constants/nested_mixins/b/c.rb +2 -0
- data/test/constants/nested_mixins/ma.rb +3 -0
- data/test/constants/nested_mixins/ma/mb.rb +3 -0
- data/test/constants/nested_mixins/ma/mb/mc.rb +2 -0
- data/test/constants/nested_mixins/oa.rb +4 -0
- data/test/constants/nested_mixins/oa/ob.rb +3 -0
- data/test/constants/nested_mixins/oa/ob/oc.rb +2 -0
- data/test/constants/single_removal/a.rb +2 -0
- data/test/constants/single_removal/b.rb +2 -0
- data/test/constants/singleton_mixins/a.rb +3 -0
- data/test/constants/singleton_mixins/b.rb +2 -0
- data/test/constants/subclass/a.rb +2 -0
- data/test/constants/subclass/b.rb +2 -0
- data/test/constants/subclass/c.rb +2 -0
- data/test/rails_development_boost_test.rb +222 -0
- data/test/stub_environment.rb +41 -0
- metadata +144 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.DS_Store
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008 Roman Le Négrate
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# RailsDevelopmentBoost
|
2
|
+
|
3
|
+
Make your Rails app 10 times faster in development mode (see FAQ below for more details).
|
4
|
+
|
5
|
+
Alternative to Josh Goebel's [`rails_dev_mode_performance`](https://github.com/yyyc514/rails_dev_mode_performance) plugin.
|
6
|
+
|
7
|
+
## Branches
|
8
|
+
|
9
|
+
If you are using **Rails 3**: [`rails-dev-boost/master`](http://github.com/thedarkone/rails-dev-boost/tree/master) branch.
|
10
|
+
|
11
|
+
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.
|
12
|
+
|
13
|
+
If you are using **Rails 2.2**: [`rails-dev-boost/rails-2-2`](http://github.com/thedarkone/rails-dev-boost/tree/rails-2-2) branch.
|
14
|
+
|
15
|
+
If you are using **Rails 2.1** or **Rails 2.0** or **anything older**: you are out of luck.
|
16
|
+
|
17
|
+
## Background
|
18
|
+
|
19
|
+
Why create a similar plugin? Because I couldn't get Josh Goebel's to work in my projects. His attempts to keep templates cached in a way that fails with recent versions of Rails. Also, removing the faulty chunk of code revealed another issue: it stats source files that may not exist, without trying to find their real path beforehand. That would be fixable is the code wasn't such a mess (no offense).
|
20
|
+
|
21
|
+
I needed better performance in development mode right away, so here is an alternative implementation.
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Rails 3
|
26
|
+
|
27
|
+
Usage through `Gemfile`:
|
28
|
+
|
29
|
+
group :development do
|
30
|
+
gem 'rails-dev-boost', :git => 'git://github.com/thedarkone/rails-dev-boost.git', :require => 'rails_development_boost'
|
31
|
+
end
|
32
|
+
|
33
|
+
Installing as a plugin:
|
34
|
+
|
35
|
+
script/rails plugin install git://github.com/thedarkone/rails-dev-boost
|
36
|
+
|
37
|
+
### Rails 2.3 and older
|
38
|
+
|
39
|
+
script/plugin install git://github.com/thedarkone/rails-dev-boost -r rails-2-3
|
40
|
+
|
41
|
+
When the server is started in *development* mode, the special unloading mechanism takes over.
|
42
|
+
|
43
|
+
It can also be used in combination with [RailsTestServing](https://github.com/Roman2K/rails-test-serving) for even faster test runs by forcefully enabling it in test mode. To do so, add the following in `config/environments/test.rb`:
|
44
|
+
|
45
|
+
def config.soft_reload() true end if RailsTestServing.active?
|
46
|
+
|
47
|
+
## FAQ
|
48
|
+
|
49
|
+
### Q: Since the plugin uses its special "unloading mechanism" won't everything break down?
|
50
|
+
A: Very unlikely... of course there are some edge cases where you might see some breakage (mainly if you're deviating from the Rails 1 file = 1 class conventions or doing some weird `require`s). This is a 99% solution and the seconds you're wasting waiting for the Rails to spit out a page in the dev mode do add up in the long run.
|
51
|
+
|
52
|
+
### Q: How big of a boost is it going to give me?
|
53
|
+
A: It depends on the size of your app (the bigger it is the bigger your boost is going to be). The speed is then approximately equal to that of production env. plus the time it takes to stat all your app's `*.rb` files (which is surprisingly fast as it is cached by OS). Empty 1 controller 2 views app will become about 4x times faster more complex apps will see huge improvements.
|
54
|
+
|
55
|
+
### Q: I'm using an older version of Rails than 2.2, will this work for me?
|
56
|
+
A: Unfortunately you are on your own right now :(.
|
57
|
+
|
58
|
+
### Q: My `Article` model does not pick up changes from the `articles` table.
|
59
|
+
A: You need to force it to be reloaded (just hit the save button in your editor for `article.rb` file).
|
60
|
+
|
61
|
+
### Q: I used `require 'article'` and the `Article` model is not being reloaded.
|
62
|
+
A: You really shouldn't be using `require` to load your files in the Rails app (if you want them to be automatically reloaded) and let automatic constant loading handle the require for you. You can also use `require_dependency 'article'`, as it goes through the Rails stack.
|
63
|
+
|
64
|
+
### Q: I'm using class variables (by class variables I mean "metaclass instance variables") and they are not being reloaded.
|
65
|
+
A: Class level instance variables are not thread safe and you shouldn't be really using them :). There is generally only one case where they might pose a problem for `rails-dev-boost`:
|
66
|
+
|
67
|
+
#app/models/article.rb
|
68
|
+
class Article < ActiveRecord::Base
|
69
|
+
end
|
70
|
+
|
71
|
+
#app/models/blog.rb
|
72
|
+
class Blog < ActiveRecord::Base
|
73
|
+
def self.all_articles
|
74
|
+
@all_articles ||= Article.all
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
Modifying `article.rb` will not reload `@all_articles` (you would always need to re-save `blog.rb` as well).
|
79
|
+
|
80
|
+
The solution is to move class instance variable to its class like this:
|
81
|
+
|
82
|
+
#app/models/article.rb
|
83
|
+
class Article < ActiveRecord::Base
|
84
|
+
def self.all_articles
|
85
|
+
@all_articles ||= all
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
#app/models/blog.rb
|
90
|
+
class Blog < ActiveRecord::Base
|
91
|
+
def self.all_articles
|
92
|
+
Article.all_articles
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
This way saving `arcticle.rb` will trigger the reload of `@all_articles`.
|
97
|
+
|
98
|
+
### Q: I'm using JRuby, is it going to work?
|
99
|
+
A: I haven't tested the plugin with JRuby, but the plugin does use `ObjectSpace` to do its magic. `ObjectSpace` is AFAIK disabled by default on JRuby.
|
100
|
+
|
101
|
+
FAQ added by [thedarkone](http://github.com/thedarkone).
|
102
|
+
|
103
|
+
## Credits
|
104
|
+
|
105
|
+
Written by [Roman Le Négrate](http://roman.flucti.com) ([contact](mailto:roman.lenegrate@gmail.com)). Released under the MIT-license: see the `LICENSE` file.
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rails-dev-boost"
|
8
|
+
gem.summary = %Q{Speeds up Rails development mode}
|
9
|
+
gem.description = %Q{Make your Rails app 10 times faster in development mode}
|
10
|
+
gem.email = "roman.lenegrate@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/thedarkone/rails-dev-boost"
|
12
|
+
gem.authors = ["Roman Le Négrate","thedarkone"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
Rake::TestTask.new(:test) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = 'test/**/test_*.rb'
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
task :rcov do
|
36
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :test => :check_dependencies
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
47
|
+
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "thing #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
data/TODO.txt
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
data/init.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module RailsDevelopmentBoost
|
2
|
+
ActiveSupport.on_load(:after_initialize) do
|
3
|
+
ReferencePatch.apply!
|
4
|
+
DependenciesPatch.apply!
|
5
|
+
DescendantsTrackerPatch.apply!
|
6
|
+
ObservablePatch.apply!
|
7
|
+
|
8
|
+
# this should go into ActiveSupport.on_load(:action_pack), alas Rails doesn't provide it
|
9
|
+
if defined?(ActionDispatch::Reloader) # post 0f7c970
|
10
|
+
ActionDispatch::Reloader.to_prepare { ActiveSupport::Dependencies.unload_modified_files! }
|
11
|
+
else
|
12
|
+
ActionDispatch::Callbacks.before { ActiveSupport::Dependencies.unload_modified_files! }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ActiveSupport.on_load(:action_controller) do
|
17
|
+
ActiveSupport.on_load(:after_initialize) do
|
18
|
+
ViewHelpersPatch.apply!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
autoload :DependenciesPatch, 'rails_development_boost/dependencies_patch'
|
23
|
+
autoload :DescendantsTrackerPatch, 'rails_development_boost/descendants_tracker_patch'
|
24
|
+
autoload :LoadedFile, 'rails_development_boost/loaded_file'
|
25
|
+
autoload :ObservablePatch, 'rails_development_boost/observable_patch'
|
26
|
+
autoload :ReferencePatch, 'rails_development_boost/reference_patch'
|
27
|
+
autoload :ViewHelpersPatch, 'rails_development_boost/view_helpers_patch'
|
28
|
+
|
29
|
+
def self.debug!
|
30
|
+
DependenciesPatch.debug!
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,332 @@
|
|
1
|
+
module RailsDevelopmentBoost
|
2
|
+
module DependenciesPatch
|
3
|
+
module LoadablePatch
|
4
|
+
def require_dependency_with_constant_tracking(*args)
|
5
|
+
ActiveSupport::Dependencies.required_dependency(args.first)
|
6
|
+
# handle plugins such as concerned_with
|
7
|
+
require_dependency_without_constant_tracking(*args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.apply!
|
12
|
+
# retain the original method in case the application overwrites it on its modules/klasses
|
13
|
+
Module.send :alias_method, :_mod_name, :name
|
14
|
+
|
15
|
+
patch = self
|
16
|
+
ActiveSupport::Dependencies.module_eval do
|
17
|
+
alias_method :local_const_defined?, :uninherited_const_defined? unless method_defined?(:local_const_defined?) # pre 4da45060 compatibility
|
18
|
+
remove_possible_method :remove_unloadable_constants!
|
19
|
+
remove_possible_method :clear
|
20
|
+
include patch
|
21
|
+
alias_method_chain :load_file, 'constant_tracking'
|
22
|
+
alias_method_chain :remove_constant, 'handling_of_connections'
|
23
|
+
extend patch
|
24
|
+
end
|
25
|
+
|
26
|
+
ActiveSupport::Dependencies::Loadable.module_eval do
|
27
|
+
include LoadablePatch
|
28
|
+
alias_method_chain :require_dependency, 'constant_tracking'
|
29
|
+
end
|
30
|
+
|
31
|
+
InstrumentationPatch.apply! if @do_instrument
|
32
|
+
|
33
|
+
ActiveSupport::Dependencies.handle_already_autoloaded_constants!
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.debug!
|
37
|
+
if ActiveSupport::Dependencies < DependenciesPatch
|
38
|
+
InstrumentationPatch.apply!
|
39
|
+
else
|
40
|
+
@do_instrument = true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
autoload :InstrumentationPatch, 'rails_development_boost/dependencies_patch/instrumentation_patch'
|
45
|
+
|
46
|
+
mattr_accessor :module_cache
|
47
|
+
self.module_cache = []
|
48
|
+
|
49
|
+
mattr_accessor :file_map
|
50
|
+
self.file_map = {}
|
51
|
+
|
52
|
+
mattr_accessor :constants_being_removed
|
53
|
+
self.constants_being_removed = []
|
54
|
+
|
55
|
+
mattr_accessor :explicit_dependencies
|
56
|
+
self.explicit_dependencies = {}
|
57
|
+
|
58
|
+
def unload_modified_files!
|
59
|
+
log_call
|
60
|
+
file_map.values.each do |file|
|
61
|
+
unload_modified_file(file) if file.changed?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def remove_explicitely_unloadable_constants!
|
66
|
+
explicitly_unloadable_constants.each { |const| remove_constant(const) }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Overridden.
|
70
|
+
def clear
|
71
|
+
end
|
72
|
+
|
73
|
+
# Augmented `load_file'.
|
74
|
+
def load_file_with_constant_tracking(path, *args, &block)
|
75
|
+
result = now_loading(path) { load_file_without_constant_tracking(path, *args, &block) }
|
76
|
+
|
77
|
+
unless load_once_path?(path)
|
78
|
+
new_constants = autoloaded_constants - file_map.values.map(&:constants).flatten
|
79
|
+
|
80
|
+
# Associate newly loaded constants to the file just loaded
|
81
|
+
associate_constants_to_file(new_constants, path)
|
82
|
+
end
|
83
|
+
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
def now_loading(path)
|
88
|
+
@currently_loading, old_currently_loading = path, @currently_loading
|
89
|
+
yield
|
90
|
+
rescue Exception => e
|
91
|
+
error_loading_file(@currently_loading, e)
|
92
|
+
ensure
|
93
|
+
@currently_loading = old_currently_loading
|
94
|
+
end
|
95
|
+
|
96
|
+
def associate_constants_to_file(constants, file_path)
|
97
|
+
# freezing strings before using them as Hash keys is slightly more memory efficient
|
98
|
+
constants.map!(&:freeze)
|
99
|
+
file_path.freeze
|
100
|
+
|
101
|
+
loaded_file_for(file_path).add_constants(constants)
|
102
|
+
end
|
103
|
+
|
104
|
+
def loaded_file_for(file_path)
|
105
|
+
file_map[file_path] ||= LoadedFile.new(file_path)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Augmented `remove_constant'.
|
109
|
+
def remove_constant_with_handling_of_connections(const_name)
|
110
|
+
fetch_module_cache do
|
111
|
+
prevent_further_removal_of(const_name) do
|
112
|
+
unprotected_remove_constant(const_name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def required_dependency(file_name)
|
118
|
+
# Rails uses require_dependency for loading helpers, we are however dealing with the helper problem elsewhere, so we can skip them
|
119
|
+
if @currently_loading && @currently_loading !~ /_controller(?:\.rb)?\Z/ && file_name !~ /_helper(?:\.rb)?\Z/
|
120
|
+
if full_path = ActiveSupport::Dependencies.search_for_file(file_name)
|
121
|
+
loaded_file_for(@currently_loading).associate_with(loaded_file_for(full_path))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_explicit_dependency(parent, child)
|
127
|
+
(explicit_dependencies[parent._mod_name] ||= []) << child._mod_name
|
128
|
+
end
|
129
|
+
|
130
|
+
def handle_already_autoloaded_constants! # we might be late to the party and other gems/plugins might have already triggered autoloading of some constants
|
131
|
+
loaded.each do |require_path|
|
132
|
+
associate_constants_to_file(autoloaded_constants, "#{require_path}.rb") # slightly heavy-handed..
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
def unprotected_remove_constant(const_name)
|
138
|
+
if qualified_const_defined?(const_name) && object = const_name.constantize
|
139
|
+
handle_connected_constants(object, const_name)
|
140
|
+
remove_same_file_constants(const_name)
|
141
|
+
if object.kind_of?(Module)
|
142
|
+
remove_parent_modules_if_autoloaded(object)
|
143
|
+
remove_child_module_constants(object)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
result = remove_constant_without_handling_of_connections(const_name)
|
147
|
+
clear_tracks_of_removed_const(const_name, object)
|
148
|
+
result
|
149
|
+
end
|
150
|
+
|
151
|
+
def unload_file(file)
|
152
|
+
file.constants.dup.each {|const| remove_constant(const)}
|
153
|
+
clean_up_if_no_constants(file)
|
154
|
+
end
|
155
|
+
alias_method :unload_modified_file, :unload_file
|
156
|
+
|
157
|
+
def error_loading_file(file_path, e)
|
158
|
+
loaded_file_for(file_path).stale!
|
159
|
+
raise e
|
160
|
+
end
|
161
|
+
|
162
|
+
def handle_connected_constants(object, const_name)
|
163
|
+
return unless Module === object && qualified_const_defined?(const_name)
|
164
|
+
remove_explicit_dependencies_of(const_name)
|
165
|
+
remove_dependent_modules(object)
|
166
|
+
update_activerecord_related_references(object)
|
167
|
+
remove_nested_constants(const_name)
|
168
|
+
end
|
169
|
+
|
170
|
+
def remove_nested_constants(const_name)
|
171
|
+
autoloaded_constants.grep(/\A#{const_name}::/).each { |const| remove_nested_constant(const_name, const) }
|
172
|
+
end
|
173
|
+
|
174
|
+
def remove_nested_constant(parent_const, child_const)
|
175
|
+
remove_constant(child_const)
|
176
|
+
end
|
177
|
+
|
178
|
+
def autoloaded_namespace_object?(object) # faster than going through Dependencies.autoloaded?
|
179
|
+
LoadedFile.constants_to_files[object._mod_name]
|
180
|
+
end
|
181
|
+
|
182
|
+
# AS::Dependencies doesn't track same-file nested constants, so we need to look out for them on our own.
|
183
|
+
# For example having loaded an abc.rb that looks like this:
|
184
|
+
# class Abc; class Inner; end; end
|
185
|
+
# AS::Dependencies would only add "Abc" constant name to its autoloaded_constants list, completely ignoring Abc::Inner. This in turn
|
186
|
+
# can cause problems for classes inheriting from Abc::Inner somewhere else in the app.
|
187
|
+
def remove_parent_modules_if_autoloaded(object)
|
188
|
+
unless autoloaded_namespace_object?(object)
|
189
|
+
initial_object = object
|
190
|
+
|
191
|
+
while (object = object.parent) != Object
|
192
|
+
if autoloaded_namespace_object?(object)
|
193
|
+
remove_autoloaded_parent_module(initial_object, object)
|
194
|
+
break
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def remove_autoloaded_parent_module(initial_object, parent_object)
|
201
|
+
remove_constant(parent_object._mod_name)
|
202
|
+
end
|
203
|
+
|
204
|
+
# AS::Dependencies doesn't track same-file nested constants, so we need to look out for them on our own and remove any dependent modules/constants
|
205
|
+
def remove_child_module_constants(object)
|
206
|
+
object.constants.each do |const_name|
|
207
|
+
# we only care about "namespace" constants (classes/modules)
|
208
|
+
if local_const_defined?(object, const_name) && (child_const = object.const_get(const_name)).kind_of?(Module)
|
209
|
+
remove_child_module_constant(object, child_const)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def remove_child_module_constant(parent_object, child_constant)
|
215
|
+
remove_constant(child_constant._mod_name)
|
216
|
+
end
|
217
|
+
|
218
|
+
def in_autoloaded_namespace?(object)
|
219
|
+
while object != Object
|
220
|
+
return true if autoloaded_namespace_object?(object)
|
221
|
+
object = object.parent
|
222
|
+
end
|
223
|
+
false
|
224
|
+
end
|
225
|
+
|
226
|
+
def remove_same_file_constants(const_name)
|
227
|
+
LoadedFile.each_file_with_const(const_name) {|file| unload_containing_file(const_name, file)}
|
228
|
+
end
|
229
|
+
|
230
|
+
def unload_containing_file(const_name, file)
|
231
|
+
unload_file(file)
|
232
|
+
end
|
233
|
+
|
234
|
+
def remove_explicit_dependencies_of(const_name)
|
235
|
+
if dependencies = explicit_dependencies.delete(const_name)
|
236
|
+
dependencies.uniq.each {|depending_const| remove_explicit_dependency(const_name, depending_const)}
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def remove_explicit_dependency(const_name, depending_const)
|
241
|
+
remove_constant(depending_const)
|
242
|
+
end
|
243
|
+
|
244
|
+
def clear_tracks_of_removed_const(const_name, object)
|
245
|
+
autoloaded_constants.delete(const_name)
|
246
|
+
module_cache.delete_if { |mod| mod._mod_name == const_name }
|
247
|
+
clean_up_references(const_name, object)
|
248
|
+
|
249
|
+
LoadedFile.each_file_with_const(const_name) do |file|
|
250
|
+
file.delete_constant(const_name)
|
251
|
+
clean_up_if_no_constants(file)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def clean_up_if_no_constants(file)
|
256
|
+
if file.constants.empty?
|
257
|
+
loaded.delete(file.require_path)
|
258
|
+
file_map.delete(file.path)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def clean_up_references(const_name, object)
|
263
|
+
ActiveSupport::Dependencies::Reference.loose!(const_name)
|
264
|
+
ActiveSupport::DescendantsTracker.delete(object)
|
265
|
+
end
|
266
|
+
|
267
|
+
def remove_dependent_modules(mod)
|
268
|
+
fetch_module_cache do |modules|
|
269
|
+
modules.dup.each do |other|
|
270
|
+
next unless other < mod || other.singleton_class.ancestors.include?(mod)
|
271
|
+
next unless first_non_anonymous_superclass(other) == mod if Class === mod
|
272
|
+
next unless qualified_const_defined?(other._mod_name) && other._mod_name.constantize == other
|
273
|
+
next unless in_autoloaded_namespace?(other)
|
274
|
+
remove_dependent_constant(mod, other)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def remove_dependent_constant(original_module, dependent_module)
|
280
|
+
remove_constant(dependent_module._mod_name)
|
281
|
+
end
|
282
|
+
|
283
|
+
def first_non_anonymous_superclass(klass)
|
284
|
+
while (klass = klass.superclass) && anonymous?(klass); end
|
285
|
+
klass
|
286
|
+
end
|
287
|
+
|
288
|
+
# egrep -ohR '@\w*([ck]lass|refl|target|own)\w*' activerecord | sort | uniq
|
289
|
+
def update_activerecord_related_references(klass)
|
290
|
+
return unless defined?(ActiveRecord)
|
291
|
+
return unless klass < ActiveRecord::Base
|
292
|
+
|
293
|
+
# Reset references held by macro reflections (klass is lazy loaded, so
|
294
|
+
# setting its cache to nil will force the name to be resolved again).
|
295
|
+
ActiveRecord::Base.descendants.each do |model|
|
296
|
+
model.reflections.each_value do |reflection|
|
297
|
+
reflection.instance_eval do
|
298
|
+
@klass = nil if @klass == klass
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def anonymous?(mod)
|
305
|
+
!(name = mod._mod_name) || name.empty?
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
def fetch_module_cache
|
311
|
+
return(yield(module_cache)) if module_cache.any?
|
312
|
+
|
313
|
+
ObjectSpace.each_object(Module) { |mod| module_cache << mod unless anonymous?(mod) }
|
314
|
+
begin
|
315
|
+
yield module_cache
|
316
|
+
ensure
|
317
|
+
module_cache.clear
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def prevent_further_removal_of(const_name)
|
322
|
+
return if constants_being_removed.include?(const_name)
|
323
|
+
|
324
|
+
constants_being_removed << const_name
|
325
|
+
begin
|
326
|
+
yield
|
327
|
+
ensure
|
328
|
+
constants_being_removed.delete(const_name)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|