From b88f4ca93bcaef9a6bfd21d95acc8f432a3c8e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 10 Oct 2010 11:03:18 +0200 Subject: Clean up the house before moving in the new furniture. This commit moves all the template rendering logic that was hanging around AV::Base to renderer objects. --- actionpack/lib/action_view.rb | 8 +- actionpack/lib/action_view/base.rb | 9 +- .../lib/action_view/helpers/prototype_helper.rb | 2 +- actionpack/lib/action_view/render/layouts.rb | 83 ---------- actionpack/lib/action_view/render/partials.rb | 177 +-------------------- actionpack/lib/action_view/render/rendering.rb | 101 +++++++----- .../lib/action_view/renderer/abstract_renderer.rb | 36 +++++ .../lib/action_view/renderer/partial_renderer.rb | 167 +++++++++++++++++++ .../lib/action_view/renderer/template_renderer.rb | 66 ++++++++ actionpack/lib/action_view/template.rb | 5 +- 10 files changed, 348 insertions(+), 306 deletions(-) delete mode 100644 actionpack/lib/action_view/render/layouts.rb create mode 100644 actionpack/lib/action_view/renderer/abstract_renderer.rb create mode 100644 actionpack/lib/action_view/renderer/partial_renderer.rb create mode 100644 actionpack/lib/action_view/renderer/template_renderer.rb diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 5f9dc70766..ad96f6c66d 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -38,11 +38,17 @@ module ActionView autoload :Helpers autoload :Base autoload :LookupContext + autoload :Render autoload :PathSet, "action_view/paths" autoload :TestCase, "action_view/test_case" + autoload_under "renderer" do + autoload :AbstractRenderer + autoload :PartialRenderer + autoload :TemplateRenderer + end + autoload_under "render" do - autoload :Layouts autoload :Partials autoload :Rendering end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 0bef3e3a08..2a601c7cee 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -158,7 +158,7 @@ module ActionView #:nodoc: module Subclasses end - include Helpers, Rendering, Partials, Layouts, ::ERB::Util, Context + include Helpers, Rendering, Partials, ::ERB::Util, Context # Specify whether RJS responses should be wrapped in a try/catch block # that alert()s the caught exception (and then re-raises it). @@ -180,8 +180,7 @@ module ActionView #:nodoc: attr_accessor :base_path, :assigns, :template_extension, :lookup_context attr_internal :captures, :request, :controller, :template, :config - delegate :find_template, :template_exists?, :formats, :formats=, :locale, :locale=, - :view_paths, :view_paths=, :with_fallbacks, :update_details, :with_layout_format, :to => :lookup_context + delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, :flash, :action_name, :controller_name, :to => :controller @@ -220,6 +219,10 @@ 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 diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 300207dfac..41cd8d5171 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -546,7 +546,7 @@ module ActionView end def with_formats(*args) - @context ? @context.update_details(:formats => args) { yield } : yield + @context ? @context.lookup_context.update_details(:formats => args) { yield } : yield end def javascript_object_for(object) diff --git a/actionpack/lib/action_view/render/layouts.rb b/actionpack/lib/action_view/render/layouts.rb deleted file mode 100644 index 08162f7fd5..0000000000 --- a/actionpack/lib/action_view/render/layouts.rb +++ /dev/null @@ -1,83 +0,0 @@ -module ActionView - # = Action View Layouts - module Layouts - # Returns the contents that are yielded to a layout, given a name or a block. - # - # You can think of a layout as a method that is called with a block. If the user calls - # yield :some_name, the block, by default, returns content_for(:some_name). - # If the user calls simply +yield+, the default block returns content_for(:layout). - # - # The user can override this default by passing a block to the layout: - # - # # The template - # <%= render :layout => "my_layout" do %> - # Content - # <% end %> - # - # # The layout - # - # <%= yield %> - # - # - # In this case, instead of the default block, which would return content_for(:layout), - # this method returns the block that was passed in to render :layout, and the response - # would be - # - # - # Content - # - # - # Finally, the block can take block arguments, which can be passed in by +yield+: - # - # # The template - # <%= render :layout => "my_layout" do |customer| %> - # Hello <%= customer.name %> - # <% end %> - # - # # The layout - # - # <%= yield Struct.new(:name).new("David") %> - # - # - # In this case, the layout would receive the block passed into render :layout, - # and the struct specified would be passed into the block as an argument. The result - # would be - # - # - # Hello David - # - # - def _layout_for(*args, &block) #:nodoc: - name = args.first - - if name.is_a?(Symbol) - @_content_for[name].html_safe - elsif block - capture(*args, &block) - else - @_content_for[:layout].html_safe - end - end - - # This is the method which actually finds the layout using details in the lookup - # context object. If no layout is found, it checks if at least a layout with - # the given name exists across all details before raising the error. - def find_layout(layout, keys) - begin - with_layout_format do - layout =~ /^\// ? - with_fallbacks { find_template(layout, nil, false, keys) } : find_template(layout, nil, false, keys) - end - rescue ActionView::MissingTemplate => e - update_details(:formats => nil) do - raise unless template_exists?(layout) - end - end - end - - # Contains the logic that actually renders the layout. - def _render_layout(layout, locals, &block) #:nodoc: - layout.render(self, locals){ |*name| _layout_for(*name, &block) } - end - end -end diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 4e03d43358..844c3e9572 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -210,181 +210,12 @@ module ActionView # <%- end -%> # <% end %> module Partials - extend ActiveSupport::Concern - - class PartialRenderer - PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} } - - def initialize(view_context, options, block) - @view = view_context - @partial_names = PARTIAL_NAMES[@view.controller.class.name] - setup(options, block) - end - - def setup(options, block) - partial = options[:partial] - - @options = options - @locals = options[:locals] || {} - @block = block - - if String === partial - @object = options[:object] - @path = partial - @collection = collection - else - @object = partial - - if @collection = collection_from_object || collection - paths = @collection_data = @collection.map { |o| partial_path(o) } - @path = paths.uniq.size == 1 ? paths.first : nil - else - @path = partial_path - end - end - - if @path - @variable, @variable_counter = retrieve_variable(@path) - else - paths.map! { |path| retrieve_variable(path).unshift(path) } - end - end - - def retrieve_variable(path) - variable = @options[:as] || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym - variable_counter = :"#{variable}_counter" if @collection - [variable, variable_counter] - end - - def render - identifier = ((@template = find_partial) ? @template.identifier : @path) - - if @collection - ActiveSupport::Notifications.instrument("render_collection.action_view", - :identifier => identifier || "collection", :count => @collection.size) do - render_collection - end - else - if !@block && (layout = @options[:layout]) - layout = find_template(layout) - end - - content = ActiveSupport::Notifications.instrument("render_partial.action_view", - :identifier => identifier) do - render_partial - end - - content = @view._render_layout(layout, @locals){ content } if layout - content - end - end - - def render_collection - return nil if @collection.blank? - - if @options.key?(:spacer_template) - spacer = find_template(@options[:spacer_template]).render(@view, @locals) - end - - result = @template ? collection_with_template : collection_without_template - result.join(spacer).html_safe - end - - def collection_with_template - segments, locals, template = [], @locals, @template - as, counter = @variable, @variable_counter - - locals[counter] = -1 - - @collection.each do |object| - locals[counter] += 1 - locals[as] = object - segments << template.render(@view, locals) - end - - segments - end - - def collection_without_template - segments, locals, collection_data = [], @locals, @collection_data - index, template, cache = -1, nil, {} - keys = @locals.keys - - @collection.each_with_index do |object, i| - path, *data = collection_data[i] - template = (cache[path] ||= find_template(path, keys + data)) - locals[data[0]] = object - locals[data[1]] = (index += 1) - segments << template.render(@view, locals) - end - - @template = template - segments - end - - def render_partial - locals, view = @locals, @view - object, as = @object, @variable - - object ||= locals[as] - locals[as] = object - - @template.render(view, locals) do |*name| - view._layout_for(*name, &@block) - end - end - - private - - def collection - if @options.key?(:collection) - @options[:collection] || [] - end - end - - def collection_from_object - if @object.respond_to?(:to_ary) - @object - end - end - - def find_partial - if path = @path - locals = @locals.keys - locals << @variable - locals << @variable_counter if @collection - find_template(path, locals) - end - end - - def find_template(path=@path, locals=@locals.keys) - prefix = @view.controller_path unless path.include?(?/) - @view.find_template(path, prefix, true, locals) - end - - def partial_path(object = @object) - @partial_names[object.class.name] ||= begin - object = object.to_model if object.respond_to?(:to_model) - - object.class.model_name.partial_path.dup.tap do |partial| - path = @view.controller_path - partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/) - end - end - end - end - def _render_partial(options, &block) #:nodoc: - _wrap_formats(options[:partial]) do - if defined?(@renderer) - @renderer.setup(options, block) - else - @renderer = PartialRenderer.new(self, options, block) - end - - @renderer.render - end + _partial_renderer.setup(options, block).render end + def _partial_renderer #:nodoc: + @_partial_renderer ||= PartialRenderer.new(self) + end end end diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index 8e599c71df..adbb6bc626 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -21,11 +21,7 @@ module ActionView elsif options.key?(:partial) _render_partial(options) else - _wrap_formats(options[:template] || options[:file]) do - template = _determine_template(options) - lookup_context.freeze_formats(template.formats, true) - _render_template(template, options[:layout], options) - end + _render_template(options) end when :update update_page(&block) @@ -34,51 +30,70 @@ module ActionView end 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) - return yield unless value.is_a?(String) - @@formats_regexp ||= /\.(#{Mime::SET.symbols.join('|')})$/ + # Returns the contents that are yielded to a layout, given a name or a block. + # + # You can think of a layout as a method that is called with a block. If the user calls + # yield :some_name, the block, by default, returns content_for(:some_name). + # If the user calls simply +yield+, the default block returns content_for(:layout). + # + # The user can override this default by passing a block to the layout: + # + # # The template + # <%= render :layout => "my_layout" do %> + # Content + # <% end %> + # + # # The layout + # + # <%= yield %> + # + # + # In this case, instead of the default block, which would return content_for(:layout), + # this method returns the block that was passed in to render :layout, and the response + # would be + # + # + # Content + # + # + # Finally, the block can take block arguments, which can be passed in by +yield+: + # + # # The template + # <%= render :layout => "my_layout" do |customer| %> + # Hello <%= customer.name %> + # <% end %> + # + # # The layout + # + # <%= yield Struct.new(:name).new("David") %> + # + # + # In this case, the layout would receive the block passed into render :layout, + # and the struct specified would be passed into the block as an argument. The result + # would be + # + # + # Hello David + # + # + def _layout_for(*args, &block) + name = args.first - if value.sub!(@@formats_regexp, "") - update_details(:formats => [$1.to_sym]){ yield } + if name.is_a?(Symbol) + @_content_for[name].html_safe + elsif block + capture(*args, &block) else - yield + @_content_for[:layout].html_safe end end - # Determine the template to be rendered using the given options. - def _determine_template(options) #:nodoc: - keys = (options[:locals] ||= {}).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) - 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?(:template) - options[:template].respond_to?(:render) ? - options[:template] : find_template(options[:template], options[:prefix], false, keys) - end + def _render_template(options) #:nodoc: + _template_renderer.render(options) end - # Renders the given template. An string representing the layout can be - # supplied as well. - def _render_template(template, layout = nil, options = {}) #:nodoc: - locals = options[:locals] || {} - layout = find_layout(layout, locals.keys) if layout - - ActiveSupport::Notifications.instrument("render_template.action_view", - :identifier => template.identifier, :layout => layout.try(:virtual_path)) do - - content = template.render(self, locals) { |*name| _layout_for(*name) } - @_content_for[:layout] = content if layout - - content = _render_layout(layout, locals) if layout - content - end + def _template_renderer #:nodoc: + @_template_renderer ||= TemplateRenderer.new(self) end end end diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb new file mode 100644 index 0000000000..f9fa63ce7f --- /dev/null +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -0,0 +1,36 @@ +module ActionView + class AbstractRenderer #:nodoc: + attr_reader :vew, :lookup_context + + delegate :find_template, :template_exists?, :with_fallbacks, :update_details, + :with_layout_format, :formats, :to => :lookup_context + + def initialize(view) + @view = view + @lookup_context = view.lookup_context + end + + 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) + return yield unless value.is_a?(String) + @@formats_regexp ||= /\.(#{Mime::SET.symbols.join('|')})$/ + + if value.sub!(@@formats_regexp, "") + update_details(:formats => [$1.to_sym]){ yield } + else + yield + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb new file mode 100644 index 0000000000..3be1702f9e --- /dev/null +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -0,0 +1,167 @@ +require 'action_view/renderer/abstract_renderer' + +module ActionView + class PartialRenderer < AbstractRenderer #:nodoc: + N = ::ActiveSupport::Notifications + PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} } + + def initialize(view) + super + @partial_names = PARTIAL_NAMES[@view.controller.class.name] + end + + def setup(options, block) + partial = options[:partial] + + @options = options + @locals = options[:locals] || {} + @block = block + + if String === partial + @object = options[:object] + @path = partial + @collection = collection + else + @object = partial + + if @collection = collection_from_object || collection + paths = @collection_data = @collection.map { |o| partial_path(o) } + @path = paths.uniq.size == 1 ? paths.first : nil + else + @path = partial_path + end + end + + if @path + @variable, @variable_counter = retrieve_variable(@path) + else + paths.map! { |path| retrieve_variable(path).unshift(path) } + end + + self + end + + def render + wrap_formats(@path) do + identifier = ((@template = find_partial) ? @template.identifier : @path) + + if @collection + N.instrument("render_collection.action_view", :identifier => identifier || "collection", :count => @collection.size) do + render_collection + end + else + N.instrument("render_partial.action_view", :identifier => identifier) do + render_partial + end + end + end + end + + def render_collection + return nil if @collection.blank? + + if @options.key?(:spacer_template) + spacer = find_template(@options[:spacer_template]).render(@view, @locals) + end + + result = @template ? collection_with_template : collection_without_template + result.join(spacer).html_safe + end + + def render_partial + locals, view, block = @locals, @view, @block + object, as = @object, @variable + + if !block && (layout = @options[:layout]) + layout = find_template(layout) + end + + object ||= locals[as] + locals[as] = object + + content = @template.render(view, locals) do |*name| + view._layout_for(*name, &block) + end + + content = render_layout(layout, locals){ content } if layout + content + end + + private + + def collection + if @options.key?(:collection) + @options[:collection] || [] + end + end + + def collection_from_object + if @object.respond_to?(:to_ary) + @object + end + end + + def find_partial + if path = @path + locals = @locals.keys + locals << @variable + locals << @variable_counter if @collection + find_template(path, locals) + end + end + + def find_template(path=@path, locals=@locals.keys) + prefix = @view.controller_path unless path.include?(?/) + @lookup_context.find_template(path, prefix, true, locals) + end + + def collection_with_template + segments, locals, template = [], @locals, @template + as, counter = @variable, @variable_counter + + locals[counter] = -1 + + @collection.each do |object| + locals[counter] += 1 + locals[as] = object + segments << template.render(@view, locals) + end + + segments + end + + def collection_without_template + segments, locals, collection_data = [], @locals, @collection_data + index, template, cache = -1, nil, {} + keys = @locals.keys + + @collection.each_with_index do |object, i| + path, *data = collection_data[i] + template = (cache[path] ||= find_template(path, keys + data)) + locals[data[0]] = object + locals[data[1]] = (index += 1) + segments << template.render(@view, locals) + end + + @template = template + segments + end + + def partial_path(object = @object) + @partial_names[object.class.name] ||= begin + object = object.to_model if object.respond_to?(:to_model) + + object.class.model_name.partial_path.dup.tap do |partial| + path = @view.controller_path + partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/) + end + end + end + + def retrieve_variable(path) + variable = @options[:as] || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym + variable_counter = :"#{variable}_counter" if @collection + [variable, variable_counter] + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb new file mode 100644 index 0000000000..3acc68dcac --- /dev/null +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -0,0 +1,66 @@ +require 'action_view/renderer/abstract_renderer' + +module ActionView + class TemplateRenderer < AbstractRenderer #:nodoc: + 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) + end + end + + # Determine the template to be rendered using the given options. + def determine_template(options) #:nodoc: + keys = (options[:locals] ||= {}).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) + 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?(:template) + options[:template].respond_to?(:render) ? + options[:template] : find_template(options[:template], options[:prefix], false, keys) + end + end + + # 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 + + 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) + end + + content + end + end + + # This is the method which actually finds the layout using details in the lookup + # context object. If no layout is found, it checks if at least a layout with + # the given name exists across all details before raising the error. + def find_layout(layout, keys) + begin + with_layout_format do + layout =~ /^\// ? + with_fallbacks { find_template(layout, nil, false, keys) } : find_template(layout, nil, false, keys) + end + rescue ActionView::MissingTemplate => e + update_details(:formats => nil) do + raise unless template_exists?(layout) + end + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 3cc9bb2710..074daa5d28 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -155,11 +155,12 @@ module ActionView # virtual path set (true just for inline templates). def refresh(view) raise "A template need to have a virtual path in order to be refreshed" unless @virtual_path + lookup = view.lookup_context pieces = @virtual_path.split("/") name = pieces.pop partial = name.sub!(/^_/, "") - view.lookup_context.disable_cache do - view.find_template(name, pieces.join, partial || false, @locals) + lookup.disable_cache do + lookup.find_template(name, pieces.join, partial || false, @locals) end end -- cgit v1.2.3