Compare commits

...

42 Commits

Author SHA1 Message Date
Aman Gupta
94fe0794fd define routes for tests 2014-01-15 01:14:25 -08:00
Aman Gupta
39b441bde4 polymorphic routes are gone 2014-01-15 01:12:33 -08:00
Aman Gupta
77934cbac7 Merge remote-tracking branch 'origin/2-3-github' into journey 2014-01-15 01:10:35 -08:00
Aman Gupta
ccf254b6cb build using 2.1 first 2014-01-15 01:10:18 -08:00
Aman Gupta
89384aced8 missing requires 2014-01-15 01:08:46 -08:00
Aman Gupta
b2321ce87d Merge branch '2-3-github' into journey
Conflicts:
	actionpack/lib/action_controller.rb
	actionpack/lib/action_controller/base.rb
	actionpack/lib/action_controller/routing/route_set.rb
	actionpack/lib/action_controller/routing/segments.rb
	actionpack/lib/action_controller/url_rewriter.rb
	activesupport/lib/active_support/concern.rb
	activesupport/lib/active_support/core_ext/hash/slice.rb
	activesupport/lib/active_support/core_ext/uri.rb
2014-01-15 01:01:55 -08:00
Mislav Marohnić
3766b1b377 github35 2014-01-13 13:58:57 -08:00
Mislav Marohnić
d3f87776a3 Merge pull request #41 from github/disable-generated-id
Disable auto-generated form field IDs by passing nil for "id" attribute
2014-01-13 13:57:38 -08:00
Mislav Marohnić
18c7c1f753 Disable auto-generated form field IDs by passing nil for "id" attribute
Previously it was not possible to opt out of auto-generated ID values
for various form fields.
2014-01-13 13:22:06 -08:00
Aman Gupta
536b85eedf raise RoutingError to let rails2.3 machinery handle 404s 2013-04-01 20:11:47 -07:00
Aman Gupta
cfe841a665 add required_defaults to deprecated mapper 2013-04-01 16:38:33 -07:00
Aman Gupta
26946cdbc5 kill controller_constraints 2013-04-01 03:34:07 -07:00
Aman Gupta
ceb7b06f79 skip blank prefixed names 2013-04-01 03:33:32 -07:00
Aman Gupta
a431bb5e70 more 1.8 compat 2013-03-31 22:40:25 -07:00
Aman Gupta
31ba1174fc skip query string on post requests 2013-03-31 22:28:55 -07:00
Aman Gupta
0749c4598a 1.8 compat 2013-03-31 19:53:04 -07:00
Aman Gupta
54a1b2a266 make sure controller request is not nil 2013-03-31 19:24:29 -07:00
Aman Gupta
d4eac0ff04 hook up new routes to test suite 2013-03-31 19:07:50 -07:00
Aman Gupta
fa6c421edb set content-length for rack::lint 2013-03-31 19:06:33 -07:00
Aman Gupta
a390e0366a update assert_redirected_to to use location header 2013-03-31 19:06:24 -07:00
Aman Gupta
46e7ead222 fix helper installation in integration tests 2013-03-31 07:12:56 -07:00
Aman Gupta
5c84664061 more 1.8 compat 2013-03-31 07:12:41 -07:00
Aman Gupta
96b21154d0 1.8 compat 2013-03-30 23:32:32 -07:00
Aman Gupta
32295bb1e7 remove name_prefix compat layer 2013-03-30 23:30:34 -07:00
Aman Gupta
1744654e1b kill require for deleted file 2013-03-30 23:30:26 -07:00
Aman Gupta
6fc589dcea ruby1.8 compat 2013-03-30 18:06:39 -07:00
Aman Gupta
6372a64940 s/request_method/request_method_string/ for rails2.3 compat 2013-03-30 14:50:27 -07:00
Aman Gupta
7ffe8e65e9 more fixes missed in merge 2013-03-30 14:50:01 -07:00
Aman Gupta
0fee4d272a remove rack hack 2013-03-30 14:50:01 -07:00
Aman Gupta
73e5333f3d more AS core ext from rails master 2013-03-30 14:49:56 -07:00
Aman Gupta
370b93b49c name_prefix and 1.8 compat 2013-03-29 03:04:36 -07:00
Aman Gupta
b86cf6843b backport router from rails master 2013-03-29 03:04:27 -07:00
Aman Gupta
d22470ab98 import journey from rails master 2013-03-29 02:59:42 -07:00
Aman Gupta
5787193c86 AS deps from rails master 2013-03-29 02:59:30 -07:00
Aman Gupta
b75d7ea2c6 fix integration runner 2013-03-28 23:51:46 -07:00
Aman Gupta
735c4e790d rewrite_options is dead 2013-03-27 20:26:10 -07:00
Aman Gupta
0e467e376b PathSegment is gone 2013-03-27 18:56:36 -07:00
Aman Gupta
e3dafa5669 fix references to ActiveModel from bad merge 2013-03-27 18:56:32 -07:00
Aman Gupta
3297a4c446 integrate backported router 2013-03-27 02:47:56 -07:00
Aman Gupta
d98e3f8489 remove UrlWriter 2013-03-27 02:46:16 -07:00
Aman Gupta
9ee6c6c082 import activesupport dependencies 2013-03-27 02:38:25 -07:00
Aman Gupta
6d4d9dd919 copy/paste router from 3-0-github 2013-03-25 23:23:53 -07:00
85 changed files with 6667 additions and 2750 deletions

View File

@@ -1 +1 @@
2.3.14.github34
2.3.14.github35

View File

@@ -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

View File

@@ -76,7 +76,7 @@ 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.require_path = 'lib'

View File

@@ -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, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
[Base, 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

View File

@@ -59,19 +59,9 @@ module ActionController
def assert_redirected_to(options = {}, message=nil)
clean_backtrace do
assert_response(:redirect, message)
return true if options == @response.redirected_to
return true if options == @response.location
# Support partial arguments for hash redirections
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
if options.all? {|(key, value)| @response.redirected_to[key] == value}
callstack = caller.dup
callstack.slice!(0, 2)
::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", callstack)
return true
end
end
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.location)
options_after_normalisation = normalize_argument_to_redirection(options)
if redirected_to_after_normalisation != options_after_normalisation

View File

@@ -1,61 +1,15 @@
require 'set'
require 'action_controller/metal/url_for'
require 'action_controller/metal/exceptions'
module ActionController #:nodoc:
class ActionControllerError < StandardError #:nodoc:
end
class SessionRestoreError < ActionControllerError #:nodoc:
end
class RenderError < ActionControllerError #:nodoc:
end
class RoutingError < ActionControllerError #:nodoc:
attr_reader :failures
def initialize(message, failures=[])
super(message)
@failures = failures
end
end
class MethodNotAllowed < ActionControllerError #:nodoc:
attr_reader :allowed_methods
def initialize(*allowed_methods)
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
@allowed_methods = allowed_methods
end
def allowed_methods_header
allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', '
end
def handle_response!(response)
response.headers['Allow'] ||= allowed_methods_header
end
end
class NotImplemented < MethodNotAllowed #:nodoc:
end
class UnknownController < ActionControllerError #:nodoc:
end
class UnknownAction < ActionControllerError #:nodoc:
end
class MissingFile < ActionControllerError #:nodoc:
end
class RenderError < ActionControllerError #:nodoc:
end
class SessionOverflowError < ActionControllerError #:nodoc:
DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
class SessionRestoreError < ActionControllerError #:nodoc:
end
class DoubleRenderError < ActionControllerError #:nodoc:
@@ -74,9 +28,6 @@ module ActionController #:nodoc:
end
end
class UnknownHttpMethod < ActionControllerError #:nodoc:
end
# Action Controllers are the core of a web request in Rails. They are made up of one or more actions that are executed
# on request and then either render a template or redirect to another action. An action is defined as a public method
# on the controller, which will automatically be made accessible to the web-server through Rails Routes.
@@ -250,6 +201,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 +493,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
@@ -1035,27 +900,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+.
@@ -1362,7 +1206,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

View File

@@ -44,7 +44,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

View File

@@ -77,6 +77,7 @@ module ActionController #:nodoc:
@master_helper_class = Class.new(ActionView::Base).tap do |klass|
klass.send(:include, master_helper_module)
klass.send(:include, ActionController::Routing::Routes.url_helpers)
end
end

View File

@@ -1,7 +1,6 @@
require 'stringio'
require 'uri'
require 'active_support/test_case'
require 'action_controller/rack_lint_patch'
module ActionController
module Integration #:nodoc:
@@ -91,12 +90,9 @@ module ActionController
unless defined? @named_routes_configured
# install the named routes in this session instance.
klass = class << self; self; end
Routing::Routes.install_helpers(klass)
# the helpers are made protected by default--we make them public for
# easier access during testing and troubleshooting.
klass.module_eval { public *Routing::Routes.named_routes.helpers }
class << self
include ActionController::Routing::Routes.url_helpers
end
@named_routes_configured = true
end
end
@@ -130,7 +126,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 +252,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
@@ -343,6 +340,10 @@ module ActionController
@response = @controller.response
@controller.send(:set_test_assigns)
else
# Fake request for integration tests that never hit a controller ('/' => redirect('/abc'))
@request = ActionController::TestRequest.new
@request.host = host
# Decorate responses from Rack Middleware and Rails Metal
# as an Response for the purposes of integration testing
@response = Response.new

View File

@@ -0,0 +1,56 @@
module ActionController
class ActionControllerError < StandardError #:nodoc:
end
class BadRequest < ActionControllerError #:nodoc:
attr_reader :original_exception
def initialize(type = nil, e = nil)
return super() unless type && e
super("Invalid #{type} parameters: #{e.message}")
@original_exception = e
set_backtrace e.backtrace
end
end
class RoutingError < ActionControllerError #:nodoc:
attr_reader :failures
def initialize(message, failures=[])
super(message)
@failures = failures
end
end
class ActionController::UrlGenerationError < RoutingError #:nodoc:
end
class MethodNotAllowed < ActionControllerError #:nodoc:
def initialize(*allowed_methods)
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
end
end
class NotImplemented < MethodNotAllowed #:nodoc:
end
class UnknownController < ActionControllerError #:nodoc:
end
class MissingFile < ActionControllerError #:nodoc:
end
class SessionOverflowError < ActionControllerError #:nodoc:
DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
def initialize(message = nil)
super(message || DEFAULT_MESSAGE)
end
end
class UnknownHttpMethod < ActionControllerError #:nodoc:
end
class UnknownFormat < ActionControllerError #:nodoc:
end
end

View 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,
:_recall => 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

View File

@@ -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

View File

@@ -1,36 +0,0 @@
# Rack 1.0 does not allow string subclass body. This does not play well with our ActiveSupport::SafeBuffer.
# The next release of Rack will be allowing string subclass body - http://github.com/rack/rack/commit/de668df02802a0335376a81ba709270e43ba9d55
# TODO : Remove this monkey patch after the next release of Rack
module RackLintPatch
module AllowStringSubclass
def self.included(base)
base.send :alias_method, :each, :each_with_hack
end
def each_with_hack
@closed = false
@body.each { |part|
assert("Body yielded non-string value #{part.inspect}") {
part.kind_of?(String)
}
yield part
}
if @body.respond_to?(:to_path)
assert("The file identified by body.to_path does not exist") {
::File.exist? @body.to_path
}
end
end
end
begin
app = proc {|env| [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, [Class.new(String).new("Hello World!")]] }
response = Rack::MockRequest.new(Rack::Lint.new(app)).get('/')
rescue Rack::Lint::LintError => e
raise(e) unless e.message =~ /Body yielded non-string value/
Rack::Lint.send :include, AllowStringSubclass
end
end

View File

@@ -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
@@ -308,6 +312,10 @@ EOM
end
end
def standard_port?
port == standard_port
end
# Returns a \port suffix like ":8080" if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
def port_string
@@ -332,6 +340,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] || '')

