Skip to content

Commit

Permalink
2-0-stable: Add documentation for polymorphic URL helpers, make API c…
Browse files Browse the repository at this point in the history
…onsistent for polymorphic_path and polymorphic_url.

References rails#10883 [mislav] References rails#8782 [gbuesing] References rails#8720 [gbuesing]

Merging [8741]


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/2-0-stable@8844 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information
NZKoz committed Feb 10, 2008
1 parent 2dbceab commit 41adf87
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 90 deletions.
105 changes: 90 additions & 15 deletions actionpack/lib/action_controller/polymorphic_routes.rb
@@ -1,8 +1,75 @@
module ActionController
# Polymorphic URL helpers are methods for smart resolution to a named route call when
# given an ActiveRecord 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 :url 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>
# * <tt>formatted_polymorphic_url</tt>, <tt>formatted_polymorphic_path</tt>
#
# Example usage:
#
# edit_polymorphic_path(@post)
# #=> /posts/1/edit
#
# formatted_polymorphic_path([@post, :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:
#
# polymorphic_url(post)
# # calls post_url(post) #=> "http://example.com/posts/1"
#
# ==== Options
# * <tt>:action</tt> -- specifies the action prefix for the named route:
# <tt>:new</tt>, <tt>:edit</tt> or <tt>:formatted</tt>. Default is no prefix.
# * <tt>:routing_type</tt> -- <tt>:path</tt> or <tt>:url</tt> (default <tt>:url</tt>).
#
# ==== Examples
#
# # an Article record
# polymorphic_url(record) #-> article_url(record)
#
# # a Comment record
# polymorphic_url(record) #-> comment_url(record)
#
# # it recognizes new records and maps to the collection
# record = Comment.new
# polymorphic_url(record) #-> comments_url()
#
def polymorphic_url(record_or_hash_or_array, options = {})
record = extract_record(record_or_hash_or_array)

record = extract_record(record_or_hash_or_array)
format = (options[:action].to_s == "formatted" and record_or_hash_or_array.pop)
namespace = extract_namespace(record_or_hash_or_array)

args = case record_or_hash_or_array
Expand All @@ -11,9 +78,11 @@ def polymorphic_url(record_or_hash_or_array, options = {})
else [ record_or_hash_or_array ]
end

args << format if format

inflection =
case
when options[:action] == "new"
when options[:action].to_s == "new"
args.pop
:singular
when record.respond_to?(:new_record?) && record.new_record?
Expand All @@ -27,8 +96,11 @@ def polymorphic_url(record_or_hash_or_array, options = {})
send!(named_route, *args)
end

def polymorphic_path(record_or_hash_or_array)
polymorphic_url(record_or_hash_or_array, :routing_type => :path)
# 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 formatted).each do |action|
Expand All @@ -43,26 +115,29 @@ def #{action}_polymorphic_path(record_or_hash)
EOT
end


private
def action_prefix(options)
options[:action] ? "#{options[:action]}_" : ""
end

def routing_type(options)
"#{options[:routing_type] || "url"}"
options[:routing_type] || :url
end

