diff options
Diffstat (limited to 'actionpack/lib/action_view/renderer')
3 files changed, 300 insertions, 0 deletions
| 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..77cfa51dff --- /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 + +    # 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 + +    protected + +    def instrument(name, options={}) +      ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield } +    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..c580397cad --- /dev/null +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -0,0 +1,166 @@ +require 'action_view/renderer/abstract_renderer' + +module ActionView +  class PartialRenderer < AbstractRenderer #:nodoc: +    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 +          instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do +            render_collection +          end +        else +          instrument(:partial, :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 = layout.render(view, 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_prefix 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_prefix +          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..36beb5c8d0 --- /dev/null +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -0,0 +1,98 @@ +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) +        render_template(template, options[:layout], options[:locals]) +      end +    end + +    def render_once(options) +      paths, locals = options[:once], options[:locals] || {} +      layout, keys  = options[:layout], locals.keys +      prefix = options.fetch(:prefix, @view.controller_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].try(:keys) || [] + +      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) +      end +    end + +    # Renders the given template. An string representing the layout can be +    # supplied as well. +    def render_template(template, layout_name = nil, locals = {}) #:nodoc: +      lookup_context.freeze_formats(template.formats, true) +      view, locals = @view, 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 + +    # 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 | 