View File

@@ -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,34 @@ 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 :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 = []
# A helper module to hold URL related helpers.
module Helpers
include PolymorphicRoutes
end
class << self
# Expects an array of controller names as the first argument.
# Executes the passed block with only the named controllers named available.
# This method is used in internal Rails testing.
def with_controllers(names)
prior_controllers = @possible_controllers
use_controllers! names
yield
ensure
use_controllers! prior_controllers
end
# Returns an array of paths, cleaned of double-slashes and relative path references.
# * "\\\" and "//" become "\\" or "/".
# * "/foo/bar/../config" becomes "/foo/config".
# The returned array is sorted by length, descending.
def normalize_paths(paths)
# do the hokey-pokey of path normalization...
paths = paths.collect do |path|
path = path.
gsub("//", "/"). # replace double / chars with a single
gsub("\\\\", "\\"). # replace double \ chars with a single
gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it
# eliminate .. paths where possible
re = %r{[^/\\]+[/\\]\.\.[/\\]}
path.gsub!(re, "") while path.match(re)
path
end
# start with longest path, first
paths = paths.uniq.sort_by { |path| - path.length }
end
# Returns the array of controller names currently available to ActionController::Routing.
def possible_controllers
unless @possible_controllers
@possible_controllers = []
paths = controller_paths.select { |path| File.directory?(path) && path != "." }
seen_paths = Hash.new {|h, k| h[k] = true; false}
normalize_paths(paths).each do |load_path|
Dir["#{load_path}/**/*_controller.rb"].collect do |path|
next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
controller_name = path[(load_path.length + 1)..-1]
controller_name.gsub!(/_controller\.rb\Z/, '')
@possible_controllers << controller_name
end
end
# remove duplicates
@possible_controllers.uniq!
end
@possible_controllers
end
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
def use_controllers!(controller_names)
@possible_controllers = controller_names
end
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
# Handles 4 scenarios:
#
# * stay in the previous controller:
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
#
# * stay in the previous namespace:
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
#
# * forced move to the root namespace:
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
#
# * previous namespace is root:
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
#
def controller_relative_to(controller, previous)
if controller.nil? then previous
elsif controller[0] == ?/ then controller[1..-1]
elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
else controller
end
end
end
SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
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

View File

@@ -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

View File

@@ -0,0 +1,506 @@
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 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 && name
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
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 = Journey::Router::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
conditions[:required_defaults] = []
defaults.each do |key, required_default|
next if Regexp === required_default
conditions[:required_defaults] << key
end
@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

View File

@@ -0,0 +1,240 @@
require 'delegate'
require 'active_support/core_ext/string/strip'
module ActionController
module Routing
class RouteWrapper < SimpleDelegator
def endpoint
rack_app ? rack_app.inspect : "#{controller}##{action}"
end
def constraints
requirements.except(:controller, :action)
end
def rack_app(app = self.app)
@rack_app ||= begin
class_name = app.class.name.to_s
if class_name == "ActionController::Routing::Mapper::Constraints"
rack_app(app.app)
elsif ActionController::Routing::Redirect === app || class_name !~ /^ActionController::Routing/
app
end
end
end
def verb
super.source.gsub(/[$^]/, '')
end
def path
super.spec.to_s
end
def name
super.to_s
end
def regexp
__getobj__.path.to_regexp
end
def json_regexp
str = regexp.inspect.
sub('\\A' , '^').
sub('\\Z' , '$').
sub('\\z' , '$').
sub(/^\// , '').
sub(/\/[a-z]*$/ , '').
gsub(/\(\?#.+\)/ , '').
gsub(/\(\?-\w+:/ , '(').
gsub(/\s/ , '')
Regexp.new(str).source
end
def reqs
@reqs ||= begin
reqs = endpoint
reqs += " #{constraints.to_s}" unless constraints.empty?
reqs
end
end
def controller
requirements[:controller] || ':controller'
end
def action
requirements[:action] || ':action'
end
def internal?
controller =~ %r{\Arails/(info|welcome)}
end
def engine?
rack_app && rack_app.respond_to?(:routes)
end
end
##
# This class is just used for displaying route information when someone
# executes `rake routes` or looks at the RoutingError page.
# People should not use this class.
class RoutesInspector # :nodoc:
def initialize(routes)
@engines = {}
@routes = routes
end
def format(formatter, filter = nil)
routes_to_display = filter_routes(filter)
routes = collect_routes(routes_to_display)
if routes.none?
formatter.no_routes
return formatter.result
end
formatter.header routes
formatter.section routes
@engines.each do |name, engine_routes|
formatter.section_title "Routes for #{name}"
formatter.section engine_routes
end
formatter.result
end
private
def filter_routes(filter)
if filter
@routes.select { |route| route.defaults[:controller] == filter }
else
@routes
end
end
def collect_routes(routes)
routes.collect do |route|
RouteWrapper.new(route)
end.reject do |route|
route.internal?
end.collect do |route|
collect_engine_routes(route)
{ :name => route.name,
:verb => route.verb,
:path => route.path,
:reqs => route.reqs,
:regexp => route.json_regexp }
end
end
def collect_engine_routes(route)
name = route.endpoint
return unless route.engine?
return if @engines[name]
routes = route.rack_app.routes
if routes.is_a?(ActionController::Routing::RouteSet)
@engines[name] = collect_routes(routes.routes)
end
end
end
class ConsoleFormatter
def initialize
@buffer = []
end
def result
@buffer.join("\n")
end
def section_title(title)
@buffer << "\n#{title}:"
end
def section(routes)
@buffer << draw_section(routes)
end
def header(routes)
@buffer << draw_header(routes)
end
def no_routes
@buffer << <<-MESSAGE.strip_heredoc
You don't have any routes defined!
Please add some routes in config/routes.rb.
For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
MESSAGE
end
private
def draw_section(routes)
name_width, verb_width, path_width = widths(routes)
routes.map do |r|
"#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
end
end
def draw_header(routes)
name_width, verb_width, path_width = widths(routes)
"#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
end
def widths(routes)
[routes.map { |r| r[:name].length }.max,
routes.map { |r| r[:verb].length }.max,
routes.map { |r| r[:path].length }.max]
end
end
class HtmlTableFormatter
def initialize(view)
@view = view
@buffer = []
end
def section_title(title)
@buffer << %(<tr><th colspan="4">#{title}</th></tr>)
end
def section(routes)
@buffer << @view.render(:partial => "routes/route", :collection => routes)
end
# the header is part of the HTML page, so we don't construct it here.
def header(routes)
end
def no_routes
@buffer << <<-MESSAGE.strip_heredoc
<p>You don't have any routes defined!</p>
<ul>
<li>Please add some routes in <tt>config/routes.rb</tt>.</li>
<li>
For more information about routes, please see the Rails guide
<a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
</li>
</ul>
MESSAGE
end
def result
@view.raw @view.render(:layout => "routes/table") {
@view.raw @buffer.join("\n")
}
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -0,0 +1,147 @@
require 'active_support/core_ext/uri'
require 'active_support/core_ext/array/extract_options'
require 'rack/utils'
require 'action_controller/metal/exceptions'
module ActionController
module Routing
class Redirect # :nodoc:
attr_reader :status, :block
def initialize(status, block)
@status = status
@block = block
end
def call(env)
req = Request.new(env)
# If any of the path parameters has a invalid encoding then
# raise since it's likely to trigger errors further on.
req.symbolized_path_parameters.each do |key, value|
unless value.valid_encoding?
raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
end
end
uri = URI.parse(path(req.symbolized_path_parameters, req))
uri.scheme ||= req.scheme
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
headers = {
'Location' => uri.to_s,
'Content-Type' => 'text/html',
'Content-Length' => body.length.to_s
}
[ status, headers, [body] ]
end
def path(params, request)
block.call params, request
end
def inspect
"redirect(#{status})"
end
end
class PathRedirect < Redirect
def path(params, request)
(params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params))
end
def inspect
"redirect(#{status}, #{block})"
end
private
def escape(params)
Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
end
end
class OptionRedirect < Redirect # :nodoc:
alias :options :block
def path(params, request)
url_options = {
:protocol => request.protocol,
:host => request.host,
:port => request.optional_port,
:path => request.path,
:params => request.query_parameters
}.merge! options
if !params.empty? && url_options[:path].match(/%\{\w*\}/)
url_options[:path] = (url_options[:path] % escape_path(params))
end
ActionController::Http::URL.url_for url_options
end
def inspect
"redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
end
private
def escape_path(params)
Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
end
end
module Redirection
# Redirect any path to another path:
#
# get "/stories" => redirect("/posts")
#
# You can also use interpolation in the supplied redirect argument:
#
# get 'docs/:article', to: redirect('/wiki/%{article}')
#
# Alternatively you can use one of the other syntaxes:
#
# The block version of redirect allows for the easy encapsulation of any logic associated with
# the redirect in question. Either the params and request are supplied as arguments, or just
# params, depending of how many arguments your block accepts. A string is required as a
# return value.
#
# get 'jokes/:number', to: redirect { |params, request|
# path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
# }
#
# Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass
# the block to +get+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
#
# The options version of redirect allows you to supply only the parts of the url which need
# to change, it also supports interpolation of the path similar to the first example.
#
# get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}')
# get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}')
#
# Finally, an object which responds to call can be supplied to redirect, allowing you to reuse
# common redirect routes. The call method must accept two arguments, params and request, and return
# a string.
#
# get 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
options = args.extract_options!
status = options.delete(:status) || 301
path = args.shift
return OptionRedirect.new(status, options) if options.any?
return PathRedirect.new(status, path) if String === path
block = path if path.respond_to? :call
raise ArgumentError, "redirection argument not supported" unless block
Redirect.new status, block
end
end
end
end

View File

@@ -1,269 +0,0 @@
module ActionController
module Routing
class Route #:nodoc:
attr_accessor :segments, :requirements, :conditions, :optimise
def initialize(segments = [], requirements = {}, conditions = {})
@segments = segments
@requirements = requirements
@conditions = conditions
if !significant_keys.include?(:action) && !requirements[:action]
@requirements[:action] = "index"
@significant_keys << :action
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
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
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
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)
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]
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
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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::DEFAULT_PARSER.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::DEFAULT_PARSER.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::DEFAULT_PARSER.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::DEFAULT_PARSER.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::DEFAULT_PARSER.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::DEFAULT_PARSER.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::DEFAULT_PARSER.unescape(m.from(1))
end
]
end
end
end
end

View File

@@ -0,0 +1,169 @@
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_writer :default_url_options
end
self.default_url_options = {}
end
include(*_url_for_modules) if respond_to?(:_url_for_modules)
end
def initialize(*)
@_routes = nil
super
end
# Hook overridden in controller to add request information
# with `default_url_options`. Application logic should not
# go into url_options.
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 nil
_routes.url_for(url_options.symbolize_keys)
when Hash
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
when String
options
else
polymorphic_url(options)
end
end
protected
def optimize_routes_generation?
return @_optimized_routes if defined?(@_optimized_routes)
@_optimized_routes = _routes.optimize_routes_generation? && default_url_options.empty?
end
def _with_routes(routes)
old_routes, @_routes = @_routes, routes
yield
ensure
@_routes = old_routes
end
def _routes_context
self
end
end
end
end

View File

@@ -36,6 +36,7 @@ module ActionController #:nodoc:
@env['RAW_POST_DATA'] ||= begin
data = url_encoded_request_parameters
data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding)
@env['CONTENT_LENGTH'] = data.bytesize.to_s
data
end
end
@@ -44,11 +45,6 @@ module ActionController #:nodoc:
@env["SERVER_PORT"] = number.to_i
end
def action=(action_name)
@query_parameters.update({ "action" => action_name })
@parameters = nil
end
# Used to check AbstractRequest's request_uri functionality.
# Disables the use of @path and @request_uri so superclass can handle those.
def set_REQUEST_URI(value)
@@ -91,23 +87,32 @@ module ActionController #:nodoc:
@path || super()
end
def assign_parameters(controller_path, action, parameters = {})
def assign_parameters(routes, controller_path, action, parameters = {})
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
extra_keys = routes.extra_keys(parameters)
non_path_parameters = get? ? query_parameters : request_parameters
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)
if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
value = value.map{ |v| v.duplicable? ? v.dup : v }
elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
elsif value.frozen? && value.duplicable?
value = value.dup
end
if extra_keys.include?(key.to_sym)
non_path_parameters[key] = value
else
if value.is_a?(Array)
value = value.map(&:to_param)
else
value = value.to_param
end
path_parameters[key.to_s] = value
end
end
raw_post # populate env['RAW_POST_DATA']
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
end
@@ -420,9 +425,11 @@ module ActionController #:nodoc:
end
def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
@routes ||= ActionController::Routing::Routes
# Sanity check for required instance variables so we can give an
# understandable error message.
%w(@controller @request @response).each do |iv_name|
%w(@routes @controller @request @response).each do |iv_name|
if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
end
@@ -434,10 +441,8 @@ module ActionController #:nodoc:
@html_document = nil
@request.env['REQUEST_METHOD'] = http_method
@request.action = action.to_s
parameters ||= {}
@request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)
@request.assign_parameters(@routes, @controller.class.controller_path, action.to_s, parameters)
@request.session = ActionController::TestSession.new(session) unless session.nil?
@request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
@@ -482,13 +487,20 @@ module ActionController #:nodoc:
end
def build_request_uri(action, parameters)
unless @request.env['REQUEST_URI']
options = @controller.__send__(:rewrite_options, parameters)
options.update(:only_path => true, :action => action)
@controller.request = @request
options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
options.update(
:only_path => true,
:action => action,
:relative_url_root => nil,
:_recall => @request.symbolized_path_parameters)
url = ActionController::UrlRewriter.new(@request, parameters)
@request.set_REQUEST_URI(url.rewrite(options))
end
url, query_string = @routes.url_for(options).split("?", 2)
@request.env["PATH_INFO"] = url
@request.env["QUERY_STRING"] = (@request.get? && query_string) || ""
@request.set_REQUEST_URI(nil)
@request.request_uri # populate REQUEST_URI
end
def html_document

