Permalink
Browse files

Resources: url_for([parent, child]) generates /parents/1/children/2 f…

…or the nested resource. Likewise with the other simply helpful methods like form_for and link_to. Closes #6432.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6951 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent b83efad commit 5dd3db86157ce2bd08c4ec07826d3aaf5c29f458 @jeremy jeremy committed Jun 5, 2007
View
@@ -1,5 +1,7 @@
*SVN*
+* Resources: url_for([parent, child]) generates /parents/1/children/2 for the nested resource. Likewise with the other simply helpful methods like form_for and link_to. #6432 [mhw, Jonathan Vaught]
+
* Assume html format when rendering partials in RJS. #8076 [Rick]
* Don't double-escape url_for in views. #8144 [Rich Collins, Josh Peek]
@@ -1,31 +1,29 @@
module ActionController
module PolymorphicRoutes
- def polymorphic_url(record_or_hash, options = {})
- record = extract_record(record_or_hash)
-
- case
- when options[:action] == "new"
- send(
- action_prefix(options) + RecordIdentifier.singular_class_name(record) + routing_type(options)
- )
-
- when record.respond_to?(:new_record?) && record.new_record?
- send(
- action_prefix(options) + RecordIdentifier.plural_class_name(record) + routing_type(options)
- )
-
- else
- send(
- action_prefix(options) + RecordIdentifier.singular_class_name(record) + routing_type(options), record_or_hash
- )
- end
+ def polymorphic_url(record_or_hash_or_array, options = {})
+ record = extract_record(record_or_hash_or_array)
+
+ args = []
+ inflection =
+ case
+ when options[:action] == "new"
+ :singular
+ when record.respond_to?(:new_record?) && record.new_record?
+ :plural
+ else
+ args = [record_or_hash_or_array]
+ :singular
+ end
+
+ named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
+ send(named_route, *args)
end
- def polymorphic_path(record_or_hash)
- polymorphic_url(record_or_hash, :routing_type => :path)
+ def polymorphic_path(record_or_hash_or_array)
+ polymorphic_url(record_or_hash_or_array, :routing_type => :path)
end
- %w( edit new formatted ).each do |action|
+ %w(edit new formatted).each do |action|
module_eval <<-EOT, __FILE__, __LINE__
def #{action}_polymorphic_url(record_or_hash)
polymorphic_url(record_or_hash, :action => "#{action}")
@@ -44,11 +42,27 @@ def action_prefix(options)
end
def routing_type(options)
- "_#{options[:routing_type] || "url"}"
+ "#{options[:routing_type] || "url"}"
+ end
+
+ def build_named_route_call(records, inflection, options = {})
+ records = Array.new([extract_record(records)]) unless records.is_a?(Array)
+ base_segment = "#{RecordIdentifier.send("#{inflection}_class_name", records.pop)}_"
+
+ method_root = records.reverse.inject(base_segment) do |string, name|
+ segment = "#{RecordIdentifier.send("singular_class_name", name)}_"
+ segment << string
+ end
+
+ action_prefix(options) + method_root + routing_type(options)
end
- def extract_record(record_or_hash)
- record_or_hash.is_a?(Hash) ? record_or_hash[:id] : record_or_hash
+ 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
@@ -25,7 +25,7 @@ module ActionController
# end
# end
#
- # As the example above shows, you can stop caring to a large extend what the actual id of the post is. You just know
+ # As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
# that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
# convention and allows you to write less code if you follow it.
module RecordIdentifier
@@ -163,9 +163,14 @@ def form_for(record_or_name, *args, &proc)
case record_or_name
when String, Symbol
object_name = record_or_name
+ when Array
+ object = record_or_name.last
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
+ apply_form_for_options!(object, options, *record_or_name)
+ args.unshift object
else
- object = record_or_name
- object_name = ActionController::RecordIdentifier.singular_class_name(record_or_name)
+ object = record_or_name
+ object_name = ActionController::RecordIdentifier.singular_class_name(object)
apply_form_for_options!(object, options)
args.unshift object
end
@@ -174,18 +179,18 @@ def form_for(record_or_name, *args, &proc)
fields_for(object_name, *(args << options), &proc)
concat('</form>', proc.binding)
end
-
- def apply_form_for_options!(object, options) #:nodoc:
- html_options = if object.respond_to?(:new_record?) && object.new_record?
- { :class => dom_class(object, :new), :id => dom_id(object), :method => :post }
- else
- { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
- end
-
+
+ def apply_form_for_options!(object, options, *nested_objects) #:nodoc:
+ html_options =
+ if object.respond_to?(:new_record?) && object.new_record?
+ { :class => dom_class(object, :new), :id => dom_id(object), :method => :post }
+ else
+ { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
+ end
+
options[:html] ||= {}
options[:html].reverse_merge!(html_options)
-
- options[:url] ||= polymorphic_path(object)
+ options[:url] ||= polymorphic_path(object, *nested_objects)
end
# Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
@@ -15,8 +15,21 @@ def new_record?
@new_record
end
end
+
+ class Comment
+ attr_reader :id
+ attr_reader :post_id
+ def save; @id = 1; @post_id = 1 end
+ def new_record?; @id.nil? end
+ def name
+ @id.nil? ? 'new comment' : "comment ##{@id}"
+ end
+ end
end
+class Comment::Nested < Comment; end
+
+
class FormHelperTest < Test::Unit::TestCase
include ActionView::Helpers::FormHelper
include ActionView::Helpers::FormTagHelper
@@ -28,6 +41,7 @@ class FormHelperTest < Test::Unit::TestCase
def setup
@post = Post.new
+ @comment = Comment.new
def @post.errors()
Class.new{
def on(field); "can't be empty" if field == "author_name"; end
@@ -579,6 +593,25 @@ def post.id() nil end
assert_equal expected, _erbout
end
+ def test_form_for_with_existing_object_in_list
+ @post.new_record = false
+ @comment.save
+ _erbout = ''
+ form_for([@post, @comment]) {}
+
+ expected = %(<form action="#{comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0"><input name="_method" type="hidden" value="put" /></div></form>)
+ assert_dom_equal expected, _erbout
+ end
+
+ def test_form_for_with_new_object_in_list
+ @post.new_record = false
+ _erbout = ''
+ form_for([@post, @comment]) {}
+
+ expected = %(<form action="#{comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>)
+ assert_dom_equal expected, _erbout
+ end
+
def test_form_for_with_existing_object_and_custom_url
_erbout = ''
@@ -600,11 +633,27 @@ def test_remote_form_for_with_html_options_adds_options_to_form_tag
protected
- def polymorphic_path(record)
- if record.new_record?
- "/posts"
+ def comments_path(post)
+ "/posts/#{post.id}/comments"
+ end
+
+ def comment_path(post, comment)
+ "/posts/#{post.id}/comments/#{comment.id}"
+ end
+
+ def polymorphic_path(object, *nested_objects)
+ if nested_objects.empty?
+ if object.new_record?
+ "/posts"
+ else
+ "/posts/#{object.id}"
+ end
else
- "/posts/#{record.id}"
+ if object.new_record?
+ "/posts/123/comments"
+ else
+ "/posts/123/comments/#{object.id}"
+ end
end
end
end
@@ -375,6 +375,22 @@ def to_s
end
end
+class Session
+ attr_accessor :id, :workshop_id, :new_record
+
+ def initialize(id, new_record)
+ @id, @new_record = id, new_record
+ end
+
+ def new_record?
+ @new_record
+ end
+
+ def to_s
+ id.to_s
+ end
+end
+
class PolymorphicControllerTest < Test::Unit::TestCase
class WorkshopsController < ActionController::Base
self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
@@ -394,35 +410,78 @@ def show
def rescue_action(e) raise e end
end
+ class SessionsController < ActionController::Base
+ self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
+
+ def self.controller_path; 'sessions' end
+
+ def index
+ @workshop = Workshop.new(params[:workshop_id], false)
+ @session = Session.new(1, true)
+ render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>"
+ end
+
+ def show
+ @workshop = Workshop.new(params[:workshop_id], false)
+ @session = Session.new(params[:id], false)
+ render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>"
+ end
+
+ def rescue_action(e) raise e end
+ end
+
include ActionView::Helpers::UrlHelper
def setup
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
- @controller = WorkshopsController.new
end
def test_new_resource
+ @controller = WorkshopsController.new
+
with_restful_routing do
get :index
assert_equal "/workshops\n<a href=\"/workshops\">Workshop</a>", @response.body
end
end
def test_existing_resource
+ @controller = WorkshopsController.new
+
with_restful_routing do
get :show, :id => 1
assert_equal "/workshops/1\n<a href=\"/workshops/1\">Workshop</a>", @response.body
end
end
+ def test_new_nested_resource
+ @controller = SessionsController.new
+
+ with_restful_routing do
+ get :index, :workshop_id => 1
+ assert_equal "/workshops/1/sessions\n<a href=\"/workshops/1/sessions\">Session</a>", @response.body
+ end
+ end
+
+ def test_existing_nested_resource
+ @controller = SessionsController.new
+
+ with_restful_routing do
+ get :show, :workshop_id => 1, :id => 1
+ assert_equal "/workshops/1/sessions/1\n<a href=\"/workshops/1/sessions/1\">Session</a>", @response.body
+ end
+ end
+
protected
def with_restful_routing
with_routing do |set|
set.draw do |map|
- map.resources :workshops
+ map.resources :workshops do |w|
+ w.resources :sessions
+ end
end
yield
end
end
-end
+end

0 comments on commit 5dd3db8

Please sign in to comment.