diff options
| author | Jon Leighton <j@jonathanleighton.com> | 2011-03-02 21:24:56 +0000 | 
|---|---|---|
| committer | Jon Leighton <j@jonathanleighton.com> | 2011-03-04 09:30:27 +0000 | 
| commit | 735844db712c511dd8abf36a5279318fbc0ff9d0 (patch) | |
| tree | 5fbd5d224ef85d8c878bf221db98b422c9345466 /actionpack/lib | |
| parent | 9a98c766e045aebc2ef6d5b716936b73407f095d (diff) | |
| parent | b171b9e73dcc6a89b1da652da61c5127fe605b51 (diff) | |
| download | rails-735844db712c511dd8abf36a5279318fbc0ff9d0.tar.gz rails-735844db712c511dd8abf36a5279318fbc0ff9d0.tar.bz2 rails-735844db712c511dd8abf36a5279318fbc0ff9d0.zip | |
Merge branch 'master' into nested_has_many_through
Conflicts:
	activerecord/CHANGELOG
	activerecord/lib/active_record/association_preload.rb
	activerecord/lib/active_record/associations.rb
	activerecord/lib/active_record/associations/class_methods/join_dependency.rb
	activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
	activerecord/lib/active_record/associations/has_many_association.rb
	activerecord/lib/active_record/associations/has_many_through_association.rb
	activerecord/lib/active_record/associations/has_one_association.rb
	activerecord/lib/active_record/associations/has_one_through_association.rb
	activerecord/lib/active_record/associations/through_association_scope.rb
	activerecord/lib/active_record/reflection.rb
	activerecord/test/cases/associations/has_many_through_associations_test.rb
	activerecord/test/cases/associations/has_one_through_associations_test.rb
	activerecord/test/cases/reflection_test.rb
	activerecord/test/cases/relations_test.rb
	activerecord/test/fixtures/memberships.yml
	activerecord/test/models/categorization.rb
	activerecord/test/models/category.rb
	activerecord/test/models/member.rb
	activerecord/test/models/reference.rb
	activerecord/test/models/tagging.rb
