diff options
| author | Jon Leighton <j@jonathanleighton.com> | 2010-10-14 10:25:43 +0100 | 
|---|---|---|
| committer | Jon Leighton <j@jonathanleighton.com> | 2010-10-14 10:25:43 +0100 | 
| commit | 3fb493c2b037ffbdda5c91d66334ec6f79faa2d1 (patch) | |
| tree | e49b072103bbfe6fb6159954c786a31f44099325 /actionpack/lib | |
| parent | 212fdd8ba9624f61421a7a950283537a3d39ac18 (diff) | |
| parent | 01ab6f961bff150d50c99f03fa3946f48ac29b17 (diff) | |
| download | rails-3fb493c2b037ffbdda5c91d66334ec6f79faa2d1.tar.gz rails-3fb493c2b037ffbdda5c91d66334ec6f79faa2d1.tar.bz2 rails-3fb493c2b037ffbdda5c91d66334ec6f79faa2d1.zip  | |
Merge branch 'master' into nested_has_many_through
Conflicts:
	activerecord/lib/active_record/associations.rb
	activerecord/test/cases/associations/cascaded_eager_loading_test.rb
Diffstat (limited to 'actionpack/lib')
38 files changed, 724 insertions, 482 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index f9f6eb945e..f83eaded88 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -13,6 +13,7 @@ module AbstractController    class Base      attr_internal :response_body      attr_internal :action_name +    attr_internal :formats      include ActiveSupport::Configurable      extend ActiveSupport::DescendantsTracker diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 5d9b35d297..1c63fb2d14 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -12,7 +12,6 @@ module AbstractController    # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,    # it will trigger the lookup_context and consequently expire the cache. -  # TODO Add some deprecation warnings to remove I18n.locale from controllers    class I18nProxy < ::I18n::Config #:nodoc:      attr_reader :i18n_config, :lookup_context @@ -50,7 +49,7 @@ module AbstractController              if controller.respond_to?(:_helpers)                include controller._helpers -              if controller.respond_to?(:_routes) +              if controller.respond_to?(:_routes) && controller._routes                  include controller._routes.url_helpers                  include controller._routes.mounted_helpers                end @@ -156,7 +155,7 @@ module AbstractController          options[:partial] = action_name        end -      if (options.keys & [:partial, :file, :template]).empty? +      if (options.keys & [:partial, :file, :template, :once]).empty?          options[:prefix] ||= _prefix        end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index ace1aabe03..329798e84f 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -119,6 +119,11 @@ module ActionController        headers["Location"] = url      end +    # basic url_for that can be overridden for more robust functionality +    def url_for(string) +      string +    end +      def status        @_status      end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 2b4a3b9392..8abcad55a2 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -2,8 +2,6 @@ module ActionController    module Head      extend ActiveSupport::Concern -    include ActionController::UrlFor -      # Return a response that has no content (merely headers). The options      # argument is interpreted to be a hash of header names and values.      # This allows you to easily return a response that consists only of @@ -27,8 +25,8 @@ module ActionController        self.status = status        self.location = url_for(location) if location -      self.content_type = Mime[formats.first] +      self.content_type = Mime[formats.first] if formats        self.response_body = " "      end    end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 86bb810947..e524e546ad 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -2,7 +2,6 @@ module ActionController    module Rendering      extend ActiveSupport::Concern -    include ActionController::RackDelegation      include AbstractController::Rendering      # Before processing, set the request formats in current controller formats. diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 851925e1b7..38d32211cc 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -161,6 +161,8 @@ module ActionController #:nodoc:          display resource.errors, :status => :unprocessable_entity        elsif post?          display resource, :status => :created, :location => api_location +      elsif has_empty_resource_definition? +        display empty_resource, :status => :ok        else          head :ok        end @@ -221,5 +223,23 @@ module ActionController #:nodoc:      def default_action        @action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]      end + +    # Check whether resource needs a specific definition of empty resource to be valid +    # +    def has_empty_resource_definition? +      respond_to?("empty_#{format}_resource") +    end + +    # Delegate to proper empty resource method +    # +    def empty_resource +      send("empty_#{format}_resource") +    end + +    # Return a valid empty JSON resource +    # +    def empty_json_resource +      "{}" +    end    end  end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 2013da3adb..312dc8eb3e 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -101,10 +101,6 @@ module ActionController #:nodoc:        #   send_data image.data, :type => image.content_type, :disposition => 'inline'        #        # See +send_file+ for more information on HTTP Content-* headers and caching. -      # -      # <b>Tip:</b> if you want to stream large amounts of on-the-fly generated -      # data to the browser, then use <tt>render :text => proc { ... }</tt> -      # instead. See ActionController::Base#render for more information.        def send_data(data, options = {}) #:doc:          send_file_headers! options.dup          render options.slice(:status, :content_type).merge(:text => data) diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 0ade42ba2d..c5a661f2b0 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -21,10 +21,10 @@ module ActionController        paths   = app.config.paths        options = app.config.action_controller -      options.assets_dir           ||= paths.public.to_a.first -      options.javascripts_dir      ||= paths.public.javascripts.to_a.first -      options.stylesheets_dir      ||= paths.public.stylesheets.to_a.first -      options.page_cache_directory ||= paths.public.to_a.first +      options.assets_dir           ||= paths["public"].first +      options.javascripts_dir      ||= paths["public/javascripts"].first +      options.stylesheets_dir      ||= paths["public/stylesheets"].first +      options.page_cache_directory ||= paths["public"].first        # make sure readers methods get compiled        options.asset_path           ||= nil diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb index fa71d55946..7a59d4f2f3 100644 --- a/actionpack/lib/action_controller/railties/paths.rb +++ b/actionpack/lib/action_controller/railties/paths.rb @@ -5,12 +5,14 @@ module ActionController          Module.new do            define_method(:inherited) do |klass|              super(klass) +              if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) } -              klass.helpers_path = namespace._railtie.config.paths.app.helpers.to_a +              paths = namespace._railtie.paths["app/helpers"].existent              else -              klass.helpers_path = app.config.helpers_paths +              paths = app.config.helpers_paths              end +            klass.helpers_path = paths              klass.helper :all if klass.superclass == ActionController::Base            end          end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index bbcdefb190..55a3166f6d 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -214,13 +214,13 @@ module ActionDispatch      # Override Rack's GET method to support indifferent access      def GET -      @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super) +      @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {})      end      alias :query_parameters :GET      # Override Rack's POST method to support indifferent access      def POST -      @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super) +      @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {})      end      alias :request_parameters :POST diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index ef0c9c51f5..71e736ce9f 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -62,6 +62,7 @@ module ActionDispatch      private        def render_exception(env, exception)          log_error(exception) +        exception = original_exception(exception)          request = Request.new(env)          if @consider_all_requests_local || request.local? @@ -154,5 +155,17 @@ module ActionDispatch        def logger          defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)        end + +    def original_exception(exception) +      if registered_original_exception?(exception) +        exception.original_exception +      else +        exception +      end +    end + +    def registered_original_exception?(exception) +      exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name) +    end    end  end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index bf10f81127..3c7dcea003 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -735,15 +735,15 @@ module ActionDispatch            resource_scope(SingletonResource.new(resources.pop, options)) do              yield if block_given? -            collection_scope do +            collection do                post :create              end if parent_resource.actions.include?(:create) -            new_scope do +            new do                get :new              end if parent_resource.actions.include?(:new) -            member_scope  do +            member do                get    :edit if parent_resource.actions.include?(:edit)                get    :show if parent_resource.actions.include?(:show)                put    :update if parent_resource.actions.include?(:update) @@ -780,16 +780,16 @@ module ActionDispatch            resource_scope(Resource.new(resources.pop, options)) do              yield if block_given? -            collection_scope do +            collection do                get  :index if parent_resource.actions.include?(:index)                post :create if parent_resource.actions.include?(:create)              end -            new_scope do +            new do                get :new              end if parent_resource.actions.include?(:new) -            member_scope  do +            member do                get    :edit if parent_resource.actions.include?(:edit)                get    :show if parent_resource.actions.include?(:show)                put    :update if parent_resource.actions.include?(:update) @@ -813,12 +813,14 @@ module ActionDispatch          # create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>          # route helpers.          def collection -          unless @scope[:scope_level] == :resources -            raise ArgumentError, "can't use collection outside resources scope" +          unless resource_scope? +            raise ArgumentError, "can't use collection outside resource(s) scope"            end -          collection_scope do -            yield +          with_scope_level(:collection) do +            scope(parent_resource.collection_scope) do +              yield +            end            end          end @@ -838,8 +840,10 @@ module ActionDispatch              raise ArgumentError, "can't use member outside resource(s) scope"            end -          member_scope do -            yield +          with_scope_level(:member) do +            scope(parent_resource.member_scope) do +              yield +            end            end          end @@ -848,8 +852,10 @@ module ActionDispatch              raise ArgumentError, "can't use new outside resource(s) scope"            end -          new_scope do -            yield +          with_scope_level(:new) do +            scope(parent_resource.new_scope(action_path(:new))) do +              yield +            end            end          end @@ -1034,30 +1040,6 @@ module ActionDispatch              end            end -          def new_scope -            with_scope_level(:new) do -              scope(parent_resource.new_scope(action_path(:new))) do -                yield -              end -            end -          end - -          def collection_scope -            with_scope_level(:collection) do -              scope(parent_resource.collection_scope) do -                yield -              end -            end -          end - -          def member_scope -            with_scope_level(:member) do -              scope(parent_resource.member_scope) do -                yield -              end -            end -          end -            def nested_options              {}.tap do |options|                options[:as] = parent_resource.member_name diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 5f9dc70766..dada64a86f 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -21,9 +21,6 @@  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  #++ -activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) -$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) -  require 'active_support/ruby/shim'  require 'active_support/core_ext/class/attribute_accessors' @@ -33,24 +30,27 @@ module ActionView    extend ActiveSupport::Autoload    eager_autoload do +    autoload :Base      autoload :Context -    autoload :Template      autoload :Helpers -    autoload :Base      autoload :LookupContext -    autoload :PathSet,        "action_view/paths" -    autoload :TestCase,       "action_view/test_case" +    autoload :Partials +    autoload :PathSet +    autoload :Rendering +    autoload :Template +    autoload :TestCase -    autoload_under "render" do -      autoload :Layouts -      autoload :Partials -      autoload :Rendering +    autoload_under "renderer" do +      autoload :AbstractRenderer +      autoload :PartialRenderer +      autoload :TemplateRenderer      end      autoload_at "action_view/template/resolver" do        autoload :Resolver        autoload :PathResolver        autoload :FileSystemResolver +      autoload :FallbackFileSystemResolver      end      autoload_at "action_view/template/error" do diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 0bef3e3a08..1beae37af3 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -155,10 +155,7 @@ module ActionView #:nodoc:    #    # See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods documentation for more details.    class Base -    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). @@ -177,13 +174,12 @@ module ActionView #:nodoc:        delegate :logger, :to => 'ActionController::Base', :allow_nil => true      end -    attr_accessor :base_path, :assigns, :template_extension, :lookup_context -    attr_internal :captures, :request, :controller, :template, :config +    attr_accessor :_template +    attr_internal :request, :controller, :config, :assigns, :lookup_context -    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, +    delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,               :flash, :action_name, :controller_name, :to => :controller      delegate :logger, :to => :controller, :allow_nil => true @@ -198,32 +194,40 @@ module ActionView #:nodoc:      end      def assign(new_assigns) # :nodoc: -      self.assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } +      @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }      end      def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc:        assign(assigns_for_first_render) -      self.helpers = self.class.helpers || Module.new - -      if @_controller = controller -        @_request = controller.request if controller.respond_to?(:request) -      end - -      @_config = controller && controller.respond_to?(:config) ? controller.config.inheritable_copy : {} +      self.helpers = Module.new unless self.class.helpers +      @_config = {}        @_content_for  = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }        @_virtual_path = nil        @output_buffer = nil -      @lookup_context = lookup_context.is_a?(ActionView::LookupContext) ? +      if @_controller = controller +        @_request = controller.request if controller.respond_to?(:request) +        @_config  = controller.config.inheritable_copy if controller.respond_to?(:config) +      end + +      @_lookup_context = lookup_context.is_a?(ActionView::LookupContext) ?          lookup_context : ActionView::LookupContext.new(lookup_context) -      @lookup_context.formats = formats if formats +      @_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 +    def controller_prefix +      @controller_prefix ||= controller && controller._prefix +    end +      ActiveSupport.run_load_hooks(:action_view, self)    end  end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index c1dfbe5dc3..d0f91f6b71 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -152,7 +152,7 @@ module ActionView      #      #   # Normally you'd calculate RELEASE_NUMBER at startup.      #   RELEASE_NUMBER = 12345 -    #   config.action_controller.asset_path_template = proc { |asset_path| +    #   config.action_controller.asset_path = proc { |asset_path|      #     "/release-#{RELEASE_NUMBER}#{asset_path}"      #   }      # @@ -292,9 +292,6 @@ module ActionView        #        # * = The application.js file is only referenced if it exists        # -      # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason -      # (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method. -      #        # You can also include all javascripts in the +javascripts+ directory using <tt>:all</tt> as the source:        #        #   javascript_include_tag :all # => diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 3cd8b02bc4..b34a74788e 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -1020,14 +1020,9 @@ module ActionView          def value_before_type_cast(object, method_name)            unless object.nil? -            if object.respond_to?(method_name) -              object.send(method_name) -            # FIXME: this is AR dependent -            elsif object.respond_to?(method_name + "_before_type_cast") -              object.send(method_name + "_before_type_cast") -            else -              raise NoMethodError, "Model #{object.class} does not respond to #{method_name}" -            end +            object.respond_to?(method_name + "_before_type_cast") ? +            object.send(method_name + "_before_type_cast") : +            object.send(method_name)            end          end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 298db46177..ae83b6bf39 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -390,7 +390,7 @@ module ActionView          end          if confirm = options.delete("confirm") -          add_confirm_to_attributes!(options, confirm) +          options["data-confirm"] = confirm          end          tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys) @@ -423,7 +423,7 @@ module ActionView          options.stringify_keys!          if confirm = options.delete("confirm") -          add_confirm_to_attributes!(options, confirm) +          options["data-confirm"] = confirm          end          tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys) 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/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 13767a09f9..8574ca6595 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -45,8 +45,8 @@ module ActionView        private          def scope_key_by_partial(key)            if key.to_s.first == "." -            if @_virtual_path -              @_virtual_path.gsub(%r{/_?}, ".") + key.to_s +            if (path = @_template && @_template.virtual_path) +              path.gsub(%r{/_?}, ".") + key.to_s              else                raise "Cannot use t(#{key.inspect}) shortcut because path is not available"              end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index da42d94318..c007cac47f 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -587,10 +587,12 @@ module ActionView              html_options = html_options.stringify_keys              html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options) +            disable_with = html_options.delete("disable_with")              confirm = html_options.delete('confirm')              method  = html_options.delete('method') -            add_confirm_to_attributes!(html_options, confirm) if confirm +            html_options["data-disable-with"] = disable_with if disable_with +            html_options["data-confirm"] = confirm if confirm              add_method_to_attributes!(html_options, method)   if method              html_options @@ -601,13 +603,9 @@ module ActionView            options.is_a?(Hash) && options.key?('remote') && options.delete('remote')          end -        def add_confirm_to_attributes!(html_options, confirm) -          html_options["data-confirm"] = confirm if confirm -        end -          def add_method_to_attributes!(html_options, method) -          html_options["rel"] = "nofollow" if method && method.to_s.downcase != "get" -          html_options["data-method"] = method if method +          html_options["rel"] = "nofollow" if method.to_s.downcase != "get" +          html_options["data-method"] = method          end          def options_for_javascript(options) diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 3ea8b86af1..27f94a73a6 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -10,7 +10,7 @@ module ActionView    # this key is generated just once during the request, it speeds up all cache accesses.    class LookupContext #:nodoc:      mattr_accessor :fallbacks -    @@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")] +    @@fallbacks = FallbackFileSystemResolver.instances      mattr_accessor :registered_details      self.registered_details = [] @@ -61,6 +61,7 @@ module ActionView      def initialize(view_paths, details = {})        @details, @details_key = { :handlers => default_handlers }, nil        @frozen_formats, @skip_default_locale = false, false +      @cache = true        self.view_paths = view_paths        self.registered_detail_setters.each do |key, setter| @@ -77,17 +78,17 @@ module ActionView          @view_paths = ActionView::Base.process_view_paths(paths)        end -      def find(name, prefix = nil, partial = false) -        @view_paths.find(*args_for_lookup(name, prefix, partial)) +      def find(name, prefix = nil, partial = false, keys = []) +        @view_paths.find(*args_for_lookup(name, prefix, partial, keys))        end        alias :find_template :find -      def find_all(name, prefix = nil, partial = false) -        @view_paths.find_all(*args_for_lookup(name, prefix, partial)) +      def find_all(name, prefix = nil, partial = false, keys = []) +        @view_paths.find_all(*args_for_lookup(name, prefix, partial, keys))        end -      def exists?(name, prefix = nil, partial = false) -        @view_paths.exists?(*args_for_lookup(name, prefix, partial)) +      def exists?(name, prefix = nil, partial = false, keys = []) +        @view_paths.exists?(*args_for_lookup(name, prefix, partial, keys))        end        alias :template_exists? :exists? @@ -106,9 +107,9 @@ module ActionView      protected -      def args_for_lookup(name, prefix, partial) #:nodoc: +      def args_for_lookup(name, prefix, partial, keys) #:nodoc:          name, prefix = normalize_name(name, prefix) -        [name, prefix, partial || false, @details, details_key] +        [name, prefix, partial || false, @details, keys, details_key]        end        # Support legacy foo.erb names even though we now ignore .erb @@ -130,10 +131,20 @@ module ActionView      end      module Details +      attr_accessor :cache +        # Calculate the details key. Remove the handlers from calculation to improve performance        # since the user cannot modify it explicitly.        def details_key #:nodoc: -        @details_key ||= DetailsKey.get(@details) +        @details_key ||= DetailsKey.get(@details) if @cache +      end + +      # Temporary skip passing the details_key forward. +      def disable_cache +        old_value, @cache = @cache, false +        yield +      ensure +        @cache = old_value        end        # Freeze the current formats in the lookup context. By freezing them, you are guaranteeing diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/partials.rb index cc9b444837..844c3e9572 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -210,165 +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 -            paths = @collection_paths = @collection.map { |o| partial_path(o) } -            @path = paths.uniq.size == 1 ? paths.first : nil -          else -            @path = partial_path -          end -        end -      end - -      def render -        identifier = ((@template = find_template) ? @template.identifier : @path) - -        if @collection -          ActiveSupport::Notifications.instrument("render_collection.action_view", -            :identifier => identifier || "collection", :count => @collection.size) do -            render_collection -          end -        else -          content = ActiveSupport::Notifications.instrument("render_partial.action_view", -            :identifier => identifier) do -            render_partial -          end - -          if !@block && (layout = @options[:layout]) -            content = @view._render_layout(find_template(layout), @locals){ content } -          end - -          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(template = @template) -        segments, locals, template = [], @locals, @template - -        if @options[:as] -          as = @options[:as] -          counter = "#{as}_counter".to_sym -        else -          as = template.variable_name -          counter = template.counter_name -        end - -        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(collection_paths = @collection_paths) -        segments, locals = [], @locals -        index, template  = -1, nil - -        if @options[:as] -          as = @options[:as] -          counter = "#{as}_counter" -        end - -        @collection.each_with_index do |object, i| -          template = find_template(collection_paths[i]) -          locals[as || template.variable_name] = object -          locals[counter || template.counter_name] = (index += 1) - -          segments << template.render(@view, locals) -        end - -        @template = template -        segments -      end - -      def render_partial(object = @object) -        locals, view, template = @locals, @view, @template - -        object ||= locals[template.variable_name] -        locals[@options[:as] || template.variable_name] = object - -        template.render(view, locals) do |*name| -          view._layout_for(*name, &@block) -        end -      end - -    private - -      def collection -        if @object.respond_to?(:to_ary) -          @object -        elsif @options.key?(:collection) -          @options[:collection] || [] -        end -      end - -      def find_template(path=@path) -        return path unless path.is_a?(String) -        prefix = @view.controller_path unless path.include?(?/) -        @view.find_template(path, prefix, true) -      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: -      if defined?(@renderer) -        @renderer.setup(options, block) -      else -        @renderer = PartialRenderer.new(self, options, block) -      end - -      @renderer.render +      _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/paths.rb b/actionpack/lib/action_view/path_set.rb index 9857d688d2..dc26d75ff3 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/path_set.rb @@ -10,8 +10,8 @@ module ActionView #:nodoc:        METHOD      end -    def find(path, prefix = nil, partial = false, details = {}, key = nil) -      template = find_all(path, prefix, partial, details, key).first +    def find(path, prefix = nil, partial = false, details = {}, keys = [], key = nil) +      template = find_all(path, prefix, partial, details, keys, key).first        raise MissingTemplate.new(self, "#{prefix}/#{path}", details, partial) unless template        template      end diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb deleted file mode 100644 index 5320245173..0000000000 --- a/actionpack/lib/action_view/render/rendering.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'active_support/core_ext/object/try' - -module ActionView -  # = Action View Rendering -  module Rendering -    # Returns the result of a render that's dictated by the options hash. The primary options are: -    # -    # * <tt>:partial</tt> - See ActionView::Partials. -    # * <tt>:update</tt> - Calls update_page with the block given. -    # * <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. -    # -    # 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. -    def render(options = {}, locals = {}, &block) -      case options -      when Hash -        if block_given? -          _render_partial(options.merge(:partial => options[:layout]), &block) -        elsif options.key?(:partial) -          _render_partial(options) -        else -          template = _determine_template(options) -          lookup_context.freeze_formats(template.formats, true) -          _render_template(template, options[:layout], options) -        end -      when :update -        update_page(&block) -      else -        _render_partial(:partial => options, :locals => locals) -      end -    end - -    # Determine the template to be rendered using the given options. -    def _determine_template(options) #:nodoc: -      if options.key?(:inline) -        handler = Template.handler_class_for_extension(options[:type] || "erb") -        Template.new(options[:inline], "inline template", handler, {}) -      elsif options.key?(:text) -        Template::Text.new(options[:text], formats.try(:first)) -      elsif options.key?(:file) -        with_fallbacks { find_template(options[:file], options[:prefix]) } -      elsif options.key?(:template) -        options[:template].respond_to?(:render) ? -          options[:template] : find_template(options[:template], options[:prefix]) -      end -    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) 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 -    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..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 diff --git a/actionpack/lib/action_view/render/layouts.rb b/actionpack/lib/action_view/rendering.rb index 8882acca31..baa5d2c3fd 100644 --- a/actionpack/lib/action_view/render/layouts.rb +++ b/actionpack/lib/action_view/rendering.rb @@ -1,6 +1,38 @@ +require 'active_support/core_ext/object/try' +  module ActionView -  # = Action View Layouts -  module Layouts +  # = Action View Rendering +  module Rendering +    # Returns the result of a render that's dictated by the options hash. The primary options are: +    # +    # * <tt>:partial</tt> - See ActionView::Partials. +    # * <tt>:update</tt> - Calls update_page with the block given. +    # * <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> - Accepts a string or an array of strings and Rails will ensure they each of them 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. +    def render(options = {}, locals = {}, &block) +      case options +      when Hash +        if block_given? +          _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 +      when :update +        update_page(&block) +      else +        _render_partial(:partial => options, :locals => locals) +      end +    end +      # 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 @@ -47,7 +79,7 @@ module ActionView      #     Hello David      #   </html>      # -    def _layout_for(*args, &block) #:nodoc: +    def _layout_for(*args, &block)        name = args.first        if name.is_a?(Symbol) @@ -59,25 +91,16 @@ module ActionView        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) -      begin -        with_layout_format do -          layout =~ /^\// ? -            with_fallbacks { find_template(layout) } : find_template(layout) -        end -      rescue ActionView::MissingTemplate => e -        update_details(:formats => nil) do -          raise unless template_exists?(layout) -        end -      end +    def _render_once(options) #:nodoc: +      _template_renderer.render_once(options) +    end + +    def _render_template(options) #:nodoc: +      _template_renderer.render(options)      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) } +    def _template_renderer #:nodoc: +      @_template_renderer ||= TemplateRenderer.new(self)      end    end  end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 164d177dcc..7dd8acf37b 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -98,8 +98,9 @@ module ActionView      extend Template::Handlers -    attr_reader :source, :identifier, :handler, :virtual_path, :formats, -                :original_encoding +    attr_accessor :locals, :formats, :virtual_path + +    attr_reader :source, :identifier, :handler, :original_encoding, :updated_at      # This finalizer is needed (and exactly with a proc inside another proc)      # otherwise templates leak in development. @@ -112,49 +113,75 @@ module ActionView      end      def initialize(source, identifier, handler, details) -      @source             = source -      @identifier         = identifier -      @handler            = handler -      @original_encoding  = nil -      @method_names       = {} - -      format   = details[:format] || :html -      @formats = Array.wrap(format).map(&:to_sym) -      @virtual_path = details[:virtual_path].try(:sub, ".#{format}", "") +      format = details[:format] || (handler.default_format if handler.respond_to?(:default_format)) + +      @source            = source +      @identifier        = identifier +      @handler           = handler +      @compiled          = false +      @original_encoding = nil +      @locals            = details[:locals] || [] +      @virtual_path      = details[:virtual_path] +      @updated_at        = details[:updated_at] || Time.now +      @formats           = Array.wrap(format).map(&:to_sym)      end +    # Render a template. If the template was not compiled yet, it is done +    # exactly before rendering. +    # +    # This method is instrumented as "!render_template.action_view". Notice that +    # we use a bang in this instrumentation because you don't want to +    # consume this in production. This is only slow if it's being listened to.      def render(view, locals, &block) -      # Notice that we use a bang in this instrumentation because you don't want to -      # consume this in production. This is only slow if it's being listened to. +      old_template, view._template = view._template, self        ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do -        if view.is_a?(ActionView::CompiledTemplates) -          mod = ActionView::CompiledTemplates -        else -          mod = view.singleton_class -        end - -        method_name = compile(locals, view, mod) +        compile!(view)          view.send(method_name, locals, &block)        end      rescue Exception => e -      if e.is_a?(Template::Error) -        e.sub_template_of(self) -        raise e -      else -        raise Template::Error.new(self, view.respond_to?(:assigns) ? view.assigns : {}, e) -      end +      handle_render_error(view, e) +    ensure +      view._template = old_template      end      def mime_type        @mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first      end -    def variable_name -      @variable_name ||= @virtual_path[%r'_?(\w+)(\.\w+)*$', 1].to_sym +    # Receives a view object and return a template similar to self by using @virtual_path. +    # +    # This method is useful if you have a template object but it does not contain its source +    # anymore since it was already compiled. In such cases, all you need to do is to call +    # refresh passing in the view object. +    # +    # Notice this method raises an error if the template to be refreshed does not have a +    # virtual path set (true just for inline templates). +    def refresh(view) +      raise "A template needs 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!(/^_/, "") +      lookup.disable_cache do +        lookup.find_template(name, pieces.join, partial, @locals) +      end      end -    def counter_name -      @counter_name ||= "#{variable_name}_counter".to_sym +    # Expires this template by setting his updated_at date to Jan 1st, 1970. +    def expire! +      @updated_at = Time.utc(1970) +    end + +    # Receives a view context and renders a template exactly like self by using +    # the @virtual_path. It raises an error if no @virtual_path was given. +    def rerender(view) +      raise "A template needs to have a virtual path in order to be rerendered" unless @virtual_path +      name = @virtual_path.dup +      if name.sub!(/(^|\/)_([^\/]*)$/, '\1\2') +        view.render :partial => name +      else +        view.render :template => @virtual_path +      end      end      def inspect @@ -166,7 +193,27 @@ module ActionView          end      end -    private +    protected + +      # Compile a template. This method ensures a template is compiled +      # just once and removes the source after it is compiled. +      def compile!(view) #:nodoc: +        return if @compiled + +        if view.is_a?(ActionView::CompiledTemplates) +          mod = ActionView::CompiledTemplates +        else +          mod = view.singleton_class +        end + +        compile(view, mod) + +        # Just discard the source if we have a virtual path. This +        # means we can get the template back. +        @source = nil if @virtual_path +        @compiled = true +      end +        # Among other things, this method is responsible for properly setting        # the encoding of the source. Until this point, we assume that the        # source is BINARY data. If no additional information is supplied, @@ -187,11 +234,8 @@ module ActionView        # encode the source into Encoding.default_internal. In general,        # this means that templates will be UTF-8 inside of Rails,        # regardless of the original source encoding. -      def compile(locals, view, mod) -        method_name = build_method_name(locals) -        return method_name if view.respond_to?(method_name) - -        locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join +      def compile(view, mod) #:nodoc: +        method_name = self.method_name          if source.encoding_aware?            # Look for # encoding: *. If we find one, we'll encode the @@ -231,9 +275,9 @@ module ActionView          # encoding of the code          source = <<-end_src            def #{method_name}(local_assigns) -            _old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code} +            _old_output_buffer = @output_buffer;#{locals_code};#{code}            ensure -            @_virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer +            @output_buffer = _old_output_buffer            end          end_src @@ -256,8 +300,6 @@ module ActionView          begin            mod.module_eval(source, identifier, 0)            ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) - -          method_name          rescue Exception => e # errors from template code            if logger = (view && view.logger)              logger.debug "ERROR: compiling #{method_name} RAISED #{e}" @@ -269,12 +311,27 @@ module ActionView          end        end -      def build_method_name(locals) -        @method_names[locals.keys.hash] ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") +      def handle_render_error(view, e) #:nodoc: +        if e.is_a?(Template::Error) +          e.sub_template_of(self) +          raise e +        else +          assigns  = view.respond_to?(:assigns) ? view.assigns : {} +          template = @virtual_path ? refresh(view) : self +          raise Template::Error.new(template, assigns, e) +        end +      end + +      def locals_code #:nodoc: +        @locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join +      end + +      def method_name #:nodoc: +        @method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_")        end -      def identifier_method_name -        @identifier_method_name ||= inspect.gsub(/[^a-z_]/, '_') +      def identifier_method_name #:nodoc: +        inspect.gsub(/[^a-z_]/, '_')        end    end  end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index 423e1e0bf5..ff256738a9 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -43,8 +43,9 @@ module ActionView    end    class Template -    # The Template::Error exception is raised when the compilation of the template fails. This exception then gathers a -    # bunch of intimate details and uses it to report a very precise exception message. +    # The Template::Error exception is raised when the compilation or rendering of the template +    # fails. This exception then gathers a bunch of intimate details and uses it to report a +    # precise exception message.      class Error < ActionViewError #:nodoc:        SOURCE_CODE_RADIUS = 3 diff --git a/actionpack/lib/action_view/template/handler.rb b/actionpack/lib/action_view/template/handler.rb index c6a1bc6235..636f3ebbad 100644 --- a/actionpack/lib/action_view/template/handler.rb +++ b/actionpack/lib/action_view/template/handler.rb @@ -1,4 +1,4 @@ -require "action_dispatch/http/mime_type" +require 'action_dispatch/http/mime_type'  require 'active_support/core_ext/class/attribute'  # Legacy TemplateHandler stub @@ -7,6 +7,8 @@ module ActionView      module Handlers #:nodoc:        module Compilable          def self.included(base) +          ActiveSupport::Deprecation.warn "Including Compilable in your template handler is deprecated. " << +            "Since Rails 3, all the API your template handler needs to implement is to respond to #call."            base.extend(ClassMethods)          end @@ -26,6 +28,12 @@ module ActionView        class_attribute :default_format        self.default_format = Mime::HTML +      def self.inherited(base) +        ActiveSupport::Deprecation.warn "Inheriting from ActionView::Template::Handler is deprecated. " << +          "Since Rails 3, all the API your template handler needs to implement is to respond to #call." +        super +      end +        def self.call(template)          raise "Need to implement #{self.class.name}#call(template)"        end diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb index ed397699b0..60347e2a95 100644 --- a/actionpack/lib/action_view/template/handlers.rb +++ b/actionpack/lib/action_view/template/handlers.rb @@ -7,13 +7,9 @@ module ActionView #:nodoc:        autoload :Builder, 'action_view/template/handlers/builder'        def self.extended(base) -        base.register_default_template_handler :erb, ERB -        base.register_template_handler :rjs, RJS -        base.register_template_handler :builder, Builder - -        # TODO: Depreciate old template extensions -        base.register_template_handler :rhtml, ERB -        base.register_template_handler :rxml, Builder +        base.register_default_template_handler :erb, ERB.new +        base.register_template_handler :rjs, RJS.new +        base.register_template_handler :builder, Builder.new        end        @@template_handlers = {} diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionpack/lib/action_view/template/handlers/builder.rb index a93cfca8aa..2c52cfd90e 100644 --- a/actionpack/lib/action_view/template/handlers/builder.rb +++ b/actionpack/lib/action_view/template/handlers/builder.rb @@ -1,11 +1,11 @@  module ActionView    module Template::Handlers -    class Builder < Template::Handler -      include Compilable - +    class Builder +      # Default format used by Builder. +      class_attribute :default_format        self.default_format = Mime::XML -      def compile(template) +      def call(template)          require 'builder'          "xml = ::Builder::XmlMarkup.new(:indent => 2);" +            "self.output_buffer = xml.target!;" + diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 24e1e44c1d..b827610456 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -1,6 +1,7 @@  require 'active_support/core_ext/class/attribute_accessors'  require 'active_support/core_ext/string/output_safety' -require "action_view/template" +require 'action_view/template' +require 'action_view/template/handler'  require 'erubis'  module ActionView @@ -47,28 +48,31 @@ module ActionView          end        end -      class ERB < Handler -        include Compilable - -        ## -        # :singleton-method: +      class ERB          # Specify trim mode for the ERB compiler. Defaults to '-'.          # See ERb documentation for suitable values. -        cattr_accessor :erb_trim_mode +        class_attribute :erb_trim_mode          self.erb_trim_mode = '-' +        # Default format used by ERB. +        class_attribute :default_format          self.default_format = Mime::HTML -        cattr_accessor :erb_implementation +        # Default implemenation used. +        class_attribute :erb_implementation          self.erb_implementation = Erubis          ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*") -        def self.handles_encoding? +        def self.call(template) +          new.call(template) +        end + +        def handles_encoding?            true          end -        def compile(template) +        def call(template)            if template.source.encoding_aware?              # First, convert to BINARY, so in case the encoding is              # wrong, we can still find an encoding tag @@ -94,6 +98,7 @@ module ActionView          end        private +          def valid_encoding(string, encoding)            # If a magic encoding comment was found, tag the            # String with this encoding. This is for a case diff --git a/actionpack/lib/action_view/template/handlers/rjs.rb b/actionpack/lib/action_view/template/handlers/rjs.rb index 128be5077c..9d71059134 100644 --- a/actionpack/lib/action_view/template/handlers/rjs.rb +++ b/actionpack/lib/action_view/template/handlers/rjs.rb @@ -1,17 +1,13 @@  module ActionView    module Template::Handlers -    class RJS < Template::Handler -      include Compilable - +    class RJS +      # Default format used by RJS. +      class_attribute :default_format        self.default_format = Mime::JS -      def compile(template) +      def call(template)          "update_page do |page|;#{template.source}\nend"        end - -      def default_format -        Mime::JS -      end      end    end  end diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index a261e08dbc..7707dbcf98 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -6,9 +6,8 @@ module ActionView    # = Action View Resolver    class Resolver      def initialize -      @path = nil -      @cached = Hash.new { |h1,k1| h1[k1] = -        Hash.new { |h2,k2| h2[k2] = Hash.new { |h3, k3| h3[k3] = {} } } } +      @cached = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2| +        h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }      end      def clear_cache @@ -16,8 +15,8 @@ module ActionView      end      # Normalizes the arguments and passes it on to find_template. -    def find_all(name, prefix=nil, partial=false, details={}, key=nil) -      cached(key, prefix, name, partial) do +    def find_all(name, prefix=nil, partial=false, details={}, locals=[], key=nil) +      cached(key, [name, prefix, partial], details, locals) do          find_templates(name, prefix, partial, details)        end      end @@ -35,34 +34,73 @@ module ActionView        raise NotImplementedError      end -    def cached(key, prefix, name, partial) -      return yield unless key && caching? -      @cached[key][prefix][name][partial] ||= yield +    # Helpers that builds a path. Useful for building virtual paths. +    def build_path(name, prefix, partial) +      path = "" +      path << "#{prefix}/" unless prefix.empty? +      path << (partial ? "_#{name}" : name) +      path +    end + +    # Hnadles templates caching. If a key is given and caching is on +    # always check the cache before hitting the resolver. Otherwise, +    # it always hits the resolver but check if the resolver is fresher +    # before returning it. +    def cached(key, path_info, details, locals) #:nodoc: +      name, prefix, partial = path_info +      locals = sort_locals(locals) + +      if key && caching? +        @cached[key][name][prefix][partial][locals] ||= decorate(yield, path_info, details, locals) +      else +        fresh = decorate(yield, path_info, details, locals) +        return fresh unless key + +        scope = @cached[key][name][prefix][partial] +        cache = scope[locals] +        mtime = cache && cache.map(&:updated_at).max + +        if !mtime || fresh.empty?  || fresh.any? { |t| t.updated_at > mtime } +          scope[locals] = fresh +        else +          cache +        end +      end +    end + +    # Ensures all the resolver information is set in the template. +    def decorate(templates, path_info, details, locals) #:nodoc: +      cached = nil +      templates.each do |t| +        t.locals         = locals +        t.formats        = details[:formats] || [:html] if t.formats.empty? +        t.virtual_path ||= (cached ||= build_path(*path_info)) +      end +    end + +    if :symbol.respond_to?("<=>") +      def sort_locals(locals) #:nodoc: +        locals.sort.freeze +      end +    else +      def sort_locals(locals) #:nodoc: +        locals = locals.map{ |l| l.to_s } +        locals.sort! +        locals.freeze +      end      end    end    class PathResolver < Resolver      EXTENSION_ORDER = [:locale, :formats, :handlers] -    def to_s -      @path.to_s -    end -    alias :to_path :to_s - -  private +    private      def find_templates(name, prefix, partial, details) -      path = build_path(name, prefix, partial, details) +      path = build_path(name, prefix, partial)        query(path, EXTENSION_ORDER.map { |ext| details[ext] }, details[:formats])      end -    def build_path(name, prefix, partial, details) -      path = "" -      path << "#{prefix}/" unless prefix.empty? -      path << (partial ? "_#{name}" : name) -      path -    end -      def query(path, exts, formats)        query = File.join(@path, path) @@ -76,26 +114,28 @@ module ActionView          contents = File.open(p, "rb") {|io| io.read }          Template.new(contents, File.expand_path(p), handler, -          :virtual_path => path, :format => format) +          :virtual_path => path, :format => format, :updated_at => mtime(p))        end      end +    # Returns the file mtime from the filesystem. +    def mtime(p) +      File.stat(p).mtime +    end +      # Extract handler and formats from path. If a format cannot be a found neither      # from the path, or the handler, we should return the array of formats given      # to the resolver.      def extract_handler_and_format(path, default_formats)        pieces = File.basename(path).split(".")        pieces.shift - -      handler  = Template.handler_class_for_extension(pieces.pop) -      format   = pieces.last && Mime[pieces.last] && pieces.pop.to_sym -      format ||= handler.default_format if handler.respond_to?(:default_format) -      format ||= default_formats - +      handler = Template.handler_class_for_extension(pieces.pop) +      format  = pieces.last && Mime[pieces.last]        [handler, format]      end    end +  # A resolver that loads files from the filesystem.    class FileSystemResolver < PathResolver      def initialize(path)        raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) @@ -103,9 +143,26 @@ module ActionView        @path = File.expand_path(path)      end +    def to_s +      @path.to_s +    end +    alias :to_path :to_s +      def eql?(resolver)        self.class.equal?(resolver.class) && to_path == resolver.to_path      end      alias :== :eql?    end + +  # The same as FileSystemResolver but does not allow templates to store +  # a virtual path since it is invalid for such resolvers. +  class FallbackFileSystemResolver < FileSystemResolver +    def self.instances +      [new(""), new("/")] +    end + +    def decorate(*) +      super.each { |t| t.virtual_path = nil } +    end +  end  end diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 51be831dfb..4261c3b5e2 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -25,10 +25,6 @@ module ActionView #:nodoc:        def formats          [@mime_type.to_sym]        end - -      def partial? -        false -      end      end    end  end diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb index b2b62528a9..55583096e0 100644 --- a/actionpack/lib/action_view/testing/resolvers.rb +++ b/actionpack/lib/action_view/testing/resolvers.rb @@ -13,26 +13,29 @@ module ActionView #:nodoc:        @hash = hash      end -  private +    private      def query(path, exts, formats) -      query = Regexp.escape(path) +      query = ""        exts.each do |ext|          query << '(' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'        end +      query = /^(#{Regexp.escape(path)})#{query}$/        templates = [] -      @hash.select { |k,v| k =~ /^#{query}$/ }.each do |_path, source| +      @hash.each do |_path, array| +        source, updated_at = array +        next unless _path =~ query          handler, format = extract_handler_and_format(_path, formats)          templates << Template.new(source, _path, handler, -          :virtual_path => _path, :format => format) +          :virtual_path => $1, :format => format, :updated_at => updated_at)        end        templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }      end    end -  class NullResolver < ActionView::PathResolver +  class NullResolver < PathResolver      def query(path, exts, formats)        handler, format = extract_handler_and_format(path, formats)        [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)]  | 
