mirror of
https://github.com/github/rails.git
synced 2026-04-04 03:00:58 -04:00
Add documentation for polymorphic URL helpers, make API consistent for polymorphic_path and polymorphic_url.
Closes #10883 [mislav] Closes #8782 [gbuesing] Closes #8720 [gbuesing] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8741 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
@@ -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
|
||||
@@ -11,9 +78,11 @@ module ActionController
|
||||
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?
|
||||
@@ -27,8 +96,11 @@ module ActionController
|
||||
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|
|
||||
@@ -43,26 +115,29 @@ module ActionController
|
||||
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)
|
||||
@@ -78,7 +153,7 @@ module ActionController
|
||||
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
|
||||
|
||||
@@ -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
|
||||
include ActionController::PolymorphicRoutes
|
||||
|
||||
def setup
|
||||
@article = Article.new
|
||||
@comment = Comment.new
|
||||
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
|
||||
|
||||
def admin_article_comment_url(article, comment)
|
||||
"http://www.example.com/admin/test/articles/#{article.id}/comments/#{comment.id}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class PolymorphicRoutesTest < Test::Unit::TestCase
|
||||
include ActionController::PolymorphicRoutes
|
||||
|
||||
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_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]))
|
||||
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
|
||||
|
||||
def test_with_record_and_action
|
||||
expects(:new_article_url).with()
|
||||
@article.expects(:new_record?).never
|
||||
polymorphic_url(@article, :action => 'new')
|
||||
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
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user