View File

@@ -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::DEFAULT_PARSER.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]
@@ -215,7 +55,7 @@ module ActionController
RESERVED_OPTIONS.each { |k| options.delete(k) }
# Generates the query string, too
Routing::Routes.generate(options, @request.symbolized_path_parameters)
Routing::Routes.url_for({:host => @request.host_with_port, :protocol => @request.protocol}.merge(options))
end
def rewrite_authentication(options)

View File

@@ -0,0 +1,211 @@
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/hash/slice'
module ActionDispatch
module Http
module URL
IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
mattr_accessor :tld_length
self.tld_length = 1
class << self
def extract_domain(host, tld_length = @@tld_length)
host.split('.').last(1 + tld_length).join('.') if named_host?(host)
end
def extract_subdomains(host, tld_length = @@tld_length)
if named_host?(host)
parts = host.split('.')
parts[0..-(tld_length + 2)]
else
[]
end
end
def extract_subdomain(host, tld_length = @@tld_length)
extract_subdomains(host, tld_length).join('.')
end
def url_for(options = {})
path = options.delete(:script_name).to_s.chomp("/")
path << options.delete(:path).to_s
params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
params.reject! { |_,v| v.to_param.nil? }
result = build_host_url(options)
if options[:trailing_slash]
if path.include?('?')
result << path.sub(/\?/, '/\&')
else
result << path.sub(/[^\/]\z|\A\z/, '\&/')
end
else
result << path
end
result << "?#{params.to_query}" unless params.empty?
result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
result
end
private
def build_host_url(options)
if options[:host].blank? && options[:only_path].blank?
raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
end
result = ""
unless options[:only_path]
protocol = extract_protocol(options)
unless options[:protocol] == false
result << protocol
result << ":" unless result.match(%r{:|//})
end
result << "//" unless result.match("//")
result << rewrite_authentication(options)
result << host_or_subdomain_and_domain(options)
result << ":#{options.delete(:port)}" if options[:port]
end
result
end
def named_host?(host)
host && IP_HOST_REGEXP !~ host
end
def rewrite_authentication(options)
if options[:user] && options[:password]
"#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
else
""
end
end
# Extracts protocol http:// or https:// from options[:host]
# needs to be called whether the :protocol is being used or not
def extract_protocol(options)
if options[:host] && match = options[:host].match(/(^.*:\/\/)(.*)/)
options[:protocol] ||= match[1]
options[:host] = match[2]
end
options[:protocol] || "http"
end
def host_or_subdomain_and_domain(options)
return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
tld_length = options[:tld_length] || @@tld_length
host = ""
unless options[:subdomain] == false
host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param
host << "."
end
host << (options[:domain] || extract_domain(options[:host], tld_length))
host
end
end
def initialize(env)
super
@protocol = nil
@port = nil
end
# Returns the complete URL used for this request.
def url
protocol + host_with_port + fullpath
end
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
def protocol
@protocol ||= ssl? ? 'https://' : 'http://'
end
# Returns the \host for this request, such as "example.com".
def raw_host_with_port
if forwarded = env["HTTP_X_FORWARDED_HOST"]
forwarded.split(/,\s?/).last
else
env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
end
end
# Returns the host for this request, such as example.com.
def host
raw_host_with_port.sub(/:\d+$/, '')
end
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080".
def host_with_port
"#{host}#{port_string}"
end
# Returns the port number of this request as an integer.
def port
@port ||= begin
if raw_host_with_port =~ /:(\d+)$/
$1.to_i
else
standard_port
end
end
end
# Returns the standard \port number for this request's protocol.
def standard_port
case protocol
when 'https://' then 443
else 80
end
end
# Returns whether this request is using the standard port
def standard_port?
port == standard_port
end
# Returns a number \port suffix like 8080 if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
def optional_port
standard_port? ? nil : port
end
# Returns a string \port suffix, including colon, like ":8080" if the \port
# number of this request is not the default HTTP \port 80 or HTTPS \port 443.
def port_string
standard_port? ? '' : ":#{port}"
end
def server_port
@env['SERVER_PORT'].to_i
end
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
def domain(tld_length = @@tld_length)
ActionDispatch::Http::URL.extract_domain(host, tld_length)
end
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
# in "www.rubyonrails.co.uk".
def subdomains(tld_length = @@tld_length)
ActionDispatch::Http::URL.extract_subdomains(host, tld_length)
end
# Returns all the \subdomains as a string, so <tt>"dev.www"</tt> would be
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
# such as 2 to catch <tt>"www"</tt> instead of <tt>"www.rubyonrails"</tt>
# in "www.rubyonrails.co.uk".
def subdomain(tld_length = @@tld_length)
ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
end
end
end
end

View File

@@ -0,0 +1,5 @@
require 'action_dispatch/journey/router'
require 'action_dispatch/journey/gtg/builder'
require 'action_dispatch/journey/gtg/simulator'
require 'action_dispatch/journey/nfa/builder'
require 'action_dispatch/journey/nfa/simulator'

View File

@@ -0,0 +1,5 @@
module Rack # :nodoc:
Mount = ActionDispatch::Journey::Router
Mount::RouteSet = ActionDispatch::Journey::Router
Mount::RegexpWithNamedGroups = ActionDispatch::Journey::Path::Pattern
end

View File

@@ -0,0 +1,146 @@
require 'action_controller/metal/exceptions'
module ActionDispatch
module Journey
# The Formatter class is used for formatting URLs. For example, parameters
# passed to +url_for+ in rails will eventually call Formatter#generate.
class Formatter # :nodoc:
attr_reader :routes
def initialize(routes)
@routes = routes
@cache = nil
end
def generate(type, name, options, recall = {}, parameterize = nil)
constraints = recall.merge(options)
missing_keys = []
match_route(name, constraints) do |route|
parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize)
next if !name && route.requirements.empty? && route.parts.empty?
missing_keys = missing_keys(route, parameterized_parts)
next unless missing_keys.empty?
params = options.dup.delete_if do |key, _|
parameterized_parts.key?(key) || route.defaults.key?(key)
end
return [route.format(parameterized_parts), params]
end
message = "No route matches #{constraints.inspect}"
message << " missing required keys: #{missing_keys.inspect}" if name
raise ActionController::UrlGenerationError, message
end
def clear
@cache = nil
end
private
def extract_parameterized_parts(route, options, recall, parameterize = nil)
parameterized_parts = recall.merge(options)
keys_to_keep = route.parts.reverse.drop_while { |part|
!options.key?(part) || (options[part] || recall[part]).nil?
} | route.required_parts
(parameterized_parts.keys - keys_to_keep).each do |bad_key|
parameterized_parts.delete(bad_key)
end
if parameterize
parameterized_parts.each do |k, v|
parameterized_parts[k] = parameterize.call(k, v)
end
end
parameterized_parts.keep_if { |_, v| v }
parameterized_parts
end
def named_routes
routes.named_routes
end
def match_route(name, options)
if named_routes.key?(name)
yield named_routes[name]
else
routes = non_recursive(cache, options.to_a)
hash = routes.group_by { |_, r| r.score(options) }
hash.keys.sort.reverse_each do |score|
next if score < 0
hash[score].sort_by { |i, _| i }.each do |_, route|
yield route
end
end
end
end
def non_recursive(cache, options)
routes = []
stack = [cache]
while stack.any?
c = stack.shift
routes.concat(c[:___routes]) if c.key?(:___routes)
options.each do |pair|
stack << c[pair] if c.key?(pair)
end
end
routes
end
# Returns an array populated with missing keys if any are present.
def missing_keys(route, parts)
missing_keys = []
tests = route.path.requirements
route.required_parts.each { |key|
if tests.key?(key)
missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
else
missing_keys << key unless parts[key]
end
}
missing_keys
end
def possibles(cache, options, depth = 0)
cache.fetch(:___routes) { [] } + options.find_all { |pair|
cache.key?(pair)
}.map { |pair|
possibles(cache[pair], options, depth + 1)
}.flatten(1)
end
# Returns +true+ if no missing keys are present, otherwise +false+.
def verify_required_parts!(route, parts)
missing_keys(route, parts).empty?
end
def build_cache
root = { :___routes => [] }
routes.each_with_index do |route, i|
leaf = route.required_defaults.inject(root) do |h, tuple|
h[tuple] ||= {}
end
(leaf[:___routes] ||= []) << [i, route]
end
root
end
def cache
@cache ||= build_cache
end
end
end
end

View File

@@ -0,0 +1,162 @@
require 'action_dispatch/journey/gtg/transition_table'
module ActionDispatch
module Journey # :nodoc:
module GTG # :nodoc:
class Builder # :nodoc:
DUMMY = Nodes::Dummy.new
attr_reader :root, :ast, :endpoints
def initialize(root)
@root = root
@ast = Nodes::Cat.new root, DUMMY
@followpos = nil
end
def transition_table
dtrans = TransitionTable.new
marked = {}
state_id = Hash.new { |h,k| h[k] = h.length }
start = firstpos(root)
dstates = [start]
until dstates.empty?
s = dstates.shift
next if marked[s]
marked[s] = true # mark s
s.group_by { |state| symbol(state) }.each do |sym, ps|
u = ps.map { |l| followpos(l) }.flatten
next if u.empty?
if u.uniq == [DUMMY]
from = state_id[s]
to = state_id[Object.new]
dtrans[from, to] = sym
dtrans.add_accepting(to)
ps.each { |state| dtrans.add_memo(to, state.memo) }
else
dtrans[state_id[s], state_id[u]] = sym
if u.include?(DUMMY)
to = state_id[u]
accepting = ps.find_all { |l| followpos(l).include?(DUMMY) }
accepting.each { |accepting_state|
dtrans.add_memo(to, accepting_state.memo)
}
dtrans.add_accepting(state_id[u])
end
end
dstates << u
end
end
dtrans
end
def nullable?(node)
case node
when Nodes::Group
true
when Nodes::Star
true
when Nodes::Or
node.children.any? { |c| nullable?(c) }
when Nodes::Cat
nullable?(node.left) && nullable?(node.right)
when Nodes::Terminal
!node.left
when Nodes::Unary
nullable?(node.left)
else
raise ArgumentError, 'unknown nullable: %s' % node.class.name
end
end
def firstpos(node)
case node
when Nodes::Star
firstpos(node.left)
when Nodes::Cat
if nullable?(node.left)
firstpos(node.left) | firstpos(node.right)
else
firstpos(node.left)
end
when Nodes::Or
node.children.map { |c| firstpos(c) }.flatten.uniq
when Nodes::Unary
firstpos(node.left)
when Nodes::Terminal
nullable?(node) ? [] : [node]
else
raise ArgumentError, 'unknown firstpos: %s' % node.class.name
end
end
def lastpos(node)
case node
when Nodes::Star
firstpos(node.left)
when Nodes::Or
node.children.map { |c| lastpos(c) }.flatten.uniq
when Nodes::Cat
if nullable?(node.right)
lastpos(node.left) | lastpos(node.right)
else
lastpos(node.right)
end
when Nodes::Terminal
nullable?(node) ? [] : [node]
when Nodes::Unary
lastpos(node.left)
else
raise ArgumentError, 'unknown lastpos: %s' % node.class.name
end
end
def followpos(node)
followpos_table[node]
end
private
def followpos_table
@followpos ||= build_followpos
end
def build_followpos
table = Hash.new { |h, k| h[k] = [] }
@ast.each do |n|
case n
when Nodes::Cat
lastpos(n.left).each do |i|
table[i] += firstpos(n.right)
end
when Nodes::Star
lastpos(n).each do |i|
table[i] += firstpos(n)
end
end
end
table
end
def symbol(edge)
case edge
when Journey::Nodes::Symbol
edge.regexp
else
edge.left
end
end
end
end
end
end

View File

@@ -0,0 +1,44 @@
require 'strscan'
module ActionDispatch
module Journey # :nodoc:
module GTG # :nodoc:
class MatchData # :nodoc:
attr_reader :memos
def initialize(memos)
@memos = memos
end
end
class Simulator # :nodoc:
attr_reader :tt
def initialize(transition_table)
@tt = transition_table
end
def simulate(string)
input = StringScanner.new(string)
state = [0]
while sym = input.scan(%r([/.?]|[^/.?]+))
state = tt.move(state, sym)
end
acceptance_states = state.find_all { |s|
tt.accepting? s
}
return if acceptance_states.empty?
memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
MatchData.new(memos)
end
alias :=~ :simulate
alias :match :simulate
end
end
end
end

View File

@@ -0,0 +1,156 @@
require 'action_dispatch/journey/nfa/dot'
module ActionDispatch
module Journey # :nodoc:
module GTG # :nodoc:
class TransitionTable # :nodoc:
include Journey::NFA::Dot
attr_reader :memos
def initialize
@regexp_states = Hash.new { |h,k| h[k] = {} }
@string_states = Hash.new { |h,k| h[k] = {} }
@accepting = {}
@memos = Hash.new { |h,k| h[k] = [] }
end
def add_accepting(state)
@accepting[state] = true
end
def accepting_states
@accepting.keys
end
def accepting?(state)
@accepting[state]
end
def add_memo(idx, memo)
@memos[idx] << memo
end
def memo(idx)
@memos[idx]
end
def eclosure(t)
Array(t)
end
def move(t, a)
move_string(t, a).concat(move_regexp(t, a))
end
def to_json
require 'json'
simple_regexp = Hash.new { |h,k| h[k] = {} }
@regexp_states.each do |from, hash|
hash.each do |re, to|
simple_regexp[from][re.source] = to
end
end
JSON.dump({
:regexp_states => simple_regexp,
:string_states => @string_states,
:accepting => @accepting
})
end
def to_svg
svg = IO.popen('dot -Tsvg', 'w+') { |f|
f.write(to_dot)
f.close_write
f.readlines
}
3.times { svg.shift }
svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '')
end
def visualizer(paths, title = 'FSM')
viz_dir = File.join File.dirname(__FILE__), '..', 'visualizer'
fsm_js = File.read File.join(viz_dir, 'fsm.js')
fsm_css = File.read File.join(viz_dir, 'fsm.css')
erb = File.read File.join(viz_dir, 'index.html.erb')
states = "function tt() { return #{to_json}; }"
fun_routes = paths.shuffle.first(3).map do |ast|
ast.map { |n|
case n
when Nodes::Symbol
case n.left
when ':id' then rand(100).to_s
when ':format' then %w{ xml json }.shuffle.first
else
'omg'
end
when Nodes::Terminal then n.symbol
else
nil
end
}.compact.join
end
stylesheets = [fsm_css]
svg = to_svg
javascripts = [states, fsm_js]
# Annoying hack for 1.9 warnings
fun_routes = fun_routes
stylesheets = stylesheets
svg = svg
javascripts = javascripts
require 'erb'
template = ERB.new erb
template.result(binding)
end
def []=(from, to, sym)
case sym
when String
@string_states[from][sym] = to
when Regexp
@regexp_states[from][sym] = to
else
raise ArgumentError, 'unknown symbol: %s' % sym.class
end
end
def states
ss = @string_states.keys + @string_states.values.map(&:values).flatten
rs = @regexp_states.keys + @regexp_states.values.map(&:values).flatten
(ss + rs).uniq
end
def transitions
@string_states.map { |from, hash|
hash.map { |s, to| [from, s, to] }
}.flatten(1) + @regexp_states.map { |from, hash|
hash.map { |s, to| [from, s, to] }
}.flatten(1)
end
private
def move_regexp(t, a)
return [] if t.empty?
t.map { |s|
@regexp_states[s].map { |re, v| re === a ? v : nil }
}.flatten.compact.uniq
end
def move_string(t, a)
return [] if t.empty?
t.map { |s| @string_states[s][a] }.compact
end
end
end
end
end