def build_named_route_call(records, namespace, 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
unless records.is_a?(Array)
record = extract_record(records)
route = ''
else
record = records.pop
route = records.inject("") do |string, parent|
string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
end
end

action_prefix(options) + namespace + method_root + routing_type(options)
route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"

action_prefix(options) + namespace + route + routing_type(options).to_s
end

def extract_record(record_or_hash_or_array)
Expand All @@ -78,7 +153,7 @@ def extract_namespace(record_or_hash_or_array)
if record_or_hash_or_array.is_a?(Array)
record_or_hash_or_array.delete_if do |record_or_namespace|
if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
namespace << "#{record_or_namespace.to_s}_"
namespace << "#{record_or_namespace}_"
end
end
end
Expand Down
181 changes: 106 additions & 75 deletions actionpack/test/controller/polymorphic_routes_test.rb
Expand Up @@ -5,94 +5,125 @@ class Article
def save; @id = 1 end
def new_record?; @id.nil? end
def name
@id.nil? ? 'new post' : "post ##{@id}"
model = self.class.name.downcase
@id.nil? ? "new #{model}" : "#{model} ##{@id}"
end
end

class Comment
attr_reader :id
class Comment < Article
def post_id; 1 end
def save; @id = 1 end
def new_record?; @id.nil? end
def name
@id.nil? ? 'new comment' : "comment ##{@id}"
end
end

class Tag < Article
def comment_id; 1 end
end

# TODO: test nested models
class Comment::Nested < Comment; end

class Test::Unit::TestCase
protected
def articles_url
'http://www.example.com/articles'
end
alias_method :new_article_url, :articles_url

def article_url(article)
"http://www.example.com/articles/#{article.id}"
end
uses_mocha 'polymorphic URL helpers' do
class PolymorphicRoutesTest < Test::Unit::TestCase

def article_comments_url(article)
"http://www.example.com/articles/#{article.id}/comments"
end

def article_comment_url(article, comment)
"http://www.example.com/articles/#{article.id}/comments/#{comment.id}"
end

def admin_articles_url
"http://www.example.com/admin/articles"
end
alias_method :new_admin_article_url, :admin_articles_url

def admin_article_url(article)
"http://www.example.com/admin/articles/#{article.id}"
end

def admin_article_comments_url(article)
"http://www.example.com/admin/articles/#{article.id}/comments"
end
include ActionController::PolymorphicRoutes

def setup
@article = Article.new
@comment = Comment.new
end

def admin_article_comment_url(article, comment)
"http://www.example.com/admin/test/articles/#{article.id}/comments/#{comment.id}"
end
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

class PolymorphicRoutesTest < Test::Unit::TestCase
include ActionController::PolymorphicRoutes
def test_with_record_and_action
expects(:new_article_url).with()
@article.expects(:new_record?).never
polymorphic_url(@article, :action => 'new')
end

def setup
@article = Article.new
@comment = Comment.new
end

def test_with_record
assert_equal(articles_url, polymorphic_url(@article, :action => 'new'))
assert_equal(articles_url, polymorphic_url(@article))
@article.save
assert_equal(article_url(@article), polymorphic_url(@article))
end

# TODO: Needs to be updated to correctly know about whether the object is in a hash or not
def xtest_with_hash
@article.save
assert_equal(article_url(@article), polymorphic_url(:id => @article))
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_formatted_url_helper
expects(:formatted_article_url).with(@article, :pdf)
formatted_polymorphic_url([@article, :pdf])
end

# TODO: should this work?
def xtest_format_option
@article.save
expects(:article_url).with(@article, :format => :pdf)
polymorphic_url(@article, :format => :pdf)
end

def test_with_nested
@comment.save
expects(:article_comment_url).with(@article, @comment)
polymorphic_url([@article, @comment])
end

def test_with_nested_unsaved
expects(:article_comments_url).with(@article)
polymorphic_url([@article, @comment])
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_comments_url).with(@article)
polymorphic_url([:admin, @article, @comment])
end

def test_nested_with_array_and_namespace
@comment.save
expects(:admin_article_comment_url).with(@article, @comment)
polymorphic_url([:admin, @article, @comment])

# a ridiculously long named route tests correct ordering of namespaces and nesting:
@tag = Tag.new
@tag.save
expects(:site_admin_article_comment_tag_url).with(@article, @comment, @tag)
polymorphic_url([:site, :admin, @article, @comment, @tag])
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_with_array
assert_equal(article_comments_url(@article), polymorphic_url([@article, @comment]))
@comment.save
assert_equal(article_comment_url(@article, @comment), polymorphic_url([@article, @comment]))
end

def test_with_array_and_namespace
assert_equal(admin_articles_url, polymorphic_url([:admin, @article], :action => 'new'))
assert_equal(admin_articles_url, polymorphic_url([:admin, @article]))
@article.save
assert_equal(admin_article_url(@article), polymorphic_url([:admin, @article]))
assert_equal(admin_article_comments_url(@article), polymorphic_url([:admin, @article, @comment]))
@comment.save
assert_equal(admin_article_comment_url(@article, @comment), polymorphic_url([:admin, @article, @comment]))
end
end

0 comments on commit 41adf87

Please sign in to comment.