aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
authorJoshua Peek <josh@joshpeek.com>2009-10-20 10:14:46 -0500
committerJoshua Peek <josh@joshpeek.com>2009-10-20 10:14:46 -0500
commita74022ecd3e078f55ed6049a96565119dc540ff5 (patch)
treee10a8556b5524cf211ea6d452817ae78eecdb720 /actionpack/lib/action_controller
parent3895e2ccb3fe7cfcf36794fe9c941dc1381a0cb7 (diff)
downloadrails-a74022ecd3e078f55ed6049a96565119dc540ff5.tar.gz
rails-a74022ecd3e078f55ed6049a96565119dc540ff5.tar.bz2
rails-a74022ecd3e078f55ed6049a96565119dc540ff5.zip
Move Routing into AD
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/deprecated.rb3
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb (renamed from actionpack/lib/action_controller/routing/polymorphic_routes.rb)0
-rw-r--r--actionpack/lib/action_controller/routing.rb381
-rw-r--r--actionpack/lib/action_controller/routing/resources.rb685
-rw-r--r--actionpack/lib/action_controller/routing/route_set.rb699
-rw-r--r--actionpack/lib/action_controller/url_rewriter.rb (renamed from actionpack/lib/action_controller/routing/url_rewriter.rb)0
6 files changed, 3 insertions, 1765 deletions
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
index d98e9ac7bd..23fe6a4c3a 100644
--- a/actionpack/lib/action_controller/deprecated.rb
+++ b/actionpack/lib/action_controller/deprecated.rb
@@ -1,2 +1,5 @@
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
+ActionController::Routing = ActionDispatch::Routing
+ActionDispatch::Resources = ActionDispatch::Routing::Resources
+ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new
diff --git a/actionpack/lib/action_controller/routing/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index 2adf3575a7..2adf3575a7 100644
--- a/actionpack/lib/action_controller/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
deleted file mode 100644
index 979a4ad8c9..0000000000
--- a/actionpack/lib/action_controller/routing.rb
+++ /dev/null
@@ -1,381 +0,0 @@
-require 'active_support/core_ext/object/conversions'
-require 'active_support/core_ext/boolean/conversions'
-require 'active_support/core_ext/nil/conversions'
-require 'active_support/core_ext/regexp'
-require 'action_controller/routing/route_set'
-
-module ActionController
- # == Routing
- #
- # The routing module provides URL rewriting in native Ruby. It's a way to
- # redirect incoming requests to controllers and actions. This replaces
- # mod_rewrite rules. Best of all, Rails' Routing works with any web server.
- # Routes are defined in <tt>config/routes.rb</tt>.
- #
- # Consider the following route, installed by Rails when you generate your
- # application:
- #
- # map.connect ':controller/:action/:id'
- #
- # This route states that it expects requests to consist of a
- # <tt>:controller</tt> followed by an <tt>:action</tt> that in turn is fed
- # some <tt>:id</tt>.
- #
- # Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end up
- # with:
- #
- # params = { :controller => 'blog',
- # :action => 'edit',
- # :id => '22'
- # }
- #
- # Think of creating routes as drawing a map for your requests. The map tells
- # them where to go based on some predefined pattern:
- #
- # ActionController::Routing::Routes.draw do |map|
- # Pattern 1 tells some request to go to one place
- # Pattern 2 tell them to go to another
- # ...
- # end
- #
- # The following symbols are special:
- #
- # :controller maps to your controller name
- # :action maps to an action with your controllers
- #
- # Other names simply map to a parameter as in the case of <tt>:id</tt>.
- #
- # == Route priority
- #
- # Not all routes are created equally. Routes have priority defined by the
- # order of appearance of the routes in the <tt>config/routes.rb</tt> file. The priority goes
- # from top to bottom. The last route in that file is at the lowest priority
- # and will be applied last. If no route matches, 404 is returned.
- #
- # Within blocks, the empty pattern is at the highest priority.
- # In practice this works out nicely:
- #
- # ActionController::Routing::Routes.draw do |map|
- # map.with_options :controller => 'blog' do |blog|
- # blog.show '', :action => 'list'
- # end
- # map.connect ':controller/:action/:view'
- # end
- #
- # In this case, invoking blog controller (with an URL like '/blog/')
- # without parameters will activate the 'list' action by default.
- #
- # == Defaults routes and default parameters
- #
- # Setting a default route is straightforward in Rails - you simply append a
- # Hash at the end of your mapping to set any default parameters.
- #
- # Example:
- #
- # ActionController::Routing:Routes.draw do |map|
- # map.connect ':controller/:action/:id', :controller => 'blog'
- # end
- #
- # This sets up +blog+ as the default controller if no other is specified.
- # This means visiting '/' would invoke the blog controller.
- #
- # More formally, you can include arbitrary parameters in the route, thus:
- #
- # map.connect ':controller/:action/:id', :action => 'show', :page => 'Dashboard'
- #
- # This will pass the :page parameter to all incoming requests that match this route.
- #
- # Note: The default routes, as provided by the Rails generator, make all actions in every
- # controller accessible via GET requests. You should consider removing them or commenting
- # them out if you're using named routes and resources.
- #
- # == Named routes
- #
- # Routes can be named with the syntax <tt>map.name_of_route options</tt>,
- # allowing for easy reference within your source as +name_of_route_url+
- # for the full URL and +name_of_route_path+ for the URI path.
- #
- # Example:
- #
- # # In routes.rb
- # map.login 'login', :controller => 'accounts', :action => 'login'
- #
- # # With render, redirect_to, tests, etc.
- # redirect_to login_url
- #
- # Arguments can be passed as well.
- #
- # redirect_to show_item_path(:id => 25)
- #
- # Use <tt>map.root</tt> as a shorthand to name a route for the root path "".
- #
- # # In routes.rb
- # map.root :controller => 'blogs'
- #
- # # would recognize http://www.example.com/ as
- # params = { :controller => 'blogs', :action => 'index' }
- #
- # # and provide these named routes
- # root_url # => 'http://www.example.com/'
- # root_path # => ''
- #
- # You can also specify an already-defined named route in your <tt>map.root</tt> call:
- #
- # # In routes.rb
- # map.new_session :controller => 'sessions', :action => 'new'
- # map.root :new_session
- #
- # Note: when using +with_options+, the route is simply named after the
- # method you call on the block parameter rather than map.
- #
- # # In routes.rb
- # map.with_options :controller => 'blog' do |blog|
- # blog.show '', :action => 'list'
- # blog.delete 'delete/:id', :action => 'delete'
- # blog.edit 'edit/:id', :action => 'edit'
- # end
- #
- # # provides named routes for show, delete, and edit
- # link_to @article.title, show_path(:id => @article.id)
- #
- # == Pretty URLs
- #
- # Routes can generate pretty URLs. For example:
- #
- # map.connect 'articles/:year/:month/:day',
- # :controller => 'articles',
- # :action => 'find_by_date',
- # :year => /\d{4}/,
- # :month => /\d{1,2}/,
- # :day => /\d{1,2}/
- #
- # Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
- # maps to
- #
- # params = {:year => '2005', :month => '11', :day => '06'}
- #
- # == Regular Expressions and parameters
- # You can specify a regular expression to define a format for a parameter.
- #
- # map.geocode 'geocode/:postalcode', :controller => 'geocode',
- # :action => 'show', :postalcode => /\d{5}(-\d{4})?/
- #
- # or, more formally:
- #
- # map.geocode 'geocode/:postalcode', :controller => 'geocode',
- # :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
- #
- # Formats can include the 'ignorecase' and 'extended syntax' regular
- # expression modifiers:
- #
- # map.geocode 'geocode/:postalcode', :controller => 'geocode',
- # :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i
- #
- # map.geocode 'geocode/:postalcode', :controller => 'geocode',
- # :action => 'show',:requirements => {
- # :postalcode => /# Postcode format
- # \d{5} #Prefix
- # (-\d{4})? #Suffix
- # /x
- # }
- #
- # Using the multiline match modifier will raise an ArgumentError.
- # Encoding regular expression modifiers are silently ignored. The
- # match will always use the default encoding or ASCII.
- #
- # == Route globbing
- #
- # Specifying <tt>*[string]</tt> as part of a rule like:
- #
- # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
- #
- # will glob all remaining parts of the route that were not recognized earlier.
- # The globbed values are in <tt>params[:path]</tt> as an array of path segments.
- #
- # == Route conditions
- #
- # With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
- #
- # * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
- # <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
- # <tt>:any</tt> means that any method can access the route.
- #
- # Example:
- #
- # map.connect 'post/:id', :controller => 'posts', :action => 'show',
- # :conditions => { :method => :get }
- # map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
- # :conditions => { :method => :post }
- #
- # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
- # URL will route to the <tt>show</tt> action.
- #
- # == Reloading routes
- #
- # You can reload routes if you feel you must:
- #
- # ActionController::Routing::Routes.reload
- #
- # This will clear all named routes and reload routes.rb if the file has been modified from
- # last load. To absolutely force reloading, use <tt>reload!</tt>.
- #
- # == Testing Routes
- #
- # The two main methods for testing your routes:
- #
- # === +assert_routing+
- #
- # def test_movie_route_properly_splits
- # opts = {:controller => "plugin", :action => "checkout", :id => "2"}
- # assert_routing "plugin/checkout/2", opts
- # end
- #
- # +assert_routing+ lets you test whether or not the route properly resolves into options.
- #
- # === +assert_recognizes+
- #
- # def test_route_has_options
- # opts = {:controller => "plugin", :action => "show", :id => "12"}
- # assert_recognizes opts, "/plugins/show/12"
- # end
- #
- # Note the subtle difference between the two: +assert_routing+ tests that
- # a URL fits options while +assert_recognizes+ tests that a URL
- # breaks into parameters properly.
- #
- # In tests you can simply pass the URL or named route to +get+ or +post+.
- #
- # def send_to_jail
- # get '/jail'
- # assert_response :success
- # assert_template "jail/front"
- # end
- #
- # def goes_to_login
- # get login_url
- # #...
- # end
- #
- # == View a list of all your routes
- #
- # Run <tt>rake routes</tt>.
- #
- module Routing
- SEPARATORS = %w( / . ? )
-
- HTTP_METHODS = [:get, :head, :post, :put, :delete, :options]
-
- ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
-
- # The root paths which may contain controller files
- mattr_accessor :controller_paths
- self.controller_paths = []
-
- # A helper module to hold URL related helpers.
- module Helpers
- include PolymorphicRoutes
- end
-
- class << self
- # Expects an array of controller names as the first argument.
- # Executes the passed block with only the named controllers named available.
- # This method is used in internal Rails testing.
- def with_controllers(names)
- prior_controllers = @possible_controllers
- use_controllers! names
- yield
- ensure
- use_controllers! prior_controllers
- end
-
- # Returns an array of paths, cleaned of double-slashes and relative path references.
- # * "\\\" and "//" become "\\" or "/".
- # * "/foo/bar/../config" becomes "/foo/config".
- # The returned array is sorted by length, descending.
- def normalize_paths(paths)
- # do the hokey-pokey of path normalization...
- paths = paths.collect do |path|
- path = path.
- gsub("//", "/"). # replace double / chars with a single
- gsub("\\\\", "\\"). # replace double \ chars with a single
- gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it
-
- # eliminate .. paths where possible
- re = %r{[^/\\]+[/\\]\.\.[/\\]}
- path.gsub!(re, "") while path.match(re)
- path
- end
-
- # start with longest path, first
- paths = paths.uniq.sort_by { |path| - path.length }
- end
-
- # Returns the array of controller names currently available to ActionController::Routing.
- def possible_controllers
- unless @possible_controllers
- @possible_controllers = []
-
- paths = controller_paths.select { |path| File.directory?(path) && path != "." }
-
- seen_paths = Hash.new {|h, k| h[k] = true; false}
- normalize_paths(paths).each do |load_path|
- Dir["#{load_path}/**/*_controller.rb"].collect do |path|
- next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
-
- controller_name = path[(load_path.length + 1)..-1]
-
- controller_name.gsub!(/_controller\.rb\Z/, '')
- @possible_controllers << controller_name
- end
- end
-
- # remove duplicates
- @possible_controllers.uniq!
- end
- @possible_controllers
- end
-
- # Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
- # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
- def use_controllers!(controller_names)
- @possible_controllers = controller_names
- end
-
- # Returns a controller path for a new +controller+ based on a +previous+ controller path.
- # Handles 4 scenarios:
- #
- # * stay in the previous controller:
- # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
- #
- # * stay in the previous namespace:
- # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
- #
- # * forced move to the root namespace:
- # controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
- #
- # * previous namespace is root:
- # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
- #
- def controller_relative_to(controller, previous)
- if controller.nil? then previous
- elsif controller[0] == ?/ then controller[1..-1]
- elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
- else controller
- end
- end
- end
-
- Routes = RouteSet.new
-
- ActiveSupport::Inflector.module_eval do
- # Ensures that routes are reloaded when Rails inflections are updated.
- def inflections_with_route_reloading(&block)
- returning(inflections_without_route_reloading(&block)) {
- ActionController::Routing::Routes.reload! if block_given?
- }
- end
-
- alias_method_chain :inflections, :route_reloading
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb
deleted file mode 100644
index 06506435a2..0000000000
--- a/actionpack/lib/action_controller/routing/resources.rb
+++ /dev/null
@@ -1,685 +0,0 @@
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/object/try'
-
-module ActionController
- # == Overview
- #
- # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
- # is something that can be pointed at and it will respond with a representation of the data requested.
- # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
- # requests XML data.
- #
- # RESTful design is based on the assumption that there are four generic verbs that a user of an
- # application can request from a \resource (the noun).
- #
- # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
- # denotes the type of action that should take place.
- #
- # === The Different Methods and their Usage
- #
- # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
- # * POST - Creation of \resources.
- # * PUT - Editing of attributes on a \resource.
- # * DELETE - Deletion of a \resource.
- #
- # === Examples
- #
- # # A GET request on the Posts resource is asking for all Posts
- # GET /posts
- #
- # # A GET request on a single Post resource is asking for that particular Post
- # GET /posts/1
- #
- # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
- # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
- #
- # # A PUT request on a single Post resource is asking for a Post to be updated
- # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
- #
- # # A DELETE request on a single Post resource is asking for it to be deleted
- # DELETE /posts # with => { :id => 1 }
- #
- # By using the REST convention, users of our application can assume certain things about how the data
- # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
- # supplying you with methods to create them in your routes.rb file.
- #
- # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
- module Resources
- INHERITABLE_OPTIONS = :namespace, :shallow
-
- class Resource #:nodoc:
- DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
-
- attr_reader :collection_methods, :member_methods, :new_methods
- attr_reader :path_prefix, :name_prefix, :path_segment
- attr_reader :plural, :singular
- attr_reader :options
-
- def initialize(entities, options)
- @plural ||= entities
- @singular ||= options[:singular] || plural.to_s.singularize
- @path_segment = options.delete(:as) || @plural
-
- @options = options
-
- arrange_actions
- add_default_actions
- set_allowed_actions
- set_prefixes
- end
-
- def controller
- @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
- end
-
- def requirements(with_id = false)
- @requirements ||= @options[:requirements] || {}
- @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
-
- with_id ? @requirements.merge(@id_requirement) : @requirements
- end
-
- def conditions
- @conditions ||= @options[:conditions] || {}
- end
-
- def path
- @path ||= "#{path_prefix}/#{path_segment}"
- end
-
- def new_path
- new_action = self.options[:path_names][:new] if self.options[:path_names]
- new_action ||= Base.resources_path_names[:new]
- @new_path ||= "#{path}/#{new_action}"
- end
-
- def shallow_path_prefix
- @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
- end
-
- def member_path
- @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
- end
-
- def nesting_path_prefix
- @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
- end
-
- def shallow_name_prefix
- @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
- end
-
- def nesting_name_prefix
- "#{shallow_name_prefix}#{singular}_"
- end
-
- def action_separator
- @action_separator ||= Base.resource_action_separator
- end
-
- def uncountable?
- @singular.to_s == @plural.to_s
- end
-
- def has_action?(action)
- !DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
- end
-
- protected
- def arrange_actions
- @collection_methods = arrange_actions_by_methods(options.delete(:collection))
- @member_methods = arrange_actions_by_methods(options.delete(:member))
- @new_methods = arrange_actions_by_methods(options.delete(:new))
- end
-
- def add_default_actions
- add_default_action(member_methods, :get, :edit)
- add_default_action(new_methods, :get, :new)
- end
-
- def set_allowed_actions
- only, except = @options.values_at(:only, :except)
- @allowed_actions ||= {}
-
- if only == :all || except == :none
- only = nil
- except = []
- elsif only == :none || except == :all
- only = []
- except = nil
- end
-
- if only
- @allowed_actions[:only] = Array(only).map {|a| a.to_sym }
- elsif except
- @allowed_actions[:except] = Array(except).map {|a| a.to_sym }
- end
- end
-
- def action_allowed?(action)
- only, except = @allowed_actions.values_at(:only, :except)
- (!only || only.include?(action)) && (!except || !except.include?(action))
- end
-
- def set_prefixes
- @path_prefix = options.delete(:path_prefix)
- @name_prefix = options.delete(:name_prefix)
- end
-
- def arrange_actions_by_methods(actions)
- (actions || {}).inject({}) do |flipped_hash, (key, value)|
- (flipped_hash[value] ||= []) << key
- flipped_hash
- end
- end
-
- def add_default_action(collection, method, action)
- (collection[method] ||= []).unshift(action)
- end
- end
-
- class SingletonResource < Resource #:nodoc:
- def initialize(entity, options)
- @singular = @plural = entity
- options[:controller] ||= @singular.to_s.pluralize
- super
- end
-
- alias_method :shallow_path_prefix, :path_prefix
- alias_method :shallow_name_prefix, :name_prefix
- alias_method :member_path, :path
- alias_method :nesting_path_prefix, :path
- end
-
- # Creates named routes for implementing verb-oriented controllers
- # for a collection \resource.
- #
- # For example:
- #
- # map.resources :messages
- #
- # will map the following actions in the corresponding controller:
- #
- # class MessagesController < ActionController::Base
- # # GET messages_url
- # def index
- # # return all messages
- # end
- #
- # # GET new_message_url
- # def new
- # # return an HTML form for describing a new message
- # end
- #
- # # POST messages_url
- # def create
- # # create a new message
- # end
- #
- # # GET message_url(:id => 1)
- # def show
- # # find and return a specific message
- # end
- #
- # # GET edit_message_url(:id => 1)
- # def edit
- # # return an HTML form for editing a specific message
- # end
- #
- # # PUT message_url(:id => 1)
- # def update
- # # find and update a specific message
- # end
- #
- # # DELETE message_url(:id => 1)
- # def destroy
- # # delete a specific message
- # end
- # end
- #
- # Along with the routes themselves, +resources+ generates named routes for use in
- # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
- #
- # Named Route Helpers
- # ============ =====================================================
- # messages messages_url, hash_for_messages_url,
- # messages_path, hash_for_messages_path
- #
- # message message_url(id), hash_for_message_url(id),
- # message_path(id), hash_for_message_path(id)
- #
- # new_message new_message_url, hash_for_new_message_url,
- # new_message_path, hash_for_new_message_path
- #
- # edit_message edit_message_url(id), hash_for_edit_message_url(id),
- # edit_message_path(id), hash_for_edit_message_path(id)
- #
- # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
- #
- # redirect_to :controller => 'messages', :action => 'index'
- # # and
- # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
- #
- # now become:
- #
- # redirect_to messages_url
- # # and
- # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
- #
- # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
- # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object:
- #
- # <%= form_tag message_path(@message), :method => :put %>
- #
- # or
- #
- # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
- #
- # or
- #
- # <% form_for @message do |f| %>
- #
- # which takes into account whether <tt>@message</tt> is a new record or not and generates the
- # path and method accordingly.
- #
- # The +resources+ method accepts the following options to customize the resulting routes:
- # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
- # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
- # an array of any of the previous, or <tt>:any</tt> if the method does not matter.
- # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
- # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
- # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
- # * <tt>:controller</tt> - Specify the controller name for the routes.
- # * <tt>:singular</tt> - Specify the singular name used in the member routes.
- # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
- # regular expressions (which must match for the route to match) or extra parameters. For example:
- #
- # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
- #
- # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
- # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
- # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
- # # products_path == '/productos'
- # map.resources :products, :as => 'productos' do |product|
- # # product_reviews_path(product) == '/productos/1234/comentarios'
- # product.resources :product_reviews, :as => 'comentarios'
- # end
- #
- # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
- # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
- #
- # You may directly specify the routing association with +has_one+ and +has_many+ like:
- #
- # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
- #
- # This is the same as:
- #
- # map.resources :notes do |notes|
- # notes.resource :author
- # notes.resources :comments
- # notes.resources :attachments
- # end
- #
- # * <tt>:path_names</tt> - Specify different path names for the actions. For example:
- # # new_products_path == '/productos/nuevo'
- # # bids_product_path(1) == '/productos/1/licitacoes'
- # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
- #
- # You can also set default action names from an environment, like this:
- # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
- #
- # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
- #
- # Weblog comments usually belong to a post, so you might use +resources+ like:
- #
- # map.resources :articles
- # map.resources :comments, :path_prefix => '/articles/:article_id'
- #
- # You can nest +resources+ calls to set this automatically:
- #
- # map.resources :articles do |article|
- # article.resources :comments
- # end
- #
- # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
- #
- # article_comments_url(@article)
- # article_comment_url(@article, @comment)
- #
- # article_comments_url(:article_id => @article)
- # article_comment_url(:article_id => @article, :id => @comment)
- #
- # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
- #
- # articles_comments_url(@comment.article_id, @comment)
- #
- # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
- # Use this if you have named routes that may clash.
- #
- # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
- # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
- #
- # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
- #
- # map.resources :articles do |article|
- # article.resources :comments, :name_prefix => nil
- # end
- #
- # This will yield named \resources like so:
- #
- # comments_url(@article)
- # comment_url(@article, @comment)
- #
- # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
- # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
- #
- # The <tt>:shallow</tt> option is inherited by any nested resource(s).
- #
- # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
- #
- # map.resources :users, :shallow => true do |user|
- # user.resources :posts do |post|
- # post.resources :comments
- # end
- # end
- # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
- # # also adds the usual named route called "user_posts"
- # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
- # # also adds the named route called "post"
- # # --> GET /posts/2/comments (maps to the CommentsController#index action)
- # # also adds the named route called "post_comments"
- # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
- # # also adds the named route called "comment"
- #
- # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
- #
- # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
- #
- # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
- #
- # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
- # list of action names. By default, routes are generated for all seven actions.
- #
- # For example:
- #
- # map.resources :posts, :only => [:index, :show] do |post|
- # post.resources :comments, :except => [:update, :destroy]
- # end
- # # --> GET /posts (maps to the PostsController#index action)
- # # --> POST /posts (fails)
- # # --> GET /posts/1 (maps to the PostsController#show action)
- # # --> DELETE /posts/1 (fails)
- # # --> POST /posts/1/comments (maps to the CommentsController#create action)
- # # --> PUT /posts/1/comments/1 (fails)
- #
- # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
- #
- # Examples:
- #
- # map.resources :messages, :path_prefix => "/thread/:thread_id"
- # # --> GET /thread/7/messages/1
- #
- # map.resources :messages, :collection => { :rss => :get }
- # # --> GET /messages/rss (maps to the #rss action)
- # # also adds a named route called "rss_messages"
- #
- # map.resources :messages, :member => { :mark => :post }
- # # --> POST /messages/1/mark (maps to the #mark action)
- # # also adds a named route called "mark_message"
- #
- # map.resources :messages, :new => { :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- #
- # map.resources :messages, :new => { :new => :any, :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- # # --> /messages/new can be invoked via any request method
- #
- # map.resources :messages, :controller => "categories",
- # :path_prefix => "/category/:category_id",
- # :name_prefix => "category_"
- # # --> GET /categories/7/messages/1
- # # has named route "category_message"
- #
- # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
- # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
- # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
- def resources(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_resource(entity, options.dup, &block) }
- end
-
- # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
- # A singleton \resource is global to its current context. For unnested singleton \resources,
- # the \resource is global to the current user visiting the application, such as a user's
- # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
- # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
- # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
- #
- # map.resources :projects do |project|
- # project.resource :project_manager
- # end
- #
- # See +resources+ for general conventions. These are the main differences:
- # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
- # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
- # * No default index route is created for the singleton \resource controller.
- # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
- #
- # For example:
- #
- # map.resource :account
- #
- # maps these actions in the Accounts controller:
- #
- # class AccountsController < ActionController::Base
- # # GET new_account_url
- # def new
- # # return an HTML form for describing the new account
- # end
- #
- # # POST account_url
- # def create
- # # create an account
- # end
- #
- # # GET account_url
- # def show
- # # find and return the account
- # end
- #
- # # GET edit_account_url
- # def edit
- # # return an HTML form for editing the account
- # end
- #
- # # PUT account_url
- # def update
- # # find and update the account
- # end
- #
- # # DELETE account_url
- # def destroy
- # # delete the account
- # end
- # end
- #
- # Along with the routes themselves, +resource+ generates named routes for
- # use in controllers and views. <tt>map.resource :account</tt> produces
- # these named routes and helpers:
- #
- # Named Route Helpers
- # ============ =============================================
- # account account_url, hash_for_account_url,
- # account_path, hash_for_account_path
- #
- # new_account new_account_url, hash_for_new_account_url,
- # new_account_path, hash_for_new_account_path
- #
- # edit_account edit_account_url, hash_for_edit_account_url,
- # edit_account_path, hash_for_edit_account_path
- def resource(*entities, &block)
- options = entities.extract_options!
- entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
- end
-
- private
- def map_resource(entities, options = {}, &block)
- resource = Resource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
-
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
-
- map_collection_actions(map, resource)
- map_default_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- end
- end
-
- def map_singleton_resource(entities, options = {}, &block)
- resource = SingletonResource.new(entities, options)
-
- with_options :controller => resource.controller do |map|
- map_associations(resource, options)
-
- if block_given?
- with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
- end
-
- map_collection_actions(map, resource)
- map_new_actions(map, resource)
- map_member_actions(map, resource)
- map_default_singleton_actions(map, resource)
- end
- end
-
- def map_associations(resource, options)
- map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
-
- path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
- name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
-
- Array(options[:has_one]).each do |association|
- resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
- end
- end
-
- def map_has_many_associations(resource, associations, options)
- case associations
- when Hash
- associations.each do |association,has_many|
- map_has_many_associations(resource, association, options.merge(:has_many => has_many))
- end
- when Array
- associations.each do |association|
- map_has_many_associations(resource, association, options)
- end
- when Symbol, String
- resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
- else
- end
- end
-
- def map_collection_actions(map, resource)
- resource.collection_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= action
-
- map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
- end
- end
- end
- end
-
- def map_default_collection_actions(map, resource)
- index_route_name = "#{resource.name_prefix}#{resource.plural}"
-
- if resource.uncountable?
- index_route_name << "_index"
- end
-
- map_resource_routes(map, resource, :index, resource.path, index_route_name)
- map_resource_routes(map, resource, :create, resource.path, index_route_name)
- end
-
- def map_default_singleton_actions(map, resource)
- map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
- end
-
- def map_new_actions(map, resource)
- resource.new_methods.each do |method, actions|
- actions.each do |action|
- route_path = resource.new_path
- route_name = "new_#{resource.name_prefix}#{resource.singular}"
-
- unless action == :new
- route_path = "#{route_path}#{resource.action_separator}#{action}"
- route_name = "#{action}_#{route_name}"
- end
-
- map_resource_routes(map, resource, action, route_path, route_name, method)
- end
- end
- end
-
- def map_member_actions(map, resource)
- resource.member_methods.each do |method, actions|
- actions.each do |action|
- [method].flatten.each do |m|
- action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
- action_path ||= Base.resources_path_names[action] || action
-
- map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
- end
- end
- end
-
- route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
- map_resource_routes(map, resource, :show, resource.member_path, route_path)
- map_resource_routes(map, resource, :update, resource.member_path, route_path)
- map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
- end
-
- def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
- if resource.has_action?(action)
- action_options = action_options_for(action, resource, method, resource_options)
- formatted_route_path = "#{route_path}.:format"
-
- if route_name && @set.named_routes[route_name.to_sym].nil?
- map.named_route(route_name, formatted_route_path, action_options)
- else
- map.connect(formatted_route_path, action_options)
- end
- end
- end
-
- def add_conditions_for(conditions, method)
- returning({:conditions => conditions.dup}) do |options|
- options[:conditions][:method] = method unless method == :any
- end
- end
-
- def action_options_for(action, resource, method = nil, resource_options = {})
- default_options = { :action => action.to_s }
- require_id = !resource.kind_of?(SingletonResource)
- force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
-
- case default_options[:action]
- when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
- when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
- when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
- when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
- when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
- else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
deleted file mode 100644
index 8135b5811e..0000000000
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ /dev/null
@@ -1,699 +0,0 @@
-require 'rack/mount'
-require 'forwardable'
-
-module ActionController
- module Routing
- class RouteSet #:nodoc:
- NotFound = lambda { |env|
- raise RoutingError, "No route matches #{env[::Rack::Mount::Const::PATH_INFO].inspect} with #{env.inspect}"
- }
-
- PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
-
- class Dispatcher
- def initialize(options = {})
- defaults = options[:defaults]
- @glob_param = options.delete(:glob)
- end
-
- def call(env)
- params = env[PARAMETERS_KEY]
- merge_default_action!(params)
- split_glob_param!(params) if @glob_param
- params.each { |key, value| params[key] = URI.unescape(value) if value.is_a?(String) }
-
- if env['action_controller.recognize']
- [200, {}, params]
- else
- controller = controller(params)
- controller.action(params[:action]).call(env)
- end
- end
-
- private
- def controller(params)
- if params && params.has_key?(:controller)
- controller = "#{params[:controller].camelize}Controller"
- ActiveSupport::Inflector.constantize(controller)
- end
- end
-
- def merge_default_action!(params)
- params[:action] ||= 'index'
- end
-
- def split_glob_param!(params)
- params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
- end
- end
-
- module RouteExtensions
- def segment_keys
- conditions[:path_info].names.compact.map { |key| key.to_sym }
- end
- end
-
- # Mapper instances are used to build routes. The object passed to the draw
- # block in config/routes.rb is a Mapper instance.
- #
- # Mapper instances have relatively few instance methods, in order to avoid
- # clashes with named routes.
- class Mapper #:doc:
- include ActionController::Resources
-
- def initialize(set) #:nodoc:
- @set = set
- end
-
- # Create an unnamed route with the provided +path+ and +options+. See
- # ActionController::Routing for an introduction to routes.
- def connect(path, options = {})
- @set.add_route(path, options)
- end
-
- # Creates a named route called "root" for matching the root level request.
- def root(options = {})
- if options.is_a?(Symbol)
- if source_route = @set.named_routes.routes[options]
- options = source_route.defaults.merge({ :conditions => source_route.conditions })
- end
- end
- named_route("root", '', options)
- end
-
- def named_route(name, path, options = {}) #:nodoc:
- @set.add_named_route(name, path, options)
- end
-
- # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
- # Example:
- #
- # map.namespace(:admin) do |admin|
- # admin.resources :products,
- # :has_many => [ :tags, :images, :variants ]
- # end
- #
- # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
- # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
- # Admin::TagsController.
- def namespace(name, options = {}, &block)
- if options[:namespace]
- with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
- else
- with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
- end
- end
-
- def method_missing(route_name, *args, &proc) #:nodoc:
- super unless args.length >= 1 && proc.nil?
- @set.add_named_route(route_name, *args)
- end
- end
-
- # A NamedRouteCollection instance is a collection of named routes, and also
- # maintains an anonymous module that can be used to install helpers for the
- # named routes.
- class NamedRouteCollection #:nodoc:
- include Enumerable
- attr_reader :routes, :helpers
-
- def initialize
- clear!
- end
-
- def clear!
- @routes = {}
- @helpers = []
-
- @module ||= Module.new
- @module.instance_methods.each do |selector|
- @module.class_eval { remove_method selector }
- end
- end
-
- def add(name, route)
- routes[name.to_sym] = route
- define_named_route_methods(name, route)
- end
-
- def get(name)
- routes[name.to_sym]
- end
-
- alias []= add
- alias [] get
- alias clear clear!
-
- def each
- routes.each { |name, route| yield name, route }
- self
- end
-
- def names
- routes.keys
- end
-
- def length
- routes.length
- end
-
- def reset!
- old_routes = routes.dup
- clear!
- old_routes.each do |name, route|
- add(name, route)
- end
- end
-
- def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
- reset! if regenerate
- Array(destinations).each do |dest|
- dest.__send__(:include, @module)
- end
- end
-
- private
- def url_helper_name(name, kind = :url)
- :"#{name}_#{kind}"
- end
-
- def hash_access_name(name, kind = :url)
- :"hash_for_#{name}_#{kind}"
- end
-
- def define_named_route_methods(name, route)
- {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
- hash = route.defaults.merge(:use_route => name).merge(opts)
- define_hash_access route, name, kind, hash
- define_url_helper route, name, kind, hash
- end
- end
-
- def named_helper_module_eval(code, *args)
- @module.module_eval(code, *args)
- end
-
- def define_hash_access(route, name, kind, options)
- selector = hash_access_name(name, kind)
- named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(options = nil) # def hash_for_users_url(options = nil)
- options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
- end # end
- protected :#{selector} # protected :hash_for_users_url
- end_eval
- helpers << selector
- end
-
- def define_url_helper(route, name, kind, options)
- selector = url_helper_name(name, kind)
- # The segment keys used for positional parameters
-
- hash_access_method = hash_access_name(name, kind)
-
- # allow ordered parameters to be associated with corresponding
- # dynamic segments, so you can do
- #
- # foo_url(bar, baz, bang)
- #
- # instead of
- #
- # foo_url(:bar => bar, :baz => baz, :bang => bang)
- #
- # Also allow options hash, so you can do
- #
- # foo_url(bar, baz, bang, :sort_by => 'baz')
- #
- named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
- def #{selector}(*args) # def users_url(*args)
- #
- opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
- args.first || {} # args.first || {}
- else # else
- options = args.extract_options! # options = args.extract_options!
- args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
- h[k] = v # h[k] = v
- h # h
- end # end
- options.merge(args) # options.merge(args)
- end # end
- #
- url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
- #
- end # end
- #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
- def formatted_#{selector}(*args) # def formatted_users_url(*args)
- ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
- "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
- "Please pass format to the standard " + # "Please pass format to the standard " +
- "#{selector} method instead.", caller) # "users_url method instead.", caller)
- #{selector}(*args) # users_url(*args)
- end # end
- protected :#{selector} # protected :users_url
- end_eval
- helpers << selector
- end
- end
-
- attr_accessor :routes, :named_routes, :configuration_files
-
- def initialize
- self.configuration_files = []
-
- self.routes = []
- self.named_routes = NamedRouteCollection.new
-
- clear!
- end
-
- def draw
- clear!
- yield Mapper.new(self)
- @set.add_route(NotFound)
- install_helpers
- @set.freeze
- end
-
- def clear!
- routes.clear
- named_routes.clear
- @set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY)
- end
-
- def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
- Array(destinations).each { |d| d.module_eval { include Helpers } }
- named_routes.install(destinations, regenerate_code)
- end
-
- def empty?
- routes.empty?
- end
-
- def add_configuration_file(path)
- self.configuration_files << path
- end
-
- # Deprecated accessor
- def configuration_file=(path)
- add_configuration_file(path)
- end
-
- # Deprecated accessor
- def configuration_file
- configuration_files
- end
-
- def load!
- Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones
- load_routes!
- end
-
- # reload! will always force a reload whereas load checks the timestamp first
- alias reload! load!
-
- def reload
- if configuration_files.any? && @routes_last_modified
- if routes_changed_at == @routes_last_modified
- return # routes didn't change, don't reload
- else
- @routes_last_modified = routes_changed_at
- end
- end
-
- load!
- end
-
- def load_routes!
- if configuration_files.any?
- configuration_files.each { |config| load(config) }
- @routes_last_modified = routes_changed_at
- else
- draw do |map|
- map.connect ":controller/:action/:id"
- end
- end
- end
-
- def routes_changed_at
- routes_changed_at = nil
-
- configuration_files.each do |config|
- config_changed_at = File.stat(config).mtime
-
- if routes_changed_at.nil? || config_changed_at > routes_changed_at
- routes_changed_at = config_changed_at
- end
- end
-
- routes_changed_at
- end
-
- def add_route(path, options = {})
- options = options.dup
-
- if conditions = options.delete(:conditions)
- conditions = conditions.dup
- method = [conditions.delete(:method)].flatten.compact
- method.map! { |m|
- m = m.to_s.upcase
-
- if m == "HEAD"
- raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
- end
-
- unless HTTP_METHODS.include?(m.downcase.to_sym)
- raise ArgumentError, "Invalid HTTP method specified in route conditions"
- end
-
- m
- }
-
- if method.length > 1
- method = Regexp.union(*method)
- elsif method.length == 1
- method = method.first
- else
- method = nil
- end
- end
-
- path_prefix = options.delete(:path_prefix)
- name_prefix = options.delete(:name_prefix)
- namespace = options.delete(:namespace)
-
- name = options.delete(:_name)
- name = "#{name_prefix}#{name}" if name_prefix
-
- requirements = options.delete(:requirements) || {}
- defaults = options.delete(:defaults) || {}
- options.each do |k, v|
- if v.is_a?(Regexp)
- if value = options.delete(k)
- requirements[k.to_sym] = value
- end
- else
- value = options.delete(k)
- defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
- end
- end
-
- requirements.each do |_, requirement|
- if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
- end
- if requirement.multiline?
- raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
- end
- end
-
- possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) }
- requirements[:controller] ||= Regexp.union(*possible_names)
-
- if defaults[:controller]
- defaults[:action] ||= 'index'
- defaults[:controller] = defaults[:controller].to_s
- defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
- end
-
- if defaults[:action]
- defaults[:action] = defaults[:action].to_s
- end
-
- if path.is_a?(String)
- path = "#{path_prefix}/#{path}" if path_prefix
- path = path.gsub('.:format', '(.:format)')
- path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
- glob = $1.to_sym if path =~ /\/\*(\w+)$/
- path = ::Rack::Mount::Utils.normalize_path(path)
- path = ::Rack::Mount::Strexp.compile(path, requirements, %w( / . ? ))
-
- if glob && !defaults[glob].blank?
- raise RoutingError, "paths cannot have non-empty default values"
- end
- end
-
- app = Dispatcher.new(:defaults => defaults, :glob => glob)
-
- conditions = {}
- conditions[:request_method] = method if method
- conditions[:path_info] = path if path
-
- route = @set.add_route(app, conditions, defaults, name)
- route.extend(RouteExtensions)
- routes << route
- route
- end
-
- def add_named_route(name, path, options = {})
- options[:_name] = name
- route = add_route(path, options)
- named_routes[route.name] = route
- route
- end
-
- def options_as_params(options)
- # If an explicit :controller was given, always make :action explicit
- # too, so that action expiry works as expected for things like
- #
- # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
- #
- # (the above is from the unit tests). In the above case, because the
- # controller was explicitly given, but no action, the action is implied to
- # be "index", not the recalled action of "show".
- #
- # great fun, eh?
-
- options_as_params = options.clone
- options_as_params[:action] ||= 'index' if options[:controller]
- options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
- options_as_params
- end
-
- def build_expiry(options, recall)
- recall.inject({}) do |expiry, (key, recalled_value)|
- expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
- expiry
- end
- end
-
- # Generate the path indicated by the arguments, and return an array of
- # the keys that were not used to generate it.
- def extra_keys(options, recall={})
- generate_extras(options, recall).last
- end
-
- def generate_extras(options, recall={})
- generate(options, recall, :generate_extras)
- end
-
- def generate(options, recall = {}, method = :generate)
- options, recall = options.dup, recall.dup
- named_route = options.delete(:use_route)
-
- options = options_as_params(options)
- expire_on = build_expiry(options, recall)
-
- recall[:action] ||= 'index' if options[:controller] || recall[:controller]
-
- if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller])
- options[:controller] = recall.delete(:controller)
-
- if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action])
- options[:action] = recall.delete(:action)
-
- if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id])
- options[:id] = recall.delete(:id)
- end
- end
- end
-
- options[:controller] = options[:controller].to_s if options[:controller]
-
- if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
- old_parts = recall[:controller].split('/')
- new_parts = options[:controller].split('/')
- parts = old_parts[0..-(new_parts.length + 1)] + new_parts
- options[:controller] = parts.join('/')
- end
-
- options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
-
- merged = options.merge(recall)
- if options.has_key?(:action) && options[:action].nil?
- options.delete(:action)
- recall[:action] = 'index'
- end
- recall[:action] = options.delete(:action) if options[:action] == 'index'
-
- path = _uri(named_route, options, recall)
- if path && method == :generate_extras
- uri = URI(path)
- extras = uri.query ?
- Rack::Utils.parse_nested_query(uri.query).keys.map { |k| k.to_sym } :
- []
- [uri.path, extras]
- elsif path
- path
- else
- raise RoutingError, "No route matches #{options.inspect}"
- end
- rescue Rack::Mount::RoutingError
- raise RoutingError, "No route matches #{options.inspect}"
- end
-
- def call(env)
- @set.call(env)
- rescue ActionController::RoutingError => e
- raise e if env['action_controller.rescue_error'] == false
-
- method, path = env['REQUEST_METHOD'].downcase.to_sym, env['PATH_INFO']
-
- # Route was not recognized. Try to find out why (maybe wrong verb).
- allows = HTTP_METHODS.select { |verb|
- begin
- recognize_path(path, {:method => verb}, false)
- rescue ActionController::RoutingError
- nil
- end
- }
-
- if !HTTP_METHODS.include?(method)
- raise NotImplemented.new(*allows)
- elsif !allows.empty?
- raise MethodNotAllowed.new(*allows)
- else
- raise e
- end
- end
-
- def recognize(request)
- params = recognize_path(request.path, extract_request_environment(request))
- request.path_parameters = params.with_indifferent_access
- "#{params[:controller].to_s.camelize}Controller".constantize
- end
-
- def recognize_path(path, environment = {}, rescue_error = true)
- method = (environment[:method] || "GET").to_s.upcase
-
- begin
- env = Rack::MockRequest.env_for(path, {:method => method})
- rescue URI::InvalidURIError => e
- raise RoutingError, e.message
- end
-
- env['action_controller.recognize'] = true
- env['action_controller.rescue_error'] = rescue_error
- status, headers, body = call(env)
- body
- end
-
- # Subclasses and plugins may override this method to extract further attributes
- # from the request, for use by route conditions and such.
- def extract_request_environment(request)
- { :method => request.method }
- end
-
- private
- def _uri(named_route, params, recall)
- params = URISegment.wrap_values(params)
- recall = URISegment.wrap_values(recall)
-
- unless result = @set.generate(:path_info, named_route, params, recall)
- return
- end
-
- uri, params = result
- params.each do |k, v|
- if v._value
- params[k] = v._value
- else
- params.delete(k)
- end
- end
-
- uri << "?#{Rack::Mount::Utils.build_nested_query(params)}" if uri && params.any?
- uri
- end
-
- class URISegment < Struct.new(:_value, :_escape)
- EXCLUDED = [:controller]
-
- def self.wrap_values(hash)
- hash.inject({}) { |h, (k, v)|
- h[k] = new(v, !EXCLUDED.include?(k.to_sym))
- h
- }
- end
-
- extend Forwardable
- def_delegators :_value, :==, :eql?, :hash
-
- def to_param
- @to_param ||= begin
- if _value.is_a?(Array)
- _value.map { |v| _escaped(v) }.join('/')
- else
- _escaped(_value)
- end
- end
- end
- alias_method :to_s, :to_param
-
- private
- def _escaped(value)
- v = value.respond_to?(:to_param) ? value.to_param : value
- _escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s
- end
- end
-
- def optionalize_trailing_dynamic_segments(path, requirements, defaults)
- path = (path =~ /^\//) ? path.dup : "/#{path}"
- optional, segments = true, []
-
- required_segments = requirements.keys
- required_segments -= defaults.keys.compact
-
- old_segments = path.split('/')
- old_segments.shift
- length = old_segments.length
-
- old_segments.reverse.each_with_index do |segment, index|
- required_segments.each do |required|
- if segment =~ /#{required}/
- optional = false
- break
- end
- end
-
- if optional
- if segment == ":id" && segments.include?(":action")
- optional = false
- elsif segment == ":controller" || segment == ":action" || segment == ":id"
- # Ignore
- elsif !(segment =~ /^:\w+$/) &&
- !(segment =~ /^:\w+\(\.:format\)$/)
- optional = false
- elsif segment =~ /^:(\w+)$/
- if defaults.has_key?($1.to_sym)
- defaults.delete($1.to_sym)
- else
- optional = false
- end
- end
- end
-
- if optional && index < length - 1
- segments.unshift('(/', segment)
- segments.push(')')
- elsif optional
- segments.unshift('/(', segment)
- segments.push(')')
- else
- segments.unshift('/', segment)
- end
- end
-
- segments.join
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
index 52b66c9303..52b66c9303 100644
--- a/actionpack/lib/action_controller/routing/url_rewriter.rb
+++ b/actionpack/lib/action_controller/url_rewriter.rb