提交 b20c575a 编写于 作者: J Jamis Buck

New routes implementation. Simpler, faster, easier to understand. The...

New routes implementation. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes.


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4394 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 74b7bfa6
*SVN* *SVN*
* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. [Nicholas Seckar, Jamis Buck]
map.connect '/foo/:id', :controller => '...', :action => '...'
map.connect '/foo/:id.:format', :controller => '...', :action => '...'
map.connect '/foo/:id', ..., :conditions => { :method => :get }
* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper] * Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper]
* Accept multipart PUT parameters. #5235 [guy.naor@famundo.com] * Accept multipart PUT parameters. #5235 [guy.naor@famundo.com]
...@@ -37,7 +43,6 @@ ...@@ -37,7 +43,6 @@
All this relies on the fact that you have a route that includes .:format. All this relies on the fact that you have a route that includes .:format.
* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post [DHH] * Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post [DHH]
* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete [DHH] * Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete [DHH]
......
...@@ -189,7 +189,7 @@ def assert_generates(expected_path, options, defaults={}, extras = {}, message=n ...@@ -189,7 +189,7 @@ def assert_generates(expected_path, options, defaults={}, extras = {}, message=n
# Load routes.rb if it hasn't been loaded. # Load routes.rb if it hasn't been loaded.
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras) generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, extras)
found_extras = options.reject {|k, v| ! extra_keys.include? k} found_extras = options.reject {|k, v| ! extra_keys.include? k}
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras) msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
...@@ -365,7 +365,8 @@ def recognized_request_for(path, request_method = nil) ...@@ -365,7 +365,8 @@ def recognized_request_for(path, request_method = nil)
request = ActionController::TestRequest.new({}, {}, nil) request = ActionController::TestRequest.new({}, {}, nil)
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
request.path = path request.path = path
ActionController::Routing::Routes.recognize!(request)
ActionController::Routing::Routes.recognize(request)
request request
end end
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
require 'action_controller/request' require 'action_controller/request'
require 'action_controller/response' require 'action_controller/response'
require 'action_controller/routing' require 'action_controller/routing'
require 'action_controller/code_generation'
require 'action_controller/url_rewriter' require 'action_controller/url_rewriter'
require 'drb' require 'drb'
require 'set' require 'set'
......
module ActionController
module CodeGeneration #:nodoc:
class GenerationError < StandardError #:nodoc:
end
class Source #:nodoc:
attr_reader :lines, :indentation_level
IndentationString = ' '
def initialize
@lines, @indentation_level = [], 0
end
def line(line)
@lines << (IndentationString * @indentation_level + line)
end
alias :<< :line
def indent
@indentation_level += 1
yield
ensure
@indentation_level -= 1
end
def to_s() lines.join("\n") end
end
class CodeGenerator #:nodoc:
attr_accessor :source, :locals
def initialize(source = nil)
@locals = []
@source = source || Source.new
end
BeginKeywords = %w(if unless begin until while def).collect {|kw| kw.to_sym}
ResumeKeywords = %w(elsif else rescue).collect {|kw| kw.to_sym}
Keywords = BeginKeywords + ResumeKeywords
def method_missing(keyword, *text)
if Keywords.include? keyword
if ResumeKeywords.include? keyword
raise GenerationError, "Can only resume with #{keyword} immediately after an end" unless source.lines.last =~ /^\s*end\s*$/
source.lines.pop # Remove the 'end'
end
line "#{keyword} #{text.join ' '}"
begin source.indent { yield(self.dup) }
ensure line 'end'
end
else
super(keyword, *text)
end
end
def line(*args) self.source.line(*args) end
alias :<< :line
def indent(*args, &block) source(*args, &block) end
def to_s() source.to_s end
def share_locals_with(other)
other.locals = self.locals = (other.locals | locals)
end
FieldsToDuplicate = [:locals]
def dup
copy = self.class.new(source)
self.class::FieldsToDuplicate.each do |sym|
value = self.send(sym)
value = value.dup unless value.nil? || value.is_a?(Numeric) || value.is_a?(Symbol)
copy.send("#{sym}=", value)
end
return copy
end
end
class RecognitionGenerator < CodeGenerator #:nodoc:
Attributes = [:after, :before, :current, :results, :constants, :depth, :move_ahead, :finish_statement, :path_name, :base_segment_name, :base_index_name]
attr_accessor(*Attributes)
FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
def initialize(*args)
super(*args)
@after, @before = [], []
@current = nil
@results, @constants = {}, {}
@depth = 0
@move_ahead = nil
@finish_statement = Proc.new {|hash_expr| hash_expr}
@path_name = :path
@base_segment_name = :segment
@base_index_name = :index
end
def if_next_matches(string, &block)
test = Routing.test_condition(next_segment(true), string)
self.if(test, &block)
end
def move_forward(places = 1)
dup = self.dup
dup.depth += 1
dup.move_ahead = places
yield dup
end
def next_segment(assign_inline = false, default = nil)
if locals.include?(segment_name)
code = segment_name
else
code = "#{segment_name} = #{path_name}[#{index_name}]"
if assign_inline
code = "(#{code})"
else
line(code)
code = segment_name
end
locals << segment_name
end
code = "(#{code} || #{default.inspect})" if default
return code.to_s
end
def segment_name() "#{base_segment_name}#{depth}".to_sym end
def index_name
move_ahead, @move_ahead = @move_ahead, nil
move_ahead ? "#{base_index_name} += #{move_ahead}" : base_index_name
end
def continue
dup = self.dup
dup.before << dup.current
dup.current = dup.after.shift
dup.go
end
def go
if current then current.write_recognition(self)
else self.finish
end
end
def result(key, expression, delay = false)
unless delay
line "#{key}_value = #{expression}"
expression = "#{key}_value"
end
results[key] = expression
end
def constant_result(key, object)
constants[key] = object
end
def finish(ensure_traversal_finished = true)
pairs = []
(results.keys + constants.keys).uniq.each do |key|
pairs << "#{key.to_s.inspect} => #{results[key] ? results[key] : constants[key].inspect}"
end
hash_expr = "{#{pairs.join(', ')}}"
statement = finish_statement.call(hash_expr)
if ensure_traversal_finished then self.if("! #{next_segment(true)}") {|gp| gp << statement}
else self << statement
end
end
end
class GenerationGenerator < CodeGenerator #:nodoc:
Attributes = [:after, :before, :current, :segments, :subpath_at]
attr_accessor(*Attributes)
FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
def initialize(*args)
super(*args)
@after, @before = [], []
@current = nil
@segments = []
@subpath_at = nil
end
def hash_name() 'hash' end
def local_name(key) "#{key}_value" end
def hash_value(key, assign = true, default = nil)
if locals.include?(local_name(key)) then code = local_name(key)
else
code = "hash[#{key.to_sym.inspect}]"
if assign
code = "(#{local_name(key)} = #{code})"
locals << local_name(key)
end
end
code = "(#{code} || (#{default.inspect}))" if default
return code
end
def expire_for_keys(*keys)
return if keys.empty?
conds = keys.collect {|key| "expire_on[#{key.to_sym.inspect}]"}
line "not_expired, #{hash_name} = false, options if not_expired && #{conds.join(' && ')}"
end
def add_segment(*segments)
d = dup
d.segments.concat segments
yield d
end
def go
if current then current.write_generation(self)
else self.finish
end
end
def continue
d = dup
d.before << d.current
d.current = d.after.shift
d.go
end
def start_subpath!
@subpath_at ||= segments.length
end
def finish
segments[subpath_at..-1] = [segments[subpath_at..-1].join(";")] if subpath_at
line %("/#{segments.join('/')}")
end
def check_conditions(conditions)
tests = []
generator = nil
conditions.each do |key, condition|
tests << (generator || self).hash_value(key, true) if condition.is_a? Regexp
tests << Routing.test_condition((generator || self).hash_value(key, false), condition)
generator = self.dup unless generator
end
return tests.join(' && ')
end
end
end
end
...@@ -106,7 +106,7 @@ def assign_parameters(controller_path, action, parameters) ...@@ -106,7 +106,7 @@ def assign_parameters(controller_path, action, parameters)
if value.is_a? Fixnum if value.is_a? Fixnum
value = value.to_s value = value.to_s
elsif value.is_a? Array elsif value.is_a? Array
value = ActionController::Routing::PathComponent::Result.new(value) value = ActionController::Routing::PathSegment::Result.new(value)
end end
if extra_keys.include?(key.to_sym) if extra_keys.include?(key.to_sym)
...@@ -433,7 +433,7 @@ def find_all_tag(conditions) ...@@ -433,7 +433,7 @@ def find_all_tag(conditions)
end end
def method_missing(selector, *args) def method_missing(selector, *args)
return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector) return @controller.send(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
return super return super
end end
......
...@@ -41,34 +41,9 @@ def rewrite_path(options) ...@@ -41,34 +41,9 @@ def rewrite_path(options)
options.update(overwrite) options.update(overwrite)
end end
RESERVED_OPTIONS.each {|k| options.delete k} RESERVED_OPTIONS.each {|k| options.delete k}
path, extra_keys = Routing::Routes.generate(options.dup, @request) # Warning: Routes will mutate and violate the options hash
path << build_query_string(options, extra_keys) unless extra_keys.empty? # Generates the query string, too
Routing::Routes.generate(options, @request.parameters)
path
end
# Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll
# be added as a path element instead of a regular parameter pair.
def build_query_string(hash, only_keys = nil)
elements = []
query_string = ""
only_keys ||= hash.keys
only_keys.each do |key|
value = hash[key]
key = CGI.escape key.to_s
if value.class == Array
key << '[]'
else
value = [ value ]
end
value.each { |val| elements << "#{key}=#{Routing.extract_parameter_value(val)}" }
end
query_string << ("?" + elements.join("&")) unless elements.empty?
query_string
end end
end end
end end
...@@ -227,10 +227,12 @@ def test_assert_redirect_url_match_pattern ...@@ -227,10 +227,12 @@ def test_assert_redirect_url_match_pattern
# test the redirection to a named route # test the redirection to a named route
def test_assert_redirect_to_named_route def test_assert_redirect_to_named_route
with_routing do |set| with_routing do |set|
set.draw do set.draw do |map|
set.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing' map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing'
set.connect ':controller/:action/:id' map.connect ':controller/:action/:id'
end end
set.named_routes.install
process :redirect_to_named_route process :redirect_to_named_route
assert_redirected_to 'http://test.host/route_one' assert_redirected_to 'http://test.host/route_one'
assert_redirected_to route_one_url assert_redirected_to route_one_url
...@@ -240,10 +242,10 @@ def test_assert_redirect_to_named_route ...@@ -240,10 +242,10 @@ def test_assert_redirect_to_named_route
def test_assert_redirect_to_named_route_failure def test_assert_redirect_to_named_route_failure
with_routing do |set| with_routing do |set|
set.draw do set.draw do |map|
set.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'one' map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'one'
set.route_two 'route_two', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two' map.route_two 'route_two', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two'
set.connect ':controller/:action/:id' map.connect ':controller/:action/:id'
end end
process :redirect_to_named_route process :redirect_to_named_route
assert_raise(Test::Unit::AssertionFailedError) do assert_raise(Test::Unit::AssertionFailedError) do
......
...@@ -5,6 +5,7 @@ class MimeTypeTest < Test::Unit::TestCase ...@@ -5,6 +5,7 @@ class MimeTypeTest < Test::Unit::TestCase
Mime::PLAIN = Mime::Type.new("text/plain") Mime::PLAIN = Mime::Type.new("text/plain")
def test_parse_single def test_parse_single
p Mime::LOOKUP.keys.sort
Mime::LOOKUP.keys.each do |mime_type| Mime::LOOKUP.keys.each do |mime_type|
assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type)
end end
......
...@@ -81,6 +81,7 @@ def setup ...@@ -81,6 +81,7 @@ def setup
@request = ActionController::TestRequest.new @request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new @response = ActionController::TestResponse.new
ActionController::Routing::Routes.reload ActionController::Routing::Routes.reload
ActionController::Routing.use_controllers! %w(content admin/user)
end end
def teardown def teardown
...@@ -317,9 +318,9 @@ def test_id_converted_to_string ...@@ -317,9 +318,9 @@ def test_id_converted_to_string
def test_array_path_parameter_handled_properly def test_array_path_parameter_handled_properly
with_routing do |set| with_routing do |set|
set.draw do set.draw do |map|
set.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params' map.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params'
set.connect ':controller/:action/:id' map.connect ':controller/:action/:id'
end end
get :test_params, :path => ['hello', 'world'] get :test_params, :path => ['hello', 'world']
...@@ -440,9 +441,9 @@ def test_assert_follow_redirect_to_different_controller ...@@ -440,9 +441,9 @@ def test_assert_follow_redirect_to_different_controller
protected protected
def with_foo_routing def with_foo_routing
with_routing do |set| with_routing do |set|
set.draw do set.draw do |map|
set.generate_url 'foo', :controller => 'test' map.generate_url 'foo', :controller => 'test'
set.connect ':controller/:action/:id' map.connect ':controller/:action/:id'
end end
yield set yield set
end end
......
...@@ -6,23 +6,6 @@ def setup ...@@ -6,23 +6,6 @@ def setup
@params = {} @params = {}
@rewriter = ActionController::UrlRewriter.new(@request, @params) @rewriter = ActionController::UrlRewriter.new(@request, @params)
end end
def test_simple_build_query_string
assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => '1', :y => '2')
end
def test_convert_ints_build_query_string
assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => 1, :y => 2)
end
def test_escape_spaces_build_query_string
assert_query_equal '?x=hello+world&y=goodbye+world', @rewriter.send(:build_query_string, :x => 'hello world', :y => 'goodbye world')
end
def test_expand_array_build_query_string
assert_query_equal '?x[]=1&x[]=2', @rewriter.send(:build_query_string, :x => [1, 2])
end
def test_escape_spaces_build_query_string_selected_keys
assert_query_equal '?x=hello+world', @rewriter.send(:build_query_string, {:x => 'hello world', :y => 'goodbye world'}, [:x])
end
def test_overwrite_params def test_overwrite_params
@params[:controller] = 'hi' @params[:controller] = 'hi'
......
*SVN* *SVN*
* Minor tweak to dispatcher to use recognize instead of recognize!, as per the new routes. [Jamis Buck]
* Make "script/plugin install" work with svn+ssh URLs. [Sam Stephenson] * Make "script/plugin install" work with svn+ssh URLs. [Sam Stephenson]
* Added lib/ to the directories that will get application docs generated [DHH] * Added lib/ to the directories that will get application docs generated [DHH]
......
...@@ -35,7 +35,7 @@ def dispatch(cgi = nil, session_options = ActionController::CgiRequest::DEFAULT_ ...@@ -35,7 +35,7 @@ def dispatch(cgi = nil, session_options = ActionController::CgiRequest::DEFAULT_
if cgi ||= new_cgi(output) if cgi ||= new_cgi(output)
request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi) request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi)
prepare_application prepare_application
ActionController::Routing::Routes.recognize!(request).process(request, response).out(output) ActionController::Routing::Routes.recognize(request).process(request, response).out(output)
end end
rescue Object => exception rescue Object => exception
failsafe_response(output, '500 Internal Server Error', exception) do failsafe_response(output, '500 Internal Server Error', exception) do
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册