提交 1f80f540 编写于 作者: J Jamis Buck

Add support in routes for semicolon delimited "subpaths", like /books/:id;:action


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4242 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 2cbb5fb1
*SVN*
* Add support in routes for semicolon delimited "subpaths", like /books/:id;:action [Jamis Buck]
* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. [zraii@comcast.net, Sam Stephenson]
* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for [DHH]
......
......@@ -65,7 +65,7 @@ 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 = value.dup unless value.nil? || value.is_a?(Numeric) || value.is_a?(Symbol)
copy.send("#{sym}=", value)
end
return copy
......@@ -73,7 +73,7 @@ def dup
end
class RecognitionGenerator < CodeGenerator #:nodoc:
Attributes = [:after, :before, :current, :results, :constants, :depth, :move_ahead, :finish_statement]
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
......@@ -85,6 +85,9 @@ def initialize(*args)
@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)
......@@ -118,11 +121,10 @@ def next_segment(assign_inline = false, default = nil)
return code.to_s
end
def segment_name() "segment#{depth}".to_sym end
def path_name() :path end
def segment_name() "#{base_segment_name}#{depth}".to_sym end
def index_name
move_ahead, @move_ahead = @move_ahead, nil
move_ahead ? "index += #{move_ahead}" : 'index'
move_ahead ? "#{base_index_name} += #{move_ahead}" : base_index_name
end
def continue
......@@ -162,9 +164,9 @@ def finish(ensure_traversal_finished = true)
end
end
end
class GenerationGenerator < CodeGenerator #:nodoc:
Attributes = [:after, :before, :current, :segments]
Attributes = [:after, :before, :current, :segments, :subpath_at]
attr_accessor(*Attributes)
FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
......@@ -173,6 +175,7 @@ def initialize(*args)
@after, @before = [], []
@current = nil
@segments = []
@subpath_at = nil
end
def hash_name() 'hash' end
......@@ -202,7 +205,7 @@ def add_segment(*segments)
d.segments.concat segments
yield d
end
def go
if current then current.write_generation(self)
else self.finish
......@@ -215,8 +218,13 @@ def continue
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
......
......@@ -60,6 +60,7 @@ def key() nil end
def self.new(string, *args)
return super(string, *args) unless self == Component
case string
when /.*;.*/ then SubpathComponent.new(string.split(/;/), *args)
when ':controller' then ControllerComponent.new(:controller, *args)
when /^:(\w+)$/ then DynamicComponent.new($1, *args)
when /^\*(\w+)$/ then PathComponent.new($1, *args)
......@@ -68,6 +69,53 @@ def self.new(string, *args)
end
end
class SubpathComponent < Component #:nodoc:
attr_reader :parts
def initialize(parts, *args)
@parts = parts.map { |part| Component.new(part, *args) }
end
def write_recognition(g)
raise RoutingError, "Subpath components must occur last" unless g.after.empty?
g.next_segment
g.line "subindex, subpath = 0, #{g.next_segment}.split(/;/)"
tweak_recognizer(g).go
g.move_forward { |gg| gg.continue }
end
def write_generation(g)
raise RoutingError, "Subpath components must occur last" unless g.after.empty?
tweak_generator(g).go
end
def key
parts.map { |p| p.key }
end
private
def tweak_recognizer(g)
gg = g.dup
gg.path_name = :subpath
gg.base_segment_name = :subsegment
gg.base_index_name = :subindex
gg.depth = 0
gg.before, gg.current, gg.after = [], parts.first, (parts[1..-1] || [])
gg
end
def tweak_generator(g)
gg = g.dup
gg.before, gg.current, gg.after = [], parts.first, (parts[1..-1] || [])
gg.start_subpath!
gg
end
end
class StaticComponent < Component #:nodoc:
attr_reader :value
......@@ -337,7 +385,7 @@ def write_recognition(generator = CodeGeneration::RecognitionGenerator.new)
g = generator.dup
g.share_locals_with generator
g.before, g.current, g.after = [], components.first, (components[1..-1] || [])
known.each do |key, value|
if key == :controller then ControllerComponent.assign_controller(g, value)
else g.constant_result(key, value)
......@@ -354,7 +402,7 @@ def write_recognition(generator = CodeGeneration::RecognitionGenerator.new)
end
def initialize_keys
@keys = (components.collect {|c| c.key} + known.keys).compact
@keys = (components.collect {|c| c.key} + known.keys).flatten.compact
@keys.freeze
end
......@@ -379,12 +427,12 @@ def initialize_components(path)
end
def initialize_hashes(options)
path_keys = components.collect {|c| c.key }.compact
path_keys = components.collect {|c| c.key }.flatten.compact
self.known = {}
defaults = options.delete(:defaults) || {}
conditions = options.delete(:require) || {}
conditions.update(options.delete(:requirements) || {})
options.each do |k, v|
if path_keys.include?(k) then (v.is_a?(Regexp) ? conditions : defaults)[k] = v
else known[k] = v
......@@ -394,7 +442,8 @@ def initialize_hashes(options)
end
def configure_components(defaults, conditions)
components.each do |component|
all_components = components.map { |c| SubpathComponent === c ? c.parts : c }.flatten
all_components.each do |component|
if defaults.key?(component.key) then component.default = defaults[component.key]
elsif component.key == :action then component.default = 'index'
elsif component.key == :id then component.default = nil
......@@ -405,7 +454,7 @@ def configure_components(defaults, conditions)
end
def add_default_requirements
component_keys = components.collect {|c| c.key}
component_keys = components.collect {|c| c.key}.flatten
known[:action] ||= 'index' unless component_keys.include? :action
end
end
......@@ -505,7 +554,7 @@ def write_recognition
route.write_recognition(g)
end
end
eval g.to_s, nil, 'generated/routing/recognition.rb'
return g.to_s
end
......
......@@ -970,6 +970,82 @@ def test_nil_defaults
assert_equal ['/journal', []], rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil)
end
def setup_request_method_routes_for(method)
@request = ActionController::TestRequest.new
@request.env["REQUEST_METHOD"] = method
@request.request_uri = "/match"
rs.draw do |r|
r.connect '/match', :controller => 'books', :action => 'get', :require => { :method => :get }
r.connect '/match', :controller => 'books', :action => 'post', :require => { :method => :post }
r.connect '/match', :controller => 'books', :action => 'put', :require => { :method => :put }
r.connect '/match', :controller => 'books', :action => 'delete', :require => { :method => :delete }
end
end
%w(GET POST PUT DELETE).each do |request_method|
define_method("test_request_method_recognized_with_#{request_method}") do
begin
Object.const_set(:BooksController, Class.new(ActionController::Base))
setup_request_method_routes_for(request_method)
assert_nothing_raised { rs.recognize(@request) }
assert_equal request_method.downcase, @request.path_parameters["action"]
ensure
Object.send(:remove_const, :BooksController) rescue nil
end
end
end
def test_subpath_recognized
Object.const_set(:SubpathBooksController, Class.new(ActionController::Base))
rs.draw do |r|
r.connect '/books/:id;edit', :controller => 'subpath_books', :action => 'edit'
r.connect '/items/:id;:action', :controller => 'subpath_books'
r.connect '/posts/new;:action', :controller => 'subpath_books'
end
hash = rs.recognize_path %w(books 17;edit)
assert_not_nil hash
assert_equal %w(subpath_books 17 edit), [hash["controller"].controller_name, hash["id"], hash["action"]]
hash = rs.recognize_path %w(items 3;complete)
assert_not_nil hash
assert_equal %w(subpath_books 3 complete), [hash["controller"].controller_name, hash["id"], hash["action"]]
hash = rs.recognize_path %w(posts new;preview)
assert_not_nil hash
assert_equal %w(subpath_books preview), [hash["controller"].controller_name, hash["action"]]
# for now, low-hanging fruit only. We don't allow subpath components anywhere
# except at the end of the path
assert_raises(ActionController::RoutingError) do
rs.draw do |r|
r.connect '/books;german/new', :controller => 'subpath_books', :action => "new"
end
end
ensure
Object.send(:remove_const, :SubpathBooksController) rescue nil
end
def test_subpath_generated
Object.const_set(:SubpathBooksController, Class.new(ActionController::Base))
rs.draw do |r|
r.connect '/books/:id;edit', :controller => 'subpath_books', :action => 'edit'
r.connect '/items/:id;:action', :controller => 'subpath_books'
r.connect '/posts/new;:action', :controller => 'subpath_books'
end
assert_equal ["/books/7;edit", []], rs.generate(:controller => "subpath_books", :id => 7, :action => "edit")
assert_equal ["/items/15;complete", []], rs.generate(:controller => "subpath_books", :id => 15, :action => "complete")
assert_equal ["/posts/new;preview", []], rs.generate(:controller => "subpath_books", :action => "preview")
ensure
Object.send(:remove_const, :SubpathBooksController) rescue nil
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册