From fad214b9e1c0a66f8fecde48fbd8d122e5c51e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 16 Apr 2011 01:09:05 +0200 Subject: [PATCH] Initial work on fibered layout. --- actionpack/lib/action_view.rb | 1 + actionpack/lib/action_view/base.rb | 53 ++++++++++++++++--- .../lib/action_view/helpers/capture_helper.rb | 6 +-- .../renderer/fibered_template_renderer.rb | 35 ++++++++++++ .../action_view/renderer/partial_renderer.rb | 2 +- .../action_view/renderer/template_renderer.rb | 4 +- actionpack/lib/action_view/rendering.rb | 26 ++++++--- .../test/template/capture_helper_test.rb | 2 +- .../test/template/fibered_render_test.rb | 27 ++++++++++ 9 files changed, 137 insertions(+), 19 deletions(-) create mode 100644 actionpack/lib/action_view/renderer/fibered_template_renderer.rb create mode 100644 actionpack/test/template/fibered_render_test.rb diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 60665387b6..9b8b694646 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -44,6 +44,7 @@ module ActionView autoload :AbstractRenderer autoload :PartialRenderer autoload :TemplateRenderer + autoload :FiberedTemplateRenderer end autoload_at "action_view/template/resolver" do diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 7488127e35..10a523eeac 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -153,7 +153,7 @@ module ActionView #:nodoc: end end - attr_accessor :_template + attr_accessor :_template, :_view_flow, :magic_medicine attr_internal :request, :controller, :config, :assigns, :lookup_context delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context @@ -181,8 +181,8 @@ module ActionView #:nodoc: self.helpers = Module.new unless self.class.helpers @_config = {} - @_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new } @_virtual_path = nil + @_view_flow = Flow.new @output_buffer = nil if @_controller = controller @@ -195,10 +195,6 @@ module ActionView #:nodoc: @_lookup_context.formats = formats if formats end - def store_content_for(key, value) - @_content_for[key] = value - end - def controller_path @controller_path ||= controller && controller.controller_path end @@ -209,4 +205,49 @@ module ActionView #:nodoc: ActiveSupport.run_load_hooks(:action_view, self) end + + class Flow + attr_reader :content + + def initialize + @content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new } + end + + def get(key) + @content[key] + end + + def set(key, value) + @content[key] = value + end + + def append(key, value) + @content[key] << value + end + end + + class FiberedFlow < Flow + def initialize(flow, fiber) + @content = flow.content + @fiber = fiber + end + + def get(key) + return super if @content.key?(key) + + begin + @waiting_for = key + Fiber.yield + ensure + @waiting_for = nil + end + + super + end + + def set(key, value) + super + @fiber.resume if @waiting_for == key + end + end end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 3808e231f1..f8b5605ed9 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -135,8 +135,8 @@ module ActionView # for elements that will be fragment cached. def content_for(name, content = nil, &block) content = capture(&block) if block_given? - @_content_for[name] << content if content - @_content_for[name] unless content + result = @_view_flow.append(name, content) if content + result unless content end # content_for? simply checks whether any content has been captured yet using content_for @@ -158,7 +158,7 @@ module ActionView # # def content_for?(name) - @_content_for[name].present? + @_view_flow.get(name).present? end # Use an alternate output buffer for the duration of the block. diff --git a/actionpack/lib/action_view/renderer/fibered_template_renderer.rb b/actionpack/lib/action_view/renderer/fibered_template_renderer.rb new file mode 100644 index 0000000000..45f48cab76 --- /dev/null +++ b/actionpack/lib/action_view/renderer/fibered_template_renderer.rb @@ -0,0 +1,35 @@ +require 'action_view/renderer/template_renderer' +require 'fiber' + +module ActionView + class FiberedTemplateRenderer < TemplateRenderer #:nodoc: + # Renders the given template. An string representing the layout can be + # supplied as well. + def render_template(template, layout_name = nil, locals = {}) #:nodoc: + view, locals = @view, locals || {} + + final = nil + layout = layout_name && find_layout(layout_name, locals.keys) + yielder = lambda { |*name| view._layout_for(*name) } + + instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do + @fiber = Fiber.new do + final = if layout + layout.render(view, locals, &yielder) + else + view._layout_for + end + end + + @view._view_flow = FiberedFlow.new(view._view_flow, @fiber) + @fiber.resume + + content = template.render(view, locals, &yielder) + view._view_flow.set(:layout, content) + @fiber.resume while @fiber.alive? + end + + final + end + end +end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 94c0a8a8fb..180afc27ac 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -79,7 +79,7 @@ module ActionView locals[as] = object content = @template.render(view, locals) do |*name| - view._layout_for(*name, &block) + view._block_layout_for(*name, &block) end content = layout.render(view, locals){ content } if layout diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index 439a661dd3..10d8cc3b87 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -7,6 +7,7 @@ module ActionView def render(options) wrap_formats(options[:template] || options[:file]) do template = determine_template(options) + freeze_formats(template.formats, true) render_template(template, options[:layout], options[:locals]) end end @@ -31,7 +32,6 @@ module ActionView # Renders the given template. An string representing the layout can be # supplied as well. def render_template(template, layout_name = nil, locals = {}) #:nodoc: - freeze_formats(template.formats, true) view, locals = @view, locals || {} render_with_layout(layout_name, locals) do |layout| @@ -47,7 +47,7 @@ module ActionView if layout view = @view - view.store_content_for(:layout, content) + view._view_flow.set(:layout, content) layout.render(view, locals){ |*name| view._layout_for(*name) } else content diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb index 30c083a2bf..e3568e70e5 100644 --- a/actionpack/lib/action_view/rendering.rb +++ b/actionpack/lib/action_view/rendering.rb @@ -73,24 +73,38 @@ module ActionView # Hello David # # - def _layout_for(*args, &block) + def _layout_for(*args) + name = args.first + name = :layout unless name.is_a?(Symbol) + @_view_flow.get(name).html_safe + end + + # Returns the content from the Flow unless we have a block. + def _block_layout_for(*args, &block) name = args.first - if name.is_a?(Symbol) - @_content_for[name].html_safe - elsif block + if !name.is_a?(Symbol) && block capture(*args, &block) else - @_content_for[:layout].html_safe + _layout_for(*args) end end def _render_template(options) #:nodoc: - _template_renderer.render(options) + if @magic_medicine + _fibered_template_renderer.render(options) + else + _template_renderer.render(options) + end end def _template_renderer #:nodoc: @_template_renderer ||= TemplateRenderer.new(self) end + + def _fibered_template_renderer #:nodoc: + @_fibered_template_renderer ||= FiberedTemplateRenderer.new(self) + end + end end diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index 03050485fa..ec252fa117 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -4,7 +4,7 @@ class CaptureHelperTest < ActionView::TestCase def setup super @av = ActionView::Base.new - @_content_for = Hash.new {|h,k| h[k] = "" } + @_view_flow = ActionView::Flow.new end def test_capture_captures_the_temporary_output_buffer_in_its_block diff --git a/actionpack/test/template/fibered_render_test.rb b/actionpack/test/template/fibered_render_test.rb new file mode 100644 index 0000000000..f62af10812 --- /dev/null +++ b/actionpack/test/template/fibered_render_test.rb @@ -0,0 +1,27 @@ +# encoding: utf-8 +require 'abstract_unit' +require 'controller/fake_models' + +class TestController < ActionController::Base +end + + +class FiberedTest < ActiveSupport::TestCase + + def setup + view_paths = ActionController::Base.view_paths + @assigns = { :secret => 'in the sauce' } + @view = ActionView::Base.new(view_paths, @assigns) + @view.magic_medicine = true + @controller_view = TestController.new.view_context + end + + def test_render_template + assert_equal "Hello world!", @view.render(:template => "test/hello_world") + end + + def test_render_with_layout + assert_equal %(\nHello world!\n), + @view.render(:template => "test/hello_world.erb", :layout => "layouts/yield") + end +end if defined?(Fiber) \ No newline at end of file