rails-dev-boost 0.1.1

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.
Files changed (46) hide show
  1. data/.gitignore +1 -0
  2. data/LICENSE +19 -0
  3. data/README.markdown +105 -0
  4. data/Rakefile +52 -0
  5. data/TODO.txt +2 -0
  6. data/VERSION +1 -0
  7. data/init.rb +3 -0
  8. data/lib/rails_development_boost.rb +32 -0
  9. data/lib/rails_development_boost/dependencies_patch.rb +332 -0
  10. data/lib/rails_development_boost/descendants_tracker_patch.rb +18 -0
  11. data/lib/rails_development_boost/loaded_file.rb +76 -0
  12. data/lib/rails_development_boost/reference_patch.rb +24 -0
  13. data/lib/rails_development_boost/view_helpers_patch.rb +17 -0
  14. data/rails-dev-boost.gemspec +116 -0
  15. data/test/constants/active_record/comment.rb +2 -0
  16. data/test/constants/active_record/message.rb +2 -0
  17. data/test/constants/active_record/other.rb +2 -0
  18. data/test/constants/active_record/post.rb +3 -0
  19. data/test/constants/deep_nesting/a.rb +2 -0
  20. data/test/constants/deep_nesting/a/b.rb +2 -0
  21. data/test/constants/deep_nesting/a/b/c.rb +2 -0
  22. data/test/constants/deep_nesting/a/b/c/d.rb +2 -0
  23. data/test/constants/double_removal/ns.rb +3 -0
  24. data/test/constants/double_removal/ns/c.rb +3 -0
  25. data/test/constants/double_removal/ns/m.rb +2 -0
  26. data/test/constants/mixins/client.rb +3 -0
  27. data/test/constants/mixins/mixin.rb +4 -0
  28. data/test/constants/mixins/update/mixin.rb +4 -0
  29. data/test/constants/nested_mixins/b.rb +2 -0
  30. data/test/constants/nested_mixins/b/c.rb +2 -0
  31. data/test/constants/nested_mixins/ma.rb +3 -0
  32. data/test/constants/nested_mixins/ma/mb.rb +3 -0
  33. data/test/constants/nested_mixins/ma/mb/mc.rb +2 -0
  34. data/test/constants/nested_mixins/oa.rb +4 -0
  35. data/test/constants/nested_mixins/oa/ob.rb +3 -0
  36. data/test/constants/nested_mixins/oa/ob/oc.rb +2 -0
  37. data/test/constants/single_removal/a.rb +2 -0
  38. data/test/constants/single_removal/b.rb +2 -0
  39. data/test/constants/singleton_mixins/a.rb +3 -0
  40. data/test/constants/singleton_mixins/b.rb +2 -0
  41. data/test/constants/subclass/a.rb +2 -0
  42. data/test/constants/subclass/b.rb +2 -0
  43. data/test/constants/subclass/c.rb +2 -0
  44. data/test/rails_development_boost_test.rb +222 -0
  45. data/test/stub_environment.rb +41 -0
  46. metadata +144 -0
