Permalink
Browse files

Add support to render :once.

This will be used internally by sprockets to ensure requires are executed just once.
  • Loading branch information...
1 parent b88f4ca commit 940b57789fb9166658974c591e68d22ecab29f34 @josevalim josevalim committed Oct 10, 2010
@@ -155,7 +155,7 @@ def _normalize_options(options)
options[:partial] = action_name
end
- if (options.keys & [:partial, :file, :template]).empty?
+ if (options.keys & [:partial, :file, :template, :once]).empty?
options[:prefix] ||= _prefix
end
@@ -10,6 +10,7 @@ module Rendering
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
+ # * <tt>:once</tt> - Receives :template paths and ensures they are rendered just once.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
@@ -20,6 +21,8 @@ def render(options = {}, locals = {}, &block)
_render_partial(options.merge(:partial => options[:layout]), &block)
elsif options.key?(:partial)
_render_partial(options)
+ elsif options.key?(:once)
+ _render_once(options)
else
_render_template(options)
end
@@ -88,6 +91,10 @@ def _layout_for(*args, &block)
end
end
+ def _render_once(options) #:nodoc:
+ _template_renderer.render_once(options)
+ end
+
def _render_template(options) #:nodoc:
_template_renderer.render(options)
end
@@ -14,12 +14,6 @@ def render
raise NotImplementedError
end
- # Contains the logic that actually renders the layout.
- def render_layout(layout, locals, &block) #:nodoc:
- view = @view
- layout.render(view, locals){ |*name| view._layout_for(*name, &block) }
- end
-
# Checks if the given path contains a format and if so, change
# the lookup context to take this new format into account.
def wrap_formats(value)
@@ -32,5 +26,11 @@ def wrap_formats(value)
yield
end
end
+
+ protected
+
+ def instrument(name, options={})
+ ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
+ end
end
end
@@ -2,7 +2,6 @@
module ActionView
class PartialRenderer < AbstractRenderer #:nodoc:
- N = ::ActiveSupport::Notifications
PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} }
def initialize(view)
@@ -46,11 +45,11 @@ def render
identifier = ((@template = find_partial) ? @template.identifier : @path)
if @collection
- N.instrument("render_collection.action_view", :identifier => identifier || "collection", :count => @collection.size) do
+ instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
render_collection
end
else
- N.instrument("render_partial.action_view", :identifier => identifier) do
+ instrument(:partial, :identifier => identifier) do
render_partial
end
end
@@ -83,7 +82,7 @@ def render_partial
view._layout_for(*name, &block)
end
- content = render_layout(layout, locals){ content } if layout
+ content = layout.render(view, locals){ content } if layout
content
end
@@ -1,26 +1,51 @@
+require 'set'
+require 'active_support/core_ext/object/try'
+require 'active_support/core_ext/array/wrap'
require 'action_view/renderer/abstract_renderer'
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
+ attr_reader :rendered
+
+ def initialize(view)
+ super
+ @rendered = Set.new
+ end
+
def render(options)
wrap_formats(options[:template] || options[:file]) do
template = determine_template(options)
- lookup_context.freeze_formats(template.formats, true)
- render_template(template, options[:layout], options)
+ render_template(template, options[:layout], options[:locals])
+ end
+ end
+
+ def render_once(options)
+ paths, locals = options[:once], options[:locals] || {}
+ layout, keys, prefix = options[:layout], locals.keys, options[:prefix]
+
+ raise "render :once expects a String or an Array to be given" unless paths
+
+ render_with_layout(layout, locals) do
+ contents = []
+ Array.wrap(paths).each do |path|
+ template = find_template(path, prefix, false, keys)
+ contents << render_template(template, nil, locals) if @rendered.add?(template)
+ end
+ contents.join("\n")
end
end
# Determine the template to be rendered using the given options.
def determine_template(options) #:nodoc:
- keys = (options[:locals] ||= {}).keys
+ keys = options[:locals].try(:keys) || []
- if options.key?(:inline)
- handler = Template.handler_class_for_extension(options[:type] || "erb")
- Template.new(options[:inline], "inline template", handler, { :locals => keys })
- elsif options.key?(:text)
+ if options.key?(:text)
Template::Text.new(options[:text], formats.try(:first))
elsif options.key?(:file)
with_fallbacks { find_template(options[:file], options[:prefix], false, keys) }
+ elsif options.key?(:inline)
+ handler = Template.handler_class_for_extension(options[:type] || "erb")
+ Template.new(options[:inline], "inline template", handler, { :locals => keys })
elsif options.key?(:template)
options[:template].respond_to?(:render) ?
options[:template] : find_template(options[:template], options[:prefix], false, keys)
@@ -29,20 +54,26 @@ def determine_template(options) #:nodoc:
# Renders the given template. An string representing the layout can be
# supplied as well.
- def render_template(template, layout = nil, options = {}) #:nodoc:
- view, locals = @view, options[:locals] || {}
- layout = find_layout(layout, locals.keys) if layout
+ def render_template(template, layout_name = nil, locals = {}) #:nodoc:
+ lookup_context.freeze_formats(template.formats, true)
+ view, locals = @view, locals || {}
- ActiveSupport::Notifications.instrument("render_template.action_view",
- :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
-
- content = template.render(view, locals) { |*name| view._layout_for(*name) }
-
- if layout
- view.store_content_for(:layout, content)
- content = render_layout(layout, locals)
+ render_with_layout(layout_name, locals) do |layout|
+ instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
+ template.render(view, locals) { |*name| view._layout_for(*name) }
end
+ end
+ end
+
+ def render_with_layout(path, locals) #:nodoc:
+ layout = path && find_layout(path, locals.keys)
+ content = yield(layout)
+ if layout
+ view = @view
+ view.store_content_for(:layout, content)
+ layout.render(view, locals){ |*name| view._layout_for(*name) }
+ else
content
end
end
@@ -0,0 +1,73 @@
+require 'abstract_unit'
+
+module RenderTemplate
+ class RenderOnceController < ActionController::Base
+ layout false
+
+ RESOLVER = ActionView::FixtureResolver.new(
+ "test/a.html.erb" => "a",
+ "test/b.html.erb" => "<>",
+ "test/c.html.erb" => "c",
+ "test/one.html.erb" => "<%= render :once => 'test/result' %>",
+ "test/two.html.erb" => "<%= render :once => 'test/result' %>",
+ "test/three.html.erb" => "<%= render :once => 'test/result' %>",
+ "test/result.html.erb" => "YES!",
+ "layouts/test.html.erb" => "l<%= yield %>l"
+ )
+
+ self.view_paths = [RESOLVER]
+
+ def multiple
+ render :once => %w(test/a test/b test/c)
+ end
+
+ def once
+ render :once => %w(test/one test/two test/three)
+ end
+
+ def duplicate
+ render :once => %w(test/a test/a test/a)
+ end
+
+ def with_layout
+ render :once => %w(test/a test/b test/c), :layout => "test"
+ end
+ end
+
+ module Tests
+ def test_mutliple_arguments_get_all_rendered
+ get :multiple
+ assert_response "a\n<>\nc"
+ end
+
+ def test_referenced_templates_get_rendered_once
+ get :once
+ assert_response "YES!\n\n"
+ end
+
+ def test_duplicated_templates_get_rendered_once
+ get :duplicate
+ assert_response "a"
+ end
+
+ def test_layout_wraps_all_rendered_templates
+ get :with_layout
+ assert_response "la\n<>\ncl"
+ end
+ end
+
+ class TestWithResolverCache < Rack::TestCase
+ testing RenderTemplate::RenderOnceController
+ include Tests
+ end
+
+ # TODO This still needs to be implemented and supported.
+ # class TestWithoutResolverCache < Rack::TestCase
+ # testing RenderTemplate::RenderOnceController
+ # include Tests
+ #
+ # def setup
+ # RenderTemplate::RenderOnceController::RESOLVER.stubs(:caching?).returns(false)
+ # end
+ # end
+end

0 comments on commit 940b577

Please sign in to comment.