rails-dev-boost 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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