View File

@@ -0,0 +1,76 @@
require 'action_dispatch/journey/nfa/transition_table'
require 'action_dispatch/journey/gtg/transition_table'
module ActionDispatch
module Journey # :nodoc:
module NFA # :nodoc:
class Visitor < Visitors::Visitor # :nodoc:
def initialize(tt)
@tt = tt
@i = -1
end
def visit_CAT(node)
left = visit(node.left)
right = visit(node.right)
@tt.merge(left.last, right.first)
[left.first, right.last]
end
def visit_GROUP(node)
from = @i += 1
left = visit(node.left)
to = @i += 1
@tt.accepting = to
@tt[from, left.first] = nil
@tt[left.last, to] = nil
@tt[from, to] = nil
[from, to]
end
def visit_OR(node)
from = @i += 1
children = node.children.map { |c| visit(c) }
to = @i += 1
children.each do |child|
@tt[from, child.first] = nil
@tt[child.last, to] = nil
end
@tt.accepting = to
[from, to]
end
def terminal(node)
from_i = @i += 1 # new state
to_i = @i += 1 # new state
@tt[from_i, to_i] = node
@tt.accepting = to_i
@tt.add_memo(to_i, node.memo)
[from_i, to_i]
end
end
class Builder # :nodoc:
def initialize(ast)
@ast = ast
end
def transition_table
tt = TransitionTable.new
Visitor.new(tt).accept(@ast)
tt
end
end
end
end
end

View File

@@ -0,0 +1,36 @@
# encoding: utf-8
module ActionDispatch
module Journey # :nodoc:
module NFA # :nodoc:
module Dot # :nodoc:
def to_dot
edges = transitions.map { |from, sym, to|
" #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
}
#memo_nodes = memos.values.flatten.map { |n|
# label = n
# if Journey::Route === n
# label = "#{n.verb.source} #{n.path.spec}"
# end
# " #{n.object_id} [label=\"#{label}\", shape=box];"
#}
#memo_edges = memos.map { |k, memos|
# (memos || []).map { |v| " #{k} -> #{v.object_id};" }
#}.flatten.uniq
<<-eodot
digraph nfa {
rankdir=LR;
node [shape = doublecircle];
#{accepting_states.join ' '};
node [shape = circle];
#{edges.join "\n"}
}
eodot
end
end
end
end
end

View File

@@ -0,0 +1,47 @@
require 'strscan'
module ActionDispatch
module Journey # :nodoc:
module NFA # :nodoc:
class MatchData # :nodoc:
attr_reader :memos
def initialize(memos)
@memos = memos
end
end
class Simulator # :nodoc:
attr_reader :tt
def initialize(transition_table)
@tt = transition_table
end
def simulate(string)
input = StringScanner.new(string)
state = tt.eclosure(0)
until input.eos?
sym = input.scan(%r([/.?]|[^/.?]+))
# FIXME: tt.eclosure is not needed for the GTG
state = tt.eclosure(tt.move(state, sym))
end
acceptance_states = state.find_all { |s|
tt.accepting?(tt.eclosure(s).sort.last)
}
return if acceptance_states.empty?
memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
MatchData.new(memos)
end
alias :=~ :simulate
alias :match :simulate
end
end
end
end

View File

@@ -0,0 +1,163 @@
require 'action_dispatch/journey/nfa/dot'
module ActionDispatch
module Journey # :nodoc:
module NFA # :nodoc:
class TransitionTable # :nodoc:
include Journey::NFA::Dot
attr_accessor :accepting
attr_reader :memos
def initialize
@table = Hash.new { |h,f| h[f] = {} }
@memos = {}
@accepting = nil
@inverted = nil
end
def accepting?(state)
accepting == state
end
def accepting_states
[accepting]
end
def add_memo(idx, memo)
@memos[idx] = memo
end
def memo(idx)
@memos[idx]
end
def []=(i, f, s)
@table[f][i] = s
end
def merge(left, right)
@memos[right] = @memos.delete(left)
@table[right] = @table.delete(left)
end
def states
(@table.keys + @table.values.map(&:keys).flatten).uniq
end
# Returns a generalized transition graph with reduced states. The states
# are reduced like a DFA, but the table must be simulated like an NFA.
#
# Edges of the GTG are regular expressions.
def generalized_table
gt = GTG::TransitionTable.new
marked = {}
state_id = Hash.new { |h,k| h[k] = h.length }
alphabet = self.alphabet
stack = [eclosure(0)]
until stack.empty?
state = stack.pop
next if marked[state] || state.empty?
marked[state] = true
alphabet.each do |alpha|
next_state = eclosure(following_states(state, alpha))
next if next_state.empty?
gt[state_id[state], state_id[next_state]] = alpha
stack << next_state
end
end
final_groups = state_id.keys.find_all { |s|
s.sort.last == accepting
}
final_groups.each do |states|
id = state_id[states]
gt.add_accepting(id)
save = states.find { |s|
@memos.key?(s) && eclosure(s).sort.last == accepting
}
gt.add_memo(id, memo(save))
end
gt
end
# Returns set of NFA states to which there is a transition on ast symbol
# +a+ from some state +s+ in +t+.
def following_states(t, a)
Array(t).map { |s| inverted[s][a] }.flatten.uniq
end
# Returns set of NFA states to which there is a transition on ast symbol
# +a+ from some state +s+ in +t+.
def move(t, a)
Array(t).map { |s|
inverted[s].keys.compact.find_all { |sym|
sym === a
}.map { |sym| inverted[s][sym] }
}.flatten.uniq
end
def alphabet
inverted.values.map(&:keys).flatten.compact.uniq.sort_by { |x| x.to_s }
end
# Returns a set of NFA states reachable from some NFA state +s+ in set
# +t+ on nil-transitions alone.
def eclosure(t)
stack = Array(t)
seen = {}
children = []
until stack.empty?
s = stack.pop
next if seen[s]
seen[s] = true
children << s
stack.concat(inverted[s][nil])
end
children.uniq
end
def transitions
@table.map { |to, hash|
hash.map { |from, sym| [from, sym, to] }
}.flatten(1)
end
private
def inverted
return @inverted if @inverted
@inverted = Hash.new { |h, from|
h[from] = Hash.new { |j, s| j[s] = [] }
}
@table.each { |to, hash|
hash.each { |from, sym|
if sym
sym = Nodes::Symbol === sym ? sym.regexp : sym.left
end
@inverted[from][sym] << to
}
}
@inverted
end
end
end
end
end

