mirror of
https://github.com/github/rails.git
synced 2026-01-12 08:08:31 -05:00
Compare commits
8 Commits
github27
...
router-bac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b75d7ea2c6 | ||
|
|
735c4e790d | ||
|
|
0e467e376b | ||
|
|
e3dafa5669 | ||
|
|
3297a4c446 | ||
|
|
d98e3f8489 | ||
|
|
9ee6c6c082 | ||
|
|
6d4d9dd919 |
@@ -280,7 +280,6 @@ module ActionMailer #:nodoc:
|
||||
class Base
|
||||
include AdvAttrAccessor, PartContainer, Quoting, Utils
|
||||
if Object.const_defined?(:ActionController)
|
||||
include ActionController::UrlWriter
|
||||
include ActionController::Layout
|
||||
end
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ spec = Gem::Specification.new do |s|
|
||||
|
||||
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
|
||||
s.add_dependency('erubis', '~> 2.7.0')
|
||||
s.add_dependency('rack', '~> 1.1')
|
||||
s.add_dependency('rack', '~> 1.1')
|
||||
s.add_dependency('rack-mount', '~> 0.8')
|
||||
|
||||
s.require_path = 'lib'
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ module ActionController
|
||||
# TODO: Review explicit to see if they will automatically be handled by
|
||||
# the initilizer if they are really needed.
|
||||
def self.load_all!
|
||||
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
|
||||
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter]
|
||||
end
|
||||
|
||||
autoload :Base, 'action_controller/base'
|
||||
@@ -57,7 +57,6 @@ module ActionController
|
||||
autoload :MiddlewareStack, 'action_controller/middleware_stack'
|
||||
autoload :MimeResponds, 'action_controller/mime_responds'
|
||||
autoload :ParamsParser, 'action_controller/params_parser'
|
||||
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
|
||||
autoload :RecordIdentifier, 'action_controller/record_identifier'
|
||||
autoload :Reloader, 'action_controller/reloader'
|
||||
autoload :Request, 'action_controller/request'
|
||||
@@ -78,7 +77,6 @@ module ActionController
|
||||
autoload :UploadedStringIO, 'action_controller/uploaded_file'
|
||||
autoload :UploadedTempfile, 'action_controller/uploaded_file'
|
||||
autoload :UrlRewriter, 'action_controller/url_rewriter'
|
||||
autoload :UrlWriter, 'action_controller/url_rewriter'
|
||||
autoload :Verification, 'action_controller/verification'
|
||||
|
||||
module Assertions
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require 'set'
|
||||
require 'action_controller/metal/url_for'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class ActionControllerError < StandardError #:nodoc:
|
||||
@@ -250,6 +251,7 @@ module ActionController #:nodoc:
|
||||
DEFAULT_RENDER_STATUS_CODE = "200 OK"
|
||||
|
||||
include StatusCodes
|
||||
include UrlFor
|
||||
|
||||
cattr_reader :protected_instance_variables
|
||||
# Controller specific instance variables which will not be accessible inside views.
|
||||
@@ -541,93 +543,6 @@ module ActionController #:nodoc:
|
||||
response
|
||||
end
|
||||
|
||||
# Returns a URL that has been rewritten according to the options hash and the defined routes.
|
||||
# (For doing a complete redirect, use +redirect_to+).
|
||||
#
|
||||
# <tt>url_for</tt> is used to:
|
||||
#
|
||||
# All keys given to +url_for+ are forwarded to the Route module, save for the following:
|
||||
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path. For example,
|
||||
# <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt>
|
||||
# will produce "/posts/show/10#comments".
|
||||
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default).
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
|
||||
# is currently not recommended since it breaks caching.
|
||||
# * <tt>:host</tt> - Overrides the default (current) host if provided.
|
||||
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
|
||||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
|
||||
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
|
||||
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the +relative_url_root+
|
||||
# of the request so the path will include the web server relative installation directory.
|
||||
#
|
||||
# The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
|
||||
# Routes composes a query string as the key/value pairs not included in the <base>.
|
||||
#
|
||||
# The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with
|
||||
# action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs:
|
||||
#
|
||||
# url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent'
|
||||
# url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts'
|
||||
# url_for :controller => 'posts', :action => 'index', :port=>'8033' # => 'proto://host.com:8033/posts'
|
||||
# url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10'
|
||||
# url_for :controller => 'posts', :user => 'd', :password => '123' # => 'proto://d:123@host.com/posts'
|
||||
#
|
||||
# When generating a new URL, missing values may be filled in from the current request's parameters. For example,
|
||||
# <tt>url_for :action => 'some_action'</tt> will retain the current controller, as expected. This behavior extends to
|
||||
# other parameters, including <tt>:controller</tt>, <tt>:id</tt>, and any other parameters that are placed into a Route's
|
||||
# path.
|
||||
#
|
||||
# The URL helpers such as <tt>url_for</tt> have a limited form of memory: when generating a new URL, they can look for
|
||||
# missing values in the current request's parameters. Routes attempts to guess when a value should and should not be
|
||||
# taken from the defaults. There are a few simple rules on how this is performed:
|
||||
#
|
||||
# * If the controller name begins with a slash no defaults are used:
|
||||
#
|
||||
# url_for :controller => '/home'
|
||||
#
|
||||
# In particular, a leading slash ensures no namespace is assumed. Thus,
|
||||
# while <tt>url_for :controller => 'users'</tt> may resolve to
|
||||
# <tt>Admin::UsersController</tt> if the current controller lives under
|
||||
# that module, <tt>url_for :controller => '/users'</tt> ensures you link
|
||||
# to <tt>::UsersController</tt> no matter what.
|
||||
# * If the controller changes, the action will default to index unless provided
|
||||
#
|
||||
# The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
|
||||
# route given by <tt>map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'</tt>.
|
||||
#
|
||||
# Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases of URLs which are generated
|
||||
# from this page.
|
||||
#
|
||||
# * <tt>url_for :action => 'bio'</tt> -- During the generation of this URL, default values will be used for the first and
|
||||
# last components, and the action shall change. The generated URL will be, "people/hh/david/bio".
|
||||
# * <tt>url_for :first => 'davids-little-brother'</tt> This generates the URL 'people/hh/davids-little-brother' -- note
|
||||
# that this URL leaves out the assumed action of 'bio'.
|
||||
#
|
||||
# However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The
|
||||
# answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the
|
||||
# value that appears in the slot for <tt>:first</tt> is not equal to default value for <tt>:first</tt> we stop using
|
||||
# defaults. On its own, this rule can account for much of the typical Rails URL behavior.
|
||||
#
|
||||
# Although a convenience, defaults can occasionally get in your way. In some cases a default persists longer than desired.
|
||||
# The default may be cleared by adding <tt>:name => nil</tt> to <tt>url_for</tt>'s options.
|
||||
# This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the
|
||||
# helper is used from. The following line will redirect to PostController's default action, regardless of the page it is
|
||||
# displayed on:
|
||||
#
|
||||
# url_for :controller => 'posts', :action => nil
|
||||
def url_for(options = {})
|
||||
options ||= {}
|
||||
case options
|
||||
when String
|
||||
options
|
||||
when Hash
|
||||
@url.rewrite(rewrite_options(options))
|
||||
else
|
||||
polymorphic_url(options)
|
||||
end
|
||||
end
|
||||
|
||||
# Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
|
||||
def controller_class_name
|
||||
self.class.controller_class_name
|
||||
@@ -1042,27 +957,6 @@ module ActionController #:nodoc:
|
||||
erase_redirect_results
|
||||
end
|
||||
|
||||
def rewrite_options(options) #:nodoc:
|
||||
if defaults = default_url_options(options)
|
||||
defaults.merge(options)
|
||||
else
|
||||
options
|
||||
end
|
||||
end
|
||||
|
||||
# Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
|
||||
# the form of a hash, just like the one you would use for url_for directly. Example:
|
||||
#
|
||||
# def default_url_options(options)
|
||||
# { :project => @project.active? ? @project.url_name : "unknown" }
|
||||
# end
|
||||
#
|
||||
# As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
|
||||
# urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
|
||||
# by this method.
|
||||
def default_url_options(options = nil)
|
||||
end
|
||||
|
||||
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
|
||||
#
|
||||
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
||||
@@ -1281,6 +1175,7 @@ module ActionController #:nodoc:
|
||||
|
||||
def initialize_template_class(response)
|
||||
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
|
||||
response.template.helpers.send :include, ActionController::Routing::Routes.url_helpers
|
||||
response.template.helpers.send :include, self.class.master_helper_module
|
||||
response.redirected_to = nil
|
||||
@performed_render = @performed_redirect = false
|
||||
@@ -1370,7 +1265,8 @@ module ActionController #:nodoc:
|
||||
# Be sure to include shadowed public instance methods of this class
|
||||
public_instance_methods(false).map { |m| m.to_s } -
|
||||
# And always exclude explicitly hidden actions
|
||||
hidden_actions
|
||||
hidden_actions -
|
||||
_routes.named_routes.helper_names
|
||||
end
|
||||
|
||||
def reset_variables_added_to_assigns
|
||||
|
||||
@@ -55,7 +55,7 @@ module ActionController
|
||||
# Run prepare callbacks before every request in development mode
|
||||
run_prepare_callbacks
|
||||
|
||||
Routing::Routes.reload
|
||||
ActionController::Routing.routes_reloader.execute_if_updated
|
||||
end
|
||||
|
||||
def cleanup_application
|
||||
|
||||
@@ -130,7 +130,7 @@ module ActionController
|
||||
# performed on the location header.
|
||||
def follow_redirect!
|
||||
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
|
||||
get(interpret_uri(headers['location']))
|
||||
get(headers['location'])
|
||||
status
|
||||
end
|
||||
|
||||
@@ -256,14 +256,15 @@ module ActionController
|
||||
|
||||
# Performs the actual request.
|
||||
def process(method, path, parameters = nil, headers = nil)
|
||||
data = requestify(parameters)
|
||||
data = requestify(parameters) if !parameters.blank?
|
||||
path = interpret_uri(path) if path =~ %r{://}
|
||||
path = "/#{path}" unless path[0] == ?/
|
||||
path, query = path.split('?', 2)
|
||||
@path = path
|
||||
env = {}
|
||||
|
||||
if method == :get
|
||||
env["QUERY_STRING"] = data
|
||||
env["QUERY_STRING"] = query || data
|
||||
data = nil
|
||||
end
|
||||
|
||||
|
||||
22
actionpack/lib/action_controller/metal/url_for.rb
Normal file
22
actionpack/lib/action_controller/metal/url_for.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
require 'active_support/concern'
|
||||
|
||||
module ActionController
|
||||
module UrlFor
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include ActionController::Routing::UrlFor
|
||||
|
||||
def url_options
|
||||
super.reverse_merge(
|
||||
:host => request.host_with_port,
|
||||
:protocol => request.protocol,
|
||||
:_path_segments => request.symbolized_path_parameters
|
||||
).merge(:script_name => request.script_name)
|
||||
end
|
||||
|
||||
def _routes
|
||||
raise "In order to use #url_for, you must include routing helpers explicitly. " \
|
||||
"For instance, `include Rails.application.routes.url_helpers"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,189 +0,0 @@
|
||||
module ActionController
|
||||
# Polymorphic URL helpers are methods for smart resolution to a named route call when
|
||||
# given an Active Record model instance. They are to be used in combination with
|
||||
# ActionController::Resources.
|
||||
#
|
||||
# These methods are useful when you want to generate correct URL or path to a RESTful
|
||||
# resource without having to know the exact type of the record in question.
|
||||
#
|
||||
# Nested resources and/or namespaces are also supported, as illustrated in the example:
|
||||
#
|
||||
# polymorphic_url([:admin, @article, @comment])
|
||||
#
|
||||
# results in:
|
||||
#
|
||||
# admin_article_comment_url(@article, @comment)
|
||||
#
|
||||
# == Usage within the framework
|
||||
#
|
||||
# Polymorphic URL helpers are used in a number of places throughout the Rails framework:
|
||||
#
|
||||
# * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
|
||||
# <tt>url_for(@article)</tt>;
|
||||
# * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
|
||||
# <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
|
||||
# action;
|
||||
# * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
|
||||
# <tt>redirect_to(post)</tt> in your controllers;
|
||||
# * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
|
||||
# for feed entries.
|
||||
#
|
||||
# == Prefixed polymorphic helpers
|
||||
#
|
||||
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
|
||||
# number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
|
||||
# in options. Those are:
|
||||
#
|
||||
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
|
||||
# * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# edit_polymorphic_path(@post) # => "/posts/1/edit"
|
||||
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
|
||||
module PolymorphicRoutes
|
||||
# Constructs a call to a named RESTful route for the given record and returns the
|
||||
# resulting URL string. For example:
|
||||
#
|
||||
# # calls post_url(post)
|
||||
# polymorphic_url(post) # => "http://example.com/posts/1"
|
||||
# polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
|
||||
# polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
|
||||
# polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:action</tt> - Specifies the action prefix for the named route:
|
||||
# <tt>:new</tt> or <tt>:edit</tt>. Default is no prefix.
|
||||
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
|
||||
# Default is <tt>:url</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # an Article record
|
||||
# polymorphic_url(record) # same as article_url(record)
|
||||
#
|
||||
# # a Comment record
|
||||
# polymorphic_url(record) # same as comment_url(record)
|
||||
#
|
||||
# # it recognizes new records and maps to the collection
|
||||
# record = Comment.new
|
||||
# polymorphic_url(record) # same as comments_url()
|
||||
#
|
||||
def polymorphic_url(record_or_hash_or_array, options = {})
|
||||
if record_or_hash_or_array.kind_of?(Array)
|
||||
record_or_hash_or_array = record_or_hash_or_array.compact
|
||||
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
|
||||
end
|
||||
|
||||
record = extract_record(record_or_hash_or_array)
|
||||
|
||||
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
|
||||
|
||||
inflection =
|
||||
case
|
||||
when options[:action].to_s == "new"
|
||||
args.pop
|
||||
:singular
|
||||
when record.respond_to?(:new_record?) && record.new_record?
|
||||
args.pop
|
||||
:plural
|
||||
else
|
||||
:singular
|
||||
end
|
||||
|
||||
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
|
||||
named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
|
||||
|
||||
url_options = options.except(:action, :routing_type)
|
||||
unless url_options.empty?
|
||||
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
|
||||
end
|
||||
|
||||
__send__(named_route, *args)
|
||||
end
|
||||
|
||||
# Returns the path component of a URL for the given record. It uses
|
||||
# <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
|
||||
def polymorphic_path(record_or_hash_or_array, options = {})
|
||||
options[:routing_type] = :path
|
||||
polymorphic_url(record_or_hash_or_array, options)
|
||||
end
|
||||
|
||||
%w(edit new).each do |action|
|
||||
module_eval <<-EOT, __FILE__, __LINE__ + 1
|
||||
def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
|
||||
polymorphic_url( # polymorphic_url(
|
||||
record_or_hash, # record_or_hash,
|
||||
options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
|
||||
end # end
|
||||
#
|
||||
def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
|
||||
polymorphic_url( # polymorphic_url(
|
||||
record_or_hash, # record_or_hash,
|
||||
options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
|
||||
end # end
|
||||
EOT
|
||||
end
|
||||
|
||||
def formatted_polymorphic_url(record_or_hash, options = {})
|
||||
ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller)
|
||||
options[:format] = record_or_hash.pop if Array === record_or_hash
|
||||
polymorphic_url(record_or_hash, options)
|
||||
end
|
||||
|
||||
def formatted_polymorphic_path(record_or_hash, options = {})
|
||||
ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller)
|
||||
options[:format] = record_or_hash.pop if record_or_hash === Array
|
||||
polymorphic_url(record_or_hash, options.merge(:routing_type => :path))
|
||||
end
|
||||
|
||||
private
|
||||
def action_prefix(options)
|
||||
options[:action] ? "#{options[:action]}_" : ''
|
||||
end
|
||||
|
||||
def routing_type(options)
|
||||
options[:routing_type] || :url
|
||||
end
|
||||
|
||||
def build_named_route_call(records, inflection, options = {})
|
||||
unless records.is_a?(Array)
|
||||
record = extract_record(records)
|
||||
route = ''
|
||||
else
|
||||
record = records.pop
|
||||
route = records.inject("") do |string, parent|
|
||||
if parent.is_a?(Symbol) || parent.is_a?(String)
|
||||
string << "#{parent}_"
|
||||
else
|
||||
string << RecordIdentifier.__send__("plural_class_name", parent).singularize
|
||||
string << "_"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if record.is_a?(Symbol) || record.is_a?(String)
|
||||
route << "#{record}_"
|
||||
else
|
||||
route << RecordIdentifier.__send__("plural_class_name", record)
|
||||
route = route.singularize if inflection == :singular
|
||||
route << "_"
|
||||
end
|
||||
|
||||
action_prefix(options) + route + routing_type(options).to_s
|
||||
end
|
||||
|
||||
def extract_record(record_or_hash_or_array)
|
||||
case record_or_hash_or_array
|
||||
when Array; record_or_hash_or_array.last
|
||||
when Hash; record_or_hash_or_array[:id]
|
||||
else record_or_hash_or_array
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -35,6 +35,10 @@ module ActionController
|
||||
@request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
|
||||
end
|
||||
|
||||
def request_method_string
|
||||
@request_method_string ||= @env['REQUEST_METHOD']
|
||||
end
|
||||
|
||||
# Returns the HTTP request \method used for action processing as a
|
||||
# lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
|
||||
# method returns <tt>:get</tt> for a HEAD request because the two are
|
||||
@@ -332,6 +336,10 @@ EOM
|
||||
parts[0..-(tld_length+2)]
|
||||
end
|
||||
|
||||
def subdomain(tld_length = 1)
|
||||
subdomains(tld_length).join('.')
|
||||
end
|
||||
|
||||
# Returns the query string, accounting for server idiosyncrasies.
|
||||
def query_string
|
||||
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
|
||||
|
||||
@@ -1,42 +1,17 @@
|
||||
require 'cgi'
|
||||
require 'uri'
|
||||
require 'action_controller/routing/optimisations'
|
||||
require 'action_controller/routing/routing_ext'
|
||||
require 'action_controller/routing/route'
|
||||
require 'action_controller/routing/segments'
|
||||
require 'action_controller/routing/builder'
|
||||
require 'action_controller/routing/route_set'
|
||||
require 'action_controller/routing/recognition_optimisation'
|
||||
require 'active_support/core_ext/object/to_param'
|
||||
require 'active_support/core_ext/regexp'
|
||||
require 'active_support/file_update_checker'
|
||||
|
||||
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.
|
||||
# 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|
|
||||
# AppName::Application.routes.draw do
|
||||
# Pattern 1 tells some request to go to one place
|
||||
# Pattern 2 tell them to go to another
|
||||
# ...
|
||||
@@ -49,60 +24,49 @@ module ActionController
|
||||
#
|
||||
# Other names simply map to a parameter as in the case of <tt>:id</tt>.
|
||||
#
|
||||
# == Route priority
|
||||
# == Resources
|
||||
#
|
||||
# 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.
|
||||
# Resource routing allows you to quickly declare all of the common routes
|
||||
# for a given resourceful controller. Instead of declaring separate routes
|
||||
# for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+
|
||||
# actions, a resourceful route declares them in a single line of code:
|
||||
#
|
||||
# Within blocks, the empty pattern is at the highest priority.
|
||||
# In practice this works out nicely:
|
||||
# resources :photos
|
||||
#
|
||||
# ActionController::Routing::Routes.draw do |map|
|
||||
# map.with_options :controller => 'blog' do |blog|
|
||||
# blog.show '', :action => 'list'
|
||||
# end
|
||||
# map.connect ':controller/:action/:view'
|
||||
# Sometimes, you have a resource that clients always look up without
|
||||
# referencing an ID. A common example, /profile always shows the profile of
|
||||
# the currently logged in user. In this case, you can use a singular resource
|
||||
# to map /profile (rather than /profile/:id) to the show action.
|
||||
#
|
||||
# resource :profile
|
||||
#
|
||||
# It's common to have resources that are logically children of other
|
||||
# resources:
|
||||
#
|
||||
# resources :magazines do
|
||||
# resources :ads
|
||||
# end
|
||||
#
|
||||
# In this case, invoking blog controller (with an URL like '/blog/')
|
||||
# without parameters will activate the 'list' action by default.
|
||||
# You may wish to organize groups of controllers under a namespace. Most
|
||||
# commonly, you might group a number of administrative controllers under
|
||||
# an +admin+ namespace. You would place these controllers under the
|
||||
# app/controllers/admin directory, and you can group them together in your
|
||||
# router:
|
||||
#
|
||||
# == 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'
|
||||
# namespace "admin" do
|
||||
# resources :posts, :comments
|
||||
# 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>,
|
||||
# Routes can be named by passing an <tt>:as</tt> option,
|
||||
# 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'
|
||||
# match '/login' => 'accounts#login', :as => 'login'
|
||||
#
|
||||
# # With render, redirect_to, tests, etc.
|
||||
# redirect_to login_url
|
||||
@@ -111,32 +75,26 @@ module ActionController
|
||||
#
|
||||
# redirect_to show_item_path(:id => 25)
|
||||
#
|
||||
# Use <tt>map.root</tt> as a shorthand to name a route for the root path "".
|
||||
# Use <tt>root</tt> as a shorthand to name a route for the root path "/".
|
||||
#
|
||||
# # In routes.rb
|
||||
# map.root :controller => 'blogs'
|
||||
# root :to => 'blogs#index'
|
||||
#
|
||||
# # 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 # => ''
|
||||
# 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
|
||||
# Note: when using +controller+, 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'
|
||||
# controller :blog do
|
||||
# match 'blog/show' => :list
|
||||
# match 'blog/delete' => :delete
|
||||
# match 'blog/edit/:id' => :edit
|
||||
# end
|
||||
#
|
||||
# # provides named routes for show, delete, and edit
|
||||
@@ -146,12 +104,11 @@ module ActionController
|
||||
#
|
||||
# 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}/
|
||||
# match '/articles/:year/:month/:day' => 'articles#find_by_id', :constraints => {
|
||||
# :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
|
||||
@@ -161,64 +118,105 @@ module ActionController
|
||||
# == 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})?/
|
||||
# controller 'geocode' do
|
||||
# match 'geocode/:postalcode' => :show, :constraints => {
|
||||
# :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
|
||||
# Constraints 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
|
||||
# controller 'geocode' do
|
||||
# match 'geocode/:postalcode' => :show, :constraints => {
|
||||
# :postalcode => /hx\d\d\s\d[a-z]{2}/i
|
||||
# }
|
||||
# end
|
||||
#
|
||||
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
|
||||
# :action => 'show',:requirements => {
|
||||
# :postalcode => /# Postcode format
|
||||
# \d{5} #Prefix
|
||||
# (-\d{4})? #Suffix
|
||||
# /x
|
||||
# }
|
||||
# controller 'geocode' do
|
||||
# match 'geocode/:postalcode' => :show, :constraints => {
|
||||
# :postalcode => /# Postcode format
|
||||
# \d{5} #Prefix
|
||||
# (-\d{4})? #Suffix
|
||||
# /x
|
||||
# }
|
||||
# end
|
||||
#
|
||||
# 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
|
||||
# == Default route
|
||||
#
|
||||
# Specifying <tt>*[string]</tt> as part of a rule like:
|
||||
# Consider the following route, which you will find commented out at the
|
||||
# bottom of your generated <tt>config/routes.rb</tt>:
|
||||
#
|
||||
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
|
||||
# match ':controller(/:action(/:id(.:format)))'
|
||||
#
|
||||
# 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.
|
||||
# This route states that it expects requests to consist of a
|
||||
# <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
|
||||
# turn is followed optionally by an <tt>:id</tt>, which in turn is followed
|
||||
# optionally by a <tt>:format</tt>.
|
||||
#
|
||||
# == Route conditions
|
||||
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
|
||||
# up with:
|
||||
#
|
||||
# With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
|
||||
# params = { :controller => 'blog',
|
||||
# :action => 'edit',
|
||||
# :id => '22'
|
||||
# }
|
||||
#
|
||||
# * <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.
|
||||
# By not relying on default routes, you improve the security of your
|
||||
# application since not all controller actions, which includes actions you
|
||||
# might add at a later time, are exposed by default.
|
||||
#
|
||||
# Example:
|
||||
# == HTTP Methods
|
||||
#
|
||||
# map.connect 'post/:id', :controller => 'posts', :action => 'show',
|
||||
# :conditions => { :method => :get }
|
||||
# map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
|
||||
# :conditions => { :method => :post }
|
||||
# Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
|
||||
# Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
|
||||
# If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
|
||||
# The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# match 'post/:id' => 'posts#show', :via => :get
|
||||
# match 'post/:id' => "posts#create_comment', :via => :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.
|
||||
#
|
||||
# === HTTP helper methods
|
||||
#
|
||||
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
|
||||
# methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# get 'post/:id' => 'posts#show'
|
||||
# post 'post/:id' => "posts#create_comment'
|
||||
#
|
||||
# This syntax is less verbose and the intention is more apparent to someone else reading your code,
|
||||
# however if your route needs to respond to more than one HTTP method (or all methods) then using the
|
||||
# <tt>:via</tt> option on <tt>match</tt> is preferable.
|
||||
#
|
||||
# == External redirects
|
||||
#
|
||||
# You can redirect any path to another path using the redirect helper in your router:
|
||||
#
|
||||
# match "/stories" => redirect("/posts")
|
||||
#
|
||||
# == Routing to Rack Applications
|
||||
#
|
||||
# Instead of a String, like <tt>posts#index</tt>, which corresponds to the
|
||||
# index action in the PostsController, you can specify any Rack application
|
||||
# as the endpoint for a matcher:
|
||||
#
|
||||
# match "/application.js" => Sprockets
|
||||
#
|
||||
# == Reloading routes
|
||||
#
|
||||
# You can reload routes if you feel you must:
|
||||
#
|
||||
# ActionController::Routing::Routes.reload
|
||||
# Rails.application.reload_routes!
|
||||
#
|
||||
# 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>.
|
||||
@@ -262,127 +260,40 @@ module ActionController
|
||||
#
|
||||
# == View a list of all your routes
|
||||
#
|
||||
# Run <tt>rake routes</tt>.
|
||||
# rake routes
|
||||
#
|
||||
# Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
|
||||
#
|
||||
module Routing
|
||||
SEPARATORS = %w( / . ? )
|
||||
autoload :DeprecatedMapper, 'action_controller/routing/deprecated_mapper'
|
||||
autoload :Mapper, 'action_controller/routing/mapper'
|
||||
autoload :Route, 'action_controller/routing/route'
|
||||
autoload :RouteSet, 'action_controller/routing/route_set'
|
||||
autoload :UrlFor, 'action_controller/routing/url_for'
|
||||
autoload :PolymorphicRoutes, 'action_controller/routing/polymorphic_routes'
|
||||
|
||||
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options]
|
||||
|
||||
ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
|
||||
|
||||
mattr_accessor :generate_best_match
|
||||
self.generate_best_match = true
|
||||
|
||||
# The root paths which may contain controller files
|
||||
mattr_accessor :controller_paths
|
||||
self.controller_paths = []
|
||||
SEPARATORS = %w( / . ? ) #:nodoc:
|
||||
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
|
||||
|
||||
# A helper module to hold URL related helpers.
|
||||
module Helpers
|
||||
module Helpers #:nodoc:
|
||||
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)
|
||||
(inflections_without_route_reloading(&block)).tap {
|
||||
ActionController::Routing::Routes.reload! if block_given?
|
||||
}
|
||||
end
|
||||
def self.routes_reloader
|
||||
@routes_reloader ||= ActiveSupport::FileUpdateChecker.new([]){ reload_routes! }
|
||||
end
|
||||
|
||||
alias_method_chain :inflections, :route_reloading
|
||||
def self.reload_routes!
|
||||
_routes = Routes
|
||||
_routes.disable_clear_and_finalize = true
|
||||
_routes.clear!
|
||||
routes_reloader.paths.each { |path| load(path) }
|
||||
_routes.finalize!
|
||||
ensure
|
||||
_routes.disable_clear_and_finalize = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
module ActionController
|
||||
module Routing
|
||||
class RouteBuilder #:nodoc:
|
||||
attr_reader :separators, :optional_separators
|
||||
attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp
|
||||
|
||||
def initialize
|
||||
@separators = Routing::SEPARATORS
|
||||
@optional_separators = %w( / )
|
||||
|
||||
@separator_regexp = /[#{Regexp.escape(separators.join)}]/
|
||||
@nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/
|
||||
@interval_regexp = /(.*?)(#{separator_regexp}|$)/
|
||||
end
|
||||
|
||||
# Accepts a "route path" (a string defining a route), and returns the array
|
||||
# of segments that corresponds to it. Note that the segment array is only
|
||||
# partially initialized--the defaults and requirements, for instance, need
|
||||
# to be set separately, via the +assign_route_options+ method, and the
|
||||
# <tt>optional?</tt> method for each segment will not be reliable until after
|
||||
# +assign_route_options+ is called, as well.
|
||||
def segments_for_route_path(path)
|
||||
rest, segments = path, []
|
||||
|
||||
until rest.empty?
|
||||
segment, rest = segment_for(rest)
|
||||
segments << segment
|
||||
end
|
||||
segments
|
||||
end
|
||||
|
||||
# A factory method that returns a new segment instance appropriate for the
|
||||
# format of the given string.
|
||||
def segment_for(string)
|
||||
segment =
|
||||
case string
|
||||
when /\A\.(:format)?\//
|
||||
OptionalFormatSegment.new
|
||||
when /\A:(\w+)/
|
||||
key = $1.to_sym
|
||||
key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
|
||||
when /\A\*(\w+)/
|
||||
PathSegment.new($1.to_sym, :optional => true)
|
||||
when /\A\?(.*?)\?/
|
||||
StaticSegment.new($1, :optional => true)
|
||||
when nonseparator_regexp
|
||||
StaticSegment.new($1)
|
||||
when separator_regexp
|
||||
DividerSegment.new($&, :optional => optional_separators.include?($&))
|
||||
end
|
||||
[segment, $~.post_match]
|
||||
end
|
||||
|
||||
# Split the given hash of options into requirement and default hashes. The
|
||||
# segments are passed alongside in order to distinguish between default values
|
||||
# and requirements.
|
||||
def divide_route_options(segments, options)
|
||||
options = options.except(:path_prefix, :name_prefix)
|
||||
|
||||
if options[:namespace]
|
||||
options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
|
||||
end
|
||||
|
||||
requirements = (options.delete(:requirements) || {}).dup
|
||||
defaults = (options.delete(:defaults) || {}).dup
|
||||
conditions = (options.delete(:conditions) || {}).dup
|
||||
|
||||
validate_route_conditions(conditions)
|
||||
|
||||
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
|
||||
options.each do |key, value|
|
||||
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
|
||||
hash[key] = value
|
||||
end
|
||||
|
||||
[defaults, requirements, conditions]
|
||||
end
|
||||
|
||||
# Takes a hash of defaults and a hash of requirements, and assigns them to
|
||||
# the segments. Any unused requirements (which do not correspond to a segment)
|
||||
# are returned as a hash.
|
||||
def assign_route_options(segments, defaults, requirements)
|
||||
route_requirements = {} # Requirements that do not belong to a segment
|
||||
|
||||
segment_named = Proc.new do |key|
|
||||
segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
|
||||
end
|
||||
|
||||
requirements.each do |key, requirement|
|
||||
segment = segment_named[key]
|
||||
if segment
|
||||
raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
|
||||
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
|
||||
segment.regexp = requirement
|
||||
else
|
||||
route_requirements[key] = requirement
|
||||
end
|
||||
end
|
||||
|
||||
defaults.each do |key, default|
|
||||
segment = segment_named[key]
|
||||
raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
|
||||
segment.is_optional = true
|
||||
segment.default = default.to_param if default
|
||||
end
|
||||
|
||||
assign_default_route_options(segments)
|
||||
ensure_required_segments(segments)
|
||||
route_requirements
|
||||
end
|
||||
|
||||
# Assign default options, such as 'index' as a default for <tt>:action</tt>. This
|
||||
# method must be run *after* user supplied requirements and defaults have
|
||||
# been applied to the segments.
|
||||
def assign_default_route_options(segments)
|
||||
segments.each do |segment|
|
||||
next unless segment.is_a? DynamicSegment
|
||||
case segment.key
|
||||
when :action
|
||||
if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
|
||||
segment.default ||= 'index'
|
||||
segment.is_optional = true
|
||||
end
|
||||
when :id
|
||||
if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
|
||||
segment.is_optional = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Makes sure that there are no optional segments that precede a required
|
||||
# segment. If any are found that precede a required segment, they are
|
||||
# made required.
|
||||
def ensure_required_segments(segments)
|
||||
allow_optional = true
|
||||
segments.reverse_each do |segment|
|
||||
allow_optional &&= segment.optional?
|
||||
if !allow_optional && segment.optional?
|
||||
unless segment.optionality_implied?
|
||||
warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
|
||||
end
|
||||
segment.is_optional = false
|
||||
elsif allow_optional && segment.respond_to?(:default) && segment.default
|
||||
# if a segment has a default, then it is optional
|
||||
segment.is_optional = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Construct and return a route with the given path and options.
|
||||
def build(path, options)
|
||||
# Wrap the path with slashes
|
||||
path = "/#{path}" unless path[0] == ?/
|
||||
path = "#{path}/" unless path[-1] == ?/
|
||||
|
||||
prefix = options[:path_prefix].to_s.gsub(/^\//,'')
|
||||
path = "/#{prefix}#{path}" unless prefix.blank?
|
||||
|
||||
segments = segments_for_route_path(path)
|
||||
defaults, requirements, conditions = divide_route_options(segments, options)
|
||||
requirements = assign_route_options(segments, defaults, requirements)
|
||||
|
||||
# TODO: Segments should be frozen on initialize
|
||||
segments.each { |segment| segment.freeze }
|
||||
|
||||
route = Route.new(segments, requirements, conditions)
|
||||
|
||||
if !route.significant_keys.include?(:controller)
|
||||
raise ArgumentError, "Illegal route: the :controller must be specified!"
|
||||
end
|
||||
|
||||
route.freeze
|
||||
end
|
||||
|
||||
private
|
||||
def validate_route_conditions(conditions)
|
||||
if method = conditions[:method]
|
||||
[method].flatten.each do |m|
|
||||
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.to_sym)
|
||||
raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
527
actionpack/lib/action_controller/routing/deprecated_mapper.rb
Normal file
527
actionpack/lib/action_controller/routing/deprecated_mapper.rb
Normal file
@@ -0,0 +1,527 @@
|
||||
require 'active_support/core_ext/object/blank'
|
||||
require 'active_support/core_ext/object/with_options'
|
||||
require 'active_support/core_ext/object/try'
|
||||
|
||||
module ActionController
|
||||
module Routing
|
||||
class RouteSet
|
||||
attr_accessor :controller_namespaces
|
||||
|
||||
CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/
|
||||
|
||||
def controller_constraints
|
||||
@controller_constraints ||= begin
|
||||
namespaces = controller_namespaces + in_memory_controller_namespaces
|
||||
source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" }
|
||||
source << CONTROLLER_REGEXP.source
|
||||
Regexp.compile(source.sort.reverse.join('|'))
|
||||
end
|
||||
end
|
||||
|
||||
def in_memory_controller_namespaces
|
||||
namespaces = Set.new
|
||||
ActionController::Base.descendants.each do |klass|
|
||||
next if klass.anonymous?
|
||||
namespaces << klass.name.underscore.split('/')[0...-1].join('/')
|
||||
end
|
||||
namespaces.delete('')
|
||||
namespaces
|
||||
end
|
||||
end
|
||||
|
||||
class DeprecatedMapper #:nodoc:
|
||||
def initialize(set) #:nodoc:
|
||||
# ActiveSupport::Deprecation.warn "You are using the old router DSL which will be removed in Rails 3.1. " <<
|
||||
# "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
|
||||
@set = set
|
||||
end
|
||||
|
||||
def connect(path, options = {})
|
||||
options = options.dup
|
||||
|
||||
if conditions = options.delete(:conditions)
|
||||
conditions = conditions.dup
|
||||
subdomain = conditions.delete(:subdomain)
|
||||
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
|
||||
|
||||
requirements[:controller] ||= @set.controller_constraints
|
||||
|
||||
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)
|
||||
|
||||
if glob && !defaults[glob].blank?
|
||||
raise ActionController::RoutingError, "paths cannot have non-empty default values"
|
||||
end
|
||||
end
|
||||
|
||||
app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob)
|
||||
|
||||
conditions = {}
|
||||
conditions[:request_method_string] = method if method
|
||||
conditions[:path_info] = path if path
|
||||
conditions[:subdomain] = subdomain if subdomain
|
||||
|
||||
@set.add_route(app, conditions, requirements, defaults, name)
|
||||
end
|
||||
|
||||
def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc:
|
||||
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) if defaults[$1.to_sym].nil?
|
||||
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
|
||||
private :optionalize_trailing_dynamic_segments
|
||||
|
||||
# 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:
|
||||
options[:_name] = name
|
||||
connect(path, options)
|
||||
end
|
||||
|
||||
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?
|
||||
named_route(route_name, *args)
|
||||
end
|
||||
|
||||
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, :defaults
|
||||
|
||||
def initialize(entities, options, defaults)
|
||||
@plural ||= entities
|
||||
@singular ||= options[:singular] || plural.to_s.singularize
|
||||
@path_segment = options.delete(:as) || @plural
|
||||
|
||||
@options = options
|
||||
@defaults = defaults
|
||||
|
||||
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 ||= self.defaults[: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 ||= ActionController::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, defaults)
|
||||
@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
|
||||
|
||||
def resources(*entities, &block)
|
||||
options = entities.extract_options!
|
||||
entities.each { |entity| map_resource(entity, options.dup, &block) }
|
||||
end
|
||||
|
||||
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, :path_names => @set.resources_path_names)
|
||||
|
||||
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, :path_names => @set.resources_path_names)
|
||||
|
||||
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 ||= @set.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)
|
||||
{:conditions => conditions.dup}.tap 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
|
||||
end
|
||||
1372
actionpack/lib/action_controller/routing/mapper.rb
Normal file
1372
actionpack/lib/action_controller/routing/mapper.rb
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,130 +0,0 @@
|
||||
module ActionController
|
||||
module Routing
|
||||
# Much of the slow performance from routes comes from the
|
||||
# complexity of expiry, <tt>:requirements</tt> matching, defaults providing
|
||||
# and figuring out which url pattern to use. With named routes
|
||||
# we can avoid the expense of finding the right route. So if
|
||||
# they've provided the right number of arguments, and have no
|
||||
# <tt>:requirements</tt>, we can just build up a string and return it.
|
||||
#
|
||||
# To support building optimisations for other common cases, the
|
||||
# generation code is separated into several classes
|
||||
module Optimisation
|
||||
def generate_optimisation_block(route, kind)
|
||||
return "" unless route.optimise?
|
||||
OPTIMISERS.inject("") do |memo, klazz|
|
||||
memo << klazz.new(route, kind).source_code
|
||||
memo
|
||||
end
|
||||
end
|
||||
|
||||
class Optimiser
|
||||
attr_reader :route, :kind
|
||||
GLOBAL_GUARD_CONDITIONS = [
|
||||
"(!defined?(default_url_options) || default_url_options.blank?)",
|
||||
"(!defined?(controller.default_url_options) || controller.default_url_options.blank?)",
|
||||
"defined?(request)",
|
||||
"request"
|
||||
]
|
||||
|
||||
def initialize(route, kind)
|
||||
@route = route
|
||||
@kind = kind
|
||||
end
|
||||
|
||||
def guard_conditions
|
||||
["false"]
|
||||
end
|
||||
|
||||
def generation_code
|
||||
'nil'
|
||||
end
|
||||
|
||||
def source_code
|
||||
if applicable?
|
||||
guard_condition = (GLOBAL_GUARD_CONDITIONS + guard_conditions).join(" && ")
|
||||
"return #{generation_code} if #{guard_condition}\n"
|
||||
else
|
||||
"\n"
|
||||
end
|
||||
end
|
||||
|
||||
# Temporarily disabled <tt>:url</tt> optimisation pending proper solution to
|
||||
# Issues around request.host etc.
|
||||
def applicable?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Given a route
|
||||
#
|
||||
# map.person '/people/:id'
|
||||
#
|
||||
# If the user calls <tt>person_url(@person)</tt>, we can simply
|
||||
# return a string like "/people/#{@person.to_param}"
|
||||
# rather than triggering the expensive logic in +url_for+.
|
||||
class PositionalArguments < Optimiser
|
||||
def guard_conditions
|
||||
number_of_arguments = route.required_segment_keys.size
|
||||
# if they're using foo_url(:id=>2) it's one
|
||||
# argument, but we don't want to generate /foos/id2
|
||||
if number_of_arguments == 1
|
||||
["args.size == 1", "!args.first.is_a?(Hash)"]
|
||||
else
|
||||
["args.size == #{number_of_arguments}"]
|
||||
end
|
||||
end
|
||||
|
||||
def generation_code
|
||||
elements = []
|
||||
idx = 0
|
||||
|
||||
if kind == :url
|
||||
elements << '#{request.protocol}'
|
||||
elements << '#{request.host_with_port}'
|
||||
end
|
||||
|
||||
elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
|
||||
|
||||
# The last entry in <tt>route.segments</tt> appears to *always* be a
|
||||
# 'divider segment' for '/' but we have assertions to ensure that
|
||||
# we don't include the trailing slashes, so skip them.
|
||||
(route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment|
|
||||
if segment.is_a?(DynamicSegment)
|
||||
elements << segment.interpolation_chunk("args[#{idx}].to_param")
|
||||
idx += 1
|
||||
else
|
||||
elements << segment.interpolation_chunk
|
||||
end
|
||||
end
|
||||
%("#{elements * ''}")
|
||||
end
|
||||
end
|
||||
|
||||
# This case is mostly the same as the positional arguments case
|
||||
# above, but it supports additional query parameters as the last
|
||||
# argument
|
||||
class PositionalArgumentsWithAdditionalParams < PositionalArguments
|
||||
def guard_conditions
|
||||
["args.size == #{route.segment_keys.size + 1}"] +
|
||||
UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" }
|
||||
end
|
||||
|
||||
# This case uses almost the same code as positional arguments,
|
||||
# but add a question mark and args.last.to_query on the end,
|
||||
# unless the last arg is empty
|
||||
def generation_code
|
||||
super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}')
|
||||
end
|
||||
|
||||
# To avoid generating "http://localhost/?host=foo.example.com" we
|
||||
# can't use this optimisation on routes without any segments
|
||||
def applicable?
|
||||
super && route.segment_keys.size > 0
|
||||
end
|
||||
end
|
||||
|
||||
OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams]
|
||||
end
|
||||
end
|
||||
end
|
||||
183
actionpack/lib/action_controller/routing/polymorphic_routes.rb
Normal file
183
actionpack/lib/action_controller/routing/polymorphic_routes.rb
Normal file
@@ -0,0 +1,183 @@
|
||||
module ActionController
|
||||
module Routing
|
||||
# Polymorphic URL helpers are methods for smart resolution to a named route call when
|
||||
# given an Active Record model instance. They are to be used in combination with
|
||||
# ActionController::Resources.
|
||||
#
|
||||
# These methods are useful when you want to generate correct URL or path to a RESTful
|
||||
# resource without having to know the exact type of the record in question.
|
||||
#
|
||||
# Nested resources and/or namespaces are also supported, as illustrated in the example:
|
||||
#
|
||||
# polymorphic_url([:admin, @article, @comment])
|
||||
#
|
||||
# results in:
|
||||
#
|
||||
# admin_article_comment_url(@article, @comment)
|
||||
#
|
||||
# == Usage within the framework
|
||||
#
|
||||
# Polymorphic URL helpers are used in a number of places throughout the \Rails framework:
|
||||
#
|
||||
# * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
|
||||
# <tt>url_for(@article)</tt>;
|
||||
# * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
|
||||
# <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
|
||||
# action;
|
||||
# * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
|
||||
# <tt>redirect_to(post)</tt> in your controllers;
|
||||
# * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
|
||||
# for feed entries.
|
||||
#
|
||||
# == Prefixed polymorphic helpers
|
||||
#
|
||||
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
|
||||
# number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
|
||||
# in options. Those are:
|
||||
#
|
||||
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
|
||||
# * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# edit_polymorphic_path(@post) # => "/posts/1/edit"
|
||||
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
|
||||
module PolymorphicRoutes
|
||||
# Constructs a call to a named RESTful route for the given record and returns the
|
||||
# resulting URL string. For example:
|
||||
#
|
||||
# # calls post_url(post)
|
||||
# polymorphic_url(post) # => "http://example.com/posts/1"
|
||||
# polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
|
||||
# polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
|
||||
# polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
|
||||
# polymorphic_url(Comment) # => "http://example.com/comments"
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:action</tt> - Specifies the action prefix for the named route:
|
||||
# <tt>:new</tt> or <tt>:edit</tt>. Default is no prefix.
|
||||
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
|
||||
# Default is <tt>:url</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # an Article record
|
||||
# polymorphic_url(record) # same as article_url(record)
|
||||
#
|
||||
# # a Comment record
|
||||
# polymorphic_url(record) # same as comment_url(record)
|
||||
#
|
||||
# # it recognizes new records and maps to the collection
|
||||
# record = Comment.new
|
||||
# polymorphic_url(record) # same as comments_url()
|
||||
#
|
||||
# # the class of a record will also map to the collection
|
||||
# polymorphic_url(Comment) # same as comments_url()
|
||||
#
|
||||
def polymorphic_url(record_or_hash_or_array, options = {})
|
||||
if record_or_hash_or_array.kind_of?(Array)
|
||||
record_or_hash_or_array = record_or_hash_or_array.compact
|
||||
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
|
||||
end
|
||||
|
||||
record = extract_record(record_or_hash_or_array)
|
||||
record = record.to_model if record.respond_to?(:to_model)
|
||||
|
||||
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
|
||||
:singular
|
||||
elsif record.respond_to?(:new_record?) && record.new_record?
|
||||
args.pop
|
||||
:plural
|
||||
elsif record.is_a?(Class)
|
||||
args.pop
|
||||
:plural
|
||||
else
|
||||
:singular
|
||||
end
|
||||
|
||||
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
|
||||
named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
|
||||
|
||||
url_options = options.except(:action, :routing_type)
|
||||
unless url_options.empty?
|
||||
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
|
||||
end
|
||||
|
||||
send(named_route, *args)
|
||||
end
|
||||
|
||||
# Returns the path component of a URL for the given record. It uses
|
||||
# <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
|
||||
def polymorphic_path(record_or_hash_or_array, options = {})
|
||||
polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
|
||||
end
|
||||
|
||||
%w(edit new).each do |action|
|
||||
module_eval <<-EOT, __FILE__, __LINE__ + 1
|
||||
def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
|
||||
polymorphic_url( # polymorphic_url(
|
||||
record_or_hash, # record_or_hash,
|
||||
options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
|
||||
end # end
|
||||
#
|
||||
def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
|
||||
polymorphic_url( # polymorphic_url(
|
||||
record_or_hash, # record_or_hash,
|
||||
options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
|
||||
end # end
|
||||
EOT
|
||||
end
|
||||
|
||||
private
|
||||
def action_prefix(options)
|
||||
options[:action] ? "#{options[:action]}_" : ''
|
||||
end
|
||||
|
||||
def routing_type(options)
|
||||
options[:routing_type] || :url
|
||||
end
|
||||
|
||||
def build_named_route_call(records, inflection, options = {})
|
||||
unless records.is_a?(Array)
|
||||
record = extract_record(records)
|
||||
route = []
|
||||
else
|
||||
record = records.pop
|
||||
route = records.map do |parent|
|
||||
if parent.is_a?(Symbol) || parent.is_a?(String)
|
||||
parent
|
||||
else
|
||||
RecordIdentifier.__send__("plural_class_name", parent).singularize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if record.is_a?(Symbol) || record.is_a?(String)
|
||||
route << record
|
||||
else
|
||||
route << RecordIdentifier.__send__("plural_class_name", record)
|
||||
route = [route.join("_").singularize] if inflection == :singular
|
||||
end
|
||||
|
||||
route << routing_type(options)
|
||||
|
||||
action_prefix(options) + route.join("_")
|
||||
end
|
||||
|
||||
def extract_record(record_or_hash_or_array)
|
||||
case record_or_hash_or_array
|
||||
when Array; record_or_hash_or_array.last
|
||||
when Hash; record_or_hash_or_array[:id]
|
||||
else record_or_hash_or_array
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
module ActionController
|
||||
module Routing
|
||||
# BEFORE: 0.191446860631307 ms/url
|
||||
# AFTER: 0.029847304022858 ms/url
|
||||
# Speed up: 6.4 times
|
||||
#
|
||||
# Route recognition is slow due to one-by-one iterating over
|
||||
# a whole routeset (each map.resources generates at least 14 routes)
|
||||
# and matching weird regexps on each step.
|
||||
#
|
||||
# We optimize this by skipping all URI segments that 100% sure can't
|
||||
# be matched, moving deeper in a tree of routes (where node == segment)
|
||||
# until first possible match is accured. In such case, we start walking
|
||||
# a flat list of routes, matching them with accurate matcher.
|
||||
# So, first step: search a segment tree for the first relevant index.
|
||||
# Second step: iterate routes starting with that index.
|
||||
#
|
||||
# How tree is walked? We can do a recursive tests, but it's smarter:
|
||||
# We just create a tree of if-s and elsif-s matching segments.
|
||||
#
|
||||
# We have segments of 3 flavors:
|
||||
# 1) nil (no segment, route finished)
|
||||
# 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg")
|
||||
# 3) const (like "/posts", "/comments")
|
||||
# 4) dynamic ("/:id", "file.:size.:extension")
|
||||
#
|
||||
# We split incoming string into segments and iterate over them.
|
||||
# When segment is nil, we drop immediately, on a current node index.
|
||||
# When segment is equal to some const, we step into branch.
|
||||
# If none constants matched, we step into 'dynamic' branch (it's a last).
|
||||
# If we can't match anything, we drop to last index on a level.
|
||||
#
|
||||
# Note: we maintain the original routes order, so we finish building
|
||||
# steps on a first dynamic segment.
|
||||
#
|
||||
#
|
||||
# Example. Given the routes:
|
||||
# 0 /posts/
|
||||
# 1 /posts/:id
|
||||
# 2 /posts/:id/comments
|
||||
# 3 /posts/blah
|
||||
# 4 /users/
|
||||
# 5 /users/:id
|
||||
# 6 /users/:id/profile
|
||||
#
|
||||
# request_uri = /users/123
|
||||
#
|
||||
# There will be only 4 iterations:
|
||||
# 1) segm test for /posts prefix, skip all /posts/* routes
|
||||
# 2) segm test for /users/
|
||||
# 3) segm test for /users/:id
|
||||
# (jump to list index = 5)
|
||||
# 4) full test for /users/:id => here we are!
|
||||
class RouteSet
|
||||
def recognize_path(path, environment={})
|
||||
result = recognize_optimized(path, environment) and return result
|
||||
|
||||
# Route was not recognized. Try to find out why (maybe wrong verb).
|
||||
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, environment.merge(:method => verb)) } }
|
||||
|
||||
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
|
||||
raise NotImplemented.new(*allows)
|
||||
elsif !allows.empty?
|
||||
raise MethodNotAllowed.new(*allows)
|
||||
else
|
||||
raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def segment_tree(routes)
|
||||
tree = [0]
|
||||
|
||||
i = -1
|
||||
routes.each do |route|
|
||||
i += 1
|
||||
# not fast, but runs only once
|
||||
segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s })
|
||||
|
||||
node = tree
|
||||
segments.each do |seg|
|
||||
seg = :dynamic if seg && seg[0] == ?:
|
||||
node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg
|
||||
node = node[node.size - 1][1]
|
||||
end
|
||||
end
|
||||
tree
|
||||
end
|
||||
|
||||
def generate_code(list, padding=' ', level = 0)
|
||||
# a digit
|
||||
return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0])
|
||||
|
||||
body = padding + "(seg = segments[#{level}]; \n"
|
||||
|
||||
i = 0
|
||||
was_nil = false
|
||||
list.each do |item|
|
||||
if Array === item
|
||||
i += 1
|
||||
start = (i == 1)
|
||||
tag, sub = item
|
||||
if tag == :dynamic
|
||||
body += padding + "#{start ? 'if' : 'elsif'} true\n"
|
||||
body += generate_code(sub, padding + " ", level + 1)
|
||||
break
|
||||
elsif tag == nil && !was_nil
|
||||
was_nil = true
|
||||
body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n"
|
||||
body += generate_code(sub, padding + " ", level + 1)
|
||||
else
|
||||
body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n"
|
||||
body += generate_code(sub, padding + " ", level + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
body += padding + "else\n"
|
||||
body += padding + " #{list[0]}\n"
|
||||
body += padding + "end)\n"
|
||||
body
|
||||
end
|
||||
|
||||
# this must be really fast
|
||||
def to_plain_segments(str)
|
||||
str = str.dup
|
||||
str.sub!(/^\/+/,'')
|
||||
str.sub!(/\/+$/,'')
|
||||
segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also
|
||||
segments << nil
|
||||
segments
|
||||
end
|
||||
|
||||
private
|
||||
def write_recognize_optimized!
|
||||
tree = segment_tree(routes)
|
||||
body = generate_code(tree)
|
||||
|
||||
remove_recognize_optimized!
|
||||
|
||||
instance_eval %{
|
||||
def recognize_optimized(path, env)
|
||||
segments = to_plain_segments(path)
|
||||
index = #{body}
|
||||
return nil unless index
|
||||
while index < routes.size
|
||||
result = routes[index].recognize(path, env) and return result
|
||||
index += 1
|
||||
end
|
||||
nil
|
||||
end
|
||||
}, '(recognize_optimized)', 1
|
||||
end
|
||||
|
||||
def clear_recognize_optimized!
|
||||
remove_recognize_optimized!
|
||||
write_recognize_optimized!
|
||||
end
|
||||
|
||||
def remove_recognize_optimized!
|
||||
if respond_to?(:recognize_optimized)
|
||||
class << self
|
||||
remove_method :recognize_optimized
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,268 +1,64 @@
|
||||
module ActionController
|
||||
module Routing
|
||||
class Route #:nodoc:
|
||||
attr_accessor :segments, :requirements, :conditions, :optimise
|
||||
attr_reader :app, :conditions, :defaults, :name
|
||||
attr_reader :path, :requirements, :set
|
||||
|
||||
def initialize(segments = [], requirements = {}, conditions = {})
|
||||
@segments = segments
|
||||
@requirements = requirements
|
||||
@conditions = conditions
|
||||
def initialize(set, app, conditions, requirements, defaults, name, anchor)
|
||||
@set = set
|
||||
@app = app
|
||||
@defaults = defaults
|
||||
@name = name
|
||||
|
||||
if !significant_keys.include?(:action) && !requirements[:action]
|
||||
@requirements[:action] = "index"
|
||||
@significant_keys << :action
|
||||
@requirements = requirements.merge(defaults)
|
||||
@requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
|
||||
@requirements.delete_if { |k, v|
|
||||
v == Regexp.compile("[^#{SEPARATORS.join}]+")
|
||||
}
|
||||
|
||||
if path = conditions[:path_info]
|
||||
@path = path
|
||||
conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor)
|
||||
end
|
||||
|
||||
# Routes cannot use the current string interpolation method
|
||||
# if there are user-supplied <tt>:requirements</tt> as the interpolation
|
||||
# code won't raise RoutingErrors when generating
|
||||
has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
|
||||
if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
|
||||
@optimise = false
|
||||
else
|
||||
@optimise = true
|
||||
end
|
||||
@conditions = conditions.inject({}) { |h, (k, v)|
|
||||
h[k] = Rack::Mount::RegexpWithNamedGroups.new(v)
|
||||
h
|
||||
}
|
||||
|
||||
@conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) }
|
||||
@requirements.delete_if{ |k,v| !valid_condition?(k) }
|
||||
end
|
||||
|
||||
# Indicates whether the routes should be optimised with the string interpolation
|
||||
# version of the named routes methods.
|
||||
def optimise?
|
||||
@optimise && ActionController::Base::optimise_named_routes
|
||||
def verb
|
||||
if method = conditions[:request_method]
|
||||
case method
|
||||
when Regexp
|
||||
source = method.source.upcase
|
||||
source =~ /\A\^[-A-Z|]+\$\Z/ ? source[1..-2] : source
|
||||
else
|
||||
method.to_s.upcase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def segment_keys
|
||||
segments.collect do |segment|
|
||||
segment.key if segment.respond_to? :key
|
||||
end.compact
|
||||
end
|
||||
|
||||
def required_segment_keys
|
||||
required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) }
|
||||
required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact
|
||||
@segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym }
|
||||
end
|
||||
|
||||
# Build a query string from the keys of the given hash. If +only_keys+
|
||||
# is given (as an array), only the keys indicated will be used to build
|
||||
# the query string. The query string will correctly build array parameter
|
||||
# values.
|
||||
def build_query_string(hash, only_keys = nil)
|
||||
elements = []
|
||||
|
||||
(only_keys || hash.keys).each do |key|
|
||||
if value = hash[key]
|
||||
elements << value.to_query(key)
|
||||
end
|
||||
end
|
||||
|
||||
elements.empty? ? '' : "?#{elements.sort * '&'}"
|
||||
end
|
||||
|
||||
# A route's parameter shell contains parameter values that are not in the
|
||||
# route's path, but should be placed in the recognized hash.
|
||||
#
|
||||
# For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
|
||||
#
|
||||
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
|
||||
#
|
||||
def parameter_shell
|
||||
@parameter_shell ||= {}.tap do |shell|
|
||||
requirements.each do |key, requirement|
|
||||
shell[key] = requirement unless requirement.is_a? Regexp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return an array containing all the keys that are used in this route. This
|
||||
# includes keys that appear inside the path, and keys that have requirements
|
||||
# placed upon them.
|
||||
def significant_keys
|
||||
@significant_keys ||= [].tap do |sk|
|
||||
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
|
||||
sk.concat requirements.keys
|
||||
sk.uniq!
|
||||
end
|
||||
end
|
||||
|
||||
# Return a hash of key/value pairs representing the keys in the route that
|
||||
# have defaults, or which are specified by non-regexp requirements.
|
||||
def defaults
|
||||
@defaults ||= {}.tap do |hash|
|
||||
segments.each do |segment|
|
||||
next unless segment.respond_to? :default
|
||||
hash[segment.key] = segment.default unless segment.default.nil?
|
||||
end
|
||||
requirements.each do |key,req|
|
||||
next if Regexp === req || req.nil?
|
||||
hash[key] = req
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def matches_controller_and_action?(controller, action)
|
||||
prepare_matching!
|
||||
(@controller_requirement.nil? || @controller_requirement === controller) &&
|
||||
(@action_requirement.nil? || @action_requirement === action)
|
||||
def to_a
|
||||
[@app, @conditions, @defaults, @name]
|
||||
end
|
||||
|
||||
def to_s
|
||||
@to_s ||= begin
|
||||
segs = segments.inject("") { |str,s| str << s.to_s }
|
||||
"%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
|
||||
"%-6s %-40s %s" % [(verb || :any).to_s.upcase, path, requirements.inspect]
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Route should be prepared and frozen on initialize
|
||||
def freeze
|
||||
unless frozen?
|
||||
write_generation!
|
||||
write_recognition!
|
||||
prepare_matching!
|
||||
|
||||
parameter_shell
|
||||
significant_keys
|
||||
defaults
|
||||
to_s
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def generate(options, hash, expire_on = {})
|
||||
path, hash = generate_raw(options, hash, expire_on)
|
||||
append_query_string(path, hash, extra_keys(options))
|
||||
end
|
||||
|
||||
def generate_extras(options, hash, expire_on = {})
|
||||
path, hash = generate_raw(options, hash, expire_on)
|
||||
[path, extra_keys(options)]
|
||||
end
|
||||
|
||||
private
|
||||
def requirement_for(key)
|
||||
return requirements[key] if requirements.key? key
|
||||
segments.each do |segment|
|
||||
return segment.regexp if segment.respond_to?(:key) && segment.key == key
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Write and compile a +generate+ method for this Route.
|
||||
def write_generation!
|
||||
# Build the main body of the generation
|
||||
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
|
||||
|
||||
# If we have conditions that must be tested first, nest the body inside an if
|
||||
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
|
||||
args = "options, hash, expire_on = {}"
|
||||
|
||||
# Nest the body inside of a def block, and then compile it.
|
||||
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
|
||||
# are the same as the keys that were recalled from the previous request. Thus,
|
||||
# we can use the expire_on.keys to determine which keys ought to be used to build
|
||||
# the query string. (Never use keys from the recalled request when building the
|
||||
# query string.)
|
||||
|
||||
raw_method
|
||||
end
|
||||
|
||||
# Build several lines of code that extract values from the options hash. If any
|
||||
# of the values are missing or rejected then a return will be executed.
|
||||
def generation_extraction
|
||||
segments.collect do |segment|
|
||||
segment.extraction_code
|
||||
end.compact * "\n"
|
||||
end
|
||||
|
||||
# Produce a condition expression that will check the requirements of this route
|
||||
# upon generation.
|
||||
def generation_requirements
|
||||
requirement_conditions = requirements.collect do |key, req|
|
||||
if req.is_a? Regexp
|
||||
value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
|
||||
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
|
||||
else
|
||||
"hash[:#{key}] == #{req.inspect}"
|
||||
end
|
||||
end
|
||||
requirement_conditions * ' && ' unless requirement_conditions.empty?
|
||||
end
|
||||
|
||||
def generation_structure
|
||||
segments.last.string_structure segments[0..-2]
|
||||
end
|
||||
|
||||
# Write and compile a +recognize+ method for this Route.
|
||||
def write_recognition!
|
||||
# Create an if structure to extract the params from a match if it occurs.
|
||||
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
|
||||
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
|
||||
|
||||
# Build the method declaration and compile it
|
||||
method_decl = "def recognize(path, env = {})\n#{body}\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
method_decl
|
||||
end
|
||||
|
||||
# Plugins may override this method to add other conditions, like checks on
|
||||
# host, subdomain, and so forth. Note that changes here only affect route
|
||||
# recognition, not generation.
|
||||
def recognition_conditions
|
||||
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
|
||||
result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
|
||||
result << "conditions[:host] === env[:host]" if conditions[:host]
|
||||
result << "conditions[:domain] === env[:domain]" if conditions[:domain]
|
||||
result << "conditions[:subdomain] === env[:subdomain]" if conditions[:subdomain]
|
||||
result << "conditions[:fullsubdomain] === env[:fullsubdomain]" if conditions[:fullsubdomain]
|
||||
result
|
||||
end
|
||||
|
||||
# Build the regular expression pattern that will match this route.
|
||||
def recognition_pattern(wrap = true)
|
||||
pattern = ''
|
||||
segments.reverse_each do |segment|
|
||||
pattern = segment.build_pattern pattern
|
||||
end
|
||||
wrap ? ("\\A" + pattern + "\\Z") : pattern
|
||||
end
|
||||
|
||||
# Write the code to extract the parameters from a matched route.
|
||||
def recognition_extraction
|
||||
next_capture = 1
|
||||
extraction = segments.collect do |segment|
|
||||
x = segment.match_extraction(next_capture)
|
||||
next_capture += segment.number_of_captures
|
||||
x
|
||||
end
|
||||
extraction.compact
|
||||
end
|
||||
|
||||
# Generate the query string with any extra keys in the hash and append
|
||||
# it to the given path, returning the new path.
|
||||
def append_query_string(path, hash, query_keys = nil)
|
||||
return nil unless path
|
||||
query_keys ||= extra_keys(hash)
|
||||
"#{path}#{build_query_string(hash, query_keys)}"
|
||||
end
|
||||
|
||||
# Determine which keys in the given hash are "extra". Extra keys are
|
||||
# those that were not used to generate a particular route. The extra
|
||||
# keys also do not include those recalled from the prior request, nor
|
||||
# do they include any keys that were implied in the route (like a
|
||||
# <tt>:controller</tt> that is required, but not explicitly used in the
|
||||
# text of the route.)
|
||||
def extra_keys(hash, recall = {})
|
||||
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
|
||||
end
|
||||
|
||||
def prepare_matching!
|
||||
unless defined? @matching_prepared
|
||||
@controller_requirement = requirement_for(:controller)
|
||||
@action_requirement = requirement_for(:action)
|
||||
@matching_prepared = true
|
||||
end
|
||||
def valid_condition?(method)
|
||||
segment_keys.include?(method) || set.valid_conditions.include?(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,60 +1,80 @@
|
||||
require 'rack/mount'
|
||||
require 'forwardable'
|
||||
require 'active_support/concern'
|
||||
require 'active_support/core_ext/object/to_query'
|
||||
require 'action_controller/routing/deprecated_mapper'
|
||||
|
||||
module ActionController
|
||||
module Routing
|
||||
class RouteSet #:nodoc:
|
||||
# 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
|
||||
# Since the router holds references to many parts of the system
|
||||
# like engines, controllers and the application itself, inspecting
|
||||
# the route set can actually be really slow, therefore we default
|
||||
# alias inspect to to_s.
|
||||
alias inspect to_s
|
||||
|
||||
def initialize(set) #:nodoc:
|
||||
@set = set
|
||||
PARAMETERS_KEY = 'action_controller.request.path_parameters'
|
||||
|
||||
class Dispatcher #:nodoc:
|
||||
def initialize(options={})
|
||||
@defaults = options[:defaults]
|
||||
@glob_param = options.delete(:glob)
|
||||
@controllers = {}
|
||||
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
|
||||
def call(env)
|
||||
params = env[PARAMETERS_KEY]
|
||||
prepare_params!(params)
|
||||
|
||||
# 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
|
||||
# Just raise undefined constant errors if a controller was specified as default.
|
||||
unless controller = controller(params, @defaults.key?(:controller))
|
||||
return [404, {'X-Cascade' => 'pass'}, []]
|
||||
end
|
||||
named_route("root", '', options)
|
||||
|
||||
dispatch(controller, params[:action], env)
|
||||
end
|
||||
|
||||
def named_route(name, path, options = {}) #:nodoc:
|
||||
@set.add_named_route(name, path, options)
|
||||
def prepare_params!(params)
|
||||
merge_default_action!(params)
|
||||
split_glob_param!(params) if @glob_param
|
||||
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)
|
||||
# If this is a default_controller (i.e. a controller specified by the user)
|
||||
# we should raise an error in case it's not found, because it usually means
|
||||
# an user error. However, if the controller was retrieved through a dynamic
|
||||
# segment, as in :controller(/:action), we should simply return nil and
|
||||
# delegate the control back to Rack cascade. Besides, if this is not a default
|
||||
# controller, it means we should respect the @scope[:module] parameter.
|
||||
def controller(params, default_controller=true)
|
||||
if params && params.key?(:controller)
|
||||
controller_param = params[:controller]
|
||||
controller_reference(controller_param)
|
||||
end
|
||||
rescue NameError => e
|
||||
raise ActionController::RoutingError, e.message, e.backtrace if default_controller
|
||||
end
|
||||
|
||||
def method_missing(route_name, *args, &proc) #:nodoc:
|
||||
super unless args.length >= 1 && proc.nil?
|
||||
@set.add_named_route(route_name, *args)
|
||||
private
|
||||
|
||||
def controller_reference(controller_param)
|
||||
unless controller = @controllers[controller_param]
|
||||
controller_name = "#{controller_param.camelize}Controller"
|
||||
controller = @controllers[controller_param] =
|
||||
ActiveSupport::Inflector.constantize(controller_name)
|
||||
end
|
||||
controller
|
||||
end
|
||||
|
||||
def dispatch(controller, action, env)
|
||||
controller.call(env).to_a
|
||||
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
|
||||
|
||||
@@ -63,20 +83,22 @@ module ActionController
|
||||
# named routes.
|
||||
class NamedRouteCollection #:nodoc:
|
||||
include Enumerable
|
||||
include ActionController::Routing::Optimisation
|
||||
attr_reader :routes, :helpers
|
||||
attr_reader :routes, :helpers, :module
|
||||
|
||||
def initialize
|
||||
clear!
|
||||
end
|
||||
|
||||
def helper_names
|
||||
self.module.instance_methods.map(&:to_s)
|
||||
end
|
||||
|
||||
def clear!
|
||||
@routes = {}
|
||||
@helpers = []
|
||||
|
||||
@module ||= Module.new
|
||||
@module.instance_methods.each do |selector|
|
||||
@module.class_eval { remove_method selector }
|
||||
@module ||= Module.new do
|
||||
instance_methods.each { |selector| remove_method(selector) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -138,104 +160,107 @@ module ActionController
|
||||
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
|
||||
|
||||
# We use module_eval to avoid leaks
|
||||
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
||||
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
|
||||
END_EVAL
|
||||
helpers << selector
|
||||
end
|
||||
|
||||
# Create a url helper allowing 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')
|
||||
#
|
||||
def define_url_helper(route, name, kind, options)
|
||||
selector = url_helper_name(name, kind)
|
||||
# The segment keys used for positional paramters
|
||||
|
||||
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)
|
||||
args.compact! #
|
||||
#
|
||||
#{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)}
|
||||
#
|
||||
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
|
||||
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
||||
def #{selector}(*args)
|
||||
options = #{hash_access_method}(args.extract_options!)
|
||||
|
||||
if args.any?
|
||||
options[:_positional_args] = args
|
||||
options[:_positional_keys] = #{route.segment_keys.inspect}
|
||||
end
|
||||
|
||||
url_for(options)
|
||||
end
|
||||
END_EVAL
|
||||
helpers << selector
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :routes, :named_routes, :configuration_files
|
||||
attr_accessor :set, :routes, :named_routes
|
||||
attr_accessor :disable_clear_and_finalize, :resources_path_names
|
||||
attr_accessor :default_url_options, :request_class, :valid_conditions
|
||||
|
||||
def initialize
|
||||
self.configuration_files = []
|
||||
def self.default_resources_path_names
|
||||
{ :new => 'new', :edit => 'edit' }
|
||||
end
|
||||
|
||||
def initialize(request_class = ActionController::Request)
|
||||
self.routes = []
|
||||
self.named_routes = NamedRouteCollection.new
|
||||
self.resources_path_names = self.class.default_resources_path_names.dup
|
||||
self.controller_namespaces = Set.new
|
||||
self.default_url_options = {}
|
||||
|
||||
clear_recognize_optimized!
|
||||
self.request_class = request_class
|
||||
self.valid_conditions = request_class.public_instance_methods.map { |m| m.to_sym }
|
||||
self.valid_conditions.delete(:id)
|
||||
self.valid_conditions.push(:controller, :action)
|
||||
|
||||
@disable_clear_and_finalize = false
|
||||
clear!
|
||||
end
|
||||
|
||||
# Subclasses and plugins may override this method to specify a different
|
||||
# RouteBuilder instance, so that other route DSL's can be created.
|
||||
def builder
|
||||
@builder ||= RouteBuilder.new
|
||||
def draw(&block)
|
||||
clear! unless @disable_clear_and_finalize
|
||||
|
||||
mapper = Mapper.new(self)
|
||||
if block.arity == 1
|
||||
mapper.instance_exec(DeprecatedMapper.new(self), &block)
|
||||
else
|
||||
mapper.instance_exec(&block)
|
||||
end
|
||||
|
||||
finalize! unless @disable_clear_and_finalize
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def draw
|
||||
yield Mapper.new(self)
|
||||
install_helpers
|
||||
def finalize!
|
||||
return if @finalized
|
||||
@finalized = true
|
||||
@set.freeze
|
||||
end
|
||||
|
||||
def clear!
|
||||
# Clear the controller cache so we may discover new ones
|
||||
@controller_constraints = nil
|
||||
@finalized = false
|
||||
routes.clear
|
||||
named_routes.clear
|
||||
@combined_regexp = nil
|
||||
@routes_by_controller = nil
|
||||
# This will force routing/recognition_optimization.rb
|
||||
# to refresh optimisations.
|
||||
clear_recognize_optimized!
|
||||
@set = ::Rack::Mount::RouteSet.new(
|
||||
:parameters_key => PARAMETERS_KEY,
|
||||
:request_class => request_class
|
||||
)
|
||||
end
|
||||
|
||||
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
|
||||
@@ -243,104 +268,183 @@ module ActionController
|
||||
named_routes.install(destinations, regenerate_code)
|
||||
end
|
||||
|
||||
def url_helpers
|
||||
@url_helpers ||= begin
|
||||
routes = self
|
||||
|
||||
helpers = Module.new do
|
||||
extend ActiveSupport::Concern
|
||||
include UrlFor
|
||||
|
||||
@routes = routes
|
||||
class << self
|
||||
delegate :url_for, :to => '@routes'
|
||||
end
|
||||
extend routes.named_routes.module
|
||||
|
||||
# ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that
|
||||
# we can include?
|
||||
# Yes plz - JP
|
||||
included do
|
||||
routes.install_helpers(self)
|
||||
singleton_class.send(:define_method, :_routes) { routes }
|
||||
end
|
||||
|
||||
define_method(:_routes) { routes }
|
||||
end
|
||||
|
||||
helpers
|
||||
end
|
||||
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
|
||||
clear!
|
||||
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
|
||||
add_route ":controller/:action/:id"
|
||||
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.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) }
|
||||
route = builder.build(path, options)
|
||||
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
|
||||
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
|
||||
route = Route.new(self, app, conditions, requirements, defaults, name, anchor)
|
||||
@set.add_route(*route)
|
||||
named_routes[name] = route if name
|
||||
routes << route
|
||||
route
|
||||
end
|
||||
|
||||
def add_named_route(name, path, options = {})
|
||||
# TODO - is options EVER used?
|
||||
name = options[:name_prefix] + name.to_s if options[:name_prefix]
|
||||
named_routes[name.to_sym] = add_route(path, options)
|
||||
end
|
||||
class Generator #:nodoc:
|
||||
attr_reader :options, :recall, :set, :script_name, :named_route
|
||||
|
||||
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?
|
||||
def initialize(options, recall, set, extras = false)
|
||||
@script_name = options.delete(:script_name)
|
||||
@named_route = options.delete(:use_route)
|
||||
@options = options.dup
|
||||
@recall = recall.dup
|
||||
@set = set
|
||||
@extras = extras
|
||||
|
||||
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
|
||||
normalize_options!
|
||||
normalize_controller_action_id!
|
||||
use_relative_controller!
|
||||
controller.sub!(%r{^/}, '') if controller
|
||||
handle_nil_action!
|
||||
end
|
||||
|
||||
def controller
|
||||
@controller ||= @options[:controller]
|
||||
end
|
||||
|
||||
def current_controller
|
||||
@recall[:controller]
|
||||
end
|
||||
|
||||
def use_recall_for(key)
|
||||
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
|
||||
if named_route_exists?
|
||||
@options[key] = @recall.delete(key) if segment_keys.include?(key)
|
||||
else
|
||||
@options[key] = @recall.delete(key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_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".
|
||||
|
||||
if options[:controller]
|
||||
options[:action] ||= 'index'
|
||||
options[:controller] = options[:controller].to_s
|
||||
end
|
||||
|
||||
if options[:action]
|
||||
options[:action] = options[:action].to_s
|
||||
end
|
||||
end
|
||||
|
||||
# This pulls :controller, :action, and :id out of the recall.
|
||||
# The recall key is only used if there is no key in the options
|
||||
# or if the key in the options is identical. If any of
|
||||
# :controller, :action or :id is not found, don't pull any
|
||||
# more keys from the recall.
|
||||
def normalize_controller_action_id!
|
||||
@recall[:action] ||= 'index' if current_controller
|
||||
|
||||
use_recall_for(:controller) or return
|
||||
use_recall_for(:action) or return
|
||||
use_recall_for(:id)
|
||||
end
|
||||
|
||||
# if the current controller is "foo/bar/baz" and :controller => "baz/bat"
|
||||
# is specified, the controller becomes "foo/baz/bat"
|
||||
def use_relative_controller!
|
||||
if !named_route && different_controller?
|
||||
old_parts = current_controller.split('/')
|
||||
size = controller.count("/") + 1
|
||||
parts = old_parts[0...-size] << controller
|
||||
@controller = @options[:controller] = parts.join("/")
|
||||
end
|
||||
end
|
||||
|
||||
# This handles the case of :action => nil being explicitly passed.
|
||||
# It is identical to :action => "index"
|
||||
def handle_nil_action!
|
||||
if options.has_key?(:action) && options[:action].nil?
|
||||
options[:action] = 'index'
|
||||
end
|
||||
recall[:action] = options.delete(:action) if options[:action] == 'index'
|
||||
end
|
||||
|
||||
def generate
|
||||
path, params = @set.set.generate(:path_info, named_route, options, recall, opts)
|
||||
|
||||
raise_routing_error unless path
|
||||
|
||||
params.reject! {|k,v| !v.to_param}
|
||||
|
||||
return [path, params.keys] if @extras
|
||||
|
||||
path << "?#{params.to_query}" if params.any?
|
||||
"#{script_name}#{path}"
|
||||
rescue Rack::Mount::RoutingError
|
||||
raise_routing_error
|
||||
end
|
||||
|
||||
def opts
|
||||
parameterize = lambda do |name, value|
|
||||
if name == :controller
|
||||
value
|
||||
elsif value.is_a?(Array)
|
||||
value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/')
|
||||
else
|
||||
return nil unless param = value.to_param
|
||||
param.split('/').map { |v| Rack::Mount::Utils.escape_uri(v) }.join("/")
|
||||
end
|
||||
end
|
||||
{:parameterize => parameterize}
|
||||
end
|
||||
|
||||
def raise_routing_error
|
||||
raise ActionController::RoutingError, "No route matches #{options.inspect}"
|
||||
end
|
||||
|
||||
def different_controller?
|
||||
return false unless current_controller
|
||||
controller.to_param != current_controller.to_param
|
||||
end
|
||||
|
||||
private
|
||||
def named_route_exists?
|
||||
named_route && set.named_routes[named_route]
|
||||
end
|
||||
|
||||
def segment_keys
|
||||
set.named_routes[named_route].segment_keys
|
||||
end
|
||||
end
|
||||
|
||||
# Generate the path indicated by the arguments, and return an array of
|
||||
@@ -350,160 +454,108 @@ module ActionController
|
||||
end
|
||||
|
||||
def generate_extras(options, recall={})
|
||||
generate(options, recall, :generate_extras)
|
||||
generate(options, recall, true)
|
||||
end
|
||||
|
||||
def generate(options, recall = {}, method=:generate)
|
||||
named_route_name = options.delete(:use_route)
|
||||
generate_all = options.delete(:generate_all)
|
||||
if named_route_name
|
||||
named_route = named_routes[named_route_name]
|
||||
options = named_route.parameter_shell.merge(options)
|
||||
end
|
||||
|
||||
options = options_as_params(options)
|
||||
expire_on = build_expiry(options, recall)
|
||||
|
||||
if options[:controller]
|
||||
options[:controller] = options[:controller].to_s
|
||||
end
|
||||
# if the controller has changed, make sure it changes relative to the
|
||||
# current controller module, if any. In other words, if we're currently
|
||||
# on admin/get, and the new controller is 'set', the new controller
|
||||
# should really be admin/set.
|
||||
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
|
||||
|
||||
# drop the leading '/' on the controller name
|
||||
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
|
||||
merged = recall.merge(options)
|
||||
|
||||
if named_route
|
||||
path = named_route.generate(options, merged, expire_on)
|
||||
if path.nil?
|
||||
raise_named_route_error(options, named_route, named_route_name)
|
||||
else
|
||||
return path
|
||||
end
|
||||
else
|
||||
merged[:action] ||= 'index'
|
||||
options[:action] ||= 'index'
|
||||
|
||||
controller = merged[:controller]
|
||||
action = merged[:action]
|
||||
|
||||
raise RoutingError, "Need controller and action!" unless controller && action
|
||||
|
||||
if generate_all
|
||||
# Used by caching to expire all paths for a resource
|
||||
return routes.collect do |route|
|
||||
route.__send__(method, options, merged, expire_on)
|
||||
end.compact
|
||||
end
|
||||
|
||||
# don't use the recalled keys when determining which routes to check
|
||||
future_routes, deprecated_routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
|
||||
routes = Routing.generate_best_match ? deprecated_routes : future_routes
|
||||
|
||||
routes.each_with_index do |route, index|
|
||||
results = route.__send__(method, options, merged, expire_on)
|
||||
if results && (!results.is_a?(Array) || results.first)
|
||||
return results
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
raise RoutingError, "No route matches #{options.inspect}"
|
||||
def generate(options, recall = {}, extras = false)
|
||||
Generator.new(options, recall, self, extras).generate
|
||||
end
|
||||
|
||||
# try to give a helpful error message when named route generation fails
|
||||
def raise_named_route_error(options, named_route, named_route_name)
|
||||
diff = named_route.requirements.diff(options)
|
||||
unless diff.empty?
|
||||
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
|
||||
else
|
||||
required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
|
||||
required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
|
||||
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
|
||||
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash]
|
||||
|
||||
def url_for(options)
|
||||
finalize!
|
||||
options = (options || {}).reverse_merge!(default_url_options)
|
||||
|
||||
handle_positional_args(options)
|
||||
|
||||
rewritten_url = ""
|
||||
|
||||
path_segments = options.delete(:_path_segments)
|
||||
|
||||
unless options[:only_path]
|
||||
rewritten_url << (options[:protocol] || "http")
|
||||
rewritten_url << "://" unless rewritten_url.match("://")
|
||||
rewritten_url << rewrite_authentication(options)
|
||||
|
||||
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
|
||||
|
||||
rewritten_url << options[:host]
|
||||
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
end
|
||||
|
||||
path_options = options.except(*RESERVED_OPTIONS)
|
||||
path_options = yield(path_options) if block_given?
|
||||
path = generate(path_options, path_segments || {})
|
||||
|
||||
# ROUTES TODO: This can be called directly, so script_name should probably be set in the routes
|
||||
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
||||
rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
|
||||
|
||||
rewritten_url
|
||||
end
|
||||
|
||||
def call(env)
|
||||
request = Request.new(env)
|
||||
app = Routing::Routes.recognize(request)
|
||||
app.call(env).to_a
|
||||
finalize!
|
||||
@set.call(env)
|
||||
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 = {})
|
||||
method = (environment[:method] || "GET").to_s.upcase
|
||||
path = Rack::Mount::Utils.normalize_path(path) unless path =~ %r{://}
|
||||
|
||||
def recognize_path(path, environment={})
|
||||
raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path."
|
||||
end
|
||||
begin
|
||||
env = Rack::MockRequest.env_for(path, {:method => method})
|
||||
rescue URI::InvalidURIError => e
|
||||
raise ActionController::RoutingError, e.message
|
||||
end
|
||||
|
||||
def routes_by_controller
|
||||
@routes_by_controller ||= Hash.new do |controller_hash, controller|
|
||||
controller_hash[controller] = Hash.new do |action_hash, action|
|
||||
action_hash[action] = Hash.new do |key_hash, keys|
|
||||
key_hash[keys] = [
|
||||
routes_for_controller_and_action_and_keys(controller, action, keys),
|
||||
deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||
]
|
||||
req = @request_class.new(env)
|
||||
@set.recognize(req) do |route, matches, params|
|
||||
params.each do |key, value|
|
||||
if value.is_a?(String)
|
||||
value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware?
|
||||
params[key] = URI.unescape(value)
|
||||
end
|
||||
end
|
||||
|
||||
dispatcher = route.app
|
||||
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)
|
||||
return params
|
||||
end
|
||||
end
|
||||
|
||||
raise ActionController::RoutingError, "No route matches #{path.inspect}"
|
||||
end
|
||||
|
||||
def routes_for(options, merged, expire_on)
|
||||
raise "Need controller and action!" unless controller && action
|
||||
controller = merged[:controller]
|
||||
merged = options if expire_on[:controller]
|
||||
action = merged[:action] || 'index'
|
||||
private
|
||||
def handle_positional_args(options)
|
||||
return unless args = options.delete(:_positional_args)
|
||||
|
||||
routes_by_controller[controller][action][merged.keys][1]
|
||||
end
|
||||
keys = options.delete(:_positional_keys)
|
||||
keys -= options.keys if args.size < keys.size - 1 # take format into account
|
||||
|
||||
def routes_for_controller_and_action(controller, action)
|
||||
ActiveSupport::Deprecation.warn "routes_for_controller_and_action() has been deprecated. Please use routes_for()"
|
||||
selected = routes.select do |route|
|
||||
route.matches_controller_and_action? controller, action
|
||||
args = args.zip(keys).inject({}) do |h, (v, k)|
|
||||
h[k] = v
|
||||
h
|
||||
end
|
||||
|
||||
# Tell url_for to skip default_url_options
|
||||
options.merge!(args)
|
||||
end
|
||||
(selected.length == routes.length) ? routes : selected
|
||||
end
|
||||
|
||||
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||
routes.select do |route|
|
||||
route.matches_controller_and_action? controller, action
|
||||
def rewrite_authentication(options)
|
||||
if options[:user] && options[:password]
|
||||
"#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@"
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||
selected = routes.select do |route|
|
||||
route.matches_controller_and_action? controller, action
|
||||
end
|
||||
selected.sort_by do |route|
|
||||
(keys - route.significant_keys).length
|
||||
end
|
||||
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,
|
||||
:host => request.host,
|
||||
:domain => request.domain,
|
||||
:subdomain => request.subdomains.first,
|
||||
:fullsubdomain => request.subdomains.join('.')
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
class Object
|
||||
def to_param
|
||||
to_s
|
||||
end
|
||||
end
|
||||
|
||||
class TrueClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class FalseClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class NilClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Regexp #:nodoc:
|
||||
def number_of_captures
|
||||
Regexp.new("|#{source}").match('').captures.length
|
||||
end
|
||||
|
||||
def multiline?
|
||||
options & MULTILINE == MULTILINE
|
||||
end
|
||||
|
||||
class << self
|
||||
def optionalize(pattern)
|
||||
case unoptionalize(pattern)
|
||||
when /\A(.|\(.*\))\Z/ then "#{pattern}?"
|
||||
else "(?:#{pattern})?"
|
||||
end
|
||||
end
|
||||
|
||||
def unoptionalize(pattern)
|
||||
[/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
|
||||
return $1 if regexp =~ pattern
|
||||
end
|
||||
return pattern
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,343 +0,0 @@
|
||||
module ActionController
|
||||
module Routing
|
||||
class Segment #:nodoc:
|
||||
RESERVED_PCHAR = ':@&=+$,;'
|
||||
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
||||
if RUBY_VERSION >= '1.9'
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
|
||||
else
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
||||
end
|
||||
|
||||
# TODO: Convert :is_optional accessor to read only
|
||||
attr_accessor :is_optional
|
||||
alias_method :optional?, :is_optional
|
||||
|
||||
def initialize
|
||||
@is_optional = false
|
||||
end
|
||||
|
||||
def number_of_captures
|
||||
Regexp.new(regexp_chunk).number_of_captures
|
||||
end
|
||||
|
||||
def extraction_code
|
||||
nil
|
||||
end
|
||||
|
||||
# Continue generating string for the prior segments.
|
||||
def continue_string_structure(prior_segments)
|
||||
if prior_segments.empty?
|
||||
interpolation_statement(prior_segments)
|
||||
else
|
||||
new_priors = prior_segments[0..-2]
|
||||
prior_segments.last.string_structure(new_priors)
|
||||
end
|
||||
end
|
||||
|
||||
def interpolation_chunk
|
||||
URI.escape(value, UNSAFE_PCHAR)
|
||||
end
|
||||
|
||||
# Return a string interpolation statement for this segment and those before it.
|
||||
def interpolation_statement(prior_segments)
|
||||
chunks = prior_segments.collect { |s| s.interpolation_chunk }
|
||||
chunks << interpolation_chunk
|
||||
"\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
|
||||
end
|
||||
|
||||
def string_structure(prior_segments)
|
||||
optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
|
||||
end
|
||||
|
||||
# Return an if condition that is true if all the prior segments can be generated.
|
||||
# If there are no optional segments before this one, then nil is returned.
|
||||
def all_optionals_available_condition(prior_segments)
|
||||
optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
|
||||
optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
|
||||
end
|
||||
|
||||
# Recognition
|
||||
|
||||
def match_extraction(next_capture)
|
||||
nil
|
||||
end
|
||||
|
||||
# Warning
|
||||
|
||||
# Returns true if this segment is optional? because of a default. If so, then
|
||||
# no warning will be emitted regarding this segment.
|
||||
def optionality_implied?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class StaticSegment < Segment #:nodoc:
|
||||
attr_reader :value, :raw
|
||||
alias_method :raw?, :raw
|
||||
|
||||
def initialize(value = nil, options = {})
|
||||
super()
|
||||
@value = value
|
||||
@raw = options[:raw] if options.key?(:raw)
|
||||
@is_optional = options[:optional] if options.key?(:optional)
|
||||
end
|
||||
|
||||
def interpolation_chunk
|
||||
raw? ? value : super
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
chunk = Regexp.escape(value)
|
||||
optional? ? Regexp.optionalize(chunk) : chunk
|
||||
end
|
||||
|
||||
def number_of_captures
|
||||
0
|
||||
end
|
||||
|
||||
def build_pattern(pattern)
|
||||
escaped = Regexp.escape(value)
|
||||
if optional? && ! pattern.empty?
|
||||
"(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
|
||||
elsif optional?
|
||||
Regexp.optionalize escaped
|
||||
else
|
||||
escaped + pattern
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
class DividerSegment < StaticSegment #:nodoc:
|
||||
def initialize(value = nil, options = {})
|
||||
super(value, {:raw => true, :optional => true}.merge(options))
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicSegment < Segment #:nodoc:
|
||||
attr_reader :key
|
||||
|
||||
# TODO: Convert these accessors to read only
|
||||
attr_accessor :default, :regexp
|
||||
|
||||
def initialize(key = nil, options = {})
|
||||
super()
|
||||
@key = key
|
||||
@default = options[:default] if options.key?(:default)
|
||||
@regexp = options[:regexp] if options.key?(:regexp)
|
||||
@is_optional = true if options[:optional] || options.key?(:default)
|
||||
end
|
||||
|
||||
def to_s
|
||||
":#{key}"
|
||||
end
|
||||
|
||||
# The local variable name that the value of this segment will be extracted to.
|
||||
def local_name
|
||||
"#{key}_value"
|
||||
end
|
||||
|
||||
def extract_value
|
||||
"#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
|
||||
end
|
||||
|
||||
def value_check
|
||||
if default # Then we know it won't be nil
|
||||
"#{value_regexp.inspect} =~ #{local_name}" if regexp
|
||||
elsif optional?
|
||||
# If we have a regexp check that the value is not given, or that it matches.
|
||||
# If we have no regexp, return nil since we do not require a condition.
|
||||
"#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
|
||||
else # Then it must be present, and if we have a regexp, it must match too.
|
||||
"#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
|
||||
end
|
||||
end
|
||||
|
||||
def expiry_statement
|
||||
"expired, hash = true, options if !expired && expire_on[:#{key}]"
|
||||
end
|
||||
|
||||
def extraction_code
|
||||
s = extract_value
|
||||
vc = value_check
|
||||
s << "\nreturn [nil,nil] unless #{vc}" if vc
|
||||
s << "\n#{expiry_statement}"
|
||||
end
|
||||
|
||||
def interpolation_chunk(value_code = local_name)
|
||||
"\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
|
||||
end
|
||||
|
||||
def string_structure(prior_segments)
|
||||
if optional? # We have a conditional to do...
|
||||
# If we should not appear in the url, just write the code for the prior
|
||||
# segments. This occurs if our value is the default value, or, if we are
|
||||
# optional, if we have nil as our value.
|
||||
"if #{local_name} == #{default.inspect}\n" +
|
||||
continue_string_structure(prior_segments) +
|
||||
"\nelse\n" + # Otherwise, write the code up to here
|
||||
"#{interpolation_statement(prior_segments)}\nend"
|
||||
else
|
||||
interpolation_statement(prior_segments)
|
||||
end
|
||||
end
|
||||
|
||||
def value_regexp
|
||||
Regexp.new "\\A#{regexp.to_s}\\Z" if regexp
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
regexp ? regexp_string : default_regexp_chunk
|
||||
end
|
||||
|
||||
def regexp_string
|
||||
regexp_has_modifiers? ? "(#{regexp.to_s})" : "(#{regexp.source})"
|
||||
end
|
||||
|
||||
def default_regexp_chunk
|
||||
"([^#{Routing::SEPARATORS.join}]+)"
|
||||
end
|
||||
|
||||
def number_of_captures
|
||||
regexp ? regexp.number_of_captures + 1 : 1
|
||||
end
|
||||
|
||||
def build_pattern(pattern)
|
||||
pattern = "#{regexp_chunk}#{pattern}"
|
||||
optional? ? Regexp.optionalize(pattern) : pattern
|
||||
end
|
||||
|
||||
def match_extraction(next_capture)
|
||||
# All non code-related keys (such as :id, :slug) are URI-unescaped as
|
||||
# path parameters.
|
||||
default_value = default ? default.inspect : nil
|
||||
%[
|
||||
value = if (m = match[#{next_capture}])
|
||||
URI.unescape(m)
|
||||
else
|
||||
#{default_value}
|
||||
end
|
||||
params[:#{key}] = value if value
|
||||
]
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
[:action, :id].include? key
|
||||
end
|
||||
|
||||
def regexp_has_modifiers?
|
||||
regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
|
||||
end
|
||||
end
|
||||
|
||||
class ControllerSegment < DynamicSegment #:nodoc:
|
||||
def regexp_chunk
|
||||
possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
|
||||
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
|
||||
end
|
||||
|
||||
# Don't URI.escape the controller name since it may contain slashes.
|
||||
def interpolation_chunk(value_code = local_name)
|
||||
"\#{#{value_code}.to_s}"
|
||||
end
|
||||
|
||||
# Make sure controller names like Admin/Content are correctly normalized to
|
||||
# admin/content
|
||||
def extract_value
|
||||
"#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
|
||||
end
|
||||
|
||||
def match_extraction(next_capture)
|
||||
if default
|
||||
"params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
|
||||
else
|
||||
"params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class PathSegment < DynamicSegment #:nodoc:
|
||||
def interpolation_chunk(value_code = local_name)
|
||||
"\#{#{value_code}}"
|
||||
end
|
||||
|
||||
def extract_value
|
||||
"#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
|
||||
end
|
||||
|
||||
def default
|
||||
''
|
||||
end
|
||||
|
||||
def default=(path)
|
||||
raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
|
||||
end
|
||||
|
||||
def match_extraction(next_capture)
|
||||
"params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
|
||||
end
|
||||
|
||||
def default_regexp_chunk
|
||||
"(.*)"
|
||||
end
|
||||
|
||||
def number_of_captures
|
||||
regexp ? regexp.number_of_captures : 1
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
true
|
||||
end
|
||||
|
||||
class Result < ::Array #:nodoc:
|
||||
def to_s() join '/' end
|
||||
def self.new_escaped(strings)
|
||||
new strings.collect {|str| URI.unescape str}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The OptionalFormatSegment allows for any resource route to have an optional
|
||||
# :format, which decreases the amount of routes created by 50%.
|
||||
class OptionalFormatSegment < DynamicSegment
|
||||
|
||||
def initialize(key = nil, options = {})
|
||||
super(:format, {:optional => true}.merge(options))
|
||||
end
|
||||
|
||||
def interpolation_chunk
|
||||
"." + super
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
'/|(\.[^/?\.]+)?'
|
||||
end
|
||||
|
||||
def to_s
|
||||
'(.:format)?'
|
||||
end
|
||||
|
||||
def extract_value
|
||||
"#{local_name} = options[:#{key}] && options[:#{key}].to_s.downcase"
|
||||
end
|
||||
|
||||
#the value should not include the period (.)
|
||||
def match_extraction(next_capture)
|
||||
%[
|
||||
if (m = match[#{next_capture}])
|
||||
params[:#{key}] = URI.unescape(m.from(1))
|
||||
end
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
140
actionpack/lib/action_controller/routing/url_for.rb
Normal file
140
actionpack/lib/action_controller/routing/url_for.rb
Normal file
@@ -0,0 +1,140 @@
|
||||
require 'active_support/concern'
|
||||
|
||||
module ActionController
|
||||
module Routing
|
||||
# In <tt>config/routes.rb</tt> you define URL-to-controller mappings, but the reverse
|
||||
# is also possible: an URL can be generated from one of your routing definitions.
|
||||
# URL generation functionality is centralized in this module.
|
||||
#
|
||||
# See ActionDispatch::Routing for general information about routing and routes.rb.
|
||||
#
|
||||
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
|
||||
# then ActionController::UrlFor is what you're looking for. Read on for
|
||||
# an introduction.
|
||||
#
|
||||
# == URL generation from parameters
|
||||
#
|
||||
# As you may know, some functions, such as ActionController::Base#url_for
|
||||
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
|
||||
# of parameters. For example, you've probably had the chance to write code
|
||||
# like this in one of your views:
|
||||
#
|
||||
# <%= link_to('Click here', :controller => 'users',
|
||||
# :action => 'new', :message => 'Welcome!') %>
|
||||
# # => "/users/new?message=Welcome%21"
|
||||
#
|
||||
# link_to, and all other functions that require URL generation functionality,
|
||||
# actually use ActionController::UrlFor under the hood. And in particular,
|
||||
# they use the ActionController::UrlFor#url_for method. One can generate
|
||||
# the same path as the above example by using the following code:
|
||||
#
|
||||
# include UrlFor
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :only_path => true)
|
||||
# # => "/users/new?message=Welcome%21"
|
||||
#
|
||||
# Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no
|
||||
# information about the website hostname that your Rails app is serving. So if you
|
||||
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
|
||||
# argument:
|
||||
#
|
||||
# include UrlFor
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :host => 'www.example.com') # Changed this.
|
||||
# # => "http://www.example.com/users/new?message=Welcome%21"
|
||||
#
|
||||
# By default, all controllers and views have access to a special version of url_for,
|
||||
# that already knows what the current hostname is. So if you use url_for in your
|
||||
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
|
||||
# argument.
|
||||
#
|
||||
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
|
||||
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
|
||||
# in full. However, mailers don't have hostname information, and what's why you'll still
|
||||
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
|
||||
#
|
||||
#
|
||||
# == URL generation for named routes
|
||||
#
|
||||
# UrlFor also allows one to access methods that have been auto-generated from
|
||||
# named routes. For example, suppose that you have a 'users' resource in your
|
||||
# <tt>config/routes.rb</tt>:
|
||||
#
|
||||
# resources :users
|
||||
#
|
||||
# This generates, among other things, the method <tt>users_path</tt>. By default,
|
||||
# this method is accessible from your controllers, views and mailers. If you need
|
||||
# to access this auto-generated method from other places (such as a model), then
|
||||
# you can do that by including ActionController::UrlFor in your class:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# include Rails.application.routes.url_helpers
|
||||
#
|
||||
# def base_uri
|
||||
# user_path(self)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# User.find(1).base_uri # => "/users/1"
|
||||
#
|
||||
module UrlFor
|
||||
extend ActiveSupport::Concern
|
||||
include PolymorphicRoutes
|
||||
|
||||
included do
|
||||
# TODO: with_routing extends @controller with url_helpers, trickling down to including this module which overrides its default_url_options
|
||||
unless method_defined?(:default_url_options)
|
||||
# Including in a class uses an inheritable hash. Modules get a plain hash.
|
||||
if respond_to?(:class_attribute)
|
||||
class_attribute :default_url_options
|
||||
else
|
||||
mattr_accessor :default_url_options
|
||||
remove_method :default_url_options
|
||||
end
|
||||
|
||||
self.default_url_options = {}
|
||||
end
|
||||
end
|
||||
|
||||
def url_options
|
||||
default_url_options
|
||||
end
|
||||
|
||||
# Generate a url based on the options provided, default_url_options and the
|
||||
# routes defined in routes.rb. The following options are supported:
|
||||
#
|
||||
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
|
||||
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
|
||||
# * <tt>:host</tt> - Specifies the host the link should be targeted at.
|
||||
# If <tt>:only_path</tt> is false, this option must be
|
||||
# provided either explicitly, or via +default_url_options+.
|
||||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
|
||||
#
|
||||
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
|
||||
# +url_for+ is forwarded to the Routes module.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
|
||||
def url_for(options = nil)
|
||||
case options
|
||||
when String
|
||||
options
|
||||
when nil, Hash
|
||||
_routes.url_for((options || {}).reverse_merge(url_options).symbolize_keys)
|
||||
else
|
||||
polymorphic_url(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -98,8 +98,6 @@ module ActionController #:nodoc:
|
||||
parameters.each do |key, value|
|
||||
if value.is_a? Fixnum
|
||||
value = value.to_s
|
||||
elsif value.is_a? Array
|
||||
value = ActionController::Routing::PathSegment::Result.new(value)
|
||||
end
|
||||
|
||||
if extra_keys.include?(key.to_sym)
|
||||
@@ -483,7 +481,7 @@ module ActionController #:nodoc:
|
||||
|
||||
def build_request_uri(action, parameters)
|
||||
unless @request.env['REQUEST_URI']
|
||||
options = @controller.__send__(:rewrite_options, parameters)
|
||||
options = parameters
|
||||
options.update(:only_path => true, :action => action)
|
||||
|
||||
url = ActionController::UrlRewriter.new(@request, parameters)
|
||||
|
||||
@@ -1,166 +1,6 @@
|
||||
require 'uri'
|
||||
|
||||
module ActionController
|
||||
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
|
||||
# is also possible: an URL can be generated from one of your routing definitions.
|
||||
# URL generation functionality is centralized in this module.
|
||||
#
|
||||
# See ActionController::Routing and ActionController::Resources for general
|
||||
# information about routing and routes.rb.
|
||||
#
|
||||
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
|
||||
# then ActionController::UrlWriter is what you're looking for. Read on for
|
||||
# an introduction.
|
||||
#
|
||||
# == URL generation from parameters
|
||||
#
|
||||
# As you may know, some functions - such as ActionController::Base#url_for
|
||||
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
|
||||
# of parameters. For example, you've probably had the chance to write code
|
||||
# like this in one of your views:
|
||||
#
|
||||
# <%= link_to('Click here', :controller => 'users',
|
||||
# :action => 'new', :message => 'Welcome!') %>
|
||||
#
|
||||
# #=> Generates a link to: /users/new?message=Welcome%21
|
||||
#
|
||||
# link_to, and all other functions that require URL generation functionality,
|
||||
# actually use ActionController::UrlWriter under the hood. And in particular,
|
||||
# they use the ActionController::UrlWriter#url_for method. One can generate
|
||||
# the same path as the above example by using the following code:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :only_path => true)
|
||||
# # => "/users/new?message=Welcome%21"
|
||||
#
|
||||
# Notice the <tt>:only_path => true</tt> part. This is because UrlWriter has no
|
||||
# information about the website hostname that your Rails app is serving. So if you
|
||||
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
|
||||
# argument:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :host => 'www.example.com') # Changed this.
|
||||
# # => "http://www.example.com/users/new?message=Welcome%21"
|
||||
#
|
||||
# By default, all controllers and views have access to a special version of url_for,
|
||||
# that already knows what the current hostname is. So if you use url_for in your
|
||||
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
|
||||
# argument.
|
||||
#
|
||||
# For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
|
||||
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
|
||||
# in full. However, mailers don't have hostname information, and what's why you'll still
|
||||
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
|
||||
#
|
||||
#
|
||||
# == URL generation for named routes
|
||||
#
|
||||
# UrlWriter also allows one to access methods that have been auto-generated from
|
||||
# named routes. For example, suppose that you have a 'users' resource in your
|
||||
# <b>routes.rb</b>:
|
||||
#
|
||||
# map.resources :users
|
||||
#
|
||||
# This generates, among other things, the method <tt>users_path</tt>. By default,
|
||||
# this method is accessible from your controllers, views and mailers. If you need
|
||||
# to access this auto-generated method from other places (such as a model), then
|
||||
# you can do that in two ways.
|
||||
#
|
||||
# The first way is to include ActionController::UrlWriter in your class:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# include ActionController::UrlWriter # !!!
|
||||
#
|
||||
# def name=(value)
|
||||
# write_attribute('name', value)
|
||||
# write_attribute('base_uri', users_path) # !!!
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The second way is to access them through ActionController::UrlWriter.
|
||||
# The autogenerated named routes methods are available as class methods:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# def name=(value)
|
||||
# write_attribute('name', value)
|
||||
# path = ActionController::UrlWriter.users_path # !!!
|
||||
# write_attribute('base_uri', path) # !!!
|
||||
# end
|
||||
# end
|
||||
module UrlWriter
|
||||
RESERVED_PCHAR = ':@&=+$,;%'
|
||||
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
||||
if RUBY_VERSION >= '1.9'
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
|
||||
else
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
||||
end
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
ActionController::Routing::Routes.install_helpers(base)
|
||||
base.mattr_accessor :default_url_options
|
||||
|
||||
# The default options for urls written by this writer. Typically a <tt>:host</tt> pair is provided.
|
||||
base.default_url_options ||= {}
|
||||
end
|
||||
|
||||
# Generate a url based on the options provided, default_url_options and the
|
||||
# routes defined in routes.rb. The following options are supported:
|
||||
#
|
||||
# * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+.
|
||||
# * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
|
||||
# * <tt>:host</tt> - Specifies the host the link should be targetted at.
|
||||
# If <tt>:only_path</tt> is false, this option must be
|
||||
# provided either explicitly, or via +default_url_options+.
|
||||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
|
||||
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
|
||||
# +relative_url_root+ set in ActionController::Base.relative_url_root.
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
|
||||
#
|
||||
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
|
||||
# +url_for+ is forwarded to the Routes module.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/'
|
||||
# url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
|
||||
def url_for(options)
|
||||
options = self.class.default_url_options.merge(options)
|
||||
|
||||
url = ''
|
||||
|
||||
unless options.delete(:only_path)
|
||||
url << (options.delete(:protocol) || 'http')
|
||||
url << '://' unless url.match("://")
|
||||
|
||||
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
|
||||
|
||||
url << options.delete(:host)
|
||||
url << ":#{options.delete(:port)}" if options.key?(:port)
|
||||
else
|
||||
# Delete the unused options to prevent their appearance in the query string.
|
||||
[:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
|
||||
end
|
||||
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
|
||||
url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
anchor = "##{URI.escape(options.delete(:anchor).to_param.to_s, UNSAFE_PCHAR)}" if options[:anchor]
|
||||
generated = Routing::Routes.generate(options, {})
|
||||
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
|
||||
url << anchor if anchor
|
||||
|
||||
url
|
||||
end
|
||||
end
|
||||
|
||||
# Rewrites URLs for Base.redirect_to and Base.url_for in the controller.
|
||||
class UrlRewriter #:nodoc:
|
||||
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root]
|
||||
|
||||
@@ -9,6 +9,16 @@ module ActionView
|
||||
module UrlHelper
|
||||
include JavaScriptHelper
|
||||
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include ActionController::Routing::UrlFor
|
||||
include TagHelper
|
||||
|
||||
def url_options
|
||||
return super unless @controller.respond_to?(:url_options)
|
||||
@controller.url_options
|
||||
end
|
||||
|
||||
# Returns the URL for the set of +options+ provided. This takes the
|
||||
# same options as +url_for+ in Action Controller (see the
|
||||
# documentation for ActionController::Base#url_for). Note that by default
|
||||
@@ -72,8 +82,8 @@ module ActionView
|
||||
when String
|
||||
options
|
||||
when Hash
|
||||
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
|
||||
@controller.send(:url_for, options)
|
||||
options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?)
|
||||
super
|
||||
when :back
|
||||
@controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
|
||||
else
|
||||
|
||||
@@ -41,7 +41,6 @@ module ActionView
|
||||
include ActionController::TestCase::Assertions
|
||||
include ActionController::TestProcess
|
||||
|
||||
include ActionController::PolymorphicRoutes
|
||||
include ActionController::RecordIdentifier
|
||||
|
||||
include ActionView::Helpers
|
||||
|
||||
64
activesupport/lib/active_support/concern.rb
Normal file
64
activesupport/lib/active_support/concern.rb
Normal file
@@ -0,0 +1,64 @@
|
||||
# A typical module looks like this
|
||||
#
|
||||
# module M
|
||||
# def self.included(base)
|
||||
# base.send(:extend, ClassMethods)
|
||||
# base.send(:include, InstanceMethods)
|
||||
# scope :foo, :conditions => { :created_at => nil }
|
||||
# end
|
||||
#
|
||||
# module ClassMethods
|
||||
# def cm; puts 'I am a class method'; end
|
||||
# end
|
||||
#
|
||||
# module InstanceMethods
|
||||
# def im; puts 'I am an instance method'; end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as:
|
||||
#
|
||||
# module M
|
||||
# extend ActiveSupport::Concern
|
||||
#
|
||||
# included do
|
||||
# scope :foo, :conditions => { :created_at => nil }
|
||||
# end
|
||||
#
|
||||
# module ClassMethods
|
||||
# def cm; puts 'I am a class method'; end
|
||||
# end
|
||||
#
|
||||
# module InstanceMethods
|
||||
# def im; puts 'I am an instance method'; end
|
||||
# end
|
||||
# end
|
||||
module ActiveSupport
|
||||
module Concern
|
||||
def self.extended(base)
|
||||
base.instance_variable_set("@_dependencies", [])
|
||||
end
|
||||
|
||||
def append_features(base)
|
||||
if base.instance_variable_defined?("@_dependencies")
|
||||
base.instance_variable_get("@_dependencies") << self
|
||||
return false
|
||||
else
|
||||
return false if base < self
|
||||
@_dependencies.each { |dep| base.send(:include, dep) }
|
||||
super
|
||||
base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
|
||||
base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods")
|
||||
base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
|
||||
end
|
||||
end
|
||||
|
||||
def included(base = nil, &block)
|
||||
if base.nil?
|
||||
@_included_block = block
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,3 +3,4 @@ require 'active_support/core_ext/class/inheritable_attributes'
|
||||
require 'active_support/core_ext/class/removal'
|
||||
require 'active_support/core_ext/class/delegating_attributes'
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
require 'active_support/core_ext/class/subclasses'
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
require 'active_support/core_ext/module/anonymous'
|
||||
require 'active_support/core_ext/module/reachable'
|
||||
|
||||
class Class #:nodoc:
|
||||
# Rubinius
|
||||
if defined?(Class.__subclasses__)
|
||||
alias :subclasses :__subclasses__
|
||||
|
||||
def descendants
|
||||
descendants = []
|
||||
__subclasses__.each do |k|
|
||||
descendants << k
|
||||
descendants.concat k.descendants
|
||||
end
|
||||
descendants
|
||||
end
|
||||
else # MRI
|
||||
begin
|
||||
ObjectSpace.each_object(Class.new) {}
|
||||
|
||||
def descendants
|
||||
descendants = []
|
||||
ObjectSpace.each_object(class << self; self; end) do |k|
|
||||
descendants.unshift k unless k == self
|
||||
end
|
||||
descendants
|
||||
end
|
||||
rescue StandardError # JRuby
|
||||
def descendants
|
||||
descendants = []
|
||||
ObjectSpace.each_object(Class) do |k|
|
||||
descendants.unshift k if k < self
|
||||
end
|
||||
descendants.uniq!
|
||||
descendants
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an array with the direct children of +self+.
|
||||
#
|
||||
# Integer.subclasses # => [Bignum, Fixnum]
|
||||
def subclasses
|
||||
subclasses, chain = [], descendants
|
||||
chain.each do |k|
|
||||
subclasses << k unless chain.any? { |c| c > k }
|
||||
end
|
||||
subclasses
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
class Module
|
||||
# A module may or may not have a name.
|
||||
#
|
||||
# module M; end
|
||||
# M.name # => "M"
|
||||
#
|
||||
# m = Module.new
|
||||
# m.name # => ""
|
||||
#
|
||||
# A module gets a name when it is first assigned to a constant. Either
|
||||
# via the +module+ or +class+ keyword or by an explicit assignment:
|
||||
#
|
||||
# m = Module.new # creates an anonymous module
|
||||
# M = m # => m gets a name here as a side-effect
|
||||
# m.name # => "M"
|
||||
#
|
||||
def anonymous?
|
||||
# Uses blank? because the name of an anonymous class is an empty
|
||||
# string in 1.8, and nil in 1.9.
|
||||
name.blank?
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
require 'active_support/core_ext/module/anonymous'
|
||||
require 'active_support/core_ext/string/inflections'
|
||||
|
||||
class Module
|
||||
def reachable? #:nodoc:
|
||||
!anonymous? && name.constantize.equal?(self)
|
||||
rescue NameError
|
||||
false
|
||||
end
|
||||
end
|
||||
50
activesupport/lib/active_support/core_ext/object/to_param.rb
Normal file
50
activesupport/lib/active_support/core_ext/object/to_param.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
|
||||
class Object
|
||||
# Alias of <tt>to_s</tt>.
|
||||
def to_param
|
||||
to_s
|
||||
end
|
||||
end
|
||||
|
||||
class NilClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class TrueClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class FalseClass
|
||||
def to_param
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
class Array
|
||||
# Calls <tt>to_param</tt> on all its elements and joins the result with
|
||||
# slashes. This is used by <tt>url_for</tt> in Action Pack.
|
||||
def to_param
|
||||
collect { |e| e.to_param }.join '/'
|
||||
end
|
||||
end
|
||||
|
||||
class Hash
|
||||
# Converts a hash into a string suitable for use as a URL query string. An optional <tt>namespace</tt> can be
|
||||
# passed to enclose the param names (see example below). The string pairs "key=value" that conform the query
|
||||
# string are sorted lexicographically in ascending order.
|
||||
#
|
||||
# ==== Examples
|
||||
# { :name => 'David', :nationality => 'Danish' }.to_param # => "name=David&nationality=Danish"
|
||||
#
|
||||
# { :name => 'David', :nationality => 'Danish' }.to_param('user') # => "user[name]=David&user[nationality]=Danish"
|
||||
def to_param(namespace = nil)
|
||||
collect do |key, value|
|
||||
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
|
||||
end.sort * '&'
|
||||
end
|
||||
end
|
||||
27
activesupport/lib/active_support/core_ext/object/to_query.rb
Normal file
27
activesupport/lib/active_support/core_ext/object/to_query.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
require 'active_support/core_ext/object/to_param'
|
||||
|
||||
class Object
|
||||
# Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the
|
||||
# param name.
|
||||
#
|
||||
# Note: This method is defined as a default implementation for all Objects for Hash#to_query to work.
|
||||
def to_query(key)
|
||||
require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
|
||||
"#{CGI.escape(key.to_s).gsub(/%(5B|5D)/n) { [$1].pack('H*') }}=#{CGI.escape(to_param.to_s)}"
|
||||
end
|
||||
end
|
||||
|
||||
class Array
|
||||
# Converts an array into a string suitable for use as a URL query string,
|
||||
# using the given +key+ as the param name.
|
||||
#
|
||||
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies[]=Rails&hobbies[]=coding"
|
||||
def to_query(key)
|
||||
prefix = "#{key}[]"
|
||||
collect { |value| value.to_query(prefix) }.join '&'
|
||||
end
|
||||
end
|
||||
|
||||
class Hash
|
||||
alias_method :to_query, :to_param
|
||||
end
|
||||
36
activesupport/lib/active_support/core_ext/object/try.rb
Normal file
36
activesupport/lib/active_support/core_ext/object/try.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class Object
|
||||
# Invokes the method identified by the symbol +method+, passing it any arguments
|
||||
# and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
|
||||
#
|
||||
# *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
|
||||
# and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# Without try
|
||||
# @person && @person.name
|
||||
# or
|
||||
# @person ? @person.name : nil
|
||||
#
|
||||
# With try
|
||||
# @person.try(:name)
|
||||
#
|
||||
# +try+ also accepts arguments and/or a block, for the method it is trying
|
||||
# Person.try(:find, 1)
|
||||
# @people.try(:collect) {|p| p.name}
|
||||
#--
|
||||
# This method definition below is for rdoc purposes only. The alias_method call
|
||||
# below overrides it as an optimization since +try+ behaves like +Object#send+,
|
||||
# unless called on +NilClass+.
|
||||
def try(method, *args, &block)
|
||||
send(method, *args, &block)
|
||||
end
|
||||
remove_method :try
|
||||
alias_method :try, :__send__
|
||||
end
|
||||
|
||||
class NilClass #:nodoc:
|
||||
def try(*args)
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
require 'active_support/option_merger'
|
||||
|
||||
class Object
|
||||
# An elegant way to factor duplication out of options passed to a series of
|
||||
# method calls. Each method called in the block, with the block variable as
|
||||
# the receiver, will have its options merged with the default +options+ hash
|
||||
# provided. Each method called on the block variable must take an options
|
||||
# hash as its final argument.
|
||||
#
|
||||
# with_options :order => 'created_at', :class_name => 'Comment' do |post|
|
||||
# post.has_many :comments, :conditions => ['approved = ?', true], :dependent => :delete_all
|
||||
# post.has_many :unapproved_comments, :conditions => ['approved = ?', false]
|
||||
# post.has_many :all_comments
|
||||
# end
|
||||
#
|
||||
# Can also be used with an explicit receiver:
|
||||
#
|
||||
# map.with_options :controller => "people" do |people|
|
||||
# people.connect "/people", :action => "index"
|
||||
# people.connect "/people/:id", :action => "show"
|
||||
# end
|
||||
#
|
||||
def with_options(options)
|
||||
yield ActiveSupport::OptionMerger.new(self, options)
|
||||
end
|
||||
end
|
||||
5
activesupport/lib/active_support/core_ext/regexp.rb
Normal file
5
activesupport/lib/active_support/core_ext/regexp.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class Regexp #:nodoc:
|
||||
def multiline?
|
||||
options & MULTILINE == MULTILINE
|
||||
end
|
||||
end
|
||||
@@ -1,14 +1,20 @@
|
||||
# encoding: utf-8
|
||||
|
||||
if RUBY_VERSION >= '1.9'
|
||||
require 'uri'
|
||||
|
||||
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
|
||||
str.force_encoding(Encoding::UTF_8) if str.respond_to?(:force_encoding)
|
||||
|
||||
unless str == URI.unescape(URI.escape(str))
|
||||
parser = URI::Parser.new
|
||||
|
||||
unless str == parser.unescape(parser.escape(str))
|
||||
URI::Parser.class_eval do
|
||||
remove_method :unescape
|
||||
def unescape(str, escaped = @regexp[:ESCAPED])
|
||||
enc = (str.encoding == Encoding::US_ASCII) ? Encoding::UTF_8 : str.encoding
|
||||
def unescape(str, escaped = /%[a-fA-F\d]{2}/)
|
||||
# TODO: Are we actually sure that ASCII == UTF-8?
|
||||
# YK: My initial experiments say yes, but let's be sure please
|
||||
enc = str.encoding
|
||||
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
|
||||
str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
|
||||
end
|
||||
end
|
||||
|
||||
36
activesupport/lib/active_support/file_update_checker.rb
Normal file
36
activesupport/lib/active_support/file_update_checker.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
module ActiveSupport
|
||||
# This class is responsible to track files and invoke the given block
|
||||
# whenever one of these files are changed. For example, this class
|
||||
# is used by Rails to reload the I18n framework whenever they are
|
||||
# changed upon a new request.
|
||||
#
|
||||
# i18n_reloader = ActiveSupport::FileUpdateChecker.new(paths) do
|
||||
# I18n.reload!
|
||||
# end
|
||||
#
|
||||
# ActionDispatch::Callbacks.to_prepare do
|
||||
# i18n_reloader.execute_if_updated
|
||||
# end
|
||||
#
|
||||
class FileUpdateChecker
|
||||
attr_reader :paths, :last_update_at
|
||||
|
||||
def initialize(paths, calculate=false, &block)
|
||||
@paths = paths
|
||||
@block = block
|
||||
@last_update_at = calculate ? updated_at : nil
|
||||
end
|
||||
|
||||
def updated_at
|
||||
paths.map { |path| File.stat(path).mtime }.max
|
||||
end
|
||||
|
||||
def execute_if_updated
|
||||
current_update_at = self.updated_at
|
||||
if @last_update_at != current_update_at
|
||||
@last_update_at = current_update_at
|
||||
@block.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -532,9 +532,9 @@ Run `rake gems:install` to install the missing gems.
|
||||
def initialize_routing
|
||||
return unless configuration.frameworks.include?(:action_controller)
|
||||
|
||||
ActionController::Routing.controller_paths += configuration.controller_paths
|
||||
ActionController::Routing::Routes.add_configuration_file(configuration.routes_configuration_file)
|
||||
ActionController::Routing::Routes.reload!
|
||||
route = configuration.routes_configuration_file
|
||||
ActionController::Routing.routes_reloader.paths.unshift(route) if File.exists?(route)
|
||||
ActionController::Routing.reload_routes!
|
||||
end
|
||||
|
||||
# Sets the dependency loading mechanism based on the value of
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
|
||||
task :routes => :environment do
|
||||
all_routes = ENV['CONTROLLER'] ? ActionController::Routing::Routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : ActionController::Routing::Routes.routes
|
||||
routes = all_routes.collect do |route|
|
||||
name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s
|
||||
verb = route.conditions[:method].to_s.upcase
|
||||
segs = route.segments.inject("") { |str,s| str << s.to_s }
|
||||
segs.chop! if segs.length > 1
|
||||
reqs = route.requirements.empty? ? "" : route.requirements.inspect
|
||||
{:name => name, :verb => verb, :segs => segs, :reqs => reqs}
|
||||
ActionController::Routing.reload_routes!
|
||||
all_routes = ActionController::Routing::Routes.routes
|
||||
|
||||
if ENV['CONTROLLER']
|
||||
all_routes = all_routes.select{ |route| route.defaults[:controller] == ENV['CONTROLLER'] }
|
||||
end
|
||||
name_width = routes.collect {|r| r[:name]}.collect {|n| n.length}.max
|
||||
verb_width = routes.collect {|r| r[:verb]}.collect {|v| v.length}.max
|
||||
segs_width = routes.collect {|r| r[:segs]}.collect {|s| s.length}.max
|
||||
|
||||
routes = all_routes.collect do |route|
|
||||
|
||||
reqs = route.requirements.dup
|
||||
reqs[:to] = route.app unless route.app.class.name.to_s =~ /^ActionController::Routing/
|
||||
reqs = reqs.empty? ? "" : reqs.inspect
|
||||
|
||||
{:name => route.name.to_s, :verb => route.verb.to_s, :path => route.path, :reqs => reqs}
|
||||
end
|
||||
|
||||
routes.reject! { |r| r[:path] =~ %r{/rails/info/properties} } # Skip the route if it's internal info route
|
||||
|
||||
name_width = routes.map{ |r| r[:name].length }.max
|
||||
verb_width = routes.map{ |r| r[:verb].length }.max
|
||||
path_width = routes.map{ |r| r[:path].length }.max
|
||||
|
||||
routes.each do |r|
|
||||
puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:segs].ljust(segs_width)} #{r[:reqs]}"
|
||||
puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user