mirror of
https://github.com/github/rails.git
synced 2026-01-12 08:08:31 -05:00
Compare commits
315 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
478cd8223c | ||
|
|
a134f8f3c8 | ||
|
|
0a2404f5cc | ||
|
|
70a8e6a7d1 | ||
|
|
6c773706b4 | ||
|
|
779db44f74 | ||
|
|
a11dc682a9 | ||
|
|
fe1ee81d1a | ||
|
|
8943da41fd | ||
|
|
3acd2bd721 | ||
|
|
19dff14814 | ||
|
|
1dd0034134 | ||
|
|
e0fd18b2aa | ||
|
|
2503fd3c3a | ||
|
|
631bf6b203 | ||
|
|
6cc01c9579 | ||
|
|
e3670a0e24 | ||
|
|
40a188b18e | ||
|
|
d81ac8d6f3 | ||
|
|
b29caa54e6 | ||
|
|
7d517a145b | ||
|
|
2ba8864782 | ||
|
|
5c6a0d57ac | ||
|
|
74191edb22 | ||
|
|
9c0e4de88b | ||
|
|
1f5a26432d | ||
|
|
d5950cdd57 | ||
|
|
fe3fefeeec | ||
|
|
3ad1a98556 | ||
|
|
099c206535 | ||
|
|
e5590d69e5 | ||
|
|
b14378cd68 | ||
|
|
07f92e8321 | ||
|
|
788d4b0cc3 | ||
|
|
6ba8d874a9 | ||
|
|
75bf71d429 | ||
|
|
bed591c36c | ||
|
|
d4c24b6bb4 | ||
|
|
173e2d2726 | ||
|
|
cfcc188dd1 | ||
|
|
a74560f8f0 | ||
|
|
92caa99eb3 | ||
|
|
d137167ca9 | ||
|
|
d080638370 | ||
|
|
0fd9743ae1 | ||
|
|
cdc26bdec6 | ||
|
|
f5977b205f | ||
|
|
67465f8a98 | ||
|
|
e522f75cbc | ||
|
|
b564d7e1e9 | ||
|
|
24267c4f3f | ||
|
|
e05afb1bcc | ||
|
|
98caf444f3 | ||
|
|
53c87f9818 | ||
|
|
eefa0a22b4 | ||
|
|
b41920de18 | ||
|
|
93d7ab48a1 | ||
|
|
d10177d0f1 | ||
|
|
91976d7e6d | ||
|
|
8ad39a0a47 | ||
|
|
1cd825751d | ||
|
|
30549718ac | ||
|
|
736cca87dc | ||
|
|
5552dadbb9 | ||
|
|
4a8d3d51f7 | ||
|
|
243cdde6a8 | ||
|
|
a9ed24c2dd | ||
|
|
b1e1088ab9 | ||
|
|
579daf5b39 | ||
|
|
633d7579cf | ||
|
|
bbe3dea3fa | ||
|
|
9679cb4d97 | ||
|
|
742bee4256 | ||
|
|
ee31d643eb | ||
|
|
f44437454c | ||
|
|
d1b93e7da2 | ||
|
|
ae11519df8 | ||
|
|
e41f4894c4 | ||
|
|
e73bdfc31b | ||
|
|
a422b8ba62 | ||
|
|
ed6509c843 | ||
|
|
41108fec89 | ||
|
|
c4d8d97e9c | ||
|
|
0ee158a3b9 | ||
|
|
92b00efd70 | ||
|
|
21fafdd290 | ||
|
|
86519a6ea8 | ||
|
|
6a04f3599e | ||
|
|
58cb43cb53 | ||
|
|
ef3ffd101e | ||
|
|
6744e35b25 | ||
|
|
7571380c9c | ||
|
|
454842a1f6 | ||
|
|
57098ad859 | ||
|
|
b79c0a05b2 | ||
|
|
2bc5e6cd14 | ||
|
|
4dc206c578 | ||
|
|
d6f6a41f1e | ||
|
|
1155252d37 | ||
|
|
9b24e1198b | ||
|
|
d4b6671cdb | ||
|
|
11a92b3582 | ||
|
|
7e33de46e5 | ||
|
|
fe42e6cc1f | ||
|
|
c28a70c04b | ||
|
|
553c23df82 | ||
|
|
c9ed2f64ae | ||
|
|
70f4a92c5d | ||
|
|
a182d85cd9 | ||
|
|
03e763ffc8 | ||
|
|
3ec808eadb | ||
|
|
f255db871b | ||
|
|
cfb3a80862 | ||
|
|
5c61b17077 | ||
|
|
025f895415 | ||
|
|
e9e86fd134 | ||
|
|
81b94c2f10 | ||
|
|
a3dafb07ec | ||
|
|
cc99f3fc18 | ||
|
|
d25ae9b6e1 | ||
|
|
517628d028 | ||
|
|
5d0fd52305 | ||
|
|
aa30fc7914 | ||
|
|
5f114177ce | ||
|
|
143cc0ec2f | ||
|
|
5385859dcf | ||
|
|
2d5bf4bbed | ||
|
|
2b6954aa8a | ||
|
|
68849e3239 | ||
|
|
debaf20803 | ||
|
|
8f84165cb4 | ||
|
|
18857ca2cb | ||
|
|
06c7584fba | ||
|
|
dd605e937f | ||
|
|
d12bcdac9d | ||
|
|
52923c9b55 | ||
|
|
1a7ce06e90 | ||
|
|
34603e7faf | ||
|
|
f7d25950f0 | ||
|
|
0ac6b5077f | ||
|
|
fd2b14c046 | ||
|
|
86f78cd8b6 | ||
|
|
398c1a504c | ||
|
|
5bf40f7b79 | ||
|
|
e76eae612f | ||
|
|
25c3ee407d | ||
|
|
de9ccd8848 | ||
|
|
9cc2e0f2f4 | ||
|
|
1696c1f68d | ||
|
|
2c1d0e2979 | ||
|
|
f1a4c44726 | ||
|
|
382547dcbe | ||
|
|
376dcb8e02 | ||
|
|
ab3f6bcfaa | ||
|
|
04383b974d | ||
|
|
fde811e2f6 | ||
|
|
83a21f75cf | ||
|
|
e9a7b233f6 | ||
|
|
21e4825596 | ||
|
|
622d70a11e | ||
|
|
c26cca3f45 | ||
|
|
b0303e1ac2 | ||
|
|
bdd09f50f8 | ||
|
|
6dc5756d39 | ||
|
|
0120b226ba | ||
|
|
b6af22ec82 | ||
|
|
4870becaeb | ||
|
|
a68d1d35f8 | ||
|
|
a4e70d3e91 | ||
|
|
ba29222851 | ||
|
|
0ff4ede7ca | ||
|
|
7269ee2da9 | ||
|
|
59be73a1e7 | ||
|
|
ce8e54e236 | ||
|
|
155dcff431 | ||
|
|
df27fb873c | ||
|
|
4aa93358fa | ||
|
|
3054a742df | ||
|
|
1fb2f21d1a | ||
|
|
da8a896d86 | ||
|
|
debd9ea2ce | ||
|
|
8ec9f4d2a0 | ||
|
|
4dcdf0834e | ||
|
|
a451fd1fbf | ||
|
|
c7e2b03424 | ||
|
|
6ccbef5862 | ||
|
|
498bca8ae8 | ||
|
|
c3c7648d20 | ||
|
|
fe2e51b047 | ||
|
|
67cda1f54e | ||
|
|
cda3d89983 | ||
|
|
0ae462a82d | ||
|
|
d5ca9a5814 | ||
|
|
72d556a391 | ||
|
|
34bc074441 | ||
|
|
6df67f5607 | ||
|
|
590b192e02 | ||
|
|
67ac59afc8 | ||
|
|
3d5fb40bb0 | ||
|
|
0c483d0c8d | ||
|
|
8804b7aeb7 | ||
|
|
2876efb784 | ||
|
|
762fc5447f | ||
|
|
bd261ffd1f | ||
|
|
ffb17e89ef | ||
|
|
2a08c4547a | ||
|
|
2b68762f5a | ||
|
|
de3ec6c008 | ||
|
|
2890b9648e | ||
|
|
98a0440a56 | ||
|
|
f09c8c4529 | ||
|
|
25d7ea8ad2 | ||
|
|
91cd8890c7 | ||
|
|
87ef365a49 | ||
|
|
d6c28bfaed | ||
|
|
b2ae346484 | ||
|
|
fb6cd55de7 | ||
|
|
c4d4660de3 | ||
|
|
205ae50d3f | ||
|
|
5b8bfc40e0 | ||
|
|
f83eedd440 | ||
|
|
691c439129 | ||
|
|
8f3e81ed53 | ||
|
|
fd8ee0a253 | ||
|
|
509c920d35 | ||
|
|
cef81e71eb | ||
|
|
3ee809655e | ||
|
|
c3cbd6b806 | ||
|
|
fe1e771222 | ||
|
|
694957ce5a | ||
|
|
12949bbc13 | ||
|
|
82e5ff7c6e | ||
|
|
783c16bd61 | ||
|
|
34a3d04af1 | ||
|
|
766ca17c91 | ||
|
|
ffa2c5fda5 | ||
|
|
87ecb782e6 | ||
|
|
2043513f4c | ||
|
|
ef6c3c4289 | ||
|
|
af43e87f38 | ||
|
|
f2fd22c2d4 | ||
|
|
17921bafcb | ||
|
|
36b3cb2c36 | ||
|
|
b93e4692b2 | ||
|
|
30e5436a3e | ||
|
|
9ba96771c3 | ||
|
|
97d9dca26f | ||
|
|
2a70f6f0a2 | ||
|
|
1e532f6606 | ||
|
|
fb60a469c0 | ||
|
|
ac2295137c | ||
|
|
acfe119cb5 | ||
|
|
004a28de81 | ||
|
|
0497868531 | ||
|
|
23569d83b2 | ||
|
|
bef626e2b1 | ||
|
|
b7f094e65e | ||
|
|
6baf9489d1 | ||
|
|
af33784698 | ||
|
|
a5631a5674 | ||
|
|
629b2ac36c | ||
|
|
dd1dc5e819 | ||
|
|
b8ff216f49 | ||
|
|
924f5cdb61 | ||
|
|
86bd0da7d0 | ||
|
|
35240ba66e | ||
|
|
b8a5d398b9 | ||
|
|
85c45051be | ||
|
|
f7c94e977b | ||
|
|
69ada0e26e | ||
|
|
9eaad14e8e | ||
|
|
9f8c805f5d | ||
|
|
46b845e784 | ||
|
|
118764e47d | ||
|
|
bcd50f1a37 | ||
|
|
5aced86de6 | ||
|
|
792780a80c | ||
|
|
5e677b67b5 | ||
|
|
dfa070aab9 | ||
|
|
ba086a4fc0 | ||
|
|
cc299d1b94 | ||
|
|
cbffafb497 | ||
|
|
84668af330 | ||
|
|
013004b592 | ||
|
|
5f5d8c224b | ||
|
|
5887b183ce | ||
|
|
320875ad77 | ||
|
|
4aa358dd86 | ||
|
|
6439d0cd3e | ||
|
|
7953eb170d | ||
|
|
5350fda95d | ||
|
|
5fac326020 | ||
|
|
17e445813b | ||
|
|
a2edd4381e | ||
|
|
2029b8a8f5 | ||
|
|
53c49036ea | ||
|
|
855e0f6f09 | ||
|
|
e4e1d2afa3 | ||
|
|
526afd3ae2 | ||
|
|
31b901aa65 | ||
|
|
a746e39584 | ||
|
|
f4d88039fd | ||
|
|
9f26164d37 | ||
|
|
c7f28e01c8 | ||
|
|
3311953d3f | ||
|
|
ce0653b1c6 | ||
|
|
229e197ac7 | ||
|
|
503c7c0afd | ||
|
|
8e01efee02 | ||
|
|
e09f224a5c | ||
|
|
6eb941ee6c | ||
|
|
7e8dd0322c | ||
|
|
159411f580 | ||
|
|
746cfb3ded | ||
|
|
2227a178ee |
@@ -1,4 +1,24 @@
|
||||
*SVN*
|
||||
*1.3.4* (October 4th, 2007)
|
||||
|
||||
* Depend on Action Pack 1.13.4
|
||||
|
||||
|
||||
*1.3.3* (March 12th, 2007)
|
||||
|
||||
* Depend on Action Pack 1.13.3
|
||||
|
||||
|
||||
*1.3.2* (February 5th, 2007)
|
||||
|
||||
* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. [Koz]
|
||||
|
||||
|
||||
*1.3.1* (January 16th, 2007)
|
||||
|
||||
* Depend on Action Pack 1.13.1
|
||||
|
||||
|
||||
*1.3.0* (January 16th, 2007)
|
||||
|
||||
* Make mime version default to 1.0. closes #2323 [ror@andreas-s.net]
|
||||
|
||||
@@ -14,8 +34,6 @@
|
||||
|
||||
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
|
||||
|
||||
* Mailer template root applies to a class and its subclasses rather than acting globally. #5555 [somekool@gmail.com]
|
||||
|
||||
* Resolve action naming collision. #5520 [ssinghi@kreeti.com]
|
||||
|
||||
* ActionMailer::Base documentation rewrite. Closes #4991 [Kevin Clark, Marcel Molina Jr.]
|
||||
@@ -24,8 +42,30 @@
|
||||
|
||||
* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
|
||||
|
||||
|
||||
*1.2.5* (August 10th, 2006)
|
||||
|
||||
* Depend on Action Pack 1.12.5
|
||||
|
||||
|
||||
*1.2.4* (August 8th, 2006)
|
||||
|
||||
* Backport of documentation enhancements. [Kevin Clark, Marcel Molina Jr]
|
||||
|
||||
* Correct spurious documentation example code which results in a SyntaxError. [Marcel Molina Jr.]
|
||||
|
||||
* Mailer template root applies to a class and its subclasses rather than acting globally. #5555 [somekool@gmail.com]
|
||||
|
||||
|
||||
*1.2.3* (June 29th, 2006)
|
||||
|
||||
* Depend on Action Pack 1.12.3
|
||||
|
||||
|
||||
*1.2.2* (June 27th, 2006)
|
||||
|
||||
* Depend on Action Pack 1.12.2
|
||||
|
||||
|
||||
*1.2.1* (April 6th, 2006)
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ spec = Gem::Specification.new do |s|
|
||||
s.rubyforge_project = "actionmailer"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 1.12.5' + PKG_BUILD)
|
||||
s.add_dependency('actionpack', '= 1.13.4' + PKG_BUILD)
|
||||
|
||||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
@@ -85,11 +85,11 @@ end
|
||||
|
||||
desc "Publish the release files to RubyForge."
|
||||
task :release => [ :package ] do
|
||||
`rubyforge login`
|
||||
require 'rubyforge'
|
||||
|
||||
for ext in %w( gem tgz zip )
|
||||
release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
|
||||
puts release_command
|
||||
system(release_command)
|
||||
end
|
||||
packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
|
||||
|
||||
rubyforge = RubyForge.new
|
||||
rubyforge.login
|
||||
rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
|
||||
end
|
||||
@@ -27,7 +27,7 @@ unless defined?(ActionController)
|
||||
require 'action_controller'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require_gem 'actionpack', '>= 1.12.5'
|
||||
gem 'actionpack', '>= 1.12.5'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ module ActionMailer #:nodoc:
|
||||
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
|
||||
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
|
||||
#
|
||||
# * <tt>server_settings</tt> - Allows detailed configuration of the server:
|
||||
# * <tt>smtp_settings</tt> - Allows detailed configuration for :smtp delivery method:
|
||||
# * <tt>:address</tt> Allows you to use a remote mail server. Just change it from its default "localhost" setting.
|
||||
# * <tt>:port</tt> On the off chance that your mail server doesn't run on port 25, you can change it.
|
||||
# * <tt>:domain</tt> If you need to specify a HELO domain, you can do it here.
|
||||
@@ -193,10 +193,12 @@ module ActionMailer #:nodoc:
|
||||
# * <tt>:authentication</tt> If your mail server requires authentication, you need to specify the authentication type here.
|
||||
# This is a symbol and one of :plain, :login, :cram_md5
|
||||
#
|
||||
# * <tt>sendmail_settings</tt> - Allows you to override options for the :sendmail delivery method
|
||||
# * <tt>:location</tt> The location of the sendmail executable, defaults to "/usr/sbin/sendmail"
|
||||
# * <tt>:arguments</tt> The command line arguments
|
||||
# * <tt>raise_delivery_errors</tt> - whether or not errors should be raised if the email fails to be delivered.
|
||||
#
|
||||
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.
|
||||
# Sendmail is assumed to be present at "/usr/sbin/sendmail".
|
||||
#
|
||||
# * <tt>perform_deliveries</tt> - Determines whether deliver_* methods are actually carried out. By default they are,
|
||||
# but this can be turned off to help functional testing.
|
||||
@@ -228,7 +230,7 @@ module ActionMailer #:nodoc:
|
||||
class_inheritable_accessor :template_root
|
||||
cattr_accessor :logger
|
||||
|
||||
@@server_settings = {
|
||||
@@smtp_settings = {
|
||||
:address => "localhost",
|
||||
:port => 25,
|
||||
:domain => 'localhost.localdomain',
|
||||
@@ -236,7 +238,13 @@ module ActionMailer #:nodoc:
|
||||
:password => nil,
|
||||
:authentication => nil
|
||||
}
|
||||
cattr_accessor :server_settings
|
||||
cattr_accessor :smtp_settings
|
||||
|
||||
@@sendmail_settings = {
|
||||
:location => '/usr/sbin/sendmail',
|
||||
:arguments => '-i -t'
|
||||
}
|
||||
cattr_accessor :sendmail_settings
|
||||
|
||||
@@raise_delivery_errors = true
|
||||
cattr_accessor :raise_delivery_errors
|
||||
@@ -355,6 +363,18 @@ module ActionMailer #:nodoc:
|
||||
def deliver(mail)
|
||||
new.deliver!(mail)
|
||||
end
|
||||
|
||||
# Server Settings is the old name for <tt>smtp_settings</tt>
|
||||
def server_settings
|
||||
smtp_settings
|
||||
end
|
||||
deprecate :server_settings=>"It's now named smtp_settings"
|
||||
|
||||
def server_settings=(settings)
|
||||
ActiveSupport::Deprecation.warn("server_settings has been renamed smtp_settings, this warning will be removed with rails 2.0", caller)
|
||||
self.smtp_settings=settings
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
|
||||
@@ -542,14 +562,14 @@ module ActionMailer #:nodoc:
|
||||
destinations = mail.destinations
|
||||
mail.ready_to_send
|
||||
|
||||
Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain],
|
||||
server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp|
|
||||
Net::SMTP.start(smtp_settings[:address], smtp_settings[:port], smtp_settings[:domain],
|
||||
smtp_settings[:user_name], smtp_settings[:password], smtp_settings[:authentication]) do |smtp|
|
||||
smtp.sendmail(mail.encoded, mail.from, destinations)
|
||||
end
|
||||
end
|
||||
|
||||
def perform_delivery_sendmail(mail)
|
||||
IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm|
|
||||
IO.popen("#{sendmail_settings[:location]} #{sendmail_settings[:arguments]}","w+") do |sm|
|
||||
sm.print(mail.encoded.gsub(/\r/, ''))
|
||||
sm.flush
|
||||
end
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module ActionMailer
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 1
|
||||
MINOR = 2
|
||||
TINY = 5
|
||||
MINOR = 3
|
||||
TINY = 4
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
||||
@@ -787,6 +787,19 @@ EOF
|
||||
assert_match %r{format=flowed}, mail['content-type'].to_s
|
||||
assert_match %r{charset=utf-8}, mail['content-type'].to_s
|
||||
end
|
||||
|
||||
def test_deprecated_server_settings
|
||||
old_smtp_settings = ActionMailer::Base.smtp_settings
|
||||
assert_deprecated do
|
||||
ActionMailer::Base.server_settings
|
||||
end
|
||||
assert_deprecated do
|
||||
ActionMailer::Base.server_settings={}
|
||||
assert_equal Hash.new, ActionMailer::Base.smtp_settings
|
||||
end
|
||||
ensure
|
||||
ActionMailer::Base.smtp_settings=old_smtp_settings
|
||||
end
|
||||
end
|
||||
|
||||
class InheritableTemplateRootTest < Test::Unit::TestCase
|
||||
|
||||
@@ -1,4 +1,193 @@
|
||||
*SVN*
|
||||
*1.13.4* (October 4th, 2007)
|
||||
|
||||
* Only accept session ids from cookies, prevents session fixation attacks. [bradediger]
|
||||
|
||||
* Change the resource seperator from ; to / change the generated routes to use the new-style named routes. e.g. new_group_user_path(@group) instead of group_new_user_path(@group). [pixeltrix]
|
||||
|
||||
* Integration tests: introduce methods for other HTTP methods. #6353 [caboose]
|
||||
|
||||
* Improve performance of action caching. Closes #8231 [skaes]
|
||||
|
||||
* Fix errors with around_filters which do not yield, restore 1.1 behaviour with after filters. Closes #8891 [skaes]
|
||||
|
||||
After filters will *no longer* be run if an around_filter fails to yield, users relying on
|
||||
this behaviour are advised to put the code in question after a yield statement in an around filter.
|
||||
|
||||
* Allow you to delete cookies with options. Closes #3685 [josh, Chris Wanstrath]
|
||||
|
||||
* Deprecate pagination. Install the classic_pagination plugin for forward compatibility, or move to the superior will_paginate plugin. #8157 [Mislav Marohnic]
|
||||
|
||||
* Fix filtered parameter logging with nil parameter values. #8422 [choonkeat]
|
||||
|
||||
* Integration tests: alias xhr to xml_http_request and add a request_method argument instead of always using POST. #7124 [Nik Wakelin, Francois Beausoleil, Wizard]
|
||||
|
||||
* Document caches_action. #5419 [Jarkko Laine]
|
||||
|
||||
* observe_form always sends the serialized form. #5271 [manfred, normelton@gmail.com]
|
||||
|
||||
* Update UrlWriter to accept :anchor parameter. Closes #6771. [octopod]
|
||||
|
||||
* Replace the current block/continuation filter chain handling by an implementation based on a simple loop. Closes #8226 [Stefan Kaes]
|
||||
|
||||
* Return the string representation from an Xml Builder when rendering a partial. #5044 [tpope]
|
||||
|
||||
* Cleaned up, corrected, and mildly expanded ActionPack documentation. Closes #7190 [jeremymcanally]
|
||||
|
||||
* Small collection of ActionController documentation cleanups. Closes #7319 [jeremymcanally]
|
||||
|
||||
* Performance: patch cgi/session/pstore to require digest/md5 once rather than per #initialize. #7583 [Stefan Kaes]
|
||||
|
||||
* Deprecation: verification with :redirect_to => :named_route shouldn't be deprecated. #7525 [Justin French]
|
||||
|
||||
|
||||
*1.13.3* (March 12th, 2007)
|
||||
|
||||
* Fix a bug in Routing where a parameter taken from the path of the current request could not be used as a query parameter for the next. #6752 [Nicholas Seckar]
|
||||
|
||||
* session_enabled? works with session :off. #6680 [Catfish]
|
||||
|
||||
* Performance: patch cgi/session to require digest/md5 once rather than per #create_new_id. [Stefan Kaes]
|
||||
|
||||
|
||||
*1.13.2* (February 5th, 2007)
|
||||
|
||||
* Add much-needed html-scanner tests. Fixed CDATA parsing bug. [Rick]
|
||||
|
||||
* improve error message for Routing for named routes. [Rob Sanheim]
|
||||
|
||||
* Added enhanced docs to routing assertions. [Rob Sanheim]
|
||||
|
||||
* fix form_for example in ActionController::Resources documentation. [gnarg]
|
||||
|
||||
* Add singleton resources from trunk [Rick Olson]
|
||||
|
||||
* select :multiple => true suffixes the attribute name with [] unless already suffixed. #6977 [nik.kakelin, ben, julik]
|
||||
|
||||
* Improve routes documentation. #7095 [zackchandler]
|
||||
|
||||
* Resource member routes require :id, eliminating the ambiguous overlap with collection routes. #7229 [dkubb]
|
||||
|
||||
* Fixed NumberHelper#number_with_delimiter to use "." always for splitting the original number, not the delimiter parameter #7389 [ceefour]
|
||||
|
||||
* Autolinking recognizes trailing and embedded . , : ; #7354 [Jarkko Laine]
|
||||
|
||||
* Make TextHelper::auto_link recognize URLs with colons in path correctly, fixes #7268. [imajes]
|
||||
|
||||
* Improved auto_link to match more valid urls correctly [Tobias Luetke]
|
||||
|
||||
|
||||
*1.13.1* (January 18th, 2007)
|
||||
|
||||
* Fixed content-type bug in Prototype [sam]
|
||||
|
||||
|
||||
*1.13.0* (January 16th, 2007)
|
||||
|
||||
* Modernize cookie testing code, and increase coverage (Heckle++) #7101 [Kevin Clark]
|
||||
|
||||
* Heckling ActionController::Resources::Resource revealed that set_prefixes didn't break when :name_prefix was munged. #7081 [Kevin Clark]
|
||||
|
||||
* Update to Prototype 1.5.0. [Sam Stephenson]
|
||||
|
||||
* Allow exempt_from_layout :rhtml. #6742, #7026 [dcmanges, Squeegy]
|
||||
|
||||
* Fix parsing of array[] CGI parameters so extra empty values aren't included. #6252 [Nicholas Seckar, aiwilliams, brentrowland]
|
||||
|
||||
* link_to_unless_current works with full URLs as well as paths. #6891 [Jarkko Laine, manfred, idrifter]
|
||||
|
||||
* Fix HTML::Node to output double quotes instead of single quotes. Closes #6845 [mitreandy]
|
||||
|
||||
* Fix no method error with error_messages_on. Closes #6935 [nik.wakelin Koz]
|
||||
|
||||
* Slight doc tweak to the ActionView::Helpers::PrototypeHelper#replace docs. Closes #6922 [Steven Bristol]
|
||||
|
||||
* Slight doc tweak to #prepend_filter. Closes #6493 [Jeremy Voorhis]
|
||||
|
||||
* Add more extensive documentation to the AssetTagHelper. Closes #6452 [Bob Silva]
|
||||
|
||||
* Clean up multiple calls to #stringify_keys in TagHelper, add better documentation and testing for TagHelper. Closes #6394 [Bob Silva]
|
||||
|
||||
* [DOCS] fix reference to ActionController::Macros::AutoComplete for #text_field_with_auto_complete. Closes #2578 [Jan Prill]
|
||||
|
||||
* Make sure html_document is reset between integration test requests. [ctm]
|
||||
|
||||
* Set session to an empty hash if :new_session => false and no session cookie or param is present. CGI::Session was raising an unrescued ArgumentError. [Josh Susser]
|
||||
|
||||
* Fix assert_redirected_to bug where redirecting from a nested to to a top-level controller incorrectly added the current controller's nesting. Closes #6128. [Rick Olson]
|
||||
|
||||
* Ensure render :json => ... skips the layout. #6808 [Josh Peek]
|
||||
|
||||
* Silence log_error deprecation warnings from inspecting deprecated instance variables. [Nate Wiger]
|
||||
|
||||
* Only cache GET requests with a 200 OK response. #6514, #6743 [RSL, anamba]
|
||||
|
||||
* Correctly report which filter halted the chain. #6699 [Martin Emde]
|
||||
|
||||
* respond_to recognizes JSON. render :json => @person.to_json automatically sets the content type and takes a :callback option to specify a client-side function to call using the rendered JSON as an argument. #4185 [Scott Raymond, eventualbuddha]
|
||||
# application/json response with body 'Element.show({:name: "David"})'
|
||||
respond_to do |format|
|
||||
format.json { render :json => { :name => "David" }.to_json, :callback => 'Element.show' }
|
||||
end
|
||||
|
||||
* Makes :discard_year work without breaking multi-attribute parsing in AR. #1260, #3800 [sean@ardismg.com, jmartin@desertflood.com, stephen@touset.org, Bob Silva]
|
||||
|
||||
* Adds html id attribute to date helper elements. #1050, #1382 [mortonda@dgrmm.net, David North, Bob Silva]
|
||||
|
||||
* Add :index and @auto_index capability to model driven date/time selects. #847, #2655 [moriq, Doug Fales, Bob Silva]
|
||||
|
||||
* Add :order to datetime_select, select_datetime, and select_date. #1427 [Timothee Peignier, patrick@lenz.sh, Bob Silva]
|
||||
|
||||
* Added time_select to work with time values in models. Update scaffolding. #2489, #2833 [Justin Palmer, Andre Caum, Bob Silva]
|
||||
|
||||
* Added :include_seconds to select_datetime, datetime_select and time_select. #2998 [csn, Bob Silva]
|
||||
|
||||
* All date/datetime selects can now accept an array of month names with :use_month_names. Allows for localization. #363 [tomasj, Bob Silva]
|
||||
|
||||
* Adds :time_separator to select_time and :date_separator to select_datetime. Preserves BC. #3811 [Bob Silva]
|
||||
|
||||
* @response.redirect_url works with 201 Created responses: just return headers['Location'] rather than checking the response status. [Jeremy Kemper]
|
||||
|
||||
* Fixed that HEAD should return the proper Content-Length header (that is, actually use @body.size, not just 0) [DHH]
|
||||
|
||||
* Added GET-masquarading for HEAD, so request.method will return :get even for HEADs. This will help anyone relying on case request.method to automatically work with HEAD and map.resources will also allow HEADs to all GET actions. Rails automatically throws away the response content in a reply to HEAD, so you don't even need to worry about that. If you, for whatever reason, still need to distinguish between GET and HEAD in some edge case, you can use Request#head? and even Request.headers["REQUEST_METHOD"] for get the "real" answer. Closes #6694 [DHH]
|
||||
|
||||
|
||||
*1.13.0 RC1* (r5619, November 22nd, 2006)
|
||||
|
||||
* Update Routing to complain when :controller is not specified by a route. Closes #6669. [Nicholas Seckar]
|
||||
|
||||
* Ensure render_to_string cleans up after itself when an exception is raised. #6658 [rsanheim]
|
||||
|
||||
* Update to Prototype and script.aculo.us [5579]. [Sam Stephenson, Thomas Fuchs]
|
||||
|
||||
* simple_format helper doesn't choke on nil. #6644 [jerry426]
|
||||
|
||||
* Reuse named route helper module between Routing reloads. Use remove_method to delete named route methods after each load. Since the module is never collected, this fixes a significant memory leak. [Nicholas Seckar]
|
||||
|
||||
* Deprecate standalone components. [Jeremy Kemper]
|
||||
|
||||
* Always clear model associations from session. #4795 [sd@notso.net, andylien@gmail.com]
|
||||
|
||||
* Remove JavaScriptLiteral in favor of ActiveSupport::JSON::Variable. [Sam Stephenson]
|
||||
|
||||
* Sync ActionController::StatusCodes::STATUS_CODES with http://www.iana.org/assignments/http-status-codes. #6586 [dkubb]
|
||||
|
||||
* Multipart form values may have a content type without being treated as uploaded files if they do not provide a filename. #6401 [Andreas Schwarz, Jeremy Kemper]
|
||||
|
||||
* assert_response supports symbolic status codes. #6569 [Kevin Clark]
|
||||
assert_response :ok
|
||||
assert_response :not_found
|
||||
assert_response :forbidden
|
||||
|
||||
* Cache parsed query parameters. #6559 [Stefan Kaes]
|
||||
|
||||
* Deprecate JavaScriptHelper#update_element_function, which is superseeded by RJS [Thomas Fuchs]
|
||||
|
||||
* Fix invalid test fixture exposed by stricter Ruby 1.8.5 multipart parsing. #6524 [Bob Silva]
|
||||
|
||||
* Set ActionView::Base.default_form_builder once rather than passing the :builder option to every form or overriding the form helper methods. [Jeremy Kemper]
|
||||
|
||||
* Deprecate expire_matched_fragments. Use expire_fragment instead. #6535 [Bob Silva]
|
||||
|
||||
* Deprecate start_form_tag and end_form_tag. Use form_tag / '</form>' from now on. [Rick]
|
||||
|
||||
@@ -35,8 +224,6 @@
|
||||
|
||||
* Fix double-escaped entities, such as &amp;, &#123;, etc. [Rick]
|
||||
|
||||
* Fix deprecation warnings when rendering the template error template. [Nicholas Seckar]
|
||||
|
||||
* Fix routing to correctly determine when generation fails. Closes #6300. [psross].
|
||||
|
||||
* Fix broken assert_generates when extra keys are being checked. [Jamis Buck]
|
||||
@@ -47,10 +234,6 @@
|
||||
|
||||
* Use String#chars in TextHelper::excerpt. Closes #6386 [Manfred Stienstra]
|
||||
|
||||
* Install named routes into ActionView::Base instead of proxying them to the view via helper_method. Closes #5932. [Nicholas Seckar]
|
||||
|
||||
* Update to latest Prototype and script.aculo.us trunk versions [Thomas Fuchs]
|
||||
|
||||
* Fix relative URL root matching problems. [Mark Imbriaco]
|
||||
|
||||
* Fix filter skipping in controller subclasses. #5949, #6297, #6299 [Martin Emde]
|
||||
@@ -106,8 +289,6 @@
|
||||
|
||||
* Filters overhaul including meantime filter support using around filters + blocks. #5949 [Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper]
|
||||
|
||||
* Update RJS render tests. [sam]
|
||||
|
||||
* Update CGI process to allow sessions to contain namespaced models. Closes #4638. [dfelstead@site5.com]
|
||||
|
||||
* Fix routing to respect user provided requirements and defaults when assigning default routing options (such as :action => 'index'). Closes #5950. [Nicholas Seckar]
|
||||
@@ -133,12 +314,6 @@
|
||||
response.content_type = Mime::ATOM
|
||||
response.charset = "utf-8"
|
||||
|
||||
* Updated prototype.js to 1.5.0_rc1 with latest fixes. [Rick Olson]
|
||||
|
||||
- XPATH support
|
||||
- Make Form.getElements() return elements in the correct order
|
||||
- fix broken Form.serialize return
|
||||
|
||||
* Declare file extensions exempt from layouts. #6219 [brandon]
|
||||
Example: ActionController::Base.exempt_from_layout 'rpdf'
|
||||
|
||||
@@ -163,8 +338,6 @@
|
||||
|
||||
* Fix assert_tag so that :content => "foo" does not match substrings, but only exact strings. Use :content => /foo/ to match substrings. #2799 [Eric Hodel]
|
||||
|
||||
* Add descriptive messages to the exceptions thrown by cgi_methods. #6091, #6103 [Nicholas Seckar, Bob Silva]
|
||||
|
||||
* Update JavaScriptGenerator#show/hide/toggle/remove to new Prototype syntax for multiple ids, #6068 [petermichaux@gmail.com]
|
||||
|
||||
* Update UrlWriter to support :only_path. [Nicholas Seckar, Dave Thomas]
|
||||
@@ -177,10 +350,6 @@
|
||||
|
||||
link_to("Hider", :class => "hider_link") { |p| p[:something].hide }
|
||||
|
||||
* Update to script.aculo.us 1.6.3 [Thomas Fuchs]
|
||||
|
||||
* Update to Prototype 1.5.0_rc1 [sam]
|
||||
|
||||
* Added access to nested attributes in RJS #4548 [richcollins@gmail.com]. Examples:
|
||||
|
||||
page['foo']['style'] # => $('foo').style;
|
||||
@@ -202,9 +371,6 @@
|
||||
|
||||
* Changed that uncaught exceptions raised any where in the application will cause RAILS_ROOT/public/500.html to be read and shown instead of just the static "Application error (Rails)" [DHH]
|
||||
|
||||
* Integration tests: thoroughly test ActionController::Integration::Session. #6022 [Kevin Clark]
|
||||
(tests skipped unless you `gem install mocha`)
|
||||
|
||||
* Added deprecation language for pagination which will become a plugin by Rails 2.0 [DHH]
|
||||
|
||||
* Added deprecation language for in_place_editor and auto_complete_field that both pieces will become plugins by Rails 2.0 [DHH]
|
||||
@@ -215,7 +381,7 @@
|
||||
|
||||
* radio_button_tag generates unique id attributes. #3353 [Bob Silva, somekool@gmail.com]
|
||||
|
||||
* strip_tags returns nil for a blank arg such as nil or "". #2229 [duncan@whomwah.com]
|
||||
* strip_tags passes through blank args such as nil or "". #2229, #6702 [duncan@whomwah.com, dharana]
|
||||
|
||||
* Cleanup assert_tag :children counting. #2181 [jamie@bravenet.com]
|
||||
|
||||
@@ -223,20 +389,12 @@
|
||||
|
||||
* Update sanitize text helper to strip plaintext tags, and <img src="javascript:bang">. [Rick Olson]
|
||||
|
||||
* Update routing documentation. Closes #6017 [Nathan Witmer]
|
||||
|
||||
* Add routing tests to assert that RoutingError is raised when conditions aren't met. Closes #6016 [Nathan Witmer]
|
||||
|
||||
* Deprecation: update docs. #5998 [jakob@mentalized.net, Kevin Clark]
|
||||
|
||||
* Make auto_link parse a greater subset of valid url formats. [Jamis Buck]
|
||||
|
||||
* Integration tests: headers beginning with X aren't excluded from the HTTP_ prefix, so X-Requested-With becomes HTTP_X_REQUESTED_WITH as expected. [Mike Clark]
|
||||
|
||||
* Tighten rescue clauses. #5985 [james@grayproductions.net]
|
||||
|
||||
* Fix send_data documentation typo. #5982 [brad@madriska.com]
|
||||
|
||||
* Switch to using FormEncodedPairParser for parsing request parameters. [Nicholas Seckar, DHH]
|
||||
|
||||
* respond_to .html now always renders #{action_name}.rhtml so that registered custom template handlers do not override it in priority. Custom mime types require a block and throw proper error now. [Tobias Luetke]
|
||||
@@ -245,12 +403,6 @@
|
||||
|
||||
* Add UrlWriter to allow writing urls from Mailers and scripts. [Nicholas Seckar]
|
||||
|
||||
* Clean up and run the Active Record integration tests by default. #5854 [kevin.clark@gmail.com, Jeremy Kemper]
|
||||
|
||||
* Correct example in cookies docs. #5832 [jessemerriman@warpmail.net]
|
||||
|
||||
* Updated to script.aculo.us 1.6.2 [Thomas Fuchs]
|
||||
|
||||
* Relax Routing's anchor pattern warning; it was preventing use of [^/] inside restrictions. [Nicholas Seckar]
|
||||
|
||||
* Add controller_paths variable to Routing. [Nicholas Seckar]
|
||||
@@ -261,8 +413,6 @@
|
||||
|
||||
* Invoke method_missing directly on hidden actions. Closes #3030. [Nicholas Seckar]
|
||||
|
||||
* Require Tempfile explicitly for TestUploadedFile due to changes in class auto loading. [Rick Olson]
|
||||
|
||||
* Add RoutingError exception when RouteSet fails to generate a path from a Named Route. [Rick Olson]
|
||||
|
||||
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
|
||||
@@ -277,8 +427,6 @@
|
||||
|
||||
* Added months and years to the resolution of DateHelper#distance_of_time_in_words, such that "60 days ago" becomes "2 months ago" #5611 [pjhyett@gmail.com]
|
||||
|
||||
* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@gmail.com]
|
||||
|
||||
* Make controller_path available as an instance method. #5724 [jmckible@gmail.com]
|
||||
|
||||
* Update query parser to support adjacent hashes. [Nicholas Seckar]
|
||||
@@ -287,10 +435,6 @@
|
||||
|
||||
* Restrict Request Method hacking with ?_method to POST requests. [Rick Olson]
|
||||
|
||||
* Fix bug when passing multiple options to SimplyRestful, like :new => { :preview => :get, :draft => :get }. [Rick Olson, Josh Susser, Lars Pind]
|
||||
|
||||
* Dup the options passed to map.resources so that multiple resources get the same options. [Rick Olson]
|
||||
|
||||
* Fixed the new_#{resource}_url route and added named route tests for Simply Restful. [Rick Olson]
|
||||
|
||||
* Added map.resources from the Simply Restful plugin [DHH]. Examples (the API has changed to use plurals!):
|
||||
@@ -318,14 +462,10 @@
|
||||
|
||||
* Provide support for decimal columns to form helpers. Closes #5672. [dave@pragprog.com]
|
||||
|
||||
* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
|
||||
|
||||
* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
|
||||
|
||||
* Reset @html_document between requests so assert_tag works. #4810 [jarkko@jlaine.net, easleydp@gmail.com]
|
||||
|
||||
* Update render :partial documentation. #5646 [matt@mattmargolis.net]
|
||||
|
||||
* Integration tests behave well with render_component. #4632 [edward.frederick@revolution.com, dev.rubyonrails@maxdunn.com]
|
||||
|
||||
* Added exception handling of missing layouts #5373 [chris@ozmm.org]
|
||||
@@ -342,7 +482,7 @@
|
||||
|
||||
* Avoid naming collision among compiled view methods. [Jeremy Kemper]
|
||||
|
||||
* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [mislav@nippur.irb.hr]
|
||||
* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [Mislav Marohnic]
|
||||
|
||||
* Determine the correct template_root for deeply nested components. #2841 [s.brink@web.de]
|
||||
|
||||
@@ -360,8 +500,6 @@
|
||||
|
||||
* Make sure :id and friends are unescaped properly. #5275 [me@julik.nl]
|
||||
|
||||
* Fix documentation for with_routing to reflect new reality. #5281 [rramdas@gmail.com]
|
||||
|
||||
* Rewind readable CGI params so others may reread them (such as CGI::Session when passing the session id in a multipart form). #210 [mklame@atxeu.com, matthew@walker.wattle.id.au]
|
||||
|
||||
* Added Mime::TEXT (text/plain) and Mime::ICS (text/calendar) as new default types [DHH]
|
||||
@@ -425,8 +563,6 @@
|
||||
|
||||
* Fix NoMethodError when parsing params like &&. [Adam Greenfield]
|
||||
|
||||
* Fix flip flopped logic in docs for url_for's :only_path option. Closes #4998. [esad@esse.at]
|
||||
|
||||
* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). [Jeremy Kemper]
|
||||
|
||||
* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 [anonymous]
|
||||
@@ -435,43 +571,17 @@
|
||||
|
||||
map.connect '*path', :controller => 'files', :action => 'show'
|
||||
|
||||
* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
|
||||
|
||||
* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
|
||||
|
||||
* Use #flush between switching from #write to #syswrite. Closes #4907. [Blair Zajac <blair@orcaware.com>]
|
||||
|
||||
* Documentation fix: integration test scripts don't require integration_test. Closes #4914. [Frederick Ros <sl33p3r@free.fr>]
|
||||
|
||||
* ActionController::Base Summary documentation rewrite. Closes #4900. [kevin.clark@gmail.com]
|
||||
|
||||
* Fix text_helper.rb documentation rendering. Closes #4725. [Frederick Ros]
|
||||
|
||||
* Fixes bad rendering of JavaScriptMacrosHelper rdoc (closes #4910) [Frederick Ros]
|
||||
|
||||
* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. [andrew@redlinesoftware.com, Marcel Molina Jr.]
|
||||
|
||||
error_messages_for :account, :user, :subscription, :object_name => :account
|
||||
|
||||
* Enhance documentation for setting headers in integration tests. Skip auto HTTP prepending when its already there. Closes #4079. [Rick Olson]
|
||||
|
||||
* Documentation for AbstractRequest. Closes #4895. [kevin.clark@gmail.com]
|
||||
|
||||
* Refactor various InstanceTag instance method to class methods. Closes #4800. [skaes@web.de]
|
||||
|
||||
* Remove all remaining references to @params in the documentation. [Marcel Molina Jr.]
|
||||
|
||||
* Add documentation for redirect_to :back's RedirectBackError exception. [Marcel Molina Jr.]
|
||||
|
||||
* Update layout and content_for documentation to use yield rather than magic @content_for instance variables. [Marcel Molina Jr.]
|
||||
|
||||
* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: [Rick]
|
||||
|
||||
redirect_to :action => 'new'
|
||||
assert_redirected_to :controller => 'monkeys', :action => 'new'
|
||||
|
||||
* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick]
|
||||
|
||||
* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method [Rick]
|
||||
|
||||
assert_recognizes({:controller => 'users', :action => 'index'}, 'users')
|
||||
@@ -483,8 +593,6 @@
|
||||
|
||||
* 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]
|
||||
|
||||
* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. [Jamis Buck]
|
||||
|
||||
* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. [Sam Stephenson]
|
||||
@@ -492,7 +600,43 @@
|
||||
:render => { :status => 405, :text => "Must be post" },
|
||||
:add_headers => { "Allow" => "POST" }
|
||||
|
||||
* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled #1897 [jeremye@bsa.ca.gov]
|
||||
|
||||
*1.12.5* (August 10th, 2006)
|
||||
|
||||
* Updated security fix
|
||||
|
||||
|
||||
*1.12.4* (August 8th, 2006)
|
||||
|
||||
* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick]
|
||||
|
||||
* 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]
|
||||
|
||||
* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled. #1897 [jeremye@bsa.ca.gov]
|
||||
|
||||
* Fixed that real files and symlinks should be treated the same when compiling templates. #5438 [zachary@panandscan.com]
|
||||
|
||||
* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 [Manfred Stienstra <m.stienstra@fngtps.com>]
|
||||
|
||||
* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
|
||||
|
||||
* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@gmail.com]
|
||||
|
||||
|
||||
*1.12.3* (June 28th, 2006)
|
||||
|
||||
* Fix broken traverse_to_controller. We now:
|
||||
Look for a _controller.rb file under RAILS_ROOT to load.
|
||||
If we find it, we require_dependency it and return the controller it defined. (If none was defined we stop looking.)
|
||||
If we don't find it, we look for a .rb file under RAILS_ROOT to load. If we find it, and it loads a constant we keep looking.
|
||||
Otherwise we check to see if a directory of the same name exists, and if it does we create a module for it.
|
||||
|
||||
|
||||
*1.12.2* (June 27th, 2006)
|
||||
|
||||
* Refinement to avoid exceptions in traverse_to_controller.
|
||||
|
||||
* (Hackish) Fix loading of arbitrary files in Ruby's load path by traverse_to_controller. [Nicholas Seckar]
|
||||
|
||||
|
||||
*1.12.1* (April 6th, 2006)
|
||||
@@ -2727,9 +2871,9 @@ Default YAML web services were retired, ActionController::Base.param_parsers car
|
||||
|
||||
* Added pluralize method to the TextHelper that makes it easy to get strings like "1 message", "3 messages"
|
||||
|
||||
* Added proper escaping for the rescues [Andreas Schwartz]
|
||||
* Added proper escaping for the rescues [Andreas Schwarz]
|
||||
|
||||
* Added proper escaping for the option and collection tags [Andreas Schwartz]
|
||||
* Added proper escaping for the option and collection tags [Andreas Schwarz]
|
||||
|
||||
* Fixed NaN errors on benchmarking [Jim Weirich]
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ spec = Gem::Specification.new do |s|
|
||||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency('activesupport', '= 1.3.1' + PKG_BUILD)
|
||||
s.add_dependency('activesupport', '= 1.4.3' + PKG_BUILD)
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'action_controller'
|
||||
@@ -144,11 +144,11 @@ end
|
||||
|
||||
desc "Publish the release files to RubyForge."
|
||||
task :release => [ :package ] do
|
||||
`rubyforge login`
|
||||
require 'rubyforge'
|
||||
|
||||
for ext in %w( gem tgz zip )
|
||||
release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
|
||||
puts release_command
|
||||
system(release_command)
|
||||
end
|
||||
end
|
||||
packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
|
||||
|
||||
rubyforge = RubyForge.new
|
||||
rubyforge.login
|
||||
rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
|
||||
end
|
||||
@@ -30,7 +30,7 @@ unless defined?(ActiveSupport)
|
||||
require 'active_support'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require_gem 'activesupport'
|
||||
gem 'activesupport'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ require 'rexml/document'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module Assertions #:nodoc:
|
||||
module DeprecatedAssertions
|
||||
module DeprecatedAssertions #:nodoc:
|
||||
def assert_success(message=nil) #:nodoc:
|
||||
assert_response(:success, message)
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module ActionController
|
||||
module Assertions
|
||||
module DomAssertions
|
||||
# test 2 html strings to be equivalent, i.e. identical up to reordering of attributes
|
||||
# Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
|
||||
def assert_dom_equal(expected, actual, message="")
|
||||
clean_backtrace do
|
||||
expected_dom = HTML::Document.new(expected).root
|
||||
@@ -11,7 +11,7 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
# negated form of +assert_dom_equivalent+
|
||||
# The negated form of +assert_dom_equivalent+.
|
||||
def assert_dom_not_equal(expected, actual, message="")
|
||||
clean_backtrace do
|
||||
expected_dom = HTML::Document.new(expected).root
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module ActionController
|
||||
module Assertions
|
||||
module ModelAssertions
|
||||
# ensures that the passed record is valid by active record standards. returns the error messages if not
|
||||
# Ensures that the passed record is valid by ActiveRecord standards and returns any error messages if it is not.
|
||||
def assert_valid(record)
|
||||
clean_backtrace do
|
||||
assert record.valid?, record.errors.full_messages.join("\n")
|
||||
|
||||
@@ -5,27 +5,31 @@ module ActionController
|
||||
module Assertions
|
||||
module ResponseAssertions
|
||||
# Asserts that the response is one of the following types:
|
||||
#
|
||||
#
|
||||
# * <tt>:success</tt>: Status code was 200
|
||||
# * <tt>:redirect</tt>: Status code was in the 300-399 range
|
||||
# * <tt>:missing</tt>: Status code was 404
|
||||
# * <tt>:error</tt>: Status code was in the 500-599 range
|
||||
#
|
||||
# You can also pass an explicit status code number as the type, like assert_response(501)
|
||||
# You can also pass an explicit status number like assert_response(501)
|
||||
# or its symbolic equivalent assert_response(:not_implemented).
|
||||
# See ActionController::StatusCodes for a full list.
|
||||
def assert_response(type, message = nil)
|
||||
clean_backtrace do
|
||||
if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
|
||||
assert_block("") { true } # to count the assertion
|
||||
elsif type.is_a?(Fixnum) && @response.response_code == type
|
||||
assert_block("") { true } # to count the assertion
|
||||
elsif type.is_a?(Symbol) && @response.response_code == ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[type]
|
||||
assert_block("") { true } # to count the assertion
|
||||
else
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial,
|
||||
# such that assert_redirected_to(:controller => "weblog") will also match the redirection of
|
||||
# such that assert_redirected_to(:controller => "weblog") will also match the redirection of
|
||||
# redirect_to(:controller => "weblog", :action => "show") and so on.
|
||||
def assert_redirected_to(options = {}, message=nil)
|
||||
clean_backtrace do
|
||||
@@ -66,18 +70,19 @@ module ActionController
|
||||
|
||||
if value.respond_to?(:[]) && value['controller']
|
||||
if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/')
|
||||
value['controller'] = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
|
||||
new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
|
||||
value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path)
|
||||
end
|
||||
value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash
|
||||
end
|
||||
url[key] = value
|
||||
end
|
||||
|
||||
|
||||
|
||||
@response_diff = url[:expected].diff(url[:actual]) if url[:actual]
|
||||
msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>), difference: <?>",
|
||||
msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>), difference: <?>",
|
||||
url[:actual], @response_diff)
|
||||
|
||||
|
||||
assert_block(msg) do
|
||||
url[:expected].keys.all? do |k|
|
||||
if k == :controller then url[:expected][k] == ActionController::Routing.controller_relative_to(url[:actual][k], @controller.class.controller_path)
|
||||
@@ -94,7 +99,7 @@ module ActionController
|
||||
end.flatten
|
||||
|
||||
assert_equal(eurl, url, msg) if eurl && url
|
||||
assert_equal(epath, path, msg) if epath && path
|
||||
assert_equal(epath, path, msg) if epath && path
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -110,11 +115,12 @@ module ActionController
|
||||
else
|
||||
expected == rendered
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Recognizes the route for a given path.
|
||||
def recognized_request_for(path, request_method = nil)
|
||||
path = "/#{path}" unless path.first == '/'
|
||||
|
||||
@@ -127,9 +133,10 @@ module ActionController
|
||||
request
|
||||
end
|
||||
|
||||
# Proxy to to_param if the object will respond to it.
|
||||
def parameterize(value)
|
||||
value.respond_to?(:to_param) ? value.to_param : value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,13 +3,23 @@ module ActionController
|
||||
module RoutingAssertions
|
||||
# Asserts that the routing of the given path was handled correctly and that the parsed options match.
|
||||
#
|
||||
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
|
||||
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items') # check the default action
|
||||
# assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list') # check a specific action
|
||||
# assert_recognizes({:controller => 'items', :action => 'list', :id => '1'}, 'items/list/1') # check an action with a parameter
|
||||
#
|
||||
# Pass a hash in the second argument to specify the request method. This is useful for routes
|
||||
# requiring a specific method.
|
||||
# requiring a specific HTTP method. The hash should contain a :path with the incoming request path
|
||||
# and a :method containing the required HTTP verb.
|
||||
#
|
||||
# # assert that POSTing to /items will call the create action on ItemsController
|
||||
# assert_recognizes({:controller => 'items', :action => 'create'}, {:path => 'items', :method => :post})
|
||||
#
|
||||
# You can also pass in "extras" with a hash containing URL parameters that would normally be in the query string. This can be used
|
||||
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
|
||||
# extras argument, appending the query string on the path directly will not work. For example:
|
||||
#
|
||||
# # assert that a path of '/items/list/1?view=print' returns the correct options
|
||||
# assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
|
||||
def assert_recognizes(expected_options, path, extras={}, message=nil)
|
||||
if path.is_a? Hash
|
||||
request_method = path[:method]
|
||||
@@ -33,7 +43,12 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the provided options can be used to generate the provided path.
|
||||
# Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes.
|
||||
# For example:
|
||||
#
|
||||
# assert_generates("/items", :controller => "items", :action => "index")
|
||||
# assert_generates("/items/list", :controller => "items", :action => "list")
|
||||
# assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
|
||||
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
|
||||
clean_backtrace do
|
||||
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
|
||||
@@ -53,7 +68,8 @@ module ActionController
|
||||
end
|
||||
|
||||
# Asserts that path and options match both ways; in other words, the URL generated from
|
||||
# options is the same as path, and also that the options recognized from path are the same as options
|
||||
# options is the same as path, and also that the options recognized from path are the same as options. This
|
||||
# essentially combines assert_recognizes and assert_generates into one step.
|
||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
||||
assert_recognizes(options, path, extras, message)
|
||||
|
||||
@@ -66,6 +82,7 @@ module ActionController
|
||||
end
|
||||
|
||||
private
|
||||
# Recognizes the route for a given path.
|
||||
def recognized_request_for(path, request_method = nil)
|
||||
path = "/#{path}" unless path.first == '/'
|
||||
|
||||
|
||||
@@ -292,6 +292,10 @@ module ActionController #:nodoc:
|
||||
# Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
|
||||
cattr_accessor :ignore_missing_templates
|
||||
|
||||
# Controls the resource action separator
|
||||
@@resource_action_separator = "/"
|
||||
cattr_accessor :resource_action_separator
|
||||
|
||||
# Holds the request object that's primarily used to get environment variables through access like
|
||||
# <tt>request.env["REQUEST_URI"]</tt>.
|
||||
attr_internal :request
|
||||
@@ -393,7 +397,8 @@ module ActionController #:nodoc:
|
||||
elsif value.is_a?(Hash)
|
||||
filtered_parameters[key] = filter_parameters(value)
|
||||
elsif block_given?
|
||||
key, value = key.dup, value.dup
|
||||
key = key.dup
|
||||
value = value.dup if value
|
||||
yield key, value
|
||||
filtered_parameters[key] = value
|
||||
else
|
||||
@@ -407,10 +412,13 @@ module ActionController #:nodoc:
|
||||
|
||||
# Don't render layouts for templates with the given extensions.
|
||||
def exempt_from_layout(*extensions)
|
||||
regexps = extensions.collect do |extension|
|
||||
extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
|
||||
end
|
||||
@@exempt_from_layout.merge regexps
|
||||
@@exempt_from_layout.merge extensions.collect { |extension|
|
||||
if extension.is_a?(Regexp)
|
||||
extension
|
||||
else
|
||||
/\.#{Regexp.escape(extension.to_s)}$/
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -535,8 +543,9 @@ module ActionController #:nodoc:
|
||||
self.class.controller_path
|
||||
end
|
||||
|
||||
# Test whether the session is enabled for this request.
|
||||
def session_enabled?
|
||||
request.session_options[:disabled] != false
|
||||
request.session_options && request.session_options[:disabled] != false
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -650,6 +659,20 @@ module ActionController #:nodoc:
|
||||
#
|
||||
# _Deprecation_ _notice_: This used to have the signature <tt>render_text("text", status = 200)</tt>
|
||||
#
|
||||
# === Rendering JSON
|
||||
#
|
||||
# Rendering JSON sets the content type to text/x-json and optionally wraps the JSON in a callback. It is expected
|
||||
# that the response will be eval'd for use as a data structure.
|
||||
#
|
||||
# # Renders '{name: "David"}'
|
||||
# render :json => {:name => "David"}.to_json
|
||||
#
|
||||
# Sometimes the result isn't handled directly by a script (such as when the request comes from a SCRIPT tag),
|
||||
# so the callback option is provided for these cases.
|
||||
#
|
||||
# # Renders 'show({name: "David"})'
|
||||
# render :json => {:name => "David"}.to_json, :callback => 'show'
|
||||
#
|
||||
# === Rendering an inline template
|
||||
#
|
||||
# Rendering of an inline template works as a cross between text and action rendering where the source for the template
|
||||
@@ -734,6 +757,9 @@ module ActionController #:nodoc:
|
||||
elsif xml = options[:xml]
|
||||
render_xml(xml, options[:status])
|
||||
|
||||
elsif json = options[:json]
|
||||
render_json(json, options[:callback], options[:status])
|
||||
|
||||
elsif partial = options[:partial]
|
||||
partial = default_template_name if partial == true
|
||||
if collection = options[:collection]
|
||||
@@ -763,13 +789,11 @@ module ActionController #:nodoc:
|
||||
# Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
|
||||
# of sending it as the response body to the browser.
|
||||
def render_to_string(options = nil, &block) #:doc:
|
||||
result = ActiveSupport::Deprecation.silence { render(options, &block) }
|
||||
|
||||
ActiveSupport::Deprecation.silence { render(options, &block) }
|
||||
ensure
|
||||
erase_render_results
|
||||
forget_variables_added_to_assigns
|
||||
reset_variables_added_to_assigns
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def render_action(action_name, status = nil, with_layout = true) #:nodoc:
|
||||
@@ -816,6 +840,13 @@ module ActionController #:nodoc:
|
||||
render_text(xml, status)
|
||||
end
|
||||
|
||||
def render_json(json, callback = nil, status = nil) #:nodoc:
|
||||
json = "#{callback}(#{json})" unless callback.blank?
|
||||
|
||||
response.content_type = Mime::JSON
|
||||
render_text(json, status)
|
||||
end
|
||||
|
||||
def render_nothing(status = nil) #:nodoc:
|
||||
render_text(' ', status)
|
||||
end
|
||||
@@ -893,7 +924,7 @@ module ActionController #:nodoc:
|
||||
response.redirected_to = nil
|
||||
response.redirected_to_method_params = nil
|
||||
response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE
|
||||
response.headers.delete('location')
|
||||
response.headers.delete('Location')
|
||||
end
|
||||
|
||||
# Erase both render and redirect results
|
||||
@@ -1089,7 +1120,11 @@ module ActionController #:nodoc:
|
||||
|
||||
def assign_default_content_type_and_charset
|
||||
response.content_type ||= Mime::HTML
|
||||
response.charset ||= self.class.default_charset
|
||||
response.charset ||= self.class.default_charset unless sending_file?
|
||||
end
|
||||
|
||||
def sending_file?
|
||||
response.headers["Content-Transfer-Encoding"] == "binary"
|
||||
end
|
||||
|
||||
def action_methods
|
||||
@@ -1164,10 +1199,9 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def template_exempt_from_layout?(template_name = default_template_name)
|
||||
@@exempt_from_layout.any? { |ext| template_name =~ ext } or
|
||||
@template.pick_template_extension(template_name) == :rjs
|
||||
rescue
|
||||
false
|
||||
extension = @template.pick_template_extension(template_name) rescue nil
|
||||
name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name
|
||||
extension == :rjs || @@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
|
||||
end
|
||||
|
||||
def assert_existence_of_template_file(template_name)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require 'fileutils'
|
||||
require 'uri'
|
||||
require 'set'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
||||
@@ -135,7 +136,7 @@ module ActionController #:nodoc:
|
||||
|
||||
private
|
||||
def caching_allowed
|
||||
!request.post? && response.headers['Status'] && response.headers['Status'].to_i < 400
|
||||
request.get? && response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
|
||||
@@ -163,13 +164,24 @@ module ActionController #:nodoc:
|
||||
module Actions
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.send(:attr_accessor, :rendered_action_cache)
|
||||
base.class_eval do
|
||||
attr_accessor :rendered_action_cache, :action_cache_path
|
||||
alias_method_chain :protected_instance_variables, :action_caching
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def protected_instance_variables_with_action_caching
|
||||
protected_instance_variables_without_action_caching + %w(@action_cache_path)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Declares that +actions+ should be cached.
|
||||
# See ActionController::Caching::Actions for details.
|
||||
def caches_action(*actions)
|
||||
return unless perform_caching
|
||||
around_filter(ActionCacheFilter.new(*actions))
|
||||
action_cache_filter = ActionCacheFilter.new(*actions)
|
||||
before_filter action_cache_filter
|
||||
after_filter action_cache_filter
|
||||
end
|
||||
end
|
||||
|
||||
@@ -185,70 +197,59 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(*actions, &block)
|
||||
@actions = actions
|
||||
def initialize(*actions)
|
||||
@actions = Set.new actions
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
return unless @actions.include?(controller.action_name.intern)
|
||||
action_cache_path = ActionCachePath.new(controller)
|
||||
if cache = controller.read_fragment(action_cache_path.path)
|
||||
return unless @actions.include?(controller.action_name.to_sym)
|
||||
cache_path = ActionCachePath.new(controller, {})
|
||||
if cache = controller.read_fragment(cache_path.path)
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(action_cache_path)
|
||||
set_content_type!(controller, cache_path.extension)
|
||||
controller.send(:render_text, cache)
|
||||
false
|
||||
else
|
||||
controller.action_cache_path = cache_path
|
||||
end
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
|
||||
controller.write_fragment(ActionCachePath.path_for(controller), controller.response.body)
|
||||
return if !@actions.include?(controller.action_name.to_sym) || controller.rendered_action_cache
|
||||
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_content_type!(action_cache_path)
|
||||
if extention = action_cache_path.extension
|
||||
content_type = Mime::EXTENSION_LOOKUP[extention]
|
||||
action_cache_path.controller.response.content_type = content_type.to_s
|
||||
end
|
||||
def set_content_type!(controller, extension)
|
||||
controller.response.content_type = Mime::EXTENSION_LOOKUP[extension].to_s if extension
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
attr_reader :controller, :options
|
||||
attr_reader :path, :extension
|
||||
|
||||
class << self
|
||||
def path_for(*args, &block)
|
||||
new(*args).path
|
||||
def path_for(controller, options)
|
||||
new(controller, options).path
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(controller, options = {})
|
||||
@controller = controller
|
||||
@options = options
|
||||
end
|
||||
|
||||
def path
|
||||
return @path if @path
|
||||
@path = controller.url_for(options).split('://').last
|
||||
normalize!
|
||||
add_extension!
|
||||
URI.unescape(@path)
|
||||
end
|
||||
|
||||
def extension
|
||||
@extension ||= extract_extension(controller.request.path)
|
||||
@extension = extract_extension(controller.request.path)
|
||||
path = controller.url_for(options).split('://').last
|
||||
normalize!(path)
|
||||
add_extension!(path, @extension)
|
||||
@path = URI.unescape(path)
|
||||
end
|
||||
|
||||
private
|
||||
def normalize!
|
||||
@path << 'index' if @path.last == '/'
|
||||
def normalize!(path)
|
||||
path << 'index' if path[-1] == ?/
|
||||
end
|
||||
|
||||
def add_extension!
|
||||
@path << ".#{extension}" if extension
|
||||
def add_extension!(path, extension)
|
||||
path << ".#{extension}" if extension
|
||||
end
|
||||
|
||||
def extract_extension(file_path)
|
||||
@@ -363,7 +364,12 @@ module ActionController #:nodoc:
|
||||
# Name can take one of three forms:
|
||||
# * String: This would normally take the form of a path like "pages/45/notes"
|
||||
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
|
||||
# * Regexp: Will destroy all the matched fragments, example: %r{pages/\d*/notes} Ensure you do not specify start and finish in the regex (^$) because the actual filename matched looks like ./cache/filename/path.cache
|
||||
# * Regexp: Will destroy all the matched fragments, example:
|
||||
# %r{pages/\d*/notes}
|
||||
# Ensure you do not specify start and finish in the regex (^$) because
|
||||
# the actual filename matched looks like ./cache/filename/path.cache
|
||||
# Regexp expiration is not supported on caches which can't iterate over
|
||||
# all keys, such as memcached.
|
||||
def expire_fragment(name, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
@@ -384,6 +390,7 @@ module ActionController #:nodoc:
|
||||
def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
|
||||
expire_fragment(matcher, options)
|
||||
end
|
||||
deprecate :expire_matched_fragments => :expire_fragment
|
||||
|
||||
|
||||
class UnthreadedMemoryStore #:nodoc:
|
||||
|
||||
@@ -27,13 +27,6 @@ class CGI #:nodoc:
|
||||
def request_parameters
|
||||
CGIMethods.parse_request_parameters(params, env_table)
|
||||
end
|
||||
|
||||
def redirect(where)
|
||||
header({
|
||||
"Status" => "302 Moved",
|
||||
"location" => "#{where}"
|
||||
})
|
||||
end
|
||||
|
||||
def session(parameters = nil)
|
||||
parameters = {} if parameters.nil?
|
||||
|
||||
@@ -23,18 +23,19 @@ class CGIMethods #:nodoc:
|
||||
def parse_request_parameters(params)
|
||||
parser = FormEncodedPairParser.new
|
||||
|
||||
finished = false
|
||||
until finished
|
||||
finished = true
|
||||
params = params.dup
|
||||
until params.empty?
|
||||
for key, value in params
|
||||
next if key.blank?
|
||||
if !key.include?('[')
|
||||
if key.blank?
|
||||
params.delete key
|
||||
elsif !key.include?('[')
|
||||
# much faster to test for the most common case first (GET)
|
||||
# and avoid the call to build_deep_hash
|
||||
parser.result[key] = get_typed_value(value[0])
|
||||
params.delete key
|
||||
elsif value.is_a?(Array)
|
||||
parser.parse(key, get_typed_value(value.shift))
|
||||
finished = false unless value.empty?
|
||||
params.delete key if value.empty?
|
||||
else
|
||||
raise TypeError, "Expected array, found #{value.inspect}"
|
||||
end
|
||||
@@ -63,48 +64,55 @@ class CGIMethods #:nodoc:
|
||||
|
||||
private
|
||||
def get_typed_value(value)
|
||||
# test most frequent case first
|
||||
if value.is_a?(String)
|
||||
value
|
||||
elsif value.respond_to?(:content_type) && ! value.content_type.blank?
|
||||
# Uploaded file
|
||||
unless value.respond_to?(:full_original_filename)
|
||||
class << value
|
||||
alias_method :full_original_filename, :original_filename
|
||||
case value
|
||||
when String
|
||||
value
|
||||
when NilClass
|
||||
''
|
||||
when Array
|
||||
value.map { |v| get_typed_value(v) }
|
||||
else
|
||||
# Uploaded file provides content type and filename.
|
||||
if value.respond_to?(:content_type) &&
|
||||
!value.content_type.blank? &&
|
||||
!value.original_filename.blank?
|
||||
unless value.respond_to?(:full_original_filename)
|
||||
class << value
|
||||
alias_method :full_original_filename, :original_filename
|
||||
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
# The Windows regexp is adapted from Perl's File::Basename.
|
||||
def original_filename
|
||||
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
|
||||
md.captures.first
|
||||
else
|
||||
File.basename full_original_filename
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
# The Windows regexp is adapted from Perl's File::Basename.
|
||||
def original_filename
|
||||
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
|
||||
md.captures.first
|
||||
else
|
||||
File.basename full_original_filename
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return the same value after overriding original_filename.
|
||||
value
|
||||
|
||||
# Multipart values may have content type, but no filename.
|
||||
elsif value.respond_to?(:read)
|
||||
result = value.read
|
||||
value.rewind
|
||||
result
|
||||
|
||||
# Unknown value, neither string nor multipart.
|
||||
else
|
||||
raise "Unknown form value: #{value.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# Return the same value after overriding original_filename.
|
||||
value
|
||||
|
||||
elsif value.respond_to?(:read)
|
||||
# Value as part of a multipart request
|
||||
result = value.read
|
||||
value.rewind
|
||||
result
|
||||
elsif value.class == Array
|
||||
value.collect { |v| get_typed_value(v) }
|
||||
else
|
||||
# other value (neither string nor a multipart request)
|
||||
value.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FormEncodedPairParser < StringScanner
|
||||
class FormEncodedPairParser < StringScanner #:nodoc:
|
||||
attr_reader :top, :parent, :result
|
||||
|
||||
def initialize(pairs = [])
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# CGI::Session::PStore.initialize requires 'digest/md5' on every call.
|
||||
# This makes sense when spawning processes per request, but is
|
||||
# unnecessarily expensive when serving requests from a long-lived
|
||||
# process.
|
||||
require 'cgi/session'
|
||||
require 'cgi/session/pstore'
|
||||
require 'digest/md5'
|
||||
|
||||
class CGI::Session::PStore #:nodoc:
|
||||
def initialize(session, option={})
|
||||
dir = option['tmpdir'] || Dir::tmpdir
|
||||
prefix = option['prefix'] || ''
|
||||
id = session.session_id
|
||||
md5 = Digest::MD5.hexdigest(id)[0,16]
|
||||
path = dir+"/"+prefix+md5
|
||||
path.untaint
|
||||
if File::exist?(path)
|
||||
@hash = nil
|
||||
else
|
||||
unless session.new_session
|
||||
raise CGI::Session::NoSession, "uninitialized session"
|
||||
end
|
||||
@hash = {}
|
||||
end
|
||||
@p = ::PStore.new(path)
|
||||
@p.transaction do |p|
|
||||
File.chmod(0600, p.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -65,7 +65,7 @@ class CGI #:nodoc:
|
||||
if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank?
|
||||
uri.split('?', 2)[1] || ''
|
||||
else
|
||||
env_qs
|
||||
env_qs || ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# CGI::Session#create_new_id requires 'digest/md5' on every call. This makes
|
||||
# sense when spawning processes per request, but is unnecessarily expensive
|
||||
# when serving requests from a long-lived process.
|
||||
#
|
||||
# http://railsexpress.de/blog/articles/2005/11/22/speeding-up-the-creation-of-new-sessions
|
||||
require 'cgi/session'
|
||||
require 'digest/md5'
|
||||
|
||||
class CGI
|
||||
class Session #:nodoc:
|
||||
private
|
||||
# Create a new session id.
|
||||
#
|
||||
# The session id is an MD5 hash based upon the time,
|
||||
# a random number, and a constant string. This routine
|
||||
# is used internally for automatically generated
|
||||
# session ids.
|
||||
def create_new_id
|
||||
md5 = Digest::MD5::new
|
||||
now = Time::now
|
||||
md5.update(now.to_s)
|
||||
md5.update(String(now.usec))
|
||||
md5.update(String(rand(0)))
|
||||
md5.update(String($$))
|
||||
md5.update('foobar')
|
||||
@new_session = true
|
||||
md5.hexdigest
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,8 @@
|
||||
require 'action_controller/cgi_ext/cgi_ext'
|
||||
require 'action_controller/cgi_ext/cookie_performance_fix'
|
||||
require 'action_controller/cgi_ext/raw_post_data_fix'
|
||||
require 'action_controller/cgi_ext/session_performance_fix'
|
||||
require 'action_controller/cgi_ext/pstore_performance_fix'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
@@ -8,13 +10,13 @@ module ActionController #:nodoc:
|
||||
# sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
|
||||
#
|
||||
# * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
|
||||
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
||||
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
||||
# lib/action_controller/session.
|
||||
# * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
|
||||
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter
|
||||
# of the request, or automatically generated for a new session.
|
||||
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ cookie, or
|
||||
# automatically generated for a new session.
|
||||
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
|
||||
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
||||
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
||||
# an ArgumentError is raised.
|
||||
# * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
|
||||
# indefinitely.
|
||||
@@ -22,28 +24,33 @@ module ActionController #:nodoc:
|
||||
# server.
|
||||
# * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
|
||||
# * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
|
||||
def self.process_cgi(cgi = CGI.new, session_options = {})
|
||||
# * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
|
||||
# the query string or POST parameters. This protects against session fixation attacks.
|
||||
def self.process_cgi(cgi = CGI.new, session_options = {})
|
||||
new.process_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
|
||||
def process_cgi(cgi, session_options = {}) #:nodoc:
|
||||
process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cgi, :session_options
|
||||
attr_accessor :cgi, :session_options, :cookie_only
|
||||
class SessionFixationAttempt < StandardError; end #:nodoc:
|
||||
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => CGI::Session::PStore,
|
||||
:prefix => "ruby_sess.",
|
||||
:session_path => "/"
|
||||
:session_path => "/",
|
||||
:cookie_only => true
|
||||
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
|
||||
|
||||
def initialize(cgi, session_options = {})
|
||||
@cgi = cgi
|
||||
@session_options = session_options
|
||||
@env = @cgi.send(:env_table)
|
||||
@cookie_only = session_options.delete :cookie_only
|
||||
super()
|
||||
end
|
||||
|
||||
@@ -51,7 +58,7 @@ module ActionController #:nodoc:
|
||||
if (qs = @cgi.query_string) && !qs.empty?
|
||||
qs
|
||||
elsif uri = @env['REQUEST_URI']
|
||||
parts = uri.split('?')
|
||||
parts = uri.split('?')
|
||||
parts.shift
|
||||
parts.join('?')
|
||||
else
|
||||
@@ -60,7 +67,8 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def query_parameters
|
||||
(qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
|
||||
@query_parameters ||=
|
||||
(qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@@ -71,7 +79,7 @@ module ActionController #:nodoc:
|
||||
CGIMethods.parse_request_parameters(@cgi.params)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def cookies
|
||||
@cgi.cookies.freeze
|
||||
end
|
||||
@@ -106,10 +114,24 @@ module ActionController #:nodoc:
|
||||
@session = Hash.new
|
||||
else
|
||||
stale_session_check! do
|
||||
if session_options_with_string_keys['new_session'] == true
|
||||
@session = new_session
|
||||
else
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
if @cookie_only && request_parameters[session_options_with_string_keys['session_key']]
|
||||
raise SessionFixationAttempt
|
||||
end
|
||||
case value = session_options_with_string_keys['new_session']
|
||||
when true
|
||||
@session = new_session
|
||||
when false
|
||||
begin
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
# CGI::Session raises ArgumentError if 'new_session' == false
|
||||
# and no session cookie or query param is present.
|
||||
rescue ArgumentError
|
||||
@session = Hash.new
|
||||
end
|
||||
when nil
|
||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
||||
else
|
||||
raise ArgumentError, "Invalid new_session option: #{value}"
|
||||
end
|
||||
@session['__valid_session']
|
||||
end
|
||||
@@ -159,7 +181,7 @@ end_msg
|
||||
end
|
||||
|
||||
def session_options_with_string_keys
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, (k,v)| options[k.to_s] = v; options }
|
||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
|
||||
end
|
||||
end
|
||||
|
||||
@@ -170,7 +192,9 @@ end_msg
|
||||
end
|
||||
|
||||
def out(output = $stdout)
|
||||
convert_content_type!(@headers)
|
||||
convert_content_type!
|
||||
set_content_length!
|
||||
|
||||
output.binmode if output.respond_to?(:binmode)
|
||||
output.sync = false if output.respond_to?(:sync=)
|
||||
|
||||
@@ -195,16 +219,22 @@ end_msg
|
||||
end
|
||||
|
||||
private
|
||||
def convert_content_type!(headers)
|
||||
if header = headers.delete("Content-Type")
|
||||
headers["type"] = header
|
||||
def convert_content_type!
|
||||
if content_type = @headers.delete("Content-Type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
if header = headers.delete("Content-type")
|
||||
headers["type"] = header
|
||||
if content_type = @headers.delete("Content-type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
if header = headers.delete("content-type")
|
||||
headers["type"] = header
|
||||
if content_type = @headers.delete("content-type")
|
||||
@headers["type"] = content_type
|
||||
end
|
||||
end
|
||||
|
||||
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
|
||||
# for, say, a 2GB streaming file.
|
||||
def set_content_length!
|
||||
@headers["Content-Length"] = @body.size unless @body.respond_to?(:call)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -75,11 +75,13 @@ module ActionController #:nodoc:
|
||||
# will also use /code/weblog/components as template root
|
||||
# and find templates in /code/weblog/components/admin/parties/users/
|
||||
def uses_component_template_root
|
||||
path_of_calling_controller = File.dirname(caller[0].split(/:\d+:/, 2).first)
|
||||
path_of_calling_controller = File.dirname(caller[1].split(/:\d+:/, 2).first)
|
||||
path_of_controller_root = path_of_calling_controller.sub(/#{Regexp.escape(File.dirname(controller_path))}$/, "")
|
||||
|
||||
self.template_root = path_of_controller_root
|
||||
end
|
||||
|
||||
deprecate :uses_component_template_root => 'Components are deprecated and will be removed in Rails 2.0.'
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
|
||||
@@ -62,9 +62,11 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
# Removes the cookie on the client machine by setting the value to an empty string
|
||||
# and setting its expiration date into the past
|
||||
def delete(name)
|
||||
set_cookie("name" => name.to_s, "value" => "", "expires" => Time.at(0))
|
||||
# and setting its expiration date into the past. Like []=, you can pass in an options
|
||||
# hash to delete cookies with extra data such as a +path+.
|
||||
def delete(name, options = {})
|
||||
options.stringify_keys!
|
||||
set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -81,10 +81,10 @@ module ActionController #:nodoc:
|
||||
# can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
|
||||
# beginning of their respective chain and executed before the rest. For example:
|
||||
#
|
||||
# class ShoppingController
|
||||
# class ShoppingController < ActionController::Base
|
||||
# before_filter :verify_open_shop
|
||||
#
|
||||
# class CheckoutController
|
||||
# class CheckoutController < ShoppingController
|
||||
# prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
|
||||
#
|
||||
# The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
|
||||
@@ -214,9 +214,10 @@ module ActionController #:nodoc:
|
||||
# == Filter Chain Halting
|
||||
#
|
||||
# <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
|
||||
# before controller action is run. This is useful, for example, to deny
|
||||
# before a controller action is run. This is useful, for example, to deny
|
||||
# access to unauthenticated users or to redirect from http to https.
|
||||
# Simply return false from the filter or call render or redirect.
|
||||
# After filters will not be executed if the filter chain is halted.
|
||||
#
|
||||
# Around filters halt the request unless the action block is called.
|
||||
# Given these filters
|
||||
@@ -238,12 +239,12 @@ module ActionController #:nodoc:
|
||||
# . . /
|
||||
# . #around (code after yield)
|
||||
# . /
|
||||
# #after (actual filter code is run)
|
||||
# #after (actual filter code is run, unless the around filter does not yield)
|
||||
#
|
||||
# If #around returns before yielding, only #after will be run. The #before
|
||||
# filter and controller action will not be run. If #before returns false,
|
||||
# the second half of #around and all of #after will still run but the
|
||||
# action will not.
|
||||
# If #around returns before yielding, #after will still not be run. The #before
|
||||
# filter and controller action will not be run. If #before returns false,
|
||||
# the second half of #around and will still run but #after and the
|
||||
# action will not. If #around does not yield, #after will not be run.
|
||||
module ClassMethods
|
||||
# The passed <tt>filters</tt> will be appended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
@@ -263,13 +264,13 @@ module ActionController #:nodoc:
|
||||
# The passed <tt>filters</tt> will be appended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def append_after_filter(*filters, &block)
|
||||
prepend_filter_to_chain(filters, :after, &block)
|
||||
append_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def prepend_after_filter(*filters, &block)
|
||||
append_filter_to_chain(filters, :after, &block)
|
||||
prepend_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# Shorthand for append_after_filter since it's the most common.
|
||||
@@ -362,12 +363,12 @@ module ActionController #:nodoc:
|
||||
|
||||
# Returns a mapping between filters and the actions that may run them.
|
||||
def included_actions #:nodoc:
|
||||
read_inheritable_attribute("included_actions") || {}
|
||||
@included_actions ||= read_inheritable_attribute("included_actions") || {}
|
||||
end
|
||||
|
||||
# Returns a mapping between filters and actions that may not run them.
|
||||
def excluded_actions #:nodoc:
|
||||
read_inheritable_attribute("excluded_actions") || {}
|
||||
@excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
|
||||
end
|
||||
|
||||
# Find a filter in the filter_chain where the filter method matches the _filter_ param
|
||||
@@ -381,10 +382,11 @@ module ActionController #:nodoc:
|
||||
|
||||
# Returns true if the filter is excluded from the given action
|
||||
def filter_excluded_from_action?(filter,action) #:nodoc:
|
||||
if (ia = included_actions[filter]) && !ia.empty?
|
||||
case
|
||||
when ia = included_actions[filter]
|
||||
!ia.include?(action)
|
||||
else
|
||||
(excluded_actions[filter] || []).include?(action)
|
||||
when ea = excluded_actions[filter]
|
||||
ea.include?(action)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -397,20 +399,28 @@ module ActionController #:nodoc:
|
||||
@filter = filter
|
||||
end
|
||||
|
||||
def type
|
||||
:around
|
||||
end
|
||||
|
||||
def before?
|
||||
false
|
||||
type == :before
|
||||
end
|
||||
|
||||
def after?
|
||||
false
|
||||
type == :after
|
||||
end
|
||||
|
||||
def around?
|
||||
true
|
||||
type == :around
|
||||
end
|
||||
|
||||
def run(controller)
|
||||
raise ActionControllerError, 'No filter type: Nothing to do here.'
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
raise(ActionControllerError, 'No filter type: Nothing to do here.')
|
||||
run(controller)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -420,35 +430,38 @@ module ActionController #:nodoc:
|
||||
def filter
|
||||
@filter.filter
|
||||
end
|
||||
|
||||
def around?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class BeforeFilterProxy < FilterProxy #:nodoc:
|
||||
def before?
|
||||
true
|
||||
def type
|
||||
:before
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
if false == @filter.call(controller) # must only stop if equal to false. only filters returning false are halted.
|
||||
controller.halt_filter_chain(@filter, :returned_false)
|
||||
else
|
||||
yield
|
||||
def run(controller)
|
||||
# only filters returning false are halted.
|
||||
if false == @filter.call(controller)
|
||||
controller.send :halt_filter_chain, @filter, :returned_false
|
||||
end
|
||||
end
|
||||
|
||||
def call(controller)
|
||||
yield unless run(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class AfterFilterProxy < FilterProxy #:nodoc:
|
||||
def after?
|
||||
true
|
||||
def type
|
||||
:after
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
yield
|
||||
def run(controller)
|
||||
@filter.call(controller)
|
||||
end
|
||||
|
||||
def call(controller)
|
||||
yield
|
||||
run(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class SymbolFilter < Filter #:nodoc:
|
||||
@@ -485,29 +498,72 @@ module ActionController #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
class ClassBeforeFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.before(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class ClassAfterFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.after(controller)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def append_filter_to_chain(filters, position = :around, &block)
|
||||
write_inheritable_array('filter_chain', create_filters(filters, position, &block) )
|
||||
def append_filter_to_chain(filters, filter_type = :around, &block)
|
||||
pos = find_filter_append_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def prepend_filter_to_chain(filters, position = :around, &block)
|
||||
write_inheritable_attribute('filter_chain', create_filters(filters, position, &block) + filter_chain)
|
||||
def prepend_filter_to_chain(filters, filter_type = :around, &block)
|
||||
pos = find_filter_prepend_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def create_filters(filters, position, &block) #:nodoc:
|
||||
def update_filter_chain(filters, filter_type, pos, &block)
|
||||
new_filters = create_filters(filters, filter_type, &block)
|
||||
new_chain = filter_chain.insert(pos, new_filters).flatten
|
||||
write_inheritable_attribute('filter_chain', new_chain)
|
||||
end
|
||||
|
||||
def find_filter_append_position(filters, filter_type)
|
||||
# appending an after filter puts it at the end of the call chain
|
||||
# before and around filters go before the first after filter in the chain
|
||||
unless filter_type == :after
|
||||
filter_chain.each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
def find_filter_prepend_position(filters, filter_type)
|
||||
# prepending a before or around filter puts it at the front of the call chain
|
||||
# after filters go before the first after filter in the chain
|
||||
if filter_type == :after
|
||||
filter_chain.each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
def create_filters(filters, filter_type, &block) #:nodoc:
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map! { |filter| find_or_create_filter(filter,position) }
|
||||
filters.map! { |filter| find_or_create_filter(filter, filter_type) }
|
||||
update_conditions(filters, conditions)
|
||||
filters
|
||||
end
|
||||
|
||||
def find_or_create_filter(filter,position)
|
||||
if found_filter = find_filter(filter) { |f| f.send("#{position}?") }
|
||||
def find_or_create_filter(filter, filter_type)
|
||||
if found_filter = find_filter(filter) { |f| f.type == filter_type }
|
||||
found_filter
|
||||
else
|
||||
f = class_for_filter(filter).new(filter)
|
||||
f = class_for_filter(filter, filter_type).new(filter)
|
||||
# apply proxy to filter if necessary
|
||||
case position
|
||||
case filter_type
|
||||
when :before
|
||||
BeforeFilterProxy.new(f)
|
||||
when :after
|
||||
@@ -520,7 +576,7 @@ module ActionController #:nodoc:
|
||||
|
||||
# The determination of the filter type was once done at run time.
|
||||
# This method is here to extract as much logic from the filter run time as possible
|
||||
def class_for_filter(filter) #:nodoc:
|
||||
def class_for_filter(filter, filter_type) #:nodoc:
|
||||
case
|
||||
when filter.is_a?(Symbol)
|
||||
SymbolFilter
|
||||
@@ -534,8 +590,12 @@ module ActionController #:nodoc:
|
||||
end
|
||||
when filter.respond_to?(:filter)
|
||||
ClassFilter
|
||||
when filter.respond_to?(:before) && filter_type == :before
|
||||
ClassBeforeFilter
|
||||
when filter.respond_to?(:after) && filter_type == :after
|
||||
ClassAfterFilter
|
||||
else
|
||||
raise(ActionControllerError, 'A filters must be a Symbol, Proc, Method, or object responding to filter.')
|
||||
raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -550,8 +610,8 @@ module ActionController #:nodoc:
|
||||
return if conditions.empty?
|
||||
if conditions[:only]
|
||||
write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
|
||||
else
|
||||
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except])) if conditions[:except]
|
||||
elsif conditions[:except]
|
||||
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except]))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -576,9 +636,9 @@ module ActionController #:nodoc:
|
||||
|
||||
def remove_actions_from_included_actions!(filters,*actions)
|
||||
actions = actions.flatten.map(&:to_s)
|
||||
updated_hash = filters.inject(included_actions) do |hash,filter|
|
||||
updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter|
|
||||
ia = (hash[filter] || []) - actions
|
||||
ia.blank? ? hash.delete(filter) : hash[filter] = ia
|
||||
ia.empty? ? hash.delete(filter) : hash[filter] = ia
|
||||
hash
|
||||
end
|
||||
write_inheritable_attribute('included_actions', updated_hash)
|
||||
@@ -595,7 +655,9 @@ module ActionController #:nodoc:
|
||||
def proxy_before_and_after_filter(filter) #:nodoc:
|
||||
return filter unless filter_responds_to_before_and_after(filter)
|
||||
Proc.new do |controller, action|
|
||||
unless filter.before(controller) == false
|
||||
if filter.before(controller) == false
|
||||
controller.send :halt_filter_chain, filter, :returned_false
|
||||
else
|
||||
begin
|
||||
action.call
|
||||
ensure
|
||||
@@ -615,55 +677,90 @@ module ActionController #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
def perform_action_with_filters
|
||||
#result = perform_filters do
|
||||
# perform_action_without_filters unless performed?
|
||||
#end
|
||||
@before_filter_chain_aborted = (call_filter(self.class.filter_chain, 0) == false)
|
||||
end
|
||||
protected
|
||||
|
||||
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
@before_filter_chain_aborted = false
|
||||
process_without_filters(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
def filter_chain
|
||||
self.class.filter_chain
|
||||
end
|
||||
|
||||
def call_filter(chain, index)
|
||||
return (performed? || perform_action_without_filters) if index >= chain.size
|
||||
filter = chain[index]
|
||||
return call_filter(chain, index.next) if self.class.filter_excluded_from_action?(filter,action_name)
|
||||
|
||||
halted = false
|
||||
filter.call(self) do
|
||||
halted = call_filter(chain, index.next)
|
||||
end
|
||||
halt_filter_chain(filter.filter, :no_yield) if halted == false
|
||||
halted
|
||||
end
|
||||
|
||||
def halt_filter_chain(filter, reason)
|
||||
if logger
|
||||
case reason
|
||||
when :no_yield
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] did not yield."
|
||||
when :returned_false
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] returned false."
|
||||
end
|
||||
end
|
||||
return false
|
||||
def perform_action_with_filters
|
||||
call_filters(self.class.filter_chain, 0, 0)
|
||||
end
|
||||
|
||||
private
|
||||
def process_cleanup_with_filters
|
||||
if @before_filter_chain_aborted
|
||||
close_session
|
||||
|
||||
def call_filters(chain, index, nesting)
|
||||
index = run_before_filters(chain, index, nesting)
|
||||
aborted = @before_filter_chain_aborted
|
||||
perform_action_without_filters unless performed? || aborted
|
||||
return index if nesting != 0 || aborted
|
||||
run_after_filters(chain, index)
|
||||
end
|
||||
|
||||
def skip_excluded_filters(chain, index)
|
||||
while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name)
|
||||
index = index.next
|
||||
end
|
||||
[filter, index]
|
||||
end
|
||||
|
||||
def run_before_filters(chain, index, nesting)
|
||||
while chain[index]
|
||||
filter, index = skip_excluded_filters(chain, index)
|
||||
break unless filter # end of call chain reached
|
||||
case filter.type
|
||||
when :before
|
||||
filter.run(self) # invoke before filter
|
||||
index = index.next
|
||||
break if @before_filter_chain_aborted
|
||||
when :around
|
||||
yielded = false
|
||||
filter.call(self) do
|
||||
yielded = true
|
||||
# all remaining before and around filters will be run in this call
|
||||
index = call_filters(chain, index.next, nesting.next)
|
||||
end
|
||||
halt_filter_chain(filter, :did_not_yield) unless yielded
|
||||
break
|
||||
else
|
||||
process_cleanup_without_filters
|
||||
break # no before or around filters left
|
||||
end
|
||||
end
|
||||
index
|
||||
end
|
||||
|
||||
def run_after_filters(chain, index)
|
||||
seen_after_filter = false
|
||||
while chain[index]
|
||||
filter, index = skip_excluded_filters(chain, index)
|
||||
break unless filter # end of call chain reached
|
||||
case filter.type
|
||||
when :after
|
||||
seen_after_filter = true
|
||||
filter.run(self) # invoke after filter
|
||||
else
|
||||
# implementation error or someone has mucked with the filter chain
|
||||
raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
|
||||
end
|
||||
index = index.next
|
||||
end
|
||||
index.next
|
||||
end
|
||||
|
||||
def halt_filter_chain(filter, reason)
|
||||
@before_filter_chain_aborted = true
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
|
||||
false
|
||||
end
|
||||
|
||||
def process_cleanup_with_filters
|
||||
if @before_filter_chain_aborted
|
||||
close_session
|
||||
else
|
||||
process_cleanup_without_filters
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -67,7 +67,7 @@ module ActionController
|
||||
@https = false
|
||||
@cookies = {}
|
||||
@controller = @request = @response = nil
|
||||
|
||||
|
||||
self.host = "www.example.com"
|
||||
self.remote_addr = "127.0.0.1"
|
||||
self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
|
||||
@@ -89,7 +89,7 @@ module ActionController
|
||||
# session.https!
|
||||
# session.https!(false)
|
||||
def https!(flag=true)
|
||||
@https = flag
|
||||
@https = flag
|
||||
end
|
||||
|
||||
# Return +true+ if the session is mimicing a secure HTTPS request.
|
||||
@@ -113,7 +113,7 @@ module ActionController
|
||||
# performed on the location header.
|
||||
def follow_redirect!
|
||||
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
|
||||
get(interpret_uri(headers["location"].first))
|
||||
get(interpret_uri(headers['location'].first))
|
||||
status
|
||||
end
|
||||
|
||||
@@ -143,10 +143,10 @@ module ActionController
|
||||
# Performs a GET request with the given parameters. The parameters may
|
||||
# be +nil+, a Hash, or a string that is appropriately encoded
|
||||
# (application/x-www-form-urlencoded or multipart/form-data). The headers
|
||||
# should be a hash. The keys will automatically be upcased, with the
|
||||
# should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
#
|
||||
# You can also perform POST, PUT, DELETE, and HEAD requests with #post,
|
||||
# You can also perform POST, PUT, DELETE, and HEAD requests with #post,
|
||||
# #put, #delete, and #head.
|
||||
def get(path, parameters=nil, headers=nil)
|
||||
process :get, path, parameters, headers
|
||||
@@ -161,31 +161,41 @@ module ActionController
|
||||
def put(path, parameters=nil, headers=nil)
|
||||
process :put, path, parameters, headers
|
||||
end
|
||||
|
||||
|
||||
# Performs a DELETE request with the given parameters. See get() for more details.
|
||||
def delete(path, parameters=nil, headers=nil)
|
||||
process :delete, path, parameters, headers
|
||||
end
|
||||
|
||||
|
||||
# Performs a HEAD request with the given parameters. See get() for more details.
|
||||
def head(path, parameters=nil, headers=nil)
|
||||
process :head, path, parameters, headers
|
||||
end
|
||||
|
||||
# Performs an XMLHttpRequest request with the given parameters, mimicing
|
||||
# the request environment created by the Prototype library. The parameters
|
||||
# may be +nil+, a Hash, or a string that is appropriately encoded
|
||||
# (application/x-www-form-urlencoded or multipart/form-data). The headers
|
||||
# should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
def xml_http_request(path, parameters=nil, headers=nil)
|
||||
headers = (headers || {}).merge(
|
||||
"X-Requested-With" => "XMLHttpRequest",
|
||||
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
|
||||
)
|
||||
# Performs an XMLHttpRequest request with the given parameters, mirroring
|
||||
# a request from the Prototype library.
|
||||
#
|
||||
# The request_method is :get, :post, :put, :delete or :head; the
|
||||
# parameters are +nil+, a hash, or a url-encoded or multipart string;
|
||||
# the headers are a hash. Keys are automatically upcased and prefixed
|
||||
# with 'HTTP_' if not already.
|
||||
#
|
||||
# This method used to omit the request_method parameter, assuming it
|
||||
# was :post. This was deprecated in Rails 1.2.4. Always pass the request
|
||||
# method as the first argument.
|
||||
def xml_http_request(request_method, path, parameters = nil, headers = nil)
|
||||
unless request_method.is_a?(Symbol)
|
||||
ActiveSupport::Deprecation.warn 'xml_http_request now takes the request_method (:get, :post, etc.) as the first argument. It used to assume :post, so add the :post argument to your existing method calls to silence this warning.'
|
||||
request_method, path, parameters, headers = :post, request_method, path, parameters
|
||||
end
|
||||
|
||||
post(path, parameters, headers)
|
||||
headers ||= {}
|
||||
headers['X-Requested-With'] = 'XMLHttpRequest'
|
||||
headers['Accept'] = 'text/javascript, text/html, application/xml, text/xml, */*'
|
||||
|
||||
process(request_method, path, parameters, headers)
|
||||
end
|
||||
alias xhr :xml_http_request
|
||||
|
||||
# Returns the URL for the given options, according to the rules specified
|
||||
# in the application's routes.
|
||||
@@ -292,7 +302,7 @@ module ActionController
|
||||
@status = @status.to_i
|
||||
end
|
||||
|
||||
# Encode the cookies hash in a format suitable for passing to a
|
||||
# Encode the cookies hash in a format suitable for passing to a
|
||||
# request.
|
||||
def encode_cookies
|
||||
cookies.inject("") do |string, (name, value)|
|
||||
@@ -450,7 +460,7 @@ module ActionController
|
||||
# without any test methods.
|
||||
def run(*args) #:nodoc:
|
||||
return if @method_name == "default_test"
|
||||
super
|
||||
super
|
||||
end
|
||||
|
||||
# Because of how use_instantiated_fixtures and use_transactional_fixtures
|
||||
@@ -490,9 +500,11 @@ module ActionController
|
||||
@integration_session = open_session
|
||||
end
|
||||
|
||||
%w(get post cookies assigns xml_http_request).each do |method|
|
||||
%w(get post put head delete cookies assigns xml_http_request).each do |method|
|
||||
define_method(method) do |*args|
|
||||
reset! unless @integration_session
|
||||
# reset the html_document variable, but only for new get/post calls
|
||||
@html_document = nil unless %w(cookies assigns).include?(method)
|
||||
returning @integration_session.send(method, *args) do
|
||||
copy_session_variables!
|
||||
end
|
||||
|
||||
@@ -27,9 +27,9 @@ module ActionController #:nodoc:
|
||||
# With layouts, you can flip it around and have the common structure know where to insert changing content. This means
|
||||
# that the header and footer are only mentioned in one place, like this:
|
||||
#
|
||||
# <!-- The header part of this layout -->
|
||||
# // The header part of this layout
|
||||
# <%= yield %>
|
||||
# <!-- The footer part of this layout -->
|
||||
# // The footer part of this layout -->
|
||||
#
|
||||
# And then you have content pages that look like this:
|
||||
#
|
||||
@@ -38,9 +38,9 @@ module ActionController #:nodoc:
|
||||
# Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
|
||||
# like this:
|
||||
#
|
||||
# <!-- The header part of this layout -->
|
||||
# // The header part of this layout
|
||||
# hello world
|
||||
# <!-- The footer part of this layout -->
|
||||
# // The footer part of this layout -->
|
||||
#
|
||||
# == Accessing shared variables
|
||||
#
|
||||
@@ -266,7 +266,7 @@ module ActionController #:nodoc:
|
||||
|
||||
def candidate_for_layout?(options)
|
||||
(options.has_key?(:layout) && options[:layout] != false) ||
|
||||
options.values_at(:text, :xml, :file, :inline, :partial, :nothing).compact.empty? &&
|
||||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? &&
|
||||
!template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
|
||||
end
|
||||
|
||||
|
||||
@@ -135,12 +135,14 @@ module Mime
|
||||
HTML = Type.new "text/html", :html, %w( application/xhtml+xml )
|
||||
JS = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript )
|
||||
ICS = Type.new "text/calendar", :ics
|
||||
CSV = Type.new "text/csv", :csv
|
||||
XML = Type.new "application/xml", :xml, %w( text/xml application/x-xml )
|
||||
RSS = Type.new "application/rss+xml", :rss
|
||||
ATOM = Type.new "application/atom+xml", :atom
|
||||
YAML = Type.new "application/x-yaml", :yaml, %w( text/yaml )
|
||||
JSON = Type.new "application/json", :json, %w( text/x-json )
|
||||
|
||||
SET = [ ALL, TEXT, HTML, JS, ICS, XML, RSS, ATOM, YAML ]
|
||||
SET = [ ALL, TEXT, HTML, JS, ICS, XML, RSS, ATOM, YAML, JSON ]
|
||||
|
||||
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k == "" }
|
||||
|
||||
@@ -157,6 +159,8 @@ module Mime
|
||||
|
||||
LOOKUP["text/calendar"] = ICS
|
||||
|
||||
LOOKUP["text/csv"] = CSV
|
||||
|
||||
LOOKUP["application/xml"] = XML
|
||||
LOOKUP["text/xml"] = XML
|
||||
LOOKUP["application/x-xml"] = XML
|
||||
@@ -167,7 +171,10 @@ module Mime
|
||||
LOOKUP["application/rss+xml"] = RSS
|
||||
LOOKUP["application/atom+xml"] = ATOM
|
||||
|
||||
|
||||
LOOKUP["application/json"] = JSON
|
||||
LOOKUP["text/x-json"] = JSON
|
||||
|
||||
|
||||
EXTENSION_LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k == "" }
|
||||
|
||||
EXTENSION_LOOKUP["html"] = HTML
|
||||
@@ -181,9 +188,13 @@ module Mime
|
||||
|
||||
EXTENSION_LOOKUP["ics"] = ICS
|
||||
|
||||
EXTENSION_LOOKUP["csv"] = CSV
|
||||
|
||||
EXTENSION_LOOKUP["yml"] = YAML
|
||||
EXTENSION_LOOKUP["yaml"] = YAML
|
||||
|
||||
EXTENSION_LOOKUP["rss"] = RSS
|
||||
EXTENSION_LOOKUP["atom"] = ATOM
|
||||
end
|
||||
|
||||
EXTENSION_LOOKUP["json"] = JSON
|
||||
end
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
module ActionController
|
||||
# === Action Pack pagination for Active Record collections
|
||||
#
|
||||
# DEPRECATION WARNING: Pagination will be separated into its own plugin with Rails 2.0.
|
||||
# DEPRECATION WARNING: Pagination will be moved to a plugin in Rails 2.0.
|
||||
# Install the classic_pagination plugin for forward compatibility:
|
||||
# script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination
|
||||
#
|
||||
# The Pagination module aids in the process of paging large collections of
|
||||
# Active Record objects. It offers macro-style automatic fetching of your
|
||||
@@ -130,6 +132,8 @@ module ActionController
|
||||
paginator_and_collection_for(collection_id, options)
|
||||
end
|
||||
|
||||
deprecate :paginate => 'Pagination is moving to a plugin in Rails 2.0: script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination'
|
||||
|
||||
# These methods become class methods on any controller
|
||||
module ClassMethods
|
||||
# Creates a +before_filter+ which automatically paginates an Active
|
||||
@@ -148,6 +152,8 @@ module ActionController
|
||||
OPTIONS[self][collection_id] = options
|
||||
end
|
||||
end
|
||||
|
||||
deprecate :paginate => 'Pagination is moving to a plugin in Rails 2.0: script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination'
|
||||
end
|
||||
|
||||
def create_paginators_and_retrieve_collections #:nodoc:
|
||||
|
||||
@@ -13,14 +13,18 @@ module ActionController
|
||||
@parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access
|
||||
end
|
||||
|
||||
# Returns the HTTP request method as a lowercase symbol (:get, for example)
|
||||
# Returns the HTTP request method as a lowercase symbol (:get, for example). Note, HEAD is returned as :get
|
||||
# since the two are supposedly to be functionaly equivilent for all purposes except that HEAD won't return a response
|
||||
# body (which Rails also takes care of elsewhere).
|
||||
def method
|
||||
@request_method ||= (!parameters[:_method].blank? && @env['REQUEST_METHOD'] == 'POST') ?
|
||||
parameters[:_method].to_s.downcase.to_sym :
|
||||
@env['REQUEST_METHOD'].downcase.to_sym
|
||||
|
||||
@request_method == :head ? :get : @request_method
|
||||
end
|
||||
|
||||
# Is this a GET request? Equivalent to request.method == :get
|
||||
# Is this a GET (or HEAD) request? Equivalent to request.method == :get
|
||||
def get?
|
||||
method == :get
|
||||
end
|
||||
@@ -40,9 +44,10 @@ module ActionController
|
||||
method == :delete
|
||||
end
|
||||
|
||||
# Is this a HEAD request? Equivalent to request.method == :head
|
||||
# Is this a HEAD request? HEAD is mapped as :get for request.method, so here we ask the
|
||||
# REQUEST_METHOD header directly. Thus, for head, both get? and head? will return true.
|
||||
def head?
|
||||
method == :head
|
||||
@env['REQUEST_METHOD'].downcase.to_sym == :head
|
||||
end
|
||||
|
||||
# Determine whether the body of a HTTP call is URL-encoded (default)
|
||||
@@ -130,19 +135,21 @@ module ActionController
|
||||
@env['RAW_POST_DATA']
|
||||
end
|
||||
|
||||
# Returns the request URI correctly, taking into account the idiosyncracies
|
||||
# of the various servers.
|
||||
# Return the request URI, accounting for server idiosyncracies.
|
||||
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
||||
def request_uri
|
||||
if uri = @env['REQUEST_URI']
|
||||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri # Remove domain, which webrick puts into the request_uri.
|
||||
else # REQUEST_URI is blank under IIS - get this from PATH_INFO and SCRIPT_NAME
|
||||
# Remove domain, which webrick puts into the request_uri.
|
||||
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
|
||||
else
|
||||
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
|
||||
script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
|
||||
uri = @env['PATH_INFO']
|
||||
uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
|
||||
unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
|
||||
uri << '?' << env_qs
|
||||
end
|
||||
uri
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -34,14 +34,16 @@ module ActionController #:nodoc:
|
||||
|
||||
# Overwrite to implement custom logging of errors. By default logs as fatal.
|
||||
def log_error(exception) #:doc:
|
||||
if ActionView::TemplateError === exception
|
||||
logger.fatal(exception.to_s)
|
||||
else
|
||||
logger.fatal(
|
||||
"\n\n#{exception.class} (#{exception.message}):\n " +
|
||||
clean_backtrace(exception).join("\n ") +
|
||||
"\n\n"
|
||||
)
|
||||
ActiveSupport::Deprecation.silence do
|
||||
if ActionView::TemplateError === exception
|
||||
logger.fatal(exception.to_s)
|
||||
else
|
||||
logger.fatal(
|
||||
"\n\n#{exception.class} (#{exception.message}):\n " +
|
||||
clean_backtrace(exception).join("\n ") +
|
||||
"\n\n"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,48 +2,68 @@ module ActionController
|
||||
module Resources
|
||||
class Resource #:nodoc:
|
||||
attr_reader :collection_methods, :member_methods, :new_methods
|
||||
attr_reader :path_prefix, :name_prefix
|
||||
attr_reader :path_prefix, :new_name_prefix
|
||||
attr_reader :plural, :singular
|
||||
attr_reader :options
|
||||
|
||||
def initialize(entities, options)
|
||||
@plural = entities
|
||||
@singular = options[:singular] || plural.to_s.singularize
|
||||
|
||||
|
||||
@options = options
|
||||
|
||||
arrange_actions
|
||||
add_default_actions
|
||||
set_prefixes
|
||||
end
|
||||
|
||||
|
||||
def controller
|
||||
(options[:controller] || plural).to_s
|
||||
@controller ||= (options[:controller] || plural).to_s
|
||||
end
|
||||
|
||||
|
||||
def path
|
||||
"#{path_prefix}/#{plural}"
|
||||
@path ||= "#{path_prefix}/#{plural}"
|
||||
end
|
||||
|
||||
|
||||
def new_path
|
||||
"#{path}/new"
|
||||
@new_path ||= "#{path}/new"
|
||||
end
|
||||
|
||||
|
||||
def member_path
|
||||
"#{path}/:id"
|
||||
@member_path ||= "#{path}/:id"
|
||||
end
|
||||
|
||||
|
||||
def nesting_path_prefix
|
||||
"#{path}/:#{singular}_id"
|
||||
@nesting_path_prefix ||= "#{path}/:#{singular}_id"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def deprecate_name_prefix?
|
||||
@name_prefix.blank? && !@new_name_prefix.blank?
|
||||
end
|
||||
|
||||
def name_prefix
|
||||
deprecate_name_prefix? ? @new_name_prefix : @name_prefix
|
||||
end
|
||||
|
||||
def old_name_prefix
|
||||
@name_prefix
|
||||
end
|
||||
|
||||
def nesting_name_prefix
|
||||
"#{new_name_prefix}#{singular}_"
|
||||
end
|
||||
|
||||
def action_separator
|
||||
@action_separator ||= Base.resource_action_separator
|
||||
end
|
||||
|
||||
protected
|
||||
def arrange_actions
|
||||
@collection_methods = arrange_actions_by_methods(options.delete(:collection))
|
||||
@member_methods = arrange_actions_by_methods(options.delete(:member))
|
||||
@new_methods = arrange_actions_by_methods(options.delete(:new))
|
||||
end
|
||||
|
||||
|
||||
def add_default_actions
|
||||
add_default_action(member_methods, :get, :edit)
|
||||
add_default_action(new_methods, :get, :new)
|
||||
@@ -52,6 +72,7 @@ module ActionController
|
||||
def set_prefixes
|
||||
@path_prefix = options.delete(:path_prefix)
|
||||
@name_prefix = options.delete(:name_prefix)
|
||||
@new_name_prefix = options.delete(:new_name_prefix)
|
||||
end
|
||||
|
||||
def arrange_actions_by_methods(actions)
|
||||
@@ -60,12 +81,25 @@ module ActionController
|
||||
flipped_hash
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add_default_action(collection, method, action)
|
||||
(collection[method] ||= []).unshift(action)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class SingletonResource < Resource #:nodoc:
|
||||
def initialize(entity, options)
|
||||
@plural = @singular = entity
|
||||
@options = options
|
||||
arrange_actions
|
||||
add_default_actions
|
||||
set_prefixes
|
||||
end
|
||||
|
||||
alias_method :member_path, :path
|
||||
alias_method :nesting_path_prefix, :path
|
||||
end
|
||||
|
||||
# Creates named routes for implementing verb-oriented controllers. This is
|
||||
# useful for implementing REST API's, where a single resource has different
|
||||
# behavior based on the HTTP verb (method) used to access it.
|
||||
@@ -145,7 +179,7 @@ module ActionController
|
||||
#
|
||||
# or
|
||||
#
|
||||
# <% form_for :message, @message, message_path(@message), :html => {:method => :put} do |f| %>
|
||||
# <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
|
||||
#
|
||||
# The #resources method accepts various options, too, to customize the resulting
|
||||
# routes:
|
||||
@@ -162,7 +196,15 @@ module ActionController
|
||||
# map.resources :articles do |article|
|
||||
# article.resources :comments
|
||||
# end
|
||||
#
|
||||
#
|
||||
# The comment resources work the same, but must now include a value for :article_id.
|
||||
#
|
||||
# article_comments_url(@article)
|
||||
# article_comment_url(@article, @comment)
|
||||
#
|
||||
# article_comments_url(:article_id => @article)
|
||||
# article_comment_url(:article_id => @article, :id => @comment)
|
||||
#
|
||||
# * <tt>:name_prefix</tt> -- define a prefix for all generated routes, usually ending in an underscore.
|
||||
# Use this if you have named routes that may clash.
|
||||
#
|
||||
@@ -171,7 +213,7 @@ module ActionController
|
||||
#
|
||||
# * <tt>:collection</tt> -- add named routes for other actions that operate on the collection.
|
||||
# Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>
|
||||
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages;rss, with a route of rss_messages_url.
|
||||
# or <tt>:any</tt> if the method does not matter. These routes map to a URL like /messages/rss, with a route of rss_messages_url.
|
||||
# * <tt>:member</tt> -- same as :collection, but for actions that operate on a specific member.
|
||||
# * <tt>:new</tt> -- same as :collection, but for actions that operate on the new resource action.
|
||||
#
|
||||
@@ -183,19 +225,19 @@ module ActionController
|
||||
# # --> GET /thread/7/messages/1
|
||||
#
|
||||
# map.resources :messages, :collection => { :rss => :get }
|
||||
# # --> GET /messages;rss (maps to the #rss action)
|
||||
# # --> GET /messages/rss (maps to the #rss action)
|
||||
# # also adds a named route called "rss_messages"
|
||||
#
|
||||
# map.resources :messages, :member => { :mark => :post }
|
||||
# # --> POST /messages/1;mark (maps to the #mark action)
|
||||
# # --> POST /messages/1/mark (maps to the #mark action)
|
||||
# # also adds a named route called "mark_message"
|
||||
#
|
||||
# map.resources :messages, :new => { :preview => :post }
|
||||
# # --> POST /messages/new;preview (maps to the #preview action)
|
||||
# # --> POST /messages/new/preview (maps to the #preview action)
|
||||
# # also adds a named route called "preview_new_message"
|
||||
#
|
||||
# map.resources :messages, :new => { :new => :any, :preview => :post }
|
||||
# # --> POST /messages/new;preview (maps to the #preview action)
|
||||
# # --> POST /messages/new/preview (maps to the #preview action)
|
||||
# # also adds a named route called "preview_new_message"
|
||||
# # --> /messages/new can be invoked via any request method
|
||||
#
|
||||
@@ -209,84 +251,238 @@ module ActionController
|
||||
entities.each { |entity| map_resource entity, options.dup, &block }
|
||||
end
|
||||
|
||||
# Creates named routes for implementing verb-oriented controllers for a singleton resource.
|
||||
# A singleton resource is global to the current user visiting the application, such as a user's
|
||||
# /account profile.
|
||||
#
|
||||
# See map.resources for general conventions. These are the main differences:
|
||||
# - A singular name is given to map.resource. The default controller name is taken from the singular name.
|
||||
# - There is no <tt>:collection</tt> option as there is only the singleton resource.
|
||||
# - There is no <tt>:singular</tt> option as the singular name is passed to map.resource.
|
||||
# - No default index route is created for the singleton resource controller.
|
||||
# - When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1')
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# map.resource :account
|
||||
#
|
||||
# class AccountController < ActionController::Base
|
||||
# # POST account_url
|
||||
# def create
|
||||
# # create an account
|
||||
# end
|
||||
#
|
||||
# # GET new_account_url
|
||||
# def new
|
||||
# # return an HTML form for describing the new account
|
||||
# end
|
||||
#
|
||||
# # GET account_url
|
||||
# def show
|
||||
# # find and return the account
|
||||
# end
|
||||
#
|
||||
# # GET edit_account_url
|
||||
# def edit
|
||||
# # return an HTML form for editing the account
|
||||
# end
|
||||
#
|
||||
# # PUT account_url
|
||||
# def update
|
||||
# # find and update the account
|
||||
# end
|
||||
#
|
||||
# # DELETE account_url
|
||||
# def destroy
|
||||
# # delete the account
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Along with the routes themselves, #resource generates named routes for use in
|
||||
# controllers and views. <tt>map.resource :account</tt> produces the following named routes and helpers:
|
||||
#
|
||||
# Named Route Helpers
|
||||
# account account_url, hash_for_account_url,
|
||||
# account_path, hash_for_account_path
|
||||
# edit_account edit_account_url, hash_for_edit_account_url,
|
||||
# edit_account_path, hash_for_edit_account_path
|
||||
def resource(*entities, &block)
|
||||
options = entities.last.is_a?(Hash) ? entities.pop : { }
|
||||
entities.each { |entity| map_singleton_resource entity, options.dup, &block }
|
||||
end
|
||||
|
||||
private
|
||||
def map_resource(entities, options = {}, &block)
|
||||
resource = Resource.new(entities, options)
|
||||
|
||||
with_options :controller => resource.controller do |map|
|
||||
map_collection_actions(map, resource)
|
||||
map_default_collection_actions(map, resource)
|
||||
map_new_actions(map, resource)
|
||||
map_member_actions(map, resource)
|
||||
|
||||
if block_given?
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, &block)
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :new_name_prefix => resource.nesting_name_prefix, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def map_singleton_resource(entities, options = {}, &block)
|
||||
resource = SingletonResource.new(entities, options)
|
||||
|
||||
with_options :controller => resource.controller do |map|
|
||||
map_collection_actions(map, resource)
|
||||
map_default_singleton_actions(map, resource)
|
||||
map_new_actions(map, resource)
|
||||
map_member_actions(map, resource)
|
||||
|
||||
if block_given?
|
||||
with_options(:path_prefix => resource.nesting_path_prefix, :new_name_prefix => resource.nesting_name_prefix, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def map_collection_actions(map, resource)
|
||||
resource.collection_methods.each do |method, actions|
|
||||
route_options = requirements_for(method)
|
||||
|
||||
actions.each do |action|
|
||||
map.named_route(
|
||||
"#{resource.name_prefix}#{action}_#{resource.plural}",
|
||||
"#{resource.path};#{action}",
|
||||
route_options.merge(:action => action.to_s)
|
||||
)
|
||||
action_options = action_options_for(action, resource, method)
|
||||
|
||||
map.named_route(
|
||||
"formatted_#{resource.name_prefix}#{action}_#{resource.plural}",
|
||||
"#{resource.path}.:format;#{action}",
|
||||
route_options.merge(:action => action.to_s)
|
||||
)
|
||||
unless resource.old_name_prefix.blank?
|
||||
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.old_name_prefix}#{action}_#{resource.plural}")
|
||||
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "formatted_#{resource.old_name_prefix}#{action}_#{resource.plural}")
|
||||
end
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{action}_#{resource.plural}")
|
||||
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "formatted_#{action}_#{resource.plural}")
|
||||
end
|
||||
|
||||
map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
|
||||
map.connect("#{resource.path};#{action}", action_options)
|
||||
map.connect("#{resource.path}.:format;#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, :action => "index", :conditions => { :method => :get })
|
||||
map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", :action => "index", :conditions => { :method => :get })
|
||||
def map_default_collection_actions(map, resource)
|
||||
index_action_options = action_options_for("index", resource)
|
||||
map.named_route("#{resource.name_prefix}#{resource.plural}", resource.path, index_action_options)
|
||||
map.named_route("formatted_#{resource.name_prefix}#{resource.plural}", "#{resource.path}.:format", index_action_options)
|
||||
|
||||
map.connect(resource.path, :action => "create", :conditions => { :method => :post })
|
||||
map.connect("#{resource.path}.:format", :action => "create", :conditions => { :method => :post })
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{resource.name_prefix}#{resource.plural}", "#{resource.plural}")
|
||||
map.deprecated_named_route("formatted_#{resource.name_prefix}#{resource.plural}", "formatted_#{resource.plural}")
|
||||
end
|
||||
|
||||
create_action_options = action_options_for("create", resource)
|
||||
map.connect(resource.path, create_action_options)
|
||||
map.connect("#{resource.path}.:format", create_action_options)
|
||||
end
|
||||
|
||||
def map_default_singleton_actions(map, resource)
|
||||
create_action_options = action_options_for("create", resource)
|
||||
map.connect(resource.path, create_action_options)
|
||||
map.connect("#{resource.path}.:format", create_action_options)
|
||||
end
|
||||
|
||||
def map_new_actions(map, resource)
|
||||
resource.new_methods.each do |method, actions|
|
||||
route_options = requirements_for(method)
|
||||
actions.each do |action|
|
||||
action_options = action_options_for(action, resource, method)
|
||||
if action == :new
|
||||
map.named_route("#{resource.name_prefix}new_#{resource.singular}", resource.new_path, route_options.merge(:action => "new"))
|
||||
map.named_route("formatted_#{resource.name_prefix}new_#{resource.singular}", "#{resource.new_path}.:format", route_options.merge(:action => "new"))
|
||||
|
||||
unless resource.old_name_prefix.blank?
|
||||
map.deprecated_named_route("new_#{resource.name_prefix}#{resource.singular}", "#{resource.old_name_prefix}new_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.old_name_prefix}new_#{resource.singular}")
|
||||
end
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("new_#{resource.name_prefix}#{resource.singular}", "new_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "formatted_new_#{resource.singular}")
|
||||
end
|
||||
|
||||
map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
|
||||
map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options)
|
||||
|
||||
else
|
||||
map.named_route("#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path};#{action}", route_options.merge(:action => action.to_s))
|
||||
map.named_route("formatted_#{resource.name_prefix}#{action}_new_#{resource.singular}", "#{resource.new_path}.:format;#{action}", route_options.merge(:action => action.to_s))
|
||||
|
||||
unless resource.old_name_prefix.blank?
|
||||
map.deprecated_named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.old_name_prefix}#{action}_new_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.old_name_prefix}#{action}_new_#{resource.singular}")
|
||||
end
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{action}_new_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "formatted_#{action}_new_#{resource.singular}")
|
||||
end
|
||||
|
||||
map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
|
||||
map.connect("#{resource.new_path};#{action}", action_options)
|
||||
map.connect("#{resource.new_path}.:format;#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def map_member_actions(map, resource)
|
||||
resource.member_methods.each do |method, actions|
|
||||
route_options = requirements_for(method)
|
||||
|
||||
actions.each do |action|
|
||||
map.named_route("#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path};#{action}", route_options.merge(:action => action.to_s))
|
||||
map.named_route("formatted_#{resource.name_prefix}#{action}_#{resource.singular}", "#{resource.member_path}.:format;#{action}", route_options.merge(:action => action.to_s))
|
||||
action_options = action_options_for(action, resource, method)
|
||||
|
||||
unless resource.old_name_prefix.blank?
|
||||
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.old_name_prefix}#{action}_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.old_name_prefix}#{action}_#{resource.singular}")
|
||||
end
|
||||
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{action}_#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "formatted_#{action}_#{resource.singular}")
|
||||
end
|
||||
|
||||
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options)
|
||||
map.connect("#{resource.member_path};#{action}", action_options)
|
||||
map.connect("#{resource.member_path}.:format;#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, :action => "show", :conditions => { :method => :get })
|
||||
map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", :action => "show", :conditions => { :method => :get })
|
||||
show_action_options = action_options_for("show", resource)
|
||||
map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
|
||||
map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
|
||||
|
||||
map.connect(resource.member_path, :action => "update", :conditions => { :method => :put })
|
||||
map.connect("#{resource.member_path}.:format", :action => "update", :conditions => { :method => :put })
|
||||
if resource.deprecate_name_prefix?
|
||||
map.deprecated_named_route("#{resource.name_prefix}#{resource.singular}", "#{resource.singular}")
|
||||
map.deprecated_named_route("formatted_#{resource.name_prefix}#{resource.singular}", "formatted_#{resource.singular}")
|
||||
end
|
||||
|
||||
map.connect(resource.member_path, :action => "destroy", :conditions => { :method => :delete })
|
||||
map.connect("#{resource.member_path}.:format", :action => "destroy", :conditions => { :method => :delete })
|
||||
update_action_options = action_options_for("update", resource)
|
||||
map.connect(resource.member_path, update_action_options)
|
||||
map.connect("#{resource.member_path}.:format", update_action_options)
|
||||
|
||||
destroy_action_options = action_options_for("destroy", resource)
|
||||
map.connect(resource.member_path, destroy_action_options)
|
||||
map.connect("#{resource.member_path}.:format", destroy_action_options)
|
||||
end
|
||||
|
||||
def requirements_for(method)
|
||||
method == :any ? {} : { :conditions => { :method => method } }
|
||||
|
||||
def conditions_for(method)
|
||||
{ :conditions => method == :any ? {} : { :method => method } }
|
||||
end
|
||||
|
||||
def action_options_for(action, resource, method = nil)
|
||||
default_options = { :action => action.to_s }
|
||||
require_id = resource.kind_of?(SingletonResource) ? {} : { :requirements => { :id => Regexp.new("[^#{Routing::SEPARATORS.join}]+") } }
|
||||
case default_options[:action]
|
||||
when "index", "new" : default_options.merge(conditions_for(method || :get))
|
||||
when "create" : default_options.merge(conditions_for(method || :post))
|
||||
when "show", "edit" : default_options.merge(conditions_for(method || :get)).merge(require_id)
|
||||
when "update" : default_options.merge(conditions_for(method || :put)).merge(require_id)
|
||||
when "destroy" : default_options.merge(conditions_for(method || :delete)).merge(require_id)
|
||||
else default_options.merge(conditions_for(method))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@ module ActionController
|
||||
|
||||
def redirect(to_url, permanently = false)
|
||||
@headers["Status"] = "302 Found" unless @headers["Status"] == "301 Moved Permanently"
|
||||
@headers["location"] = to_url
|
||||
@headers["Location"] = to_url
|
||||
|
||||
@body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ class NilClass
|
||||
end
|
||||
end
|
||||
|
||||
class Regexp
|
||||
class Regexp #:nodoc:
|
||||
def number_of_captures
|
||||
Regexp.new("|#{source}").match('').captures.length
|
||||
end
|
||||
@@ -126,7 +126,8 @@ module ActionController
|
||||
# == Named routes
|
||||
#
|
||||
# Routes can be named with the syntax <tt>map.name_of_route options</tt>,
|
||||
# allowing for easy reference within your source as +name_of_route_url+.
|
||||
# allowing for easy reference within your source as +name_of_route_url+
|
||||
# for the full URL and +name_of_route_path+ for the URI path.
|
||||
#
|
||||
# Example:
|
||||
# # In routes.rb
|
||||
@@ -137,29 +138,39 @@ module ActionController
|
||||
#
|
||||
# Arguments can be passed as well.
|
||||
#
|
||||
# redirect_to show_item_url(:id => 25)
|
||||
# redirect_to show_item_path(:id => 25)
|
||||
#
|
||||
# When using +with_options+, the name goes after the item passed to the block.
|
||||
# Use <tt>map.root</tt> as a shorthand to name a route for the root path ""
|
||||
#
|
||||
# ActionController::Routing::Routes.draw do |map|
|
||||
# map.with_options :controller => 'blog' do |blog|
|
||||
# blog.show '', :action => 'list'
|
||||
# blog.delete 'delete/:id', :action => 'delete',
|
||||
# blog.edit 'edit/:id', :action => 'edit'
|
||||
# end
|
||||
# map.connect ':controller/:action/:view
|
||||
# end
|
||||
# # In routes.rb
|
||||
# map.root :controller => 'blogs'
|
||||
#
|
||||
# You would then use the named routes in your views:
|
||||
# # would recognize http://www.example.com/ as
|
||||
# params = { :controller => 'blogs', :action => 'index' }
|
||||
#
|
||||
# link_to @article.title, show_url(:id => @article.id)
|
||||
# # and provide these named routes
|
||||
# root_url # => 'http://www.example.com/'
|
||||
# root_path # => ''
|
||||
#
|
||||
# == Pretty URL's
|
||||
# Note: when using +with_options+, the route is simply named after the
|
||||
# method you call on the block parameter rather than map.
|
||||
#
|
||||
# # In routes.rb
|
||||
# map.with_options :controller => 'blog' do |blog|
|
||||
# blog.show '', :action => 'list'
|
||||
# blog.delete 'delete/:id', :action => 'delete',
|
||||
# blog.edit 'edit/:id', :action => 'edit'
|
||||
# end
|
||||
#
|
||||
# # provides named routes for show, delete, and edit
|
||||
# link_to @article.title, show_path(:id => @article.id)
|
||||
#
|
||||
# == Pretty URLs
|
||||
#
|
||||
# Routes can generate pretty URLs. For example:
|
||||
#
|
||||
# map.connect 'articles/:year/:month/:day',
|
||||
# :controller => 'articles',
|
||||
# :controller => 'articles',
|
||||
# :action => 'find_by_date',
|
||||
# :year => /\d{4}/,
|
||||
# :month => /\d{1,2}/,
|
||||
@@ -305,7 +316,7 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
class Route
|
||||
class Route #:nodoc:
|
||||
attr_accessor :segments, :requirements, :conditions
|
||||
|
||||
def initialize
|
||||
@@ -333,10 +344,10 @@ module ActionController
|
||||
# the query string. (Never use keys from the recalled request when building the
|
||||
# query string.)
|
||||
|
||||
method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(hash, expire_on))\nend"
|
||||
method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(hash, expire_on)]\nend"
|
||||
method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
raw_method
|
||||
end
|
||||
@@ -536,7 +547,7 @@ module ActionController
|
||||
|
||||
end
|
||||
|
||||
class Segment
|
||||
class Segment #:nodoc:
|
||||
attr_accessor :is_optional
|
||||
alias_method :optional?, :is_optional
|
||||
|
||||
@@ -591,7 +602,7 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
class StaticSegment < Segment
|
||||
class StaticSegment < Segment #:nodoc:
|
||||
attr_accessor :value, :raw
|
||||
alias_method :raw?, :raw
|
||||
|
||||
@@ -625,7 +636,7 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
class DividerSegment < StaticSegment
|
||||
class DividerSegment < StaticSegment #:nodoc:
|
||||
def initialize(value = nil)
|
||||
super(value)
|
||||
self.raw = true
|
||||
@@ -637,7 +648,7 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
class DynamicSegment < Segment
|
||||
class DynamicSegment < Segment #:nodoc:
|
||||
attr_accessor :key, :default, :regexp
|
||||
|
||||
def initialize(key = nil, options = {})
|
||||
@@ -725,7 +736,7 @@ module ActionController
|
||||
|
||||
end
|
||||
|
||||
class ControllerSegment < DynamicSegment
|
||||
class ControllerSegment < DynamicSegment #:nodoc:
|
||||
def regexp_chunk
|
||||
possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
|
||||
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
|
||||
@@ -752,7 +763,7 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
class PathSegment < DynamicSegment
|
||||
class PathSegment < DynamicSegment #:nodoc:
|
||||
EscapedSlash = CGI.escape("/")
|
||||
def interpolation_chunk
|
||||
"\#{CGI.escape(#{local_name}.to_s).gsub(#{EscapedSlash.inspect}, '/')}"
|
||||
@@ -782,7 +793,7 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
class RouteBuilder
|
||||
class RouteBuilder #:nodoc:
|
||||
attr_accessor :separators, :optional_separators
|
||||
|
||||
def initialize
|
||||
@@ -950,17 +961,21 @@ module ActionController
|
||||
route.significant_keys << :action
|
||||
end
|
||||
|
||||
if !route.significant_keys.include?(:controller)
|
||||
raise ArgumentError, "Illegal route: the :controller must be specified!"
|
||||
end
|
||||
|
||||
route
|
||||
end
|
||||
end
|
||||
|
||||
class RouteSet
|
||||
class RouteSet #:nodoc:
|
||||
# Mapper instances are used to build routes. The object passed to the draw
|
||||
# block in config/routes.rb is a Mapper instance.
|
||||
#
|
||||
# Mapper instances have relatively few instance methods, in order to avoid
|
||||
# clashes with named routes.
|
||||
class Mapper
|
||||
class Mapper #:nodoc:
|
||||
def initialize(set)
|
||||
@set = set
|
||||
end
|
||||
@@ -974,6 +989,18 @@ module ActionController
|
||||
def named_route(name, path, options = {})
|
||||
@set.add_named_route(name, path, options)
|
||||
end
|
||||
|
||||
def deprecated_named_route(name, deprecated_name, options = {})
|
||||
@set.add_deprecated_named_route(name, deprecated_name)
|
||||
end
|
||||
|
||||
# Added deprecation notice for anyone who already added a named route called "root".
|
||||
# It'll be used as a shortcut for map.connect '' in Rails 2.0.
|
||||
def root(*args, &proc)
|
||||
super unless args.length >= 1 && proc.nil?
|
||||
@set.add_named_route("root", *args)
|
||||
end
|
||||
deprecate :root => "(as the the label for a named route) will become a shortcut for map.connect '', so find another name"
|
||||
|
||||
def method_missing(route_name, *args, &proc)
|
||||
super unless args.length >= 1 && proc.nil?
|
||||
@@ -984,7 +1011,7 @@ module ActionController
|
||||
# A NamedRouteCollection instance is a collection of named routes, and also
|
||||
# maintains an anonymous module that can be used to install helpers for the
|
||||
# named routes.
|
||||
class NamedRouteCollection
|
||||
class NamedRouteCollection #:nodoc:
|
||||
include Enumerable
|
||||
|
||||
attr_reader :routes, :helpers
|
||||
@@ -996,7 +1023,11 @@ module ActionController
|
||||
def clear!
|
||||
@routes = {}
|
||||
@helpers = []
|
||||
@module = Module.new
|
||||
|
||||
@module ||= Module.new
|
||||
@module.instance_methods.each do |selector|
|
||||
@module.send :remove_method, selector
|
||||
end
|
||||
end
|
||||
|
||||
def add(name, route)
|
||||
@@ -1028,6 +1059,38 @@ module ActionController
|
||||
def install(destinations = [ActionController::Base, ActionView::Base])
|
||||
Array(destinations).each { |dest| dest.send :include, @module }
|
||||
end
|
||||
|
||||
def define_deprecated_named_route_methods(name, deprecated_name)
|
||||
|
||||
[:url, :path].each do |kind|
|
||||
@module.send :module_eval, <<-end_eval # We use module_eval to avoid leaks
|
||||
|
||||
def #{url_helper_name(deprecated_name, kind)}(*args)
|
||||
|
||||
ActiveSupport::Deprecation.warn(
|
||||
'The named route "#{url_helper_name(deprecated_name, kind)}" uses a format that has been deprecated. ' +
|
||||
'You should use "#{url_helper_name(name, kind)}" instead.', caller
|
||||
)
|
||||
|
||||
send :#{url_helper_name(name, kind)}, *args
|
||||
|
||||
end
|
||||
|
||||
def #{hash_access_name(deprecated_name, kind)}(*args)
|
||||
|
||||
ActiveSupport::Deprecation.warn(
|
||||
'The named route "#{hash_access_name(deprecated_name, kind)}" uses a format that has been deprecated. ' +
|
||||
'You should use "#{hash_access_name(name, kind)}" instead.', caller
|
||||
)
|
||||
|
||||
send :#{hash_access_name(name, kind)}, *args
|
||||
|
||||
end
|
||||
|
||||
end_eval
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
def url_helper_name(name, kind = :url)
|
||||
@@ -1150,6 +1213,10 @@ module ActionController
|
||||
def add_named_route(name, path, options = {})
|
||||
named_routes[name] = add_route(path, options)
|
||||
end
|
||||
|
||||
def add_deprecated_named_route(name, deprecated_name)
|
||||
named_routes.define_deprecated_named_route_methods(name, deprecated_name)
|
||||
end
|
||||
|
||||
def options_as_params(options)
|
||||
# If an explicit :controller was given, always make :action explicit
|
||||
@@ -1211,11 +1278,14 @@ module ActionController
|
||||
# drop the leading '/' on the controller name
|
||||
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
|
||||
merged = recall.merge(options)
|
||||
|
||||
|
||||
if named_route
|
||||
path = named_route.generate(options, merged, expire_on)
|
||||
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}" if path.nil?
|
||||
return path
|
||||
if path.nil?
|
||||
raise_named_route_error(options, named_route, named_route_name)
|
||||
else
|
||||
return path
|
||||
end
|
||||
else
|
||||
merged[:action] ||= 'index'
|
||||
options[:action] ||= 'index'
|
||||
@@ -1235,6 +1305,18 @@ module ActionController
|
||||
|
||||
raise RoutingError, "No route matches #{options.inspect}"
|
||||
end
|
||||
|
||||
# try to give a helpful error message when named route generation fails
|
||||
def raise_named_route_error(options, named_route, named_route_name)
|
||||
diff = named_route.requirements.diff(options)
|
||||
unless diff.empty?
|
||||
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
|
||||
else
|
||||
required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
|
||||
required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
|
||||
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisifed?"
|
||||
end
|
||||
end
|
||||
|
||||
def recognize(request)
|
||||
params = recognize_path(request.path, extract_request_environment(request))
|
||||
|
||||
@@ -5,6 +5,8 @@ require 'base64'
|
||||
|
||||
class CGI
|
||||
class Session
|
||||
attr_reader :data
|
||||
|
||||
# Return this session's underlying Session instance. Useful for the DB-backed session stores.
|
||||
def model
|
||||
@dbman.model if @dbman
|
||||
|
||||
@@ -26,6 +26,10 @@ class CGI #:nodoc:all
|
||||
def delete
|
||||
@@session_data.delete(@session_id)
|
||||
end
|
||||
|
||||
def data
|
||||
@@session_data[@session_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,6 +93,10 @@ begin
|
||||
end
|
||||
@session_data = {}
|
||||
end
|
||||
|
||||
def data
|
||||
@session_data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -120,16 +120,16 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def process_cleanup_with_session_management_support
|
||||
process_cleanup_without_session_management_support
|
||||
clear_persistent_model_associations
|
||||
process_cleanup_without_session_management_support
|
||||
end
|
||||
|
||||
# Clear cached associations in session data so they don't overflow
|
||||
# the database field. Only applies to ActiveRecordStore since there
|
||||
# is not a standard way to iterate over session data.
|
||||
def clear_persistent_model_associations #:doc:
|
||||
if defined?(@_session) && @_session.instance_variables.include?('@data')
|
||||
session_data = @_session.instance_variable_get('@data')
|
||||
if defined?(@_session) && @_session.respond_to?(:data)
|
||||
session_data = @_session.data
|
||||
|
||||
if session_data && session_data.respond_to?(:each_value)
|
||||
session_data.each_value do |obj|
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
module ActionController
|
||||
module StatusCodes
|
||||
|
||||
module StatusCodes #:nodoc:
|
||||
# Defines the standard HTTP status codes, by integer, with their
|
||||
# corresponding default message texts.
|
||||
# Source: http://www.iana.org/assignments/http-status-codes
|
||||
STATUS_CODES = {
|
||||
100 => "Continue",
|
||||
101 => "Switching Protocols",
|
||||
102 => "Processing",
|
||||
|
||||
200 => "OK",
|
||||
201 => "Created",
|
||||
@@ -14,6 +15,8 @@ module ActionController
|
||||
204 => "No Content",
|
||||
205 => "Reset Content",
|
||||
206 => "Partial Content",
|
||||
207 => "Multi-Status",
|
||||
226 => "IM Used",
|
||||
|
||||
300 => "Multiple Choices",
|
||||
301 => "Moved Permanently",
|
||||
@@ -41,13 +44,19 @@ module ActionController
|
||||
415 => "Unsupported Media Type",
|
||||
416 => "Requested Range Not Satisfiable",
|
||||
417 => "Expectation Failed",
|
||||
422 => "Unprocessable Entity",
|
||||
423 => "Locked",
|
||||
424 => "Failed Dependency",
|
||||
426 => "Upgrade Required",
|
||||
|
||||
500 => "Internal Server Error",
|
||||
501 => "Not Implemented",
|
||||
502 => "Bad Gateway",
|
||||
503 => "Service Unavailable",
|
||||
504 => "Gateway Timeout",
|
||||
505 => "HTTP Version Not Supported"
|
||||
505 => "HTTP Version Not Supported",
|
||||
507 => "Insufficient Storage",
|
||||
510 => "Not Extended"
|
||||
}
|
||||
|
||||
# Provides a symbol-to-fixnum lookup for converting a symbol (like
|
||||
|
||||
@@ -24,6 +24,7 @@ module ActionController #:nodoc:
|
||||
attr_accessor :cookies, :session_options
|
||||
attr_accessor :query_parameters, :request_parameters, :path, :session, :env
|
||||
attr_accessor :host
|
||||
attr_reader :request_uri_overridden
|
||||
|
||||
def initialize(query_parameters = nil, request_parameters = nil, session = nil)
|
||||
@query_parameters = query_parameters || {}
|
||||
@@ -38,7 +39,7 @@ module ActionController #:nodoc:
|
||||
|
||||
def reset_session
|
||||
@session = TestSession.new
|
||||
end
|
||||
end
|
||||
|
||||
def raw_post
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
@@ -67,12 +68,14 @@ module ActionController #:nodoc:
|
||||
# Used to check AbstractRequest's request_uri functionality.
|
||||
# Disables the use of @path and @request_uri so superclass can handle those.
|
||||
def set_REQUEST_URI(value)
|
||||
@request_uri_overridden = true
|
||||
@env["REQUEST_URI"] = value
|
||||
@request_uri = nil
|
||||
@path = nil
|
||||
end
|
||||
|
||||
def request_uri=(uri)
|
||||
@env["REQUEST_URI"] = uri
|
||||
@request_uri = uri
|
||||
@path = uri.split("?").first
|
||||
end
|
||||
@@ -179,7 +182,7 @@ module ActionController #:nodoc:
|
||||
|
||||
# returns the redirection location or nil
|
||||
def redirect_url
|
||||
redirect? ? headers['location'] : nil
|
||||
headers['Location']
|
||||
end
|
||||
|
||||
# does the redirect location match this regexp pattern?
|
||||
@@ -275,27 +278,40 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
class TestSession #:nodoc:
|
||||
def initialize(attributes = {})
|
||||
attr_accessor :session_id
|
||||
|
||||
def initialize(attributes = nil)
|
||||
@session_id = ''
|
||||
@attributes = attributes
|
||||
@saved_attributes = nil
|
||||
end
|
||||
|
||||
def data
|
||||
@attributes ||= @saved_attributes || {}
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@attributes[key]
|
||||
data[key]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@attributes[key] = value
|
||||
data[key] = value
|
||||
end
|
||||
|
||||
def session_id
|
||||
""
|
||||
def update
|
||||
@saved_attributes = @attributes
|
||||
end
|
||||
|
||||
def update() end
|
||||
def close() end
|
||||
def delete() @attributes = {} end
|
||||
def delete
|
||||
@attributes = nil
|
||||
end
|
||||
|
||||
def close
|
||||
update
|
||||
delete
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Essentially generates a modified Tempfile object similar to the object
|
||||
# you'd get from the standard library CGI module in a multipart
|
||||
# request. This means you can use an ActionController::TestUploadedFile
|
||||
@@ -413,12 +429,12 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def build_request_uri(action, parameters)
|
||||
unless @request.env['REQUEST_URI']
|
||||
unless @request.request_uri_overridden
|
||||
options = @controller.send(:rewrite_options, parameters)
|
||||
options.update(:only_path => true, :action => action)
|
||||
|
||||
url = ActionController::UrlRewriter.new(@request, parameters)
|
||||
@request.set_REQUEST_URI(url.rewrite(options))
|
||||
@request.request_uri = url.rewrite(options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -52,8 +52,9 @@ module ActionController
|
||||
# Delete the unused options to prevent their appearance in the query string
|
||||
[:protocol, :host, :port].each { |k| options.delete k }
|
||||
end
|
||||
anchor = "##{options.delete(:anchor)}" if options.key?(:anchor)
|
||||
url << Routing::Routes.generate(options, {})
|
||||
return url
|
||||
return "#{url}#{anchor}"
|
||||
end
|
||||
|
||||
end
|
||||
@@ -76,6 +77,7 @@ module ActionController
|
||||
alias_method :to_s, :to_str
|
||||
|
||||
private
|
||||
# Given a path and options, returns a rewritten URL string
|
||||
def rewrite_url(path, options)
|
||||
rewritten_url = ""
|
||||
unless options[:only_path]
|
||||
@@ -91,6 +93,7 @@ module ActionController
|
||||
rewritten_url
|
||||
end
|
||||
|
||||
# Given a Hash of options, generates a route
|
||||
def rewrite_path(options)
|
||||
options = options.symbolize_keys
|
||||
options.update(options[:params].symbolize_keys) if options[:params]
|
||||
|
||||
@@ -92,7 +92,6 @@ module HTML #:nodoc:
|
||||
# returns non +nil+. Returns the result of the #find call that succeeded.
|
||||
def find(conditions)
|
||||
conditions = validate_conditions(conditions)
|
||||
|
||||
@children.each do |child|
|
||||
node = child.find(conditions)
|
||||
return node if node
|
||||
@@ -152,7 +151,7 @@ module HTML #:nodoc:
|
||||
|
||||
if scanner.skip(/!\[CDATA\[/)
|
||||
scanner.scan_until(/\]\]>/)
|
||||
return CDATA.new(parent, line, pos, scanner.pre_match)
|
||||
return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/<!\[CDATA\[/, ''))
|
||||
end
|
||||
|
||||
closing = ( scanner.scan(/\//) ? :close : nil )
|
||||
@@ -316,7 +315,7 @@ module HTML #:nodoc:
|
||||
s = "<#{@name}"
|
||||
@attributes.each do |k,v|
|
||||
s << " #{k}"
|
||||
s << "='#{v.gsub(/'/,"\\\\'")}'" if String === v
|
||||
s << "=\"#{v}\"" if String === v
|
||||
end
|
||||
s << " /" if @closing == :self
|
||||
s << ">"
|
||||
@@ -410,7 +409,6 @@ module HTML #:nodoc:
|
||||
# :child => /hello world/ }
|
||||
def match(conditions)
|
||||
conditions = validate_conditions(conditions)
|
||||
|
||||
# check content of child nodes
|
||||
if conditions[:content]
|
||||
if children.empty?
|
||||
|
||||
@@ -201,7 +201,8 @@ module HTML
|
||||
|
||||
|
||||
# An invalid selector.
|
||||
class InvalidSelectorError < StandardError ; end
|
||||
class InvalidSelectorError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
|
||||
class << self
|
||||
|
||||
@@ -95,6 +95,7 @@ module ActionController #:nodoc:
|
||||
response.headers.update(options[:add_headers]) if options[:add_headers]
|
||||
unless performed?
|
||||
render(options[:render]) if options[:render]
|
||||
options[:redirect_to] = self.send(options[:redirect_to]) if options[:redirect_to].is_a? Symbol
|
||||
redirect_to(options[:redirect_to]) if options[:redirect_to]
|
||||
end
|
||||
return false
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module ActionPack #:nodoc:
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 1
|
||||
MINOR = 12
|
||||
TINY = 5
|
||||
MINOR = 13
|
||||
TINY = 4
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
||||
@@ -148,7 +148,7 @@ module ActionView #:nodoc:
|
||||
#
|
||||
# This refreshes the sidebar, removes a person element and highlights the user list.
|
||||
#
|
||||
# See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator documentation for more details.
|
||||
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
|
||||
class Base
|
||||
include ERB::Util
|
||||
|
||||
@@ -160,7 +160,7 @@ module ActionView #:nodoc:
|
||||
attr_internal *ActionController::Base::DEPRECATED_INSTANCE_VARIABLES
|
||||
|
||||
# Specify trim mode for the ERB compiler. Defaults to '-'.
|
||||
# See ERB documentation for suitable values.
|
||||
# See ERb documentation for suitable values.
|
||||
@@erb_trim_mode = '-'
|
||||
cattr_accessor :erb_trim_mode
|
||||
|
||||
@@ -191,17 +191,17 @@ module ActionView #:nodoc:
|
||||
end
|
||||
include CompiledTemplates
|
||||
|
||||
# maps inline templates to their method names
|
||||
# Maps inline templates to their method names
|
||||
@@method_names = {}
|
||||
# map method names to their compile time
|
||||
# Map method names to their compile time
|
||||
@@compile_time = {}
|
||||
# map method names to the names passed in local assigns so far
|
||||
# Map method names to the names passed in local assigns so far
|
||||
@@template_args = {}
|
||||
# count the number of inline templates
|
||||
# Count the number of inline templates
|
||||
@@inline_template_count = 0
|
||||
# maps template paths without extension to their file extension returned by pick_template_extension.
|
||||
# if for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions
|
||||
# used by pick_template_extension determines whether ext1 or ext2 will be stored
|
||||
# Maps template paths without extension to their file extension returned by pick_template_extension.
|
||||
# If for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions
|
||||
# used by pick_template_extension determines whether ext1 or ext2 will be stored.
|
||||
@@cached_template_extension = {}
|
||||
|
||||
class ObjectWrapper < Struct.new(:value) #:nodoc:
|
||||
@@ -305,7 +305,6 @@ module ActionView #:nodoc:
|
||||
# Render the provided template with the given local assigns. If the template has not been rendered with the provided
|
||||
# local assigns yet, or if the template has been updated on disk, then the template will be compiled to a method.
|
||||
#
|
||||
|
||||
# Either, but not both, of template and file_path may be nil. If file_path is given, the template
|
||||
# will only be read if it has to be compiled.
|
||||
#
|
||||
@@ -371,10 +370,12 @@ module ActionView #:nodoc:
|
||||
end
|
||||
|
||||
private
|
||||
# Builds a string holding the full path of the template including extension
|
||||
def full_template_path(template_path, extension)
|
||||
"#{@base_path}/#{template_path}.#{extension}"
|
||||
end
|
||||
|
||||
# Asserts the existence of a template.
|
||||
def template_exists?(template_path, extension)
|
||||
file_path = full_template_path(template_path, extension)
|
||||
@@method_names.has_key?(file_path) || FileTest.exists?(file_path)
|
||||
@@ -389,6 +390,7 @@ module ActionView #:nodoc:
|
||||
@@cache_template_extensions && @@cached_template_extension[template_path]
|
||||
end
|
||||
|
||||
# Determines the template's file extension, such as rhtml, rxml, or rjs.
|
||||
def find_template_extension_for(template_path)
|
||||
if match = delegate_template_exists?(template_path)
|
||||
match.first.to_sym
|
||||
@@ -405,6 +407,7 @@ module ActionView #:nodoc:
|
||||
File.read(template_path)
|
||||
end
|
||||
|
||||
# Evaluate the local assigns and pushes them to the view.
|
||||
def evaluate_assigns
|
||||
unless @assigns_added
|
||||
assign_variables_from_controller
|
||||
@@ -416,6 +419,7 @@ module ActionView #:nodoc:
|
||||
handler.new(self).render(template, local_assigns)
|
||||
end
|
||||
|
||||
# Assigns instance variables from the controller to the view.
|
||||
def assign_variables_from_controller
|
||||
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
|
||||
end
|
||||
@@ -427,10 +431,10 @@ module ActionView #:nodoc:
|
||||
((args = @@template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })
|
||||
end
|
||||
|
||||
# Check whether compilation is necessary.
|
||||
# Compile if the inline template or file has not been compiled yet.
|
||||
# Or if local_assigns has a new key, which isn't supported by the compiled code yet.
|
||||
# Or if the file has changed on disk and checking file mods hasn't been disabled.
|
||||
# Method to check whether template compilation is necessary.
|
||||
# The template will be compiled if the inline template or file has not been compiled yet,
|
||||
# if local_assigns has a new key, which isn't supported by the compiled code yet,
|
||||
# or if the file has changed on disk and checking file mods hasn't been disabled.
|
||||
def compile_template?(template, file_name, local_assigns)
|
||||
method_key = file_name || template
|
||||
render_symbol = @@method_names[method_key]
|
||||
@@ -445,14 +449,15 @@ module ActionView #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
# Create source code for given template
|
||||
# Method to create the source code for a given template.
|
||||
def create_template_source(extension, template, render_symbol, locals)
|
||||
if template_requires_setup?(extension)
|
||||
body = case extension.to_sym
|
||||
when :rxml
|
||||
"controller.response.content_type ||= 'application/xml'\n" +
|
||||
"xml = Builder::XmlMarkup.new(:indent => 2)\n" +
|
||||
template
|
||||
"xml ||= Builder::XmlMarkup.new(:indent => 2)\n" +
|
||||
template +
|
||||
"\nxml.target!\n"
|
||||
when :rjs
|
||||
"controller.response.content_type ||= 'text/javascript'\n" +
|
||||
"update_page do |page|\n#{template}\nend"
|
||||
@@ -473,11 +478,11 @@ module ActionView #:nodoc:
|
||||
"def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
|
||||
end
|
||||
|
||||
def template_requires_setup?(extension)
|
||||
def template_requires_setup?(extension) #:nodoc:
|
||||
templates_requiring_setup.include? extension.to_s
|
||||
end
|
||||
|
||||
def templates_requiring_setup
|
||||
def templates_requiring_setup #:nodoc:
|
||||
%w(rxml rjs)
|
||||
end
|
||||
|
||||
@@ -501,6 +506,7 @@ module ActionView #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
# Compile and evaluate the template's code
|
||||
def compile_template(extension, template, file_name, local_assigns)
|
||||
render_symbol = assign_method_name(extension, template, file_name)
|
||||
render_source = create_template_source(extension, template, render_symbol, local_assigns.keys)
|
||||
|
||||
@@ -3,14 +3,14 @@ module ActionView
|
||||
|
||||
# CompiledTemplates modules hold methods that have been compiled.
|
||||
# Templates are compiled into these methods so that they do not need to be
|
||||
# re-read and re-parsed each request.
|
||||
# read and parsed for each request.
|
||||
#
|
||||
# Each template may be compiled into one or more methods. Each method accepts a given
|
||||
# set of parameters which is used to implement local assigns passing.
|
||||
#
|
||||
# To use a compiled template module, create a new instance and include it into the class
|
||||
# in which you want the template to be rendered.
|
||||
class CompiledTemplates < Module #:nodoc:
|
||||
class CompiledTemplates < Module
|
||||
attr_reader :method_names
|
||||
|
||||
def initialize
|
||||
|
||||
@@ -13,17 +13,18 @@ module ActionView
|
||||
# is a great of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
|
||||
# In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html
|
||||
module ActiveRecordHelper
|
||||
# Returns a default input tag for the type of object returned by the method. Example
|
||||
# (title is a VARCHAR column and holds "Hello World"):
|
||||
# Returns a default input tag for the type of object returned by the method. For example, let's say you have a model
|
||||
# that has an attribute +title+ of type VARCHAR column, and this instance holds "Hello World":
|
||||
# input("post", "title") =>
|
||||
# <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
|
||||
def input(record_name, method, options = {})
|
||||
InstanceTag.new(record_name, method, self).to_tag(options)
|
||||
end
|
||||
|
||||
# Returns an entire form with input tags and everything for a specified Active Record object. Example
|
||||
# (post is a new record that has a title using VARCHAR and a body using TEXT):
|
||||
# form("post") =>
|
||||
# Returns an entire form with all needed input tags for a specified Active Record object. For example, let's say you
|
||||
# have a table model <tt>Post</tt> with attributes named <tt>title</tt> of type <tt>VARCHAR</tt> and <tt>body</tt> of type <tt>TEXT</tt>:
|
||||
# form("post")
|
||||
# That line would yield a form like the following:
|
||||
# <form action='/post/create' method='post'>
|
||||
# <p>
|
||||
# <label for="post_title">Title</label><br />
|
||||
@@ -32,14 +33,13 @@ module ActionView
|
||||
# <p>
|
||||
# <label for="post_body">Body</label><br />
|
||||
# <textarea cols="40" id="post_body" name="post[body]" rows="20">
|
||||
# Back to the hill and over it again!
|
||||
# </textarea>
|
||||
# </p>
|
||||
# <input type='submit' value='Create' />
|
||||
# </form>
|
||||
#
|
||||
# It's possible to specialize the form builder by using a different action name and by supplying another
|
||||
# block renderer. Example (entry is a new record that has a message attribute using VARCHAR):
|
||||
# block renderer. For example, let's say you have a model <tt>Entry</tt> with an attribute <tt>message</tt> of type <tt>VARCHAR</tt>:
|
||||
#
|
||||
# form("entry", :action => "sign", :input_block =>
|
||||
# Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}<br />" }) =>
|
||||
@@ -74,27 +74,29 @@ module ActionView
|
||||
content_tag('form', contents, :action => action, :method => 'post', :enctype => options[:multipart] ? 'multipart/form-data': nil)
|
||||
end
|
||||
|
||||
# Returns a string containing the error message attached to the +method+ on the +object+, if one exists.
|
||||
# This error message is wrapped in a DIV tag, which can be specialized to include both a +prepend_text+ and +append_text+
|
||||
# to properly introduce the error and a +css_class+ to style it accordingly. Examples (post has an error message
|
||||
# "can't be empty" on the title attribute):
|
||||
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
|
||||
# This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a +prepend_text+ and/or +append_text+
|
||||
# (to properly explain the error), and a +css_class+ to style it accordingly. As an example, let's say you have a model
|
||||
# +post+ that has an error message on the +title+ attribute:
|
||||
#
|
||||
# <%= error_message_on "post", "title" %> =>
|
||||
# <div class="formError">can't be empty</div>
|
||||
#
|
||||
# <%= error_message_on "post", "title", "Title simply ", " (or it won't work)", "inputError" %> =>
|
||||
# <div class="inputError">Title simply can't be empty (or it won't work)</div>
|
||||
# <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> =>
|
||||
# <div class="inputError">Title simply can't be empty (or it won't work).</div>
|
||||
def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
|
||||
if errors = instance_variable_get("@#{object}").errors.on(method)
|
||||
if (obj = instance_variable_get("@#{object}")) && (errors = obj.errors.on(method))
|
||||
content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a string with a div containing all of the error messages for the objects located as instance variables by the names
|
||||
# Returns a string with a <tt>DIV</tt> containing all of the error messages for the objects located as instance variables by the names
|
||||
# given. If more than one object is specified, the errors for the objects are displayed in the order that the object names are
|
||||
# provided.
|
||||
#
|
||||
# This div can be tailored by the following options:
|
||||
# This <tt>DIV</tt> can be tailored by the following options:
|
||||
#
|
||||
# * <tt>header_tag</tt> - Used for the header of the error div (default: h2)
|
||||
# * <tt>id</tt> - The id of the error div (default: errorExplanation)
|
||||
@@ -103,12 +105,12 @@ module ActionView
|
||||
# any text that you prefer. If <tt>object_name</tt> is not set, the name of
|
||||
# the first object will be used.
|
||||
#
|
||||
# Specifying one object:
|
||||
# To specify the display for one object, you simply provide its name as a parameter. For example, for the +User+ model:
|
||||
#
|
||||
# error_messages_for 'user'
|
||||
#
|
||||
# Specifying more than one object (and using the name 'user' in the
|
||||
# header as the <tt>object_name</tt> instead of 'user_common'):
|
||||
# To specify more than one object, you simply list them; optionally, you can add an extra +object_name+ parameter, which
|
||||
# be the name in the header.
|
||||
#
|
||||
# error_messages_for 'user_common', 'user', :object_name => 'user'
|
||||
#
|
||||
@@ -167,6 +169,8 @@ module ActionView
|
||||
to_date_select_tag(options)
|
||||
when :datetime, :timestamp
|
||||
to_datetime_select_tag(options)
|
||||
when :time
|
||||
to_time_select_tag(options)
|
||||
when :boolean
|
||||
to_boolean_select_tag(options)
|
||||
end
|
||||
@@ -208,6 +212,15 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag
|
||||
def to_time_select_tag(options = {})
|
||||
if object.respond_to?("errors") && object.errors.respond_to?("on")
|
||||
error_wrapping(to_time_select_tag_without_error_wrapping(options), object.errors.on(@method_name))
|
||||
else
|
||||
to_time_select_tag_without_error_wrapping(options)
|
||||
end
|
||||
end
|
||||
|
||||
def error_wrapping(html_tag, has_error)
|
||||
has_error ? Base.field_error_proc.call(html_tag, self) : html_tag
|
||||
end
|
||||
|
||||
@@ -3,20 +3,36 @@ require File.dirname(__FILE__) + '/url_helper'
|
||||
require File.dirname(__FILE__) + '/tag_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# Provides methods for linking a HTML page together with other assets, such as javascripts, stylesheets, and feeds.
|
||||
module Helpers #:nodoc:
|
||||
# Provides methods for linking an HTML page together with other assets such
|
||||
# as images, javascripts, stylesheets, and feeds. You can direct Rails to
|
||||
# link to assets from a dedicated assets server by setting ActionController::Base.asset_host
|
||||
# in your environment.rb. These methods do not verify the assets exist before
|
||||
# linking to them.
|
||||
#
|
||||
# ActionController::Base.asset_host = "http://assets.example.com"
|
||||
# image_tag("rails.png")
|
||||
# => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
|
||||
# stylesheet_include_tag("application")
|
||||
# => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
module AssetTagHelper
|
||||
# Returns a link tag that browsers and news readers can use to auto-detect a RSS or ATOM feed for this page. The +type+ can
|
||||
# either be <tt>:rss</tt> (default) or <tt>:atom</tt> and the +options+ follow the url_for style of declaring a link target.
|
||||
# Returns a link tag that browsers and news readers can use to auto-detect
|
||||
# an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
|
||||
# <tt>:atom</tt>. Control the link options in url_for format using the
|
||||
# +url_options+. You can modify the LINK tag itself in +tag_options+.
|
||||
#
|
||||
# Examples:
|
||||
# auto_discovery_link_tag # =>
|
||||
# Tag Options:
|
||||
# * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
|
||||
# * <tt>:type</tt> - Override the auto-generated mime type
|
||||
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
|
||||
#
|
||||
# auto_discovery_link_tag # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/action" />
|
||||
# auto_discovery_link_tag(:atom) # =>
|
||||
# auto_discovery_link_tag(:atom) # =>
|
||||
# <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.curenthost.com/controller/action" />
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/feed" />
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
|
||||
# auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
|
||||
# <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.curenthost.com/controller/feed" />
|
||||
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
|
||||
tag(
|
||||
@@ -28,9 +44,14 @@ module ActionView
|
||||
)
|
||||
end
|
||||
|
||||
# Returns path to a javascript asset. Example:
|
||||
# Computes the path to a javascript asset in the public javascripts directory.
|
||||
# If the +source+ filename has no extension, .js will be appended.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by javascript_include_tag to build the script path.
|
||||
#
|
||||
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
|
||||
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
|
||||
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
|
||||
def javascript_path(source)
|
||||
compute_public_path(source, 'javascripts', 'js')
|
||||
end
|
||||
@@ -38,7 +59,15 @@ module ActionView
|
||||
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
||||
@@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
|
||||
# Returns a script include tag per source given as argument. Examples:
|
||||
# Returns an html script tag for each of the +sources+ provided. You
|
||||
# can pass in the filename (.js extension is optional) of javascript files
|
||||
# that exist in your public/javascripts directory for inclusion into the
|
||||
# current page or you can pass the full path relative to your document
|
||||
# root. To include the Prototype and Scriptaculous javascript libraries in
|
||||
# your application, pass <tt>:defaults</tt> as the source. When using
|
||||
# :defaults, if an <tt>application.js</tt> file exists in your public
|
||||
# javascripts directory, it will be included as well. You can modify the
|
||||
# html attributes of the script tag by passing a hash as the last argument.
|
||||
#
|
||||
# javascript_include_tag "xmlhr" # =>
|
||||
# <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
|
||||
@@ -52,11 +81,6 @@ module ActionView
|
||||
# <script type="text/javascript" src="/javascripts/effects.js"></script>
|
||||
# ...
|
||||
# <script type="text/javascript" src="/javascripts/application.js"></script> *see below
|
||||
#
|
||||
# If there's an <tt>application.js</tt> file in your <tt>public/javascripts</tt> directory,
|
||||
# <tt>javascript_include_tag :defaults</tt> will automatically include it. This file
|
||||
# facilitates the inclusion of small snippets of JavaScript code, along the lines of
|
||||
# <tt>controllers/application.rb</tt> and <tt>helpers/application_helper.rb</tt>.
|
||||
def javascript_include_tag(*sources)
|
||||
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
||||
|
||||
@@ -69,18 +93,16 @@ module ActionView
|
||||
sources << "application" if defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js")
|
||||
end
|
||||
|
||||
sources.collect { |source|
|
||||
sources.collect do |source|
|
||||
source = javascript_path(source)
|
||||
content_tag("script", "", { "type" => "text/javascript", "src" => source }.merge(options))
|
||||
}.join("\n")
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Register one or more additional JavaScript files to be included when
|
||||
#
|
||||
# javascript_include_tag :defaults
|
||||
#
|
||||
# is called. This method is intended to be called only from plugin initialization
|
||||
# to register extra .js files the plugin installed in <tt>public/javascripts</tt>.
|
||||
# <tt>javascript_include_tag :defaults</tt> is called. This method is
|
||||
# only intended to be called from plugin initialization to register additional
|
||||
# .js files that the plugin installed in <tt>public/javascripts</tt>.
|
||||
def self.register_javascript_include_default(*sources)
|
||||
@@javascript_default_sources.concat(sources)
|
||||
end
|
||||
@@ -89,14 +111,21 @@ module ActionView
|
||||
@@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
|
||||
end
|
||||
|
||||
# Returns path to a stylesheet asset. Example:
|
||||
# Computes the path to a stylesheet asset in the public stylesheets directory.
|
||||
# If the +source+ filename has no extension, .css will be appended.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by stylesheet_link_tag to build the stylesheet path.
|
||||
#
|
||||
# stylesheet_path "style" # => /stylesheets/style.css
|
||||
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
|
||||
# stylesheet_path "/dir/style.css" # => /dir/style.css
|
||||
def stylesheet_path(source)
|
||||
compute_public_path(source, 'stylesheets', 'css')
|
||||
end
|
||||
|
||||
# Returns a css link tag per source given as argument. Examples:
|
||||
# Returns a stylesheet link tag for the sources specified as arguments. If
|
||||
# you don't specify an extension, .css will be appended automatically.
|
||||
# You can modify the link attributes by passing a hash as the last argument.
|
||||
#
|
||||
# stylesheet_link_tag "style" # =>
|
||||
# <link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
@@ -109,18 +138,20 @@ module ActionView
|
||||
# <link href="/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
def stylesheet_link_tag(*sources)
|
||||
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
||||
sources.collect { |source|
|
||||
sources.collect do |source|
|
||||
source = stylesheet_path(source)
|
||||
tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
|
||||
}.join("\n")
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Returns path to an image asset. Example:
|
||||
# Computes the path to an image asset in the public images directory.
|
||||
# Full paths from the document root will be passed through.
|
||||
# Used internally by image_tag to build the image path. Passing
|
||||
# a filename without an extension is deprecated.
|
||||
#
|
||||
# The +src+ can be supplied as a...
|
||||
# * full path, like "/my_images/image.gif"
|
||||
# * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
|
||||
# * file name without extension, like "logo", that gets expanded to "/images/logo.png"
|
||||
# image_path("edit.png") # => /images/edit.png
|
||||
# image_path("icons/edit.png") # => /images/icons/edit.png
|
||||
# image_path("/icons/edit.png") # => /icons/edit.png
|
||||
def image_path(source)
|
||||
unless (source.split("/").last || source).include?(".") || source.blank?
|
||||
ActiveSupport::Deprecation.warn(
|
||||
@@ -133,15 +164,24 @@ module ActionView
|
||||
compute_public_path(source, 'images', 'png')
|
||||
end
|
||||
|
||||
# Returns an image tag converting the +options+ into html options on the tag, but with these special cases:
|
||||
# Returns an html image tag for the +source+. The +source+ can be a full
|
||||
# path or a file that exists in your public images directory. Note that
|
||||
# specifying a filename without the extension is now deprecated in Rails.
|
||||
# You can add html attributes using the +options+. The +options+ supports
|
||||
# two additional keys for convienence and conformance:
|
||||
#
|
||||
# * <tt>:alt</tt> - If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension)
|
||||
# * <tt>:size</tt> - Supplied as "XxY", so "30x45" becomes width="30" and height="45"
|
||||
# * <tt>:alt</tt> - If no alt text is given, the file name part of the
|
||||
# +source+ is used (capitalized and without the extension)
|
||||
# * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
|
||||
# width="30" and height="45". <tt>:size</tt> will be ignored if the
|
||||
# value is not in the correct format.
|
||||
#
|
||||
# The +src+ can be supplied as a...
|
||||
# * full path, like "/my_images/image.gif"
|
||||
# * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
|
||||
# * file name without extension, like "logo", that gets expanded to "/images/logo.png"
|
||||
# image_tag("icon.png") # =>
|
||||
# <img src="/images/icon.png" alt="Icon" />
|
||||
# image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
|
||||
# <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
|
||||
# image_tag("/icons/icon.gif", :size => "16x16") # =>
|
||||
# <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
|
||||
def image_tag(source, options = {})
|
||||
options.symbolize_keys!
|
||||
|
||||
@@ -149,8 +189,8 @@ module ActionView
|
||||
options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize
|
||||
|
||||
if options[:size]
|
||||
options[:width], options[:height] = options[:size].split("x")
|
||||
options.delete :size
|
||||
options[:width], options[:height] = options[:size].split("x") if options[:size] =~ %r{^\d+x\d+$}
|
||||
options.delete(:size)
|
||||
end
|
||||
|
||||
tag("img", options)
|
||||
@@ -163,7 +203,7 @@ module ActionView
|
||||
unless source =~ %r{^[-a-z]+://}
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
asset_id = rails_asset_id(source)
|
||||
source << '?' + asset_id if defined?(RAILS_ROOT) and not asset_id.blank?
|
||||
source << '?' + asset_id if defined?(RAILS_ROOT) && !asset_id.blank?
|
||||
source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}"
|
||||
end
|
||||
source
|
||||
|
||||
@@ -62,7 +62,7 @@ module ActionView
|
||||
when 40..59 then 'less than a minute'
|
||||
else '1 minute'
|
||||
end
|
||||
|
||||
|
||||
when 2..44 then "#{distance_in_minutes} minutes"
|
||||
when 45..89 then 'about 1 hour'
|
||||
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
||||
@@ -74,12 +74,12 @@ module ActionView
|
||||
else "over #{(distance_in_minutes / 525960).round} years"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
|
||||
def time_ago_in_words(from_time, include_seconds = false)
|
||||
distance_of_time_in_words(from_time, Time.now, include_seconds)
|
||||
end
|
||||
|
||||
|
||||
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
|
||||
|
||||
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
|
||||
@@ -108,6 +108,19 @@ module ActionView
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options)
|
||||
end
|
||||
|
||||
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
|
||||
# time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
|
||||
# You can include the seconds with <tt>:include_seconds</tt>.
|
||||
# Examples:
|
||||
#
|
||||
# time_select("post", "sunrise")
|
||||
# time_select("post", "start_time", :include_seconds => true)
|
||||
#
|
||||
# The selects are prepared for multi-parameter assignment to an Active Record object.
|
||||
def time_select(object_name, method, options = {})
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options)
|
||||
end
|
||||
|
||||
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
|
||||
# attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
|
||||
#
|
||||
@@ -119,36 +132,55 @@ module ActionView
|
||||
InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options)
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
|
||||
def select_date(date = Date.today, options = {})
|
||||
select_year(date, options) + select_month(date, options) + select_day(date, options)
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
|
||||
def select_datetime(datetime = Time.now, options = {})
|
||||
select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) +
|
||||
select_hour(datetime, options) + select_minute(datetime, options)
|
||||
# It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
|
||||
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
|
||||
# will be appened onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt>
|
||||
# keys to the +options+ to control visual display of the elements.
|
||||
def select_datetime(datetime = Time.now, options = {})
|
||||
separator = options[:datetime_separator] || ''
|
||||
select_date(datetime, options) + separator + select_time(datetime, options)
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
|
||||
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
|
||||
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
|
||||
# will be appened onto the <tt>:order</tt> passed in.
|
||||
def select_date(date = Date.today, options = {})
|
||||
options[:order] ||= []
|
||||
[:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
|
||||
|
||||
select_date = ''
|
||||
options[:order].each do |o|
|
||||
select_date << self.send("select_#{o}", date, options)
|
||||
end
|
||||
select_date
|
||||
end
|
||||
|
||||
# Returns a set of html select-tags (one for hour and minute)
|
||||
# You can set <tt>:add_separator</tt> key to format the output.
|
||||
def select_time(datetime = Time.now, options = {})
|
||||
h = select_hour(datetime, options) + select_minute(datetime, options) + (options[:include_seconds] ? select_second(datetime, options) : '')
|
||||
separator = options[:time_separator] || ''
|
||||
select_hour(datetime, options) + separator + select_minute(datetime, options) + (options[:include_seconds] ? separator + select_second(datetime, options) : '')
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
|
||||
# The <tt>second</tt> can also be substituted for a second number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
|
||||
def select_second(datetime, options = {})
|
||||
second_options = []
|
||||
|
||||
0.upto(59) do |second|
|
||||
second_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) == second) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
|
||||
)
|
||||
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
|
||||
if options[:use_hidden]
|
||||
options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : ''
|
||||
else
|
||||
second_options = []
|
||||
0.upto(59) do |second|
|
||||
second_options << ((val == second) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'second', second_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'second', second_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
|
||||
@@ -156,84 +188,100 @@ module ActionView
|
||||
# The <tt>minute</tt> can also be substituted for a minute number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
|
||||
def select_minute(datetime, options = {})
|
||||
minute_options = []
|
||||
|
||||
0.step(59, options[:minute_step] || 1) do |minute|
|
||||
minute_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.min) == minute) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
|
||||
)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'minute', minute_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'minute', val, options)
|
||||
else
|
||||
minute_options = []
|
||||
0.step(59, options[:minute_step] || 1) do |minute|
|
||||
minute_options << ((val == minute) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'minute', minute_options, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
|
||||
# The <tt>hour</tt> can also be substituted for a hour number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
|
||||
def select_hour(datetime, options = {})
|
||||
hour_options = []
|
||||
|
||||
0.upto(23) do |hour|
|
||||
hour_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) == hour) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
|
||||
)
|
||||
val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'hour', val, options)
|
||||
else
|
||||
hour_options = []
|
||||
0.upto(23) do |hour|
|
||||
hour_options << ((val == hour) ?
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
|
||||
%(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'hour', hour_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'hour', hour_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
|
||||
# The <tt>date</tt> can also be substituted for a hour number.
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
|
||||
def select_day(date, options = {})
|
||||
day_options = []
|
||||
|
||||
1.upto(31) do |day|
|
||||
day_options << ((date && (date.kind_of?(Fixnum) ? date : date.day) == day) ?
|
||||
%(<option value="#{day}" selected="selected">#{day}</option>\n) :
|
||||
%(<option value="#{day}">#{day}</option>\n)
|
||||
)
|
||||
val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'day', val, options)
|
||||
else
|
||||
day_options = []
|
||||
1.upto(31) do |day|
|
||||
day_options << ((val == day) ?
|
||||
%(<option value="#{day}" selected="selected">#{day}</option>\n) :
|
||||
%(<option value="#{day}">#{day}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'day', day_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'day', day_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the months January through December with the current month selected.
|
||||
# The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
|
||||
# (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
|
||||
# set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
|
||||
# set the <tt>:add_month_numbers</tt> key in +options+ to true. Examples:
|
||||
# set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations,
|
||||
# set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the
|
||||
# <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# select_month(Date.today) # Will use keys like "January", "March"
|
||||
# select_month(Date.today, :use_month_numbers => true) # Will use keys like "1", "3"
|
||||
# select_month(Date.today, :add_month_numbers => true) # Will use keys like "1 - January", "3 - March"
|
||||
# select_month(Date.today, :use_short_month => true) # Will use keys like "Jan", "Mar"
|
||||
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...)) # Will use keys like "Januar", "Marts"
|
||||
#
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'month' by default.
|
||||
#
|
||||
# If you would prefer to show month names as abbreviations, set the
|
||||
# <tt>:use_short_month</tt> key in +options+ to true.
|
||||
def select_month(date, options = {})
|
||||
month_options = []
|
||||
month_names = options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES
|
||||
val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'month', val, options)
|
||||
else
|
||||
month_options = []
|
||||
month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
|
||||
month_names.unshift(nil) if month_names.size < 13
|
||||
1.upto(12) do |month_number|
|
||||
month_name = if options[:use_month_numbers]
|
||||
month_number
|
||||
elsif options[:add_month_numbers]
|
||||
month_number.to_s + ' - ' + month_names[month_number]
|
||||
else
|
||||
month_names[month_number]
|
||||
end
|
||||
|
||||
1.upto(12) do |month_number|
|
||||
month_name = if options[:use_month_numbers]
|
||||
month_number
|
||||
elsif options[:add_month_numbers]
|
||||
month_number.to_s + ' - ' + month_names[month_number]
|
||||
else
|
||||
month_names[month_number]
|
||||
month_options << ((val == month_number) ?
|
||||
%(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
|
||||
%(<option value="#{month_number}">#{month_name}</option>\n)
|
||||
)
|
||||
end
|
||||
|
||||
month_options << ((date && (date.kind_of?(Fixnum) ? date : date.month) == month_number) ?
|
||||
%(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
|
||||
%(<option value="#{month_number}">#{month_name}</option>\n)
|
||||
)
|
||||
select_html(options[:field_name] || 'month', month_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'month', month_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
# Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
|
||||
@@ -243,37 +291,51 @@ module ActionView
|
||||
#
|
||||
# select_year(Date.today, :start_year => 1992, :end_year => 2007) # ascending year values
|
||||
# select_year(Date.today, :start_year => 2005, :end_year => 1900) # descending year values
|
||||
# select_year(2006, :start_year => 2000, :end_year => 2010)
|
||||
#
|
||||
# Override the field name using the <tt>:field_name</tt> option, 'year' by default.
|
||||
def select_year(date, options = {})
|
||||
year_options = []
|
||||
y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
|
||||
val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
|
||||
if options[:use_hidden]
|
||||
hidden_html(options[:field_name] || 'year', val, options)
|
||||
else
|
||||
year_options = []
|
||||
y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
|
||||
|
||||
start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
|
||||
step_val = start_year < end_year ? 1 : -1
|
||||
|
||||
start_year.step(end_year, step_val) do |year|
|
||||
year_options << ((date && (date.kind_of?(Fixnum) ? date : date.year) == year) ?
|
||||
%(<option value="#{year}" selected="selected">#{year}</option>\n) :
|
||||
%(<option value="#{year}">#{year}</option>\n)
|
||||
)
|
||||
start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
|
||||
step_val = start_year < end_year ? 1 : -1
|
||||
start_year.step(end_year, step_val) do |year|
|
||||
year_options << ((val == year) ?
|
||||
%(<option value="#{year}" selected="selected">#{year}</option>\n) :
|
||||
%(<option value="#{year}">#{year}</option>\n)
|
||||
)
|
||||
end
|
||||
select_html(options[:field_name] || 'year', year_options, options)
|
||||
end
|
||||
|
||||
select_html(options[:field_name] || 'year', year_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
|
||||
end
|
||||
|
||||
private
|
||||
def select_html(type, options, prefix = nil, include_blank = false, discard_type = false, disabled = false)
|
||||
select_html = %(<select name="#{prefix || DEFAULT_PREFIX})
|
||||
select_html << "[#{type}]" unless discard_type
|
||||
select_html << %(")
|
||||
select_html << %( disabled="disabled") if disabled
|
||||
|
||||
def select_html(type, html_options, options)
|
||||
name_and_id_from_options(options, type)
|
||||
select_html = %(<select id="#{options[:id]}" name="#{options[:name]}")
|
||||
select_html << %( disabled="disabled") if options[:disabled]
|
||||
select_html << %(>\n)
|
||||
select_html << %(<option value=""></option>\n) if include_blank
|
||||
select_html << options.to_s
|
||||
select_html << %(<option value=""></option>\n) if options[:include_blank]
|
||||
select_html << html_options.to_s
|
||||
select_html << "</select>\n"
|
||||
end
|
||||
|
||||
def hidden_html(type, value, options)
|
||||
name_and_id_from_options(options, type)
|
||||
hidden_html = %(<input type="hidden" id="#{options[:id]}" name="#{options[:name]}" value="#{value}" />\n)
|
||||
end
|
||||
|
||||
def name_and_id_from_options(options, type)
|
||||
options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
|
||||
options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
|
||||
end
|
||||
|
||||
def leading_zero_on_single_digits(number)
|
||||
number > 9 ? number : "0#{number}"
|
||||
end
|
||||
@@ -283,45 +345,71 @@ module ActionView
|
||||
include DateHelper
|
||||
|
||||
def to_date_select_tag(options = {})
|
||||
defaults = { :discard_type => true }
|
||||
options = defaults.merge(options)
|
||||
options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
|
||||
value = value(object)
|
||||
date = options[:include_blank] ? (value || 0) : (value || Date.today)
|
||||
date_or_time_select options.merge(:discard_hour => true)
|
||||
end
|
||||
|
||||
date_select = ''
|
||||
options[:order] = [:month, :year, :day] if options[:month_before_year] # For backwards compatibility
|
||||
options[:order] ||= [:year, :month, :day]
|
||||
|
||||
position = {:year => 1, :month => 2, :day => 3}
|
||||
|
||||
discard = {}
|
||||
discard[:year] = true if options[:discard_year]
|
||||
discard[:month] = true if options[:discard_month]
|
||||
discard[:day] = true if options[:discard_day] or options[:discard_month]
|
||||
|
||||
options[:order].each do |param|
|
||||
date_select << self.send("select_#{param}", date, options_with_prefix.call(position[param])) unless discard[param]
|
||||
end
|
||||
|
||||
date_select
|
||||
def to_time_select_tag(options = {})
|
||||
date_or_time_select options.merge(:discard_year => true, :discard_month => true)
|
||||
end
|
||||
|
||||
def to_datetime_select_tag(options = {})
|
||||
defaults = { :discard_type => true }
|
||||
options = defaults.merge(options)
|
||||
options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
|
||||
value = value(object)
|
||||
datetime = options[:include_blank] ? (value || nil) : (value || Time.now)
|
||||
|
||||
datetime_select = select_year(datetime, options_with_prefix.call(1))
|
||||
datetime_select << select_month(datetime, options_with_prefix.call(2)) unless options[:discard_month]
|
||||
datetime_select << select_day(datetime, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month]
|
||||
datetime_select << ' — ' + select_hour(datetime, options_with_prefix.call(4)) unless options[:discard_hour]
|
||||
datetime_select << ' : ' + select_minute(datetime, options_with_prefix.call(5)) unless options[:discard_minute] || options[:discard_hour]
|
||||
|
||||
datetime_select
|
||||
date_or_time_select options
|
||||
end
|
||||
|
||||
private
|
||||
def date_or_time_select(options)
|
||||
defaults = { :discard_type => true }
|
||||
options = defaults.merge(options)
|
||||
datetime = value(object)
|
||||
datetime ||= Time.now unless options[:include_blank]
|
||||
|
||||
position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
|
||||
|
||||
order = (options[:order] ||= [:year, :month, :day])
|
||||
|
||||
# Discard explicit and implicit by not being included in the :order
|
||||
discard = {}
|
||||
discard[:year] = true if options[:discard_year] or !order.include?(:year)
|
||||
discard[:month] = true if options[:discard_month] or !order.include?(:month)
|
||||
discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day)
|
||||
discard[:hour] = true if options[:discard_hour]
|
||||
discard[:minute] = true if options[:discard_minute] or discard[:hour]
|
||||
discard[:second] = true unless options[:include_seconds] && !discard[:minute]
|
||||
|
||||
# Maintain valid dates by including hidden fields for discarded elements
|
||||
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
|
||||
# Ensure proper ordering of :hour, :minute and :second
|
||||
[:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
|
||||
|
||||
date_or_time_select = ''
|
||||
order.reverse.each do |param|
|
||||
# Send hidden fields for discarded elements once output has started
|
||||
# This ensures AR can reconstruct valid dates using ParseDate
|
||||
next if discard[param] && date_or_time_select.empty?
|
||||
|
||||
date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param]))))
|
||||
date_or_time_select.insert(0,
|
||||
case param
|
||||
when :hour then (discard[:year] && discard[:day] ? "" : " — ")
|
||||
when :minute then " : "
|
||||
when :second then options[:include_seconds] ? " : " : ""
|
||||
else ""
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
date_or_time_select
|
||||
end
|
||||
|
||||
def options_with_prefix(position, options)
|
||||
prefix = "#{@object_name}"
|
||||
if options[:index]
|
||||
prefix << "[#{options[:index]}]"
|
||||
elsif @auto_index
|
||||
prefix << "[#{@auto_index}]"
|
||||
end
|
||||
options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
|
||||
end
|
||||
end
|
||||
|
||||
class FormBuilder
|
||||
@@ -329,6 +417,10 @@ module ActionView
|
||||
@template.date_select(@object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def time_select(method, options = {})
|
||||
@template.time_select(@object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
def datetime_select(method, options = {})
|
||||
@template.datetime_select(@object_name, method, options.merge(:object => @object))
|
||||
end
|
||||
|
||||
@@ -3,6 +3,16 @@ module ActionView
|
||||
# Provides a set of methods for making it easier to locate problems.
|
||||
module DebugHelper
|
||||
# Returns a <pre>-tag set with the +object+ dumped by YAML. Very readable way to inspect an object.
|
||||
# my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]}
|
||||
# debug(my_hash)
|
||||
# => <pre class='debug_dump'>---
|
||||
# first: 1
|
||||
# second: two
|
||||
# third:
|
||||
# - 1
|
||||
# - 2
|
||||
# - 3
|
||||
# </pre>
|
||||
def debug(object)
|
||||
begin
|
||||
Marshal::dump(object)
|
||||
|
||||
37
actionpack/lib/action_view/helpers/deprecated_helper.rb
Normal file
37
actionpack/lib/action_view/helpers/deprecated_helper.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
module ActionView
|
||||
module Helpers
|
||||
module PrototypeHelper
|
||||
|
||||
# Method to execute an element update using Prototype.
|
||||
# DEPRECATION WARNING: This helper has been depercated; use RJS instead.
|
||||
# See ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods for more information.
|
||||
def update_element_function(element_id, options = {}, &block)
|
||||
content = escape_javascript(options[:content] || '')
|
||||
content = escape_javascript(capture(&block)) if block
|
||||
|
||||
javascript_function = case (options[:action] || :update)
|
||||
when :update
|
||||
if options[:position]
|
||||
"new Insertion.#{options[:position].to_s.camelize}('#{element_id}','#{content}')"
|
||||
else
|
||||
"$('#{element_id}').innerHTML = '#{content}'"
|
||||
end
|
||||
|
||||
when :empty
|
||||
"$('#{element_id}').innerHTML = ''"
|
||||
|
||||
when :remove
|
||||
"Element.remove('#{element_id}')"
|
||||
|
||||
else
|
||||
raise ArgumentError, "Invalid action, choose one of :update, :remove, :empty"
|
||||
end
|
||||
|
||||
javascript_function << ";\n"
|
||||
options[:binding] ? concat(javascript_function, options[:binding]) : javascript_function
|
||||
end
|
||||
deprecate :update_element_function => "use RJS instead"
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -142,11 +142,13 @@ module ActionView
|
||||
#
|
||||
# Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
|
||||
# Like collection_select and datetime_select.
|
||||
def fields_for(object_name, *args, &proc)
|
||||
def fields_for(object_name, *args, &block)
|
||||
raise ArgumentError, "Missing block" unless block_given?
|
||||
options = args.last.is_a?(Hash) ? args.pop : {}
|
||||
object = args.first
|
||||
yield((options[:builder] || FormBuilder).new(object_name, object, self, options, proc))
|
||||
|
||||
builder = options[:builder] || ActionView::Base.default_form_builder
|
||||
yield builder.new(object_name, object, self, options, block)
|
||||
end
|
||||
|
||||
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
|
||||
@@ -385,7 +387,7 @@ module ActionView
|
||||
options["name"] ||= tag_name_with_index(@auto_index)
|
||||
options["id"] ||= tag_id_with_index(@auto_index)
|
||||
else
|
||||
options["name"] ||= tag_name
|
||||
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
|
||||
options["id"] ||= tag_id
|
||||
end
|
||||
end
|
||||
@@ -436,4 +438,9 @@ module ActionView
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Base
|
||||
cattr_accessor :default_form_builder
|
||||
self.default_form_builder = ::ActionView::Helpers::FormBuilder
|
||||
end
|
||||
end
|
||||
|
||||
@@ -189,9 +189,9 @@ module ActionView
|
||||
# Wrapper for text_field with added AJAX autocompletion functionality.
|
||||
#
|
||||
# In your controller, you'll need to define an action called
|
||||
# auto_complete_for_object_method to respond the AJAX calls,
|
||||
# auto_complete_for to respond the AJAX calls,
|
||||
#
|
||||
# See the RDoc on ActionController::AutoComplete to learn more about this.
|
||||
# See the RDoc on ActionController::Macros::AutoComplete to learn more about this.
|
||||
def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {})
|
||||
(completion_options[:skip_style] ? "" : auto_complete_stylesheet) +
|
||||
text_field(object, method, tag_options) +
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
|
||||
// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
@@ -473,6 +473,7 @@ Ajax.InPlaceEditor.prototype = {
|
||||
this.element = $(element);
|
||||
|
||||
this.options = Object.extend({
|
||||
paramName: "value",
|
||||
okButton: true,
|
||||
okText: "ok",
|
||||
cancelLink: true,
|
||||
@@ -604,7 +605,7 @@ Ajax.InPlaceEditor.prototype = {
|
||||
var textField = document.createElement("input");
|
||||
textField.obj = this;
|
||||
textField.type = "text";
|
||||
textField.name = "value";
|
||||
textField.name = this.options.paramName;
|
||||
textField.value = text;
|
||||
textField.style.backgroundColor = this.options.highlightcolor;
|
||||
textField.className = 'editor_field';
|
||||
@@ -617,7 +618,7 @@ Ajax.InPlaceEditor.prototype = {
|
||||
this.options.textarea = true;
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.obj = this;
|
||||
textArea.name = "value";
|
||||
textArea.name = this.options.paramName;
|
||||
textArea.value = this.convertHTMLLineBreaks(text);
|
||||
textArea.rows = this.options.rows;
|
||||
textArea.cols = this.options.cols || 40;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// Contributors:
|
||||
// Justin Palmer (http://encytemedia.com/)
|
||||
// Mark Pilgrim (http://diveintomark.org/)
|
||||
@@ -10,7 +10,7 @@
|
||||
// converts rgb() and #xxx to #xxxxxx format,
|
||||
// returns self (or first argument) if not convertable
|
||||
String.prototype.parseColor = function() {
|
||||
var color = '#';
|
||||
var color = '#';
|
||||
if(this.slice(0,4) == 'rgb(') {
|
||||
var cols = this.slice(4,this.length-1).split(',');
|
||||
var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
|
||||
@@ -939,8 +939,142 @@ Effect.Fold = function(element) {
|
||||
}}, arguments[1] || {}));
|
||||
};
|
||||
|
||||
Effect.Morph = Class.create();
|
||||
Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
if(!this.element) throw(Effect._elementDoesNotExistError);
|
||||
var options = Object.extend({
|
||||
style: ''
|
||||
}, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
setup: function(){
|
||||
function parseColor(color){
|
||||
if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
|
||||
color = color.parseColor();
|
||||
return $R(0,2).map(function(i){
|
||||
return parseInt( color.slice(i*2+1,i*2+3), 16 )
|
||||
});
|
||||
}
|
||||
this.transforms = this.options.style.parseStyle().map(function(property){
|
||||
var originalValue = this.element.getStyle(property[0]);
|
||||
return $H({
|
||||
style: property[0],
|
||||
originalValue: property[1].unit=='color' ?
|
||||
parseColor(originalValue) : parseFloat(originalValue || 0),
|
||||
targetValue: property[1].unit=='color' ?
|
||||
parseColor(property[1].value) : property[1].value,
|
||||
unit: property[1].unit
|
||||
});
|
||||
}.bind(this)).reject(function(transform){
|
||||
return (
|
||||
(transform.originalValue == transform.targetValue) ||
|
||||
(
|
||||
transform.unit != 'color' &&
|
||||
(isNaN(transform.originalValue) || isNaN(transform.targetValue))
|
||||
)
|
||||
)
|
||||
});
|
||||
},
|
||||
update: function(position) {
|
||||
var style = $H(), value = null;
|
||||
this.transforms.each(function(transform){
|
||||
value = transform.unit=='color' ?
|
||||
$R(0,2).inject('#',function(m,v,i){
|
||||
return m+(Math.round(transform.originalValue[i]+
|
||||
(transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) :
|
||||
transform.originalValue + Math.round(
|
||||
((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
|
||||
style[transform.style] = value;
|
||||
});
|
||||
this.element.setStyle(style);
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Transform = Class.create();
|
||||
Object.extend(Effect.Transform.prototype, {
|
||||
initialize: function(tracks){
|
||||
this.tracks = [];
|
||||
this.options = arguments[1] || {};
|
||||
this.addTracks(tracks);
|
||||
},
|
||||
addTracks: function(tracks){
|
||||
tracks.each(function(track){
|
||||
var data = $H(track).values().first();
|
||||
this.tracks.push($H({
|
||||
ids: $H(track).keys().first(),
|
||||
effect: Effect.Morph,
|
||||
options: { style: data }
|
||||
}));
|
||||
}.bind(this));
|
||||
return this;
|
||||
},
|
||||
play: function(){
|
||||
return new Effect.Parallel(
|
||||
this.tracks.map(function(track){
|
||||
var elements = [$(track.ids) || $$(track.ids)].flatten();
|
||||
return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
|
||||
}).flatten(),
|
||||
this.options
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage',
|
||||
'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle',
|
||||
'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth',
|
||||
'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor',
|
||||
'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content',
|
||||
'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction',
|
||||
'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch',
|
||||
'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight',
|
||||
'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight',
|
||||
'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity',
|
||||
'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY',
|
||||
'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore',
|
||||
'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes',
|
||||
'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress',
|
||||
'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top',
|
||||
'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows',
|
||||
'width', 'wordSpacing', 'zIndex'];
|
||||
|
||||
Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
|
||||
|
||||
String.prototype.parseStyle = function(){
|
||||
var element = Element.extend(document.createElement('div'));
|
||||
element.innerHTML = '<div style="' + this + '"></div>';
|
||||
var style = element.down().style, styleRules = $H();
|
||||
|
||||
Element.CSS_PROPERTIES.each(function(property){
|
||||
if(style[property]) styleRules[property] = style[property];
|
||||
});
|
||||
|
||||
var result = $H();
|
||||
|
||||
styleRules.each(function(pair){
|
||||
var property = pair[0], value = pair[1], unit = null;
|
||||
|
||||
if(value.parseColor('#zzzzzz') != '#zzzzzz') {
|
||||
value = value.parseColor();
|
||||
unit = 'color';
|
||||
} else if(Element.CSS_LENGTH.test(value))
|
||||
var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/),
|
||||
value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null;
|
||||
|
||||
result[property.underscore().dasherize()] = $H({ value:value, unit:unit });
|
||||
}.bind(this));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Element.morph = function(element, style) {
|
||||
new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
|
||||
return element;
|
||||
};
|
||||
|
||||
['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
|
||||
'collectTextNodes','collectTextNodesIgnoreClass'].each(
|
||||
'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
|
||||
function(f) { Element.Methods[f] = Element[f]; }
|
||||
);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -94,16 +94,16 @@ module ActionView
|
||||
end
|
||||
|
||||
# Formats a +number+ with grouped thousands using +delimiter+. You
|
||||
# can customize the format in the +options+ hash.
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter, defaults to ","
|
||||
# * <tt>:separator</tt> - Sets the separator between the units, defaults to "."
|
||||
# can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters.
|
||||
# * <tt>delimiter</tt> - Sets the thousands delimiter, defaults to ","
|
||||
# * <tt>separator</tt> - Sets the separator between the units, defaults to "."
|
||||
#
|
||||
# number_with_delimiter(12345678) => 12,345,678
|
||||
# number_with_delimiter(12345678.05) => 12,345,678.05
|
||||
# number_with_delimiter(12345678, :delimiter => ".") => 12.345.678
|
||||
# number_with_delimiter(12345678, ".") => 12.345.678
|
||||
def number_with_delimiter(number, delimiter=",", separator=".")
|
||||
begin
|
||||
parts = number.to_s.split(separator)
|
||||
parts = number.to_s.split('.')
|
||||
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
||||
parts.join separator
|
||||
rescue
|
||||
|
||||
@@ -63,7 +63,7 @@ module ActionView
|
||||
# can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
|
||||
#
|
||||
# Example:
|
||||
# link_to_remote "Destroy", person_url(:id => person), :method => :delete
|
||||
# link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
|
||||
#
|
||||
# By default, these remote requests are processed asynchronous during
|
||||
# which various JavaScript callbacks can be triggered (for progress
|
||||
@@ -204,81 +204,6 @@ module ActionView
|
||||
tag("input", options[:html], false)
|
||||
end
|
||||
|
||||
# Returns a JavaScript function (or expression) that'll update a DOM
|
||||
# element according to the options passed.
|
||||
#
|
||||
# * <tt>:content</tt>: The content to use for updating. Can be left out
|
||||
# if using block, see example.
|
||||
# * <tt>:action</tt>: Valid options are :update (assumed by default),
|
||||
# :empty, :remove
|
||||
# * <tt>:position</tt> If the :action is :update, you can optionally
|
||||
# specify one of the following positions: :before, :top, :bottom,
|
||||
# :after.
|
||||
#
|
||||
# Examples:
|
||||
# <%= javascript_tag(update_element_function("products",
|
||||
# :position => :bottom, :content => "<p>New product!</p>")) %>
|
||||
#
|
||||
# <% replacement_function = update_element_function("products") do %>
|
||||
# <p>Product 1</p>
|
||||
# <p>Product 2</p>
|
||||
# <% end %>
|
||||
# <%= javascript_tag(replacement_function) %>
|
||||
#
|
||||
# This method can also be used in combination with remote method call
|
||||
# where the result is evaluated afterwards to cause multiple updates on
|
||||
# a page. Example:
|
||||
#
|
||||
# # Calling view
|
||||
# <%= form_remote_tag :url => { :action => "buy" },
|
||||
# :complete => evaluate_remote_response %>
|
||||
# all the inputs here...
|
||||
#
|
||||
# # Controller action
|
||||
# def buy
|
||||
# @product = Product.find(1)
|
||||
# end
|
||||
#
|
||||
# # Returning view
|
||||
# <%= update_element_function(
|
||||
# "cart", :action => :update, :position => :bottom,
|
||||
# :content => "<p>New Product: #{@product.name}</p>")) %>
|
||||
# <% update_element_function("status", :binding => binding) do %>
|
||||
# You've bought a new product!
|
||||
# <% end %>
|
||||
#
|
||||
# Notice how the second call doesn't need to be in an ERb output block
|
||||
# since it uses a block and passes in the binding to render directly.
|
||||
# This trick will however only work in ERb (not Builder or other
|
||||
# template forms).
|
||||
#
|
||||
# See also JavaScriptGenerator and update_page.
|
||||
def update_element_function(element_id, options = {}, &block)
|
||||
content = escape_javascript(options[:content] || '')
|
||||
content = escape_javascript(capture(&block)) if block
|
||||
|
||||
javascript_function = case (options[:action] || :update)
|
||||
when :update
|
||||
if options[:position]
|
||||
"new Insertion.#{options[:position].to_s.camelize}('#{element_id}','#{content}')"
|
||||
else
|
||||
"$('#{element_id}').innerHTML = '#{content}'"
|
||||
end
|
||||
|
||||
when :empty
|
||||
"$('#{element_id}').innerHTML = ''"
|
||||
|
||||
when :remove
|
||||
"Element.remove('#{element_id}')"
|
||||
|
||||
else
|
||||
raise ArgumentError, "Invalid action, choose one of :update, :remove, :empty"
|
||||
end
|
||||
|
||||
javascript_function << ";\n"
|
||||
options[:binding] ? concat(javascript_function, options[:binding]) : javascript_function
|
||||
end
|
||||
|
||||
# Returns 'eval(request.responseText)' which is the JavaScript function
|
||||
# that form_remote_tag can call in :complete to evaluate a multiple
|
||||
# update return document using update_element_function calls.
|
||||
@@ -325,8 +250,10 @@ module ActionView
|
||||
return function
|
||||
end
|
||||
|
||||
# Observes the field with the DOM ID specified by +field_id+ and makes
|
||||
# an Ajax call when its contents have changed.
|
||||
# Observes the field with the DOM ID specified by +field_id+ and calls a
|
||||
# callback when its contents have changed. The default callback is an
|
||||
# Ajax call. By default the value of the observed field is sent as a
|
||||
# parameter with the Ajax call.
|
||||
#
|
||||
# Required +options+ are either of:
|
||||
# <tt>:url</tt>:: +url_for+-style options for the action to call
|
||||
@@ -343,14 +270,24 @@ module ActionView
|
||||
# <tt>:update</tt>:: Specifies the DOM ID of the element whose
|
||||
# innerHTML should be updated with the
|
||||
# XMLHttpRequest response text.
|
||||
# <tt>:with</tt>:: A JavaScript expression specifying the
|
||||
# parameters for the XMLHttpRequest. This defaults
|
||||
# to 'value', which in the evaluated context
|
||||
# refers to the new field value. If you specify a
|
||||
# string without a "=", it'll be extended to mean
|
||||
# the form key that the value should be assigned to.
|
||||
# So :with => "term" gives "'term'=value". If a "=" is
|
||||
# present, no extension will happen.
|
||||
# <tt>:with</tt>:: A JavaScript expression specifying the parameters
|
||||
# for the XMLHttpRequest. The default is to send the
|
||||
# key and value of the observed field. Any custom
|
||||
# expressions should return a valid URL query string.
|
||||
# The value of the field is stored in the JavaScript
|
||||
# variable +value+.
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# :with => "'my_custom_key=' + value"
|
||||
# :with => "'person[name]=' + prompt('New name')"
|
||||
# :with => "Form.Element.serialize('other-field')"
|
||||
#
|
||||
# Finally
|
||||
# :with => 'name'
|
||||
# is shorthand for
|
||||
# :with => "'name=' + value"
|
||||
# This essentially just changes the key of the parameter.
|
||||
# <tt>:on</tt>:: Specifies which event handler to observe. By default,
|
||||
# it's set to "changed" for text fields and areas and
|
||||
# "click" for radio buttons and checkboxes. With this,
|
||||
@@ -366,11 +303,15 @@ module ActionView
|
||||
build_observer('Form.Element.EventObserver', field_id, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Like +observe_field+, but operates on an entire form identified by the
|
||||
# DOM ID +form_id+. +options+ are the same as +observe_field+, except
|
||||
# the default value of the <tt>:with</tt> option evaluates to the
|
||||
# serialized (request string) value of the form.
|
||||
|
||||
# Observes the form with the DOM ID specified by +form_id+ and calls a
|
||||
# callback when its contents have changed. The default callback is an
|
||||
# Ajax call. By default all fields of the observed field are sent as
|
||||
# parameters with the Ajax call.
|
||||
#
|
||||
# The +options+ for +observe_form+ are the same as the options for
|
||||
# +observe_field+. The JavaScript variable +value+ available to the
|
||||
# <tt>:with</tt> option is set to the serialized form by default.
|
||||
def observe_form(form_id, options = {})
|
||||
if options[:frequency]
|
||||
build_observer('Form.Observer', form_id, options)
|
||||
@@ -466,7 +407,7 @@ module ActionView
|
||||
# Returns an object whose <tt>#to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
|
||||
# expression as an argument to another JavaScriptGenerator method.
|
||||
def literal(code)
|
||||
JavaScriptLiteral.new(code)
|
||||
ActiveSupport::JSON::Variable.new(code.to_s)
|
||||
end
|
||||
|
||||
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
|
||||
@@ -542,7 +483,7 @@ module ActionView
|
||||
#
|
||||
# # Replace the DOM element having ID 'person-45' with the
|
||||
# # 'person' partial for the appropriate object.
|
||||
# replace_html 'person-45', :partial => 'person', :object => @person
|
||||
# replace 'person-45', :partial => 'person', :object => @person
|
||||
#
|
||||
# This allows the same partial that is used for the +insert_html+ to
|
||||
# be also used for the input to +replace+ without resorting to
|
||||
@@ -735,10 +676,10 @@ module ActionView
|
||||
end
|
||||
|
||||
def build_observer(klass, name, options = {})
|
||||
if options[:with] && !options[:with].include?("=")
|
||||
if options[:with] && (options[:with] !~ /[=(.]/)
|
||||
options[:with] = "'#{options[:with]}=' + value"
|
||||
else
|
||||
options[:with] ||= 'value' if options[:update]
|
||||
options[:with] ||= 'value' unless options[:function]
|
||||
end
|
||||
|
||||
callback = options[:function] || remote_function(options)
|
||||
@@ -763,13 +704,6 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
# Bypasses string escaping so you can pass around raw JavaScript
|
||||
class JavaScriptLiteral < String #:nodoc:
|
||||
def to_json
|
||||
to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Converts chained method calls on DOM proxy elements into JavaScript chains
|
||||
class JavaScriptProxy < Builder::BlankSlate #:nodoc:
|
||||
def initialize(generator, root = nil)
|
||||
|
||||
@@ -2,30 +2,48 @@ require 'cgi'
|
||||
require 'erb'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# This is poor man's Builder for the rare cases where you need to programmatically make tags but can't use Builder.
|
||||
module Helpers #:nodoc:
|
||||
# Use these methods to generate HTML tags programmatically when you can't use
|
||||
# a Builder. By default, they output XHTML compliant tags.
|
||||
module TagHelper
|
||||
include ERB::Util
|
||||
|
||||
# Examples:
|
||||
# * <tt>tag("br") => <br /></tt>
|
||||
# * <tt>tag("input", { "type" => "text"}) => <input type="text" /></tt>
|
||||
# Returns an empty HTML tag of type +name+ which by default is XHTML
|
||||
# compliant. Setting +open+ to true will create an open tag compatible
|
||||
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
|
||||
# hash to +options+. For attributes with no value like (disabled and
|
||||
# readonly), give it a value of true in the +options+ hash. You can use
|
||||
# symbols or strings for the attribute names.
|
||||
#
|
||||
# tag("br")
|
||||
# # => <br />
|
||||
# tag("br", nil, true)
|
||||
# # => <br>
|
||||
# tag("input", { :type => 'text', :disabled => true })
|
||||
# # => <input type="text" disabled="disabled" />
|
||||
def tag(name, options = nil, open = false)
|
||||
"<#{name}#{tag_options(options.stringify_keys) if options}" + (open ? ">" : " />")
|
||||
"<#{name}#{tag_options(options) if options}" + (open ? ">" : " />")
|
||||
end
|
||||
|
||||
# Examples:
|
||||
# * <tt>content_tag(:p, "Hello world!") => <p>Hello world!</p></tt>
|
||||
# * <tt>content_tag(:div, content_tag(:p, "Hello world!"), :class => "strong") => </tt>
|
||||
# <tt><div class="strong"><p>Hello world!</p></div></tt>
|
||||
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
|
||||
# HTML attributes by passing an attributes hash to +options+. For attributes
|
||||
# with no value like (disabled and readonly), give it a value of true in
|
||||
# the +options+ hash. You can use symbols or strings for the attribute names.
|
||||
#
|
||||
# ERb example:
|
||||
# <% content_tag :div, :class => "strong" do -%>
|
||||
# Hello world!
|
||||
# <% end -%>
|
||||
# content_tag(:p, "Hello world!")
|
||||
# # => <p>Hello world!</p>
|
||||
# content_tag(:div, content_tag(:p, "Hello world!"), :class => "strong")
|
||||
# # => <div class="strong"><p>Hello world!</p></div>
|
||||
# content_tag("select", options, :multiple => true)
|
||||
# # => <select multiple="multiple">...options...</select>
|
||||
#
|
||||
# Will output:
|
||||
# <div class="strong"><p>Hello world!</p></div>
|
||||
# Instead of passing the content as an argument, you can also use a block
|
||||
# in which case, you pass your +options+ as the second parameter.
|
||||
#
|
||||
# <% content_tag :div, :class => "strong" do -%>
|
||||
# Hello world!
|
||||
# <% end -%>
|
||||
# # => <div class="strong"><p>Hello world!</p></div>
|
||||
def content_tag(name, content_or_options_with_block = nil, options = nil, &block)
|
||||
if block_given?
|
||||
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
||||
@@ -37,27 +55,28 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a CDATA section for the given +content+. CDATA sections
|
||||
# Returns a CDATA section with the given +content+. CDATA sections
|
||||
# are used to escape blocks of text containing characters which would
|
||||
# otherwise be recognized as markup. CDATA sections begin with the string
|
||||
# <tt><![CDATA[</tt> and end with (and may not contain) the string
|
||||
# <tt>]]></tt>.
|
||||
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
|
||||
#
|
||||
# cdata_section("<hello world>")
|
||||
# # => <![CDATA[<hello world>]]>
|
||||
def cdata_section(content)
|
||||
"<![CDATA[#{content}]]>"
|
||||
end
|
||||
|
||||
# Escapes a given string, while leaving any currently escaped entities alone.
|
||||
# Returns the escaped +html+ without affecting existing escaped entities.
|
||||
#
|
||||
# escape_once("1 > 2 & 3")
|
||||
# # => "1 < 2 & 3"
|
||||
#
|
||||
# # => "1 < 2 & 3"
|
||||
def escape_once(html)
|
||||
fix_double_escape(html_escape(html.to_s))
|
||||
end
|
||||
|
||||
private
|
||||
def content_tag_string(name, content, options)
|
||||
tag_options = options ? tag_options(options.stringify_keys) : ""
|
||||
tag_options = options ? tag_options(options) : ""
|
||||
"<#{name}#{tag_options}>#{content}</#{name}>"
|
||||
end
|
||||
|
||||
|
||||
@@ -154,12 +154,10 @@ module ActionView
|
||||
# considered as a linebreak and a <tt><br /></tt> tag is appended. This
|
||||
# method does not remove the newlines from the +text+.
|
||||
def simple_format(text)
|
||||
text = text.gsub(/(\r\n|\n|\r)/, "\n") # lets make them newlines crossplatform
|
||||
text.gsub!(/\n\n+/, "\n\n") # zap dupes
|
||||
text.gsub!(/\n\n/, '</p>\0<p>') # turn two newlines into paragraph
|
||||
text.gsub!(/([^\n])(\n)(?=[^\n])/, '\1\2<br />') # turn single newline into <br />
|
||||
|
||||
content_tag("p", text)
|
||||
content_tag 'p', text.to_s.
|
||||
gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
|
||||
gsub(/\n\n+/, "</p>\n\n<p>"). # 2+ newline -> paragraph
|
||||
gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
|
||||
end
|
||||
|
||||
# Turns all urls and email addresses into clickable links. The +link+ parameter
|
||||
@@ -256,7 +254,7 @@ module ActionView
|
||||
# html-scanner tokenizer and so its HTML parsing ability is limited by
|
||||
# that of html-scanner.
|
||||
def strip_tags(html)
|
||||
return nil if html.blank?
|
||||
return html if html.blank?
|
||||
if html.index("<")
|
||||
text = ""
|
||||
tokenizer = HTML::Tokenizer.new(html)
|
||||
@@ -356,26 +354,26 @@ module ActionView
|
||||
@_cycles = Hash.new unless defined?(@_cycles)
|
||||
@_cycles[name] = cycle_object
|
||||
end
|
||||
|
||||
|
||||
AUTO_LINK_RE = %r{
|
||||
( # leading text
|
||||
<\w+.*?>| # leading HTML tag, or
|
||||
[^=!:'"/]| # leading punctuation, or
|
||||
^ # beginning of line
|
||||
( # leading text
|
||||
<\w+.*?>| # leading HTML tag, or
|
||||
[^=!:'"/]| # leading punctuation, or
|
||||
^ # beginning of line
|
||||
)
|
||||
(
|
||||
(?:https?://)| # protocol spec, or
|
||||
(?:www\.) # www.*
|
||||
(?:https?://)| # protocol spec, or
|
||||
(?:www\.) # www.*
|
||||
)
|
||||
(
|
||||
[-\w]+ # subdomain or domain
|
||||
(?:\.[-\w]+)* # remaining subdomains or domain
|
||||
(?::\d+)? # port
|
||||
(?:/(?:[~\w%.;-]+)?)* # path
|
||||
(?:\?[\w%&=.;-]+)? # query string
|
||||
(?:\#\w*)? # trailing anchor
|
||||
[-\w]+ # subdomain or domain
|
||||
(?:\.[-\w]+)* # remaining subdomains or domain
|
||||
(?::\d+)? # port
|
||||
(?:/(?:(?:[~\w\+%-]|(?:[,.;:][^\s$]))+)?)* # path
|
||||
(?:\?[\w\+%&=.;-]+)? # query string
|
||||
(?:\#[\w\-]*)? # trailing anchor
|
||||
)
|
||||
([[:punct:]]|\s|<|$) # trailing text
|
||||
([[:punct:]]|\s|<|$) # trailing text
|
||||
}x unless const_defined?(:AUTO_LINK_RE)
|
||||
|
||||
# Turns all urls into clickable links. If a block is given, each url
|
||||
|
||||
@@ -310,9 +310,15 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
# Returns true if the current page uri is generated by the +options+ passed.
|
||||
# True if the current request uri was generated by the given +options+.
|
||||
def current_page?(options)
|
||||
CGI.escapeHTML(self.url_for(options)) == @controller.request.request_uri
|
||||
url_string = CGI.escapeHTML(url_for(options))
|
||||
request = @controller.request
|
||||
if url_string =~ /^\w+:\/\//
|
||||
url_string == "#{request.protocol}#{request.host_with_port}#{request.request_uri}"
|
||||
else
|
||||
url_string == request.request_uri
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -6,16 +6,22 @@ module ActionView
|
||||
|
||||
attr_reader :original_exception
|
||||
|
||||
def initialize(base_path, file_name, assigns, source, original_exception)
|
||||
@base_path, @assigns, @source, @original_exception =
|
||||
base_path, assigns, source, original_exception
|
||||
@file_name = file_name
|
||||
def initialize(base_path, file_path, assigns, source, original_exception)
|
||||
@base_path, @assigns, @source, @original_exception =
|
||||
base_path, assigns.dup, source, original_exception
|
||||
@file_path = file_path
|
||||
|
||||
remove_deprecated_assigns!
|
||||
end
|
||||
|
||||
|
||||
def message
|
||||
original_exception.message
|
||||
ActiveSupport::Deprecation.silence { original_exception.message }
|
||||
end
|
||||
|
||||
|
||||
def clean_backtrace
|
||||
original_exception.clean_backtrace
|
||||
end
|
||||
|
||||
def sub_template_message
|
||||
if @sub_templates
|
||||
"Trace of template inclusion: " +
|
||||
@@ -24,63 +30,81 @@ module ActionView
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def source_extract(indention = 0)
|
||||
source_code = IO.readlines(@file_name)
|
||||
|
||||
start_on_line = [ line_number - SOURCE_CODE_RADIUS - 1, 0 ].max
|
||||
end_on_line = [ line_number + SOURCE_CODE_RADIUS - 1, source_code.length].min
|
||||
|
||||
def source_extract(indentation = 0)
|
||||
return unless num = line_number
|
||||
num = num.to_i
|
||||
|
||||
source_code = IO.readlines(@file_path)
|
||||
|
||||
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
|
||||
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
|
||||
|
||||
indent = ' ' * indentation
|
||||
line_counter = start_on_line
|
||||
extract = source_code[start_on_line..end_on_line].collect do |line|
|
||||
|
||||
source_code[start_on_line..end_on_line].sum do |line|
|
||||
line_counter += 1
|
||||
"#{' ' * indention}#{line_counter}: " + line
|
||||
"#{indent}#{line_counter}: #{line}"
|
||||
end
|
||||
|
||||
extract.join
|
||||
end
|
||||
|
||||
def sub_template_of(file_name)
|
||||
def sub_template_of(template_path)
|
||||
@sub_templates ||= []
|
||||
@sub_templates << file_name
|
||||
@sub_templates << template_path
|
||||
end
|
||||
|
||||
|
||||
def line_number
|
||||
if file_name
|
||||
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
|
||||
[@original_exception.message, @original_exception.clean_backtrace].flatten.each do |line|
|
||||
return $1.to_i if regexp =~ line
|
||||
@line_number ||=
|
||||
if file_name
|
||||
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
|
||||
|
||||
$1 if message =~ regexp or clean_backtrace.find { |line| line =~ regexp }
|
||||
end
|
||||
end
|
||||
0
|
||||
end
|
||||
|
||||
|
||||
def file_name
|
||||
stripped = strip_base_path(@file_name)
|
||||
stripped[0] == ?/ ? stripped[1..-1] : stripped
|
||||
stripped = strip_base_path(@file_path)
|
||||
stripped.slice!(0,1) if stripped[0] == ?/
|
||||
stripped
|
||||
end
|
||||
|
||||
|
||||
def to_s
|
||||
"\n\n#{self.class} (#{message}) on line ##{line_number} of #{file_name}:\n" +
|
||||
source_extract + "\n " +
|
||||
original_exception.clean_backtrace.join("\n ") +
|
||||
"\n\n"
|
||||
"\n\n#{self.class} (#{message}) #{source_location}:\n" +
|
||||
"#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n"
|
||||
end
|
||||
|
||||
def backtrace
|
||||
[
|
||||
"On line ##{line_number} of #{file_name}\n\n#{source_extract(4)}\n " +
|
||||
original_exception.clean_backtrace.join("\n ")
|
||||
[
|
||||
"#{source_location.capitalize}\n\n#{source_extract(4)}\n " +
|
||||
clean_backtrace.join("\n ")
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
def strip_base_path(file_name)
|
||||
file_name = File.expand_path(file_name).gsub(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '')
|
||||
file_name.gsub(@base_path, "")
|
||||
def remove_deprecated_assigns!
|
||||
ActionController::Base::DEPRECATED_INSTANCE_VARIABLES.each do |ivar|
|
||||
@assigns.delete(ivar)
|
||||
end
|
||||
end
|
||||
|
||||
def strip_base_path(path)
|
||||
File.expand_path(path).
|
||||
gsub(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '').
|
||||
gsub(@base_path, "")
|
||||
end
|
||||
|
||||
def source_location
|
||||
if line_number
|
||||
"on line ##{line_number} of "
|
||||
else
|
||||
'in '
|
||||
end + file_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, ''] if defined?(Exception::TraceSubstitutions)
|
||||
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}}, '#{RAILS_ROOT}'] if defined?(RAILS_ROOT)
|
||||
if defined?(Exception::TraceSubstitutions)
|
||||
Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, '']
|
||||
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}}, '#{RAILS_ROOT}'] if defined?(RAILS_ROOT)
|
||||
end
|
||||
|
||||
@@ -5,6 +5,8 @@ class PaginationTest < ActiveRecordTestCase
|
||||
|
||||
class PaginationController < ActionController::Base
|
||||
self.template_root = "#{File.dirname(__FILE__)}/../fixtures/"
|
||||
|
||||
around_filter :silence_deprecation_warnings
|
||||
|
||||
def simple_paginate
|
||||
@topic_pages, @topics = paginate(:topics)
|
||||
@@ -67,6 +69,13 @@ class PaginationTest < ActiveRecordTestCase
|
||||
:count => "d.id")
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
|
||||
def silence_deprecation_warnings
|
||||
ActiveSupport::Deprecation.silence do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def rescue_errors(e) raise e end
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ require File.dirname(__FILE__) + '/../abstract_unit'
|
||||
class ActionPackAssertionsController < ActionController::Base
|
||||
|
||||
# this does absolutely nothing
|
||||
def nothing() render_text ""; end
|
||||
def nothing() head :ok end
|
||||
|
||||
# a standard template
|
||||
def hello_world() render "test/hello_world"; end
|
||||
@@ -27,13 +27,13 @@ class ActionPackAssertionsController < ActionController::Base
|
||||
def redirect_external() redirect_to_url "http://www.rubyonrails.org"; end
|
||||
|
||||
# a 404
|
||||
def response404() render_text "", "404 AWOL"; end
|
||||
def response404() head '404 AWOL' end
|
||||
|
||||
# a 500
|
||||
def response500() render_text "", "500 Sorry"; end
|
||||
def response500() head '500 Sorry' end
|
||||
|
||||
# a fictional 599
|
||||
def response599() render_text "", "599 Whoah!"; end
|
||||
def response599() head '599 Whoah!' end
|
||||
|
||||
# putting stuff in the flash
|
||||
def flash_me
|
||||
@@ -139,6 +139,10 @@ module Admin
|
||||
def redirect_to_fellow_controller
|
||||
redirect_to :controller => 'user'
|
||||
end
|
||||
|
||||
def redirect_to_top_level_named_route
|
||||
redirect_to top_level_url(:id => "foo")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -155,10 +159,16 @@ ActionPackAssertionsController.template_root = File.dirname(__FILE__) + "/../fix
|
||||
class ActionPackAssertionsControllerTest < Test::Unit::TestCase
|
||||
# let's get this party started
|
||||
def setup
|
||||
ActionController::Routing::Routes.reload
|
||||
ActionController::Routing.use_controllers!(%w(action_pack_assertions admin/inner_module content admin/user))
|
||||
@controller = ActionPackAssertionsController.new
|
||||
@request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActionController::Routing::Routes.reload
|
||||
end
|
||||
|
||||
# -- assertion-based testing ------------------------------------------------
|
||||
|
||||
def test_assert_tag_and_url_for
|
||||
@@ -295,6 +305,19 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_assert_redirected_to_top_level_named_route_from_nested_controller
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.top_level '/action_pack_assertions/:id', :controller => 'action_pack_assertions', :action => 'index'
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
@controller = Admin::InnerModuleController.new
|
||||
process :redirect_to_top_level_named_route
|
||||
# passes -> assert_redirected_to "http://test.host/action_pack_assertions/foo"
|
||||
assert_redirected_to "/action_pack_assertions/foo"
|
||||
end
|
||||
end
|
||||
|
||||
# test the flash-based assertions with something is in the flash
|
||||
def test_flash_assertions_full
|
||||
process :flash_me
|
||||
@@ -402,7 +425,9 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
|
||||
|
||||
process :redirect_external
|
||||
assert_equal 'http://www.rubyonrails.org', @response.redirect_url
|
||||
end
|
||||
|
||||
def test_no_redirect_url
|
||||
process :nothing
|
||||
assert_nil @response.redirect_url
|
||||
end
|
||||
|
||||
@@ -39,7 +39,10 @@ class AddressesTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_list
|
||||
get :list
|
||||
# because pagination is deprecated
|
||||
ActiveSupport::Deprecation.silence do
|
||||
get :list
|
||||
end
|
||||
assert_equal "We only need to get this far!", @response.body.chomp
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ unless defined?(ActionMailer)
|
||||
require 'action_mailer'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require_gem 'actionmailer'
|
||||
gem 'actionmailer'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ class ControllerInstanceTests < Test::Unit::TestCase
|
||||
# Mocha adds methods to Object which are then included in the public_instance_methods
|
||||
# This method hides those from the controller so the above tests won't know the difference
|
||||
def hide_mocha_methods_from_controller(controller)
|
||||
mocha_methods = [:expects, :metaclass, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, :stubba_method, :stubs, :verify]
|
||||
mocha_methods = [:expects, :metaclass, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, :stubba_method, :stubs, :verify, :__is_a__, :__metaclass__]
|
||||
controller.class.send(:hide_action, *mocha_methods)
|
||||
end
|
||||
|
||||
|
||||
@@ -5,8 +5,29 @@ CACHE_DIR = 'test_cache'
|
||||
# Don't change '/../temp/' cavalierly or you might hoze something you don't want hozed
|
||||
FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
|
||||
ActionController::Base.perform_caching = true
|
||||
ActionController::Base.page_cache_directory = FILE_STORE_PATH
|
||||
ActionController::Base.fragment_cache_store = :file_store, FILE_STORE_PATH
|
||||
|
||||
class PageCachingTestController < ActionController::Base
|
||||
caches_page :ok, :no_content, :found, :not_found
|
||||
|
||||
def ok
|
||||
head :ok
|
||||
end
|
||||
|
||||
def no_content
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def found
|
||||
redirect_to :action => 'ok'
|
||||
end
|
||||
|
||||
def not_found
|
||||
head :not_found
|
||||
end
|
||||
end
|
||||
|
||||
class PageCachingTest < Test::Unit::TestCase
|
||||
def setup
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
@@ -14,11 +35,23 @@ class PageCachingTest < Test::Unit::TestCase
|
||||
map.resources :posts
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
|
||||
|
||||
@request = ActionController::TestRequest.new
|
||||
@request.host = 'hostname.com'
|
||||
|
||||
@response = ActionController::TestResponse.new
|
||||
@controller = PageCachingTestController.new
|
||||
|
||||
@params = {:controller => 'posts', :action => 'index', :only_path => true, :skip_relative_url_root => true}
|
||||
@rewriter = ActionController::UrlRewriter.new(@request, @params)
|
||||
end
|
||||
|
||||
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
|
||||
FileUtils.mkdir_p(FILE_STORE_PATH)
|
||||
end
|
||||
|
||||
def teardown
|
||||
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
|
||||
end
|
||||
|
||||
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
|
||||
@params[:format] = 'rss'
|
||||
@@ -26,35 +59,68 @@ class PageCachingTest < Test::Unit::TestCase
|
||||
@params[:format] = nil
|
||||
assert_equal '/', @rewriter.rewrite(@params)
|
||||
end
|
||||
|
||||
def test_should_cache_get_with_ok_status
|
||||
get :ok
|
||||
assert_response :ok
|
||||
assert_page_cached :ok, "get with ok status should have been cached"
|
||||
end
|
||||
|
||||
[:ok, :no_content, :found, :not_found].each do |status|
|
||||
[:get, :post, :put, :delete].each do |method|
|
||||
unless method == :get and status == :ok
|
||||
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
|
||||
@request.env['REQUEST_METHOD'] = method.to_s.upcase
|
||||
process status
|
||||
assert_response status
|
||||
assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def assert_page_cached(action, message = "#{action} should have been cached")
|
||||
assert page_cached?(action), message
|
||||
end
|
||||
|
||||
def assert_page_not_cached(action, message = "#{action} shouldn't have been cached")
|
||||
assert !page_cached?(action), message
|
||||
end
|
||||
|
||||
def page_cached?(action)
|
||||
File.exist? "#{FILE_STORE_PATH}/page_caching_test/#{action}.html"
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCachingTestController < ActionController::Base
|
||||
caches_action :index
|
||||
|
||||
|
||||
def index
|
||||
sleep 0.01
|
||||
@cache_this = Time.now.to_f.to_s
|
||||
render :text => @cache_this
|
||||
end
|
||||
|
||||
|
||||
def expire
|
||||
expire_action :controller => 'action_caching_test', :action => 'index'
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
class ActionCachingMockController
|
||||
attr_accessor :mock_url_for
|
||||
attr_accessor :mock_path
|
||||
|
||||
|
||||
def initialize
|
||||
yield self if block_given?
|
||||
end
|
||||
|
||||
|
||||
def url_for(*args)
|
||||
@mock_url_for
|
||||
end
|
||||
|
||||
|
||||
def request
|
||||
mocked_path = @mock_path
|
||||
Object.new.instance_eval(<<-EVAL)
|
||||
@@ -71,95 +137,93 @@ class ActionCacheTest < Test::Unit::TestCase
|
||||
@path_class = ActionController::Caching::Actions::ActionCachePath
|
||||
@mock_controller = ActionCachingMockController.new
|
||||
end
|
||||
|
||||
|
||||
def teardown
|
||||
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
|
||||
end
|
||||
|
||||
|
||||
def test_simple_action_cache
|
||||
get :index
|
||||
cached_time = content_to_cache
|
||||
assert_equal cached_time, @response.body
|
||||
reset!
|
||||
|
||||
|
||||
get :index
|
||||
assert_equal cached_time, @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_cache_expiration
|
||||
get :index
|
||||
cached_time = content_to_cache
|
||||
reset!
|
||||
|
||||
|
||||
get :index
|
||||
assert_equal cached_time, @response.body
|
||||
reset!
|
||||
|
||||
get :expire
|
||||
reset!
|
||||
|
||||
|
||||
get :index
|
||||
new_cached_time = content_to_cache
|
||||
assert_not_equal cached_time, @response.body
|
||||
reset!
|
||||
|
||||
|
||||
get :index
|
||||
assert_response :success
|
||||
assert_equal new_cached_time, @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_cache_is_scoped_by_subdomain
|
||||
@request.host = 'jamis.hostname.com'
|
||||
get :index
|
||||
jamis_cache = content_to_cache
|
||||
|
||||
|
||||
@request.host = 'david.hostname.com'
|
||||
get :index
|
||||
david_cache = content_to_cache
|
||||
assert_not_equal jamis_cache, @response.body
|
||||
|
||||
|
||||
@request.host = 'jamis.hostname.com'
|
||||
get :index
|
||||
assert_equal jamis_cache, @response.body
|
||||
|
||||
|
||||
@request.host = 'david.hostname.com'
|
||||
get :index
|
||||
assert_equal david_cache, @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_xml_version_of_resource_is_treated_as_different_cache
|
||||
@mock_controller.mock_url_for = 'http://example.org/posts/'
|
||||
@mock_controller.mock_path = '/posts/index.xml'
|
||||
path_object = @path_class.new(@mock_controller)
|
||||
path_object = @path_class.new(@mock_controller, {})
|
||||
assert_equal 'xml', path_object.extension
|
||||
assert_equal 'example.org/posts/index.xml', path_object.path
|
||||
end
|
||||
|
||||
|
||||
def test_empty_path_is_normalized
|
||||
@mock_controller.mock_url_for = 'http://example.org/'
|
||||
@mock_controller.mock_path = '/'
|
||||
|
||||
assert_equal 'example.org/index', @path_class.path_for(@mock_controller)
|
||||
assert_equal 'example.org/index', @path_class.path_for(@mock_controller, {})
|
||||
end
|
||||
|
||||
|
||||
def test_file_extensions
|
||||
get :index, :id => 'kitten.jpg'
|
||||
get :index, :id => 'kitten.jpg'
|
||||
get :index, :id => 'kitten.jpg'
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def content_to_cache
|
||||
assigns(:cache_this)
|
||||
end
|
||||
|
||||
|
||||
def reset!
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
@controller = ActionCachingTestController.new
|
||||
@request.host = 'hostname.com'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,15 +7,15 @@ class CaptureController < ActionController::Base
|
||||
def content_for
|
||||
render :layout => "talk_from_action"
|
||||
end
|
||||
|
||||
|
||||
def erb_content_for
|
||||
render :layout => "talk_from_action"
|
||||
end
|
||||
|
||||
|
||||
def block_content_for
|
||||
render :layout => "talk_from_action"
|
||||
end
|
||||
|
||||
|
||||
def non_erb_block_content_for
|
||||
render :layout => "talk_from_action"
|
||||
end
|
||||
@@ -43,36 +43,38 @@ class CaptureTest < Test::Unit::TestCase
|
||||
get :capturing
|
||||
assert_equal "Dreamy days", @response.body.strip
|
||||
end
|
||||
|
||||
|
||||
def test_content_for
|
||||
get :content_for
|
||||
assert_equal expected_content_for_output, @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_erb_content_for
|
||||
get :content_for
|
||||
assert_equal expected_content_for_output, @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_block_content_for
|
||||
get :block_content_for
|
||||
assert_equal expected_content_for_output, @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_non_erb_block_content_for
|
||||
get :non_erb_block_content_for
|
||||
assert_equal expected_content_for_output, @response.body
|
||||
end
|
||||
|
||||
def test_update_element_with_capture
|
||||
get :update_element_with_capture
|
||||
assert_deprecated 'update_element_function' do
|
||||
get :update_element_with_capture
|
||||
end
|
||||
assert_equal(
|
||||
"<script type=\"text/javascript\">\n//<![CDATA[\n$('products').innerHTML = '\\n <p>Product 1</p>\\n <p>Product 2</p>\\n';\n\n//]]>\n</script>" +
|
||||
"\n\n$('status').innerHTML = '\\n <b>You bought something!</b>\\n';",
|
||||
"\n\n$('status').innerHTML = '\\n <b>You bought something!</b>\\n';",
|
||||
@response.body.strip
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def expected_content_for_output
|
||||
"<title>Putting stuff in the title!</title>\n\nGreat stuff!"
|
||||
|
||||
@@ -155,9 +155,10 @@ class CGITest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_parse_params_from_multipart_upload
|
||||
mockup = Struct.new(:content_type, :original_filename)
|
||||
mockup = Struct.new(:content_type, :original_filename, :read, :rewind)
|
||||
file = mockup.new('img/jpeg', 'foo.jpg')
|
||||
ie_file = mockup.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg')
|
||||
non_file_text_part = mockup.new('text/plain', '', 'abc')
|
||||
|
||||
input = {
|
||||
"something" => [ StringIO.new("") ],
|
||||
@@ -168,9 +169,10 @@ class CGITest < Test::Unit::TestCase
|
||||
"products[string]" => [ StringIO.new("Apple Computer") ],
|
||||
"products[file]" => [ file ],
|
||||
"ie_products[string]" => [ StringIO.new("Microsoft") ],
|
||||
"ie_products[file]" => [ ie_file ]
|
||||
"ie_products[file]" => [ ie_file ],
|
||||
"text_part" => [non_file_text_part]
|
||||
}
|
||||
|
||||
|
||||
expected_output = {
|
||||
"something" => "",
|
||||
"array_of_stringios" => ["One", "Two"],
|
||||
@@ -192,7 +194,8 @@ class CGITest < Test::Unit::TestCase
|
||||
"ie_products" => {
|
||||
"string" => "Microsoft",
|
||||
"file" => ie_file
|
||||
}
|
||||
},
|
||||
"text_part" => "abc"
|
||||
}
|
||||
|
||||
params = CGIMethods.parse_request_parameters(input)
|
||||
@@ -338,7 +341,7 @@ class MultipartCGITest < Test::Unit::TestCase
|
||||
assert_equal 'bar', params['foo']
|
||||
|
||||
# Ruby CGI doesn't handle multipart/mixed for us.
|
||||
assert_kind_of StringIO, params['files']
|
||||
assert_kind_of String, params['files']
|
||||
assert_equal 19756, params['files'].size
|
||||
end
|
||||
|
||||
@@ -422,4 +425,16 @@ class CGIRequestTest < Test::Unit::TestCase
|
||||
assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], alt_cookies["_session_id"]
|
||||
assert_equal ["yes"], alt_cookies["is_admin"]
|
||||
end
|
||||
|
||||
def test_unbalanced_query_string_with_array
|
||||
assert_equal(
|
||||
{'location' => ["1", "2"], 'age_group' => ["2"]},
|
||||
CGIMethods.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")
|
||||
)
|
||||
assert_equal(
|
||||
{'location' => ["1", "2"], 'age_group' => ["2"]},
|
||||
CGIMethods.parse_request_parameters({'location[]' => ["1", "2"],
|
||||
'age_group[]' => ["2"]})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -134,7 +134,8 @@ module A
|
||||
class NestedController < ActionController::Base
|
||||
# Stub for uses_component_template_root
|
||||
def self.caller
|
||||
['./test/fixtures/a/b/c/nested_controller.rb']
|
||||
[ '/path/to/active_support/deprecation.rb:93:in `uses_component_template_root',
|
||||
'./test/fixtures/a/b/c/nested_controller.rb' ]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -143,6 +144,8 @@ end
|
||||
|
||||
class UsesComponentTemplateRootTest < Test::Unit::TestCase
|
||||
def test_uses_component_template_root
|
||||
assert_equal './test/fixtures/', A::B::C::NestedController.uses_component_template_root
|
||||
assert_deprecated 'uses_component_template_root' do
|
||||
assert_equal './test/fixtures/', A::B::C::NestedController.uses_component_template_root
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,77 +4,96 @@ class CookieTest < Test::Unit::TestCase
|
||||
class TestController < ActionController::Base
|
||||
def authenticate_with_deprecated_writer
|
||||
cookie "name" => "user_name", "value" => "david"
|
||||
render_text "hello world"
|
||||
end
|
||||
|
||||
def authenticate
|
||||
cookies["user_name"] = "david"
|
||||
render_text "hello world"
|
||||
end
|
||||
|
||||
def authenticate_for_fourten_days
|
||||
cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
|
||||
render_text "hello world"
|
||||
end
|
||||
|
||||
def authenticate_for_fourten_days_with_symbols
|
||||
cookies[:user_name] = { :value => "david", :expires => Time.local(2005, 10, 10) }
|
||||
render_text "hello world"
|
||||
end
|
||||
|
||||
def set_multiple_cookies
|
||||
cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
|
||||
cookies["login"] = "XJ-122"
|
||||
render_text "hello world"
|
||||
end
|
||||
|
||||
def access_frozen_cookies
|
||||
cookies["will"] = "work"
|
||||
end
|
||||
|
||||
def logout
|
||||
cookies.delete("user_name")
|
||||
end
|
||||
|
||||
def delete_cookie_with_path
|
||||
cookies.delete("user_name", :path => '/beaten')
|
||||
render_text "hello world"
|
||||
end
|
||||
|
||||
def rescue_action(e) raise end
|
||||
def rescue_action(e)
|
||||
raise unless ActionController::MissingTemplate # No templates here, and we don't care about the output
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
||||
@controller = TestController.new
|
||||
@request.host = "www.nextangle.com"
|
||||
end
|
||||
|
||||
def test_setting_cookie_with_deprecated_writer
|
||||
@request.action = "authenticate_with_deprecated_writer"
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], process_request.headers["cookie"]
|
||||
get :authenticate_with_deprecated_writer
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], @response.headers["cookie"]
|
||||
end
|
||||
|
||||
def test_setting_cookie
|
||||
@request.action = "authenticate"
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], process_request.headers["cookie"]
|
||||
get :authenticate
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], @response.headers["cookie"]
|
||||
end
|
||||
|
||||
def test_setting_cookie_for_fourteen_days
|
||||
@request.action = "authenticate_for_fourten_days"
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], process_request.headers["cookie"]
|
||||
get :authenticate_for_fourten_days
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"]
|
||||
end
|
||||
|
||||
def test_setting_cookie_for_fourteen_days_with_symbols
|
||||
@request.action = "authenticate_for_fourten_days"
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], process_request.headers["cookie"]
|
||||
get :authenticate_for_fourten_days
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"]
|
||||
end
|
||||
|
||||
def test_multiple_cookies
|
||||
@request.action = "set_multiple_cookies"
|
||||
assert_equal 2, process_request.headers["cookie"].size
|
||||
get :set_multiple_cookies
|
||||
assert_equal 2, @response.cookies.size
|
||||
end
|
||||
|
||||
def test_setting_test_cookie
|
||||
@request.action = "access_frozen_cookies"
|
||||
assert_nothing_raised { process_request }
|
||||
assert_nothing_raised { get :access_frozen_cookies }
|
||||
end
|
||||
|
||||
def test_expiring_cookie
|
||||
get :logout
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)) ], @response.headers["cookie"]
|
||||
end
|
||||
|
||||
def test_cookiejar_accessor
|
||||
@request.cookies["user_name"] = CGI::Cookie.new("name" => "user_name", "value" => "david", "expires" => Time.local(2025, 10, 10))
|
||||
@controller.request = @request
|
||||
jar = ActionController::CookieJar.new(@controller)
|
||||
assert_equal "david", jar["user_name"]
|
||||
assert_equal nil, jar["something_else"]
|
||||
end
|
||||
|
||||
private
|
||||
def process_request
|
||||
TestController.process(@request, @response)
|
||||
end
|
||||
def test_delete_cookie_with_path
|
||||
get :delete_cookie_with_path
|
||||
assert_equal "/beaten", @response.headers["cookie"].first.path
|
||||
assert_not_equal "/", @response.headers["cookie"].first.path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
require File.dirname(__FILE__) + '/../../abstract_unit'
|
||||
|
||||
class DeprecatedBaseMethodsTest < Test::Unit::TestCase
|
||||
# ActiveRecord model mock to test pagination deprecation
|
||||
class DummyModel
|
||||
def self.find(*args) [] end
|
||||
def self.count(*args) 0 end
|
||||
end
|
||||
|
||||
class Target < ActionController::Base
|
||||
def deprecated_symbol_parameter_to_url_for
|
||||
redirect_to(url_for(:home_url, "superstars"))
|
||||
@@ -14,6 +20,15 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
|
||||
"http://example.com/#{greeting}"
|
||||
end
|
||||
|
||||
def raises_name_error
|
||||
this_method_doesnt_exist
|
||||
end
|
||||
|
||||
def pagination
|
||||
paginate :dummy_models, :class_name => 'DeprecatedBaseMethodsTest::DummyModel'
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def rescue_action(e) raise e end
|
||||
end
|
||||
|
||||
@@ -23,6 +38,7 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
@controller = Target.new
|
||||
@controller.logger = Logger.new(nil) unless @controller.logger
|
||||
end
|
||||
|
||||
def test_deprecated_symbol_parameter_to_url_for
|
||||
@@ -40,4 +56,23 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
|
||||
|
||||
assert_equal "Living in a nested world", @response.body
|
||||
end
|
||||
|
||||
def test_log_error_silences_deprecation_warnings
|
||||
get :raises_name_error
|
||||
rescue => e
|
||||
assert_not_deprecated { @controller.send :log_error, e }
|
||||
end
|
||||
|
||||
def test_assertion_failed_error_silences_deprecation_warnings
|
||||
get :raises_name_error
|
||||
rescue => e
|
||||
error = Test::Unit::Error.new('testing ur doodz', e)
|
||||
assert_not_deprecated { error.message }
|
||||
end
|
||||
|
||||
def test_pagination_deprecation
|
||||
assert_deprecated('svn://errtheblog.com/svn/plugins/classic_pagination') do
|
||||
get :pagination
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,6 +16,7 @@ class FilterParamTest < Test::Unit::TestCase
|
||||
assert @controller.respond_to?(:filter_parameters)
|
||||
|
||||
test_hashes = [[{},{},[]],
|
||||
[{'foo'=>nil},{'foo'=>nil},[]],
|
||||
[{'foo'=>'bar'},{'foo'=>'bar'},[]],
|
||||
[{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
|
||||
[{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],
|
||||
|
||||
@@ -14,7 +14,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
@ran_filter ||= []
|
||||
@ran_filter << "ensure_login"
|
||||
end
|
||||
|
||||
|
||||
def clean_up
|
||||
@ran_after_filter ||= []
|
||||
@ran_after_filter << "clean_up"
|
||||
@@ -62,7 +62,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
render :inline => "something else"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class ConditionalFilterController < ActionController::Base
|
||||
def show
|
||||
render :inline => "ran action"
|
||||
@@ -86,7 +86,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
@ran_filter ||= []
|
||||
@ran_filter << "clean_up_tmp"
|
||||
end
|
||||
|
||||
|
||||
def rescue_action(e) raise(e) end
|
||||
end
|
||||
|
||||
@@ -94,7 +94,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
before_filter :ensure_login, :except => [ :show_without_filter, :another_action ]
|
||||
end
|
||||
|
||||
class OnlyConditionSymController < ConditionalFilterController
|
||||
class OnlyConditionSymController < ConditionalFilterController
|
||||
before_filter :ensure_login, :only => :show
|
||||
end
|
||||
|
||||
@@ -104,10 +104,10 @@ class FilterTest < Test::Unit::TestCase
|
||||
|
||||
class BeforeAndAfterConditionController < ConditionalFilterController
|
||||
before_filter :ensure_login, :only => :show
|
||||
after_filter :clean_up_tmp, :only => :show
|
||||
after_filter :clean_up_tmp, :only => :show
|
||||
end
|
||||
|
||||
class OnlyConditionProcController < ConditionalFilterController
|
||||
|
||||
class OnlyConditionProcController < ConditionalFilterController
|
||||
before_filter(:only => :show) {|c| c.assigns["ran_proc_filter"] = true }
|
||||
end
|
||||
|
||||
@@ -131,6 +131,14 @@ class FilterTest < Test::Unit::TestCase
|
||||
before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true}
|
||||
end
|
||||
|
||||
class EmptyFilterChainController < TestController
|
||||
self.filter_chain.clear
|
||||
def show
|
||||
@action_executed = true
|
||||
render :text => "yawp!"
|
||||
end
|
||||
end
|
||||
|
||||
class PrependingController < TestController
|
||||
prepend_before_filter :wonderful_life
|
||||
# skip_before_filter :fire_flash
|
||||
@@ -145,7 +153,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
class ConditionalSkippingController < TestController
|
||||
skip_before_filter :ensure_login, :only => [ :login ]
|
||||
skip_after_filter :clean_up, :only => [ :login ]
|
||||
|
||||
|
||||
before_filter :find_user, :only => [ :change_password ]
|
||||
|
||||
def login
|
||||
@@ -155,7 +163,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
def change_password
|
||||
render :inline => "ran action"
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
def find_user
|
||||
@ran_filter ||= []
|
||||
@@ -166,15 +174,15 @@ class FilterTest < Test::Unit::TestCase
|
||||
class ConditionalParentOfConditionalSkippingController < ConditionalFilterController
|
||||
before_filter :conditional_in_parent, :only => [:show, :another_action]
|
||||
after_filter :conditional_in_parent, :only => [:show, :another_action]
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
def conditional_in_parent
|
||||
@ran_filter ||= []
|
||||
@ran_filter << 'conditional_in_parent'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
|
||||
skip_before_filter :conditional_in_parent, :only => :another_action
|
||||
skip_after_filter :conditional_in_parent, :only => :another_action
|
||||
@@ -197,7 +205,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
controller.assigns["was_audited"] = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class AroundFilter
|
||||
def before(controller)
|
||||
@execution_log = "before"
|
||||
@@ -209,7 +217,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
controller.assigns["execution_log"] = @execution_log + " and after"
|
||||
controller.assigns["after_ran"] = true
|
||||
controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AppendedAroundFilter
|
||||
@@ -219,12 +227,12 @@ class FilterTest < Test::Unit::TestCase
|
||||
|
||||
def after(controller)
|
||||
controller.class.execution_log << " after appended aroundfilter "
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
class AuditController < ActionController::Base
|
||||
before_filter(AuditFilter)
|
||||
|
||||
|
||||
def show
|
||||
render_text "hello"
|
||||
end
|
||||
@@ -234,6 +242,14 @@ class FilterTest < Test::Unit::TestCase
|
||||
around_filter AroundFilter.new
|
||||
end
|
||||
|
||||
class BeforeAfterClassFilterController < PrependingController
|
||||
begin
|
||||
filter = AroundFilter.new
|
||||
before_filter filter
|
||||
after_filter filter
|
||||
end
|
||||
end
|
||||
|
||||
class MixedFilterController < PrependingController
|
||||
cattr_accessor :execution_log
|
||||
|
||||
@@ -247,7 +263,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
after_filter { |c| c.class.execution_log << " after procfilter " }
|
||||
append_around_filter AppendedAroundFilter.new
|
||||
end
|
||||
|
||||
|
||||
class MixedSpecializationController < ActionController::Base
|
||||
class OutOfOrder < StandardError; end
|
||||
|
||||
@@ -285,6 +301,101 @@ class FilterTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
class PrependingBeforeAndAfterController < ActionController::Base
|
||||
prepend_before_filter :before_all
|
||||
prepend_after_filter :after_all
|
||||
before_filter :between_before_all_and_after_all
|
||||
|
||||
def before_all
|
||||
@ran_filter ||= []
|
||||
@ran_filter << 'before_all'
|
||||
end
|
||||
|
||||
def after_all
|
||||
@ran_filter ||= []
|
||||
@ran_filter << 'after_all'
|
||||
end
|
||||
|
||||
def between_before_all_and_after_all
|
||||
@ran_filter ||= []
|
||||
@ran_filter << 'between_before_all_and_after_all'
|
||||
end
|
||||
def show
|
||||
render :text => 'hello'
|
||||
end
|
||||
end
|
||||
|
||||
class NonYieldingAroundFilterController < ActionController::Base
|
||||
|
||||
before_filter :filter_one
|
||||
around_filter :non_yielding_filter
|
||||
before_filter :filter_two
|
||||
after_filter :filter_three
|
||||
|
||||
def index
|
||||
render :inline => "index"
|
||||
end
|
||||
|
||||
#make sure the controller complains
|
||||
def rescue_action(e); raise e; end
|
||||
|
||||
private
|
||||
|
||||
def filter_one
|
||||
@filters ||= []
|
||||
@filters << "filter_one"
|
||||
end
|
||||
|
||||
def filter_two
|
||||
@filters << "filter_two"
|
||||
end
|
||||
|
||||
def non_yielding_filter
|
||||
@filters << "zomg it didn't yield"
|
||||
@filter_return_value
|
||||
end
|
||||
|
||||
def filter_three
|
||||
@filters << "filter_three"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def test_non_yielding_around_filters_not_returning_false_do_not_raise
|
||||
controller = NonYieldingAroundFilterController.new
|
||||
controller.instance_variable_set "@filter_return_value", true
|
||||
assert_nothing_raised do
|
||||
test_process(controller, "index")
|
||||
end
|
||||
end
|
||||
|
||||
def test_non_yielding_around_filters_returning_false_do_not_raise
|
||||
controller = NonYieldingAroundFilterController.new
|
||||
controller.instance_variable_set "@filter_return_value", false
|
||||
assert_nothing_raised do
|
||||
test_process(controller, "index")
|
||||
end
|
||||
end
|
||||
|
||||
def test_after_filters_are_not_run_if_around_filter_returns_false
|
||||
controller = NonYieldingAroundFilterController.new
|
||||
controller.instance_variable_set "@filter_return_value", false
|
||||
test_process(controller, "index")
|
||||
assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters']
|
||||
end
|
||||
|
||||
def test_after_filters_are_not_run_if_around_filter_does_not_yield
|
||||
controller = NonYieldingAroundFilterController.new
|
||||
controller.instance_variable_set "@filter_return_value", true
|
||||
test_process(controller, "index")
|
||||
assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters']
|
||||
end
|
||||
|
||||
def test_empty_filter_chain
|
||||
assert_equal 0, EmptyFilterChainController.filter_chain.size
|
||||
assert test_process(EmptyFilterChainController).template.assigns['action_executed']
|
||||
end
|
||||
|
||||
def test_added_filter_to_inheritance_graph
|
||||
assert_equal [ :ensure_login ], TestController.before_filters
|
||||
end
|
||||
@@ -292,11 +403,11 @@ class FilterTest < Test::Unit::TestCase
|
||||
def test_base_class_in_isolation
|
||||
assert_equal [ ], ActionController::Base.before_filters
|
||||
end
|
||||
|
||||
|
||||
def test_prepending_filter
|
||||
assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_filters
|
||||
end
|
||||
|
||||
|
||||
def test_running_filters
|
||||
assert_equal %w( wonderful_life ensure_login ), test_process(PrependingController).template.assigns["ran_filter"]
|
||||
end
|
||||
@@ -304,11 +415,11 @@ class FilterTest < Test::Unit::TestCase
|
||||
def test_running_filters_with_proc
|
||||
assert test_process(ProcController).template.assigns["ran_proc_filter"]
|
||||
end
|
||||
|
||||
|
||||
def test_running_filters_with_implicit_proc
|
||||
assert test_process(ImplicitProcController).template.assigns["ran_proc_filter"]
|
||||
end
|
||||
|
||||
|
||||
def test_running_filters_with_class
|
||||
assert test_process(AuditController).template.assigns["was_audited"]
|
||||
end
|
||||
@@ -319,7 +430,7 @@ class FilterTest < Test::Unit::TestCase
|
||||
assert response.template.assigns["ran_class_filter"]
|
||||
assert response.template.assigns["ran_proc_filter1"]
|
||||
assert response.template.assigns["ran_proc_filter2"]
|
||||
|
||||
|
||||
response = test_process(AnomolousYetValidConditionController, "show_without_filter")
|
||||
assert_equal nil, response.template.assigns["ran_filter"]
|
||||
assert !response.template.assigns["ran_class_filter"]
|
||||
@@ -373,6 +484,12 @@ class FilterTest < Test::Unit::TestCase
|
||||
assert controller.template.assigns["after_ran"]
|
||||
end
|
||||
|
||||
def test_before_after_class_filter
|
||||
controller = test_process(BeforeAfterClassFilterController)
|
||||
assert controller.template.assigns["before_ran"]
|
||||
assert controller.template.assigns["after_ran"]
|
||||
end
|
||||
|
||||
def test_having_properties_in_around_filter
|
||||
controller = test_process(AroundFilterController)
|
||||
assert_equal "before and after", controller.template.assigns["execution_log"]
|
||||
@@ -381,10 +498,10 @@ class FilterTest < Test::Unit::TestCase
|
||||
def test_prepending_and_appending_around_filter
|
||||
controller = test_process(MixedFilterController)
|
||||
assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
|
||||
" after appended aroundfilter after aroundfilter after procfilter ",
|
||||
" after appended aroundfilter after aroundfilter after procfilter ",
|
||||
MixedFilterController.execution_log
|
||||
end
|
||||
|
||||
|
||||
def test_rendering_breaks_filtering_chain
|
||||
response = test_process(RenderingController)
|
||||
assert_equal "something else", response.body
|
||||
@@ -412,6 +529,12 @@ class FilterTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_running_prepended_before_and_after_filter
|
||||
assert_equal 3, PrependingBeforeAndAfterController.filter_chain.length
|
||||
response = test_process(PrependingBeforeAndAfterController)
|
||||
assert_equal %w( before_all between_before_all_and_after_all after_all ), response.template.assigns["ran_filter"]
|
||||
end
|
||||
|
||||
def test_conditional_skipping_of_filters
|
||||
assert_nil test_process(ConditionalSkippingController, "login").template.assigns["ran_filter"]
|
||||
assert_equal %w( ensure_login find_user ), test_process(ConditionalSkippingController, "change_password").template.assigns["ran_filter"]
|
||||
|
||||
@@ -11,7 +11,7 @@ require 'stubba'
|
||||
module ActionController
|
||||
module Integration
|
||||
class Session
|
||||
def process
|
||||
def process(*args)
|
||||
end
|
||||
|
||||
def generic_url_rewriter
|
||||
@@ -56,8 +56,7 @@ class SessionTest < Test::Unit::TestCase
|
||||
|
||||
@session.expects(:get).with(path,args)
|
||||
|
||||
redirects = [true, true, false]
|
||||
@session.stubs(:redirect?).returns(lambda { redirects.shift })
|
||||
@session.stubs(:redirect?).returns(true).then.returns(true).then.returns(false)
|
||||
@session.expects(:follow_redirect!).times(2)
|
||||
|
||||
@session.stubs(:status).returns(200)
|
||||
@@ -69,8 +68,7 @@ class SessionTest < Test::Unit::TestCase
|
||||
|
||||
@session.expects(:post).with(path,args)
|
||||
|
||||
redirects = [true, true, false]
|
||||
@session.stubs(:redirect?).returns(lambda { redirects.shift })
|
||||
@session.stubs(:redirect?).returns(true).then.returns(true).then.returns(false)
|
||||
@session.expects(:follow_redirect!).times(2)
|
||||
|
||||
@session.stubs(:status).returns(200)
|
||||
@@ -134,15 +132,102 @@ class SessionTest < Test::Unit::TestCase
|
||||
@session.head(path,params,headers)
|
||||
end
|
||||
|
||||
def test_xml_http_request
|
||||
def test_xml_http_request_deprecated_call
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
headers_after_xhr = headers.merge(
|
||||
"X-Requested-With" => "XMLHttpRequest",
|
||||
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
|
||||
)
|
||||
@session.expects(:post).with(path,params,headers_after_xhr)
|
||||
@session.xml_http_request(path,params,headers)
|
||||
@session.expects(:process).with(:post,path,params,headers_after_xhr)
|
||||
assert_deprecated { @session.xml_http_request(path,params,headers) }
|
||||
end
|
||||
|
||||
def test_xml_http_request_get
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
headers_after_xhr = headers.merge(
|
||||
"X-Requested-With" => "XMLHttpRequest",
|
||||
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
|
||||
)
|
||||
@session.expects(:process).with(:get,path,params,headers_after_xhr)
|
||||
@session.xml_http_request(:get,path,params,headers)
|
||||
end
|
||||
|
||||
def test_xml_http_request_post
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
headers_after_xhr = headers.merge(
|
||||
"X-Requested-With" => "XMLHttpRequest",
|
||||
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
|
||||
)
|
||||
@session.expects(:process).with(:post,path,params,headers_after_xhr)
|
||||
@session.xml_http_request(:post,path,params,headers)
|
||||
end
|
||||
|
||||
def test_xml_http_request_put
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
headers_after_xhr = headers.merge(
|
||||
"X-Requested-With" => "XMLHttpRequest",
|
||||
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
|
||||
)
|
||||
@session.expects(:process).with(:put,path,params,headers_after_xhr)
|
||||
@session.xml_http_request(:put,path,params,headers)
|
||||
end
|
||||
|
||||
def test_xml_http_request_delete
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
headers_after_xhr = headers.merge(
|
||||
"X-Requested-With" => "XMLHttpRequest",
|
||||
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
|
||||
)
|
||||
@session.expects(:process).with(:delete,path,params,headers_after_xhr)
|
||||
@session.xml_http_request(:delete,path,params,headers)
|
||||
end
|
||||
|
||||
def test_xml_http_request_head
|
||||
path = "/index"; params = "blah"; headers = {:location => 'blah'}
|
||||
headers_after_xhr = headers.merge(
|
||||
"X-Requested-With" => "XMLHttpRequest",
|
||||
"Accept" => "text/javascript, text/html, application/xml, text/xml, */*"
|
||||
)
|
||||
@session.expects(:process).with(:head,path,params,headers_after_xhr)
|
||||
@session.xml_http_request(:head,path,params,headers)
|
||||
end
|
||||
end
|
||||
|
||||
class IntegrationTestTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
@test = ::ActionController::IntegrationTest.new(:default_test)
|
||||
@test.class.stubs(:fixture_table_names).returns([])
|
||||
@session = @test.open_session
|
||||
end
|
||||
|
||||
def test_opens_new_session
|
||||
@test.class.expects(:fixture_table_names).times(2).returns(['foo'])
|
||||
|
||||
session1 = @test.open_session { |sess| }
|
||||
session2 = @test.open_session # implicit session
|
||||
|
||||
assert_equal ::ActionController::Integration::Session, session1.class
|
||||
assert_equal ::ActionController::Integration::Session, session2.class
|
||||
assert_not_equal session1, session2
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Tests that integration tests don't call Controller test methods for processing.
|
||||
# Integration tests have their own setup and teardown.
|
||||
class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest
|
||||
|
||||
def self.fixture_table_names
|
||||
[]
|
||||
end
|
||||
|
||||
def test_integration_methods_called
|
||||
%w( get post head put delete ).each do |verb|
|
||||
assert_nothing_raised("'#{verb}' should use integration test methods") { send(verb, '/') }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# TODO
|
||||
|
||||
@@ -75,6 +75,8 @@ end
|
||||
class ExemptFromLayoutTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = LayoutTest.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
end
|
||||
|
||||
def test_rjs_exempt_from_layout
|
||||
@@ -103,6 +105,15 @@ class ExemptFromLayoutTest < Test::Unit::TestCase
|
||||
ActionController::Base.exempt_from_layout /\.rdoc/
|
||||
assert @controller.send(:template_exempt_from_layout?, 'test.rdoc')
|
||||
end
|
||||
|
||||
def test_rhtml_exempt_from_layout_status_should_prevent_layout_render
|
||||
ActionController::Base.exempt_from_layout :rhtml
|
||||
assert @controller.send(:template_exempt_from_layout?, 'test.rhtml')
|
||||
|
||||
get :hello
|
||||
assert_equal 'hello.rhtml', @response.body
|
||||
ActionController::Base.exempt_from_layout.delete(/\.rhtml$/)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,13 @@ class RespondToController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
def json_or_yaml
|
||||
respond_to do |type|
|
||||
type.json { render :text => "JSON" }
|
||||
type.yaml { render :text => "YAML" }
|
||||
end
|
||||
end
|
||||
|
||||
def html_or_xml
|
||||
respond_to do |type|
|
||||
type.html { render :text => "HTML" }
|
||||
@@ -164,6 +171,27 @@ class MimeControllerTest < Test::Unit::TestCase
|
||||
assert_response 406
|
||||
end
|
||||
|
||||
def test_json_or_yaml
|
||||
get :json_or_yaml
|
||||
assert_equal 'JSON', @response.body
|
||||
|
||||
get :json_or_yaml, :format => 'json'
|
||||
assert_equal 'JSON', @response.body
|
||||
|
||||
get :json_or_yaml, :format => 'yaml'
|
||||
assert_equal 'YAML', @response.body
|
||||
|
||||
{ 'YAML' => %w(text/yaml),
|
||||
'JSON' => %w(application/json text/x-json)
|
||||
}.each do |body, content_types|
|
||||
content_types.each do |content_type|
|
||||
@request.env['HTTP_ACCEPT'] = content_type
|
||||
get :json_or_yaml
|
||||
assert_equal body, @response.body
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_js_or_anything
|
||||
@request.env["HTTP_ACCEPT"] = "text/javascript, */*"
|
||||
get :js_or_html
|
||||
|
||||
@@ -160,6 +160,33 @@ class NewRenderTestController < ActionController::Base
|
||||
@customers = [ Customer.new("david"), Customer.new("mary") ]
|
||||
render :text => "How's there? #{render_to_string("test/list")}"
|
||||
end
|
||||
|
||||
def render_to_string_with_assigns
|
||||
@before = "i'm before the render"
|
||||
render_to_string :text => "foo"
|
||||
@after = "i'm after the render"
|
||||
render :action => "test/hello_world"
|
||||
end
|
||||
|
||||
def render_to_string_with_partial
|
||||
@partial_only = render_to_string :partial => "partial_only"
|
||||
@partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") }
|
||||
render :action => "test/hello_world"
|
||||
end
|
||||
|
||||
def render_to_string_with_exception
|
||||
render_to_string :file => "exception that will not be caught - this will certainly not work", :use_full_path => true
|
||||
end
|
||||
|
||||
def render_to_string_with_caught_exception
|
||||
@before = "i'm before the render"
|
||||
begin
|
||||
render_to_string :file => "exception that will be caught- hope my future instance vars still work!", :use_full_path => true
|
||||
rescue
|
||||
end
|
||||
@after = "i'm after the render"
|
||||
render :action => "test/hello_world"
|
||||
end
|
||||
|
||||
def accessing_params_in_template
|
||||
render :inline => "Hello: <%= params[:name] %>"
|
||||
@@ -187,6 +214,11 @@ class NewRenderTestController < ActionController::Base
|
||||
render :text => "hello"
|
||||
redirect_to :action => "double_render"
|
||||
end
|
||||
|
||||
def render_to_string_and_render
|
||||
@stuff = render_to_string :text => "here is some cached stuff"
|
||||
render :text => "Hi web users! #{@stuff}"
|
||||
end
|
||||
|
||||
def rendering_with_conflicting_local_vars
|
||||
@name = "David"
|
||||
@@ -523,6 +555,28 @@ EOS
|
||||
assert_not_deprecated { get :hello_in_a_string }
|
||||
assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
|
||||
end
|
||||
|
||||
def test_render_to_string_doesnt_break_assigns
|
||||
get :render_to_string_with_assigns
|
||||
assert_equal "i'm before the render", assigns(:before)
|
||||
assert_equal "i'm after the render", assigns(:after)
|
||||
end
|
||||
|
||||
def test_render_to_string_partial
|
||||
get :render_to_string_with_partial
|
||||
assert_equal "only partial", assigns(:partial_only)
|
||||
assert_equal "Hello: david", assigns(:partial_with_locals)
|
||||
end
|
||||
|
||||
def test_bad_render_to_string_still_throws_exception
|
||||
assert_raises(ActionController::MissingTemplate) { get :render_to_string_with_exception }
|
||||
end
|
||||
|
||||
def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns
|
||||
assert_nothing_raised { get :render_to_string_with_caught_exception }
|
||||
assert_equal "i'm before the render", assigns(:before)
|
||||
assert_equal "i'm after the render", assigns(:after)
|
||||
end
|
||||
|
||||
def test_nested_rendering
|
||||
get :hello_world
|
||||
@@ -555,6 +609,12 @@ EOS
|
||||
def test_render_and_redirect
|
||||
assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
|
||||
end
|
||||
|
||||
# specify the one exception to double render rule - render_to_string followed by render
|
||||
def test_render_to_string_and_render
|
||||
get :render_to_string_and_render
|
||||
assert_equal("Hi web users! here is some cached stuff", @response.body)
|
||||
end
|
||||
|
||||
def test_rendering_with_conflicting_local_vars
|
||||
get :rendering_with_conflicting_local_vars
|
||||
@@ -640,24 +700,29 @@ EOS
|
||||
get :head_with_location_header
|
||||
assert @response.body.blank?
|
||||
assert_equal "/foo", @response.headers["Location"]
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
def test_head_with_custom_header
|
||||
get :head_with_custom_header
|
||||
assert @response.body.blank?
|
||||
assert_equal "something", @response.headers["X-Custom-Header"]
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
def test_head_with_symbolic_status
|
||||
get :head_with_symbolic_status, :status => "ok"
|
||||
assert_equal "200 OK", @response.headers["Status"]
|
||||
assert_response :ok
|
||||
|
||||
get :head_with_symbolic_status, :status => "not_found"
|
||||
assert_equal "404 Not Found", @response.headers["Status"]
|
||||
assert_response :not_found
|
||||
|
||||
ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.each do |status, code|
|
||||
get :head_with_symbolic_status, :status => status.to_s
|
||||
assert_equal code, @response.response_code
|
||||
assert_response status
|
||||
end
|
||||
end
|
||||
|
||||
@@ -672,6 +737,7 @@ EOS
|
||||
get :head_with_string_status, :status => "404 Eat Dirt"
|
||||
assert_equal 404, @response.response_code
|
||||
assert_equal "Eat Dirt", @response.message
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
def test_head_with_status_code_first
|
||||
@@ -679,5 +745,6 @@ EOS
|
||||
assert_equal 403, @response.response_code
|
||||
assert_equal "Forbidden", @response.message
|
||||
assert_equal "something", @response.headers["X-Custom-Header"]
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,6 +39,18 @@ class TestController < ActionController::Base
|
||||
render_text "hello world"
|
||||
end
|
||||
|
||||
def render_json_hello_world
|
||||
render_json({:hello => 'world'}.to_json)
|
||||
end
|
||||
|
||||
def render_json_hello_world_with_callback
|
||||
render_json({:hello => 'world'}.to_json, 'alert')
|
||||
end
|
||||
|
||||
def render_symbol_json
|
||||
render :json => {:hello => 'world'}.to_json
|
||||
end
|
||||
|
||||
def render_custom_code
|
||||
render_text "hello world", "404 Moved"
|
||||
end
|
||||
@@ -117,6 +129,7 @@ class TestController < ActionController::Base
|
||||
case action_name
|
||||
when "layout_test": "layouts/standard"
|
||||
when "builder_layout_test": "layouts/builder"
|
||||
when "render_symbol_json": "layouts/standard" # to make sure layouts don't interfere
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -164,6 +177,24 @@ class RenderTest < Test::Unit::TestCase
|
||||
assert_equal "hello world", @response.body
|
||||
end
|
||||
|
||||
def test_do_with_render_json
|
||||
get :render_json_hello_world
|
||||
assert_equal '{hello: "world"}', @response.body
|
||||
assert_equal 'application/json', @response.content_type
|
||||
end
|
||||
|
||||
def test_do_with_render_json_with_callback
|
||||
get :render_json_hello_world_with_callback
|
||||
assert_equal 'alert({hello: "world"})', @response.body
|
||||
assert_equal 'application/json', @response.content_type
|
||||
end
|
||||
|
||||
def test_do_with_render_symbol_json
|
||||
get :render_symbol_json
|
||||
assert_equal '{hello: "world"}', @response.body
|
||||
assert_equal 'application/json', @response.content_type
|
||||
end
|
||||
|
||||
def test_do_with_render_custom_code
|
||||
get :render_custom_code
|
||||
assert_response 404
|
||||
|
||||
@@ -174,48 +174,54 @@ class RequestTest < Test::Unit::TestCase
|
||||
assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
|
||||
assert_equal "/path/of/some/uri", @request.path
|
||||
|
||||
@request.set_REQUEST_URI nil
|
||||
@request.relative_url_root = nil
|
||||
@request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
|
||||
@request.env['SCRIPT_NAME'] = "/path/dispatch.rb"
|
||||
assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
|
||||
assert_equal "/of/some/uri", @request.path
|
||||
|
||||
@request.set_REQUEST_URI nil
|
||||
@request.relative_url_root = nil
|
||||
@request.env['PATH_INFO'] = "/path/of/some/uri"
|
||||
@request.env['SCRIPT_NAME'] = nil
|
||||
assert_equal "/path/of/some/uri", @request.request_uri
|
||||
assert_equal "/path/of/some/uri", @request.path
|
||||
|
||||
@request.set_REQUEST_URI nil
|
||||
@request.relative_url_root = nil
|
||||
@request.env['PATH_INFO'] = "/"
|
||||
assert_equal "/", @request.request_uri
|
||||
assert_equal "/", @request.path
|
||||
|
||||
@request.set_REQUEST_URI nil
|
||||
@request.relative_url_root = nil
|
||||
@request.env['PATH_INFO'] = "/?m=b"
|
||||
assert_equal "/?m=b", @request.request_uri
|
||||
assert_equal "/", @request.path
|
||||
|
||||
|
||||
@request.set_REQUEST_URI nil
|
||||
@request.relative_url_root = nil
|
||||
@request.env['PATH_INFO'] = "/"
|
||||
@request.env['SCRIPT_NAME'] = "/dispatch.cgi"
|
||||
assert_equal "/", @request.request_uri
|
||||
assert_equal "/", @request.path
|
||||
assert_equal "/", @request.path
|
||||
|
||||
@request.set_REQUEST_URI nil
|
||||
@request.relative_url_root = nil
|
||||
@request.env['PATH_INFO'] = "/hieraki/"
|
||||
@request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
|
||||
assert_equal "/hieraki/", @request.request_uri
|
||||
assert_equal "/", @request.path
|
||||
|
||||
assert_equal "/", @request.path
|
||||
|
||||
@request.set_REQUEST_URI '/hieraki/dispatch.cgi'
|
||||
@request.relative_url_root = '/hieraki'
|
||||
assert_equal "/dispatch.cgi", @request.path
|
||||
assert_equal "/dispatch.cgi", @request.path
|
||||
@request.relative_url_root = nil
|
||||
|
||||
@request.set_REQUEST_URI '/hieraki/dispatch.cgi'
|
||||
@request.relative_url_root = '/foo'
|
||||
assert_equal "/hieraki/dispatch.cgi", @request.path
|
||||
assert_equal "/hieraki/dispatch.cgi", @request.path
|
||||
@request.relative_url_root = nil
|
||||
|
||||
# This test ensures that Rails uses REQUEST_URI over PATH_INFO
|
||||
@@ -226,7 +232,7 @@ class RequestTest < Test::Unit::TestCase
|
||||
assert_equal "/some/path", @request.request_uri
|
||||
assert_equal "/some/path", @request.path
|
||||
end
|
||||
|
||||
|
||||
|
||||
def test_host_with_port
|
||||
@request.host = "rubyonrails.org"
|
||||
@@ -274,7 +280,7 @@ class RequestTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_symbolized_request_methods
|
||||
[:head, :get, :post, :put, :delete].each do |method|
|
||||
[:get, :post, :put, :delete].each do |method|
|
||||
set_request_method_to method
|
||||
assert_equal method, @request.method
|
||||
end
|
||||
@@ -282,7 +288,7 @@ class RequestTest < Test::Unit::TestCase
|
||||
|
||||
def test_allow_method_hacking_on_post
|
||||
set_request_method_to :post
|
||||
[:head, :get, :put, :delete].each do |method|
|
||||
[:get, :put, :delete].each do |method|
|
||||
@request.instance_eval { @parameters = { :_method => method } ; @request_method = nil }
|
||||
assert_equal method, @request.method
|
||||
end
|
||||
@@ -290,11 +296,18 @@ class RequestTest < Test::Unit::TestCase
|
||||
|
||||
def test_restrict_method_hacking
|
||||
@request.instance_eval { @parameters = { :_method => 'put' } }
|
||||
[:head, :get, :put, :delete].each do |method|
|
||||
[:get, :put, :delete].each do |method|
|
||||
set_request_method_to method
|
||||
assert_equal method, @request.method
|
||||
end
|
||||
end
|
||||
|
||||
def test_head_masquarading_as_get
|
||||
set_request_method_to :head
|
||||
assert_equal :get, @request.method
|
||||
assert @request.get?
|
||||
assert @request.head?
|
||||
end
|
||||
|
||||
protected
|
||||
def set_request_method_to(method)
|
||||
|
||||
@@ -2,6 +2,7 @@ require File.dirname(__FILE__) + '/../abstract_unit'
|
||||
|
||||
class ResourcesController < ActionController::Base
|
||||
def index() render :nothing => true end
|
||||
alias_method :show, :index
|
||||
def rescue_action(e) raise e end
|
||||
end
|
||||
|
||||
@@ -9,6 +10,9 @@ class ThreadsController < ResourcesController; end
|
||||
class MessagesController < ResourcesController; end
|
||||
class CommentsController < ResourcesController; end
|
||||
|
||||
class AccountController < ResourcesController; end
|
||||
class AdminController < ResourcesController; end
|
||||
class ProductsController < ResourcesController; end
|
||||
|
||||
class ResourcesTest < Test::Unit::TestCase
|
||||
def test_should_arrange_actions
|
||||
@@ -24,6 +28,23 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
assert_resource_methods [:new, :preview, :draft], resource, :new, :get
|
||||
end
|
||||
|
||||
def test_should_resource_controller_name_equal_resource_name_by_default
|
||||
resource = ActionController::Resources::Resource.new(:messages, {})
|
||||
assert_equal 'messages', resource.controller
|
||||
end
|
||||
|
||||
def test_should_resource_controller_name_equal_controller_option
|
||||
resource = ActionController::Resources::Resource.new(:messages, :controller => 'posts')
|
||||
assert_equal 'posts', resource.controller
|
||||
end
|
||||
|
||||
def test_should_all_singleton_paths_be_the_same
|
||||
[ :path, :nesting_path_prefix, :member_path ].each do |method|
|
||||
resource = ActionController::Resources::SingletonResource.new(:messages, :path_prefix => 'admin')
|
||||
assert_equal 'admin/messages', resource.send(method)
|
||||
end
|
||||
end
|
||||
|
||||
def test_default_restful_routes
|
||||
with_restful_routing :messages do
|
||||
assert_simply_restful_for :messages
|
||||
@@ -43,16 +64,22 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_multile_with_path_prefix
|
||||
def test_multiple_with_path_prefix
|
||||
with_restful_routing :messages, :comments, :path_prefix => '/thread/:thread_id' do
|
||||
assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
|
||||
assert_simply_restful_for :comments, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
|
||||
end
|
||||
end
|
||||
|
||||
def test_with_name_prefix
|
||||
with_restful_routing :messages, :name_prefix => 'post_' do
|
||||
assert_simply_restful_for :messages, :name_prefix => 'post_'
|
||||
end
|
||||
end
|
||||
|
||||
def test_with_collection_action
|
||||
rss_options = {:action => 'rss'}
|
||||
rss_path = "/messages;rss"
|
||||
rss_path = "/messages/rss"
|
||||
actions = { 'a' => :put, 'b' => :post, 'c' => :delete }
|
||||
|
||||
with_restful_routing :messages, :collection => { :rss => :get }.merge(actions) do
|
||||
@@ -60,14 +87,14 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
assert_routing rss_path, options.merge(rss_options)
|
||||
|
||||
actions.each do |action, method|
|
||||
assert_recognizes(options.merge(:action => action), :path => "/messages;#{action}", :method => method)
|
||||
assert_recognizes(options.merge(:action => action), :path => "/messages/#{action}", :method => method)
|
||||
end
|
||||
end
|
||||
|
||||
assert_restful_named_routes_for :messages do |options|
|
||||
assert_named_route rss_path, :rss_messages_path, rss_options
|
||||
actions.keys.each do |action|
|
||||
assert_named_route "/messages;#{action}", "#{action}_messages_path", :action => action
|
||||
assert_named_route "/messages/#{action}", "#{action}_messages_path", :action => action
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -77,7 +104,7 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
[:put, :post].each do |method|
|
||||
with_restful_routing :messages, :member => { :mark => method } do
|
||||
mark_options = {:action => 'mark', :id => '1'}
|
||||
mark_path = "/messages/1;mark"
|
||||
mark_path = "/messages/1/mark"
|
||||
assert_restful_routes_for :messages do |options|
|
||||
assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method)
|
||||
end
|
||||
@@ -94,7 +121,7 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
with_restful_routing :messages, :member => { :mark => method, :unmark => method } do
|
||||
%w(mark unmark).each do |action|
|
||||
action_options = {:action => action, :id => '1'}
|
||||
action_path = "/messages/1;#{action}"
|
||||
action_path = "/messages/1/#{action}"
|
||||
assert_restful_routes_for :messages do |options|
|
||||
assert_recognizes(options.merge(action_options), :path => action_path, :method => method)
|
||||
end
|
||||
@@ -107,11 +134,10 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_with_new_action
|
||||
with_restful_routing :messages, :new => { :preview => :post } do
|
||||
preview_options = {:action => 'preview'}
|
||||
preview_path = "/messages/new;preview"
|
||||
preview_path = "/messages/new/preview"
|
||||
assert_restful_routes_for :messages do |options|
|
||||
assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post)
|
||||
end
|
||||
@@ -153,9 +179,11 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
assert_simply_restful_for :threads
|
||||
assert_simply_restful_for :messages,
|
||||
:path_prefix => 'threads/1/',
|
||||
:name_prefix => 'thread_',
|
||||
:options => { :thread_id => '1' }
|
||||
assert_simply_restful_for :comments,
|
||||
:path_prefix => 'threads/1/messages/2/',
|
||||
:name_prefix => 'thread_message_',
|
||||
:options => { :thread_id => '1', :message_id => '2' }
|
||||
end
|
||||
end
|
||||
@@ -172,6 +200,296 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_create_singleton_resource_routes
|
||||
with_singleton_resources :account do
|
||||
assert_singleton_restful_for :account
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_create_multiple_singleton_resource_routes
|
||||
with_singleton_resources :account, :admin do
|
||||
assert_singleton_restful_for :account
|
||||
assert_singleton_restful_for :admin
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_create_nested_singleton_resource_routes
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resource :admin do |admin|
|
||||
admin.resource :account
|
||||
end
|
||||
end
|
||||
|
||||
assert_singleton_restful_for :admin
|
||||
assert_singleton_restful_for :account, :path_prefix => 'admin/', :name_prefix => 'admin_'
|
||||
end
|
||||
end
|
||||
|
||||
def test_singleton_resource_with_member_action
|
||||
[:put, :post].each do |method|
|
||||
with_singleton_resources :account, :member => { :reset => method } do
|
||||
reset_options = {:action => 'reset'}
|
||||
reset_path = "/account/reset"
|
||||
assert_singleton_routes_for :account do |options|
|
||||
assert_recognizes(options.merge(reset_options), :path => reset_path, :method => method)
|
||||
end
|
||||
|
||||
assert_singleton_named_routes_for :account do |options|
|
||||
assert_named_route reset_path, :reset_account_path, reset_options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_singleton_resource_with_two_member_actions_with_same_method
|
||||
[:put, :post].each do |method|
|
||||
with_singleton_resources :account, :member => { :reset => method, :disable => method } do
|
||||
%w(reset disable).each do |action|
|
||||
action_options = {:action => action}
|
||||
action_path = "/account/#{action}"
|
||||
assert_singleton_routes_for :account do |options|
|
||||
assert_recognizes(options.merge(action_options), :path => action_path, :method => method)
|
||||
end
|
||||
|
||||
assert_singleton_named_routes_for :account do |options|
|
||||
assert_named_route action_path, "#{action}_account_path".to_sym, action_options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_nest_resources_in_singleton_resource
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resource :account do |account|
|
||||
account.resources :messages
|
||||
end
|
||||
end
|
||||
|
||||
assert_singleton_restful_for :account
|
||||
assert_simply_restful_for :messages, :path_prefix => 'account/', :name_prefix => 'account_'
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_nest_resources_in_singleton_resource_with_path_prefix
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resource(:account, :path_prefix => ':site_id') do |account|
|
||||
account.resources :messages
|
||||
end
|
||||
end
|
||||
|
||||
assert_singleton_restful_for :account, :path_prefix => '7/', :options => { :site_id => '7' }
|
||||
assert_simply_restful_for :messages, :path_prefix => '7/account/', :name_prefix => 'account_', :options => { :site_id => '7' }
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_nest_singleton_resource_in_resources
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resources :threads do |thread|
|
||||
thread.resource :admin
|
||||
end
|
||||
end
|
||||
|
||||
assert_simply_restful_for :threads
|
||||
assert_singleton_restful_for :admin, :path_prefix => 'threads/5/', :name_prefix => 'thread_', :options => { :thread_id => '5' }
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_not_allow_delete_or_put_on_collection_path
|
||||
controller_name = :messages
|
||||
with_restful_routing controller_name do
|
||||
options = { :controller => controller_name.to_s }
|
||||
collection_path = "/#{controller_name}"
|
||||
|
||||
assert_raises(ActionController::RoutingError) do
|
||||
assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
|
||||
end
|
||||
|
||||
assert_raises(ActionController::RoutingError) do
|
||||
assert_recognizes(options.merge(:action => 'destroy'), :path => collection_path, :method => :delete)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_resource_action_separator
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
|
||||
map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
|
||||
end
|
||||
|
||||
action_separator = ActionController::Base.resource_action_separator
|
||||
|
||||
assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
|
||||
assert_named_route "/threads/1/messages#{action_separator}search", "search_thread_messages_path", {}
|
||||
assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
|
||||
assert_named_route "/threads/1/messages/new#{action_separator}preview", "preview_new_thread_message_path", {}
|
||||
assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/'
|
||||
assert_named_route "/admin/account#{action_separator}login", "login_admin_account_path", {}
|
||||
assert_named_route "/admin/account/new", "new_admin_account_path", {}
|
||||
assert_named_route "/admin/account/new#{action_separator}preview", "preview_new_admin_account_path", {}
|
||||
end
|
||||
end
|
||||
|
||||
def test_new_style_named_routes_for_resource
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
|
||||
end
|
||||
assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
|
||||
assert_named_route "/threads/1/messages/search", "search_thread_messages_path", {}
|
||||
assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
|
||||
assert_named_route "/threads/1/messages/new/preview", "preview_new_thread_message_path", {}
|
||||
end
|
||||
end
|
||||
|
||||
def test_new_style_named_routes_for_singleton_resource
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
|
||||
end
|
||||
assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/'
|
||||
assert_named_route "/admin/account/login", "login_admin_account_path", {}
|
||||
assert_named_route "/admin/account/new", "new_admin_account_path", {}
|
||||
assert_named_route "/admin/account/new/preview", "preview_new_admin_account_path", {}
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_add_deprecated_named_routes_for_resource
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
|
||||
end
|
||||
assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
|
||||
assert_deprecated do
|
||||
assert_named_route "/threads/1/messages/search", "thread_search_messages_path", {}
|
||||
assert_named_route "/threads/1/messages/new", "thread_new_message_path", {}
|
||||
assert_named_route "/threads/1/messages/new/preview", "thread_preview_new_message_path", {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_add_deprecated_named_routes_for_singleton_resource
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
|
||||
end
|
||||
assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/'
|
||||
assert_deprecated do
|
||||
assert_named_route "/admin/account/login", "admin_login_account_path", {}
|
||||
assert_named_route "/admin/account/new", "admin_new_account_path", {}
|
||||
assert_named_route "/admin/account/new/preview", "admin_preview_new_account_path", {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_add_deprecated_named_routes_for_nested_resources
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resources :threads do |map|
|
||||
map.resources :messages do |map|
|
||||
map.resources :comments
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assert_simply_restful_for :threads
|
||||
assert_simply_restful_for :messages,
|
||||
:path_prefix => 'threads/1/',
|
||||
:name_prefix => 'thread_',
|
||||
:options => { :thread_id => '1' }
|
||||
assert_simply_restful_for :comments,
|
||||
:path_prefix => 'threads/1/messages/2/',
|
||||
:name_prefix => 'thread_message_',
|
||||
:options => { :thread_id => '1', :message_id => '2' }
|
||||
|
||||
assert_deprecated do
|
||||
assert_named_route "/threads/1/messages", "messages_path", {}
|
||||
assert_named_route "/threads/1/messages/1", "message_path", {:thread_id => '1', :id => '1'}
|
||||
assert_named_route "/threads/1/messages/new", "new_message_path", {:thread_id => '1'}
|
||||
assert_named_route "/threads/1/messages/1/edit", "edit_message_path", {:thread_id => '1', :id => '1'}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_add_deprecated_named_routes_for_nested_singleton_resources
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resource :admin do |admin|
|
||||
admin.resource :account
|
||||
end
|
||||
end
|
||||
|
||||
assert_singleton_restful_for :admin
|
||||
assert_singleton_restful_for :account, :path_prefix => 'admin/', :name_prefix => 'admin_'
|
||||
|
||||
assert_deprecated do
|
||||
assert_named_route "/admin/account", "account_path", {}
|
||||
assert_named_route "/admin/account/new", "new_account_path", {}
|
||||
assert_named_route "/admin/account/edit", "edit_account_path", {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_add_deprecated_named_routes_for_nested_resources_in_singleton_resource
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resource :account do |account|
|
||||
account.resources :messages
|
||||
end
|
||||
end
|
||||
|
||||
assert_singleton_restful_for :account
|
||||
assert_simply_restful_for :messages, :path_prefix => 'account/', :name_prefix => 'account_'
|
||||
|
||||
assert_deprecated do
|
||||
assert_named_route "/account/messages", "messages_path", {}
|
||||
assert_named_route "/account/messages/1", "message_path", {:id => '1'}
|
||||
assert_named_route "/account/messages/new", "new_message_path", {}
|
||||
assert_named_route "/account/messages/1/edit", "edit_message_path", {:id => '1'}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_add_deprecated_named_routes_for_nested_singleton_resource_in_resources
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resources :threads do |thread|
|
||||
thread.resource :admin
|
||||
end
|
||||
end
|
||||
|
||||
assert_simply_restful_for :threads
|
||||
assert_singleton_restful_for :admin, :path_prefix => 'threads/5/', :name_prefix => 'thread_', :options => { :thread_id => '5' }
|
||||
|
||||
assert_deprecated do
|
||||
assert_named_route "/threads/5/admin", "admin_path", {}
|
||||
assert_named_route "/threads/5/admin/new", "new_admin_path", {}
|
||||
assert_named_route "/threads/5/admin/edit", "edit_admin_path", {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_add_deprecated_formatted_routes
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.resources :products, :collection => { :specials => :get }, :member => { :thumbnail => :get }
|
||||
map.resource :account, :member => { :icon => :get }
|
||||
end
|
||||
assert_restful_routes_for :products do |options|
|
||||
assert_recognizes options.merge({ :action => 'specials', :format => 'xml' }), :path => '/products.xml;specials', :method => :get
|
||||
assert_recognizes options.merge({ :action => 'thumbnail', :format => 'jpg', :id => '1' }), :path => '/products/1.jpg;thumbnail', :method => :get
|
||||
end
|
||||
assert_singleton_restful_for :account do |options|
|
||||
assert_recognizes options.merge({ :action => 'icon', :format => 'jpg' }), :path => '/account.jpg;icon', :method => :get
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def with_restful_routing(*args)
|
||||
with_routing do |set|
|
||||
@@ -180,39 +498,59 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def with_singleton_resources(*args)
|
||||
with_routing do |set|
|
||||
set.draw { |map| map.resource(*args) }
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
# runs assert_restful_routes_for and assert_restful_named_routes for on the controller_name and options, without passing a block.
|
||||
def assert_simply_restful_for(controller_name, options = {})
|
||||
assert_restful_routes_for controller_name, options
|
||||
assert_restful_named_routes_for controller_name, options
|
||||
end
|
||||
|
||||
def assert_singleton_restful_for(singleton_name, options = {})
|
||||
assert_singleton_routes_for singleton_name, options
|
||||
assert_singleton_named_routes_for singleton_name, options
|
||||
end
|
||||
|
||||
def assert_restful_routes_for(controller_name, options = {})
|
||||
(options[:options] ||= {})[:controller] = controller_name.to_s
|
||||
|
||||
collection_path = "/#{options[:path_prefix]}#{controller_name}"
|
||||
member_path = "#{collection_path}/1"
|
||||
new_path = "#{collection_path}/new"
|
||||
collection_path = "/#{options[:path_prefix]}#{controller_name}"
|
||||
member_path = "#{collection_path}/1"
|
||||
new_path = "#{collection_path}/new"
|
||||
edit_member_path = "#{member_path}/edit"
|
||||
formatted_edit_member_path = "#{member_path}/edit.xml"
|
||||
|
||||
with_options(options[:options]) do |controller|
|
||||
controller.assert_routing collection_path, :action => 'index'
|
||||
controller.assert_routing "#{collection_path}.xml" , :action => 'index', :format => 'xml'
|
||||
controller.assert_routing new_path, :action => 'new'
|
||||
controller.assert_routing member_path, :action => 'show', :id => '1'
|
||||
controller.assert_routing "#{member_path};edit", :action => 'edit', :id => '1'
|
||||
controller.assert_routing edit_member_path, :action => 'edit', :id => '1'
|
||||
controller.assert_routing "#{collection_path}.xml", :action => 'index', :format => 'xml'
|
||||
controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml'
|
||||
controller.assert_routing "#{member_path}.xml", :action => 'show', :id => '1', :format => 'xml'
|
||||
controller.assert_routing formatted_edit_member_path, :action => 'edit', :id => '1', :format => 'xml'
|
||||
end
|
||||
|
||||
assert_recognizes(
|
||||
options[:options].merge(:action => 'create'),
|
||||
:path => collection_path, :method => :post)
|
||||
assert_recognizes(options[:options].merge(:action => 'index'), :path => collection_path, :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'create'), :path => collection_path, :method => :post)
|
||||
assert_recognizes(options[:options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put)
|
||||
assert_recognizes(options[:options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete)
|
||||
|
||||
assert_recognizes(
|
||||
options[:options].merge(:action => 'update', :id => '1'),
|
||||
:path => member_path, :method => :put)
|
||||
|
||||
assert_recognizes(
|
||||
options[:options].merge(:action => 'destroy', :id => '1'),
|
||||
:path => member_path, :method => :delete)
|
||||
assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post)
|
||||
assert_recognizes(options[:options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put)
|
||||
assert_recognizes(options[:options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete)
|
||||
|
||||
yield options[:options] if block_given?
|
||||
end
|
||||
@@ -232,16 +570,72 @@ class ResourcesTest < Test::Unit::TestCase
|
||||
options[:options].delete :action
|
||||
|
||||
full_prefix = "/#{options[:path_prefix]}#{controller_name}"
|
||||
name_prefix = options[:name_prefix]
|
||||
|
||||
assert_named_route "#{full_prefix}", "#{controller_name}_path", options[:options]
|
||||
assert_named_route "#{full_prefix}.xml", "formatted_#{controller_name}_path", options[:options].merge(:format => 'xml')
|
||||
assert_named_route "#{full_prefix}/new", "new_#{singular_name}_path", options[:options]
|
||||
assert_named_route "#{full_prefix}/1", "#{singular_name}_path", options[:options].merge(:id => '1')
|
||||
assert_named_route "#{full_prefix}/1;edit", "edit_#{singular_name}_path", options[:options].merge(:id => '1')
|
||||
assert_named_route "#{full_prefix}/1.xml", "formatted_#{singular_name}_path", options[:options].merge(:format => 'xml', :id => '1')
|
||||
assert_named_route "#{full_prefix}", "#{name_prefix}#{controller_name}_path", options[:options]
|
||||
assert_named_route "#{full_prefix}/new", "new_#{name_prefix}#{singular_name}_path", options[:options]
|
||||
assert_named_route "#{full_prefix}/1", "#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1')
|
||||
assert_named_route "#{full_prefix}/1/edit", "edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1')
|
||||
assert_named_route "#{full_prefix}.xml", "formatted_#{name_prefix}#{controller_name}_path", options[:options].merge( :format => 'xml')
|
||||
assert_named_route "#{full_prefix}/new.xml", "formatted_new_#{name_prefix}#{singular_name}_path", options[:options].merge( :format => 'xml')
|
||||
assert_named_route "#{full_prefix}/1.xml", "formatted_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml')
|
||||
assert_named_route "#{full_prefix}/1/edit.xml", "formatted_edit_#{name_prefix}#{singular_name}_path", options[:options].merge(:id => '1', :format => 'xml')
|
||||
yield options[:options] if block_given?
|
||||
end
|
||||
|
||||
def assert_singleton_routes_for(singleton_name, options = {})
|
||||
(options[:options] ||= {})[:controller] ||= singleton_name.to_s
|
||||
|
||||
full_path = "/#{options[:path_prefix]}#{singleton_name}"
|
||||
new_path = "#{full_path}/new"
|
||||
edit_path = "#{full_path}/edit"
|
||||
formatted_edit_path = "#{full_path}/edit.xml"
|
||||
|
||||
with_options options[:options] do |controller|
|
||||
controller.assert_routing full_path, :action => 'show'
|
||||
controller.assert_routing new_path, :action => 'new'
|
||||
controller.assert_routing edit_path, :action => 'edit'
|
||||
controller.assert_routing "#{full_path}.xml", :action => 'show', :format => 'xml'
|
||||
controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml'
|
||||
controller.assert_routing formatted_edit_path, :action => 'edit', :format => 'xml'
|
||||
end
|
||||
|
||||
assert_recognizes(options[:options].merge(:action => 'show'), :path => full_path, :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'edit'), :path => edit_path, :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'create'), :path => full_path, :method => :post)
|
||||
assert_recognizes(options[:options].merge(:action => 'update'), :path => full_path, :method => :put)
|
||||
assert_recognizes(options[:options].merge(:action => 'destroy'), :path => full_path, :method => :delete)
|
||||
|
||||
assert_recognizes(options[:options].merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get)
|
||||
assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post)
|
||||
assert_recognizes(options[:options].merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put)
|
||||
assert_recognizes(options[:options].merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete)
|
||||
|
||||
yield options[:options] if block_given?
|
||||
end
|
||||
|
||||
def assert_singleton_named_routes_for(singleton_name, options = {})
|
||||
(options[:options] ||= {})[:controller] ||= singleton_name.to_s
|
||||
@controller = "#{options[:options][:controller].camelize}Controller".constantize.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
get :show, options[:options]
|
||||
options[:options].delete :action
|
||||
|
||||
full_path = "/#{options[:path_prefix]}#{singleton_name}"
|
||||
full_name = "#{options[:name_prefix]}#{singleton_name}"
|
||||
|
||||
assert_named_route "#{full_path}", "#{full_name}_path", options[:options]
|
||||
assert_named_route "#{full_path}/new", "new_#{full_name}_path", options[:options]
|
||||
assert_named_route "#{full_path}/edit", "edit_#{full_name}_path", options[:options]
|
||||
assert_named_route "#{full_path}.xml", "formatted_#{full_name}_path", options[:options].merge(:format => 'xml')
|
||||
assert_named_route "#{full_path}/new.xml", "formatted_new_#{full_name}_path", options[:options].merge(:format => 'xml')
|
||||
assert_named_route "#{full_path}/edit.xml", "formatted_edit_#{full_name}_path", options[:options].merge(:format => 'xml')
|
||||
end
|
||||
|
||||
def assert_named_route(expected, route, options)
|
||||
actual = @controller.send(route, options) rescue $!.class.name
|
||||
assert_equal expected, actual, "Error on route: #{route}(#{options.inspect})"
|
||||
|
||||
@@ -236,7 +236,39 @@ class LegacyRouteSetTests < Test::Unit::TestCase
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_list_options_diff_when_routing_requirements_dont_match
|
||||
rs.draw do |map|
|
||||
map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/}
|
||||
end
|
||||
exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") }
|
||||
assert_match /^post_url failed to generate/, exception.message
|
||||
from_match = exception.message.match(/from \{[^\}]+\}/).to_s
|
||||
assert_match /:bad_param=>"foo"/, from_match
|
||||
assert_match /:action=>"show"/, from_match
|
||||
assert_match /:controller=>"post"/, from_match
|
||||
|
||||
expected_match = exception.message.match(/expected: \{[^\}]+\}/).to_s
|
||||
assert_no_match /:bad_param=>"foo"/, expected_match
|
||||
assert_match /:action=>"show"/, expected_match
|
||||
assert_match /:controller=>"post"/, expected_match
|
||||
|
||||
diff_match = exception.message.match(/diff: \{[^\}]+\}/).to_s
|
||||
assert_match /:bad_param=>"foo"/, diff_match
|
||||
assert_no_match /:action=>"show"/, diff_match
|
||||
assert_no_match /:controller=>"post"/, diff_match
|
||||
end
|
||||
|
||||
# this specifies the case where your formerly would get a very confusing error message with an empty diff
|
||||
def test_should_have_better_error_message_when_options_diff_is_empty
|
||||
rs.draw do |map|
|
||||
map.content '/content/:query', :controller => 'content', :action => 'show'
|
||||
end
|
||||
exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'content', :action => 'show', :use_route => "content") }
|
||||
expected_message = "content_url failed to generate from #{{:action=>"show", :controller=>"content"}.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: [\"content\", :query] - are they all satisifed?"
|
||||
assert_equal expected_message, exception.message
|
||||
end
|
||||
|
||||
def test_dynamic_path_allowed
|
||||
rs.draw do |map|
|
||||
map.connect '*path', :controller => 'content', :action => 'show_file'
|
||||
@@ -376,7 +408,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
|
||||
|
||||
def test_named_url_with_no_action_specified
|
||||
rs.draw do |map|
|
||||
map.root '', :controller => 'content'
|
||||
map.home '', :controller => 'content'
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
|
||||
@@ -384,14 +416,14 @@ class LegacyRouteSetTests < Test::Unit::TestCase
|
||||
assert_equal '/', rs.generate(:controller => 'content')
|
||||
|
||||
x = setup_for_named_route.new
|
||||
assert_equal({:controller => 'content', :action => 'index', :use_route => :root, :only_path => false},
|
||||
x.send(:root_url))
|
||||
assert_equal({:controller => 'content', :action => 'index', :use_route => :home, :only_path => false},
|
||||
x.send(:home_url))
|
||||
end
|
||||
|
||||
def test_url_generated_when_forgetting_action
|
||||
[{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash|
|
||||
rs.draw do |map|
|
||||
map.root '', hash
|
||||
map.home '', hash
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
assert_equal '/', rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'})
|
||||
@@ -853,6 +885,12 @@ class RouteTest < Test::Unit::TestCase
|
||||
{ :controller => "users", :action => "show", :format => "html" },
|
||||
route.defaults)
|
||||
end
|
||||
|
||||
def test_builder_complains_without_controller
|
||||
assert_raises(ArgumentError) do
|
||||
ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index"
|
||||
end
|
||||
end
|
||||
|
||||
def test_significant_keys_for_default_route
|
||||
keys = default_route.significant_keys.sort_by {|k| k.to_s }
|
||||
@@ -1576,6 +1614,18 @@ class RouteSetTest < Test::Unit::TestCase
|
||||
Object.send(:remove_const, :PeopleController)
|
||||
end
|
||||
|
||||
def test_deprecation_warning_for_root_route
|
||||
Object.const_set(:PeopleController, Class.new)
|
||||
|
||||
set.draw do |map|
|
||||
assert_deprecated do
|
||||
map.root('', :controller => "people")
|
||||
end
|
||||
end
|
||||
ensure
|
||||
Object.send(:remove_const, :PeopleController)
|
||||
end
|
||||
|
||||
def test_generate_with_default_action
|
||||
set.draw do |map|
|
||||
map.connect "/people", :controller => "people"
|
||||
@@ -1667,16 +1717,30 @@ class RouteSetTest < Test::Unit::TestCase
|
||||
)
|
||||
end
|
||||
|
||||
def test_query_params_will_be_shown_when_recalled
|
||||
set.draw do |map|
|
||||
map.connect 'show_post/:parameter', :controller => 'post', :action => 'show'
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
assert_equal '/post/edit?parameter=1', set.generate(
|
||||
{:action => 'edit', :parameter => 1},
|
||||
{:controller => 'post', :action => 'show', :parameter => 1}
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class RoutingTest < Test::Unit::TestCase
|
||||
|
||||
def test_possible_controllers
|
||||
true_controller_paths = ActionController::Routing.controller_paths
|
||||
|
||||
|
||||
ActionController::Routing.use_controllers! nil
|
||||
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures')
|
||||
|
||||
|
||||
silence_warnings do
|
||||
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures')
|
||||
end
|
||||
|
||||
ActionController::Routing.controller_paths = [
|
||||
RAILS_ROOT, RAILS_ROOT + '/app/controllers', RAILS_ROOT + '/vendor/plugins/bad_plugin/lib'
|
||||
]
|
||||
@@ -1734,4 +1798,4 @@ class RoutingTest < Test::Unit::TestCase
|
||||
assert_equal %w(vendor\\rails\\railties\\builtin\\rails_info vendor\\rails\\actionpack\\lib app\\controllers app\\helpers app\\models lib .), paths
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,6 +63,14 @@ class SendFileTest < Test::Unit::TestCase
|
||||
assert_equal file_data, response.body
|
||||
end
|
||||
|
||||
def test_headers_after_send_shouldnt_include_charset
|
||||
response = process('data')
|
||||
assert_equal "application/octet-stream", response.headers["Content-Type"]
|
||||
|
||||
response = process('file')
|
||||
assert_equal "application/octet-stream", response.headers["Content-Type"]
|
||||
end
|
||||
|
||||
# Test that send_file_headers! is setting the correct HTTP headers.
|
||||
def test_send_file_headers!
|
||||
options = {
|
||||
|
||||
@@ -44,6 +44,49 @@ class SessionManagementTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
class AssociationCachingTestController < ActionController::Base
|
||||
class ObjectWithAssociationCache
|
||||
def initialize
|
||||
@cached_associations = false
|
||||
end
|
||||
|
||||
def fetch_associations
|
||||
@cached_associations = true
|
||||
end
|
||||
|
||||
def clear_association_cache
|
||||
@cached_associations = false
|
||||
end
|
||||
|
||||
def has_cached_associations?
|
||||
@cached_associations
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
session[:object] = ObjectWithAssociationCache.new
|
||||
session[:object].fetch_associations
|
||||
if session[:object].has_cached_associations?
|
||||
render :text => "has cached associations"
|
||||
else
|
||||
render :text => "does not have cached associations"
|
||||
end
|
||||
end
|
||||
|
||||
def tell
|
||||
if session[:object]
|
||||
if session[:object].has_cached_associations?
|
||||
render :text => "has cached associations"
|
||||
else
|
||||
render :text => "does not have cached associations"
|
||||
end
|
||||
else
|
||||
render :text => "there is no object"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def setup
|
||||
@request, @response = ActionController::TestRequest.new,
|
||||
ActionController::TestResponse.new
|
||||
@@ -91,4 +134,23 @@ class SessionManagementTest < Test::Unit::TestCase
|
||||
assert_equal CGI::Session::ActiveRecordStore, ActionController::Base.session_store
|
||||
end
|
||||
end
|
||||
|
||||
def test_process_cleanup_with_session_management_support
|
||||
@controller = AssociationCachingTestController.new
|
||||
get :show
|
||||
assert_equal "has cached associations", @response.body
|
||||
get :tell
|
||||
assert_equal "does not have cached associations", @response.body
|
||||
end
|
||||
|
||||
def test_session_is_enabled
|
||||
@controller = TestController.new
|
||||
get :show
|
||||
assert_nothing_raised do
|
||||
assert_equal false, @controller.session_enabled?
|
||||
end
|
||||
|
||||
get :tell
|
||||
assert @controller.session_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -66,6 +66,11 @@ HTML
|
||||
redirect_to :controller => 'fail', :id => 5
|
||||
end
|
||||
|
||||
def create
|
||||
headers['Location'] = 'created resource'
|
||||
head :created
|
||||
end
|
||||
|
||||
private
|
||||
def rescue_action(e)
|
||||
raise e
|
||||
@@ -81,7 +86,7 @@ HTML
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
ActionController::Routing::Routes.reload
|
||||
ActionController::Routing.use_controllers! %w(content admin/user)
|
||||
ActionController::Routing.use_controllers! %w(content admin/user test_test/test)
|
||||
end
|
||||
|
||||
def teardown
|
||||
@@ -463,6 +468,36 @@ HTML
|
||||
end
|
||||
end
|
||||
|
||||
def test_redirect_url_only_cares_about_location_header
|
||||
get :create
|
||||
assert_response :created
|
||||
|
||||
# Redirect url doesn't care that it wasn't a :redirect response.
|
||||
assert_equal 'created resource', @response.redirect_url
|
||||
assert_equal @response.redirect_url, redirect_to_url
|
||||
|
||||
# Must be a :redirect response.
|
||||
assert_raise(Test::Unit::AssertionFailedError) do
|
||||
assert_redirected_to 'created resource'
|
||||
end
|
||||
end
|
||||
|
||||
def test_request_uri_updates
|
||||
get :test_params
|
||||
uri = @request.request_uri
|
||||
assert_equal @request.env['REQUEST_URI'], uri
|
||||
|
||||
get :test_uri
|
||||
assert_not_equal uri, @request.request_uri
|
||||
uri = @request.request_uri
|
||||
assert_equal @request.env['REQUEST_URI'], uri
|
||||
|
||||
get :test_uri, :testing => true
|
||||
assert_not_equal uri, @request.request_uri
|
||||
uri = @request.request_uri
|
||||
assert_equal @request.env['REQUEST_URI'], uri
|
||||
end
|
||||
|
||||
protected
|
||||
def with_foo_routing
|
||||
with_routing do |set|
|
||||
|
||||
@@ -17,7 +17,13 @@ class UrlRewriterTests < Test::Unit::TestCase
|
||||
assert_match %r(/hi/hi/2$), u
|
||||
end
|
||||
|
||||
|
||||
def test_anchor
|
||||
assert_equal(
|
||||
'http://test.host/c/a/i#anchor',
|
||||
@rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
def split_query_string(str)
|
||||
[str[0].chr] + str[1..-1].split(/&/).sort
|
||||
@@ -75,10 +81,16 @@ class UrlWriterTests < Test::Unit::TestCase
|
||||
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
|
||||
)
|
||||
end
|
||||
|
||||
def test_anchor
|
||||
assert_equal('/c/a#anchor',
|
||||
W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor')
|
||||
)
|
||||
end
|
||||
|
||||
def test_named_route
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.home '/home/sweet/home/:user'
|
||||
map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
|
||||
@@ -96,7 +108,7 @@ class UrlWriterTests < Test::Unit::TestCase
|
||||
|
||||
def test_only_path
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.home '/home/sweet/home/:user'
|
||||
map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index'
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
|
||||
|
||||
@@ -34,9 +34,16 @@ class VerificationTest < Test::Unit::TestCase
|
||||
|
||||
verify :only => :must_be_post, :method => :post, :render => { :status => 405, :text => "Must be post" }, :add_headers => { "Allow" => "POST" }
|
||||
|
||||
verify :only => :guarded_one_for_named_route_test, :params => "one",
|
||||
:redirect_to => :foo_url
|
||||
|
||||
def guarded_one
|
||||
render :text => "#{params[:one]}"
|
||||
end
|
||||
|
||||
def guarded_one_for_named_route_test
|
||||
render :text => "#{params[:one]}"
|
||||
end
|
||||
|
||||
def guarded_with_flash
|
||||
render :text => "#{params[:one]}"
|
||||
@@ -94,6 +101,14 @@ class VerificationTest < Test::Unit::TestCase
|
||||
@controller = TestController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
ActionController::Routing::Routes.add_named_route :foo, '/foo', :controller => 'test', :action => 'foo'
|
||||
end
|
||||
|
||||
def test_no_deprecation_warning_for_named_route
|
||||
assert_not_deprecated do
|
||||
get :guarded_one_for_named_route_test, :two => "not one"
|
||||
assert_redirected_to '/foo'
|
||||
end
|
||||
end
|
||||
|
||||
def test_guarded_one_with_prereqs
|
||||
|
||||
BIN
actionpack/test/fixtures/multipart/binary_file
vendored
BIN
actionpack/test/fixtures/multipart/binary_file
vendored
Binary file not shown.
1
actionpack/test/fixtures/public/javascripts/application.js
vendored
Normal file
1
actionpack/test/fixtures/public/javascripts/application.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# Test file for javascript_include_tag
|
||||
@@ -1,3 +1,4 @@
|
||||
xml.html do
|
||||
xml.p "Hello"
|
||||
end
|
||||
end
|
||||
"String return value"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -34,6 +34,7 @@ class AssetTagHelperTest < Test::Unit::TestCase
|
||||
|
||||
AutoDiscoveryToTag = {
|
||||
%(auto_discovery_link_tag) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
|
||||
%(auto_discovery_link_tag(:rss)) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
|
||||
%(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com" rel="alternate" title="ATOM" type="application/atom+xml" />),
|
||||
%(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
|
||||
%(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(<link href="http://localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
|
||||
@@ -47,11 +48,13 @@ class AssetTagHelperTest < Test::Unit::TestCase
|
||||
|
||||
JavascriptPathToTag = {
|
||||
%(javascript_path("xmlhr")) => %(/javascripts/xmlhr.js),
|
||||
%(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js)
|
||||
%(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js),
|
||||
%(javascript_path("/super/xmlhr.js")) => %(/super/xmlhr.js)
|
||||
}
|
||||
|
||||
JavascriptIncludeToTag = {
|
||||
%(javascript_include_tag("xmlhr")) => %(<script src="/javascripts/xmlhr.js" type="text/javascript"></script>),
|
||||
%(javascript_include_tag("xmlhr.js")) => %(<script src="/javascripts/xmlhr.js" type="text/javascript"></script>),
|
||||
%(javascript_include_tag("xmlhr", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/xmlhr.js" type="text/javascript"></script>),
|
||||
%(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>),
|
||||
%(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>),
|
||||
@@ -61,12 +64,14 @@ class AssetTagHelperTest < Test::Unit::TestCase
|
||||
|
||||
StylePathToTag = {
|
||||
%(stylesheet_path("style")) => %(/stylesheets/style.css),
|
||||
%(stylesheet_path("style.css")) => %(/stylesheets/style.css),
|
||||
%(stylesheet_path('dir/file')) => %(/stylesheets/dir/file.css),
|
||||
%(stylesheet_path('/dir/file')) => %(/dir/file.css)
|
||||
%(stylesheet_path('/dir/file.rcss')) => %(/dir/file.rcss)
|
||||
}
|
||||
|
||||
StyleLinkToTag = {
|
||||
%(stylesheet_link_tag("style")) => %(<link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
|
||||
%(stylesheet_link_tag("style.css")) => %(<link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
|
||||
%(stylesheet_link_tag("/dir/file")) => %(<link href="/dir/file.css" media="screen" rel="Stylesheet" type="text/css" />),
|
||||
%(stylesheet_link_tag("dir/file")) => %(<link href="/stylesheets/dir/file.css" media="screen" rel="Stylesheet" type="text/css" />),
|
||||
%(stylesheet_link_tag("style", :media => "all")) => %(<link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />),
|
||||
@@ -75,18 +80,28 @@ class AssetTagHelperTest < Test::Unit::TestCase
|
||||
}
|
||||
|
||||
ImagePathToTag = {
|
||||
%(image_path("xml")) => %(/images/xml.png),
|
||||
%(image_path("xml.png")) => %(/images/xml.png),
|
||||
%(image_path("dir/xml.png")) => %(/images/dir/xml.png),
|
||||
%(image_path("/dir/xml.png")) => %(/dir/xml.png)
|
||||
}
|
||||
|
||||
ImageLinkToTag = {
|
||||
%(image_tag("xml")) => %(<img alt="Xml" src="/images/xml.png" />),
|
||||
%(image_tag("rss", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.png" />),
|
||||
%(image_tag("gold", :size => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
|
||||
%(image_tag("symbolize", "size" => "45x70")) => %(<img alt="Symbolize" height="70" src="/images/symbolize.png" width="45" />),
|
||||
%(image_tag("http://www.rubyonrails.com/images/rails")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />)
|
||||
%(image_tag("xml.png")) => %(<img alt="Xml" src="/images/xml.png" />),
|
||||
%(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />),
|
||||
%(image_tag("gold.png", :size => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
|
||||
%(image_tag("gold.png", "size" => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
|
||||
%(image_tag("error.png", "size" => "45")) => %(<img alt="Error" src="/images/error.png" />),
|
||||
%(image_tag("error.png", "size" => "45 x 70")) => %(<img alt="Error" src="/images/error.png" />),
|
||||
%(image_tag("error.png", "size" => "x")) => %(<img alt="Error" src="/images/error.png" />),
|
||||
%(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />)
|
||||
}
|
||||
|
||||
def test_auto_discovery
|
||||
DeprecatedImagePathToTag = {
|
||||
%(image_path("xml")) => %(/images/xml.png)
|
||||
}
|
||||
|
||||
|
||||
def test_auto_discovery_link_tag
|
||||
AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
@@ -94,8 +109,12 @@ class AssetTagHelperTest < Test::Unit::TestCase
|
||||
JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
def test_javascript_include
|
||||
def test_javascript_include_tag
|
||||
JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
|
||||
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
|
||||
ENV["RAILS_ASSET_ID"] = "1"
|
||||
assert_dom_equal(%(<script src="/javascripts/prototype.js?1" type="text/javascript"></script>\n<script src="/javascripts/effects.js?1" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js?1" type="text/javascript"></script>\n<script src="/javascripts/controls.js?1" type="text/javascript"></script>\n<script src="/javascripts/application.js?1" type="text/javascript"></script>), javascript_include_tag(:defaults))
|
||||
end
|
||||
|
||||
def test_register_javascript_include_default
|
||||
@@ -105,23 +124,25 @@ class AssetTagHelperTest < Test::Unit::TestCase
|
||||
assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/slider.js" type="text/javascript"></script>\n<script src="/javascripts/lib1.js" type="text/javascript"></script>\n<script src="/elsewhere/blub/lib2.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
|
||||
end
|
||||
|
||||
def test_style_path
|
||||
def test_stylesheet_path
|
||||
StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
def test_style_link
|
||||
def test_stylesheet_link_tag
|
||||
StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
def test_image_path
|
||||
ImagePathToTag.each do |method, tag|
|
||||
assert_deprecated(/image_path/) { assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
|
||||
def test_image_tag
|
||||
ImageLinkToTag.each do |method, tag|
|
||||
assert_deprecated(/image_path/) { assert_dom_equal(tag, eval(method)) }
|
||||
ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
def test_should_deprecate_image_filename_with_no_extension
|
||||
DeprecatedImagePathToTag.each do |method, tag|
|
||||
assert_deprecated("image_path") { assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -131,12 +152,12 @@ class AssetTagHelperTest < Test::Unit::TestCase
|
||||
assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png")
|
||||
end
|
||||
|
||||
def test_skipping_asset_id_on_complete_url
|
||||
def test_should_skip_asset_id_on_complete_url
|
||||
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
|
||||
assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png")
|
||||
end
|
||||
|
||||
def test_preset_asset_id
|
||||
def test_should_use_preset_asset_id
|
||||
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
|
||||
ENV["RAILS_ASSET_ID"] = "4500"
|
||||
assert_equal %(<img alt="Rails" src="/images/rails.png?4500" />), image_tag("rails.png")
|
||||
@@ -144,17 +165,20 @@ class AssetTagHelperTest < Test::Unit::TestCase
|
||||
|
||||
def test_preset_empty_asset_id
|
||||
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
|
||||
# on windows, setting ENV["XXX"] to "" makes ENV["XXX"] return nil
|
||||
if RUBY_PLATFORM =~ /win32/
|
||||
ENV["RAILS_ASSET_ID"] = " "
|
||||
else
|
||||
ENV["RAILS_ASSET_ID"] = ""
|
||||
end
|
||||
assert_equal %(<img alt="Rails" src="/images/rails.png" />), image_tag("rails.png")
|
||||
end
|
||||
|
||||
def test_url_dup_image_tag
|
||||
Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
|
||||
img_url = '/images/rails.png'
|
||||
url_copy = img_url.dup
|
||||
image_tag(img_url)
|
||||
|
||||
assert_equal url_copy, img_url
|
||||
def test_should_not_modify_source_string
|
||||
source = '/images/rails.png'
|
||||
copy = source.dup
|
||||
image_tag(source)
|
||||
assert_equal copy, source
|
||||
end
|
||||
end
|
||||
|
||||
@@ -168,13 +192,13 @@ class AssetTagHelperNonVhostTest < Test::Unit::TestCase
|
||||
attr_accessor :request
|
||||
|
||||
def url_for(options, *parameters_for_method_reference)
|
||||
"http://www.example.com/calloboration/hieraki"
|
||||
"http://www.example.com/collaboration/hieraki"
|
||||
end
|
||||
end.new
|
||||
|
||||
@request = Class.new do
|
||||
def relative_url_root
|
||||
"/calloboration/hieraki"
|
||||
"/collaboration/hieraki"
|
||||
end
|
||||
end.new
|
||||
|
||||
@@ -183,89 +207,31 @@ class AssetTagHelperNonVhostTest < Test::Unit::TestCase
|
||||
ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
|
||||
end
|
||||
|
||||
AutoDiscoveryToTag = {
|
||||
%(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com/calloboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />),
|
||||
%(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com/calloboration/hieraki" rel="alternate" title="ATOM" type="application/atom+xml" />),
|
||||
%(auto_discovery_link_tag) => %(<link href="http://www.example.com/calloboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />),
|
||||
}
|
||||
|
||||
JavascriptPathToTag = {
|
||||
%(javascript_path("xmlhr")) => %(/calloboration/hieraki/javascripts/xmlhr.js),
|
||||
}
|
||||
|
||||
JavascriptIncludeToTag = {
|
||||
%(javascript_include_tag("xmlhr")) => %(<script src="/calloboration/hieraki/javascripts/xmlhr.js" type="text/javascript"></script>),
|
||||
%(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/calloboration/hieraki/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/calloboration/hieraki/elsewhere/cools.js" type="text/javascript"></script>),
|
||||
%(javascript_include_tag(:defaults)) => %(<script src="/calloboration/hieraki/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/effects.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/controls.js" type="text/javascript"></script>)
|
||||
}
|
||||
|
||||
StylePathToTag = {
|
||||
%(stylesheet_path("style")) => %(/calloboration/hieraki/stylesheets/style.css),
|
||||
}
|
||||
|
||||
StyleLinkToTag = {
|
||||
%(stylesheet_link_tag("style")) => %(<link href="/calloboration/hieraki/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
|
||||
%(stylesheet_link_tag("random.styles", "/css/stylish")) => %(<link href="/calloboration/hieraki/stylesheets/random.styles" media="screen" rel="Stylesheet" type="text/css" />\n<link href="/calloboration/hieraki/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />)
|
||||
}
|
||||
|
||||
ImagePathToTag = {
|
||||
%(image_path("xml")) => %(/calloboration/hieraki/images/xml.png),
|
||||
}
|
||||
|
||||
ImageLinkToTag = {
|
||||
%(image_tag("xml")) => %(<img alt="Xml" src="/calloboration/hieraki/images/xml.png" />),
|
||||
%(image_tag("rss", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/calloboration/hieraki/images/rss.png" />),
|
||||
%(image_tag("gold", :size => "45x70")) => %(<img alt="Gold" height="70" src="/calloboration/hieraki/images/gold.png" width="45" />),
|
||||
%(image_tag("symbolize", "size" => "45x70")) => %(<img alt="Symbolize" height="70" src="/calloboration/hieraki/images/symbolize.png" width="45" />)
|
||||
}
|
||||
|
||||
def test_auto_discovery
|
||||
AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
def test_javascript_path
|
||||
JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
def test_javascript_include
|
||||
JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
def test_should_compute_proper_path
|
||||
assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag)
|
||||
assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
|
||||
assert_dom_equal(%(/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
|
||||
assert_dom_equal(%(/collaboration/hieraki/images/xml.png), image_path("xml.png"))
|
||||
end
|
||||
|
||||
def test_register_javascript_include_default
|
||||
ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'slider'
|
||||
assert_dom_equal %(<script src="/calloboration/hieraki/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/effects.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/controls.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/slider.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
|
||||
ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2'
|
||||
assert_dom_equal %(<script src="/calloboration/hieraki/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/effects.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/controls.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/slider.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/lib1.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/elsewhere/blub/lib2.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
|
||||
def test_should_ignore_relative_root_path_on_complete_url
|
||||
assert_dom_equal(%(http://www.example.com/images/xml.png), image_path("http://www.example.com/images/xml.png"))
|
||||
end
|
||||
|
||||
def test_style_path
|
||||
StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
def test_style_link
|
||||
StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
|
||||
def test_image_path
|
||||
ImagePathToTag.each { |method, tag| assert_deprecated(/image_path/) { assert_dom_equal(tag, eval(method)) } }
|
||||
end
|
||||
|
||||
def test_image_tag
|
||||
ImageLinkToTag.each do |method, tag|
|
||||
assert_deprecated(/image_path/) { assert_dom_equal(tag, eval(method)) }
|
||||
end
|
||||
# Assigning a default alt tag should not cause an exception to be raised
|
||||
assert_nothing_raised { image_tag('') }
|
||||
end
|
||||
|
||||
def test_stylesheet_with_asset_host_already_encoded
|
||||
ActionController::Base.asset_host = "http://foo.example.com"
|
||||
result = stylesheet_link_tag("http://bar.example.com/stylesheets/style.css")
|
||||
assert_dom_equal(
|
||||
%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
|
||||
result)
|
||||
def test_should_compute_proper_path_with_asset_host
|
||||
ActionController::Base.asset_host = "http://assets.example.com"
|
||||
assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag)
|
||||
assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
|
||||
assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
|
||||
assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
|
||||
ensure
|
||||
ActionController::Base.asset_host = ""
|
||||
end
|
||||
|
||||
def test_should_ignore_asset_host_on_complete_url
|
||||
ActionController::Base.asset_host = "http://assets.example.com"
|
||||
assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css"))
|
||||
ensure
|
||||
ActionController::Base.asset_host = ""
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -71,7 +71,12 @@ class CompiledTemplateTests < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_compile_time
|
||||
`echo '#{@a}' > #{@a}; echo '#{@b}' > #{@b}; ln -s #{@a} #{@s}`
|
||||
File.open(@a, "w"){|f| f.puts @a}
|
||||
File.open(@b, "w"){|f| f.puts @b}
|
||||
|
||||
# windows doesn't support symlinks (even under cygwin)
|
||||
windows = (RUBY_PLATFORM =~ /win32/)
|
||||
`ln -s #{@a} #{@s}` unless windows
|
||||
|
||||
v = ActionView::Base.new
|
||||
v.base_path = '.'
|
||||
@@ -79,47 +84,54 @@ class CompiledTemplateTests < Test::Unit::TestCase
|
||||
|
||||
sleep 1
|
||||
t = Time.now
|
||||
sleep 1
|
||||
|
||||
v.compile_and_render_template(:rhtml, '', @a)
|
||||
v.compile_and_render_template(:rhtml, '', @b)
|
||||
v.compile_and_render_template(:rhtml, '', @s)
|
||||
v.compile_and_render_template(:rhtml, '', @s) unless windows
|
||||
|
||||
a_n = v.method_names[@a]
|
||||
b_n = v.method_names[@b]
|
||||
s_n = v.method_names[@s]
|
||||
s_n = v.method_names[@s] unless windows
|
||||
ct_a = v.compile_time[a_n]
|
||||
ct_b = v.compile_time[b_n]
|
||||
ct_s = v.compile_time[s_n] unless windows
|
||||
# all of the files have changed since last compile
|
||||
assert v.compile_time[a_n] > t
|
||||
assert v.compile_time[b_n] > t
|
||||
assert v.compile_time[s_n] > t
|
||||
assert v.compile_time[s_n] > t unless windows
|
||||
|
||||
sleep 1
|
||||
t = Time.now
|
||||
v.compile_and_render_template(:rhtml, '', @a)
|
||||
v.compile_and_render_template(:rhtml, '', @b)
|
||||
v.compile_and_render_template(:rhtml, '', @s)
|
||||
v.compile_and_render_template(:rhtml, '', @s) unless windows
|
||||
# none of the files have changed since last compile
|
||||
assert v.compile_time[a_n] < t
|
||||
assert v.compile_time[b_n] < t
|
||||
assert v.compile_time[s_n] < t
|
||||
# so they should not have been recmpiled
|
||||
assert_equal ct_a, v.compile_time[a_n]
|
||||
assert_equal ct_b, v.compile_time[b_n]
|
||||
assert_equal ct_s, v.compile_time[s_n] unless windows
|
||||
|
||||
`rm #{@s}; ln -s #{@b} #{@s}`
|
||||
`rm #{@s}; ln -s #{@b} #{@s}` unless windows
|
||||
v.compile_and_render_template(:rhtml, '', @a)
|
||||
v.compile_and_render_template(:rhtml, '', @b)
|
||||
v.compile_and_render_template(:rhtml, '', @s)
|
||||
v.compile_and_render_template(:rhtml, '', @s) unless windows
|
||||
# the symlink has changed since last compile
|
||||
assert v.compile_time[a_n] < t
|
||||
assert v.compile_time[b_n] < t
|
||||
assert v.compile_time[s_n] > t
|
||||
assert_equal ct_a, v.compile_time[a_n]
|
||||
assert_equal ct_b, v.compile_time[b_n]
|
||||
assert v.compile_time[s_n] > t unless windows
|
||||
|
||||
sleep 1
|
||||
`touch #{@b}`
|
||||
FileUtils.touch @b
|
||||
t = Time.now
|
||||
sleep 1
|
||||
v.compile_and_render_template(:rhtml, '', @a)
|
||||
v.compile_and_render_template(:rhtml, '', @b)
|
||||
v.compile_and_render_template(:rhtml, '', @s)
|
||||
v.compile_and_render_template(:rhtml, '', @s) unless windows
|
||||
# the file at the end of the symlink has changed since last compile
|
||||
# both the symlink and the file at the end of it should be recompiled
|
||||
assert v.compile_time[a_n] < t
|
||||
assert v.compile_time[b_n] > t
|
||||
assert v.compile_time[s_n] > t
|
||||
assert v.compile_time[s_n] > t unless windows
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
36
actionpack/test/template/deprecated_helper_test.rb
Normal file
36
actionpack/test/template/deprecated_helper_test.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
require File.dirname(__FILE__) + '/../abstract_unit'
|
||||
|
||||
class DeprecatedHelperTest < Test::Unit::TestCase
|
||||
include ActionView::Helpers::JavaScriptHelper
|
||||
include ActionView::Helpers::CaptureHelper
|
||||
|
||||
def test_update_element_function
|
||||
assert_deprecated 'update_element_function' do
|
||||
|
||||
assert_equal %($('myelement').innerHTML = 'blub';\n),
|
||||
update_element_function('myelement', :content => 'blub')
|
||||
assert_equal %($('myelement').innerHTML = 'blub';\n),
|
||||
update_element_function('myelement', :action => :update, :content => 'blub')
|
||||
assert_equal %($('myelement').innerHTML = '';\n),
|
||||
update_element_function('myelement', :action => :empty)
|
||||
assert_equal %(Element.remove('myelement');\n),
|
||||
update_element_function('myelement', :action => :remove)
|
||||
|
||||
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
|
||||
update_element_function('myelement', :position => 'bottom', :content => 'blub')
|
||||
assert_equal %(new Insertion.Bottom('myelement','blub');\n),
|
||||
update_element_function('myelement', :action => :update, :position => :bottom, :content => 'blub')
|
||||
|
||||
_erbout = ""
|
||||
assert_equal %($('myelement').innerHTML = 'test';\n),
|
||||
update_element_function('myelement') { _erbout << "test" }
|
||||
|
||||
_erbout = ""
|
||||
assert_equal %($('myelement').innerHTML = 'blockstuff';\n),
|
||||
update_element_function('myelement', :content => 'paramstuff') { _erbout << "blockstuff" }
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -396,7 +396,31 @@ class FormHelperTest < Test::Unit::TestCase
|
||||
|
||||
assert_dom_equal expected, _erbout
|
||||
end
|
||||
|
||||
|
||||
def test_default_form_builder
|
||||
old_default_form_builder, ActionView::Base.default_form_builder =
|
||||
ActionView::Base.default_form_builder, LabelledFormBuilder
|
||||
|
||||
_erbout = ''
|
||||
form_for(:post, @post) do |f|
|
||||
_erbout.concat f.text_field(:title)
|
||||
_erbout.concat f.text_area(:body)
|
||||
_erbout.concat f.check_box(:secret)
|
||||
end
|
||||
|
||||
expected =
|
||||
"<form action='http://www.example.com' method='post'>" +
|
||||
"<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
|
||||
"<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
|
||||
"<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
|
||||
"<input name='post[secret]' type='hidden' value='0' /><br/>" +
|
||||
"</form>"
|
||||
|
||||
assert_dom_equal expected, _erbout
|
||||
ensure
|
||||
ActionView::Base.default_form_builder = old_default_form_builder
|
||||
end
|
||||
|
||||
# Perhaps this test should be moved to prototype helper tests.
|
||||
def test_remote_form_for_with_labelled_builder
|
||||
self.extend ActionView::Helpers::PrototypeHelper
|
||||
|
||||
@@ -360,6 +360,25 @@ class FormOptionsHelperTest < Test::Unit::TestCase
|
||||
)
|
||||
end
|
||||
|
||||
def test_collection_select_with_multiple_option_appends_array_brackets
|
||||
@posts = [
|
||||
Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
|
||||
Post.new("Babe went home", "Babe", "To a little house", "shh!"),
|
||||
Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
|
||||
]
|
||||
|
||||
@post = Post.new
|
||||
@post.author_name = "Babe"
|
||||
|
||||
expected = "<select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>"
|
||||
|
||||
# Should suffix default name with [].
|
||||
assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, :multiple => true)
|
||||
|
||||
# Shouldn't suffix custom name with [].
|
||||
assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true)
|
||||
end
|
||||
|
||||
def test_country_select
|
||||
@post = Post.new
|
||||
@post.origin = "Denmark"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user