View File

@@ -0,0 +1,124 @@
require 'action_dispatch/journey/visitors'
module ActionDispatch
module Journey # :nodoc:
module Nodes # :nodoc:
class Node # :nodoc:
include Enumerable
attr_accessor :left, :memo
def initialize(left)
@left = left
@memo = nil
end
def each(&block)
Visitors::Each.new(block).accept(self)
end
def to_s
Visitors::String.new.accept(self)
end
def to_dot
Visitors::Dot.new.accept(self)
end
def to_sym
name.to_sym
end
def name
left.tr '*:', ''
end
def type
raise NotImplementedError
end
def symbol?; false; end
def literal?; false; end
end
class Terminal < Node # :nodoc:
alias :symbol :left
end
class Literal < Terminal # :nodoc:
def literal?; true; end
def type; :LITERAL; end
end
class Dummy < Literal # :nodoc:
def initialize(x = Object.new)
super
end
def literal?; false; end
end
%w{ Symbol Slash Dot }.each do |t|
class_eval <<-eoruby, __FILE__, __LINE__ + 1
class #{t} < Terminal;
def type; :#{t.upcase}; end
end
eoruby
end
class Symbol < Terminal # :nodoc:
attr_accessor :regexp
alias :symbol :regexp
DEFAULT_EXP = /[^\.\/\?]+/
def initialize(left)
super
@regexp = DEFAULT_EXP
end
def default_regexp?
regexp == DEFAULT_EXP
end
def symbol?; true; end
end
class Unary < Node # :nodoc:
def children; [left] end
end
class Group < Unary # :nodoc:
def type; :GROUP; end
end
class Star < Unary # :nodoc:
def type; :STAR; end
end
class Binary < Node # :nodoc:
attr_accessor :right
def initialize(left, right)
super(left)
@right = right
end
def children; [left, right] end
end
class Cat < Binary # :nodoc:
def type; :CAT; end
end
class Or < Node # :nodoc:
attr_reader :children
def initialize(children)
@children = children
end
def type; :OR; end
end
end
end
end

View File

@@ -0,0 +1,206 @@
#
# DO NOT MODIFY!!!!
# This file is automatically generated by Racc 1.4.9
# from Racc grammer file "".
#
require 'racc/parser.rb'
require 'action_dispatch/journey/parser_extras'
module ActionDispatch
module Journey # :nodoc:
class Parser < Racc::Parser # :nodoc:
##### State transition tables begin ###
racc_action_table = [
17, 21, 13, 15, 14, 7, nil, 16, 8, 19,
13, 15, 14, 7, 23, 16, 8, 19, 13, 15,
14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
16, 8, 13, 15, 14, 7, nil, 16, 8 ]
racc_action_check = [
1, 17, 1, 1, 1, 1, nil, 1, 1, 1,
20, 20, 20, 20, 20, 20, 20, 20, 7, 7,
7, 7, nil, 7, 7, 19, 19, 19, 19, nil,
19, 19, 0, 0, 0, 0, nil, 0, 0 ]
racc_action_pointer = [
30, 0, nil, nil, nil, nil, nil, 16, nil, nil,
nil, nil, nil, nil, nil, nil, nil, 1, nil, 23,
8, nil, nil, nil ]
racc_action_default = [
-18, -18, -2, -3, -4, -5, -6, -18, -9, -10,
-11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
-18, 24, -8, -7 ]
racc_goto_table = [
18, 1, nil, nil, nil, nil, nil, nil, 20, nil,
nil, nil, nil, nil, nil, nil, nil, nil, 22, 18 ]
racc_goto_check = [
2, 1, nil, nil, nil, nil, nil, nil, 1, nil,
nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
racc_goto_pointer = [
nil, 1, -1, nil, nil, nil, nil, nil, nil, nil,
nil ]
racc_goto_default = [
nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
12 ]
racc_reduce_table = [
0, 0, :racc_error,
2, 11, :_reduce_1,
1, 11, :_reduce_2,
1, 11, :_reduce_none,
1, 12, :_reduce_none,
1, 12, :_reduce_none,
1, 12, :_reduce_none,
3, 15, :_reduce_7,
3, 13, :_reduce_8,
1, 16, :_reduce_9,
1, 14, :_reduce_none,
1, 14, :_reduce_none,
1, 14, :_reduce_none,
1, 14, :_reduce_none,
1, 19, :_reduce_14,
1, 17, :_reduce_15,
1, 18, :_reduce_16,
1, 20, :_reduce_17 ]
racc_reduce_n = 18
racc_shift_n = 24
racc_token_table = {
false => 0,
:error => 1,
:SLASH => 2,
:LITERAL => 3,
:SYMBOL => 4,
:LPAREN => 5,
:RPAREN => 6,
:DOT => 7,
:STAR => 8,
:OR => 9 }
racc_nt_base = 10
racc_use_result_var = true
Racc_arg = [
racc_action_table,
racc_action_check,
racc_action_default,
racc_action_pointer,
racc_goto_table,
racc_goto_check,
racc_goto_default,
racc_goto_pointer,
racc_nt_base,
racc_reduce_table,
racc_token_table,
racc_shift_n,
racc_reduce_n,
racc_use_result_var ]
Racc_token_to_s_table = [
"$end",
"error",
"SLASH",
"LITERAL",
"SYMBOL",
"LPAREN",
"RPAREN",
"DOT",
"STAR",
"OR",
"$start",
"expressions",
"expression",
"or",
"terminal",
"group",
"star",
"symbol",
"literal",
"slash",
"dot" ]
Racc_debug_parser = false
##### State transition tables end #####
# reduce 0 omitted
def _reduce_1(val, _values, result)
result = Cat.new(val.first, val.last)
result
end
def _reduce_2(val, _values, result)
result = val.first
result
end
# reduce 3 omitted
# reduce 4 omitted
# reduce 5 omitted
# reduce 6 omitted
def _reduce_7(val, _values, result)
result = Group.new(val[1])
result
end
def _reduce_8(val, _values, result)
result = Or.new([val.first, val.last])
result
end
def _reduce_9(val, _values, result)
result = Star.new(Symbol.new(val.last))
result
end
# reduce 10 omitted
# reduce 11 omitted
# reduce 12 omitted
# reduce 13 omitted
def _reduce_14(val, _values, result)
result = Slash.new('/')
result
end
def _reduce_15(val, _values, result)
result = Symbol.new(val.first)
result
end
def _reduce_16(val, _values, result)
result = Literal.new(val.first)
result
end
def _reduce_17(val, _values, result)
result = Dot.new(val.first)
result
end
def _reduce_none(val, _values, result)
val[0]
end
end # class Parser
end # module Journey
end # module ActionDispatch

View File

@@ -0,0 +1,47 @@
class ActionDispatch::Journey::Parser
token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
rule
expressions
: expressions expression { result = Cat.new(val.first, val.last) }
| expression { result = val.first }
| or
;
expression
: terminal
| group
| star
;
group
: LPAREN expressions RPAREN { result = Group.new(val[1]) }
;
or
: expressions OR expression { result = Or.new([val.first, val.last]) }
;
star
: STAR { result = Star.new(Symbol.new(val.last)) }
;
terminal
: symbol
| literal
| slash
| dot
;
slash
: SLASH { result = Slash.new('/') }
;
symbol
: SYMBOL { result = Symbol.new(val.first) }
;
literal
: LITERAL { result = Literal.new(val.first) }
dot
: DOT { result = Dot.new(val.first) }
;
end
---- header
require 'action_dispatch/journey/parser_extras'

View File

@@ -0,0 +1,23 @@
require 'action_dispatch/journey/scanner'
require 'action_dispatch/journey/nodes/node'
module ActionDispatch
module Journey # :nodoc:
class Parser < Racc::Parser # :nodoc:
include Journey::Nodes
def initialize
@scanner = Scanner.new
end
def parse(string)
@scanner.scan_setup(string)
do_parse
end
def next_token
@scanner.next_token
end
end
end
end

View File

@@ -0,0 +1,196 @@
module ActionDispatch
module Journey # :nodoc:
module Path # :nodoc:
class Pattern # :nodoc:
attr_reader :spec, :requirements, :anchored
def initialize(strexp)
parser = Journey::Parser.new
@anchored = true
case strexp
when String
@spec = parser.parse(strexp)
@requirements = {}
@separators = "/.?"
when Router::Strexp
@spec = parser.parse(strexp.path)
@requirements = strexp.requirements
@separators = strexp.separators.join
@anchored = strexp.anchor
else
raise ArgumentError, "Bad expression: #{strexp}"
end
@names = nil
@optional_names = nil
@required_names = nil
@re = nil
@offsets = nil
end
def ast
@spec.grep(Nodes::Symbol).each do |node|
re = @requirements[node.to_sym]
node.regexp = re if re
end
@spec.grep(Nodes::Star).each do |node|
node = node.left
node.regexp = @requirements[node.to_sym] || /(.+)/
end
@spec
end
def names
@names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
end
def required_names
@required_names ||= names - optional_names
end
def optional_names
@optional_names ||= spec.grep(Nodes::Group).map { |group|
group.grep(Nodes::Symbol)
}.flatten.map { |n| n.name }.uniq
end
class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
attr_reader :offsets
def initialize(matchers)
@matchers = matchers
@capture_count = [0]
end
def visit(node)
super
@capture_count
end
def visit_SYMBOL(node)
node = node.to_sym
if @matchers.key?(node)
re = /#{@matchers[node]}|/
@capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
else
@capture_count << (@capture_count.last || 0)
end
end
end
class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
def initialize(separator, matchers)
@separator = separator
@matchers = matchers
@separator_re = "([^#{separator}]+)"
super()
end
def accept(node)
%r{\A#{visit node}\Z}
end
def visit_CAT(node)
[visit(node.left), visit(node.right)].join
end
def visit_SYMBOL(node)
node = node.to_sym
return @separator_re unless @matchers.key?(node)
re = @matchers[node]
"(#{re})"
end
def visit_GROUP(node)
"(?:#{visit node.left})?"
end
def visit_LITERAL(node)
Regexp.escape(node.left)
end
alias :visit_DOT :visit_LITERAL
def visit_SLASH(node)
node.left
end
def visit_STAR(node)
re = @matchers[node.left.to_sym] || '.+'
"(#{re})"
end
end
class UnanchoredRegexp < AnchoredRegexp # :nodoc:
def accept(node)
%r{\A#{visit node}}
end
end
class MatchData # :nodoc:
attr_reader :names
def initialize(names, offsets, match)
@names = names
@offsets = offsets
@match = match
end
def captures
(length - 1).times.map { |i| self[i + 1] }
end
def [](x)
idx = @offsets[x - 1] + x
@match[idx]
end
def length
@offsets.length
end
def post_match
@match.post_match
end
def to_s
@match.to_s
end
end
def match(other)
return unless match = to_regexp.match(other)
MatchData.new(names, offsets, match)
end
alias :=~ :match
def source
to_regexp.source
end
def to_regexp
@re ||= regexp_visitor.new(@separators, @requirements).accept spec
end
private
def regexp_visitor
@anchored ? AnchoredRegexp : UnanchoredRegexp
end
def offsets
return @offsets if @offsets
viz = RegexpOffsets.new(@requirements)
@offsets = viz.accept(spec)
end
end
end
end
end

View File

@@ -0,0 +1,120 @@
module ActionDispatch
module Journey # :nodoc:
class Route # :nodoc:
attr_reader :app, :path, :defaults, :name
attr_reader :constraints
alias :conditions :constraints
attr_accessor :precedence
##
# +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route.
def initialize(name, app, path, constraints, defaults = {})
@name = name
@app = app
@path = path
@constraints = constraints
@defaults = defaults
@required_defaults = nil
@required_parts = nil
@parts = nil
@decorated_ast = nil
@precedence = 0
end
def ast
@decorated_ast ||= begin
decorated_ast = path.ast
decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
decorated_ast
end
end
def requirements # :nodoc:
# needed for rails `rake routes`
path.requirements.merge(@defaults).delete_if { |_,v|
/.+?/ == v
}
end
def segments
path.names
end
def required_keys
required_parts + required_defaults.keys
end
def score(constraints)
required_keys = path.required_names
supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
return -1 unless (required_keys - supplied_keys).empty?
score = (supplied_keys & path.names).length
score + (required_defaults.length * 2)
end
def parts
@parts ||= segments.map { |n| n.to_sym }
end
alias :segment_keys :parts
def format(path_options)
path_options.delete_if do |key, value|
value.to_s == defaults[key].to_s && !required_parts.include?(key)
end
Visitors::Formatter.new(path_options).accept(path.spec)
end
def optimized_path
Visitors::OptimizedPath.new.accept(path.spec)
end
def optional_parts
path.optional_names.map { |n| n.to_sym }
end
def required_parts
@required_parts ||= path.required_names.map { |n| n.to_sym }
end
def required_default?(key)
(constraints[:required_defaults] || []).include?(key)
end
def required_defaults
@required_defaults ||= @defaults.dup.delete_if do |k,_|
parts.include?(k) || !required_default?(k)
end
end
def matches?(request)
constraints.all? do |method, value|
next true unless request.respond_to?(method)
case value
when Regexp, String
value === request.send(method).to_s
when Array
value.include?(request.send(method))
else
value === request.send(method)
end
end
end
def ip
constraints[:ip] || //
end
def verb
constraints[:request_method_string] || //
end
end
end
end

View File

@@ -0,0 +1,163 @@
require 'action_dispatch/journey/router/utils'
require 'action_dispatch/journey/router/strexp'
require 'action_dispatch/journey/routes'
require 'action_dispatch/journey/formatter'
before = $-w
$-w = false
require 'action_dispatch/journey/parser'
$-w = before
require 'action_dispatch/journey/route'
require 'action_dispatch/journey/path/pattern'
require 'active_support/core_ext/array/sort_by'
require 'active_support/core_ext/array/select'
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
# :nodoc:
VERSION = '2.0.0'
class NullReq # :nodoc:
attr_reader :env
def initialize(env)
@env = env
end
def request_method_string
env['REQUEST_METHOD']
end
def path_info
env['PATH_INFO']
end
def ip
env['REMOTE_ADDR']
end
def [](k); env[k]; end
end
attr_reader :request_class, :formatter
attr_accessor :routes
def initialize(routes, options)
@options = options
@params_key = options[:parameters_key]
@request_class = options[:request_class] || NullReq
@routes = routes
end
def call(env)
env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
find_routes(env).each do |match, parameters, route|
script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
'PATH_INFO',
@params_key)
unless route.path.anchored
env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
env['PATH_INFO'] = match.post_match
end
env[@params_key] = (set_params || {}).merge parameters
status, headers, body = route.app.call(env)
if 'pass' == headers['X-Cascade']
env['SCRIPT_NAME'] = script_name
env['PATH_INFO'] = path_info
env[@params_key] = set_params
next
end
return [status, headers, body]
end
raise ActionController::RoutingError, "No route matches"
end
def recognize(req)
find_routes(req.env).each do |match, parameters, route|
unless route.path.anchored
req.env['SCRIPT_NAME'] = match.to_s
req.env['PATH_INFO'] = match.post_match.sub(/^([^\/])/, '/\1')
end
yield(route, nil, parameters)
end
end
def visualizer
tt = GTG::Builder.new(ast).transition_table
groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
asts = groups.values.map { |v| v.first }
tt.visualizer(asts)
end
private
def partitioned_routes
routes.partitioned_routes
end
def ast
routes.ast
end
def simulator
routes.simulator
end
def custom_routes
partitioned_routes.last
end
def filter_routes(path)
return [] unless ast
data = simulator.match(path)
data ? data.memos : []
end
def find_routes env
req = request_class.new(env)
routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
r.path.match(req.path_info)
}
routes.concat get_routes_as_head(routes)
routes.sort_by!(&:precedence).select! { |r| r.matches?(req) }
routes.map! { |r|
match_data = r.path.match(req.path_info)
match_names = match_data.names.map { |n| n.to_sym }
match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
info = Hash[match_names.zip(match_values).find_all { |_, y| y }]
[match_data, r.defaults.merge(info), r]
}
end
def get_routes_as_head(routes)
precedence = (routes.map(&:precedence).max || 0) + 1
routes = routes.select { |r|
r.verb === "GET" && !(r.verb === "HEAD")
}.map! { |r|
Route.new(r.name,
r.app,
r.path,
r.conditions.merge(:request_method_string => "HEAD"),
r.defaults).tap do |route|
route.precedence = r.precedence + precedence
end
}
routes.flatten!
routes
end
end
end
end

View File

@@ -0,0 +1,24 @@
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
class Strexp # :nodoc:
class << self
alias :compile :new
end
attr_reader :path, :requirements, :separators, :anchor
def initialize(path, requirements, separators, anchor = true)
@path = path
@requirements = requirements
@separators = separators
@anchor = anchor
end
def names
@path.scan(/:\w+/).map { |s| s.tr(':', '') }
end
end
end
end
end

View File

@@ -0,0 +1,54 @@
require 'uri'
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
class Utils # :nodoc:
# Normalizes URI path.
#
# Strips off trailing slash and ensures there is a leading slash.
#
# normalize_path("/foo") # => "/foo"
# normalize_path("/foo/") # => "/foo"
# normalize_path("foo") # => "/foo"
# normalize_path("") # => "/"
def self.normalize_path(path)
path = "/#{path}"
path.squeeze!('/')
path.sub!(%r{/+\Z}, '')
path = '/' if path == ''
path
end
# URI path and fragment escaping
# http://tools.ietf.org/html/rfc3986
module UriEscape # :nodoc:
# Symbol captures can generate multiple path segments, so include /.
reserved_segment = '/'
reserved_fragment = '/?'
reserved_pchar = ':@&=+$,;%'
safe_pchar = "#{URI::REGEXP::PATTERN::UNRESERVED}#{reserved_pchar}"
safe_segment = "#{safe_pchar}#{reserved_segment}"
safe_fragment = "#{safe_pchar}#{reserved_fragment}"
UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false).freeze
UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
end
Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
def self.escape_path(path)
Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
end
def self.escape_fragment(fragment)
Parser.escape(fragment.to_s, UriEscape::UNSAFE_FRAGMENT)
end
def self.unescape_uri(uri)
Parser.unescape(uri)
end
end
end
end
end

