aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorYehuda Katz <wycats@gmail.com>2009-08-09 03:28:46 -0300
committerYehuda Katz <wycats@gmail.com>2009-08-09 04:12:09 -0300
commite58b2769cf2c4df348d4ac31a8c5497cbd545564 (patch)
tree6ec76a37ffb1b1da11b11f7f6563cf2d24cbe43f /actionpack
parent10eaba8f412d3acbdb6a57e1cd457e074c127953 (diff)
downloadrails-e58b2769cf2c4df348d4ac31a8c5497cbd545564.tar.gz
rails-e58b2769cf2c4df348d4ac31a8c5497cbd545564.tar.bz2
rails-e58b2769cf2c4df348d4ac31a8c5497cbd545564.zip
Experimental: Improve performance of ActionView by preventing method cache flushing due to runtime Kernel#extend:
* The helper module adds a new _helper_serial property onto AbstractController subclasses * When #helper is used to add helpers to a class, the serial number is updated * An ActionView subclass is created and cached based on this serial number. * That subclass includes the helper module from the controller * Subsequent requests using the same controller with the same serial will result in reusing that subclass, rather than being forced to take an action (like include or extend) that will result in a global method cache flush on MRI and a flush of the entire AV class' cache on JRuby. * For now, this optimization is not applied to the RJS helpers, which results in a global method cache flush in MRI and a flush of the JavaScriptGenerator class in JRuby only when using RJS. * Since the effects are limited to using RJS, and would only affect JavaScriptGenerator in JRuby (as opposed to the entire view object), it seems worthwhile to apply this now. * This resulted in a significant performance improvement. I will have benchmarks in the next day or two that show the performance impact of the last several commits. * There is a small chance this could break existing code (although I'm not sure how). If that happens, please report it immediately.
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb10
-rw-r--r--actionpack/lib/action_view/base.rb34
2 files changed, 31 insertions, 13 deletions
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 04eaa02441..f3072fad74 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -4,8 +4,16 @@ module AbstractController
include RenderingController
+ def self.next_serial
+ @helper_serial ||= 0
+ @helper_serial += 1
+ end
+
included do
extlib_inheritable_accessor(:_helpers) { Module.new }
+ extlib_inheritable_accessor(:_helper_serial) do
+ AbstractController::Helpers.next_serial
+ end
end
module ClassMethods
@@ -58,6 +66,8 @@ module AbstractController
# of the helper module. Any methods defined in the block
# will be helpers.
def helper(*args, &block)
+ self._helper_serial = AbstractController::Helpers.next_serial + 1
+
args.flatten.each do |arg|
case arg
when Module
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index c3361c9367..c171a5a8f5 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -164,6 +164,9 @@ module ActionView #:nodoc:
#
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
class Base
+ module Subclasses
+ end
+
include Helpers, Rendering, Partials, ::ERB::Util
extend ActiveSupport::Memoizable
@@ -212,30 +215,35 @@ module ActionView #:nodoc:
ActionView::PathSet.new(Array(value))
end
+ extlib_inheritable_accessor :helpers
attr_reader :helpers
- class ProxyModule < Module
- def initialize(receiver)
- @receiver = receiver
- end
+ def self.for_controller(controller)
+ @views ||= {}
- def include(*args)
- super(*args)
- @receiver.extend(*args)
- end
- end
+ # TODO: Decouple this so helpers are a separate concern in AV just like
+ # they are in AC.
+ if controller.class.respond_to?(:_helper_serial)
+ klass = @views[controller.class._helper_serial] ||= Class.new(self) do
+ Subclasses.const_set(controller.class.name.gsub(/::/, '__'), self)
- def self.for_controller(controller)
- new(controller.class.view_paths, {}, controller).tap do |view|
- view.helpers.include(controller._helpers) if controller.respond_to?(:_helpers)
+ if controller.respond_to?(:_helpers)
+ include controller._helpers
+ self.helpers = controller._helpers
+ end
+ end
+ else
+ klass = self
end
+
+ klass.new(controller.class.view_paths, {}, controller)
end
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc:
@formats = formats || [:html]
@assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
@controller = controller
- @helpers = ProxyModule.new(self)
+ @helpers = self.class.helpers || Module.new
@_content_for = Hash.new {|h,k| h[k] = "" }
self.view_paths = view_paths
end