Skip to content

Commit 758ad13

Browse files
committed
Avoid creating helper modules until modified
In applications which use :all helpers (the default), most controllers won't be making modifications to their _helpers module. In CRuby this created many ICLASS objects which could cause a large increase in memory usage in applications with many controllers and helpers. To avoid creating unnecessary modules this PR builds modules only when a modification is being made: ethier by calling `helper`, `helper_method`, or through having a default helper (one matching the controller's name) included onto it.
1 parent 10df693 commit 758ad13

File tree

2 files changed

+33
-7
lines changed

2 files changed

+33
-7
lines changed

actionpack/lib/abstract_controller/helpers.rb

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,19 @@ module Helpers
77
extend ActiveSupport::Concern
88

99
included do
10-
class_attribute :_helpers, default: define_helpers_module(self)
1110
class_attribute :_helper_methods, default: Array.new
11+
12+
# This is here so that it is always higher in the inheritance chain than
13+
# the definition in lib/action_view/rendering.rb
14+
redefine_singleton_method(:_helpers) do
15+
if @_helpers ||= nil
16+
@_helpers
17+
else
18+
superclass._helpers
19+
end
20+
end
21+
22+
self._helpers = define_helpers_module(self)
1223
end
1324

1425
class MissingHelperError < LoadError
@@ -25,17 +36,24 @@ def initialize(error, path)
2536
end
2637
end
2738

39+
def _helpers
40+
self.class._helpers
41+
end
42+
2843
module ClassMethods
2944
# When a class is inherited, wrap its helper module in a new module.
3045
# This ensures that the parent class's module can be changed
3146
# independently of the child class's.
3247
def inherited(klass)
33-
helpers = _helpers
34-
klass._helpers = define_helpers_module(klass, helpers)
48+
# Inherited from parent by default
49+
klass._helpers = nil
50+
3551
klass.class_eval { default_helper_module! } unless klass.anonymous?
3652
super
3753
end
3854

55+
attr_writer :_helpers
56+
3957
# Declare a controller method as a helper. For example, the following
4058
# makes the +current_user+ and +logged_in?+ controller methods available
4159
# to the view:
@@ -65,7 +83,7 @@ def helper_method(*methods)
6583
file, line = location.path, location.lineno
6684

6785
methods.each do |method|
68-
_helpers.class_eval <<-ruby_eval, file, line
86+
_helpers_for_modification.class_eval <<-ruby_eval, file, line
6987
def #{method}(*args, &block) # def current_user(*args, &block)
7088
controller.send(:'#{method}', *args, &block) # controller.send(:'current_user', *args, &block)
7189
end # end
@@ -127,10 +145,11 @@ def #{method}(*args, &block) # def current_user(*args, &block
127145
#
128146
def helper(*args, &block)
129147
modules_for_helpers(args).each do |mod|
130-
_helpers.include(mod)
148+
next if _helpers.include?(mod)
149+
_helpers_for_modification.include(mod)
131150
end
132151

133-
_helpers.module_eval(&block) if block_given?
152+
_helpers_for_modification.module_eval(&block) if block_given?
134153
end
135154

136155
# Clears up all existing helpers in this class, only keeping the helper
@@ -161,6 +180,13 @@ def modules_for_helpers(modules_or_helper_prefixes)
161180
end
162181
end
163182

183+
def _helpers_for_modification
184+
unless @_helpers
185+
self._helpers = define_helpers_module(self, superclass._helpers)
186+
end
187+
_helpers
188+
end
189+
164190
private
165191
def define_helpers_module(klass, helpers = nil)
166192
# In some tests inherited is called explicitly. In that case, just

actionview/lib/action_view/test_case.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def determine_default_helper_class(name)
7474
def helper_method(*methods)
7575
# Almost a duplicate from ActionController::Helpers
7676
methods.flatten.each do |method|
77-
_helpers.module_eval <<-end_eval, __FILE__, __LINE__ + 1
77+
_helpers_for_modification.module_eval <<-end_eval, __FILE__, __LINE__ + 1
7878
def #{method}(*args, &block) # def current_user(*args, &block)
7979
_test_case.send(:'#{method}', *args, &block) # _test_case.send(:'current_user', *args, &block)
8080
end # end

0 commit comments

Comments
 (0)