View File

@@ -0,0 +1,75 @@
module ActionDispatch
module Journey # :nodoc:
# The Routing table. Contains all routes for a system. Routes can be
# added to the table by calling Routes#add_route.
class Routes # :nodoc:
include Enumerable
attr_reader :routes, :named_routes
def initialize
@routes = []
@named_routes = {}
@ast = nil
@partitioned_routes = nil
@simulator = nil
end
def length
routes.length
end
alias :size :length
def last
routes.last
end
def each(&block)
routes.each(&block)
end
def clear
routes.clear
end
def partitioned_routes
@partitioned_routes ||= routes.partition do |r|
r.path.anchored && r.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
end
end
def ast
@ast ||= begin
asts = partitioned_routes.first.map(&:ast)
Nodes::Or.new(asts) unless asts.empty?
end
end
def simulator
@simulator ||= begin
gtg = GTG::Builder.new(ast).transition_table
GTG::Simulator.new(gtg)
end
end
# Add a route to the routing table.
def add_route(app, path, conditions, defaults, name = nil)
route = Route.new(name, app, path, conditions, defaults)
route.precedence = routes.length
routes << route
named_routes[name] = route if name && !named_routes[name]
clear_cache!
route
end
private
def clear_cache!
@ast = nil
@partitioned_routes = nil
@simulator = nil
end
end
end
end

View File