Diffstat (limited to 'actionpack/lib')
66 files changed, 1009 insertions, 414 deletions
| diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index f169ab7c3a..95992c2698 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -13,7 +13,7 @@ module AbstractController      # Override AbstractController::Base's process_action to run the      # process_action callbacks around the normal behavior. -    def process_action(method_name) +    def process_action(method_name, *args)        run_callbacks(:process_action, method_name) do          super        end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 606f7eedec..4ee54474cc 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -265,11 +265,11 @@ module AbstractController            raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"          when nil            if name -            _prefix = "layouts" unless _implied_layout_name =~ /\blayouts/ +            _prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]              self.class_eval <<-RUBY, __FILE__, __LINE__ + 1                def _layout -                if template_exists?("#{_implied_layout_name}", #{_prefix.inspect}) +                if template_exists?("#{_implied_layout_name}", #{_prefixes.inspect})                    "#{_implied_layout_name}"                  else                    super diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 91b75273fa..691310d5d2 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -13,14 +13,15 @@ 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.    class I18nProxy < ::I18n::Config #:nodoc: -    attr_reader :i18n_config, :lookup_context +    attr_reader :original_config, :lookup_context -    def initialize(i18n_config, lookup_context) -      @i18n_config, @lookup_context = i18n_config, lookup_context +    def initialize(original_config, lookup_context) +      original_config = original_config.original_config if original_config.respond_to?(:original_config) +      @original_config, @lookup_context = original_config, lookup_context      end      def locale -      @i18n_config.locale +      @original_config.locale      end      def locale=(value) @@ -60,6 +61,20 @@ module AbstractController            end          end        end + +      def parent_prefixes +        @parent_prefixes ||= begin +          parent_controller = superclass +          prefixes = [] + +          until parent_controller.abstract? +            prefixes << parent_controller.controller_path +            parent_controller = parent_controller.superclass +          end + +          prefixes +        end +      end      end      attr_writer :view_context_class @@ -98,7 +113,7 @@ module AbstractController      def render_to_string(*args, &block)        options = _normalize_args(*args, &block)        _normalize_options(options) -      render_to_body(options) +      render_to_body(options).tap { self.response_body = nil }      end      # Raw rendering of a template to a Rack-compatible body. @@ -114,9 +129,12 @@ module AbstractController        view_context.render(options)      end -    # The prefix used in render "foo" shortcuts. -    def _prefix -      controller_path +    # The prefixes used in render "foo" shortcuts. +    def _prefixes +      @_prefixes ||= begin +        parent_prefixes = self.class.parent_prefixes +        parent_prefixes.dup.unshift(controller_path) +      end      end      private @@ -156,7 +174,7 @@ module AbstractController        end        if (options.keys & [:partial, :file, :template, :once]).empty? -        options[:prefix] ||= _prefix +        options[:prefixes] ||= _prefixes        end        options[:template] ||= (options[:action] || action_name).to_s diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 48308cbb60..81c0698fb8 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -24,7 +24,7 @@ module ActionController    #    # Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action    # after executing code in the action. For example, the +index+ action of the PostsController would render the -  # template <tt>app/views/posts/index.erb</tt> by default after populating the <tt>@posts</tt> instance variable. +  # template <tt>app/views/posts/index.html.erb</tt> by default after populating the <tt>@posts</tt> instance variable.    #    # Unlike index, the create action will not render a template. After performing its main purpose (creating a    # new post), it initiates a redirect instead. This redirect works by returning an external diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index a4bac3caed..a1c582560c 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -103,12 +103,14 @@ module ActionController #:nodoc:        end        def _save_fragment(name, options) -        return unless caching_allowed? -          content = response_body          content = content.join if content.is_a?(Array) -        write_fragment(name, content, options) +        if caching_allowed? +          write_fragment(name, content, options) +        else +          content +        end        end      protected diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 37c155b9cd..0be04b70a1 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -1,52 +1,72 @@  module ActionController #:nodoc:    module Caching -    # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when -    # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple -    # parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like: +    # Fragment caching is used for caching various blocks within  +    # views without caching the entire action as a whole. This is +    # useful when certain elements of an action change frequently or  +    # depend on complicated state while other parts rarely change or  +    # can be shared amongst multiple parties. The caching is done using +    # the <tt>cache</tt> helper available in the Action View. A  +    # template with fragment caching might look like:      #      #   <b>Hello <%= @name %></b> +    #      #   <% cache do %>      #     All the topics in the system:      #     <%= render :partial => "topic", :collection => Topic.find(:all) %>      #   <% end %>      # -    # This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would -    # be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>. +    # This cache will bind the name of the action that called it, so if +    # this code was part of the view for the topics/list action, you  +    # would be able to invalidate it using: +    #    +    #   expire_fragment(:controller => "topics", :action => "list")      # -    # This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using -    # <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like: +    # This default behavior is limited if you need to cache multiple  +    # fragments per action or if the action itself is cached using  +    # <tt>caches_action</tt>. To remedy this, there is an option to  +    # qualify the name of the cached fragment by using the  +    # <tt>:action_suffix</tt> option:      #      #   <% cache(:action => "list", :action_suffix => "all_topics") do %>      # -    # That would result in a name such as <tt>/topics/list/all_topics</tt>, avoiding conflicts with the action cache and with any fragments that use a -    # different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique -    # cache names that we can refer to when we need to expire the cache. +    # That would result in a name such as  +    # <tt>/topics/list/all_topics</tt>, avoiding conflicts with the  +    # action cache and with any fragments that use a different suffix. +    # Note that the URL doesn't have to really exist or be callable +    # - the url_for system is just used to generate unique cache names +    # that we can refer to when we need to expire the cache.      #      # The expiration call for this example is:      # -    #   expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics") +    #   expire_fragment(:controller => "topics",  +    #                   :action => "list",  +    #                   :action_suffix => "all_topics")      module Fragments -      # Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading, -      # writing, or expiring a cached fragment. If the key is a hash, the generated key is the return -      # value of url_for on that hash (without the protocol). All keys are prefixed with <tt>views/</tt> and uses +      # Given a key (as described in <tt>expire_fragment</tt>), returns +      # a key suitable for use in reading, writing, or expiring a  +      # cached fragment. If the key is a hash, the generated key is the +      # return value of url_for on that hash (without the protocol).  +      # All keys are prefixed with <tt>views/</tt> and uses        # ActiveSupport::Cache.expand_cache_key for the expansion.        def fragment_cache_key(key)          ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)        end -      # Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats) +      # Writes <tt>content</tt> to the location signified by  +      # <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats).        def write_fragment(key, content, options = nil)          return content unless cache_configured?          key = fragment_cache_key(key)          instrument_fragment_cache :write_fragment, key do -          content = content.html_safe.to_str if content.respond_to?(:html_safe) +          content = content.to_str            cache_store.write(key, content, options)          end          content        end -      # Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats) +      # Reads a cached fragment from the location signified by <tt>key</tt> +      # (see <tt>expire_fragment</tt> for acceptable formats).        def read_fragment(key, options = nil)          return unless cache_configured? @@ -57,7 +77,8 @@ module ActionController #:nodoc:          end        end -      # Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats) +      # Check if a cached fragment from the location signified by  +      # <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats)        def fragment_exist?(key, options = nil)          return unless cache_configured?          key = fragment_cache_key(key) @@ -70,6 +91,7 @@ module ActionController #:nodoc:        # Removes fragments from the cache.        #        # +key+ can take one of three forms: +      #        # * String - This would normally take the form of a path, like        #   <tt>pages/45/notes</tt>.        # * Hash - Treated as an implicit call to +url_for+, like diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 3e57d2c236..8c583c7ce0 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -106,7 +106,7 @@ module ActionController #:nodoc:            end            def page_cache_path(path, extension = nil) -            page_cache_directory + page_cache_file(path, extension) +            page_cache_directory.to_s + page_cache_file(path, extension)            end            def instrument_page_cache(name, path) diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 3b19310a69..3fae697cc3 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -16,7 +16,11 @@ module ActionController        payload   = event.payload        additions = ActionController::Base.log_process_action(payload) -      message = "Completed #{payload[:status]} #{Rack::Utils::HTTP_STATUS_CODES[payload[:status]]} in %.0fms" % event.duration +      status = payload[:status] +      if status.nil? && payload[:exception].present? +        status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil  +      end  +      message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration        message << " (#{additions.join(" | ")})" unless additions.blank?        info(message) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 329798e84f..e5db31061b 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -36,19 +36,68 @@ module ActionController        action = action.to_s        raise "MiddlewareStack#build requires an app" unless app -      reverse.inject(app) do |a, middleware| +      middlewares.reverse.inject(app) do |a, middleware|          middleware.valid?(action) ?            middleware.build(a) : a        end      end    end -  # Provides a way to get a valid Rack application from a controller. +  # <tt>ActionController::Metal</tt> is the simplest possible controller, providing a +  # valid Rack interface without the additional niceties provided by +  # <tt>ActionController::Base</tt>. +  # +  # A sample metal controller might look like this: +  # +  #   class HelloController < ActionController::Metal +  #     def index +  #       self.response_body = "Hello World!" +  #     end +  #   end +  # +  # And then to route requests to your metal controller, you would add +  # something like this to <tt>config/routes.rb</tt>: +  # +  #   match 'hello', :to => HelloController.action(:index) +  # +  # The +action+ method returns a valid Rack application for the \Rails +  # router to dispatch to. +  # +  # == Rendering Helpers +  # +  # <tt>ActionController::Metal</tt> by default provides no utilities for rendering +  # views, partials, or other responses aside from explicitly calling of +  # <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. To +  # add the render helpers you're used to having in a normal controller, you +  # can do the following: +  # +  #   class HelloController < ActionController::Metal +  #     include ActionController::Rendering +  #     append_view_path "#{Rails.root}/app/views" +  # +  #     def index +  #       render "hello/index" +  #     end +  #   end +  # +  # == Redirection Helpers +  # +  # To add redirection helpers to your metal controller, do the following: +  # +  #   class HelloController < ActionController::Metal +  #     include ActionController::Redirecting +  #     include Rails.application.routes.url_helpers +  # +  #     def index +  #       redirect_to root_url +  #     end +  #   end +  # +  # == Other Helpers +  # +  # You can refer to the modules included in <tt>ActionController::Base</tt> to see +  # other features you can bring into your metal controller.    # -  # In AbstractController, dispatching is triggered directly by calling #process on a new controller. -  # <tt>ActionController::Metal</tt> provides an <tt>action</tt> method that returns a valid Rack application for a -  # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the \Rails router, -  # can dispatch directly to actions returned by controllers in your application.    class Metal < AbstractController::Base      abstract! @@ -133,7 +182,7 @@ module ActionController      end      def response_body=(val) -      body = val.respond_to?(:each) ? val : [val] +      body = val.nil? ? nil : (val.respond_to?(:each) ? val : [val])        super body      end diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 282dcf66b3..cfa7004048 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -12,10 +12,10 @@ module ActionController      def method_for_action(action_name)        super || begin -        if template_exists?(action_name.to_s, _prefix) +        if template_exists?(action_name.to_s, _prefixes)            "default_render"          end        end      end    end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 9ba37134b8..a2e06fe0a6 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -63,13 +63,13 @@ module ActionController #:nodoc:      # might look something like this:      #      #   def index -    #     @people = Person.find(:all) +    #     @people = Person.all      #   end      #      # Here's the same action, with web-service support baked in:      #      #   def index -    #     @people = Person.find(:all) +    #     @people = Person.all      #      #     respond_to do |format|      #       format.html @@ -155,7 +155,7 @@ module ActionController #:nodoc:      # Respond to also allows you to specify a common block for different formats by using any:      #      #   def index -    #     @people = Person.find(:all) +    #     @people = Person.all      #      #     respond_to do |format|      #       format.html @@ -178,7 +178,7 @@ module ActionController #:nodoc:      #     respond_to :html, :xml, :json      #      #     def index -    #       @people = Person.find(:all) +    #       @people = Person.all      #       respond_with(@person)      #     end      #   end @@ -208,8 +208,8 @@ module ActionController #:nodoc:      # It also accepts a block to be given. It's used to overwrite a default      # response:      # -    #   def destroy -    #     @user = User.find(params[:id]) +    #   def create +    #     @user = User.new(params[:user])      #     flash[:notice] = "User was successfully created." if @user.save      #      #     respond_with(@user) do |format| diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index d6f6ab1855..38711c8462 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/class/attribute'  require 'active_support/core_ext/object/blank'  module ActionController +  # See <tt>Renderers.add</tt>    def self.add_renderer(key, &block)      Renderers.add(key, &block)    end @@ -39,7 +40,43 @@ module ActionController        nil      end +    # Hash of available renderers, mapping a renderer name to its proc. +    # Default keys are :json, :js, :xml and :update.      RENDERERS = {} + +    # Adds a new renderer to call within controller actions. +    # A renderer is invoked by passing its name as an option to +    # <tt>AbstractController::Rendering#render</tt>. To create a renderer +    # pass it a name and a block. The block takes two arguments, the first +    # is the value paired with its key and the second is the remaining +    # hash of options passed to +render+. +    # +    # === Example +    # Create a csv renderer: +    # +    #   ActionController::Renderers.add :csv do |obj, options| +    #     filename = options[:filename] || 'data' +    #     str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s +    #     send_data str, :type => Mime::CSV, +    #       :disposition => "attachment; filename=#{filename}.csv" +    #   end +    # +    # Note that we used Mime::CSV for the csv mime type as it comes with Rails. +    # For a custom renderer, you'll need to register a mime type with +    # <tt>Mime::Type.register</tt>. +    # +    # To use the csv renderer in a controller action: +    # +    #   def show +    #     @csvable = Csvable.find(params[:id]) +    #     respond_to do |format| +    #       format.html +    #       format.csv { render :csv => @csvable, :filename => @csvable.name } +    #     } +    #   end +    # To use renderers and their mime types in more concise ways, see +    # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and +    # <tt>ActionController::MimeResponds#respond_with</tt>      def self.add(key, &block)        define_method("_render_option_#{key}", &block)        RENDERERS[key] = block diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 14cc547dd0..32d52c84c4 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -6,7 +6,7 @@ module ActionController      # Before processing, set the request formats in current controller formats.      def process_action(*) #:nodoc: -      self.formats = request.formats.map { |x| x.to_sym } +      self.formats = request.formats.map { |x| x.ref }        super      end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 148efbb081..1cd93a188c 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -66,30 +66,29 @@ module ActionController #:nodoc:        # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call.  Set which actions are verified.        def protect_from_forgery(options = {})          self.request_forgery_protection_token ||= :authenticity_token -        before_filter :verify_authenticity_token, options +        prepend_before_filter :verify_authenticity_token, options        end      end      protected - -      def protect_from_forgery(options = {}) -        self.request_forgery_protection_token ||= :authenticity_token -        before_filter :verify_authenticity_token, options -      end -        # The actual before_filter that is used.  Modify this to change how you handle unverified requests.        def verify_authenticity_token -        verified_request? || raise(ActionController::InvalidAuthenticityToken) +        verified_request? || handle_unverified_request +      end + +      def handle_unverified_request +        reset_session        end        # Returns true or false if a request is verified.  Checks:        # -      # * is the format restricted?  By default, only HTML requests are checked.        # * is it a GET request?  Gets should be safe and idempotent        # * Does the form_authenticity_token match the given token value from the params? +      # * Does the X-CSRF-Token header match the form_authenticity_token        def verified_request? -        !protect_against_forgery? || request.forgery_whitelisted? || -          form_authenticity_token == params[request_forgery_protection_token] +        !protect_against_forgery? || request.get? || +          form_authenticity_token == params[request_forgery_protection_token] || +          form_authenticity_token == request.headers['X-CSRF-Token']        end        # Sets the token value for the current session. diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 38d32211cc..4b45413cf8 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -77,8 +77,6 @@ module ActionController #:nodoc:    #    #   respond_with(@project, :manager, @task)    # -  # Check <code>polymorphic_url</code> documentation for more examples. -  #    class Responder      attr_reader :controller, :request, :format, :resource, :resources, :options @@ -115,7 +113,7 @@ module ActionController #:nodoc:      # Main entry point for responder responsible to dispatch to the proper format.      #      def respond -      method = :"to_#{format}" +      method = "to_#{format}"        respond_to?(method) ? send(method) : to_format      end @@ -171,7 +169,7 @@ module ActionController #:nodoc:      # Checks whether the resource responds to the current format or not.      #      def resourceful? -      resource.respond_to?(:"to_#{format}") +      resource.respond_to?("to_#{format}")      end      # Returns the resource location by retrieving it from the options or diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb index 699c44c62c..dce3c2fe88 100644 --- a/actionpack/lib/action_controller/railties/paths.rb +++ b/actionpack/lib/action_controller/railties/paths.rb @@ -16,6 +16,14 @@ module ActionController              if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers                klass.helper :all              end + +            if app.config.serve_static_assets && namespace +              paths = namespace._railtie.config.paths + +              klass.config.assets_dir      = paths["public"].first +              klass.config.javascripts_dir = paths["public/javascripts"].first +              klass.config.stylesheets_dir = paths["public/stylesheets"].first +            end            end          end        end diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 3e5d23b5c1..09dd08898c 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -170,7 +170,7 @@ module HTML      def contains_bad_protocols?(attr_name, value)        uri_attributes.include?(attr_name) && -      (value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first)) +      (value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase))      end    end  end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 50faf666e6..49971fc9f8 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2004-2010 David Heinemeier Hansson +# Copyright (c) 2004-2011 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the @@ -53,6 +53,7 @@ module ActionDispatch      autoload :Flash      autoload :Head      autoload :ParamsParser +    autoload :Reloader      autoload :RemoteIp      autoload :Rescue      autoload :ShowExceptions diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 1d2f7e4f19..4f4cb96a74 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -43,7 +43,7 @@ module ActionDispatch          alias :etag? :etag          def initialize(*) -          status, header, body = super +          super            @cache_control = {}            @etag = self["ETag"] diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 5b87a80c1b..7c9ebe7c7b 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -216,7 +216,11 @@ module Mime      end      def to_sym -      @symbol || @string.to_sym +      @symbol +    end + +    def ref +      to_sym || to_s      end      def ===(list) diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 08f30e068d..f07ac44f7a 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -2,6 +2,7 @@ require 'tempfile'  require 'stringio'  require 'strscan' +require 'active_support/core_ext/module/deprecation'  require 'active_support/core_ext/hash/indifferent_access'  require 'active_support/core_ext/string/access'  require 'active_support/inflector' @@ -133,8 +134,9 @@ module ActionDispatch      end      def forgery_whitelisted? -      get? || xhr? || content_mime_type.nil? || !content_mime_type.verify_request? +      get?      end +    deprecate :forgery_whitelisted? => "it is just an alias for 'get?' now, update your code"      def media_type        content_mime_type.to_s diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 796cd8c09b..535ff42b90 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -28,8 +28,11 @@ module ActionDispatch            rewritten_url = ""            unless options[:only_path] -            rewritten_url << (options[:protocol] || "http") -            rewritten_url << "://" unless rewritten_url.match("://") +            unless options[:protocol] == false +              rewritten_url << (options[:protocol] || "http") +              rewritten_url << ":" unless rewritten_url.match(%r{:|//}) +            end +            rewritten_url << "//" unless rewritten_url.match("//")              rewritten_url << rewrite_authentication(options)              rewritten_url << host_or_subdomain_and_domain(options)              rewritten_url << ":#{options.delete(:port)}" if options[:port] diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 0bb950d1cc..1bb2ad7f67 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -1,32 +1,14 @@ +require 'active_support/core_ext/module/delegation' +  module ActionDispatch    # Provide callbacks to be executed before and after the request dispatch. -  # -  # It also provides a to_prepare callback, which is performed in all requests -  # in development by only once in production and notification callback for async -  # operations. -  #    class Callbacks      include ActiveSupport::Callbacks      define_callbacks :call, :rescuable => true -    define_callbacks :prepare, :scope => :name -    # Add a preparation callback. Preparation callbacks are run before every -    # request in development mode, and before the first request in production mode. -    # -    # If a symbol with a block is given, the symbol is used as an identifier. -    # That allows to_prepare to be called again with the same identifier to -    # replace the existing callback. Passing an identifier is a suggested -    # practice if the code adding a preparation block may be reloaded. -    def self.to_prepare(*args, &block) -      first_arg = args.first -      if first_arg.is_a?(Symbol) && block_given? -        remove_method :"__#{first_arg}" if method_defined?(:"__#{first_arg}") -        define_method :"__#{first_arg}", &block -        set_callback(:prepare, :"__#{first_arg}") -      else -        set_callback(:prepare, *args, &block) -      end +    class << self +      delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"      end      def self.before(*args, &block) @@ -37,14 +19,13 @@ module ActionDispatch        set_callback(:call, :after, *args, &block)      end -    def initialize(app, prepare_each_request = false) -      @app, @prepare_each_request = app, prepare_each_request -      _run_prepare_callbacks +    def initialize(app, unused = nil) +      ActiveSupport::Deprecation.warn "Passing a second argument to ActionDispatch::Callbacks.new is deprecated." unless unused.nil? +      @app = app      end      def call(env) -      _run_call_callbacks do -        _run_prepare_callbacks if @prepare_each_request +      run_callbacks :call do          @app.call(env)        end      end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index b0a4e3d949..7ac608f0a8 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -90,17 +90,14 @@ module ActionDispatch        # **.**, ***.** style TLDs like co.uk or com.au        #        # www.example.co.uk gives: -      # $1 => example -      # $2 => co.uk +      # $& => example.co.uk        #        # example.com gives: -      # $1 => example -      # $2 => com +      # $& => example.com        #        # lots.of.subdomains.example.local gives: -      # $1 => example -      # $2 => local -      DOMAIN_REGEXP = /([^.]*)\.([^.]*|..\...|...\...)$/ +      # $& => example.local +      DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/        def self.build(request)          secret = request.env[TOKEN_KEY] @@ -131,8 +128,17 @@ module ActionDispatch          options[:path] ||= "/"          if options[:domain] == :all -          @host =~ DOMAIN_REGEXP -          options[:domain] = ".#{$1}.#{$2}" +          # if there is a provided tld length then we use it otherwise default domain regexp +          domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP + +          # if host is not ip and matches domain regexp +          # (ip confirms to domain regexp so we explicitly check for ip) +          options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp) +            ".#{$&}" +          end +        elsif options[:domain].is_a? Array +          # if host matches one of the supplied domains without a dot in front of it +          options[:domain] = options[:domain].find {|domain| @host.include? domain[/^\.?(.*)$/, 1] }          end        end diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb new file mode 100644 index 0000000000..29289a76b4 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -0,0 +1,76 @@ +module ActionDispatch +  # ActionDispatch::Reloader provides prepare and cleanup callbacks, +  # intended to assist with code reloading during development. +  # +  # Prepare callbacks are run before each request, and cleanup callbacks +  # after each request. In this respect they are analogs of ActionDispatch::Callback's +  # before and after callbacks. However, cleanup callbacks are not called until the +  # request is fully complete -- that is, after #close has been called on +  # the response body. This is important for streaming responses such as the +  # following: +  # +  #     self.response_body = lambda { |response, output| +  #       # code here which refers to application models +  #     } +  # +  # Cleanup callbacks will not be called until after the response_body lambda +  # is evaluated, ensuring that it can refer to application models and other +  # classes before they are unloaded. +  # +  # By default, ActionDispatch::Reloader is included in the middleware stack +  # only in the development environment; specifically, when config.cache_classes +  # is false. Callbacks may be registered even when it is not included in the +  # middleware stack, but are executed only when +ActionDispatch::Reloader.prepare!+ +  # or +ActionDispatch::Reloader.cleanup!+ are called manually. +  # +  class Reloader +    include ActiveSupport::Callbacks + +    define_callbacks :prepare, :scope => :name +    define_callbacks :cleanup, :scope => :name + +    # Add a prepare callback. Prepare callbacks are run before each request, prior +    # to ActionDispatch::Callback's before callbacks. +    def self.to_prepare(*args, &block) +      set_callback(:prepare, *args, &block) +    end + +    # Add a cleanup callback. Cleanup callbacks are run after each request is +    # complete (after #close is called on the response body). +    def self.to_cleanup(*args, &block) +      set_callback(:cleanup, *args, &block) +    end + +    # Execute all prepare callbacks. +    def self.prepare! +      new(nil).run_callbacks :prepare +    end + +    # Execute all cleanup callbacks. +    def self.cleanup! +      new(nil).run_callbacks :cleanup +    end + +    def initialize(app) +      @app = app +    end + +    module CleanupOnClose +      def close +        super if defined?(super) +      ensure +        ActionDispatch::Reloader.cleanup! +      end +    end + +    def call(env) +      run_callbacks :prepare +      response = @app.call(env) +      response[2].extend(CleanupOnClose) +      response +    rescue Exception +      run_callbacks :cleanup +      raise +    end +  end +end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 71e736ce9f..dbe3206808 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -43,20 +43,20 @@ module ActionDispatch      end      def call(env) -      status, headers, body = @app.call(env) - -      # Only this middleware cares about RoutingError. So, let's just raise -      # it here. -      # TODO: refactor this middleware to handle the X-Cascade scenario without -      # having to raise an exception. -      if headers['X-Cascade'] == 'pass' -        raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}" +      begin +        status, headers, body = @app.call(env) +        exception = nil + +        # Only this middleware cares about RoutingError. So, let's just raise +        # it here. +        if headers['X-Cascade'] == 'pass' +           raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}" +        end +      rescue Exception => exception +        raise exception if env['action_dispatch.show_exceptions'] == false        end -      [status, headers, body] -    rescue Exception => exception -      raise exception if env['action_dispatch.show_exceptions'] == false -      render_exception(env, exception) +      exception ? render_exception(env, exception) : [status, headers, body]      end      private diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index e3cd779756..a4308f528c 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -2,17 +2,26 @@ require "active_support/inflector/methods"  require "active_support/dependencies"  module ActionDispatch -  class MiddlewareStack < Array +  class MiddlewareStack      class Middleware -      attr_reader :args, :block +      attr_reader :args, :block, :name, :classcache        def initialize(klass_or_name, *args, &block) -        @ref = ActiveSupport::Dependencies::Reference.new(klass_or_name) +        @klass = nil + +        if klass_or_name.respond_to?(:name) +          @klass = klass_or_name +          @name  = @klass.name +        else +          @name  = klass_or_name.to_s +        end + +        @classcache = ActiveSupport::Dependencies::Reference          @args, @block = args, block        end        def klass -        @ref.get +        @klass || classcache[@name]        end        def ==(middleware) @@ -22,7 +31,7 @@ module ActionDispatch          when Class            klass == middleware          else -          normalize(@ref.name) == normalize(middleware) +          normalize(@name) == normalize(middleware)          end        end @@ -41,18 +50,39 @@ module ActionDispatch        end      end -    # Use this instead of super to work around a warning. -    alias :array_initialize :initialize +    include Enumerable + +    attr_accessor :middlewares      def initialize(*args) -      array_initialize(*args) +      @middlewares = []        yield(self) if block_given?      end +    def each +      @middlewares.each { |x| yield x } +    end + +    def size +      middlewares.size +    end + +    def last +      middlewares.last +    end + +    def [](i) +      middlewares[i] +    end + +    def initialize_copy(other) +      self.middlewares = other.middlewares.dup +    end +      def insert(index, *args, &block)        index = assert_index(index, :before)        middleware = self.class::Middleware.new(*args, &block) -      super(index, middleware) +      middlewares.insert(index, middleware)      end      alias_method :insert_before, :insert @@ -67,21 +97,25 @@ module ActionDispatch        delete(target)      end +    def delete(target) +      middlewares.delete target +    end +      def use(*args, &block)        middleware = self.class::Middleware.new(*args, &block) -      push(middleware) +      middlewares.push(middleware)      end      def build(app = nil, &block)        app ||= block        raise "MiddlewareStack#build requires an app" unless app -      reverse.inject(app) { |a, e| e.build(a) } +      middlewares.reverse.inject(app) { |a, e| e.build(a) }      end    protected      def assert_index(index, where) -      i = index.is_a?(Integer) ? index : self.index(index) +      i = index.is_a?(Integer) ? index : middlewares.index(index)        raise "No such middleware to insert #{where}: #{index.inspect}" unless i        i      end diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 913b899e20..c57f694c4d 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -3,10 +3,10 @@ require 'rack/utils'  module ActionDispatch    class FileHandler      def initialize(at, root) -      @at, @root = at.chomp('/'), root.chomp('/') -      @compiled_at = (Regexp.compile(/^#{Regexp.escape(at)}/) unless @at.blank?) -      @compiled_root = Regexp.compile(/^#{Regexp.escape(root)}/) -      @file_server = ::Rack::File.new(@root) +      @at, @root     = at.chomp('/'), root.chomp('/') +      @compiled_at   = @at.blank? ? nil : /^#{Regexp.escape(at)}/ +      @compiled_root = /^#{Regexp.escape(root)}/ +      @file_server   = ::Rack::File.new(@root)      end      def match?(path) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb index bd6ffbab5d..50d8ca9484 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -6,5 +6,5 @@  </h1>  <pre><%=h @exception.message %></pre> -<%= render :file => "rescues/_trace.erb" %> -<%= render :file => "rescues/_request_and_response.erb" %> +<%= render :template => "rescues/_trace" %> +<%= render :template => "rescues/_request_and_response" %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb index 02fa18211d..c658559be9 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb @@ -13,9 +13,5 @@  <p><%=h @exception.sub_template_message %></p> -<% @real_exception = @exception -   @exception = @exception.original_exception || @exception %> -<%= render :file => "rescues/_trace.erb" %> -<% @exception = @real_exception %> - -<%= render :file => "rescues/_request_and_response.erb" %> +<%= render :template => "rescues/_trace" %> +<%= render :template => "rescues/_request_and_response" %> diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 8810227a59..43fd93adf6 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -56,6 +56,18 @@ module ActionDispatch    #     resources :posts, :comments    #   end    # +  # Alternately, you can add prefixes to your path without using a separate +  # directory by using +scope+. +scope+ takes additional options which +  # apply to all enclosed routes. +  # +  #   scope :path => "/cpanel", :as => 'admin' do +  #     resources :posts, :comments +  #   end +  # +  # For more, see <tt>Routing::Mapper::Resources#resources</tt>, +  # <tt>Routing::Mapper::Scoping#namespace</tt>, and +  # <tt>Routing::Mapper::Scoping#scope</tt>. +  #    # == Named routes    #    # Routes can be named by passing an <tt>:as</tt> option, diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 430fcdbe07..589df218a8 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -22,18 +22,22 @@ module ActionDispatch            @app, @constraints, @request = app, constraints, request          end -        def call(env) +        def matches?(env)            req = @request.new(env)            @constraints.each { |constraint|              if constraint.respond_to?(:matches?) && !constraint.matches?(req) -              return [ 404, {'X-Cascade' => 'pass'}, [] ] +              return false              elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req)) -              return [ 404, {'X-Cascade' => 'pass'}, [] ] +              return false              end            } -          @app.call(env) +          return true +        end + +        def call(env) +          matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]          end          private @@ -247,7 +251,7 @@ module ActionDispatch          #          #   root :to => 'pages#main'          # -        # For options, see the +match+ method's documentation, as +root+ uses it internally. +        # For options, see +match+, as +root+ uses it internally.          #          # You should put the root route at the top of <tt>config/routes.rb</tt>,          # because this means it will be matched first. As this is the most popular route @@ -256,15 +260,114 @@ module ActionDispatch            match '/', options.reverse_merge(:as => :root)          end -        # When you set up a regular route, you supply a series of symbols that -        # Rails maps to parts of an incoming HTTP request. +        # Matches a url pattern to one or more routes. Any symbols in a pattern +        # are interpreted as url query parameters and thus available as +params+ +        # in an action: +        # +        #   # sets :controller, :action and :id in params +        #   match ':controller/:action/:id' +        # +        # Two of these symbols are special, +:controller+ maps to the controller +        # and +:action+ to the controller's action. A pattern can also map +        # wildcard segments (globs) to params: +        # +        #   match 'songs/*category/:title' => 'songs#show' +        # +        #   # 'songs/rock/classic/stairway-to-heaven' sets +        #   #  params[:category] = 'rock/classic' +        #   #  params[:title] = 'stairway-to-heaven' +        # +        # When a pattern points to an internal route, the route's +:action+ and +        # +:controller+ should be set in options or hash shorthand. Examples: +        # +        #   match 'photos/:id' => 'photos#show' +        #   match 'photos/:id', :to => 'photos#show' +        #   match 'photos/:id', :controller => 'photos', :action => 'show' +        # +        # A pattern can also point to a +Rack+ endpoint i.e. anything that +        # responds to +call+: +        # +        #   match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon" } +        #   match 'photos/:id' => PhotoRackApp +        #   # Yes, controller actions are just rack endpoints +        #   match 'photos/:id' => PhotosController.action(:show) +        # +        # === Options +        # +        # Any options not seen here are passed on as params with the url. +        # +        # [:controller] +        #   The route's controller. +        # +        # [:action] +        #   The route's action. +        # +        # [:path] +        #   The path prefix for the routes. +        # +        # [:module] +        #   The namespace for :controller. +        # +        #     match 'path' => 'c#a', :module => 'sekret', :controller => 'posts' +        #     #=> Sekret::PostsController +        # +        #   See <tt>Scoping#namespace</tt> for its scope equivalent. +        # +        # [:as] +        #   The name used to generate routing helpers. +        # +        # [:via] +        #   Allowed HTTP verb(s) for route. +        # +        #      match 'path' => 'c#a', :via => :get +        #      match 'path' => 'c#a', :via => [:get, :post] +        # +        # [:to] +        #   Points to a +Rack+ endpoint. Can be an object that responds to +        #   +call+ or a string representing a controller's action. +        # +        #      match 'path', :to => 'controller#action' +        #      match 'path', :to => lambda { [200, {}, "Success!"] } +        #      match 'path', :to => RackApp +        # +        # [:on] +        #   Shorthand for wrapping routes in a specific RESTful context. Valid +        #   values are :member, :collection, and :new.  Only use within +        #   <tt>resource(s)</tt> block. For example: +        # +        #      resource :bar do +        #        match 'foo' => 'c#a', :on => :member, :via => [:get, :post] +        #      end          # -        #   match ':controller/:action/:id/:user_id' +        #   Is equivalent to:          # -        # Two of these symbols are special: :controller maps to the name of a -        # controller in your application, and :action maps to the name of an -        # action within that controller. Anything other than :controller or -        # :action will be available to the action as part of params. +        #      resource :bar do +        #        member do +        #          match 'foo' => 'c#a', :via => [:get, :post] +        #        end +        #      end +        # +        # [:constraints] +        #   Constrains parameters with a hash of regular expressions or an +        #   object that responds to #matches? +        # +        #     match 'path/:id', :constraints => { :id => /[A-Z]\d{5}/ } +        # +        #     class Blacklist +        #       def matches?(request) request.remote_ip == '1.2.3.4' end +        #     end +        #     match 'path' => 'c#a', :constraints => Blacklist.new +        # +        #   See <tt>Scoping#constraints</tt> for more examples with its scope +        #   equivalent. +        # +        # [:defaults] +        #   Sets defaults for parameters +        # +        #     # Sets params[:format] to 'jpg' by default +        #     match 'path' => 'c#a', :defaults => { :format => 'jpg' } +        # +        #   See <tt>Scoping#defaults</tt> for its scope equivalent.          def match(path, options=nil)            mapping = Mapping.new(@set, @scope, path, options || {}).to_route            @set.add_route(*mapping) @@ -279,6 +382,8 @@ module ActionDispatch          #          #   mount(SomeRackApp => "some_route")          # +        # For options, see +match+, as +mount+ uses it internally. +        #          # All mounted applications come with routing helpers to access them.          # These are named after the class specified, so for the above example          # the helper is either +some_rack_app_path+ or +some_rack_app_url+. @@ -349,7 +454,7 @@ module ActionDispatch        module HttpHelpers          # Define a route that only recognizes HTTP GET. -        # For supported arguments, see +match+. +        # For supported arguments, see <tt>Base#match</tt>.          #          # Example:          # @@ -359,7 +464,7 @@ module ActionDispatch          end          # Define a route that only recognizes HTTP POST. -        # For supported arguments, see +match+. +        # For supported arguments, see <tt>Base#match</tt>.          #          # Example:          # @@ -369,7 +474,7 @@ module ActionDispatch          end          # Define a route that only recognizes HTTP PUT. -        # For supported arguments, see +match+. +        # For supported arguments, see <tt>Base#match</tt>.          #          # Example:          # @@ -379,7 +484,7 @@ module ActionDispatch          end          # Define a route that only recognizes HTTP PUT. -        # For supported arguments, see +match+. +        # For supported arguments, see <tt>Base#match</tt>.          #          # Example:          # @@ -458,51 +563,38 @@ module ActionDispatch            super          end -        # === Supported options -        # [:module] -        #   If you want to route /posts (without the prefix /admin) to -        #   Admin::PostsController, you could use -        # -        #     scope :module => "admin" do -        #       resources :posts -        #     end -        # -        # [:path] -        #   If you want to prefix the route, you could use +        # Scopes a set of routes to the given default options.          # -        #     scope :path => "/admin" do -        #       resources :posts -        #     end +        # Take the following route definition as an example:          # -        #   This will prefix all of the +posts+ resource's requests with '/admin' -        # -        # [:as] -        #  Prefixes the routing helpers in this scope with the specified label. +        #   scope :path => ":account_id", :as => "account" do +        #     resources :projects +        #   end          # -        #    scope :as => "sekret" do -        #      resources :posts -        #    end +        # This generates helpers such as +account_projects_path+, just like +resources+ does. +        # The difference here being that the routes generated are like /rails/projects/2, +        # rather than /accounts/rails/projects/2.          # -        # Helpers such as +posts_path+ will now be +sekret_posts_path+ +        # === Options          # -        # [:shallow_path] +        # Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.          # -        #   Prefixes nested shallow routes with the specified path. +        # === Examples          # -        #   scope :shallow_path => "sekret" do -        #     resources :posts do -        #       resources :comments, :shallow => true -        #     end +        #   # route /posts (without the prefix /admin) to Admin::PostsController +        #   scope :module => "admin" do +        #     resources :posts +        #   end          # -        #   The +comments+ resource here will have the following routes generated for it: +        #   # prefix the posts resource's requests with '/admin' +        #   scope :path => "/admin" do +        #     resources :posts +        #   end          # -        #     post_comments    GET    /sekret/posts/:post_id/comments(.:format) -        #     post_comments    POST   /sekret/posts/:post_id/comments(.:format) -        #     new_post_comment GET    /sekret/posts/:post_id/comments/new(.:format) -        #     edit_comment     GET    /sekret/comments/:id/edit(.:format) -        #     comment          GET    /sekret/comments/:id(.:format) -        #     comment          PUT    /sekret/comments/:id(.:format) -        #     comment          DELETE /sekret/comments/:id(.:format) +        #   # prefix the routing helper name: sekret_posts_path instead of posts_path +        #   scope :as => "sekret" do +        #     resources :posts +        #   end          def scope(*args)            options = args.extract_options!            options = options.dup @@ -558,50 +650,38 @@ module ActionDispatch          #          # This generates the following routes:          # -        #      admin_posts GET    /admin/posts(.:format)          {:action=>"index", :controller=>"admin/posts"} -        #      admin_posts POST   /admin/posts(.:format)          {:action=>"create", :controller=>"admin/posts"} -        #   new_admin_post GET    /admin/posts/new(.:format)      {:action=>"new", :controller=>"admin/posts"} -        #  edit_admin_post GET    /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"} -        #       admin_post GET    /admin/posts/:id(.:format)      {:action=>"show", :controller=>"admin/posts"} -        #       admin_post PUT    /admin/posts/:id(.:format)      {:action=>"update", :controller=>"admin/posts"} -        #       admin_post DELETE /admin/posts/:id(.:format)      {:action=>"destroy", :controller=>"admin/posts"} -        # === Supported options +        #       admin_posts GET    /admin/posts(.:format)          {:action=>"index", :controller=>"admin/posts"} +        #       admin_posts POST   /admin/posts(.:format)          {:action=>"create", :controller=>"admin/posts"} +        #    new_admin_post GET    /admin/posts/new(.:format)      {:action=>"new", :controller=>"admin/posts"} +        #   edit_admin_post GET    /admin/posts/:id/edit(.:format) {:action=>"edit", :controller=>"admin/posts"} +        #        admin_post GET    /admin/posts/:id(.:format)      {:action=>"show", :controller=>"admin/posts"} +        #        admin_post PUT    /admin/posts/:id(.:format)      {:action=>"update", :controller=>"admin/posts"} +        #        admin_post DELETE /admin/posts/:id(.:format)      {:action=>"destroy", :controller=>"admin/posts"}          # -        # The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ options all default to the name of the namespace. +        # === Options          # -        # [:path] -        #   The path prefix for the routes. +        # The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ +        # options all default to the name of the namespace. +        # +        # For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see +        # <tt>Resources#resources</tt>.          # +        # === Examples +        # +        #   # accessible through /sekret/posts rather than /admin/posts          #   namespace :admin, :path => "sekret" do          #     resources :posts          #   end          # -        #   All routes for the above +resources+ will be accessible through +/sekret/posts+, rather than +/admin/posts+ -        # -        # [:module] -        #   The namespace for the controllers. -        # +        #   # maps to Sekret::PostsController rather than Admin::PostsController          #   namespace :admin, :module => "sekret" do          #     resources :posts          #   end          # -        #   The +PostsController+ here should go in the +Sekret+ namespace and so it should be defined like this: -        # -        #   class Sekret::PostsController < ApplicationController -        #     # code go here +        #   # generates sekret_posts_path rather than admin_posts_path +        #   namespace :admin, :as => "sekret" do +        #     resources :posts          #   end -        # -        # [:as] -        #   Changes the name used in routing helpers for this namespace. -        # -        #     namespace :admin, :as => "sekret" do -        #       resources :posts -        #     end -        # -        # Routing helpers such as +admin_posts_path+ will now be +sekret_posts_path+. -        # -        # [:shallow_path] -        #   See the +scope+ method.          def namespace(path, options = {})            path = path.to_s            options = { :path => path, :as => path, :module => path, @@ -668,9 +748,9 @@ module ActionDispatch          end          # Allows you to set default parameters for a route, such as this: -        # defaults :id => 'home' do -        #   match 'scoped_pages/(:id)', :to => 'pages#show' -        # end +        #   defaults :id => 'home' do +        #     match 'scoped_pages/(:id)', :to => 'pages#show' +        #   end          # Using this, the +:id+ parameter here will default to 'home'.          def defaults(defaults = {})            scope(:defaults => defaults) { yield } @@ -767,6 +847,14 @@ module ActionDispatch        #     resources :posts, :comments        #   end        # +      # By default the :id parameter doesn't accept dots. If you need to +      # use dots as part of the :id parameter add a constraint which +      # overrides this restriction, e.g: +      # +      #   resources :articles, :id => /[^\/]+/ +      # +      # This allows any character other than a slash as part of your :id. +      #        module Resources          # CANONICAL_ACTIONS holds all actions that does not need a prefix or          # a path appended since they fit properly in their scope level. @@ -815,7 +903,8 @@ module ActionDispatch            alias :member_name :singular -          # Checks for uncountable plurals, and appends "_index" if they're. +          # Checks for uncountable plurals, and appends "_index" if the plural  +          # and singular form are the same.            def collection_name              singular == plural ? "#{plural}_index" : plural            end @@ -894,6 +983,9 @@ module ActionDispatch          #   GET     /geocoder/edit          #   PUT     /geocoder          #   DELETE  /geocoder +        # +        # === Options +        # Takes same options as +resources+.          def resource(*resources, &block)            options = resources.extract_options! @@ -955,7 +1047,9 @@ module ActionDispatch          #   PUT     /photos/:id/comments/:id          #   DELETE  /photos/:id/comments/:id          # -        # === Supported options +        # === Options +        # Takes same options as <tt>Base#match</tt> as well as: +        #          # [:path_names]          #   Allows you to change the paths of the seven default actions.          #   Paths not specified are not changed. @@ -964,20 +1058,59 @@ module ActionDispatch          #          #   The above example will now change /posts/new to /posts/brand_new          # -        # [:module] -        #   Set the module where the controller can be found. Defaults to nothing. +        # [:only] +        #   Only generate routes for the given actions.          # -        #     resources :posts, :module => "admin" +        #     resources :cows, :only => :show +        #     resources :cows, :only => [:show, :index]          # -        #   All requests to the posts resources will now go to +Admin::PostsController+. +        # [:except] +        #   Generate all routes except for the given actions.          # -        # [:path] +        #     resources :cows, :except => :show +        #     resources :cows, :except => [:show, :index] +        # +        # [:shallow] +        #   Generates shallow routes for nested resource(s). When placed on a parent resource, +        #   generates shallow routes for all nested resources. +        # +        #     resources :posts, :shallow => true do +        #       resources :comments +        #     end +        # +        #   Is the same as: +        # +        #     resources :posts do +        #       resources :comments +        #     end +        #     resources :comments +        # +        # [:shallow_path] +        #   Prefixes nested shallow routes with the specified path. +        # +        #   scope :shallow_path => "sekret" do +        #     resources :posts do +        #       resources :comments, :shallow => true +        #     end +        #   end +        # +        #   The +comments+ resource here will have the following routes generated for it: +        # +        #     post_comments    GET    /sekret/posts/:post_id/comments(.:format) +        #     post_comments    POST   /sekret/posts/:post_id/comments(.:format) +        #     new_post_comment GET    /sekret/posts/:post_id/comments/new(.:format) +        #     edit_comment     GET    /sekret/comments/:id/edit(.:format) +        #     comment          GET    /sekret/comments/:id(.:format) +        #     comment          PUT    /sekret/comments/:id(.:format) +        #     comment          DELETE /sekret/comments/:id(.:format)          # -        #  Set a path prefix for this resource. +        # === Examples          # -        #     resources :posts, :path => "admin" +        #   # routes call Admin::PostsController +        #   resources :posts, :module => "admin"          # -        #  All actions for this resource will now be at +/admin/posts+. +        #   # resource actions are at /admin/posts. +        #   resources :posts, :path => "admin"          def resources(*resources, &block)            options = resources.extract_options! @@ -1099,7 +1232,7 @@ module ActionDispatch          end          def shallow -          scope(:shallow => true) do +          scope(:shallow => true, :shallow_path => @scope[:path]) do              yield            end          end @@ -1309,7 +1442,7 @@ module ActionDispatch              name = case @scope[:scope_level]              when :nested -              [member_name, prefix] +              [name_prefix, prefix]              when :collection                [prefix, name_prefix, collection_name]              when :new diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 49e237f8db..82c4fadb50 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -99,11 +99,9 @@ module ActionDispatch          record = extract_record(record_or_hash_or_array)          record = record.to_model if record.respond_to?(:to_model) -        args = case record_or_hash_or_array -          when Hash;  [ record_or_hash_or_array ] -          when Array; record_or_hash_or_array.dup -          else        [ record_or_hash_or_array ] -        end +        args = Array === record_or_hash_or_array ? +          record_or_hash_or_array.dup : +          [ record_or_hash_or_array ]          inflection = if options[:action] && options[:action].to_s == "new"            args.pop diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 03bfe178e5..fc86d52a3a 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -50,12 +50,13 @@ module ActionDispatch        private          def controller_reference(controller_param) +          controller_name = "#{controller_param.camelize}Controller" +            unless controller = @controllers[controller_param] -            controller_name = "#{controller_param.camelize}Controller"              controller = @controllers[controller_param] = -              ActiveSupport::Dependencies.ref(controller_name) +              ActiveSupport::Dependencies.reference(controller_name)            end -          controller.get +          controller.get(controller_name)          end          def dispatch(controller, action, env) @@ -450,7 +451,7 @@ module ActionDispatch          end          def raise_routing_error -          raise ActionController::RoutingError.new("No route matches #{options.inspect}") +          raise ActionController::RoutingError, "No route matches #{options.inspect}"          end          def different_controller? @@ -540,7 +541,9 @@ module ActionDispatch            end            dispatcher = route.app -          dispatcher = dispatcher.app while dispatcher.is_a?(Mapper::Constraints) +          while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do +            dispatcher = dispatcher.app +          end            if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false)              dispatcher.prepare_params!(params) diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 1558c3ae05..77a15f3e97 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -20,7 +20,7 @@ module ActionDispatch        #        # You can also pass an explicit status number like <tt>assert_response(501)</tt>        # or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>. -      # See ActionDispatch::StatusCodes for a full list. +      # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.        #        # ==== Examples        # diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 1390b74a95..11e8c63fa0 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -37,9 +37,6 @@ module ActionDispatch        #        #   # Test a custom route        #   assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1') -      # -      #   # Check a Simply RESTful generated route -      #   assert_recognizes list_items_url, 'items/list'        def assert_recognizes(expected_options, path, extras={}, message=nil)          request = recognized_request_for(path) @@ -124,7 +121,8 @@ module ActionDispatch            options[:controller] = "/#{controller}"          end -        assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message) +        generate_options = options.dup.delete_if{ |k,v| defaults.key?(k) } +        assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)        end        # A helper to make it easier to test different route configurations. diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index 1a1497385a..914b13dbfb 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2004-2010 David Heinemeier Hansson +# Copyright (c) 2004-2011 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index dada64a86f..60665387b6 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2004-2010 David Heinemeier Hansson +# Copyright (c) 2004-2011 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 15944138f7..ab8c6259c5 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -18,7 +18,7 @@ module ActionView #:nodoc:    # following loop for names:    #    #   <b>Names of all the people</b> -  #   <% for person in @people %> +  #   <% @people.each do |person| %>    #     Name: <%= person.name %><br/>    #   <% end %>    # @@ -172,6 +172,14 @@ module ActionView #:nodoc:      class << self        delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'        delegate :logger, :to => 'ActionController::Base', :allow_nil => true + +      def cache_template_loading +        ActionView::Resolver.caching? +      end + +      def cache_template_loading=(value) +        ActionView::Resolver.caching = value +      end      end      attr_accessor :_template @@ -224,8 +232,8 @@ module ActionView #:nodoc:        @controller_path ||= controller && controller.controller_path      end -    def controller_prefix -      @controller_prefix ||= controller && controller._prefix +    def controller_prefixes +      @controller_prefixes ||= controller && controller._prefixes      end      ActiveSupport.run_load_hooks(:action_view, self) diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index 41013c800c..d338ce616a 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -18,7 +18,7 @@ module ActionView #:nodoc:      autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"      autoload :NumberHelper      autoload :PrototypeHelper -    autoload :RawOutputHelper +    autoload :OutputSafetyHelper      autoload :RecordTagHelper      autoload :SanitizeHelper      autoload :ScriptaculousHelper @@ -48,7 +48,7 @@ module ActionView #:nodoc:      include JavaScriptHelper      include NumberHelper      include PrototypeHelper -    include RawOutputHelper +    include OutputSafetyHelper      include RecordTagHelper      include SanitizeHelper      include ScriptaculousHelper diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb index e52797042f..52eb43a1cd 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb @@ -11,7 +11,9 @@ module ActionView          attr_reader :config, :asset_paths          class_attribute :expansions -        self.expansions = { } +        def self.inherited(base) +          base.expansions = { } +        end          def initialize(config, asset_paths)            @config = config @@ -69,9 +71,21 @@ module ActionView              if sources.first == :all                collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}")              else -              sources.collect do |source| -                determine_source(source, expansions) -              end.flatten +              sources.inject([]) do |list, source| +                determined_source = determine_source(source, expansions) +                update_source_list(list, determined_source) +              end +            end +          end + +          def update_source_list(list, source) +            case source +            when String +              list.delete(source) +              list << source +            when Array +              updated_sources = source - list +              list.concat(updated_sources)              end            end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index b4e61f2034..014a03c54d 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -21,7 +21,7 @@ module ActionView            @controller = controller          end -        # Add the the extension +ext+ if not present. Return full URLs otherwise untouched. +        # Add the extension +ext+ if not present. Return full URLs otherwise untouched.          # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL          # roots. Rewrite the asset path for cache-busting asset ids. Include          # asset host, if configured, with the correct request protocol. diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index 6581e1d6f2..b9126af944 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -33,13 +33,21 @@ module ActionView                all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application']) << 'application'                ((determine_source(:defaults, expansions).dup & all_asset_files) + all_asset_files).uniq              else -              expanded_sources = sources.collect do |source| -                determine_source(source, expansions) -              end.flatten -              expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}")) +              expanded_sources = sources.inject([]) do |list, source| +                determined_source = determine_source(source, expansions) +                update_source_list(list, determined_source) +              end +              add_application_js(expanded_sources, sources)                expanded_sources              end            end + +          def add_application_js(expanded_sources, sources) +            if sources.include?(:defaults) && File.exist?(File.join(custom_dir, "application.#{extension}")) +              expanded_sources.delete('application') +              expanded_sources << "application" +            end +          end        end @@ -59,7 +67,10 @@ module ActionView            #     <script type="text/javascript" src="/javascripts/body.js"></script>            #     <script type="text/javascript" src="/javascripts/tail.js"></script>            def register_javascript_expansion(expansions) -            JavascriptIncludeTag.expansions.merge!(expansions) +            js_expansions = JavascriptIncludeTag.expansions +            expansions.each do |key, values| +              js_expansions[key] = (js_expansions[key] || []) | Array(values) if values +            end            end          end @@ -170,4 +181,4 @@ module ActionView      end    end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index d02b28d7f6..f3e041de95 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -44,7 +44,10 @@ module ActionView            #     <link href="/stylesheets/body.css"  media="screen" rel="stylesheet" type="text/css" />            #     <link href="/stylesheets/tail.css"  media="screen" rel="stylesheet" type="text/css" />            def register_stylesheet_expansion(expansions) -            StylesheetIncludeTag.expansions.merge!(expansions) +            style_expansions = StylesheetIncludeTag.expansions +            expansions.each do |key, values| +              style_expansions[key] = (style_expansions[key] || []) | Array(values) if values +            end            end          end @@ -141,4 +144,4 @@ module ActionView      end    end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index f544a9d147..385378ea29 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -2,31 +2,29 @@ module ActionView    # = Action View Cache Helper    module Helpers      module CacheHelper -      # This helper to exposes a method for caching of view fragments. -      # See ActionController::Caching::Fragments for usage instructions. +      # This helper exposes a method for caching fragments of a view  +      # rather than an entire action or page.  This technique is useful +      # caching pieces like menus, lists of newstopics, static HTML +      # fragments, and so on. This method takes a block that contains +      # the content you wish to cache.         # -      # A method for caching fragments of a view rather than an entire -      # action or page.  This technique is useful caching pieces like -      # menus, lists of news topics, static HTML fragments, and so on. -      # This method takes a block that contains the content you wish -      # to cache.  See ActionController::Caching::Fragments for more -      # information. +      # See ActionController::Caching::Fragments for usage instructions.        #        # ==== Examples -      # If you wanted to cache a navigation menu, you could do the -      # following. +      # If you want to cache a navigation menu, you can do following:        #        #   <% cache do %>        #     <%= render :partial => "menu" %>        #   <% end %>        # -      # You can also cache static content... +      # You can also cache static content:        #        #   <% cache do %>        #      <p>Hello users!  Welcome to our website!</p>        #   <% end %>        # -      # ...and static content mixed with RHTML content. +      # Static content with embedded ruby content can be cached as  +      # well:        #        #    <% cache do %>        #      Topics: @@ -46,8 +44,8 @@ module ActionView      private        # TODO: Create an object that has caching read/write on it        def fragment_for(name = {}, options = nil, &block) #:nodoc: -        if controller.fragment_exist?(name, options) -          controller.read_fragment(name, options) +        if fragment = controller.read_fragment(name, options) +          fragment          else            # VIEW TODO: Make #capture usable outside of ERB            # This dance is needed because Builder can't use capture diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 875ec9b77b..dc8e4bc316 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -1,5 +1,6 @@  require 'date'  require 'action_view/helpers/tag_helper' +require 'active_support/core_ext/date/conversions'  require 'active_support/core_ext/hash/slice'  require 'active_support/core_ext/object/with_options' @@ -566,6 +567,27 @@ module ActionView        def select_year(date, options = {}, html_options = {})          DateTimeSelector.new(date, options, html_options).select_year        end + +      # Returns an html time tag for the given date or time. +      # +      # ==== Examples +      #   time_tag Date.today  # => +      #     <time datetime="2010-11-04">November 04, 2010</time> +      #   time_tag Time.now  # => +      #     <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time> +      #   time_tag Date.yesterday, 'Yesterday'  # => +      #     <time datetime="2010-11-03">Yesterday</time> +      #   time_tag Date.today, :pubdate => true  # => +      #     <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time> +      # +      def time_tag(date_or_time, *args) +        options  = args.extract_options! +        format   = options.delete(:format) || :long +        content  = args.first || I18n.l(date_or_time, :format => format) +        datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339 + +        content_tag(:time, content, options.reverse_merge(:datetime => datetime)) +      end      end      class DateTimeSelector #:nodoc: diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index ef5bbd8ae3..befaa3e8d9 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -298,6 +298,23 @@ module ActionView        #        # If you don't need to attach a form to a model instance, then check out        # FormTagHelper#form_tag. +      # +      # === Form to external resources +      # +      # When you build forms to external resources sometimes you need to set an authenticity token or just render a form +      # without it, for example when you submit data to a payment gateway number and types of fields could be limited. +      # +      # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter +      # +      #   <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f| +      #     ... +      #   <% end %> +      # +      # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>: +      # +      #   <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f| +      #     ... +      #   <% end %>        def form_for(record, options = {}, &proc)          raise ArgumentError, "Missing block" unless block_given? @@ -314,6 +331,8 @@ module ActionView          end          options[:html][:remote] = options.delete(:remote) +        options[:html][:authenticity_token] = options.delete(:authenticity_token) +                  builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)          fields_for = fields_for(object_name, object, options, &proc)          default_options = builder.multipart? ? { :multipart => true } : {} @@ -530,8 +549,11 @@ module ActionView        #     <% end %>        #     ...        #   <% end %> -      def fields_for(record, record_object = nil, options = nil, &block) -        capture(instantiate_builder(record, record_object, options, &block), &block) +      def fields_for(record, record_object = nil, options = {}, &block) +        builder = instantiate_builder(record, record_object, options, &block) +        output = capture(builder, &block) +        output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id? +        output        end        # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object @@ -858,8 +880,7 @@ module ActionView          end      end -    module InstanceTagMethods #:nodoc: -      extend ActiveSupport::Concern +    class InstanceTag        include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper        attr_reader :object, :method_name, :object_name @@ -1025,7 +1046,7 @@ module ActionView          self.class.value_before_type_cast(object, @method_name)        end -      module ClassMethods +      class << self          def value(object, method_name)            object.send method_name if object          end @@ -1111,11 +1132,7 @@ module ActionView          end      end -    class InstanceTag -      include InstanceTagMethods -    end - -    class FormBuilder #:nodoc: +    class FormBuilder        # The methods which wrap a form helper call.        class_attribute :field_helpers        self.field_helpers = (FormHelper.instance_method_names - ['form_for']) @@ -1248,7 +1265,7 @@ module ActionView        def submit(value=nil, options={})          value, options = nil, value if value.is_a?(Hash)          value ||= submit_default_value -        @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit")) +        @template.submit_tag(value, options)        end        def emitted_hidden_id? @@ -1309,14 +1326,8 @@ module ActionView          def fields_for_nested_model(name, object, options, block)            object = convert_to_model(object) -          if object.persisted? -            @template.fields_for(name, object, options) do |builder| -              block.call(builder) -              @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id? -            end -          else -            @template.fields_for(name, object, options, &block) -          end +          options[:hidden_field_id] = object.persisted? +          @template.fields_for(name, object, options, &block)          end          def nested_child_index(name) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 6ac8577785..7698602022 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -533,7 +533,7 @@ module ActionView            else              selected = Array.wrap(selected)              options = selected.extract_options!.symbolize_keys -            [ options[:selected] || selected , options[:disabled] ] +            [ options.include?(:selected) ? options[:selected] : selected, options[:disabled] ]            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 50f065f03d..71f8534cbf 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -25,6 +25,9 @@ module ActionView        # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".        #   If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>        #   is added to simulate the verb over post. +      # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to +      #   pass custom authenticity token string, or to not add authenticity_token field at all +      #   (by passing <tt>false</tt>).        # * A list of parameters to feed to the URL the form will be posted to.        # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the        #   submit behaviour. By default this behaviour is an ajax submit. @@ -47,6 +50,12 @@ module ActionView        #  <%= form_tag('/posts', :remote => true) %>        #   # => <form action="/posts" method="post" data-remote="true">        # +      #   form_tag('http://far.away.com/form', :authenticity_token => false) +      #   # form without authenticity token +      # +      #   form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae") +      #   # form with custom authenticity token +      #        def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)          html_options = html_options_for_form(url_for_options, options, *parameters_for_url)          if block_given? @@ -68,7 +77,7 @@ module ActionView        # * Any other key creates standard HTML attributes for the tag.        #        # ==== Examples -      #   select_tag "people", options_from_collection_for_select(@people, "name", "id") +      #   select_tag "people", options_from_collection_for_select(@people, "id", "name")        #   # <select id="people" name="people"><option value="1">David</option></select>        #        #   select_tag "people", "<option>David</option>" @@ -112,6 +121,7 @@ module ActionView        # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.        # * <tt>:size</tt> - The number of visible characters that will fit in the input.        # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter. +      # * <tt>:placeholder</tt> - The text contained in the field by default which is removed when the field receives focus.        # * Any other key creates standard HTML attributes for the tag.        #        # ==== Examples @@ -121,6 +131,9 @@ module ActionView        #   text_field_tag 'query', 'Enter your search query here'        #   # => <input id="query" name="query" type="text" value="Enter your search query here" />        # +      #   text_field_tag 'search', nil, :placeholder => 'Enter search term...' +      #   # => <input id="search" name="search" placeholder="Enter search term..." type="text" /> +      #        #   text_field_tag 'request', nil, :class => 'special_input'        #   # => <input class="special_input" id="request" name="request" type="text" />        # @@ -397,6 +410,59 @@ module ActionView          tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)        end +      # Creates a button element that defines a <tt>submit</tt> button, +      # <tt>reset</tt>button or a generic button which can be used in +      # JavaScript, for example. You can use the button tag as a regular +      # submit tag but it isn't supported in legacy browsers. However, +      # the button tag allows richer labels such as images and emphasis, +      # so this helper will also accept a block. +      # +      # ==== Options +      # * <tt>:confirm => 'question?'</tt> - If present, the +      #   unobtrusive JavaScript drivers will provide a prompt with +      #   the question specified. If the user accepts, the form is +      #   processed normally, otherwise no action is taken. +      # * <tt>:disabled</tt> - If true, the user will not be able to +      #   use this input. +      # * <tt>:disable_with</tt> - Value of this parameter will be +      #   used as the value for a disabled version of the submit +      #   button when the form is submitted. This feature is provided +      #   by the unobtrusive JavaScript driver. +      # * Any other key creates standard HTML options for the tag. +      # +      # ==== Examples +      #   button_tag +      #   # => <button name="button" type="submit">Button</button> +      # +      #   button_tag(:type => 'button') do +      #     content_tag(:strong, 'Ask me!') +      #   end +      #   # => <button name="button" type="button"> +      #          <strong>Ask me!</strong> +      #        </button> +      # +      #   button_tag "Checkout", :disable_with => "Please wait..." +      #   # => <button data-disable-with="Please wait..." name="button" +      #                type="submit">Checkout</button> +      # +      def button_tag(content_or_options = nil, options = nil, &block) +        options = content_or_options if block_given? && content_or_options.is_a?(Hash) +        options ||= {} +        options.stringify_keys! + +        if disable_with = options.delete("disable_with") +          options["data-disable-with"] = disable_with +        end + +        if confirm = options.delete("confirm") +          options["data-confirm"] = confirm +        end + +        options.reverse_merge! 'name' => 'button', 'type' => 'submit' + +        content_tag :button, content_or_options || 'Button', options, &block +      end +        # Displays an image which when clicked will submit the form.        #        # <tt>source</tt> is passed to AssetTagHelper#path_to_image @@ -530,6 +596,7 @@ module ActionView              html_options["action"]  = url_for(url_for_options, *parameters_for_url)              html_options["accept-charset"] = "UTF-8"              html_options["data-remote"] = true if html_options.delete("remote") +            html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token")            end          end @@ -537,6 +604,7 @@ module ActionView            snowman_tag = tag(:input, :type => "hidden",                              :name => "utf8", :value => "✓".html_safe) +          authenticity_token = html_options.delete("authenticity_token")            method = html_options.delete("method").to_s            method_tag = case method @@ -545,10 +613,10 @@ module ActionView                ''              when /^post$/i, "", nil                html_options["method"] = "post" -              token_tag +              token_tag(authenticity_token)              else                html_options["method"] = "post" -              tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag +              tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)            end            tags = snowman_tag << method_tag @@ -568,11 +636,12 @@ module ActionView            output.safe_concat("</form>")          end -        def token_tag -          unless protect_against_forgery? +        def token_tag(token) +          if token == false || !protect_against_forgery?              ''            else -            tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) +            token = form_authenticity_token if token.nil? +            tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)            end          end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index a9400c347f..26f8dce3c3 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -270,12 +270,13 @@ module ActionView              digits, rounded_number = 1, 0            else              digits = (Math.log10(number.abs) + 1).floor -            rounded_number = BigDecimal.new((number / 10 ** (digits - precision)).to_s).round.to_f * 10 ** (digits - precision) +            rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision) +            digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed            end            precision -= digits            precision = precision > 0 ? precision : 0  #don't let it be negative          else -          rounded_number = BigDecimal.new((number * (10 ** precision)).to_s).round.to_f / 10 ** precision +          rounded_number = BigDecimal.new(number.to_s).round(precision).to_f          end          formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)          if strip_insignificant_zeros diff --git a/actionpack/lib/action_view/helpers/output_safety_helper.rb b/actionpack/lib/action_view/helpers/output_safety_helper.rb new file mode 100644 index 0000000000..a035dd70ad --- /dev/null +++ b/actionpack/lib/action_view/helpers/output_safety_helper.rb @@ -0,0 +1,38 @@ +require 'active_support/core_ext/string/output_safety' + +module ActionView #:nodoc: +  # = Action View Raw Output Helper +  module Helpers #:nodoc: +    module OutputSafetyHelper +      # This method outputs without escaping a string. Since escaping tags is +      # now default, this can be used when you don't want Rails to automatically +      # escape tags. This is not recommended if the data is coming from the user's +      # input. +      # +      # For example: +      # +      # <%=raw @user.name %> +      def raw(stringish) +        stringish.to_s.html_safe +      end + +      # This method returns a html safe string similar to what <tt>Array#join</tt> +      # would return. All items in the array, including the supplied separator, are +      # html escaped unless they are html safe, and the returned string is marked +      # as html safe. +      # +      #   safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />") +      #   # => "<p>foo</p><br /><p>bar</p>" +      # +      #   safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe) +      #   # => "<p>foo</p><br /><p>bar</p>" +      # +      def safe_join(array, sep=$,) +        sep ||= "".html_safe +        sep = ERB::Util.html_escape(sep) + +        array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe +      end +    end +  end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/raw_output_helper.rb b/actionpack/lib/action_view/helpers/raw_output_helper.rb deleted file mode 100644 index 216683a2e0..0000000000 --- a/actionpack/lib/action_view/helpers/raw_output_helper.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionView #:nodoc: -  # = Action View Raw Output Helper -  module Helpers #:nodoc: -    module RawOutputHelper -      # This method outputs without escaping a string. Since escaping tags is -      # now default, this can be used when you don't want Rails to automatically -      # escape tags. This is not recommended if the data is coming from the user's -      # input. -      # -      # For example: -      # -      # <%=raw @user.name %> -      def raw(stringish) -        stringish.to_s.html_safe -      end -    end -  end -end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index ee51617a2b..786af5ca58 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -14,7 +14,7 @@ module ActionView        BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer                             autoplay controls loop selected hidden scoped async                             defer reversed ismap seemless muted required -                           autofocus novalidate formnovalidate open).to_set +                           autofocus novalidate formnovalidate open pubdate).to_set        BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })        # Returns an empty HTML tag of type +name+ which by default is XHTML diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 3d276000a1..2d3c5fe7e7 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -234,6 +234,10 @@ module ActionView        #        # You can pass any HTML attributes into <tt>html_options</tt>.  These        # will be added to all created paragraphs. +      # +      # ==== Options +      # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+. +      #        # ==== Examples        #   my_text = "Here is some basic text...\n...with a line break."        # @@ -247,6 +251,9 @@ module ActionView        #        #   simple_format("Look ma! A class!", :class => 'description')        #   # => "<p class='description'>Look ma! A class!</p>" +      # +      #   simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false) +      #   # => "<p><span>I'm allowed!</span> It's true.</p>"        def simple_format(text, html_options={}, options={})          text = ''.html_safe if text.nil?          start_tag = tag('p', html_options, true) @@ -459,7 +466,7 @@ module ActionView          end          AUTO_LINK_RE = %r{ -            (?: ([\w+.:-]+:)// | www\. ) +            (?: ([0-9A-Za-z+.:-]+:)// | www\. )              [^\s<]+            }x diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index c23315b344..2cd2dca711 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -253,8 +253,9 @@ module ActionView        # using the +link_to+ method with the <tt>:method</tt> modifier as described in        # the +link_to+ documentation.        # -      # The generated form element has a class name of <tt>button_to</tt> -      # to allow styling of the form itself and its children. You can control +      # By default, the generated form element has a class name of <tt>button_to</tt> +      # to allow styling of the form itself and its children. This can be changed +      # using the <tt>:form_class</tt> modifier within +html_options+. You can control        # the form submission and input element behavior using +html_options+.        # This method accepts the <tt>:method</tt> and <tt>:confirm</tt> modifiers        # described in the +link_to+ documentation. If no <tt>:method</tt> modifier @@ -275,6 +276,8 @@ module ActionView        #   processed normally, otherwise no action is taken.        # * <tt>:remote</tt> -  If set to true, will allow the Unobtrusive JavaScript drivers to control the        #   submit behaviour. By default this behaviour is an ajax submit. +      # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will +      #   be placed        #        # ==== Examples        #   <%= button_to "New", :action => "new" %> @@ -283,6 +286,12 @@ module ActionView        #   #    </form>"        #        # +      #   <%= button_to "New", :action => "new", :form_class => "new-thing" %> +      #   # => "<form method="post" action="/controller/new" class="new-thing"> +      #   #      <div><input value="New" type="submit" /></div> +      #   #    </form>" +      # +      #        #   <%= button_to "Delete Image", { :action => "delete", :id => @image.id },        #             :confirm => "Are you sure?", :method => :delete %>        #   # => "<form method="post" action="/images/delete/1" class="button_to"> @@ -312,6 +321,7 @@ module ActionView          end          form_method = method.to_s == 'get' ? 'get' : 'post' +        form_class = html_options.delete('form_class') || 'button_to'          remote = html_options.delete('remote') @@ -327,7 +337,7 @@ module ActionView          html_options.merge!("type" => "submit", "value" => name) -        ("<form method=\"#{form_method}\" action=\"#{ERB::Util.html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" + +        ("<form method=\"#{form_method}\" action=\"#{ERB::Util.html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"#{ERB::Util.html_escape(form_class)}\"><div>" +            method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe        end @@ -487,13 +497,14 @@ module ActionView          email_address_obfuscated = email_address.dup          email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at")          email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot") -          case encode          when "javascript" -          string = -            "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".unpack('C*').map { |c| -            sprintf("%%%x", c) -          }.join +          string = '' +          html   = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)) +          html   = escape_javascript(html) +          "document.write('#{html}');".each_byte do |c| +            string << sprintf("%%%x", c) +          end            "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe          when "hex"            email_address_encoded = email_address_obfuscated.unpack('C*').map {|c| diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index d524c68450..06c607931d 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -78,17 +78,17 @@ module ActionView          @view_paths = ActionView::Base.process_view_paths(paths)        end -      def find(name, prefix = nil, partial = false, keys = []) -        @view_paths.find(*args_for_lookup(name, prefix, partial, keys)) +      def find(name, prefixes = [], partial = false, keys = []) +        @view_paths.find(*args_for_lookup(name, prefixes, partial, keys))        end        alias :find_template :find -      def find_all(name, prefix = nil, partial = false, keys = []) -        @view_paths.find_all(*args_for_lookup(name, prefix, partial, keys)) +      def find_all(name, prefixes = [], partial = false, keys = []) +        @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys))        end -      def exists?(name, prefix = nil, partial = false, keys = []) -        @view_paths.exists?(*args_for_lookup(name, prefix, partial, keys)) +      def exists?(name, prefixes = [], partial = false, keys = []) +        @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys))        end        alias :template_exists? :exists? @@ -107,18 +107,26 @@ module ActionView      protected -      def args_for_lookup(name, prefix, partial, keys) #:nodoc: -        name, prefix = normalize_name(name, prefix) -        [name, prefix, partial || false, @details, details_key, keys] +      def args_for_lookup(name, prefixes, partial, keys) #:nodoc: +        name, prefixes = normalize_name(name, prefixes) +        [name, prefixes, partial || false, @details, details_key, keys]        end        # Support legacy foo.erb names even though we now ignore .erb        # as well as incorrectly putting part of the path in the template        # name instead of the prefix. -      def normalize_name(name, prefix) #:nodoc: +      def normalize_name(name, prefixes) #:nodoc:          name  = name.to_s.gsub(handlers_regexp, '')          parts = name.split('/') -        return parts.pop, [prefix, *parts].compact.join("/") +        name  = parts.pop + +        prefixes = if prefixes.blank? +          [parts.join('/')] +        else +          prefixes.map { |prefix| [prefix, *parts].compact.join('/') } +        end + +        return name, prefixes        end        def default_handlers #:nodoc: @@ -156,11 +164,11 @@ module ActionView          @frozen_formats = true        end -      # Overload formats= to reject [:"*/*"] values. +      # Overload formats= to reject ["*/*"] values.        def formats=(values)          if values && values.size == 1            value = values.first -          values = nil    if value == :"*/*" +          values = nil    if value == "*/*"            values << :html if value == :js          end          super(values) @@ -178,11 +186,11 @@ module ActionView        end        # Overload locale= to also set the I18n.locale. If the current I18n.config object responds -      # to i18n_config, it means that it's has a copy of the original I18n configuration and it's +      # to original_config, it means that it's has a copy of the original I18n configuration and it's        # acting as proxy, which we need to skip.        def locale=(value)          if value -          config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config +          config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config            config.locale = value          end @@ -237,4 +245,4 @@ module ActionView      include Details      include ViewPaths    end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index cd3f130dc4..c181689e62 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -12,19 +12,19 @@ module ActionView    #    #  <%= render :partial => "account" %>    # -  # This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable +  # This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable    # +account+ to the template for display.    #    # In another template for Advertiser#buy, we could have:    #    #   <%= render :partial => "account", :locals => { :account => @buyer } %>    # -  #   <% for ad in @advertisements %> +  #   <% @advertisements.each do |ad| %>    #     <%= render :partial => "ad", :locals => { :ad => ad } %>    #   <% end %>    # -  # This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then -  # render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. +  # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then +  # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display.    #    # == The :as and :object options    # @@ -40,16 +40,16 @@ module ActionView    # With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we    # wanted it to be +agreement+ instead of +contract+ we'd do:    # -  #   <%= render :partial => "contract", :as => :agreement %> +  #   <%= render :partial => "contract", :as => 'agreement' %>    #    # The <tt>:object</tt> option can be used to directly specify which object is rendered into the partial;    # useful when the template's object is elsewhere, in a different ivar or in a local variable for instance. -  #  +  #    # Revisiting a previous example we could have written this code: -  #  +  #    #   <%= render :partial => "account", :object => @buyer %>    # -  #   <% for ad in @advertisements %> +  #   <% @advertisements.each do |ad| %>    #     <%= render :partial => "ad", :object => ad %>    #   <% end %>    # @@ -64,12 +64,12 @@ module ActionView    #    #   <%= render :partial => "ad", :collection => @advertisements %>    # -  # This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An +  # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An    # iteration counter will automatically be made available to the template with a name of the form    # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.    #    # The <tt>:as</tt> option may be used when rendering partials. -  #  +  #    # You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.    # The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:    # @@ -89,7 +89,7 @@ module ActionView    #    #   <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>    # -  # This will render the partial "advertisement/_ad.erb" regardless of which controller this is being called from. +  # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.    #    # == Rendering objects with the RecordIdentifier    # diff --git a/actionpack/lib/action_view/path_set.rb b/actionpack/lib/action_view/path_set.rb index fa35120a0d..e3de3e1eac 100644 --- a/actionpack/lib/action_view/path_set.rb +++ b/actionpack/lib/action_view/path_set.rb @@ -11,13 +11,15 @@ module ActionView #:nodoc:      end      def find(*args) -      find_all(*args).first || raise(MissingTemplate.new(self, "#{args[1]}/#{args[0]}", args[3], args[2])) +      find_all(*args).first || raise(MissingTemplate.new(self, *args))      end -    def find_all(*args) -      each do |resolver| -        templates = resolver.find_all(*args) -        return templates unless templates.empty? +    def find_all(path, prefixes = [], *args) +      prefixes.each do |prefix| +        each do |resolver| +          templates = resolver.find_all(path, prefix, *args) +          return templates unless templates.empty? +        end        end        []      end diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index 71cd1a788a..501ec07b09 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -35,5 +35,13 @@ module ActionView          end        end      end + +    initializer "action_view.caching" do |app| +      ActiveSupport.on_load(:action_view) do +        if app.config.action_view.cache_template_loading.nil? +          ActionView::Resolver.caching = app.config.cache_classes +        end +      end +    end    end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 317479ad4c..94c0a8a8fb 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -108,11 +108,11 @@ module ActionView          locals << @variable_counter if @collection          find_template(path, locals)        end -    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) +      prefixes = path.include?(?/) ? [] : @view.controller_prefixes +      @lookup_context.find_template(path, prefixes, true, locals)      end      def collection_with_template @@ -152,14 +152,14 @@ module ActionView          object = object.to_model if object.respond_to?(:to_model)          object.class.model_name.partial_path.dup.tap do |partial| -          path = @view.controller_prefix +          path = @view.controller_prefixes.first            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 = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym        variable_counter = :"#{variable}_counter" if @collection        [variable, variable_counter]      end diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index ece3f621b6..9ae1636131 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -22,14 +22,14 @@ module ActionView      def render_once(options)        paths, locals = options[:once], options[:locals] || {}        layout, keys  = options[:layout], locals.keys -      prefix = options.fetch(:prefix, @view.controller_prefix) +      prefixes = options.fetch(:prefixes, @view.controller_prefixes)        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) +          template = find_template(path, prefixes, false, keys)            contents << render_template(template, nil, locals) if @rendered.add?(template)          end          contents.join("\n") @@ -43,13 +43,13 @@ module ActionView        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) } +        with_fallbacks { find_template(options[:file], options[:prefixes], false, keys) }        elsif options.key?(:inline)          handler = Template.handler_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) +          options[:template] : find_template(options[:template], options[:prefixes], false, keys)        end      end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 0d8373ef14..96d506fac5 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -123,7 +123,7 @@ module ActionView        @locals            = details[:locals] || []        @virtual_path      = details[:virtual_path]        @updated_at        = details[:updated_at] || Time.now -      @formats           = Array.wrap(format).map(&:to_sym) +      @formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }      end      # Render a template. If the template was not compiled yet, it is done @@ -163,7 +163,7 @@ module ActionView        name    = pieces.pop        partial = !!name.sub!(/^_/, "")        lookup.disable_cache do -        lookup.find_template(name, pieces.join('/'), partial, @locals) +        lookup.find_template(name, [ pieces.join('/') ], partial, @locals)        end      end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index ff256738a9..e246646963 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -27,7 +27,7 @@ module ActionView    class MissingTemplate < ActionViewError #:nodoc:      attr_reader :path -    def initialize(paths, path, details, partial) +    def initialize(paths, path, prefixes, partial, details, *)        @path = path        display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")        template_type = if partial @@ -38,7 +38,11 @@ module ActionView          'template'        end -      super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}") +      searched_paths = prefixes.map { |prefix| [prefix, path].join("/") } + +      out  = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n" +      out += paths.compact.map { |p| "  * #{p.to_s.inspect}\n" }.join +      super out      end    end @@ -52,6 +56,7 @@ module ActionView        attr_reader :original_exception, :backtrace        def initialize(template, assigns, original_exception) +        super(original_exception.message)          @template, @assigns, @original_exception = template, assigns.dup, original_exception          @sub_templates = nil          @backtrace = original_exception.backtrace @@ -61,10 +66,6 @@ module ActionView          @template.identifier        end -      def message -        ActiveSupport::Deprecation.silence { original_exception.message } -      end -        def sub_template_message          if @sub_templates            "Trace of template inclusion: " + diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index a17454da28..4d999fb3b2 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -5,6 +5,13 @@ require "action_view/template"  module ActionView    # = Action View Resolver    class Resolver +    cattr_accessor :caching +    self.caching = true + +    class << self +      alias :caching? :caching +    end +      def initialize        @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] = {} } } } } @@ -23,9 +30,7 @@ module ActionView    private -    def caching? -      @caching ||= !defined?(Rails.application) || Rails.application.config.cache_classes -    end +    delegate :caching?, :to => "self.class"      # This is what child classes implement. No defaults are needed      # because Resolver guarantees that the arguments are present and @@ -42,7 +47,7 @@ module ActionView        path      end -    # Hnadles templates caching. If a key is given and caching is on +    # Handles 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. @@ -104,18 +109,27 @@ module ActionView      def query(path, exts, formats)        query = File.join(@path, path) -      exts.each do |ext| -        query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << ',}' -      end +      query << exts.map { |ext| +        "{#{ext.compact.map { |e| ".#{e}" }.join(',')},}" +      }.join -      Dir[query].reject { |p| File.directory?(p) }.map do |p| -        handler, format = extract_handler_and_format(p, formats) +      query.gsub!(/\{\.html,/, "{.html,.text.html,") +      query.gsub!(/\{\.text,/, "{.text,.text.plain,") + +      templates = [] +      sanitizer = Hash.new { |h,k| h[k] = Dir["#{File.dirname(k)}/*"] } + +      Dir[query].each do |p| +        next if File.directory?(p) || !sanitizer[p].include?(p) +        handler, format = extract_handler_and_format(p, formats)          contents = File.open(p, "rb") {|io| io.read } -        Template.new(contents, File.expand_path(p), handler, +        templates << Template.new(contents, File.expand_path(p), handler,            :virtual_path => path, :format => format, :updated_at => mtime(p))        end + +      templates      end      # Returns the file mtime from the filesystem. diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 4026f7a40e..2ce109ea99 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -156,10 +156,8 @@ module ActionView        # The instance of ActionView::Base that is used by +render+.        def view          @view ||= begin -          view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller) +          view = @controller.view_context            view.singleton_class.send :include, _helpers -          view.singleton_class.send :include, @controller._routes.url_helpers -          view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash"            view.extend(Locals)            view.locals = self.locals            view.output_buffer = self.output_buffer @@ -171,6 +169,7 @@ module ActionView        INTERNAL_IVARS = %w{          @__name__ +        @__io__          @_assertion_wrapped          @_assertions          @_result diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb index 55583096e0..5c5cab7c7d 100644 --- a/actionpack/lib/action_view/testing/resolvers.rb +++ b/actionpack/lib/action_view/testing/resolvers.rb @@ -13,7 +13,11 @@ module ActionView #:nodoc:        @hash = hash      end -    private +    def to_s +      @hash.keys.join(', ') +    end + +  private      def query(path, exts, formats)        query = "" | 