@@ -0,0 +1,18 @@
1
+ module RailsDevelopmentBoost
2
+ module DescendantsTrackerPatch
3
+ def self.apply!
4
+ ActiveSupport::DescendantsTracker.extend self
5
+ ActiveSupport::DescendantsTracker.singleton_class.remove_possible_method :clear
6
+ end
7
+
8
+ def delete(klass)
9
+ class_variable_get(:@@direct_descendants).tap do |direct_descendants|
10
+ direct_descendants.delete(klass)
11
+ direct_descendants.each_value {|descendants| descendants.delete(klass)}
12
+ end
13
+ end
14
+
15
+ def clear
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,76 @@
1
+ module RailsDevelopmentBoost
2
+ class LoadedFile
3
+ @constants_to_files = {}
4
+
5
+ class << self
6
+ attr_reader :constants_to_files
7
+ end
8
+
9
+ attr_accessor :path, :constants
10
+ delegate :constants_to_files, :to => 'self.class'
11
+
12
+ def initialize(path, constants=[])
13
+ @path = path
14
+ @constants = constants
15
+ @mtime = current_mtime
16
+ end
17
+
18
+ def changed?
19
+ previous_mtime, @mtime = @mtime, current_mtime
20
+ previous_mtime != @mtime
21
+ end
22
+
23
+ def add_constants(new_constants)
24
+ new_constants.each do |new_constant|
25
+ (constants_to_files[new_constant] ||= []) << self
26
+ end
27
+ @constants |= new_constants
28
+ retrieve_associated_files.each {|file| file.add_constants(@constants)} if @associated_files
29
+ end
30
+
31
+ def delete_constant(const_name)
32
+ delete_from_constants_to_files(const_name)
33
+ @constants.delete(const_name)
34
+ end
35
+
36
+ def associate_with(other_loaded_file)
37
+ (@associated_files ||= []) << other_loaded_file
38
+ end
39
+
40
+ def retrieve_associated_files
41
+ associated_files, @associated_files = @associated_files, nil
42
+ associated_files
43
+ end
44
+
45
+ def require_path
46
+ @path.sub(/\.rb\Z/, '')
47
+ end
48
+
49
+ def self.each_file_with_const(const_name, &block)
50
+ if files = constants_to_files[const_name]
51
+ files.dup.each(&block)
52
+ end
53
+ end
54
+
55
+ def stale!
56
+ @mtime = 0
57
+ if associated_files = retrieve_associated_files
58
+ associated_files.each(&:stale!)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def delete_from_constants_to_files(const_name)
65
+ if files = constants_to_files[const_name]
66
+ files.delete(self)
67
+ constants_to_files.delete(const_name) if files.empty?
68
+ end
69
+ end
70
+
71
+ def current_mtime
72
+ # trying to be more efficient: there is no need for a full-fledged Time instance, just grab the timestamp
73
+ File.mtime(@path).to_i rescue nil
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,24 @@
1
+ module RailsDevelopmentBoost
2
+ module ReferencePatch
3
+ if defined?(ActiveSupport::Dependencies::ClassCache) # post Rails' f345e2380cac2560f3bb
4
+ def self.apply!
5
+ ActiveSupport::Dependencies::ClassCache.send :include, self
6
+ end
7
+
8
+ def loose!(const_name)
9
+ @store.delete(const_name)
10
+ end
11
+ else
12
+ def self.apply!
13
+ ActiveSupport::Dependencies::Reference.cattr_reader :constants
14
+ ActiveSupport::Dependencies::Reference.extend ClassMethods
15
+ end
16
+
17
+ module ClassMethods
18
+ def loose!(const_name)
19
+ constants.delete(const_name)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module RailsDevelopmentBoost
2
+ module ViewHelpersPatch
3
+ def self.apply!
4
+ AbstractController::Helpers::ClassMethods.send :include, self
5
+ end
6
+
7
+ # we need to explicitly associate helpers to their including controllers/mailers
8
+ def add_template_helper_with_const_association_tracking(helper_module)
9
+ ActiveSupport::Dependencies.add_explicit_dependency(helper_module, self)
10
+ add_template_helper_without_const_association_tracking(helper_module)
11
+ end
12
+
13
+ def self.included(base)
14
+ base.alias_method_chain :add_template_helper, :const_association_tracking
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,116 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rails-dev-boost}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Roman Le N\303\251grate", "thedarkone"]
12
+ s.date = %q{2010-11-11}
13
+ s.description = %q{Make your Rails app 10 times faster in development mode}
14
+ s.email = %q{roman.lenegrate@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.markdown",
23
+ "Rakefile",
24
+ "TODO.txt",
25
+ "VERSION",
26
+ "init.rb",
27
+ "lib/rails_development_boost.rb",
28
+ "lib/rails_development_boost/dependencies_patch.rb",
29
+ "lib/rails_development_boost/descendants_tracker_patch.rb",
30
+ "lib/rails_development_boost/loaded_file.rb",
31
+ "lib/rails_development_boost/reference_patch.rb",
32
+ "lib/rails_development_boost/view_helpers_patch.rb",
33
+ "rails-dev-boost.gemspec",
34
+ "test/constants/.DS_Store",
35
+ "test/constants/active_record/comment.rb",
36
+ "test/constants/active_record/message.rb",
37
+ "test/constants/active_record/other.rb",
38
+ "test/constants/active_record/post.rb",
39
+ "test/constants/deep_nesting/a.rb",
40
+ "test/constants/deep_nesting/a/b.rb",
41
+ "test/constants/deep_nesting/a/b/c.rb",
42
+ "test/constants/deep_nesting/a/b/c/d.rb",
43
+ "test/constants/double_removal/ns.rb",
44
+ "test/constants/double_removal/ns/c.rb",
45
+ "test/constants/double_removal/ns/m.rb",
46
+ "test/constants/mixins/client.rb",
47
+ "test/constants/mixins/mixin.rb",
48
+ "test/constants/mixins/update/mixin.rb",
49
+ "test/constants/nested_mixins/b.rb",
50
+ "test/constants/nested_mixins/b/c.rb",
51
+ "test/constants/nested_mixins/ma.rb",
52
+ "test/constants/nested_mixins/ma/mb.rb",
53
+ "test/constants/nested_mixins/ma/mb/mc.rb",
54
+ "test/constants/nested_mixins/oa.rb",
55
+ "test/constants/nested_mixins/oa/ob.rb",
56
+ "test/constants/nested_mixins/oa/ob/oc.rb",
57
+ "test/constants/single_removal/a.rb",
58
+ "test/constants/single_removal/b.rb",
59
+ "test/constants/singleton_mixins/a.rb",
60
+ "test/constants/singleton_mixins/b.rb",
61
+ "test/constants/subclass/a.rb",
62
+ "test/constants/subclass/b.rb",
63
+ "test/constants/subclass/c.rb",
64
+ "test/rails_development_boost_test.rb",
65
+ "test/stub_environment.rb"
66
+ ]
67
+ s.homepage = %q{http://github.com/thedarkone/rails-dev-boost}
68
+ s.rdoc_options = ["--charset=UTF-8"]
69
+ s.require_paths = ["lib"]
70
+ s.rubygems_version = %q{1.3.7}
71
+ s.summary = %q{Speeds up Rails development mode}
72
+ s.test_files = [
73
+ "test/constants/active_record/comment.rb",
74
+ "test/constants/active_record/message.rb",
75
+ "test/constants/active_record/other.rb",
76
+ "test/constants/active_record/post.rb",
77
+ "test/constants/deep_nesting/a/b/c/d.rb",
78
+ "test/constants/deep_nesting/a/b/c.rb",
79
+ "test/constants/deep_nesting/a/b.rb",
80
+ "test/constants/deep_nesting/a.rb",
81
+ "test/constants/double_removal/ns/c.rb",
82
+ "test/constants/double_removal/ns/m.rb",
83
+ "test/constants/double_removal/ns.rb",
84
+ "test/constants/mixins/client.rb",
85
+ "test/constants/mixins/mixin.rb",
86
+ "test/constants/mixins/update/mixin.rb",
87
+ "test/constants/nested_mixins/b/c.rb",
88
+ "test/constants/nested_mixins/b.rb",
89
+ "test/constants/nested_mixins/ma/mb/mc.rb",
90
+ "test/constants/nested_mixins/ma/mb.rb",
91
+ "test/constants/nested_mixins/ma.rb",
92
+ "test/constants/nested_mixins/oa/ob/oc.rb",
93
+ "test/constants/nested_mixins/oa/ob.rb",
94
+ "test/constants/nested_mixins/oa.rb",
95
+ "test/constants/single_removal/a.rb",
96
+ "test/constants/single_removal/b.rb",
97
+ "test/constants/singleton_mixins/a.rb",
98
+ "test/constants/singleton_mixins/b.rb",
99
+ "test/constants/subclass/a.rb",
100
+ "test/constants/subclass/b.rb",
101
+ "test/constants/subclass/c.rb",
102
+ "test/rails_development_boost_test.rb",
103
+ "test/stub_environment.rb"
104
+ ]
105
+
106
+ if s.respond_to? :specification_version then
107
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
108
+ s.specification_version = 3
109
+
110
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
111
+ else
112
+ end
113
+ else
114
+ end
115
+ end
116
+
@@ -0,0 +1,2 @@
1
+ class Comment < Message
2
+ end
@@ -0,0 +1,2 @@
1
+ class Message < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Other < ActiveRecord::Base
2
+ end
@@ -0,0 +1,3 @@
1
+ class Post < Message
2
+ has_many :comments
3
+ end
@@ -0,0 +1,2 @@
1
+ module A
2
+ end
@@ -0,0 +1,2 @@
1
+ module A::B
2
+ end
@@ -0,0 +1,2 @@
1
+ module A::B::C
2
+ end
@@ -0,0 +1,2 @@
1
+ module A::B::C::D
2
+ end
@@ -0,0 +1,3 @@
1
+ class Ns
2
+ include M
3
+ end
@@ -0,0 +1,3 @@
1
+ class Ns::C
2
+ include Ns::M
3
+ end
@@ -0,0 +1,2 @@
1
+ module Ns::M
2
+ end
@@ -0,0 +1,3 @@
1
+ class Client
2
+ include Mixin
3
+ end
@@ -0,0 +1,4 @@
1
+ module Mixin
2
+ def from_mixin
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Mixin
2
+ def from_mixin_update
3
+ end
4
+ end
@@ -0,0 +1,2 @@
1
+ module B
2
+ end
@@ -0,0 +1,2 @@
1
+ class B::C
2
+ end
@@ -0,0 +1,3 @@
1
+ module Ma
2
+ include Mb
3
+ end
@@ -0,0 +1,3 @@
1
+ module Ma::Mb
2
+ include Mc
3
+ end
@@ -0,0 +1,2 @@
1
+ module Ma::Mb::Mc
2
+ end
@@ -0,0 +1,4 @@
1
+ module Oa
2
+ include Ma::Mb
3
+ Ob
4
+ end
@@ -0,0 +1,3 @@
1
+ module Oa::Ob
2
+ Oc
3
+ end
@@ -0,0 +1,2 @@
1
+ class Oa::Ob::Oc < B::C
2
+ end
@@ -0,0 +1,2 @@
1
+ class A
2
+ end
@@ -0,0 +1,2 @@
1
+ class B
2
+ end
@@ -0,0 +1,3 @@
1
+ module A
2
+ extend B
3
+ end
@@ -0,0 +1,2 @@
1
+ module B
2
+ end
@@ -0,0 +1,2 @@
1
+ class A
2
+ end
@@ -0,0 +1,2 @@
1
+ class B < A
2
+ end
@@ -0,0 +1,2 @@
1
+ class C
2
+ end
@@ -0,0 +1,222 @@
1
+ require 'test/unit'
2
+ require 'mocha'
3
+
4
+ require 'stub_environment'
5
+ require 'rails_development_boost'
6
+ RailsDevelopmentBoost.apply!
7
+
8
+ class RailsDevelopmentBoostTest < Test::Unit::TestCase
9
+ def test_single_removal
10
+ load_from "single_removal"
11
+
12
+ assert_same_object_id('A') { reload! }
13
+ assert_different_object_id('A') { reload! { update("a.rb") } }
14
+ assert_different_object_id('A') { reload! { update("a.rb") } }
15
+ assert_same_object_id('A') { reload! }
16
+
17
+ assert_same_object_id('B') do
18
+ assert_different_object_id('A') do
19
+ reload! do
20
+ update("a.rb")
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def test_subclass_update_cascade
27
+ load_from "subclass"
28
+
29
+ assert_different_object_id 'A', 'B' do
30
+ assert_same_object_id 'C' do
31
+ reload! do
32
+ update("a.rb")
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def test_nested_constant_update_cascade
39
+ load_from "deep_nesting"
40
+
41
+ assert_different_object_id 'A::B::C::D', 'A::B::C', 'A::B', 'A' do
42
+ reload! do
43
+ update("a.rb")
44
+ end
45
+ end
46
+ end
47
+
48
+ def test_mixin_update_cascade
49
+ load_from "mixins"
50
+
51
+ assert_different_object_id 'Mixin', 'Client' do
52
+ reload! do
53
+ update("mixin.rb")
54
+ end
55
+ end
56
+
57
+ assert Client.public_method_defined?('from_mixin') # sanity check
58
+
59
+ # Simulate a change in the mixin file
60
+ reload! do
61
+ update("mixin.rb")
62
+ end
63
+ Deps.load_paths.unshift("#{@constant_dir}/update")
64
+
65
+ assert !Client.public_method_defined?('from_mixin')
66
+ assert Client.public_method_defined?('from_mixin_update')
67
+ end
68
+
69
+ def test_prevention_of_removal_cycle
70
+ load_from "double_removal"
71
+
72
+ # Failure of this test = SystemStackError: stack level too deep
73
+ assert_different_object_id 'Ns::M', 'Ns::C', 'Ns' do
74
+ reload! do
75
+ update("ns/m.rb")
76
+ end
77
+ end
78
+ end
79
+
80
+ def test_nested_mixins
81
+ load_from "nested_mixins"
82
+
83
+ assert_different_object_id 'Ma::Mb::Mc', 'Ma::Mb', 'Ma' do
84
+ assert_different_object_id 'Oa::Ob::Oc', 'Oa::Ob', 'Oa' do
85
+ assert_same_object_id 'B::C' do
86
+ reload! do
87
+ update("ma/mb/mc.rb")
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ def test_singleton_mixins
95
+ load_from "singleton_mixins"
96
+
97
+ assert_different_object_id 'A' do
98
+ reload! do
99
+ update("b.rb")
100
+ end
101
+ end
102
+ assert_same_object_id 'B' do
103
+ reload! do
104
+ update("a.rb")
105
+ end
106
+ end
107
+ end
108
+
109
+ def test_consistency_of_activerecord_registry
110
+ load_from "active_record"
111
+
112
+ fetch_registered_ar_subclasses = lambda do
113
+ ActiveRecord::Base.instance_eval { subclasses }.sort_by(&:name)
114
+ end
115
+
116
+ # Load initial version of the models
117
+ assert_equal [Comment, Message, Other, Post], fetch_registered_ar_subclasses[]
118
+
119
+ # AR::Base subclass tree is updated
120
+ assert_different_object_id 'Message', 'Post', 'Comment' do
121
+ assert_same_object_id 'Other' do
122
+ reload! do
123
+ update("message.rb")
124
+ end
125
+ end
126
+ end
127
+ assert_equal [Comment, Message, Other, Post], fetch_registered_ar_subclasses[]
128
+
129
+ # Create initial references to reflection classes
130
+ assert_equal Comment, Post.new.comments.new.class
131
+
132
+ # Reflections are updated
133
+ assert_same_object_id 'Post' do
134
+ assert_different_object_id 'Comment' do
135
+ reload! do
136
+ update("comment.rb")
137
+ end
138
+ end
139
+ end
140
+ assert_equal Comment, Post.new.comments.new.class
141
+ end
142
+
143
+ protected
144
+
145
+ CONSTANT_DIR = "#{File.dirname(__FILE__)}/constants".freeze
146
+ CONSTANT_FILES = Dir.chdir(CONSTANT_DIR) { Dir.glob("**/*.rb") }.freeze
147
+
148
+ Deps = ActiveSupport::Dependencies
149
+
150
+ def setup
151
+ # Cleanup
152
+ clean_up! "setup"
153
+ @constant_dir = CONSTANT_DIR
154
+
155
+ # Configuration
156
+ Deps.load_paths = [CONSTANT_DIR]
157
+ Deps.logger = Logger.new(STDERR)
158
+ Deps.log_activity = false
159
+
160
+ # Stub mtimes
161
+ CONSTANT_FILES.each { |file| stub_mtime(file) }
162
+ end
163
+
164
+ def teardown
165
+ clean_up! "teardown"
166
+ end
167
+
168
+ private
169
+
170
+ def load_from(root)
171
+ @constant_dir = "#{CONSTANT_DIR}/#{root}"
172
+ Deps.load_paths = [@constant_dir]
173
+ end
174
+
175
+ def update(path)
176
+ stub_mtime(path, File.mtime("#{@constant_dir}/#{path}") + 1)
177
+ end
178
+
179
+ def stub_mtime(path, time=1)
180
+ File.stubs(:mtime).with("#{@constant_dir}/#{path}").returns time
181
+ end
182
+
183
+ def reload!
184
+ ActionController::Dispatcher.new.cleanup_application
185
+ yield if block_given?
186
+ ActionController::Dispatcher.new.reload_application
187
+ end
188
+
189
+ def clean_up!(stage)
190
+ message = "#{stage} dependency cleanup of <#{@method_name}> failed"
191
+
192
+ Deps.clear
193
+ Deps.history.clear
194
+
195
+ assert_equal([], Deps.constants_being_removed, message)
196
+ assert_equal([], Deps.module_cache, message)
197
+ assert_equal(Set.new, Deps.loaded, message)
198
+ assert_equal({}, Deps.file_map, message)
199
+ assert_equal([], Deps.autoloaded_constants, message)
200
+ end
201
+
202
+ def assert_same_object_id(*expressions, &block)
203
+ each_object_id_diff(block, expressions) do |expr, before, after|
204
+ assert_equal(before, after, "<#{expr}.object_id> has changed")
205
+ end
206
+ end
207
+
208
+ def assert_different_object_id(*expressions, &block)
209
+ each_object_id_diff(block, expressions) do |expr, before, after|
210
+ assert_not_equal(before, after, "<#{expr}.object_id> has remained the same")
211
+ end
212
+ end
213
+
214
+ def each_object_id_diff(alter, expressions)
215
+ ids_before = expressions.map { |expr| [expr, eval("#{expr}.object_id")] }
216
+ alter.call
217
+ ids_before.each do |expr, before|
218
+ after = eval("#{expr}.object_id")
219
+ yield expr, before, after
220
+ end
221
+ end
222
+ end