@@ -0,0 +1,61 @@
require 'strscan'
module ActionDispatch
module Journey # :nodoc:
class Scanner # :nodoc:
def initialize
@ss = nil
end
def scan_setup(str)
@ss = StringScanner.new(str)
end
def eos?
@ss.eos?
end
def pos
@ss.pos
end
def pre_match
@ss.pre_match
end
def next_token
return if @ss.eos?
until token = scan || @ss.eos?; end
token
end
private
def scan
case
# /
when text = @ss.scan(/\//)
[:SLASH, text]
when text = @ss.scan(/\*\w+/)
[:STAR, text]
when text = @ss.scan(/\(/)
[:LPAREN, text]
when text = @ss.scan(/\)/)
[:RPAREN, text]
when text = @ss.scan(/\|/)
[:OR, text]
when text = @ss.scan(/\./)
[:DOT, text]
when text = @ss.scan(/:\w+/)
[:SYMBOL, text]
when text = @ss.scan(/[\w%\-~]+/)
[:LITERAL, text]
# any char
when text = @ss.scan(/./)
[:LITERAL, text]
end
end
end
end
end

View File

@@ -0,0 +1,197 @@
# encoding: utf-8
module ActionDispatch
module Journey # :nodoc:
module Visitors # :nodoc:
class Visitor # :nodoc:
DISPATCH_CACHE = Hash.new { |h,k|
h[k] = "visit_#{k}"
}
def accept(node)
visit(node)
end
private
def visit node
send(DISPATCH_CACHE[node.type], node)
end
def binary(node)
visit(node.left)
visit(node.right)
end
def visit_CAT(n); binary(n); end
def nary(node)
node.children.each { |c| visit(c) }
end
def visit_OR(n); nary(n); end
def unary(node)
visit(node.left)
end
def visit_GROUP(n); unary(n); end
def visit_STAR(n); unary(n); end
def terminal(node); end
%w{ LITERAL SYMBOL SLASH DOT }.each do |t|
class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
end
end
# Loop through the requirements AST
class Each < Visitor # :nodoc:
attr_reader :block
def initialize(block)
@block = block
end
def visit(node)
super
block.call(node)
end
end
class String < Visitor # :nodoc:
private
def binary(node)
[visit(node.left), visit(node.right)].join
end
def nary(node)
node.children.map { |c| visit(c) }.join '|'
end
def terminal(node)
node.left
end
def visit_GROUP(node)
"(#{visit(node.left)})"
end
end
class OptimizedPath < String # :nodoc:
private
def visit_GROUP(node)
""
end
end
# Used for formatting urls (url_for)
class Formatter < Visitor # :nodoc:
attr_reader :options, :consumed
def initialize(options)
@options = options
@consumed = {}
end
private
def visit_GROUP(node)
if consumed == options
nil
else
route = visit(node.left)
route.include?("\0") ? nil : route
end
end
def terminal(node)
node.left
end
def binary(node)
[visit(node.left), visit(node.right)].join
end
def nary(node)
node.children.map { |c| visit(c) }.join
end
def visit_SYMBOL(node)
key = node.to_sym
if value = options[key]
consumed[key] = value
Router::Utils.escape_path(value)
else
"\0"
end
end
end
class Dot < Visitor # :nodoc:
def initialize
@nodes = []
@edges = []
end
def accept(node)
super
<<-eodot
digraph parse_tree {
size="8,5"
node [shape = none];
edge [dir = none];
#{@nodes.join "\n"}
#{@edges.join("\n")}
}
eodot
end
private
def binary(node)
node.children.each do |c|
@edges << "#{node.object_id} -> #{c.object_id};"
end
super
end
def nary(node)
node.children.each do |c|
@edges << "#{node.object_id} -> #{c.object_id};"
end
super
end
def unary(node)
@edges << "#{node.object_id} -> #{node.left.object_id};"
super
end
def visit_GROUP(node)
@nodes << "#{node.object_id} [label=\"()\"];"
super
end
def visit_CAT(node)
@nodes << "#{node.object_id} [label=\"\"];"
super
end
def visit_STAR(node)
@nodes << "#{node.object_id} [label=\"*\"];"
super
end
def visit_OR(node)
@nodes << "#{node.object_id} [label=\"|\"];"
super
end
def terminal(node)
value = node.left
@nodes << "#{node.object_id} [label=\"#{value}\"];"
end
end
end
end
end

View File

@@ -0,0 +1,34 @@
body {
font-family: "Helvetica Neue", Helvetica, Arial, Sans-Serif;
margin: 0;
}
h1 {
font-size: 2.0em; font-weight: bold; text-align: center;
color: white; background-color: black;
padding: 5px 0;
margin: 0 0 20px;
}
h2 {
text-align: center;
display: none;
font-size: 0.5em;
}
div#chart-2 {
height: 350px;
}
.clearfix {display: inline-block; }
.input { overflow: show;}
.instruction { color: #666; padding: 0 30px 20px; font-size: 0.9em}
.instruction p { padding: 0 0 5px; }
.instruction li { padding: 0 10px 5px; }
.form { background: #EEE; padding: 20px 30px; border-radius: 5px; margin-left: auto; margin-right: auto; width: 500px; margin-bottom: 20px}
.form p, .form form { text-align: center }
.form form {padding: 0 10px 5px; }
.form .fun_routes { font-size: 0.9em;}
.form .fun_routes a { margin: 0 5px 0 0; }

View File

@@ -0,0 +1,134 @@
function tokenize(input, callback) {
while(input.length > 0) {
callback(input.match(/^[\/\.\?]|[^\/\.\?]+/)[0]);
input = input.replace(/^[\/\.\?]|[^\/\.\?]+/, '');
}
}
var graph = d3.select("#chart-2 svg");
var svg_edges = {};
var svg_nodes = {};
graph.selectAll("g.edge").each(function() {
var node = d3.select(this);
var index = node.select("title").text().split("->");
var left = parseInt(index[0]);
var right = parseInt(index[1]);
if(!svg_edges[left]) { svg_edges[left] = {} }
svg_edges[left][right] = node;
});
graph.selectAll("g.node").each(function() {
var node = d3.select(this);
var index = parseInt(node.select("title").text());
svg_nodes[index] = node;
});
function reset_graph() {
for(var key in svg_edges) {
for(var mkey in svg_edges[key]) {
var node = svg_edges[key][mkey];
var path = node.select("path");
var arrow = node.select("polygon");
path.style("stroke", "black");
arrow.style("stroke", "black").style("fill", "black");
}
}
for(var key in svg_nodes) {
var node = svg_nodes[key];
node.select('ellipse').style("fill", "white");
node.select('polygon').style("fill", "white");
}
return false;
}
function highlight_edge(from, to) {
var node = svg_edges[from][to];
var path = node.select("path");
var arrow = node.select("polygon");
path
.transition().duration(500)
.style("stroke", "green");
arrow
.transition().duration(500)
.style("stroke", "green").style("fill", "green");
}
function highlight_state(index, color) {
if(!color) { color = "green"; }
svg_nodes[index].select('ellipse')
.style("fill", "white")
.transition().duration(500)
.style("fill", color);
}
function highlight_finish(index) {
svg_nodes[index].select('polygon')
.style("fill", "while")
.transition().duration(500)
.style("fill", "blue");
}
function match(input) {
reset_graph();
var table = tt();
var states = [0];
var regexp_states = table['regexp_states'];
var string_states = table['string_states'];
var accepting = table['accepting'];
highlight_state(0);
tokenize(input, function(token) {
var new_states = [];
for(var key in states) {
var state = states[key];
if(string_states[state] && string_states[state][token]) {
var new_state = string_states[state][token];
highlight_edge(state, new_state);
highlight_state(new_state);
new_states.push(new_state);
}
if(regexp_states[state]) {
for(var key in regexp_states[state]) {
var re = new RegExp("^" + key + "$");
if(re.test(token)) {
var new_state = regexp_states[state][key];
highlight_edge(state, new_state);
highlight_state(new_state);
new_states.push(new_state);
}
}
}
}
if(new_states.length == 0) {
return;
}
states = new_states;
});
for(var key in states) {
var state = states[key];
if(accepting[state]) {
for(var mkey in svg_edges[state]) {
if(!regexp_states[mkey] && !string_states[mkey]) {
highlight_edge(state, mkey);
highlight_finish(mkey);
}
}
} else {
highlight_state(state, "red");
}
}
return false;
}

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="https://raw.github.com/gist/1706081/af944401f75ea20515a02ddb3fb43d23ecb8c662/reset.css" type="text/css">
<style>
<% stylesheets.each do |style| %>
<%= style %>
<% end %>
</style>
<script src="https://raw.github.com/gist/1706081/df464722a05c3c2bec450b7b5c8240d9c31fa52d/d3.min.js" type="text/javascript"></script>
</head>
<body>
<div id="wrapper">
<h1>Routes FSM with NFA simulation</h1>
<div class="instruction form">
<p>
Type a route in to the box and click "simulate".
</p>
<form onsubmit="return match(this.route.value);">
<input type="text" size="30" name="route" value="/articles/new" />
<button>simulate</button>
<input type="reset" value="reset" onclick="return reset_graph();"/>
</form>
<p class="fun_routes">
Some fun routes to try:
<% fun_routes.each do |path| %>
<a href="#" onclick="document.forms[0].elements[0].value=this.text.replace(/^\s+|\s+$/g,''); return match(this.text.replace(/^\s+|\s+$/g,''));">
<%= path %>
</a>
<% end %>
</p>
</div>
<div class='chart' id='chart-2'>
<%= svg %>
</div>
<div class="instruction">
<p>
This is a FSM for a system that has the following routes:
</p>
<ul>
<% paths.each do |route| %>
<li><%= route %></li>
<% end %>
</ul>
</div>
</div>
<% javascripts.each do |js| %>
<script><%= js %></script>
<% end %>
</body>
</html>

View File

@@ -768,7 +768,11 @@ module ActionView
options = options.stringify_keys
tag_value = options.delete("value")
name_and_id = options.dup
name_and_id["id"] = name_and_id["for"]
if name_and_id.has_key?("for")
name_and_id["id"] = name_and_id["for"]
else
name_and_id.delete("id")
end
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options["for"] ||= name_and_id["id"]
@@ -928,15 +932,15 @@ module ActionView
def add_default_name_and_id(options)
if options.has_key?("index")
options["name"] ||= tag_name_with_index(options["index"])
options["id"] ||= tag_id_with_index(options["index"])
options["name"] = tag_name_with_index(options["index"]) unless options.has_key?("name")
options["id"] = tag_id_with_index(options["index"]) unless options.has_key?("id")
options.delete("index")
elsif defined?(@auto_index)
options["name"] ||= tag_name_with_index(@auto_index)
options["id"] ||= tag_id_with_index(@auto_index)
options["name"] = tag_name_with_index(@auto_index) unless options.has_key?("name")
options["id"] = tag_id_with_index(@auto_index) unless options.has_key?("id")
else
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
options["id"] ||= tag_id
options["name"] = tag_name + (options.has_key?('multiple') ? '[]' : '') unless options.has_key?("name")
options["id"] = tag_id unless options.has_key?("id")
end
end

View File

@@ -1,4 +1,5 @@
#require 'action_view/helpers/javascript_helper'
require 'active_support/concern'
module ActionView
module Helpers #:nodoc:
@@ -9,6 +10,25 @@ 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
def _routes_context
@controller
end
def optimize_routes_generation?
@controller.respond_to?(:optimize_routes_generation?, true) ?
@controller.optimize_routes_generation? : super
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
@@ -67,20 +87,18 @@ module ActionView
# # if request.env["HTTP_REFERER"] is not set or is blank
# # => javascript:history.back()
def url_for(options = {})
options ||= {}
url = case options
case options
when String
options
when Hash
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
@controller.send(:url_for, options)
when nil, Hash
options ||= {}
options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys)
super
when :back
@controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
polymorphic_path(options)
end
url
end
# Creates a link tag of the given +name+ using a URL created by the set

View File

@@ -41,7 +41,6 @@ module ActionView
include ActionController::TestCase::Assertions
include ActionController::TestProcess
include ActionController::PolymorphicRoutes
include ActionController::RecordIdentifier
include ActionView::Helpers

View File

@@ -1,297 +0,0 @@
require 'abstract_unit'
class Article
attr_reader :id
def save; @id = 1 end
def new_record?; @id.nil? end
def name
model = self.class.name.downcase
@id.nil? ? "new #{model}" : "#{model} ##{@id}"
end
end
class Response < Article
def post_id; 1 end
end
class Tag < Article
def response_id; 1 end
end
class Tax
attr_reader :id
def save; @id = 1 end
def new_record?; @id.nil? end
def name
model = self.class.name.downcase
@id.nil? ? "new #{model}" : "#{model} ##{@id}"
end
end
class Fax < Tax
def store_id; 1 end
end
# TODO: test nested models
class Response::Nested < Response; end
class PolymorphicRoutesTest < ActiveSupport::TestCase
include ActionController::PolymorphicRoutes
def setup
@article = Article.new
@response = Response.new
@tax = Tax.new
@fax = Fax.new
end
def test_with_record
@article.save
expects(:article_url).with(@article)
polymorphic_url(@article)
end
def test_with_new_record
expects(:articles_url).with()
@article.expects(:new_record?).returns(true)
polymorphic_url(@article)
end
def test_with_record_and_action
expects(:new_article_url).with()
@article.expects(:new_record?).never
polymorphic_url(@article, :action => 'new')
end
def test_url_helper_prefixed_with_new
expects(:new_article_url).with()
new_polymorphic_url(@article)
end
def test_url_helper_prefixed_with_edit
@article.save
expects(:edit_article_url).with(@article)
edit_polymorphic_url(@article)
end
def test_url_helper_prefixed_with_edit_with_url_options
@article.save
expects(:edit_article_url).with(@article, :param1 => '10')
edit_polymorphic_url(@article, :param1 => '10')
end
def test_url_helper_with_url_options
@article.save
expects(:article_url).with(@article, :param1 => '10')
polymorphic_url(@article, :param1 => '10')
end
def test_formatted_url_helper_is_deprecated
expects(:articles_url).with(:format => :pdf)
assert_deprecated do
formatted_polymorphic_url([@article, :pdf])
end
end
def test_format_option
@article.save
expects(:article_url).with(@article, :format => :pdf)
polymorphic_url(@article, :format => :pdf)
end
def test_format_option_with_url_options
@article.save
expects(:article_url).with(@article, :format => :pdf, :param1 => '10')
polymorphic_url(@article, :format => :pdf, :param1 => '10')
end
def test_id_and_format_option
@article.save
expects(:article_url).with(:id => @article, :format => :pdf)
polymorphic_url(:id => @article, :format => :pdf)
end
def test_with_nested
@response.save
expects(:article_response_url).with(@article, @response)
polymorphic_url([@article, @response])
end
def test_with_nested_unsaved
expects(:article_responses_url).with(@article)
polymorphic_url([@article, @response])
end
def test_new_with_array_and_namespace
expects(:new_admin_article_url).with()
polymorphic_url([:admin, @article], :action => 'new')
end
def test_unsaved_with_array_and_namespace
expects(:admin_articles_url).with()
polymorphic_url([:admin, @article])
end
def test_nested_unsaved_with_array_and_namespace
@article.save
expects(:admin_article_url).with(@article)
polymorphic_url([:admin, @article])
expects(:admin_article_responses_url).with(@article)
polymorphic_url([:admin, @article, @response])
end
def test_nested_with_array_and_namespace
@response.save
expects(:admin_article_response_url).with(@article, @response)
polymorphic_url([:admin, @article, @response])
# a ridiculously long named route tests correct ordering of namespaces and nesting:
@tag = Tag.new
@tag.save
expects(:site_admin_article_response_tag_url).with(@article, @response, @tag)
polymorphic_url([:site, :admin, @article, @response, @tag])
end
def test_nesting_with_array_ending_in_singleton_resource
expects(:article_response_url).with(@article)
polymorphic_url([@article, :response])
end
def test_nesting_with_array_containing_singleton_resource
@tag = Tag.new
@tag.save
expects(:article_response_tag_url).with(@article, @tag)
polymorphic_url([@article, :response, @tag])
end
def test_nesting_with_array_containing_namespace_and_singleton_resource
@tag = Tag.new
@tag.save
expects(:admin_article_response_tag_url).with(@article, @tag)
polymorphic_url([:admin, @article, :response, @tag])
end
def test_nesting_with_array_containing_singleton_resource_and_format
@tag = Tag.new
@tag.save
expects(:article_response_tag_url).with(@article, @tag, :format => :pdf)
polymorphic_url([@article, :response, @tag], :format => :pdf)
end
def test_nesting_with_array_containing_singleton_resource_and_format_option
@tag = Tag.new
@tag.save
expects(:article_response_tag_url).with(@article, @tag, :format => :pdf)
polymorphic_url([@article, :response, @tag], :format => :pdf)
end
def test_nesting_with_array_containing_nil
expects(:article_response_url).with(@article)
polymorphic_url([@article, nil, :response])
end
def test_with_array_containing_single_object
@article.save
expects(:article_url).with(@article)
polymorphic_url([nil, @article])
end
def test_with_array_containing_single_name
@article.save
expects(:articles_url)
polymorphic_url([:articles])
end
# TODO: Needs to be updated to correctly know about whether the object is in a hash or not
def xtest_with_hash
expects(:article_url).with(@article)
@article.save
polymorphic_url(:id => @article)
end
def test_polymorphic_path_accepts_options
expects(:new_article_path).with()
polymorphic_path(@article, :action => :new)
end
def test_polymorphic_path_does_not_modify_arguments
expects(:admin_article_responses_url).with(@article)
path = [:admin, @article, @response]
assert_no_difference 'path.size' do
polymorphic_url(path)
end
end
# Tests for names where .plural.singular doesn't round-trip
def test_with_irregular_plural_record
@tax.save
expects(:taxis_url).with(@tax)
polymorphic_url(@tax)
end
def test_with_irregular_plural_new_record
expects(:taxes_url).with()
@tax.expects(:new_record?).returns(true)
polymorphic_url(@tax)
end
def test_with_irregular_plural_record_and_action
expects(:new_taxis_url).with()
@tax.expects(:new_record?).never
polymorphic_url(@tax, :action => 'new')
end
def test_irregular_plural_url_helper_prefixed_with_new
expects(:new_taxis_url).with()
new_polymorphic_url(@tax)
end
def test_irregular_plural_url_helper_prefixed_with_edit
@tax.save
expects(:edit_taxis_url).with(@tax)
edit_polymorphic_url(@tax)
end
def test_with_nested_irregular_plurals
@fax.save
expects(:taxis_faxis_url).with(@tax, @fax)
polymorphic_url([@tax, @fax])
end
def test_with_nested_unsaved_irregular_plurals
expects(:taxis_faxes_url).with(@tax)
polymorphic_url([@tax, @fax])
end
def test_new_with_irregular_plural_array_and_namespace
expects(:new_admin_taxis_url).with()
polymorphic_url([:admin, @tax], :action => 'new')
end
def test_unsaved_with_irregular_plural_array_and_namespace
expects(:admin_taxes_url).with()
polymorphic_url([:admin, @tax])
end
def test_nesting_with_irregular_plurals_and_array_ending_in_singleton_resource
expects(:taxis_faxis_url).with(@tax)
polymorphic_url([@tax, :faxis])
end
def test_with_array_containing_single_irregular_plural_object
@tax.save
expects(:taxis_url).with(@tax)
polymorphic_url([nil, @tax])
end
def test_with_array_containing_single_name_irregular_plural
@tax.save
expects(:taxes_url)
polymorphic_url([:taxes])
end
def test_with_array_containing_symbols
expects(:new_article_url).with()
polymorphic_url([:new, :article])
end
end

View File

@@ -175,6 +175,10 @@ class FormHelperTest < ActionView::TestCase
I18n.locale = old_locale
end
def test_label_with_for_attribute_as_nil
assert_dom_equal('<label>Title</label>', label(:post, :title, nil, :for => nil))
end
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
end
@@ -274,6 +278,11 @@ class FormHelperTest < ActionView::TestCase
hidden_field("post", "title", :value => "Something Else")
end
def test_text_field_with_id_as_nil
assert_dom_equal '<input name="post[title]" type="hidden" value="Hello World" />',
hidden_field("post", "title", :id => nil)
end
def test_check_box
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',

View File

@@ -131,4 +131,4 @@ module ActiveSupport
end
end
end
end
end

View File

@@ -0,0 +1,8 @@
unless Array.method_defined? :select!
class Array
def select!
return to_enum(:select!) unless block_given?
reject!{|elem| ! yield elem}
end
end
end

View File

@@ -0,0 +1,9 @@
unless Array.method_defined? :sort_by!
class Array
def sort_by!
return to_enum(:sort_by!) unless block_given?
raise "can't modify frozen array" if frozen?
replace sort_by{|e| yield e}
end
end
end

View File

@@ -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'

View File

@@ -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

View File

@@ -9,6 +9,4 @@ class Hash #:nodoc:
include ActiveSupport::CoreExtensions::Hash::ReverseMerge
include ActiveSupport::CoreExtensions::Hash::Conversions
include ActiveSupport::CoreExtensions::Hash::Diff
include ActiveSupport::CoreExtensions::Hash::Slice
include ActiveSupport::CoreExtensions::Hash::Except
end

View File

@@ -1,25 +1,15 @@
require 'set'
class Hash
# Return a hash that includes everything but the given keys. This is useful for
# limiting a set of parameters to everything but a few known toggles:
#
# @person.update(params[:person].except(:admin))
def except(*keys)
dup.except!(*keys)
end
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Hash #:nodoc:
# Return a hash that includes everything but the given keys. This is useful for
# limiting a set of parameters to everything but a few known toggles:
#
# @person.update_attributes(params[:person].except(:admin))
module Except
# Returns a new hash without the given keys.
def except(*keys)
dup.except!(*keys)
end
# Replaces the hash without the given keys.
def except!(*keys)
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
keys.each { |key| delete(key) }
self
end
end
end
# Replaces the hash without the given keys.
def except!(*keys)
keys.each { |key| delete(key) }
self
end
end

View File

@@ -0,0 +1,8 @@
unless Hash.method_defined? :keep_if
class Hash
def keep_if
return to_enum(:keep_if) unless block_given?
delete_if{|key, value| ! yield key, value}
end
end
end

View File

@@ -1,40 +1,40 @@
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Hash #:nodoc:
# Slice a hash to include only the given keys. This is useful for
# limiting an options hash to valid keys before passing to a method:
#
# def search(criteria = {})
# assert_valid_keys(:mass, :velocity, :time)
# end
#
# search(options.slice(:mass, :velocity, :time))
#
# If you have an array of keys you want to limit to, you should splat them:
#
# valid_keys = [:mass, :velocity, :time]
# search(options.slice(*valid_keys))
module Slice
# Returns a new hash with only the given keys.
def slice(*keys)
keys = keys.map { |key| convert_key(key) } if respond_to?(:convert_key, true)
hash = self.class.new
keys.each { |k| hash[k] = self[k] if has_key?(k) }
hash
end
class Hash
# Slice a hash to include only the given keys. This is useful for
# limiting an options hash to valid keys before passing to a method:
#
# def search(criteria = {})
# criteria.assert_valid_keys(:mass, :velocity, :time)
# end
#
# search(options.slice(:mass, :velocity, :time))
#
# If you have an array of keys you want to limit to, you should splat them:
#
# valid_keys = [:mass, :velocity, :time]
# search(options.slice(*valid_keys))
def slice(*keys)
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
end
# Replaces the hash with only the given keys.
# Returns a hash contained the removed key/value pairs
# {:a => 1, :b => 2, :c => 3, :d => 4}.slice!(:a, :b) # => {:c => 3, :d =>4}
def slice!(*keys)
keys = keys.map { |key| convert_key(key) } if respond_to?(:convert_key, true)
omit = slice(*(self.keys - keys))
hash = slice(*keys)
replace(hash)
omit
end
end
end
# Replaces the hash with only the given keys.
# Returns a hash containing the removed key/value pairs.
#
# { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
# # => {:c=>3, :d=>4}
def slice!(*keys)
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
omit = slice(*self.keys - keys)
hash = slice(*keys)
replace(hash)
omit
end
# Removes and returns the key/value pairs matching the given keys.
#
# { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
# { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
def extract!(*keys)
keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
end
end

View File

@@ -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

View File

@@ -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

View File

@@ -3,4 +3,9 @@ class Module
remove_method(method)
rescue NameError
end
def redefine_method(method, &block)
remove_possible_method(method)
define_method(method, &block)
end
end

View 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

View 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

View 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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
class Regexp #:nodoc:
def multiline?
options & MULTILINE == MULTILINE
end
end

View File

@@ -0,0 +1,26 @@
require 'active_support/core_ext/object/try'
class String
# Strips indentation in heredocs.
#
# For example in
#
# if options[:usage]
# puts <<-USAGE.strip_heredoc
# This command does such and such.
#
# Supported options are:
# -h This message
# ...
# USAGE
# end
#
# the user would see the usage message aligned against the left margin.
#
# Technically, it looks for the least indented line in the whole string, and removes
# that amount of leading whitespace.
def strip_heredoc
indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
gsub(/^[ \t]{#{indent}}/, '')
end
end

View File

@@ -1,8 +1,9 @@
# 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::DEFAULT_PARSER.unescape(URI::DEFAULT_PARSER.escape(str))
URI::Parser.class_eval do
@@ -14,3 +15,11 @@ if RUBY_VERSION >= '1.9'
end
end
end
module URI
class << self
def parser
@parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
end
end
end

View File

@@ -1,4 +1,5 @@
require 'thread'
require 'set'
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:

View 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

View File

@@ -531,9 +531,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

View File

@@ -1,18 +1,7 @@
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}
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.each do |r|
puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:segs].ljust(segs_width)} #{r[:reqs]}"
end
all_routes = ActionController::Routing::Routes.routes
require 'action_controller/routing/inspector'
inspector = ActionController::Routing::RoutesInspector.new(all_routes)
puts inspector.format(ActionController::Routing::ConsoleFormatter.new, ENV['CONTROLLER'])
end

View File

@@ -3,5 +3,5 @@
set -x
set -e
script/cibuild-on 1.9.3-p231-tcs-github
script/cibuild-on 2.1.0-github
script/cibuild-on 1.9.3-p231-tcs-github