diff options
author | Jamis Buck <jamis@37signals.com> | 2006-04-21 15:17:02 +0000 |
---|---|---|
committer | Jamis Buck <jamis@37signals.com> | 2006-04-21 15:17:02 +0000 |
commit | 1f80f540a7618e0d9c853319f6488e25ac201b2c (patch) | |
tree | c860a5eccd5e319d12f4c0006156034ac25c74a1 | |
parent | 2cbb5fb176f12badcc408b6fe142475e7df554f7 (diff) | |
download | rails-1f80f540a7618e0d9c853319f6488e25ac201b2c.tar.gz rails-1f80f540a7618e0d9c853319f6488e25ac201b2c.tar.bz2 rails-1f80f540a7618e0d9c853319f6488e25ac201b2c.zip |
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
-rw-r--r-- | actionpack/CHANGELOG | 2 | ||||
-rw-r--r-- | actionpack/lib/action_controller/code_generation.rb | 26 | ||||
-rw-r--r-- | actionpack/lib/action_controller/routing.rb | 63 | ||||
-rw-r--r-- | actionpack/test/controller/routing_test.rb | 76 |
4 files changed, 151 insertions, 16 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 33f6b2a80d..81ce343a8f 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *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] diff --git a/actionpack/lib/action_controller/code_generation.rb b/actionpack/lib/action_controller/code_generation.rb index 6762b7610d..312dff6be0 100644 --- a/actionpack/lib/action_controller/code_generation.rb +++ b/actionpack/lib/action_controller/code_generation.rb @@ -65,7 +65,7 @@ module ActionController 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 @@ module ActionController 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 @@ module ActionController @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 @@ module ActionController 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 @@ module ActionController 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 @@ module ActionController @after, @before = [], [] @current = nil @segments = [] + @subpath_at = nil end def hash_name() 'hash' end @@ -202,7 +205,7 @@ module ActionController d.segments.concat segments yield d end - + def go if current then current.write_generation(self) else self.finish @@ -215,8 +218,13 @@ module ActionController 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 diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb index b25f99c37f..4fa1acf95e 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_controller/routing.rb @@ -60,6 +60,7 @@ module ActionController 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 @@ module ActionController 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 @@ module ActionController 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 @@ module ActionController 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 @@ module ActionController 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 @@ module ActionController 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 @@ module ActionController 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 @@ module ActionController route.write_recognition(g) end end - + eval g.to_s, nil, 'generated/routing/recognition.rb' return g.to_s end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index b570909641..4f12707f02 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -970,6 +970,82 @@ class RouteSetTests < Test::Unit::TestCase 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 |