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/action_dispatch | |
| 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/action_dispatch')
18 files changed, 437 insertions, 191 deletions
| 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. | 
