mirror of
https://github.com/github/rails.git
synced 2026-02-03 10:45:01 -05:00
Merge branch 'master' of git@github.com:rails/rails
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,4 +12,5 @@ actionpack/pkg
|
||||
actionmailer/pkg
|
||||
activesupport/pkg
|
||||
railties/pkg
|
||||
railties/test/500.html
|
||||
*.rbc
|
||||
|
||||
0
actionmailer/README
Executable file → Normal file
0
actionmailer/README
Executable file → Normal file
0
actionmailer/Rakefile
Executable file → Normal file
0
actionmailer/Rakefile
Executable file → Normal file
12
actionmailer/lib/action_mailer.rb
Executable file → Normal file
12
actionmailer/lib/action_mailer.rb
Executable file → Normal file
@@ -21,13 +21,13 @@
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
unless defined?(ActionController)
|
||||
begin
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib"
|
||||
begin
|
||||
require 'action_controller'
|
||||
rescue LoadError
|
||||
actionpack_path = "#{File.dirname(__FILE__)}/../../actionpack/lib"
|
||||
if File.directory?(actionpack_path)
|
||||
$:.unshift actionpack_path
|
||||
require 'action_controller'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
gem 'actionpack', '>= 1.12.5'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ module ActionMailer #:nodoc:
|
||||
# * <tt>:domain</tt> - If you need to specify a HELO domain, you can do it here.
|
||||
# * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.
|
||||
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
|
||||
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here.
|
||||
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here.
|
||||
# This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt>.
|
||||
#
|
||||
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
|
||||
@@ -233,10 +233,10 @@ module ActionMailer #:nodoc:
|
||||
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with <tt>delivery_method :test</tt>. Most useful
|
||||
# for unit and functional testing.
|
||||
#
|
||||
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
|
||||
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
|
||||
# pick a different charset from inside a method with +charset+.
|
||||
# * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
|
||||
# can also pick a different content type from inside a method with +content_type+.
|
||||
# can also pick a different content type from inside a method with +content_type+.
|
||||
# * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to <tt>1.0</tt>. You
|
||||
# can also pick a different value from inside a method with +mime_version+.
|
||||
# * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
|
||||
@@ -250,12 +250,9 @@ module ActionMailer #:nodoc:
|
||||
|
||||
private_class_method :new #:nodoc:
|
||||
|
||||
class_inheritable_accessor :template_root
|
||||
class_inheritable_accessor :view_paths
|
||||
cattr_accessor :logger
|
||||
|
||||
cattr_accessor :template_extensions
|
||||
@@template_extensions = ['erb', 'builder', 'rhtml', 'rxml']
|
||||
|
||||
@@smtp_settings = {
|
||||
:address => "localhost",
|
||||
:port => 25,
|
||||
@@ -414,19 +411,18 @@ module ActionMailer #:nodoc:
|
||||
new.deliver!(mail)
|
||||
end
|
||||
|
||||
# Register a template extension so mailer templates written in a
|
||||
# templating language other than rhtml or rxml are supported.
|
||||
# To use this, include in your template-language plugin's init
|
||||
# code or on a per-application basis, this can be invoked from
|
||||
# <tt>config/environment.rb</tt>:
|
||||
#
|
||||
# ActionMailer::Base.register_template_extension('haml')
|
||||
def register_template_extension(extension)
|
||||
template_extensions << extension
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"ActionMailer::Base.register_template_extension has been deprecated." +
|
||||
"Use ActionView::Base.register_template_extension instead", caller)
|
||||
end
|
||||
|
||||
def template_root
|
||||
self.view_paths && self.view_paths.first
|
||||
end
|
||||
|
||||
def template_root=(root)
|
||||
write_inheritable_attribute(:template_root, ActionView::PathSet.new(Array(root)))
|
||||
self.view_paths = ActionView::Base.process_view_paths(root)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -451,16 +447,18 @@ module ActionMailer #:nodoc:
|
||||
# "the_template_file.text.html.erb", etc.). Only do this if parts
|
||||
# have not already been specified manually.
|
||||
if @parts.empty?
|
||||
templates = Dir.glob("#{template_path}/#{@template}.*")
|
||||
templates.each do |path|
|
||||
basename = File.basename(path)
|
||||
template_regex = Regexp.new("^([^\\\.]+)\\\.([^\\\.]+\\\.[^\\\.]+)\\\.(" + template_extensions.join('|') + ")$")
|
||||
next unless md = template_regex.match(basename)
|
||||
template_name = basename
|
||||
content_type = md.captures[1].gsub('.', '/')
|
||||
@parts << Part.new(:content_type => content_type,
|
||||
:disposition => "inline", :charset => charset,
|
||||
:body => render_message(template_name, @body))
|
||||
Dir.glob("#{template_path}/#{@template}.*").each do |path|
|
||||
template = template_root["#{mailer_name}/#{File.basename(path)}"]
|
||||
|
||||
# Skip unless template has a multipart format
|
||||
next unless template.multipart?
|
||||
|
||||
@parts << Part.new(
|
||||
:content_type => template.content_type,
|
||||
:disposition => "inline",
|
||||
:charset => charset,
|
||||
:body => render_message(template, @body)
|
||||
)
|
||||
end
|
||||
unless @parts.empty?
|
||||
@content_type = "multipart/alternative"
|
||||
@@ -473,7 +471,7 @@ module ActionMailer #:nodoc:
|
||||
# normal template exists (or if there were no implicit parts) we render
|
||||
# it.
|
||||
template_exists = @parts.empty?
|
||||
template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| File.basename(i).split(".").length == 2 }
|
||||
template_exists ||= template_root["#{mailer_name}/#{@template}"]
|
||||
@body = render_message(@template, @body) if template_exists
|
||||
|
||||
# Finally, if there are other message parts and a textual body exists,
|
||||
@@ -529,24 +527,31 @@ module ActionMailer #:nodoc:
|
||||
end
|
||||
|
||||
def render_message(method_name, body)
|
||||
render :file => method_name, :body => body, :use_full_path => true
|
||||
render :file => method_name, :body => body
|
||||
end
|
||||
|
||||
def render(opts)
|
||||
body = opts.delete(:body)
|
||||
if opts[:file] && opts[:file] !~ /\//
|
||||
if opts[:file] && (opts[:file] !~ /\// && !opts[:file].respond_to?(:render))
|
||||
opts[:file] = "#{mailer_name}/#{opts[:file]}"
|
||||
end
|
||||
opts[:use_full_path] = true
|
||||
initialize_template_class(body).render(opts)
|
||||
end
|
||||
|
||||
def template_root
|
||||
self.class.template_root
|
||||
end
|
||||
|
||||
def template_root=(root)
|
||||
self.class.template_root = root
|
||||
end
|
||||
|
||||
def template_path
|
||||
"#{template_root}/#{mailer_name}"
|
||||
end
|
||||
|
||||
def initialize_template_class(assigns)
|
||||
ActionView::Base.new(template_root, assigns, self)
|
||||
ActionView::Base.new(view_paths, assigns, self)
|
||||
end
|
||||
|
||||
def sort_parts(parts, order = [])
|
||||
|
||||
@@ -38,7 +38,7 @@ module TMail
|
||||
# = Class Address
|
||||
#
|
||||
# Provides a complete handling library for email addresses. Can parse a string of an
|
||||
# address directly or take in preformatted addresses themseleves. Allows you to add
|
||||
# address directly or take in preformatted addresses themselves. Allows you to add
|
||||
# and remove phrases from the front of the address and provides a compare function for
|
||||
# email addresses.
|
||||
#
|
||||
@@ -143,7 +143,7 @@ module TMail
|
||||
|
||||
# This is to catch an unquoted "@" symbol in the local part of the
|
||||
# address. Handles addresses like <"@"@me.com> and makes sure they
|
||||
# stay like <"@"@me.com> (previously were becomming <@@me.com>)
|
||||
# stay like <"@"@me.com> (previously were becoming <@@me.com>)
|
||||
if local && (local.join == '@' || local.join =~ /\A[^"].*?@.*?[^"]\Z/)
|
||||
@local = "\"#{local.join}\""
|
||||
else
|
||||
|
||||
@@ -59,7 +59,7 @@ module TMail
|
||||
#
|
||||
# This is because a mailbox doesn't have the : after the From that designates the
|
||||
# beginning of the envelope sender (which can be different to the from address of
|
||||
# the emial)
|
||||
# the email)
|
||||
#
|
||||
# Other fields can be passed as normal, "Reply-To", "Received" etc.
|
||||
#
|
||||
|
||||
@@ -42,7 +42,7 @@ module TMail
|
||||
# Allows you to query the mail object with a string to get the contents
|
||||
# of the field you want.
|
||||
#
|
||||
# Returns a string of the exact contnts of the field
|
||||
# Returns a string of the exact contents of the field
|
||||
#
|
||||
# mail.from = "mikel <mikel@lindsaar.net>"
|
||||
# mail.header_string("From") #=> "mikel <mikel@lindsaar.net>"
|
||||
|
||||
@@ -255,7 +255,7 @@ module TMail
|
||||
alias fetch []
|
||||
|
||||
# Allows you to set or delete TMail header objects at will.
|
||||
# Eamples:
|
||||
# Examples:
|
||||
# @mail = TMail::Mail.new
|
||||
# @mail['to'].to_s # => 'mikel@test.com.au'
|
||||
# @mail['to'] = 'mikel@elsewhere.org'
|
||||
@@ -265,7 +265,7 @@ module TMail
|
||||
# @mail['to'].to_s # => nil
|
||||
# @mail.encoded # => "\r\n"
|
||||
#
|
||||
# Note: setting mail[] = nil actualy deletes the header field in question from the object,
|
||||
# Note: setting mail[] = nil actually deletes the header field in question from the object,
|
||||
# it does not just set the value of the hash to nil
|
||||
def []=( key, val )
|
||||
dkey = key.downcase
|
||||
|
||||
@@ -30,12 +30,20 @@ class Net::SMTP
|
||||
end
|
||||
end
|
||||
|
||||
# Wrap tests that use Mocha and skip if unavailable.
|
||||
def uses_mocha(test_name)
|
||||
gem 'mocha', ">=0.9.0"
|
||||
def uses_gem(gem_name, test_name, version = '> 0')
|
||||
require 'rubygems'
|
||||
gem gem_name.to_s, version
|
||||
require gem_name.to_s
|
||||
yield
|
||||
rescue Gem::LoadError
|
||||
$stderr.puts "Skipping #{test_name} tests (Mocha >= 0.5 is required). `gem install mocha` and try again."
|
||||
rescue LoadError
|
||||
$stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
|
||||
end
|
||||
|
||||
# Wrap tests that use Mocha and skip if unavailable.
|
||||
unless defined? uses_mocha
|
||||
def uses_mocha(test_name, &block)
|
||||
uses_gem('mocha', test_name, '>= 0.5.5', &block)
|
||||
end
|
||||
end
|
||||
|
||||
def set_delivery_method(delivery_method)
|
||||
|
||||
@@ -20,13 +20,13 @@ class RenderMailer < ActionMailer::Base
|
||||
subject "rendering rxml template"
|
||||
from "tester@example.com"
|
||||
end
|
||||
|
||||
|
||||
def included_subtemplate(recipient)
|
||||
recipients recipient
|
||||
subject "Including another template in the one being rendered"
|
||||
from "tester@example.com"
|
||||
end
|
||||
|
||||
|
||||
def included_old_subtemplate(recipient)
|
||||
recipients recipient
|
||||
subject "Including another template in the one being rendered"
|
||||
@@ -83,17 +83,11 @@ class RenderHelperTest < Test::Unit::TestCase
|
||||
mail = RenderMailer.deliver_rxml_template(@recipient)
|
||||
assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<test/>", mail.body.strip
|
||||
end
|
||||
|
||||
|
||||
def test_included_subtemplate
|
||||
mail = RenderMailer.deliver_included_subtemplate(@recipient)
|
||||
assert_equal "Hey Ho, let's go!", mail.body.strip
|
||||
end
|
||||
|
||||
def test_deprecated_old_subtemplate
|
||||
assert_raises ActionView::ActionViewError do
|
||||
RenderMailer.deliver_included_old_subtemplate(@recipient)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FirstSecondHelperTest < Test::Unit::TestCase
|
||||
|
||||
37
actionmailer/test/mail_service_test.rb
Executable file → Normal file
37
actionmailer/test/mail_service_test.rb
Executable file → Normal file
@@ -219,7 +219,7 @@ class TestMailer < ActionMailer::Base
|
||||
end
|
||||
attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
|
||||
end
|
||||
|
||||
|
||||
def nested_multipart_with_body(recipient)
|
||||
recipients recipient
|
||||
subject "nested multipart with body"
|
||||
@@ -321,7 +321,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
|
||||
assert_equal 2,created.parts.size
|
||||
assert_equal 2,created.parts.first.parts.size
|
||||
|
||||
|
||||
assert_equal "multipart/mixed", created.content_type
|
||||
assert_equal "multipart/alternative", created.parts.first.content_type
|
||||
assert_equal "bar", created.parts.first.header['foo'].to_s
|
||||
@@ -366,7 +366,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
end
|
||||
|
||||
|
||||
def test_custom_template
|
||||
expected = new_mail
|
||||
expected.to = @recipient
|
||||
@@ -382,7 +382,6 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_custom_templating_extension
|
||||
#
|
||||
# N.b., custom_templating_extension.text.plain.haml is expected to be in fixtures/test_mailer directory
|
||||
expected = new_mail
|
||||
expected.to = @recipient
|
||||
@@ -390,18 +389,10 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
expected.body = "Hello there, \n\nMr. #{@recipient}"
|
||||
expected.from = "system@loudthinking.com"
|
||||
expected.date = Time.local(2004, 12, 12)
|
||||
|
||||
|
||||
# Stub the render method so no alternative renderers need be present.
|
||||
ActionView::Base.any_instance.stubs(:render).returns("Hello there, \n\nMr. #{@recipient}")
|
||||
|
||||
# If the template is not registered, there should be no parts.
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
|
||||
assert_not_nil created
|
||||
assert_equal 0, created.parts.length
|
||||
|
||||
ActionMailer::Base.register_template_extension('haml')
|
||||
|
||||
|
||||
# Now that the template is registered, there should be one part. The text/plain part.
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
|
||||
@@ -428,7 +419,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
end
|
||||
|
||||
|
||||
def test_cc_bcc
|
||||
expected = new_mail
|
||||
expected.to = @recipient
|
||||
@@ -550,7 +541,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
TestMailer.deliver_signed_up(@recipient)
|
||||
assert_equal 1, ActionMailer::Base.deliveries.size
|
||||
end
|
||||
|
||||
|
||||
def test_doesnt_raise_errors_when_raise_delivery_errors_is_false
|
||||
ActionMailer::Base.raise_delivery_errors = false
|
||||
TestMailer.any_instance.expects(:perform_delivery_test).raises(Exception)
|
||||
@@ -670,7 +661,7 @@ EOF
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
end
|
||||
|
||||
|
||||
def test_utf8_body_is_not_quoted
|
||||
@recipient = "Foo áëô îü <extended@example.net>"
|
||||
expected = new_mail "utf-8"
|
||||
@@ -760,7 +751,7 @@ EOF
|
||||
mail = TestMailer.create_multipart_with_mime_version(@recipient)
|
||||
assert_equal "1.1", mail.mime_version
|
||||
end
|
||||
|
||||
|
||||
def test_multipart_with_utf8_subject
|
||||
mail = TestMailer.create_multipart_with_utf8_subject(@recipient)
|
||||
assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
|
||||
@@ -825,7 +816,7 @@ EOF
|
||||
mail = TestMailer.create_implicitly_multipart_example(@recipient, 'iso-8859-1')
|
||||
|
||||
assert_equal "multipart/alternative", mail.header['content-type'].body
|
||||
|
||||
|
||||
assert_equal 'iso-8859-1', mail.parts[0].sub_header("content-type", "charset")
|
||||
assert_equal 'iso-8859-1', mail.parts[1].sub_header("content-type", "charset")
|
||||
assert_equal 'iso-8859-1', mail.parts[2].sub_header("content-type", "charset")
|
||||
@@ -852,7 +843,7 @@ EOF
|
||||
assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
|
||||
assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body
|
||||
end
|
||||
|
||||
|
||||
def test_headers_removed_on_smtp_delivery
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
TestMailer.deliver_cc_bcc(@recipient)
|
||||
@@ -942,13 +933,13 @@ end # uses_mocha
|
||||
class InheritableTemplateRootTest < Test::Unit::TestCase
|
||||
def test_attr
|
||||
expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
|
||||
assert_equal [expected], FunkyPathMailer.template_root.map(&:to_s)
|
||||
assert_equal expected, FunkyPathMailer.template_root
|
||||
|
||||
sub = Class.new(FunkyPathMailer)
|
||||
sub.template_root = 'test/path'
|
||||
|
||||
assert_equal ['test/path'], sub.template_root.map(&:to_s)
|
||||
assert_equal [expected], FunkyPathMailer.template_root.map(&:to_s)
|
||||
assert_equal 'test/path', sub.template_root
|
||||
assert_equal expected, FunkyPathMailer.template_root
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
*Edge*
|
||||
|
||||
* Allow polymorphic_url helper to take url options. #880 [Tarmo Tänav]
|
||||
|
||||
* Switched integration test runner to use Rack processor instead of CGI [Josh Peek]
|
||||
|
||||
* Made AbstractRequest.if_modified_sense return nil if the header could not be parsed [Jamis Buck]
|
||||
|
||||
* Added back ActionController::Base.allow_concurrency flag [Josh Peek]
|
||||
|
||||
* AbstractRequest.relative_url_root is no longer automatically configured by a HTTP header. It can now be set in your configuration environment with config.action_controller.relative_url_root [Josh Peek]
|
||||
|
||||
* Update Prototype to 1.6.0.2 #599 [Patrick Joyce]
|
||||
|
||||
* Conditional GET utility methods. [Jeremy Kemper]
|
||||
response.last_modified = @post.updated_at
|
||||
response.etag = [:admin, @post, current_user]
|
||||
|
||||
if request.fresh?(response)
|
||||
head :not_modified
|
||||
else
|
||||
# render ...
|
||||
end
|
||||
|
||||
* All 2xx requests are considered successful [Josh Peek]
|
||||
|
||||
* Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run [DHH]
|
||||
|
||||
* Removed config.action_view.cache_template_loading, use config.cache_classes instead [Josh Peek]
|
||||
|
||||
* Get buffer for fragment cache from template's @output_buffer [Josh Peek]
|
||||
|
||||
* Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached [Josh Peek]
|
||||
|
||||
* Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class [Josh Peek]
|
||||
|
||||
@@ -31,7 +31,7 @@ http://www.rubyonrails.org.
|
||||
A short rundown of the major features:
|
||||
|
||||
* Actions grouped in controller as methods instead of separate command objects
|
||||
and can therefore share helper methods.
|
||||
and can therefore share helper methods
|
||||
|
||||
BlogController < ActionController::Base
|
||||
def show
|
||||
@@ -168,7 +168,7 @@ A short rundown of the major features:
|
||||
{Learn more}[link:classes/ActionController/Base.html]
|
||||
|
||||
|
||||
* Javascript and Ajax integration.
|
||||
* Javascript and Ajax integration
|
||||
|
||||
link_to_function "Greeting", "alert('Hello world!')"
|
||||
link_to_remote "Delete this post", :update => "posts",
|
||||
@@ -177,7 +177,7 @@ A short rundown of the major features:
|
||||
{Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
|
||||
|
||||
|
||||
* Pagination for navigating lists of results.
|
||||
* Pagination for navigating lists of results
|
||||
|
||||
# controller
|
||||
def list
|
||||
@@ -192,15 +192,9 @@ A short rundown of the major features:
|
||||
{Learn more}[link:classes/ActionController/Pagination.html]
|
||||
|
||||
|
||||
* Easy testing of both controller and template result through TestRequest/Response
|
||||
|
||||
class LoginControllerTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = LoginController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
end
|
||||
* Easy testing of both controller and rendered template through ActionController::TestCase
|
||||
|
||||
class LoginControllerTest < ActionController::TestCase
|
||||
def test_failing_authenticate
|
||||
process :authenticate, :user_name => "nop", :password => ""
|
||||
assert flash.has_key?(:alert)
|
||||
@@ -208,7 +202,7 @@ A short rundown of the major features:
|
||||
end
|
||||
end
|
||||
|
||||
{Learn more}[link:classes/ActionController/TestRequest.html]
|
||||
{Learn more}[link:classes/ActionController/TestCase.html]
|
||||
|
||||
|
||||
* Automated benchmarking and integrated logging
|
||||
|
||||
15
actionpack/lib/action_controller.rb
Executable file → Normal file
15
actionpack/lib/action_controller.rb
Executable file → Normal file
@@ -21,16 +21,13 @@
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
$:.unshift(File.dirname(__FILE__)) unless
|
||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
||||
|
||||
unless defined?(ActiveSupport)
|
||||
begin
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
||||
begin
|
||||
require 'active_support'
|
||||
rescue LoadError
|
||||
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
||||
if File.directory?(activesupport_path)
|
||||
$:.unshift activesupport_path
|
||||
require 'active_support'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
gem 'activesupport'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -87,11 +87,11 @@ module ActionController
|
||||
#
|
||||
def assert_template(expected = nil, message=nil)
|
||||
clean_backtrace do
|
||||
rendered = @response.rendered_template
|
||||
rendered = @response.rendered_template.to_s
|
||||
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
|
||||
assert_block(msg) do
|
||||
if expected.nil?
|
||||
@response.rendered_template.nil?
|
||||
@response.rendered_template.blank?
|
||||
else
|
||||
rendered.to_s.match(expected)
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ module ActionController
|
||||
module Assertions
|
||||
# Suite of assertions to test routes generated by Rails and the handling of requests made to them.
|
||||
module RoutingAssertions
|
||||
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
|
||||
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
|
||||
# match +path+. Basically, it asserts that Rails recognizes the route given by +expected_options+.
|
||||
#
|
||||
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
|
||||
@@ -14,16 +14,16 @@ module ActionController
|
||||
#
|
||||
# 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:
|
||||
# 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" })
|
||||
# assert_recognizes({:controller => 'items', :action => 'list', :id => '1', :view => 'print'}, 'items/list/1', { :view => "print" })
|
||||
#
|
||||
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
|
||||
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Check the default route (i.e., the index action)
|
||||
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
|
||||
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
|
||||
#
|
||||
# # Test a specific action
|
||||
# assert_recognizes({:controller => 'items', :action => 'list'}, 'items/list')
|
||||
@@ -44,16 +44,16 @@ module ActionController
|
||||
request_method = nil
|
||||
end
|
||||
|
||||
clean_backtrace do
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
clean_backtrace do
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
request = recognized_request_for(path, request_method)
|
||||
|
||||
|
||||
expected_options = expected_options.clone
|
||||
extras.each_key { |key| expected_options.delete key } unless extras.nil?
|
||||
|
||||
|
||||
expected_options.stringify_keys!
|
||||
routing_diff = expected_options.diff(request.path_parameters)
|
||||
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
|
||||
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
|
||||
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
|
||||
assert_block(msg) { request.path_parameters == expected_options }
|
||||
end
|
||||
@@ -64,7 +64,7 @@ module ActionController
|
||||
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
|
||||
#
|
||||
# The +defaults+ parameter is unused.
|
||||
#
|
||||
#
|
||||
# ==== Examples
|
||||
# # Asserts that the default action is generated for a route with no action
|
||||
# assert_generates("/items", :controller => "items", :action => "index")
|
||||
@@ -73,34 +73,34 @@ module ActionController
|
||||
# assert_generates("/items/list", :controller => "items", :action => "list")
|
||||
#
|
||||
# # Tests the generation of a route with a parameter
|
||||
# assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
|
||||
# assert_generates("/items/list/1", { :controller => "items", :action => "list", :id => "1" })
|
||||
#
|
||||
# # Asserts that the generated route gives us our custom route
|
||||
# assert_generates "changesets/12", { :controller => 'scm', :action => 'show_diff', :revision => "12" }
|
||||
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
|
||||
clean_backtrace do
|
||||
clean_backtrace do
|
||||
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
|
||||
# Load routes.rb if it hasn't been loaded.
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
||||
|
||||
generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults)
|
||||
found_extras = options.reject {|k, v| ! extra_keys.include? k}
|
||||
|
||||
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
|
||||
assert_block(msg) { found_extras == extras }
|
||||
|
||||
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
|
||||
|
||||
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
|
||||
expected_path)
|
||||
assert_block(msg) { expected_path == generated_path }
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
|
||||
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
|
||||
# <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
|
||||
# and +assert_generates+ into one step.
|
||||
#
|
||||
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
|
||||
# +message+ parameter allows you to specify a custom error message to display upon failure.
|
||||
# +message+ parameter allows you to specify a custom error message to display upon failure.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Assert a basic route: a controller with the default action (index)
|
||||
@@ -119,12 +119,12 @@ module ActionController
|
||||
# assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
|
||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
||||
assert_recognizes(options, path, extras, message)
|
||||
|
||||
controller, default_controller = options[:controller], defaults[:controller]
|
||||
|
||||
controller, default_controller = options[:controller], defaults[:controller]
|
||||
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
|
||||
options[:controller] = "/#{controller}"
|
||||
end
|
||||
|
||||
|
||||
assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message)
|
||||
end
|
||||
|
||||
|
||||
@@ -21,10 +21,8 @@ module ActionController
|
||||
# from the response HTML or elements selected by the enclosing assertion.
|
||||
#
|
||||
# In addition to HTML responses, you can make the following assertions:
|
||||
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and
|
||||
# insertion operations.
|
||||
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML,
|
||||
# for example for dealing with feed item descriptions.
|
||||
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and insertion operations.
|
||||
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions.
|
||||
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
|
||||
#
|
||||
# Also see HTML::Selector to learn how to use selectors.
|
||||
@@ -409,6 +407,7 @@ module ActionController
|
||||
|
||||
if rjs_type == :insert
|
||||
arg = args.shift
|
||||
position = arg
|
||||
insertion = "insert_#{arg}".to_sym
|
||||
raise ArgumentError, "Unknown RJS insertion type #{arg}" unless RJS_STATEMENTS[insertion]
|
||||
statement = "(#{RJS_STATEMENTS[insertion]})"
|
||||
@@ -420,6 +419,7 @@ module ActionController
|
||||
else
|
||||
statement = "#{RJS_STATEMENTS[:any]}"
|
||||
end
|
||||
position ||= Regexp.new(RJS_INSERTIONS.join('|'))
|
||||
|
||||
# Next argument we're looking for is the element identifier. If missing, we pick
|
||||
# any element.
|
||||
@@ -436,9 +436,14 @@ module ActionController
|
||||
Regexp.new("\\$\\(\"#{id}\"\\)#{statement}\\(#{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
|
||||
when :remove, :show, :hide, :toggle
|
||||
Regexp.new("#{statement}\\(\"#{id}\"\\)")
|
||||
else
|
||||
Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
|
||||
end
|
||||
when :replace, :replace_html
|
||||
Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)")
|
||||
when :insert, :insert_html
|
||||
Regexp.new("Element.insert\\(\\\"#{id}\\\", \\{ #{position}: #{RJS_PATTERN_HTML} \\}\\);")
|
||||
else
|
||||
Regexp.union(Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)"),
|
||||
Regexp.new("Element.insert\\(\\\"#{id}\\\", \\{ #{position}: #{RJS_PATTERN_HTML} \\}\\);"))
|
||||
end
|
||||
|
||||
# Duplicate the body since the next step involves destroying it.
|
||||
matches = nil
|
||||
@@ -447,7 +452,7 @@ module ActionController
|
||||
matches = @response.body.match(pattern)
|
||||
else
|
||||
@response.body.gsub(pattern) do |match|
|
||||
html = unescape_rjs($2)
|
||||
html = unescape_rjs(match)
|
||||
matches ||= []
|
||||
matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? }
|
||||
""
|
||||
@@ -587,17 +592,16 @@ module ActionController
|
||||
:hide => /Element\.hide/,
|
||||
:toggle => /Element\.toggle/
|
||||
}
|
||||
RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
|
||||
RJS_PATTERN_HTML = /"((\\"|[^"])*)"/
|
||||
RJS_INSERTIONS = [:top, :bottom, :before, :after]
|
||||
RJS_INSERTIONS.each do |insertion|
|
||||
RJS_STATEMENTS["insert_#{insertion}".to_sym] = Regexp.new(Regexp.quote("new Insertion.#{insertion.to_s.camelize}"))
|
||||
RJS_STATEMENTS["insert_#{insertion}".to_sym] = /Element.insert\(\"([^\"]*)\", \{ #{insertion.to_s.downcase}: #{RJS_PATTERN_HTML} \}\);/
|
||||
end
|
||||
RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
|
||||
RJS_STATEMENTS[:insert_html] = Regexp.new(RJS_INSERTIONS.collect do |insertion|
|
||||
Regexp.quote("new Insertion.#{insertion.to_s.camelize}")
|
||||
/Element.insert\(\"([^\"]*)\", \{ #{insertion.to_s.downcase}: #{RJS_PATTERN_HTML} \}\);/
|
||||
end.join('|'))
|
||||
RJS_PATTERN_HTML = /"((\\"|[^"])*)"/
|
||||
RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)",
|
||||
Regexp::MULTILINE)
|
||||
RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE)
|
||||
RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
|
||||
end
|
||||
|
||||
|
||||
105
actionpack/lib/action_controller/base.rb
Executable file → Normal file
105
actionpack/lib/action_controller/base.rb
Executable file → Normal file
@@ -283,6 +283,14 @@ module ActionController #:nodoc:
|
||||
@@debug_routes = true
|
||||
cattr_accessor :debug_routes
|
||||
|
||||
# Indicates whether to allow concurrent action processing. Your
|
||||
# controller actions and any other code they call must also behave well
|
||||
# when called from concurrent threads. Turned off by default.
|
||||
@@allow_concurrency = false
|
||||
cattr_accessor :allow_concurrency
|
||||
|
||||
@@guard = Monitor.new
|
||||
|
||||
# Modern REST web services often need to submit complex data to the web application.
|
||||
# The <tt>@@param_parsers</tt> hash lets you register handlers which will process the HTTP body and add parameters to the
|
||||
# <tt>params</tt> hash. These handlers are invoked for POST and PUT requests.
|
||||
@@ -354,6 +362,15 @@ module ActionController #:nodoc:
|
||||
class_inheritable_accessor :allow_forgery_protection
|
||||
self.allow_forgery_protection = true
|
||||
|
||||
# If you are deploying to a subdirectory, you will need to set
|
||||
# <tt>config.action_controller.relative_url_root</tt>
|
||||
# This defaults to ENV['RAILS_RELATIVE_URL_ROOT']
|
||||
cattr_writer :relative_url_root
|
||||
|
||||
def self.relative_url_root
|
||||
@@relative_url_root || ENV['RAILS_RELATIVE_URL_ROOT']
|
||||
end
|
||||
|
||||
# Holds the request object that's primarily used to get environment variables through access like
|
||||
# <tt>request.env["REQUEST_URI"]</tt>.
|
||||
attr_internal :request
|
||||
@@ -411,11 +428,7 @@ module ActionController #:nodoc:
|
||||
# By default, all methods defined in ActionController::Base and included modules are hidden.
|
||||
# More methods can be hidden using <tt>hide_actions</tt>.
|
||||
def hidden_actions
|
||||
unless read_inheritable_attribute(:hidden_actions)
|
||||
write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map { |m| m.to_s })
|
||||
end
|
||||
|
||||
read_inheritable_attribute(:hidden_actions)
|
||||
read_inheritable_attribute(:hidden_actions) || write_inheritable_attribute(:hidden_actions, [])
|
||||
end
|
||||
|
||||
# Hide each of the given methods from being callable as actions.
|
||||
@@ -519,6 +532,8 @@ module ActionController #:nodoc:
|
||||
public
|
||||
# Extracts the action_name from the request parameters and performs that action.
|
||||
def process(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
response.request = request
|
||||
|
||||
initialize_template_class(response)
|
||||
assign_shortcuts(request, response)
|
||||
initialize_current_url
|
||||
@@ -526,11 +541,13 @@ module ActionController #:nodoc:
|
||||
forget_variables_added_to_assigns
|
||||
|
||||
log_processing
|
||||
send(method, *arguments)
|
||||
|
||||
assign_default_content_type_and_charset
|
||||
if @@allow_concurrency
|
||||
send(method, *arguments)
|
||||
else
|
||||
@@guard.synchronize { send(method, *arguments) }
|
||||
end
|
||||
|
||||
response.request = request
|
||||
response.prepare! unless component_request?
|
||||
response
|
||||
ensure
|
||||
@@ -763,9 +780,6 @@ module ActionController #:nodoc:
|
||||
# render :file => "/path/to/some/template.erb", :layout => true, :status => 404
|
||||
# render :file => "c:/path/to/some/template.erb", :layout => true, :status => 404
|
||||
#
|
||||
# # Renders a template relative to the template root and chooses the proper file extension
|
||||
# render :file => "some/template", :use_full_path => true
|
||||
#
|
||||
# === Rendering text
|
||||
#
|
||||
# Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text
|
||||
@@ -896,21 +910,10 @@ module ActionController #:nodoc:
|
||||
response.content_type ||= Mime::JSON
|
||||
render_for_text(json, options[:status])
|
||||
|
||||
elsif partial = options[:partial]
|
||||
partial = default_template_name if partial == true
|
||||
elsif options[:partial]
|
||||
options[:partial] = default_template_name if options[:partial] == true
|
||||
add_variables_to_assigns
|
||||
|
||||
if collection = options[:collection]
|
||||
render_for_text(
|
||||
@template.send!(:render_partial_collection, partial, collection,
|
||||
options[:spacer_template], options[:locals], options[:as]), options[:status]
|
||||
)
|
||||
else
|
||||
render_for_text(
|
||||
@template.send!(:render_partial, partial,
|
||||
options[:object], options[:locals]), options[:status]
|
||||
)
|
||||
end
|
||||
render_for_text(@template.render(options), options[:status])
|
||||
|
||||
elsif options[:update]
|
||||
add_variables_to_assigns
|
||||
@@ -921,8 +924,7 @@ module ActionController #:nodoc:
|
||||
render_for_text(generator.to_s, options[:status])
|
||||
|
||||
elsif options[:nothing]
|
||||
# Safari doesn't pass the headers of the return if the response is zero length
|
||||
render_for_text(" ", options[:status])
|
||||
render_for_text(nil, options[:status])
|
||||
|
||||
else
|
||||
render_for_file(default_template_name, options[:status], true)
|
||||
@@ -968,6 +970,17 @@ module ActionController #:nodoc:
|
||||
render :nothing => true, :status => status
|
||||
end
|
||||
|
||||
# Sets the Last-Modified response header. Returns 304 Not Modified if the
|
||||
# If-Modified-Since request header is <= last modified.
|
||||
def last_modified!(utc_time)
|
||||
head(:not_modified) if response.last_modified!(utc_time)
|
||||
end
|
||||
|
||||
# Sets the ETag response header. Returns 304 Not Modified if the
|
||||
# If-None-Match request header matches.
|
||||
def etag!(etag)
|
||||
head(:not_modified) if response.etag!(etag)
|
||||
end
|
||||
|
||||
# Clears the rendered results, allowing for another render to be performed.
|
||||
def erase_render_results #:nodoc:
|
||||
@@ -1125,7 +1138,11 @@ module ActionController #:nodoc:
|
||||
response.body ||= ''
|
||||
response.body << text.to_s
|
||||
else
|
||||
response.body = text.is_a?(Proc) ? text : text.to_s
|
||||
response.body = case text
|
||||
when Proc then text
|
||||
when nil then " " # Safari doesn't pass the headers of the return if the response is zero length
|
||||
else text.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1155,7 +1172,7 @@ module ActionController #:nodoc:
|
||||
|
||||
def log_processing
|
||||
if logger && logger.info?
|
||||
logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
|
||||
logger.info "\n\nProcessing #{self.class.name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
|
||||
logger.info " Session ID: #{@_session.session_id}" if @_session and @_session.respond_to?(:session_id)
|
||||
logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(params).inspect : params.inspect}"
|
||||
end
|
||||
@@ -1166,16 +1183,16 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def perform_action
|
||||
if self.class.action_methods.include?(action_name)
|
||||
if action_methods.include?(action_name)
|
||||
send(action_name)
|
||||
default_render unless performed?
|
||||
elsif respond_to? :method_missing
|
||||
method_missing action_name
|
||||
default_render unless performed?
|
||||
elsif template_exists? && template_public?
|
||||
elsif template_exists?
|
||||
default_render
|
||||
else
|
||||
raise UnknownAction, "No action responded to #{action_name}", caller
|
||||
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1188,20 +1205,24 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def assign_default_content_type_and_charset
|
||||
response.content_type ||= Mime::HTML
|
||||
response.charset ||= self.class.default_charset unless sending_file?
|
||||
end
|
||||
|
||||
def sending_file?
|
||||
response.headers["Content-Transfer-Encoding"] == "binary"
|
||||
response.assign_default_content_type_and_charset!
|
||||
end
|
||||
deprecate :assign_default_content_type_and_charset => :'response.assign_default_content_type_and_charset!'
|
||||
|
||||
def action_methods
|
||||
self.class.action_methods
|
||||
end
|
||||
|
||||
def self.action_methods
|
||||
@action_methods ||= Set.new(public_instance_methods.map { |m| m.to_s }) - hidden_actions
|
||||
@action_methods ||=
|
||||
# All public instance methods of this class, including ancestors
|
||||
public_instance_methods(true).map { |m| m.to_s }.to_set -
|
||||
# Except for public instance methods of Base and its ancestors
|
||||
Base.public_instance_methods(true).map { |m| m.to_s } +
|
||||
# Be sure to include shadowed public instance methods of this class
|
||||
public_instance_methods(false).map { |m| m.to_s } -
|
||||
# And always exclude explicitly hidden actions
|
||||
hidden_actions
|
||||
end
|
||||
|
||||
def add_variables_to_assigns
|
||||
@@ -1243,13 +1264,11 @@ module ActionController #:nodoc:
|
||||
@template.file_exists?(template_name)
|
||||
end
|
||||
|
||||
def template_public?(template_name = default_template_name)
|
||||
@template.file_public?(template_name)
|
||||
end
|
||||
|
||||
def template_exempt_from_layout?(template_name = default_template_name)
|
||||
template_name = @template.pick_template(template_name).to_s if @template
|
||||
@@exempt_from_layout.any? { |ext| template_name =~ ext }
|
||||
rescue ActionView::MissingTemplate
|
||||
false
|
||||
end
|
||||
|
||||
def default_template_name(action_name = self.action_name)
|
||||
|
||||
@@ -2,7 +2,7 @@ module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
|
||||
# certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
|
||||
# parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
|
||||
# parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like:
|
||||
#
|
||||
# <b>Hello <%= @name %></b>
|
||||
# <% cache do %>
|
||||
@@ -60,10 +60,8 @@ module ActionController #:nodoc:
|
||||
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
|
||||
end
|
||||
|
||||
def fragment_for(block, name = {}, options = nil) #:nodoc:
|
||||
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
|
||||
if perform_caching
|
||||
buffer = yield
|
||||
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.concat(cache)
|
||||
else
|
||||
|
||||
@@ -43,7 +43,7 @@ module ActionController #:nodoc:
|
||||
:session_path => "/", # available to all paths in app
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true
|
||||
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
|
||||
}
|
||||
|
||||
def initialize(cgi, session_options = {})
|
||||
@cgi = cgi
|
||||
@@ -61,53 +61,14 @@ module ActionController #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||
# variable is already set, wrap it in a StringIO.
|
||||
def body
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
|
||||
StringIO.new(raw_post)
|
||||
else
|
||||
@cgi.stdinput
|
||||
end
|
||||
end
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||= self.class.parse_query_parameters(query_string)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@request_parameters ||= parse_formatted_request_parameters
|
||||
def body_stream #:nodoc:
|
||||
@cgi.stdinput
|
||||
end
|
||||
|
||||
def cookies
|
||||
@cgi.cookies.freeze
|
||||
end
|
||||
|
||||
def host_with_port_without_standard_port_handling
|
||||
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
||||
forwarded.split(/,\s?/).last
|
||||
elsif http_host = env['HTTP_HOST']
|
||||
http_host
|
||||
elsif server_name = env['SERVER_NAME']
|
||||
server_name
|
||||
else
|
||||
"#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
||||
end
|
||||
end
|
||||
|
||||
def host
|
||||
host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
|
||||
end
|
||||
|
||||
def port
|
||||
if host_with_port_without_standard_port_handling =~ /:(\d+)$/
|
||||
$1.to_i
|
||||
else
|
||||
standard_port
|
||||
end
|
||||
end
|
||||
|
||||
def session
|
||||
unless defined?(@session)
|
||||
if @session_options == false
|
||||
|
||||
@@ -22,6 +22,16 @@ module ActionController #:nodoc:
|
||||
#
|
||||
# cookies.delete :user_name
|
||||
#
|
||||
# Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
|
||||
#
|
||||
# cookies[:key] = {
|
||||
# :value => 'a yummy cookie',
|
||||
# :expires => 1.year.from_now,
|
||||
# :domain => 'domain.com'
|
||||
# }
|
||||
#
|
||||
# cookies.delete(:key, :domain => 'domain.com')
|
||||
#
|
||||
# The option symbols for setting cookies are:
|
||||
#
|
||||
# * <tt>:value</tt> - The cookie's value or list of values (as an array).
|
||||
|
||||
@@ -2,8 +2,6 @@ module ActionController
|
||||
# Dispatches requests to the appropriate controller and takes care of
|
||||
# reloading the app after each request when Dependencies.load? is true.
|
||||
class Dispatcher
|
||||
@@guard = Mutex.new
|
||||
|
||||
class << self
|
||||
def define_dispatcher_callbacks(cache_classes)
|
||||
unless cache_classes
|
||||
@@ -26,7 +24,7 @@ module ActionController
|
||||
to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers }
|
||||
end
|
||||
|
||||
after_dispatch :flush_logger if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush)
|
||||
after_dispatch :flush_logger if Base.logger && Base.logger.respond_to?(:flush)
|
||||
end
|
||||
|
||||
# Backward-compatible class method takes CGI-specific args. Deprecated
|
||||
@@ -46,7 +44,7 @@ module ActionController
|
||||
def to_prepare(identifier = nil, &block)
|
||||
@prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new
|
||||
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
|
||||
@prepare_dispatch_callbacks | callback
|
||||
@prepare_dispatch_callbacks.replace_or_append!(callback)
|
||||
end
|
||||
|
||||
# If the block raises, send status code as a last-ditch response.
|
||||
@@ -101,15 +99,13 @@ module ActionController
|
||||
end
|
||||
|
||||
def dispatch
|
||||
@@guard.synchronize do
|
||||
begin
|
||||
run_callbacks :before_dispatch
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
||||
end
|
||||
begin
|
||||
run_callbacks :before_dispatch
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
||||
end
|
||||
end
|
||||
|
||||
@@ -146,7 +142,7 @@ module ActionController
|
||||
end
|
||||
|
||||
def flush_logger
|
||||
RAILS_DEFAULT_LOGGER.flush
|
||||
Base.logger.flush
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
@@ -109,16 +109,17 @@ module ActionController #:nodoc:
|
||||
update_options! options
|
||||
end
|
||||
|
||||
# override these to return true in appropriate subclass
|
||||
def before?
|
||||
self.class == BeforeFilter
|
||||
false
|
||||
end
|
||||
|
||||
def after?
|
||||
self.class == AfterFilter
|
||||
false
|
||||
end
|
||||
|
||||
def around?
|
||||
self.class == AroundFilter
|
||||
false
|
||||
end
|
||||
|
||||
# Make sets of strings from :only/:except options
|
||||
@@ -170,6 +171,10 @@ module ActionController #:nodoc:
|
||||
:around
|
||||
end
|
||||
|
||||
def around?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
if should_run_callback?(controller)
|
||||
method = filter_responds_to_before_and_after? ? around_proc : self.method
|
||||
@@ -212,6 +217,10 @@ module ActionController #:nodoc:
|
||||
:before
|
||||
end
|
||||
|
||||
def before?
|
||||
true
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
super
|
||||
if controller.send!(:performed?)
|
||||
@@ -224,6 +233,10 @@ module ActionController #:nodoc:
|
||||
def type
|
||||
:after
|
||||
end
|
||||
|
||||
def after?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
require 'active_support/memoizable'
|
||||
|
||||
module ActionController
|
||||
module Http
|
||||
class Headers < ::Hash
|
||||
|
||||
def initialize(constructor = {})
|
||||
if constructor.is_a?(Hash)
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
def initialize(*args)
|
||||
if args.size == 1 && args[0].is_a?(Hash)
|
||||
super()
|
||||
update(constructor)
|
||||
update(args[0])
|
||||
else
|
||||
super(constructor)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def [](header_name)
|
||||
if include?(header_name)
|
||||
super
|
||||
super
|
||||
else
|
||||
super(normalize_header(header_name))
|
||||
super(env_name(header_name))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
private
|
||||
# Takes an HTTP header name and returns it in the
|
||||
# format
|
||||
def normalize_header(header_name)
|
||||
# Converts a HTTP header name to an environment variable name.
|
||||
def env_name(header_name)
|
||||
"HTTP_#{header_name.upcase.gsub(/-/, '_')}"
|
||||
end
|
||||
memoize :env_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -101,7 +101,7 @@ module ActionController
|
||||
@https = flag
|
||||
end
|
||||
|
||||
# Return +true+ if the session is mimicing a secure HTTPS request.
|
||||
# Return +true+ if the session is mimicking a secure HTTPS request.
|
||||
#
|
||||
# if session.https?
|
||||
# ...
|
||||
@@ -165,11 +165,19 @@ module ActionController
|
||||
status/100 == 3
|
||||
end
|
||||
|
||||
# Performs a GET request with the given parameters. The parameters may
|
||||
# be +nil+, a Hash, or a string that is appropriately encoded
|
||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
||||
# The headers should be a hash. The keys will automatically be upcased, with the
|
||||
# prefix 'HTTP_' added if needed.
|
||||
# Performs a GET request with the given parameters.
|
||||
#
|
||||
# - +path+: The URI (as a String) on which you want to perform a GET request.
|
||||
# - +parameters+: The HTTP parameters that you want to pass. This may be +nil+,
|
||||
# a Hash, or a String that is appropriately encoded
|
||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
||||
# - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
|
||||
# automatically be upcased, with the prefix 'HTTP_' added if needed.
|
||||
#
|
||||
# This method returns an AbstractResponse object, which one can use to inspect
|
||||
# the details of the response. Furthermore, if this method was called from an
|
||||
# ActionController::IntegrationTest object, then that object's <tt>@response</tt>
|
||||
# instance variable will point to the same response object.
|
||||
#
|
||||
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
|
||||
# +put+, +delete+, and +head+.
|
||||
@@ -220,21 +228,6 @@ module ActionController
|
||||
end
|
||||
|
||||
private
|
||||
class StubCGI < CGI #:nodoc:
|
||||
attr_accessor :stdinput, :stdoutput, :env_table
|
||||
|
||||
def initialize(env, stdinput = nil)
|
||||
self.env_table = env
|
||||
self.stdoutput = StringIO.new
|
||||
|
||||
super
|
||||
|
||||
stdinput.set_encoding(Encoding::BINARY) if stdinput.respond_to?(:set_encoding)
|
||||
stdinput.force_encoding(Encoding::BINARY) if stdinput.respond_to?(:force_encoding)
|
||||
@stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '')
|
||||
end
|
||||
end
|
||||
|
||||
# Tailors the session based on the given URI, setting the HTTPS value
|
||||
# and the hostname.
|
||||
def interpret_uri(path)
|
||||
@@ -282,9 +275,8 @@ module ActionController
|
||||
|
||||
ActionController::Base.clear_last_instantiation!
|
||||
|
||||
cgi = StubCGI.new(env, data)
|
||||
ActionController::Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
|
||||
@result = cgi.stdoutput.string
|
||||
env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
|
||||
@status, @headers, result_body = ActionController::Dispatcher.new.call(env)
|
||||
@request_count += 1
|
||||
|
||||
@controller = ActionController::Base.last_instantiation
|
||||
@@ -298,7 +290,29 @@ module ActionController
|
||||
|
||||
@html_document = nil
|
||||
|
||||
parse_result
|
||||
# Inject status back in for backwords compatibility with CGI
|
||||
@headers['Status'] = @status
|
||||
|
||||
@status, @status_message = @status.split(/ /)
|
||||
@status = @status.to_i
|
||||
|
||||
cgi_headers = Hash.new { |h,k| h[k] = [] }
|
||||
@headers.each do |key, value|
|
||||
cgi_headers[key.downcase] << value
|
||||
end
|
||||
cgi_headers['set-cookie'] = cgi_headers['set-cookie'].first
|
||||
@headers = cgi_headers
|
||||
|
||||
@response.headers['cookie'] ||= []
|
||||
(@headers['set-cookie'] || []).each do |cookie|
|
||||
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
|
||||
@cookies[name] = value
|
||||
|
||||
# Fake CGI cookie header
|
||||
# DEPRECATE: Use response.headers["Set-Cookie"] instead
|
||||
@response.headers['cookie'] << CGI::Cookie::new("name" => name, "value" => value)
|
||||
end
|
||||
|
||||
return status
|
||||
rescue MultiPartNeededException
|
||||
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
||||
@@ -306,26 +320,6 @@ module ActionController
|
||||
return status
|
||||
end
|
||||
|
||||
# Parses the result of the response and extracts the various values,
|
||||
# like cookies, status, headers, etc.
|
||||
def parse_result
|
||||
response_headers, result_body = @result.split(/\r\n\r\n/, 2)
|
||||
|
||||
@headers = Hash.new { |h,k| h[k] = [] }
|
||||
response_headers.to_s.each_line do |line|
|
||||
key, value = line.strip.split(/:\s*/, 2)
|
||||
@headers[key.downcase] << value
|
||||
end
|
||||
|
||||
(@headers['set-cookie'] || [] ).each do |string|
|
||||
name, value = string.match(/^([^=]*)=([^;]*);/)[1,2]
|
||||
@cookies[name] = value
|
||||
end
|
||||
|
||||
@status, @status_message = @headers["status"].first.to_s.split(/ /)
|
||||
@status = @status.to_i
|
||||
end
|
||||
|
||||
# Encode the cookies hash in a format suitable for passing to a
|
||||
# request.
|
||||
def encode_cookies
|
||||
@@ -336,13 +330,15 @@ module ActionController
|
||||
|
||||
# Get a temporary URL writer object
|
||||
def generic_url_rewriter
|
||||
cgi = StubCGI.new('REQUEST_METHOD' => "GET",
|
||||
'QUERY_STRING' => "",
|
||||
"REQUEST_URI" => "/",
|
||||
"HTTP_HOST" => host,
|
||||
"SERVER_PORT" => https? ? "443" : "80",
|
||||
"HTTPS" => https? ? "on" : "off")
|
||||
ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
|
||||
env = {
|
||||
'REQUEST_METHOD' => "GET",
|
||||
'QUERY_STRING' => "",
|
||||
"REQUEST_URI" => "/",
|
||||
"HTTP_HOST" => host,
|
||||
"SERVER_PORT" => https? ? "443" : "80",
|
||||
"HTTPS" => https? ? "on" : "off"
|
||||
}
|
||||
ActionController::UrlRewriter.new(ActionController::RackRequest.new(env), {})
|
||||
end
|
||||
|
||||
def name_with_prefix(prefix, name)
|
||||
@@ -443,7 +439,7 @@ EOF
|
||||
end
|
||||
|
||||
%w(get post put head delete cookies assigns
|
||||
xml_http_request get_via_redirect post_via_redirect).each do |method|
|
||||
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
|
||||
define_method(method) do |*args|
|
||||
reset! unless @integration_session
|
||||
# reset the html_document variable, but only for new get/post calls
|
||||
@@ -499,7 +495,7 @@ EOF
|
||||
# Delegate unhandled messages to the current session instance.
|
||||
def method_missing(sym, *args, &block)
|
||||
reset! unless @integration_session
|
||||
returning @integration_session.send!(sym, *args, &block) do
|
||||
returning @integration_session.__send__(sym, *args, &block) do
|
||||
copy_session_variables!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -102,6 +102,12 @@ module ActionController
|
||||
args << format if format
|
||||
|
||||
named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
|
||||
|
||||
url_options = options.except(:action, :routing_type, :format)
|
||||
unless url_options.empty?
|
||||
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
|
||||
end
|
||||
|
||||
send!(named_route, *args)
|
||||
end
|
||||
|
||||
@@ -114,19 +120,19 @@ module ActionController
|
||||
|
||||
%w(edit new formatted).each do |action|
|
||||
module_eval <<-EOT, __FILE__, __LINE__
|
||||
def #{action}_polymorphic_url(record_or_hash)
|
||||
polymorphic_url(record_or_hash, :action => "#{action}")
|
||||
def #{action}_polymorphic_url(record_or_hash, options = {})
|
||||
polymorphic_url(record_or_hash, options.merge(:action => "#{action}"))
|
||||
end
|
||||
|
||||
def #{action}_polymorphic_path(record_or_hash)
|
||||
polymorphic_url(record_or_hash, :action => "#{action}", :routing_type => :path)
|
||||
def #{action}_polymorphic_path(record_or_hash, options = {})
|
||||
polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path))
|
||||
end
|
||||
EOT
|
||||
end
|
||||
|
||||
private
|
||||
def action_prefix(options)
|
||||
options[:action] ? "#{options[:action]}_" : ""
|
||||
options[:action] ? "#{options[:action]}_" : options[:format] ? "formatted_" : ""
|
||||
end
|
||||
|
||||
def routing_type(options)
|
||||
|
||||
@@ -3,7 +3,7 @@ require 'action_controller/session/cookie_store'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
class RackRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :env, :session_options
|
||||
attr_accessor :session_options
|
||||
attr_reader :cgi
|
||||
|
||||
class SessionFixationAttempt < StandardError #:nodoc:
|
||||
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
|
||||
:session_path => "/", # available to all paths in app
|
||||
:session_key => "_session_id",
|
||||
:cookie_only => true
|
||||
} unless const_defined?(:DEFAULT_SESSION_OPTIONS)
|
||||
}
|
||||
|
||||
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
|
||||
@session_options = session_options
|
||||
@@ -24,41 +24,36 @@ module ActionController #:nodoc:
|
||||
super()
|
||||
end
|
||||
|
||||
%w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
|
||||
PATH_TRANSLATED QUERY_STRING REMOTE_HOST
|
||||
%w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
|
||||
PATH_TRANSLATED REMOTE_HOST
|
||||
REMOTE_IDENT REMOTE_USER SCRIPT_NAME
|
||||
SERVER_NAME SERVER_PROTOCOL
|
||||
|
||||
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
|
||||
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
|
||||
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
|
||||
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
|
||||
define_method(env.sub(/^HTTP_/n, '').downcase) do
|
||||
@env[env]
|
||||
end
|
||||
end
|
||||
|
||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||
# variable is already set, wrap it in a StringIO.
|
||||
def body
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
StringIO.new(raw_post)
|
||||
def query_string
|
||||
qs = super
|
||||
if !qs.blank?
|
||||
qs
|
||||
else
|
||||
@env['rack.input']
|
||||
@env['QUERY_STRING']
|
||||
end
|
||||
end
|
||||
|
||||
def body_stream #:nodoc:
|
||||
@env['rack.input']
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@env.key?(key)
|
||||
end
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||= self.class.parse_query_parameters(query_string)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@request_parameters ||= parse_formatted_request_parameters
|
||||
end
|
||||
|
||||
def cookies
|
||||
return {} unless @env["HTTP_COOKIE"]
|
||||
|
||||
@@ -70,38 +65,6 @@ module ActionController #:nodoc:
|
||||
@env["rack.request.cookie_hash"]
|
||||
end
|
||||
|
||||
def host_with_port_without_standard_port_handling
|
||||
if forwarded = @env["HTTP_X_FORWARDED_HOST"]
|
||||
forwarded.split(/,\s?/).last
|
||||
elsif http_host = @env['HTTP_HOST']
|
||||
http_host
|
||||
elsif server_name = @env['SERVER_NAME']
|
||||
server_name
|
||||
else
|
||||
"#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
||||
end
|
||||
end
|
||||
|
||||
def host
|
||||
host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
|
||||
end
|
||||
|
||||
def port
|
||||
if host_with_port_without_standard_port_handling =~ /:(\d+)$/
|
||||
$1.to_i
|
||||
else
|
||||
standard_port
|
||||
end
|
||||
end
|
||||
|
||||
def remote_addr
|
||||
@env['REMOTE_ADDR']
|
||||
end
|
||||
|
||||
def request_method
|
||||
@env['REQUEST_METHOD'].downcase.to_sym
|
||||
end
|
||||
|
||||
def server_port
|
||||
@env['SERVER_PORT'].to_i
|
||||
end
|
||||
@@ -189,23 +152,30 @@ end_msg
|
||||
end
|
||||
|
||||
class RackResponse < AbstractResponse #:nodoc:
|
||||
attr_accessor :status
|
||||
|
||||
def initialize(request)
|
||||
@request = request
|
||||
@cgi = request.cgi
|
||||
@writer = lambda { |x| @body << x }
|
||||
@block = nil
|
||||
super()
|
||||
end
|
||||
|
||||
# Retrieve status from instance variable if has already been delete
|
||||
def status
|
||||
@status || super
|
||||
end
|
||||
|
||||
def out(output = $stdout, &block)
|
||||
# Nasty hack because CGI sessions are closed after the normal
|
||||
# prepare! statement
|
||||
set_cookies!
|
||||
|
||||
@block = block
|
||||
normalize_headers(@headers)
|
||||
if [204, 304].include?(@status.to_i)
|
||||
@headers.delete "Content-Type"
|
||||
[status, @headers.to_hash, []]
|
||||
@status = headers.delete("Status")
|
||||
if [204, 304].include?(status.to_i)
|
||||
headers.delete("Content-Type")
|
||||
[status, headers.to_hash, []]
|
||||
else
|
||||
[status, @headers.to_hash, self]
|
||||
[status, headers.to_hash, self]
|
||||
end
|
||||
end
|
||||
alias to_a out
|
||||
@@ -237,43 +207,57 @@ end_msg
|
||||
@block == nil && @body.empty?
|
||||
end
|
||||
|
||||
def prepare!
|
||||
super
|
||||
|
||||
convert_language!
|
||||
convert_expires!
|
||||
set_status!
|
||||
# set_cookies!
|
||||
end
|
||||
|
||||
private
|
||||
def normalize_headers(options = "text/html")
|
||||
if options.is_a?(String)
|
||||
headers['Content-Type'] = options unless headers['Content-Type']
|
||||
else
|
||||
headers['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']
|
||||
def convert_language!
|
||||
headers["Content-Language"] = headers.delete("language") if headers["language"]
|
||||
end
|
||||
|
||||
headers['Content-Type'] = options.delete('type') || "text/html"
|
||||
headers['Content-Type'] += "; charset=" + options.delete('charset') if options['charset']
|
||||
def convert_expires!
|
||||
headers["Expires"] = headers.delete("") if headers["expires"]
|
||||
end
|
||||
|
||||
headers['Content-Language'] = options.delete('language') if options['language']
|
||||
headers['Expires'] = options.delete('expires') if options['expires']
|
||||
def convert_content_type!
|
||||
super
|
||||
headers['Content-Type'] = headers.delete('type') || "text/html"
|
||||
headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
|
||||
end
|
||||
|
||||
@status = options['Status'] || "200 OK"
|
||||
def set_content_length!
|
||||
super
|
||||
headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"]
|
||||
end
|
||||
|
||||
# Convert 'cookie' header to 'Set-Cookie' headers.
|
||||
# Because Set-Cookie header can appear more the once in the response body,
|
||||
# we store it in a line break seperated string that will be translated to
|
||||
# multiple Set-Cookie header by the handler.
|
||||
if cookie = options.delete('cookie')
|
||||
cookies = []
|
||||
def set_status!
|
||||
self.status ||= "200 OK"
|
||||
end
|
||||
|
||||
case cookie
|
||||
when Array then cookie.each { |c| cookies << c.to_s }
|
||||
when Hash then cookie.each { |_, c| cookies << c.to_s }
|
||||
else cookies << cookie.to_s
|
||||
end
|
||||
def set_cookies!
|
||||
# Convert 'cookie' header to 'Set-Cookie' headers.
|
||||
# Because Set-Cookie header can appear more the once in the response body,
|
||||
# we store it in a line break separated string that will be translated to
|
||||
# multiple Set-Cookie header by the handler.
|
||||
if cookie = headers.delete('cookie')
|
||||
cookies = []
|
||||
|
||||
@request.cgi.output_cookies.each { |c| cookies << c.to_s } if @request.cgi.output_cookies
|
||||
|
||||
headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
|
||||
case cookie
|
||||
when Array then cookie.each { |c| cookies << c.to_s }
|
||||
when Hash then cookie.each { |_, c| cookies << c.to_s }
|
||||
else cookies << cookie.to_s
|
||||
end
|
||||
|
||||
options.each { |k,v| headers[k] = v }
|
||||
end
|
||||
@cgi.output_cookies.each { |c| cookies << c.to_s } if @cgi.output_cookies
|
||||
|
||||
""
|
||||
headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
206
actionpack/lib/action_controller/request.rb
Executable file → Normal file
206
actionpack/lib/action_controller/request.rb
Executable file → Normal file
@@ -2,14 +2,21 @@ require 'tempfile'
|
||||
require 'stringio'
|
||||
require 'strscan'
|
||||
|
||||
module ActionController
|
||||
# HTTP methods which are accepted by default.
|
||||
ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
|
||||
require 'active_support/memoizable'
|
||||
|
||||
module ActionController
|
||||
# CgiRequest and TestRequest provide concrete implementations.
|
||||
class AbstractRequest
|
||||
cattr_accessor :relative_url_root
|
||||
remove_method :relative_url_root
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
def self.relative_url_root=(*args)
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"ActionController::AbstractRequest.relative_url_root= has been renamed." +
|
||||
"You can now set it with config.action_controller.relative_url_root=", caller)
|
||||
end
|
||||
|
||||
HTTP_METHODS = %w(get head put post delete options)
|
||||
HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
|
||||
|
||||
# The hash of environment variables for this request,
|
||||
# such as { 'RAILS_ENV' => 'production' }.
|
||||
@@ -18,15 +25,12 @@ module ActionController
|
||||
# The true HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
|
||||
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
|
||||
def request_method
|
||||
@request_method ||= begin
|
||||
method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase
|
||||
if ACCEPTED_HTTP_METHODS.include?(method)
|
||||
method.to_sym
|
||||
else
|
||||
raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}"
|
||||
end
|
||||
end
|
||||
method = @env['REQUEST_METHOD']
|
||||
method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
|
||||
|
||||
HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
|
||||
end
|
||||
memoize :request_method
|
||||
|
||||
# The HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
|
||||
# Note, HEAD is returned as <tt>:get</tt> since the two are functionally
|
||||
@@ -61,36 +65,62 @@ module ActionController
|
||||
request_method == :head
|
||||
end
|
||||
|
||||
# Provides acccess to the request's HTTP headers, for example:
|
||||
# Provides access to the request's HTTP headers, for example:
|
||||
# request.headers["Content-Type"] # => "text/plain"
|
||||
def headers
|
||||
@headers ||= ActionController::Http::Headers.new(@env)
|
||||
ActionController::Http::Headers.new(@env)
|
||||
end
|
||||
memoize :headers
|
||||
|
||||
def content_length
|
||||
@content_length ||= env['CONTENT_LENGTH'].to_i
|
||||
@env['CONTENT_LENGTH'].to_i
|
||||
end
|
||||
memoize :content_length
|
||||
|
||||
# The MIME type of the HTTP request, such as Mime::XML.
|
||||
#
|
||||
# For backward compatibility, the post format is extracted from the
|
||||
# X-Post-Data-Format HTTP header if present.
|
||||
def content_type
|
||||
@content_type ||= Mime::Type.lookup(content_type_without_parameters)
|
||||
Mime::Type.lookup(content_type_without_parameters)
|
||||
end
|
||||
memoize :content_type
|
||||
|
||||
# Returns the accepted MIME type for the request
|
||||
def accepts
|
||||
@accepts ||=
|
||||
begin
|
||||
header = @env['HTTP_ACCEPT'].to_s.strip
|
||||
header = @env['HTTP_ACCEPT'].to_s.strip
|
||||
|
||||
if header.empty?
|
||||
[content_type, Mime::ALL].compact
|
||||
else
|
||||
Mime::Type.parse(header)
|
||||
end
|
||||
end
|
||||
if header.empty?
|
||||
[content_type, Mime::ALL].compact
|
||||
else
|
||||
Mime::Type.parse(header)
|
||||
end
|
||||
end
|
||||
memoize :accepts
|
||||
|
||||
def if_modified_since
|
||||
if since = env['HTTP_IF_MODIFIED_SINCE']
|
||||
Time.rfc2822(since) rescue nil
|
||||
end
|
||||
end
|
||||
memoize :if_modified_since
|
||||
|
||||
def if_none_match
|
||||
env['HTTP_IF_NONE_MATCH']
|
||||
end
|
||||
|
||||
def not_modified?(modified_at)
|
||||
if_modified_since && modified_at && if_modified_since >= modified_at
|
||||
end
|
||||
|
||||
def etag_matches?(etag)
|
||||
if_none_match && if_none_match == etag
|
||||
end
|
||||
|
||||
# Check response freshness (Last-Modified and ETag) against request
|
||||
# If-Modified-Since and If-None-Match conditions.
|
||||
def fresh?(response)
|
||||
not_modified?(response.last_modified) || etag_matches?(response.etag)
|
||||
end
|
||||
|
||||
# Returns the Mime type for the format used in the request.
|
||||
@@ -99,7 +129,7 @@ module ActionController
|
||||
# GET /posts/5.xhtml | request.format => Mime::HTML
|
||||
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
|
||||
def format
|
||||
@format ||= begin
|
||||
@format ||=
|
||||
if parameters[:format]
|
||||
Mime::Type.lookup_by_extension(parameters[:format])
|
||||
elsif ActionController::Base.use_accept_header
|
||||
@@ -109,16 +139,15 @@ module ActionController
|
||||
else
|
||||
Mime::Type.lookup_by_extension("html")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
# Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension.
|
||||
# Example:
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# before_filter :adjust_format_for_iphone
|
||||
#
|
||||
#
|
||||
# private
|
||||
# def adjust_format_for_iphone
|
||||
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
|
||||
@@ -197,42 +226,62 @@ EOM
|
||||
|
||||
@env['REMOTE_ADDR']
|
||||
end
|
||||
memoize :remote_ip
|
||||
|
||||
# Returns the lowercase name of the HTTP server software.
|
||||
def server_software
|
||||
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
|
||||
end
|
||||
memoize :server_software
|
||||
|
||||
|
||||
# Returns the complete URL used for this request
|
||||
def url
|
||||
protocol + host_with_port + request_uri
|
||||
end
|
||||
memoize :url
|
||||
|
||||
# Return 'https://' if this is an SSL request and 'http://' otherwise.
|
||||
def protocol
|
||||
ssl? ? 'https://' : 'http://'
|
||||
end
|
||||
memoize :protocol
|
||||
|
||||
# Is this an SSL request?
|
||||
def ssl?
|
||||
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
||||
end
|
||||
|
||||
def raw_host_with_port
|
||||
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
||||
forwarded.split(/,\s?/).last
|
||||
else
|
||||
env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the host for this request, such as example.com.
|
||||
def host
|
||||
raw_host_with_port.sub(/:\d+$/, '')
|
||||
end
|
||||
memoize :host
|
||||
|
||||
# Returns a host:port string for this request, such as example.com or
|
||||
# example.com:8080.
|
||||
def host_with_port
|
||||
@host_with_port ||= host + port_string
|
||||
"#{host}#{port_string}"
|
||||
end
|
||||
memoize :host_with_port
|
||||
|
||||
# Returns the port number of this request as an integer.
|
||||
def port
|
||||
@port_as_int ||= @env['SERVER_PORT'].to_i
|
||||
if raw_host_with_port =~ /:(\d+)$/
|
||||
$1.to_i
|
||||
else
|
||||
standard_port
|
||||
end
|
||||
end
|
||||
memoize :port
|
||||
|
||||
# Returns the standard port number for this request's protocol
|
||||
def standard_port
|
||||
@@ -245,7 +294,7 @@ EOM
|
||||
# Returns a port suffix like ":8080" if the port number of this request
|
||||
# is not the default HTTP port 80 or HTTPS port 443.
|
||||
def port_string
|
||||
(port == standard_port) ? '' : ":#{port}"
|
||||
port == standard_port ? '' : ":#{port}"
|
||||
end
|
||||
|
||||
# Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
|
||||
@@ -265,7 +314,7 @@ EOM
|
||||
parts[0..-(tld_length+2)]
|
||||
end
|
||||
|
||||
# Return the query string, accounting for server idiosyncracies.
|
||||
# Return the query string, accounting for server idiosyncrasies.
|
||||
def query_string
|
||||
if uri = @env['REQUEST_URI']
|
||||
uri.split('?', 2)[1] || ''
|
||||
@@ -273,8 +322,9 @@ EOM
|
||||
@env['QUERY_STRING'] || ''
|
||||
end
|
||||
end
|
||||
memoize :query_string
|
||||
|
||||
# Return the request URI, accounting for server idiosyncracies.
|
||||
# Return the request URI, accounting for server idiosyncrasies.
|
||||
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
||||
def request_uri
|
||||
if uri = @env['REQUEST_URI']
|
||||
@@ -282,46 +332,33 @@ EOM
|
||||
(%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
|
||||
uri = @env['PATH_INFO'].to_s
|
||||
|
||||
if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
|
||||
uri = uri.sub(/#{script_filename}\//, '')
|
||||
end
|
||||
|
||||
if uri.nil?
|
||||
env_qs = @env['QUERY_STRING'].to_s
|
||||
uri += "?#{env_qs}" unless env_qs.empty?
|
||||
|
||||
if uri.blank?
|
||||
@env.delete('REQUEST_URI')
|
||||
uri
|
||||
else
|
||||
@env['REQUEST_URI'] = uri
|
||||
end
|
||||
end
|
||||
end
|
||||
memoize :request_uri
|
||||
|
||||
# Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
|
||||
def path
|
||||
path = (uri = request_uri) ? uri.split('?').first.to_s : ''
|
||||
|
||||
# Cut off the path to the installation directory if given
|
||||
path.sub!(%r/^#{relative_url_root}/, '')
|
||||
path || ''
|
||||
path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
|
||||
path || ''
|
||||
end
|
||||
|
||||
# Returns the path minus the web server relative installation directory.
|
||||
# This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
|
||||
# It can be automatically extracted for Apache setups. If the server is not
|
||||
# Apache, this method returns an empty string.
|
||||
def relative_url_root
|
||||
@@relative_url_root ||= case
|
||||
when @env["RAILS_RELATIVE_URL_ROOT"]
|
||||
@env["RAILS_RELATIVE_URL_ROOT"]
|
||||
when server_software == 'apache'
|
||||
@env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
memoize :path
|
||||
|
||||
# Read the request body. This is useful for web services that need to
|
||||
# work with raw requests directly.
|
||||
@@ -343,34 +380,56 @@ EOM
|
||||
@symbolized_path_parameters = @parameters = nil
|
||||
end
|
||||
|
||||
# The same as <tt>path_parameters</tt> with explicitly symbolized keys
|
||||
def symbolized_path_parameters
|
||||
# The same as <tt>path_parameters</tt> with explicitly symbolized keys
|
||||
def symbolized_path_parameters
|
||||
@symbolized_path_parameters ||= path_parameters.symbolize_keys
|
||||
end
|
||||
|
||||
# Returns a hash with the parameters used to form the path of the request.
|
||||
# Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||
#
|
||||
# Example:
|
||||
# Example:
|
||||
#
|
||||
# {'action' => 'my_action', 'controller' => 'my_controller'}
|
||||
def path_parameters
|
||||
@path_parameters ||= {}
|
||||
end
|
||||
|
||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||
# variable is already set, wrap it in a StringIO.
|
||||
def body
|
||||
if raw_post = env['RAW_POST_DATA']
|
||||
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
|
||||
StringIO.new(raw_post)
|
||||
else
|
||||
body_stream
|
||||
end
|
||||
end
|
||||
|
||||
def remote_addr
|
||||
@env['REMOTE_ADDR']
|
||||
end
|
||||
|
||||
def referrer
|
||||
@env['HTTP_REFERER']
|
||||
end
|
||||
alias referer referrer
|
||||
|
||||
|
||||
def query_parameters
|
||||
@query_parameters ||= self.class.parse_query_parameters(query_string)
|
||||
end
|
||||
|
||||
def request_parameters
|
||||
@request_parameters ||= parse_formatted_request_parameters
|
||||
end
|
||||
|
||||
|
||||
#--
|
||||
# Must be implemented in the concrete request
|
||||
#++
|
||||
|
||||
# The request body is an IO input stream.
|
||||
def body
|
||||
end
|
||||
|
||||
def query_parameters #:nodoc:
|
||||
end
|
||||
|
||||
def request_parameters #:nodoc:
|
||||
def body_stream #:nodoc:
|
||||
end
|
||||
|
||||
def cookies #:nodoc:
|
||||
@@ -397,8 +456,9 @@ EOM
|
||||
|
||||
# The raw content type string with its parameters stripped off.
|
||||
def content_type_without_parameters
|
||||
@content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
|
||||
self.class.extract_content_type_without_parameters(content_type_with_parameters)
|
||||
end
|
||||
memoize :content_type_without_parameters
|
||||
|
||||
private
|
||||
def content_type_from_legacy_post_data_format_header
|
||||
|
||||
@@ -17,7 +17,7 @@ module ActionController #:nodoc:
|
||||
# forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all
|
||||
# forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
|
||||
# HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
|
||||
# scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway.
|
||||
# scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
|
||||
#
|
||||
# This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an
|
||||
# ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in
|
||||
|
||||
0
actionpack/lib/action_controller/request_profiler.rb
Executable file → Normal file
0
actionpack/lib/action_controller/request_profiler.rb
Executable file → Normal file
@@ -112,19 +112,23 @@ module ActionController #:nodoc:
|
||||
protected
|
||||
# Exception handler called when the performance of an action raises an exception.
|
||||
def rescue_action(exception)
|
||||
log_error(exception) if logger
|
||||
erase_results if performed?
|
||||
|
||||
# Let the exception alter the response if it wants.
|
||||
# For example, MethodNotAllowed sets the Allow header.
|
||||
if exception.respond_to?(:handle_response!)
|
||||
exception.handle_response!(response)
|
||||
end
|
||||
|
||||
if consider_all_requests_local || local_request?
|
||||
rescue_action_locally(exception)
|
||||
if handler_for_rescue(exception)
|
||||
rescue_action_with_handler(exception)
|
||||
else
|
||||
rescue_action_in_public(exception)
|
||||
log_error(exception) if logger
|
||||
erase_results if performed?
|
||||
|
||||
# Let the exception alter the response if it wants.
|
||||
# For example, MethodNotAllowed sets the Allow header.
|
||||
if exception.respond_to?(:handle_response!)
|
||||
exception.handle_response!(response)
|
||||
end
|
||||
|
||||
if consider_all_requests_local || local_request?
|
||||
rescue_action_locally(exception)
|
||||
else
|
||||
rescue_action_in_public(exception)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -178,7 +182,7 @@ module ActionController #:nodoc:
|
||||
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
|
||||
@template.send!(:assign_variables_from_controller)
|
||||
|
||||
@template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception), :use_full_path => false))
|
||||
@template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
|
||||
|
||||
response.content_type = Mime::HTML
|
||||
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
|
||||
@@ -200,7 +204,7 @@ module ActionController #:nodoc:
|
||||
def perform_action_with_rescue #:nodoc:
|
||||
perform_action_without_rescue
|
||||
rescue Exception => exception
|
||||
rescue_action_with_handler(exception) || rescue_action(exception)
|
||||
rescue_action(exception)
|
||||
end
|
||||
|
||||
def rescues_path(template_name)
|
||||
|
||||
@@ -14,10 +14,10 @@ module ActionController
|
||||
#
|
||||
# === The Different Methods and their Usage
|
||||
#
|
||||
# +GET+ Requests for a resource, no saving or editing of a resource should occur in a GET request
|
||||
# +POST+ Creation of resources
|
||||
# +PUT+ Editing of attributes on a resource
|
||||
# +DELETE+ Deletion of a resource
|
||||
# [+GET+] Requests for a resource, no saving or editing of a resource should occur in a GET request
|
||||
# [+POST+] Creation of resources
|
||||
# [+PUT+] Editing of attributes on a resource
|
||||
# [+DELETE+] Deletion of a resource
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
@@ -296,6 +296,10 @@ module ActionController
|
||||
# article_comments_url(:article_id => @article)
|
||||
# article_comment_url(:article_id => @article, :id => @comment)
|
||||
#
|
||||
# If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
|
||||
#
|
||||
# articles_comments_url(@comment.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.
|
||||
#
|
||||
@@ -303,13 +307,13 @@ module ActionController
|
||||
# map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
|
||||
#
|
||||
# You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested resource:
|
||||
#
|
||||
#
|
||||
# map.resources :articles do |article|
|
||||
# article.resources :comments, :name_prefix => nil
|
||||
# end
|
||||
#
|
||||
# end
|
||||
#
|
||||
# This will yield named resources like so:
|
||||
#
|
||||
#
|
||||
# comments_url(@article)
|
||||
# comment_url(@article, @comment)
|
||||
#
|
||||
@@ -477,8 +481,7 @@ module ActionController
|
||||
resource.collection_methods.each do |method, actions|
|
||||
actions.each do |action|
|
||||
action_options = action_options_for(action, resource, method)
|
||||
map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -491,18 +494,15 @@ module ActionController
|
||||
index_route_name << "_index"
|
||||
end
|
||||
|
||||
map.named_route(index_route_name, resource.path, index_action_options)
|
||||
map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options)
|
||||
map_named_routes(map, index_route_name, resource.path, index_action_options)
|
||||
|
||||
create_action_options = action_options_for("create", resource)
|
||||
map.connect(resource.path, create_action_options)
|
||||
map.connect("#{resource.path}.:format", create_action_options)
|
||||
map_unnamed_routes(map, resource.path, 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)
|
||||
map_unnamed_routes(map, resource.path, create_action_options)
|
||||
end
|
||||
|
||||
def map_new_actions(map, resource)
|
||||
@@ -510,11 +510,9 @@ module ActionController
|
||||
actions.each do |action|
|
||||
action_options = action_options_for(action, resource, method)
|
||||
if action == :new
|
||||
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)
|
||||
map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
|
||||
else
|
||||
map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
|
||||
map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
|
||||
map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -528,22 +526,28 @@ module ActionController
|
||||
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
||||
action_path ||= Base.resources_path_names[action] || action
|
||||
|
||||
map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
|
||||
map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options)
|
||||
map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
|
||||
end
|
||||
end
|
||||
|
||||
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_named_routes(map, "#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
|
||||
|
||||
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)
|
||||
map_unnamed_routes(map, resource.member_path, 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)
|
||||
map_unnamed_routes(map, resource.member_path, destroy_action_options)
|
||||
end
|
||||
|
||||
def map_unnamed_routes(map, path_without_format, options)
|
||||
map.connect(path_without_format, options)
|
||||
map.connect("#{path_without_format}.:format", options)
|
||||
end
|
||||
|
||||
def map_named_routes(map, name, path_without_format, options)
|
||||
map.named_route(name, path_without_format, options)
|
||||
map.named_route("formatted_#{name}", "#{path_without_format}.:format", options)
|
||||
end
|
||||
|
||||
def add_conditions_for(conditions, method)
|
||||
@@ -555,6 +559,7 @@ module ActionController
|
||||
def action_options_for(action, resource, method = nil)
|
||||
default_options = { :action => action.to_s }
|
||||
require_id = !resource.kind_of?(SingletonResource)
|
||||
|
||||
case default_options[:action]
|
||||
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
|
||||
when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
|
||||
@@ -569,4 +574,4 @@ end
|
||||
|
||||
class ActionController::Routing::RouteSet::Mapper
|
||||
include ActionController::Resources
|
||||
end
|
||||
end
|
||||
151
actionpack/lib/action_controller/response.rb
Executable file → Normal file
151
actionpack/lib/action_controller/response.rb
Executable file → Normal file
@@ -1,58 +1,161 @@
|
||||
require 'digest/md5'
|
||||
|
||||
module ActionController
|
||||
class AbstractResponse #:nodoc:
|
||||
module ActionController # :nodoc:
|
||||
# Represents an HTTP response generated by a controller action. One can use an
|
||||
# ActionController::AbstractResponse object to retrieve the current state of the
|
||||
# response, or customize the response. An AbstractResponse object can either
|
||||
# represent a "real" HTTP response (i.e. one that is meant to be sent back to the
|
||||
# web browser) or a test response (i.e. one that is generated from integration
|
||||
# tests). See CgiResponse and TestResponse, respectively.
|
||||
#
|
||||
# AbstractResponse is mostly a Ruby on Rails framework implement detail, and should
|
||||
# never be used directly in controllers. Controllers should use the methods defined
|
||||
# in ActionController::Base instead. For example, if you want to set the HTTP
|
||||
# response's content MIME type, then use ActionControllerBase#headers instead of
|
||||
# AbstractResponse#headers.
|
||||
#
|
||||
# Nevertheless, integration tests may want to inspect controller responses in more
|
||||
# detail, and that's when AbstractResponse can be useful for application developers.
|
||||
# Integration test methods such as ActionController::Integration::Session#get and
|
||||
# ActionController::Integration::Session#post return objects of type TestResponse
|
||||
# (which are of course also of type AbstractResponse).
|
||||
#
|
||||
# For example, the following demo integration "test" prints the body of the
|
||||
# controller response to the console:
|
||||
#
|
||||
# class DemoControllerTest < ActionController::IntegrationTest
|
||||
# def test_print_root_path_to_console
|
||||
# get('/')
|
||||
# puts @response.body
|
||||
# end
|
||||
# end
|
||||
class AbstractResponse
|
||||
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
||||
attr_accessor :request
|
||||
attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
|
||||
|
||||
# The body content (e.g. HTML) of the response, as a String.
|
||||
attr_accessor :body
|
||||
# The headers of the response, as a Hash. It maps header names to header values.
|
||||
attr_accessor :headers
|
||||
attr_accessor :session, :cookies, :assigns, :template, :layout
|
||||
attr_accessor :redirected_to, :redirected_to_method_params
|
||||
|
||||
delegate :default_charset, :to => 'ActionController::Base'
|
||||
|
||||
def initialize
|
||||
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
|
||||
end
|
||||
|
||||
def status; headers['Status'] end
|
||||
def status=(status) headers['Status'] = status end
|
||||
|
||||
def location; headers['Location'] end
|
||||
def location=(url) headers['Location'] = url end
|
||||
|
||||
|
||||
# Sets the HTTP response's content MIME type. For example, in the controller
|
||||
# you could write this:
|
||||
#
|
||||
# response.content_type = "text/plain"
|
||||
#
|
||||
# If a character set has been defined for this response (see charset=) then
|
||||
# the character set information will also be included in the content type
|
||||
# information.
|
||||
def content_type=(mime_type)
|
||||
self.headers["Content-Type"] = charset ? "#{mime_type}; charset=#{charset}" : mime_type
|
||||
self.headers["Content-Type"] =
|
||||
if mime_type =~ /charset/ || (c = charset).nil?
|
||||
mime_type.to_s
|
||||
else
|
||||
"#{mime_type}; charset=#{c}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns the response's content MIME type, or nil if content type has been set.
|
||||
def content_type
|
||||
content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
|
||||
content_type.blank? ? nil : content_type
|
||||
end
|
||||
|
||||
def charset=(encoding)
|
||||
self.headers["Content-Type"] = "#{content_type || Mime::HTML}; charset=#{encoding}"
|
||||
|
||||
# Set the charset of the Content-Type header. Set to nil to remove it.
|
||||
# If no content type is set, it defaults to HTML.
|
||||
def charset=(charset)
|
||||
headers["Content-Type"] =
|
||||
if charset
|
||||
"#{content_type || Mime::HTML}; charset=#{charset}"
|
||||
else
|
||||
content_type || Mime::HTML.to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def charset
|
||||
charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
|
||||
charset.blank? ? nil : charset.strip.split("=")[1]
|
||||
end
|
||||
|
||||
def redirect(to_url, response_status)
|
||||
self.headers["Status"] = response_status
|
||||
self.headers["Location"] = to_url
|
||||
def last_modified
|
||||
if last = headers['Last-Modified']
|
||||
Time.httpdate(last)
|
||||
end
|
||||
end
|
||||
|
||||
self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
|
||||
def last_modified?
|
||||
headers.include?('Last-Modified')
|
||||
end
|
||||
|
||||
def last_modified=(utc_time)
|
||||
headers['Last-Modified'] = utc_time.httpdate
|
||||
end
|
||||
|
||||
def etag; headers['ETag'] end
|
||||
def etag?; headers.include?('ETag') end
|
||||
def etag=(etag)
|
||||
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
|
||||
end
|
||||
|
||||
def redirect(url, status)
|
||||
self.status = status
|
||||
self.location = url
|
||||
self.body = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
|
||||
end
|
||||
|
||||
def sending_file?
|
||||
headers["Content-Transfer-Encoding"] == "binary"
|
||||
end
|
||||
|
||||
def assign_default_content_type_and_charset!
|
||||
self.content_type ||= Mime::HTML
|
||||
self.charset ||= default_charset unless sending_file?
|
||||
end
|
||||
|
||||
def prepare!
|
||||
assign_default_content_type_and_charset!
|
||||
set_content_length!
|
||||
handle_conditional_get!
|
||||
convert_content_type!
|
||||
set_content_length!
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def handle_conditional_get!
|
||||
if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty?
|
||||
self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}")
|
||||
self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
|
||||
|
||||
if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
|
||||
self.headers['Status'] = '304 Not Modified'
|
||||
if nonempty_ok_response?
|
||||
self.etag ||= body
|
||||
if request && request.etag_matches?(etag)
|
||||
self.status = '304 Not Modified'
|
||||
self.body = ''
|
||||
end
|
||||
end
|
||||
|
||||
set_conditional_cache_control! if etag? || last_modified?
|
||||
end
|
||||
|
||||
def nonempty_ok_response?
|
||||
ok = !status || status[0..2] == '200'
|
||||
ok && body.is_a?(String) && !body.empty?
|
||||
end
|
||||
|
||||
def set_conditional_cache_control!
|
||||
if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
|
||||
headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
|
||||
end
|
||||
end
|
||||
|
||||
def convert_content_type!
|
||||
@@ -70,7 +173,9 @@ module ActionController
|
||||
# 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!
|
||||
self.headers["Content-Length"] = body.size unless body.respond_to?(:call)
|
||||
unless body.respond_to?(:call) || (status && status[0..2] == '304')
|
||||
self.headers["Content-Length"] ||= body.size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -201,7 +201,7 @@ module ActionController
|
||||
# With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
|
||||
#
|
||||
# * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
|
||||
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
|
||||
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
|
||||
# <tt>:any</tt> means that any method can access the route.
|
||||
#
|
||||
# Example:
|
||||
@@ -213,7 +213,7 @@ module ActionController
|
||||
#
|
||||
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
|
||||
# URL will route to the <tt>show</tt> action.
|
||||
#
|
||||
#
|
||||
# == Reloading routes
|
||||
#
|
||||
# You can reload routes if you feel you must:
|
||||
@@ -281,9 +281,9 @@ module ActionController
|
||||
end
|
||||
|
||||
class << self
|
||||
# Expects an array of controller names as the first argument.
|
||||
# Executes the passed block with only the named controllers named available.
|
||||
# This method is used in internal Rails testing.
|
||||
# Expects an array of controller names as the first argument.
|
||||
# Executes the passed block with only the named controllers named available.
|
||||
# This method is used in internal Rails testing.
|
||||
def with_controllers(names)
|
||||
prior_controllers = @possible_controllers
|
||||
use_controllers! names
|
||||
@@ -292,10 +292,10 @@ module ActionController
|
||||
use_controllers! prior_controllers
|
||||
end
|
||||
|
||||
# Returns an array of paths, cleaned of double-slashes and relative path references.
|
||||
# * "\\\" and "//" become "\\" or "/".
|
||||
# * "/foo/bar/../config" becomes "/foo/config".
|
||||
# The returned array is sorted by length, descending.
|
||||
# Returns an array of paths, cleaned of double-slashes and relative path references.
|
||||
# * "\\\" and "//" become "\\" or "/".
|
||||
# * "/foo/bar/../config" becomes "/foo/config".
|
||||
# The returned array is sorted by length, descending.
|
||||
def normalize_paths(paths)
|
||||
# do the hokey-pokey of path normalization...
|
||||
paths = paths.collect do |path|
|
||||
@@ -314,7 +314,7 @@ module ActionController
|
||||
paths = paths.uniq.sort_by { |path| - path.length }
|
||||
end
|
||||
|
||||
# Returns the array of controller names currently available to ActionController::Routing.
|
||||
# Returns the array of controller names currently available to ActionController::Routing.
|
||||
def possible_controllers
|
||||
unless @possible_controllers
|
||||
@possible_controllers = []
|
||||
@@ -339,28 +339,27 @@ module ActionController
|
||||
@possible_controllers
|
||||
end
|
||||
|
||||
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
|
||||
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
|
||||
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
|
||||
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
|
||||
def use_controllers!(controller_names)
|
||||
@possible_controllers = controller_names
|
||||
end
|
||||
|
||||
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
|
||||
# Handles 4 scenarios:
|
||||
#
|
||||
# * stay in the previous controller:
|
||||
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
|
||||
#
|
||||
# * stay in the previous namespace:
|
||||
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
|
||||
#
|
||||
# * forced move to the root namespace:
|
||||
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
|
||||
#
|
||||
# * previous namespace is root:
|
||||
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
|
||||
#
|
||||
|
||||
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
|
||||
# Handles 4 scenarios:
|
||||
#
|
||||
# * stay in the previous controller:
|
||||
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
|
||||
#
|
||||
# * stay in the previous namespace:
|
||||
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
|
||||
#
|
||||
# * forced move to the root namespace:
|
||||
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
|
||||
#
|
||||
# * previous namespace is root:
|
||||
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
|
||||
#
|
||||
def controller_relative_to(controller, previous)
|
||||
if controller.nil? then previous
|
||||
elsif controller[0] == ?/ then controller[1..-1]
|
||||
@@ -369,12 +368,11 @@ module ActionController
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Routes = RouteSet.new
|
||||
|
||||
ActiveSupport::Inflector.module_eval do
|
||||
# Ensures that routes are reloaded when Rails inflections are updated.
|
||||
# Ensures that routes are reloaded when Rails inflections are updated.
|
||||
def inflections_with_route_reloading(&block)
|
||||
returning(inflections_without_route_reloading(&block)) {
|
||||
ActionController::Routing::Routes.reload! if block_given?
|
||||
|
||||
@@ -48,14 +48,10 @@ module ActionController
|
||||
end
|
||||
when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
|
||||
when /\A\?(.*?)\?/
|
||||
returning segment = StaticSegment.new($1) do
|
||||
segment.is_optional = true
|
||||
end
|
||||
StaticSegment.new($1, :optional => true)
|
||||
when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
|
||||
when Regexp.new(separator_pattern) then
|
||||
returning segment = DividerSegment.new($&) do
|
||||
segment.is_optional = (optional_separators.include? $&)
|
||||
end
|
||||
DividerSegment.new($&, :optional => (optional_separators.include? $&))
|
||||
end
|
||||
[segment, $~.post_match]
|
||||
end
|
||||
@@ -76,6 +72,8 @@ module ActionController
|
||||
defaults = (options.delete(:defaults) || {}).dup
|
||||
conditions = (options.delete(:conditions) || {}).dup
|
||||
|
||||
validate_route_conditions(conditions)
|
||||
|
||||
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
|
||||
options.each do |key, value|
|
||||
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
|
||||
@@ -174,30 +172,30 @@ module ActionController
|
||||
defaults, requirements, conditions = divide_route_options(segments, options)
|
||||
requirements = assign_route_options(segments, defaults, requirements)
|
||||
|
||||
route = Route.new
|
||||
# TODO: Segments should be frozen on initialize
|
||||
segments.each { |segment| segment.freeze }
|
||||
|
||||
route.segments = segments
|
||||
route.requirements = requirements
|
||||
route.conditions = conditions
|
||||
|
||||
if !route.significant_keys.include?(:action) && !route.requirements[:action]
|
||||
route.requirements[:action] = "index"
|
||||
route.significant_keys << :action
|
||||
end
|
||||
|
||||
# Routes cannot use the current string interpolation method
|
||||
# if there are user-supplied <tt>:requirements</tt> as the interpolation
|
||||
# code won't raise RoutingErrors when generating
|
||||
if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
|
||||
route.optimise = false
|
||||
end
|
||||
route = Route.new(segments, requirements, conditions)
|
||||
|
||||
if !route.significant_keys.include?(:controller)
|
||||
raise ArgumentError, "Illegal route: the :controller must be specified!"
|
||||
end
|
||||
|
||||
route
|
||||
route.freeze
|
||||
end
|
||||
|
||||
private
|
||||
def validate_route_conditions(conditions)
|
||||
if method = conditions[:method]
|
||||
if method == :head
|
||||
raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
|
||||
end
|
||||
|
||||
unless HTTP_METHODS.include?(method.to_sym)
|
||||
raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
module ActionController
|
||||
module Routing
|
||||
# Much of the slow performance from routes comes from the
|
||||
# Much of the slow performance from routes comes from the
|
||||
# complexity of expiry, <tt>:requirements</tt> matching, defaults providing
|
||||
# and figuring out which url pattern to use. With named routes
|
||||
# we can avoid the expense of finding the right route. So if
|
||||
# and figuring out which url pattern to use. With named routes
|
||||
# we can avoid the expense of finding the right route. So if
|
||||
# they've provided the right number of arguments, and have no
|
||||
# <tt>:requirements</tt>, we can just build up a string and return it.
|
||||
#
|
||||
# To support building optimisations for other common cases, the
|
||||
# generation code is separated into several classes
|
||||
#
|
||||
# To support building optimisations for other common cases, the
|
||||
# generation code is separated into several classes
|
||||
module Optimisation
|
||||
def generate_optimisation_block(route, kind)
|
||||
return "" unless route.optimise?
|
||||
@@ -20,6 +20,7 @@ module ActionController
|
||||
|
||||
class Optimiser
|
||||
attr_reader :route, :kind
|
||||
|
||||
def initialize(route, kind)
|
||||
@route = route
|
||||
@kind = kind
|
||||
@@ -53,12 +54,12 @@ module ActionController
|
||||
# map.person '/people/:id'
|
||||
#
|
||||
# If the user calls <tt>person_url(@person)</tt>, we can simply
|
||||
# return a string like "/people/#{@person.to_param}"
|
||||
# return a string like "/people/#{@person.to_param}"
|
||||
# rather than triggering the expensive logic in +url_for+.
|
||||
class PositionalArguments < Optimiser
|
||||
def guard_condition
|
||||
number_of_arguments = route.segment_keys.size
|
||||
# if they're using foo_url(:id=>2) it's one
|
||||
# if they're using foo_url(:id=>2) it's one
|
||||
# argument, but we don't want to generate /foos/id2
|
||||
if number_of_arguments == 1
|
||||
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)"
|
||||
@@ -76,7 +77,7 @@ module ActionController
|
||||
elements << '#{request.host_with_port}'
|
||||
end
|
||||
|
||||
elements << '#{request.relative_url_root if request.relative_url_root}'
|
||||
elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
|
||||
|
||||
# The last entry in <tt>route.segments</tt> appears to *always* be a
|
||||
# 'divider segment' for '/' but we have assertions to ensure that
|
||||
@@ -94,14 +95,14 @@ module ActionController
|
||||
end
|
||||
|
||||
# This case is mostly the same as the positional arguments case
|
||||
# above, but it supports additional query parameters as the last
|
||||
# above, but it supports additional query parameters as the last
|
||||
# argument
|
||||
class PositionalArgumentsWithAdditionalParams < PositionalArguments
|
||||
def guard_condition
|
||||
"(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)"
|
||||
end
|
||||
|
||||
# This case uses almost the same code as positional arguments,
|
||||
# This case uses almost the same code as positional arguments,
|
||||
# but add an args.last.to_query on the end
|
||||
def generation_code
|
||||
super.insert(-2, '?#{args.last.to_query}')
|
||||
@@ -110,7 +111,7 @@ module ActionController
|
||||
# To avoid generating "http://localhost/?host=foo.example.com" we
|
||||
# can't use this optimisation on routes without any segments
|
||||
def applicable?
|
||||
super && route.segment_keys.size > 0
|
||||
super && route.segment_keys.size > 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ module ActionController
|
||||
# 3) segm test for /users/:id
|
||||
# (jump to list index = 5)
|
||||
# 4) full test for /users/:id => here we are!
|
||||
|
||||
class RouteSet
|
||||
def recognize_path(path, environment={})
|
||||
result = recognize_optimized(path, environment) and return result
|
||||
@@ -68,28 +67,6 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
def recognize_optimized(path, env)
|
||||
write_recognize_optimized
|
||||
recognize_optimized(path, env)
|
||||
end
|
||||
|
||||
def write_recognize_optimized
|
||||
tree = segment_tree(routes)
|
||||
body = generate_code(tree)
|
||||
instance_eval %{
|
||||
def recognize_optimized(path, env)
|
||||
segments = to_plain_segments(path)
|
||||
index = #{body}
|
||||
return nil unless index
|
||||
while index < routes.size
|
||||
result = routes[index].recognize(path, env) and return result
|
||||
index += 1
|
||||
end
|
||||
nil
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
end
|
||||
|
||||
def segment_tree(routes)
|
||||
tree = [0]
|
||||
|
||||
@@ -153,6 +130,23 @@ module ActionController
|
||||
segments
|
||||
end
|
||||
|
||||
private
|
||||
def write_recognize_optimized!
|
||||
tree = segment_tree(routes)
|
||||
body = generate_code(tree)
|
||||
instance_eval %{
|
||||
def recognize_optimized(path, env)
|
||||
segments = to_plain_segments(path)
|
||||
index = #{body}
|
||||
return nil unless index
|
||||
while index < routes.size
|
||||
result = routes[index].recognize(path, env) and return result
|
||||
index += 1
|
||||
end
|
||||
nil
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,11 +3,25 @@ module ActionController
|
||||
class Route #:nodoc:
|
||||
attr_accessor :segments, :requirements, :conditions, :optimise
|
||||
|
||||
def initialize
|
||||
@segments = []
|
||||
@requirements = {}
|
||||
@conditions = {}
|
||||
@optimise = true
|
||||
def initialize(segments = [], requirements = {}, conditions = {})
|
||||
@segments = segments
|
||||
@requirements = requirements
|
||||
@conditions = conditions
|
||||
|
||||
if !significant_keys.include?(:action) && !requirements[:action]
|
||||
@requirements[:action] = "index"
|
||||
@significant_keys << :action
|
||||
end
|
||||
|
||||
# Routes cannot use the current string interpolation method
|
||||
# if there are user-supplied <tt>:requirements</tt> as the interpolation
|
||||
# code won't raise RoutingErrors when generating
|
||||
has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
|
||||
if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
|
||||
@optimise = false
|
||||
else
|
||||
@optimise = true
|
||||
end
|
||||
end
|
||||
|
||||
# Indicates whether the routes should be optimised with the string interpolation
|
||||
@@ -22,129 +36,6 @@ module ActionController
|
||||
end.compact
|
||||
end
|
||||
|
||||
# Write and compile a +generate+ method for this Route.
|
||||
def write_generation
|
||||
# Build the main body of the generation
|
||||
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
|
||||
|
||||
# If we have conditions that must be tested first, nest the body inside an if
|
||||
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
|
||||
args = "options, hash, expire_on = {}"
|
||||
|
||||
# Nest the body inside of a def block, and then compile it.
|
||||
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
|
||||
# are the same as the keys that were recalled from the previous request. Thus,
|
||||
# we can use the expire_on.keys to determine which keys ought to be used to build
|
||||
# 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(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(options)]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
raw_method
|
||||
end
|
||||
|
||||
# Build several lines of code that extract values from the options hash. If any
|
||||
# of the values are missing or rejected then a return will be executed.
|
||||
def generation_extraction
|
||||
segments.collect do |segment|
|
||||
segment.extraction_code
|
||||
end.compact * "\n"
|
||||
end
|
||||
|
||||
# Produce a condition expression that will check the requirements of this route
|
||||
# upon generation.
|
||||
def generation_requirements
|
||||
requirement_conditions = requirements.collect do |key, req|
|
||||
if req.is_a? Regexp
|
||||
value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
|
||||
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
|
||||
else
|
||||
"hash[:#{key}] == #{req.inspect}"
|
||||
end
|
||||
end
|
||||
requirement_conditions * ' && ' unless requirement_conditions.empty?
|
||||
end
|
||||
|
||||
def generation_structure
|
||||
segments.last.string_structure segments[0..-2]
|
||||
end
|
||||
|
||||
# Write and compile a +recognize+ method for this Route.
|
||||
def write_recognition
|
||||
# Create an if structure to extract the params from a match if it occurs.
|
||||
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
|
||||
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
|
||||
|
||||
# Build the method declaration and compile it
|
||||
method_decl = "def recognize(path, env={})\n#{body}\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
method_decl
|
||||
end
|
||||
|
||||
# Plugins may override this method to add other conditions, like checks on
|
||||
# host, subdomain, and so forth. Note that changes here only affect route
|
||||
# recognition, not generation.
|
||||
def recognition_conditions
|
||||
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
|
||||
result << "conditions[:method] === env[:method]" if conditions[:method]
|
||||
result
|
||||
end
|
||||
|
||||
# Build the regular expression pattern that will match this route.
|
||||
def recognition_pattern(wrap = true)
|
||||
pattern = ''
|
||||
segments.reverse_each do |segment|
|
||||
pattern = segment.build_pattern pattern
|
||||
end
|
||||
wrap ? ("\\A" + pattern + "\\Z") : pattern
|
||||
end
|
||||
|
||||
# Write the code to extract the parameters from a matched route.
|
||||
def recognition_extraction
|
||||
next_capture = 1
|
||||
extraction = segments.collect do |segment|
|
||||
x = segment.match_extraction(next_capture)
|
||||
next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
|
||||
x
|
||||
end
|
||||
extraction.compact
|
||||
end
|
||||
|
||||
# Write the real generation implementation and then resend the message.
|
||||
def generate(options, hash, expire_on = {})
|
||||
write_generation
|
||||
generate options, hash, expire_on
|
||||
end
|
||||
|
||||
def generate_extras(options, hash, expire_on = {})
|
||||
write_generation
|
||||
generate_extras options, hash, expire_on
|
||||
end
|
||||
|
||||
# Generate the query string with any extra keys in the hash and append
|
||||
# it to the given path, returning the new path.
|
||||
def append_query_string(path, hash, query_keys=nil)
|
||||
return nil unless path
|
||||
query_keys ||= extra_keys(hash)
|
||||
"#{path}#{build_query_string(hash, query_keys)}"
|
||||
end
|
||||
|
||||
# Determine which keys in the given hash are "extra". Extra keys are
|
||||
# those that were not used to generate a particular route. The extra
|
||||
# keys also do not include those recalled from the prior request, nor
|
||||
# do they include any keys that were implied in the route (like a
|
||||
# <tt>:controller</tt> that is required, but not explicitly used in the
|
||||
# text of the route.)
|
||||
def extra_keys(hash, recall={})
|
||||
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
|
||||
end
|
||||
|
||||
# Build a query string from the keys of the given hash. If +only_keys+
|
||||
# is given (as an array), only the keys indicated will be used to build
|
||||
# the query string. The query string will correctly build array parameter
|
||||
@@ -161,12 +52,6 @@ module ActionController
|
||||
elements.empty? ? '' : "?#{elements.sort * '&'}"
|
||||
end
|
||||
|
||||
# Write the real recognition implementation and then resend the message.
|
||||
def recognize(path, environment={})
|
||||
write_recognition
|
||||
recognize path, environment
|
||||
end
|
||||
|
||||
# A route's parameter shell contains parameter values that are not in the
|
||||
# route's path, but should be placed in the recognized hash.
|
||||
#
|
||||
@@ -186,7 +71,7 @@ module ActionController
|
||||
# includes keys that appear inside the path, and keys that have requirements
|
||||
# placed upon them.
|
||||
def significant_keys
|
||||
@significant_keys ||= returning [] do |sk|
|
||||
@significant_keys ||= returning([]) do |sk|
|
||||
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
|
||||
sk.concat requirements.keys
|
||||
sk.uniq!
|
||||
@@ -209,12 +94,7 @@ module ActionController
|
||||
end
|
||||
|
||||
def matches_controller_and_action?(controller, action)
|
||||
unless defined? @matching_prepared
|
||||
@controller_requirement = requirement_for(:controller)
|
||||
@action_requirement = requirement_for(:action)
|
||||
@matching_prepared = true
|
||||
end
|
||||
|
||||
prepare_matching!
|
||||
(@controller_requirement.nil? || @controller_requirement === controller) &&
|
||||
(@action_requirement.nil? || @action_requirement === action)
|
||||
end
|
||||
@@ -226,15 +106,150 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def requirement_for(key)
|
||||
return requirements[key] if requirements.key? key
|
||||
segments.each do |segment|
|
||||
return segment.regexp if segment.respond_to?(:key) && segment.key == key
|
||||
# TODO: Route should be prepared and frozen on initialize
|
||||
def freeze
|
||||
unless frozen?
|
||||
write_generation!
|
||||
write_recognition!
|
||||
prepare_matching!
|
||||
|
||||
parameter_shell
|
||||
significant_keys
|
||||
defaults
|
||||
to_s
|
||||
end
|
||||
nil
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
def requirement_for(key)
|
||||
return requirements[key] if requirements.key? key
|
||||
segments.each do |segment|
|
||||
return segment.regexp if segment.respond_to?(:key) && segment.key == key
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# Write and compile a +generate+ method for this Route.
|
||||
def write_generation!
|
||||
# Build the main body of the generation
|
||||
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
|
||||
|
||||
# If we have conditions that must be tested first, nest the body inside an if
|
||||
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
|
||||
args = "options, hash, expire_on = {}"
|
||||
|
||||
# Nest the body inside of a def block, and then compile it.
|
||||
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
|
||||
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
|
||||
# are the same as the keys that were recalled from the previous request. Thus,
|
||||
# we can use the expire_on.keys to determine which keys ought to be used to build
|
||||
# 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(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(options)]\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
raw_method
|
||||
end
|
||||
|
||||
# Build several lines of code that extract values from the options hash. If any
|
||||
# of the values are missing or rejected then a return will be executed.
|
||||
def generation_extraction
|
||||
segments.collect do |segment|
|
||||
segment.extraction_code
|
||||
end.compact * "\n"
|
||||
end
|
||||
|
||||
# Produce a condition expression that will check the requirements of this route
|
||||
# upon generation.
|
||||
def generation_requirements
|
||||
requirement_conditions = requirements.collect do |key, req|
|
||||
if req.is_a? Regexp
|
||||
value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
|
||||
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
|
||||
else
|
||||
"hash[:#{key}] == #{req.inspect}"
|
||||
end
|
||||
end
|
||||
requirement_conditions * ' && ' unless requirement_conditions.empty?
|
||||
end
|
||||
|
||||
def generation_structure
|
||||
segments.last.string_structure segments[0..-2]
|
||||
end
|
||||
|
||||
# Write and compile a +recognize+ method for this Route.
|
||||
def write_recognition!
|
||||
# Create an if structure to extract the params from a match if it occurs.
|
||||
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
|
||||
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
|
||||
|
||||
# Build the method declaration and compile it
|
||||
method_decl = "def recognize(path, env = {})\n#{body}\nend"
|
||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
||||
method_decl
|
||||
end
|
||||
|
||||
# Plugins may override this method to add other conditions, like checks on
|
||||
# host, subdomain, and so forth. Note that changes here only affect route
|
||||
# recognition, not generation.
|
||||
def recognition_conditions
|
||||
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
|
||||
result << "conditions[:method] === env[:method]" if conditions[:method]
|
||||
result
|
||||
end
|
||||
|
||||
# Build the regular expression pattern that will match this route.
|
||||
def recognition_pattern(wrap = true)
|
||||
pattern = ''
|
||||
segments.reverse_each do |segment|
|
||||
pattern = segment.build_pattern pattern
|
||||
end
|
||||
wrap ? ("\\A" + pattern + "\\Z") : pattern
|
||||
end
|
||||
|
||||
# Write the code to extract the parameters from a matched route.
|
||||
def recognition_extraction
|
||||
next_capture = 1
|
||||
extraction = segments.collect do |segment|
|
||||
x = segment.match_extraction(next_capture)
|
||||
next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
|
||||
x
|
||||
end
|
||||
extraction.compact
|
||||
end
|
||||
|
||||
# Generate the query string with any extra keys in the hash and append
|
||||
# it to the given path, returning the new path.
|
||||
def append_query_string(path, hash, query_keys = nil)
|
||||
return nil unless path
|
||||
query_keys ||= extra_keys(hash)
|
||||
"#{path}#{build_query_string(hash, query_keys)}"
|
||||
end
|
||||
|
||||
# Determine which keys in the given hash are "extra". Extra keys are
|
||||
# those that were not used to generate a particular route. The extra
|
||||
# keys also do not include those recalled from the prior request, nor
|
||||
# do they include any keys that were implied in the route (like a
|
||||
# <tt>:controller</tt> that is required, but not explicitly used in the
|
||||
# text of the route.)
|
||||
def extra_keys(hash, recall = {})
|
||||
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
|
||||
end
|
||||
|
||||
def prepare_matching!
|
||||
unless defined? @matching_prepared
|
||||
@controller_requirement = requirement_for(:controller)
|
||||
@action_requirement = requirement_for(:action)
|
||||
@matching_prepared = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module ActionController
|
||||
module Routing
|
||||
class RouteSet #:nodoc:
|
||||
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.
|
||||
#
|
||||
@@ -194,6 +194,8 @@ module ActionController
|
||||
def initialize
|
||||
self.routes = []
|
||||
self.named_routes = NamedRouteCollection.new
|
||||
|
||||
write_recognize_optimized!
|
||||
end
|
||||
|
||||
# Subclasses and plugins may override this method to specify a different
|
||||
@@ -231,7 +233,6 @@ module ActionController
|
||||
Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
|
||||
clear!
|
||||
load_routes!
|
||||
install_helpers
|
||||
end
|
||||
|
||||
# reload! will always force a reload whereas load checks the timestamp first
|
||||
@@ -432,4 +433,4 @@ module ActionController
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
class Object
|
||||
def to_param
|
||||
to_s
|
||||
|
||||
@@ -2,13 +2,15 @@ module ActionController
|
||||
module Routing
|
||||
class Segment #:nodoc:
|
||||
RESERVED_PCHAR = ':@&=+$,;'
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
|
||||
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
||||
|
||||
# TODO: Convert :is_optional accessor to read only
|
||||
attr_accessor :is_optional
|
||||
alias_method :optional?, :is_optional
|
||||
|
||||
def initialize
|
||||
self.is_optional = false
|
||||
@is_optional = false
|
||||
end
|
||||
|
||||
def extraction_code
|
||||
@@ -63,12 +65,14 @@ module ActionController
|
||||
end
|
||||
|
||||
class StaticSegment < Segment #:nodoc:
|
||||
attr_accessor :value, :raw
|
||||
attr_reader :value, :raw
|
||||
alias_method :raw?, :raw
|
||||
|
||||
def initialize(value = nil)
|
||||
def initialize(value = nil, options = {})
|
||||
super()
|
||||
self.value = value
|
||||
@value = value
|
||||
@raw = options[:raw] if options.key?(:raw)
|
||||
@is_optional = options[:optional] if options.key?(:optional)
|
||||
end
|
||||
|
||||
def interpolation_chunk
|
||||
@@ -97,10 +101,8 @@ module ActionController
|
||||
end
|
||||
|
||||
class DividerSegment < StaticSegment #:nodoc:
|
||||
def initialize(value = nil)
|
||||
super(value)
|
||||
self.raw = true
|
||||
self.is_optional = true
|
||||
def initialize(value = nil, options = {})
|
||||
super(value, {:raw => true, :optional => true}.merge(options))
|
||||
end
|
||||
|
||||
def optionality_implied?
|
||||
@@ -109,13 +111,17 @@ module ActionController
|
||||
end
|
||||
|
||||
class DynamicSegment < Segment #:nodoc:
|
||||
attr_accessor :key, :default, :regexp
|
||||
attr_reader :key
|
||||
|
||||
# TODO: Convert these accessors to read only
|
||||
attr_accessor :default, :regexp
|
||||
|
||||
def initialize(key = nil, options = {})
|
||||
super()
|
||||
self.key = key
|
||||
self.default = options[:default] if options.key? :default
|
||||
self.is_optional = true if options[:optional] || options.key?(:default)
|
||||
@key = key
|
||||
@default = options[:default] if options.key?(:default)
|
||||
@regexp = options[:regexp] if options.key?(:regexp)
|
||||
@is_optional = true if options[:optional] || options.key?(:default)
|
||||
end
|
||||
|
||||
def to_s
|
||||
@@ -130,6 +136,7 @@ module ActionController
|
||||
def extract_value
|
||||
"#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
|
||||
end
|
||||
|
||||
def value_check
|
||||
if default # Then we know it won't be nil
|
||||
"#{value_regexp.inspect} =~ #{local_name}" if regexp
|
||||
@@ -141,6 +148,7 @@ module ActionController
|
||||
"#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
|
||||
end
|
||||
end
|
||||
|
||||
def expiry_statement
|
||||
"expired, hash = true, options if !expired && expire_on[:#{key}]"
|
||||
end
|
||||
@@ -175,7 +183,7 @@ module ActionController
|
||||
end
|
||||
|
||||
def regexp_chunk
|
||||
if regexp
|
||||
if regexp
|
||||
if regexp_has_modifiers?
|
||||
"(#{regexp.to_s})"
|
||||
else
|
||||
@@ -214,7 +222,6 @@ module ActionController
|
||||
def regexp_has_modifiers?
|
||||
regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ControllerSegment < DynamicSegment #:nodoc:
|
||||
|
||||
@@ -129,7 +129,7 @@ class CGI::Session::CookieStore
|
||||
private
|
||||
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
||||
def marshal(session)
|
||||
data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
|
||||
data = ActiveSupport::Base64.encode64s(Marshal.dump(session))
|
||||
"#{data}--#{generate_digest(data)}"
|
||||
end
|
||||
|
||||
|
||||
14
actionpack/lib/action_controller/session/drb_server.rb
Normal file → Executable file
14
actionpack/lib/action_controller/session/drb_server.rb
Normal file → Executable file
@@ -1,8 +1,8 @@
|
||||
#!/usr/local/bin/ruby -w
|
||||
|
||||
# This is a really simple session storage daemon, basically just a hash,
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# This is a really simple session storage daemon, basically just a hash,
|
||||
# which is enabled for DRb access.
|
||||
|
||||
|
||||
require 'drb'
|
||||
|
||||
session_hash = Hash.new
|
||||
@@ -14,13 +14,13 @@ class <<session_hash
|
||||
super(key, value)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def [](key)
|
||||
@mutex.synchronize do
|
||||
super(key)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def delete(key)
|
||||
@mutex.synchronize do
|
||||
super(key)
|
||||
@@ -29,4 +29,4 @@ class <<session_hash
|
||||
end
|
||||
|
||||
DRb.start_service('druby://127.0.0.1:9192', session_hash)
|
||||
DRb.thread.join
|
||||
DRb.thread.join
|
||||
|
||||
@@ -12,19 +12,21 @@ module ActionController #:nodoc:
|
||||
X_SENDFILE_HEADER = 'X-Sendfile'.freeze
|
||||
|
||||
protected
|
||||
# Sends the file by streaming it 4096 bytes at a time. This way the
|
||||
# whole file doesn't need to be read into memory at once. This makes
|
||||
# it feasible to send even large files.
|
||||
# Sends the file, by default streaming it 4096 bytes at a time. This way the
|
||||
# whole file doesn't need to be read into memory at once. This makes it
|
||||
# feasible to send even large files. You can optionally turn off streaming
|
||||
# and send the whole file at once.
|
||||
#
|
||||
# Be careful to sanitize the path parameter if it coming from a web
|
||||
# Be careful to sanitize the path parameter if it is coming from a web
|
||||
# page. <tt>send_file(params[:path])</tt> allows a malicious user to
|
||||
# download any file on your server.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||
# Defaults to <tt>File.basename(path)</tt>.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type.
|
||||
# Defaults to 'application/octet-stream'.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
||||
# * <tt>:length</tt> - used to manually override the length (in bytes) of the content that
|
||||
# is going to be sent to the client. Defaults to <tt>File.size(path)</tt>.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# Valid values are 'inline' and 'attachment' (default).
|
||||
# * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+)
|
||||
@@ -35,6 +37,12 @@ module ActionController #:nodoc:
|
||||
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
|
||||
# the URL, which is necessary for i18n filenames on certain browsers
|
||||
# (setting <tt>:filename</tt> overrides this option).
|
||||
# * <tt>:x_sendfile</tt> - uses X-Sendfile to send the file when set to +true+. This is currently
|
||||
# only available with Lighttpd/Apache2 and specific modules installed and activated. Since this
|
||||
# uses the web server to send the file, this may lower memory consumption on your server and
|
||||
# it will not block your application for further requests.
|
||||
# See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and
|
||||
# http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+.
|
||||
#
|
||||
# The default Content-Type and Content-Disposition headers are
|
||||
# set to download arbitrary binary files in as many browsers as
|
||||
@@ -99,8 +107,7 @@ module ActionController #:nodoc:
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type.
|
||||
# Defaults to 'application/octet-stream'.
|
||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||
# Valid values are 'inline' and 'attachment' (default).
|
||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
</h1>
|
||||
<pre><%=h @exception.clean_message %></pre>
|
||||
|
||||
<%= render(:file => @rescues_path + "/_trace.erb", :use_full_path => false) %>
|
||||
<%= render(:file => @rescues_path + "/_trace.erb") %>
|
||||
|
||||
<%= render(:file => @rescues_path + "/_request_and_response.erb", :use_full_path => false) %>
|
||||
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<% @real_exception = @exception
|
||||
@exception = @exception.original_exception || @exception %>
|
||||
<%= render(:file => @rescues_path + "/_trace.erb", :use_full_path => false) %>
|
||||
<%= render(:file => @rescues_path + "/_trace.erb") %>
|
||||
<% @exception = @real_exception %>
|
||||
|
||||
<%= render(:file => @rescues_path + "/_request_and_response.erb", :use_full_path => false) %>
|
||||
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
|
||||
|
||||
@@ -15,6 +15,65 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
# Superclass for ActionController functional tests. Functional tests allow you to
|
||||
# test a single controller action per test method. This should not be confused with
|
||||
# integration tests (see ActionController::IntegrationTest), which are more like
|
||||
# "stories" that can involve multiple controllers and mutliple actions (i.e. multiple
|
||||
# different HTTP requests).
|
||||
#
|
||||
# == Basic example
|
||||
#
|
||||
# Functional tests are written as follows:
|
||||
# 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
|
||||
# an HTTP request.
|
||||
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
|
||||
# the controller's HTTP response, the database contents, etc.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# class BooksControllerTest < ActionController::TestCase
|
||||
# def test_create
|
||||
# # Simulate a POST response with the given HTTP parameters.
|
||||
# post(:create, :book => { :title => "Love Hina" })
|
||||
#
|
||||
# # Assert that the controller tried to redirect us to
|
||||
# # the created book's URI.
|
||||
# assert_response :found
|
||||
#
|
||||
# # Assert that the controller really put the book in the database.
|
||||
# assert_not_nil Book.find_by_title("Love Hina")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# == Special instance variables
|
||||
#
|
||||
# ActionController::TestCase will also automatically provide the following instance
|
||||
# variables for use in the tests:
|
||||
#
|
||||
# <b>@controller</b>::
|
||||
# The controller instance that will be tested.
|
||||
# <b>@request</b>::
|
||||
# An ActionController::TestRequest, representing the current HTTP
|
||||
# request. You can modify this object before sending the HTTP request. For example,
|
||||
# you might want to set some session properties before sending a GET request.
|
||||
# <b>@response</b>::
|
||||
# An ActionController::TestResponse object, representing the response
|
||||
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
|
||||
# after calling +post+. If the various assert methods are not sufficient, then you
|
||||
# may use this object to inspect the HTTP response in detail.
|
||||
#
|
||||
# (Earlier versions of Rails required each functional test to subclass
|
||||
# Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
|
||||
#
|
||||
# == Controller is automatically inferred
|
||||
#
|
||||
# ActionController::TestCase will automatically infer the controller under test
|
||||
# from the test class name. If the controller cannot be inferred from the test
|
||||
# class name, you can explicity set it with +tests+.
|
||||
#
|
||||
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
|
||||
# tests WidgetController
|
||||
# end
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
|
||||
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
|
||||
@@ -41,6 +100,8 @@ module ActionController
|
||||
@@controller_class = nil
|
||||
|
||||
class << self
|
||||
# Sets the controller class name. Useful if the name can't be inferred from test class.
|
||||
# Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
|
||||
def tests(controller_class)
|
||||
self.controller_class = controller_class
|
||||
end
|
||||
@@ -80,4 +141,4 @@ module ActionController
|
||||
@request.remote_addr = '208.77.188.166' # example.com
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ module ActionController #:nodoc:
|
||||
|
||||
class TestRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cookies, :session_options
|
||||
attr_accessor :query_parameters, :request_parameters, :path, :session, :env
|
||||
attr_accessor :query_parameters, :request_parameters, :path, :session
|
||||
attr_accessor :host, :user_agent
|
||||
|
||||
def initialize(query_parameters = nil, request_parameters = nil, session = nil)
|
||||
@@ -42,7 +42,7 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
# Wraps raw_post in a StringIO.
|
||||
def body
|
||||
def body_stream #:nodoc:
|
||||
StringIO.new(raw_post)
|
||||
end
|
||||
|
||||
@@ -54,7 +54,7 @@ module ActionController #:nodoc:
|
||||
|
||||
def port=(number)
|
||||
@env["SERVER_PORT"] = number.to_i
|
||||
@port_as_int = nil
|
||||
port(true)
|
||||
end
|
||||
|
||||
def action=(action_name)
|
||||
@@ -68,6 +68,8 @@ module ActionController #:nodoc:
|
||||
@env["REQUEST_URI"] = value
|
||||
@request_uri = nil
|
||||
@path = nil
|
||||
request_uri(true)
|
||||
path(true)
|
||||
end
|
||||
|
||||
def request_uri=(uri)
|
||||
@@ -77,21 +79,26 @@ module ActionController #:nodoc:
|
||||
|
||||
def accept=(mime_types)
|
||||
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
|
||||
accepts(true)
|
||||
end
|
||||
|
||||
def if_modified_since=(last_modified)
|
||||
@env["HTTP_IF_MODIFIED_SINCE"] = last_modified
|
||||
end
|
||||
|
||||
def if_none_match=(etag)
|
||||
@env["HTTP_IF_NONE_MATCH"] = etag
|
||||
end
|
||||
|
||||
def remote_addr=(addr)
|
||||
@env['REMOTE_ADDR'] = addr
|
||||
end
|
||||
|
||||
def remote_addr
|
||||
@env['REMOTE_ADDR']
|
||||
end
|
||||
|
||||
def request_uri
|
||||
def request_uri(*args)
|
||||
@request_uri || super
|
||||
end
|
||||
|
||||
def path
|
||||
def path(*args)
|
||||
@path || super
|
||||
end
|
||||
|
||||
@@ -113,17 +120,13 @@ module ActionController #:nodoc:
|
||||
end
|
||||
end
|
||||
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def recycle!
|
||||
self.request_parameters = {}
|
||||
self.query_parameters = {}
|
||||
self.path_parameters = {}
|
||||
@request_method, @accepts, @content_type = nil, nil, nil
|
||||
end
|
||||
|
||||
def referer
|
||||
@env["HTTP_REFERER"]
|
||||
unmemoize_all
|
||||
end
|
||||
|
||||
private
|
||||
@@ -135,7 +138,7 @@ module ActionController #:nodoc:
|
||||
@host = "test.host"
|
||||
@request_uri = "/"
|
||||
@user_agent = "Rails Testing"
|
||||
self.remote_addr = "0.0.0.0"
|
||||
self.remote_addr = "0.0.0.0"
|
||||
@env["SERVER_PORT"] = 80
|
||||
@env['REQUEST_METHOD'] = "GET"
|
||||
end
|
||||
@@ -157,21 +160,21 @@ module ActionController #:nodoc:
|
||||
module TestResponseBehavior #:nodoc:
|
||||
# The response code of the request
|
||||
def response_code
|
||||
headers['Status'][0,3].to_i rescue 0
|
||||
status[0,3].to_i rescue 0
|
||||
end
|
||||
|
||||
|
||||
# Returns a String to ensure compatibility with Net::HTTPResponse
|
||||
def code
|
||||
headers['Status'].to_s.split(' ')[0]
|
||||
status.to_s.split(' ')[0]
|
||||
end
|
||||
|
||||
def message
|
||||
headers['Status'].to_s.split(' ',2)[1]
|
||||
status.to_s.split(' ',2)[1]
|
||||
end
|
||||
|
||||
# Was the response successful?
|
||||
def success?
|
||||
response_code == 200
|
||||
(200..299).include?(response_code)
|
||||
end
|
||||
|
||||
# Was the URL not found?
|
||||
@@ -211,7 +214,7 @@ module ActionController #:nodoc:
|
||||
template._first_render
|
||||
end
|
||||
|
||||
# A shortcut to the flash. Returns an empyt hash if no session flash exists.
|
||||
# A shortcut to the flash. Returns an empty hash if no session flash exists.
|
||||
def flash
|
||||
session['flash'] || {}
|
||||
end
|
||||
@@ -243,11 +246,11 @@ module ActionController #:nodoc:
|
||||
|
||||
# Does the specified template object exist?
|
||||
def has_template_object?(name=nil)
|
||||
!template_objects[name].nil?
|
||||
!template_objects[name].nil?
|
||||
end
|
||||
|
||||
# Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
|
||||
#
|
||||
#
|
||||
# assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
|
||||
def cookies
|
||||
headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
|
||||
@@ -266,7 +269,13 @@ module ActionController #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
class TestResponse < AbstractResponse #:nodoc:
|
||||
# Integration test methods such as ActionController::Integration::Session#get
|
||||
# and ActionController::Integration::Session#post return objects of class
|
||||
# TestResponse, which represent the HTTP response results of the requested
|
||||
# controller actions.
|
||||
#
|
||||
# See AbstractResponse for more information on controller response objects.
|
||||
class TestResponse < AbstractResponse
|
||||
include TestResponseBehavior
|
||||
end
|
||||
|
||||
@@ -313,7 +322,7 @@ module ActionController #:nodoc:
|
||||
#
|
||||
# Usage example, within a functional test:
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
|
||||
#
|
||||
#
|
||||
# Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
|
||||
require 'tempfile'
|
||||
@@ -348,6 +357,7 @@ module ActionController #:nodoc:
|
||||
module TestProcess
|
||||
def self.included(base)
|
||||
# execute the request simulating a specific HTTP method and set/volley the response
|
||||
# TODO: this should be un-DRY'ed for the sake of API documentation.
|
||||
%w( get post put delete head ).each do |method|
|
||||
base.class_eval <<-EOV, __FILE__, __LINE__
|
||||
def #{method}(action, parameters = nil, session = nil, flash = nil)
|
||||
@@ -393,13 +403,13 @@ module ActionController #:nodoc:
|
||||
end
|
||||
alias xhr :xml_http_request
|
||||
|
||||
def assigns(key = nil)
|
||||
if key.nil?
|
||||
@response.template.assigns
|
||||
else
|
||||
@response.template.assigns[key.to_s]
|
||||
end
|
||||
end
|
||||
def assigns(key = nil)
|
||||
if key.nil?
|
||||
@response.template.assigns
|
||||
else
|
||||
@response.template.assigns[key.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
def session
|
||||
@response.session
|
||||
@@ -441,10 +451,13 @@ module ActionController #:nodoc:
|
||||
end
|
||||
|
||||
def method_missing(selector, *args)
|
||||
return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
||||
return super
|
||||
if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
|
||||
@controller.send(selector, *args)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Shortcut for <tt>ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type)</tt>:
|
||||
#
|
||||
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
|
||||
@@ -455,7 +468,7 @@ module ActionController #:nodoc:
|
||||
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
|
||||
def fixture_file_upload(path, mime_type = nil, binary = false)
|
||||
ActionController::TestUploadedFile.new(
|
||||
Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
|
||||
Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
|
||||
mime_type,
|
||||
binary
|
||||
)
|
||||
@@ -463,7 +476,7 @@ module ActionController #:nodoc:
|
||||
|
||||
# A helper to make it easier to test different route configurations.
|
||||
# This method temporarily replaces ActionController::Routing::Routes
|
||||
# with a new RouteSet instance.
|
||||
# with a new RouteSet instance.
|
||||
#
|
||||
# The new instance is yielded to the passed block. Typically the block
|
||||
# will create some routes using <tt>map.draw { map.connect ... }</tt>:
|
||||
|
||||
@@ -1,19 +1,96 @@
|
||||
module ActionController
|
||||
# Write URLs from arbitrary places in your codebase, such as your mailers.
|
||||
# In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse
|
||||
# is also possible: an URL can be generated from one of your routing definitions.
|
||||
# URL generation functionality is centralized in this module.
|
||||
#
|
||||
# Example:
|
||||
# See ActionController::Routing and ActionController::Resources for general
|
||||
# information about routing and routes.rb.
|
||||
#
|
||||
# class MyMailer
|
||||
# include ActionController::UrlWriter
|
||||
# default_url_options[:host] = 'www.basecamphq.com'
|
||||
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
|
||||
# then ActionController::UrlWriter is what you're looking for. Read on for
|
||||
# an introduction.
|
||||
#
|
||||
# def signup_url(token)
|
||||
# url_for(:controller => 'signup', action => 'index', :token => token)
|
||||
# == URL generation from parameters
|
||||
#
|
||||
# As you may know, some functions - such as ActionController::Base#url_for
|
||||
# and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
|
||||
# of parameters. For example, you've probably had the chance to write code
|
||||
# like this in one of your views:
|
||||
#
|
||||
# <%= link_to('Click here', :controller => 'users',
|
||||
# :action => 'new', :message => 'Welcome!') %>
|
||||
#
|
||||
# #=> Generates a link to: /users/new?message=Welcome%21
|
||||
#
|
||||
# link_to, and all other functions that require URL generation functionality,
|
||||
# actually use ActionController::UrlWriter under the hood. And in particular,
|
||||
# they use the ActionController::UrlWriter#url_for method. One can generate
|
||||
# the same path as the above example by using the following code:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :only_path => true)
|
||||
# # => "/users/new?message=Welcome%21"
|
||||
#
|
||||
# Notice the <tt>:only_path => true</tt> part. This is because UrlWriter has no
|
||||
# information about the website hostname that your Rails app is serving. So if you
|
||||
# want to include the hostname as well, then you must also pass the <tt>:host</tt>
|
||||
# argument:
|
||||
#
|
||||
# include UrlWriter
|
||||
# url_for(:controller => 'users',
|
||||
# :action => 'new',
|
||||
# :message => 'Welcome!',
|
||||
# :host => 'www.example.com') # Changed this.
|
||||
# # => "http://www.example.com/users/new?message=Welcome%21"
|
||||
#
|
||||
# By default, all controllers and views have access to a special version of url_for,
|
||||
# that already knows what the current hostname is. So if you use url_for in your
|
||||
# controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
|
||||
# argument.
|
||||
#
|
||||
# For convenience reasons, mailers provide a shortcut for ActionController::UrlWriter#url_for.
|
||||
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#url_for'
|
||||
# in full. However, mailers don't have hostname information, and what's why you'll still
|
||||
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
|
||||
#
|
||||
#
|
||||
# == URL generation for named routes
|
||||
#
|
||||
# UrlWriter also allows one to access methods that have been auto-generated from
|
||||
# named routes. For example, suppose that you have a 'users' resource in your
|
||||
# <b>routes.rb</b>:
|
||||
#
|
||||
# map.resources :users
|
||||
#
|
||||
# This generates, among other things, the method <tt>users_path</tt>. By default,
|
||||
# this method is accessible from your controllers, views and mailers. If you need
|
||||
# to access this auto-generated method from other places (such as a model), then
|
||||
# you can do that in two ways.
|
||||
#
|
||||
# The first way is to include ActionController::UrlWriter in your class:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# include ActionController::UrlWriter # !!!
|
||||
#
|
||||
# def name=(value)
|
||||
# write_attribute('name', value)
|
||||
# write_attribute('base_uri', users_path) # !!!
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# In addition to providing +url_for+, named routes are also accessible after
|
||||
# including UrlWriter.
|
||||
# The second way is to access them through ActionController::UrlWriter.
|
||||
# The autogenerated named routes methods are available as class methods:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# def name=(value)
|
||||
# write_attribute('name', value)
|
||||
# path = ActionController::UrlWriter.users_path # !!!
|
||||
# write_attribute('base_uri', path) # !!!
|
||||
# end
|
||||
# end
|
||||
module UrlWriter
|
||||
# The default options for urls written by this writer. Typically a <tt>:host</tt>
|
||||
# pair is provided.
|
||||
@@ -37,7 +114,7 @@ module ActionController
|
||||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
|
||||
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the
|
||||
# +relative_url_root+ set in ActionController::AbstractRequest.relative_url_root.
|
||||
# +relative_url_root+ set in ActionController::Base.relative_url_root.
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
|
||||
#
|
||||
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
|
||||
@@ -67,7 +144,7 @@ module ActionController
|
||||
[:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) }
|
||||
end
|
||||
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
|
||||
url << ActionController::AbstractRequest.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor]
|
||||
generated = Routing::Routes.generate(options, {})
|
||||
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
|
||||
@@ -108,7 +185,7 @@ module ActionController
|
||||
end
|
||||
|
||||
path = rewrite_path(options)
|
||||
rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
||||
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
|
||||
rewritten_url << "##{options[:anchor]}" if options[:anchor]
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ module HTML
|
||||
#
|
||||
# When using a combination of the above, the element name comes first
|
||||
# followed by identifier, class names, attributes, pseudo classes and
|
||||
# negation in any order. Do not seprate these parts with spaces!
|
||||
# negation in any order. Do not separate these parts with spaces!
|
||||
# Space separation is used for descendant selectors.
|
||||
#
|
||||
# For example:
|
||||
@@ -158,7 +158,7 @@ module HTML
|
||||
# * <tt>:not(selector)</tt> -- Match the element only if the element does not
|
||||
# match the simple selector.
|
||||
#
|
||||
# As you can see, <tt>:nth-child<tt> pseudo class and its varient can get quite
|
||||
# As you can see, <tt>:nth-child<tt> pseudo class and its variant can get quite
|
||||
# tricky and the CSS specification doesn't do a much better job explaining it.
|
||||
# But after reading the examples and trying a few combinations, it's easy to
|
||||
# figure out.
|
||||
|
||||
@@ -21,6 +21,15 @@
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
begin
|
||||
require 'active_support'
|
||||
rescue LoadError
|
||||
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
||||
if File.directory?(activesupport_path)
|
||||
$:.unshift activesupport_path
|
||||
require 'active_support'
|
||||
end
|
||||
end
|
||||
|
||||
require 'action_view/template_handlers'
|
||||
require 'action_view/renderable'
|
||||
@@ -34,10 +43,13 @@ require 'action_view/base'
|
||||
require 'action_view/partials'
|
||||
require 'action_view/template_error'
|
||||
|
||||
I18n.backend.populate do
|
||||
I18n.load_translations "#{File.dirname(__FILE__)}/action_view/locale/en-US.yml"
|
||||
end
|
||||
|
||||
require 'action_view/helpers'
|
||||
|
||||
ActionView::Base.class_eval do
|
||||
include ActionView::Partials
|
||||
|
||||
ActionView::Base.helper_modules.each do |helper_module|
|
||||
include helper_module
|
||||
end
|
||||
include ActionView::Helpers
|
||||
end
|
||||
|
||||
@@ -158,6 +158,7 @@ module ActionView #:nodoc:
|
||||
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
|
||||
class Base
|
||||
include ERB::Util
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
attr_accessor :base_path, :assigns, :template_extension
|
||||
attr_accessor :controller
|
||||
@@ -169,15 +170,19 @@ module ActionView #:nodoc:
|
||||
|
||||
class << self
|
||||
delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB'
|
||||
delegate :logger, :to => 'ActionController::Base'
|
||||
end
|
||||
|
||||
# Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed.
|
||||
@@cache_template_loading = false
|
||||
cattr_accessor :cache_template_loading
|
||||
def self.cache_template_loading=(*args)
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"config.action_view.cache_template_loading option has been deprecated" +
|
||||
"and has no effect. Please remove it from your config files.", caller)
|
||||
end
|
||||
|
||||
def self.cache_template_extensions=(*args)
|
||||
ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no affect. " <<
|
||||
"Please remove it from your config files.", caller)
|
||||
ActiveSupport::Deprecation.warn(
|
||||
"config.action_view.cache_template_extensions option has been" +
|
||||
"deprecated and has no effect. Please remove it from your config files.", caller)
|
||||
end
|
||||
|
||||
# Specify whether RJS responses should be wrapped in a try/catch block
|
||||
@@ -199,23 +204,6 @@ module ActionView #:nodoc:
|
||||
end
|
||||
include CompiledTemplates
|
||||
|
||||
# Cache public asset paths
|
||||
cattr_reader :computed_public_paths
|
||||
@@computed_public_paths = {}
|
||||
|
||||
def self.helper_modules #:nodoc:
|
||||
helpers = []
|
||||
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|
|
||||
next unless file =~ /^([a-z][a-z_]*_helper).rb$/
|
||||
require "action_view/helpers/#{$1}"
|
||||
helper_module_name = $1.camelize
|
||||
if Helpers.const_defined?(helper_module_name)
|
||||
helpers << Helpers.const_get(helper_module_name)
|
||||
end
|
||||
end
|
||||
return helpers
|
||||
end
|
||||
|
||||
def self.process_view_paths(value)
|
||||
ActionView::PathSet.new(Array(value))
|
||||
end
|
||||
@@ -239,7 +227,7 @@ module ActionView #:nodoc:
|
||||
local_assigns ||= {}
|
||||
|
||||
if options.is_a?(String)
|
||||
render_file(options, nil, local_assigns)
|
||||
render(:file => options, :locals => local_assigns)
|
||||
elsif options == :update
|
||||
update_page(&block)
|
||||
elsif options.is_a?(Hash)
|
||||
@@ -247,31 +235,34 @@ module ActionView #:nodoc:
|
||||
|
||||
if partial_layout = options.delete(:layout)
|
||||
if block_given?
|
||||
wrap_content_for_layout capture(&block) do
|
||||
begin
|
||||
@_proc_for_layout = block
|
||||
concat(render(options.merge(:partial => partial_layout)))
|
||||
ensure
|
||||
@_proc_for_layout = nil
|
||||
end
|
||||
else
|
||||
wrap_content_for_layout render(options) do
|
||||
begin
|
||||
original_content_for_layout, @content_for_layout = @content_for_layout, render(options)
|
||||
render(options.merge(:partial => partial_layout))
|
||||
ensure
|
||||
@content_for_layout = original_content_for_layout
|
||||
end
|
||||
end
|
||||
elsif options[:file]
|
||||
render_file(options[:file], nil, options[:locals])
|
||||
elsif options[:partial] && options[:collection]
|
||||
render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals], options[:as])
|
||||
if options[:use_full_path]
|
||||
ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller)
|
||||
end
|
||||
|
||||
pick_template(options[:file]).render_template(self, options[:locals])
|
||||
elsif options[:partial]
|
||||
render_partial(options[:partial], options[:object], options[:locals])
|
||||
render_partial(options)
|
||||
elsif options[:inline]
|
||||
render_inline(options[:inline], options[:locals], options[:type])
|
||||
InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns true is the file may be rendered implicitly.
|
||||
def file_public?(template_path)#:nodoc:
|
||||
template_path.split('/').last[0,1] != '_'
|
||||
end
|
||||
|
||||
# The format to be used when choosing between multiple templates with
|
||||
# the same name but differing formats. See +Request#template_format+
|
||||
# for more details.
|
||||
@@ -301,6 +292,8 @@ module ActionView #:nodoc:
|
||||
# # => 'users/legacy.rhtml'
|
||||
#
|
||||
def pick_template(template_path)
|
||||
return template_path if template_path.respond_to?(:render)
|
||||
|
||||
path = template_path.sub(/^\//, '')
|
||||
if m = path.match(/(.*)\.(\w+)$/)
|
||||
template_file_name, template_file_extension = m[1], m[2]
|
||||
@@ -321,54 +314,20 @@ module ActionView #:nodoc:
|
||||
else
|
||||
template = Template.new(template_path, view_paths)
|
||||
|
||||
if self.class.warn_cache_misses && logger = ActionController::Base.logger
|
||||
if self.class.warn_cache_misses && logger
|
||||
logger.debug "[PERFORMANCE] Rendering a template that was " +
|
||||
"not found in view path. Templates outside the view path are " +
|
||||
"not cached and result in expensive disk operations. Move this " +
|
||||
"file into #{view_paths.join(':')} or add the folder to your " +
|
||||
"not cached and result in expensive disk operations. Move this " +
|
||||
"file into #{view_paths.join(':')} or add the folder to your " +
|
||||
"view path list"
|
||||
end
|
||||
|
||||
template
|
||||
end
|
||||
end
|
||||
memoize :pick_template
|
||||
|
||||
private
|
||||
# Renders the template present at <tt>template_path</tt>. The hash in <tt>local_assigns</tt>
|
||||
# is made available as local variables.
|
||||
def render_file(template_path, use_full_path = nil, local_assigns = {}) #:nodoc:
|
||||
unless use_full_path == nil
|
||||
ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller)
|
||||
end
|
||||
|
||||
if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
|
||||
raise ActionViewError, <<-END_ERROR
|
||||
Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.
|
||||
|
||||
render "user_mailer/signup"
|
||||
render :file => "user_mailer/signup"
|
||||
|
||||
If you are rendering a subtemplate, you must now use controller-like partial syntax:
|
||||
|
||||
render :partial => 'signup' # no mailer_name necessary
|
||||
END_ERROR
|
||||
end
|
||||
|
||||
template = pick_template(template_path)
|
||||
template.render_template(self, local_assigns)
|
||||
end
|
||||
|
||||
def render_inline(text, local_assigns = {}, type = nil)
|
||||
InlineTemplate.new(text, type).render(self, local_assigns)
|
||||
end
|
||||
|
||||
def wrap_content_for_layout(content)
|
||||
original_content_for_layout, @content_for_layout = @content_for_layout, content
|
||||
yield
|
||||
ensure
|
||||
@content_for_layout = original_content_for_layout
|
||||
end
|
||||
|
||||
# Evaluate the local assigns and pushes them to the view.
|
||||
def evaluate_assigns
|
||||
unless @assigns_added
|
||||
@@ -382,9 +341,9 @@ module ActionView #:nodoc:
|
||||
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
|
||||
end
|
||||
|
||||
def execute(template, local_assigns = {})
|
||||
send(template.method(local_assigns), local_assigns) do |*names|
|
||||
instance_variable_get "@content_for_#{names.first || 'layout'}"
|
||||
def set_controller_content_type(content_type)
|
||||
if controller.respond_to?(:response)
|
||||
controller.response.content_type ||= content_type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
39
actionpack/lib/action_view/helpers.rb
Normal file
39
actionpack/lib/action_view/helpers.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|
|
||||
next unless file =~ /^([a-z][a-z_]*_helper).rb$/
|
||||
require "action_view/helpers/#{$1}"
|
||||
end
|
||||
|
||||
module ActionView #:nodoc:
|
||||
module Helpers #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
include SanitizeHelper::ClassMethods
|
||||
end
|
||||
|
||||
include ActiveRecordHelper
|
||||
include AssetTagHelper
|
||||
include AtomFeedHelper
|
||||
include BenchmarkHelper
|
||||
include CacheHelper
|
||||
include CaptureHelper
|
||||
include DateHelper
|
||||
include DebugHelper
|
||||
include FormCountryHelper
|
||||
include FormHelper
|
||||
include FormOptionsHelper
|
||||
include FormTagHelper
|
||||
include NumberHelper
|
||||
include PrototypeHelper
|
||||
include RecordIdentificationHelper
|
||||
include RecordTagHelper
|
||||
include SanitizeHelper
|
||||
include ScriptaculousHelper
|
||||
include TagHelper
|
||||
include TextHelper
|
||||
include TranslationHelper
|
||||
include UrlHelper
|
||||
end
|
||||
end
|
||||
@@ -25,7 +25,7 @@ module ActionView
|
||||
# Returns an entire form with all needed input tags for a specified Active Record object. For example, if <tt>@post</tt>
|
||||
# has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then
|
||||
#
|
||||
# form("post")
|
||||
# form("post")
|
||||
#
|
||||
# would yield a form like the following (modulus formatting):
|
||||
#
|
||||
@@ -90,23 +90,41 @@ module ActionView
|
||||
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 <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. +object+ should either be the name of an instance variable or
|
||||
# the actual object. As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
|
||||
# This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a <tt>:prepend_text</tt>
|
||||
# and/or <tt>:append_text</tt> (to properly explain the error), and a <tt>:css_class</tt> to style it
|
||||
# accordingly. +object+ should either be the name of an instance variable or the actual object. The method can be
|
||||
# passed in either as a string or a symbol.
|
||||
# As an example, let's say you have a model <tt>@post</tt> 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" %>
|
||||
# <%= 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>
|
||||
def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
|
||||
# <%= error_message_on "post", "title",
|
||||
# :prepend_text => "Title simply ",
|
||||
# :append_text => " (or it won't work).",
|
||||
# :css_class => "inputError" %>
|
||||
def error_message_on(object, method, *args)
|
||||
options = args.extract_options!
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' +
|
||||
'prepend_text, append_text, and css_class arguments', caller)
|
||||
|
||||
options[:prepend_text] = args[0] || ''
|
||||
options[:append_text] = args[1] || ''
|
||||
options[:css_class] = args[2] || 'formError'
|
||||
end
|
||||
options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError')
|
||||
|
||||
if (obj = (object.respond_to?(:errors) ? object : 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
|
||||
content_tag("div",
|
||||
"#{options[:prepend_text]}#{errors.is_a?(Array) ? errors.first : errors}#{options[:append_text]}",
|
||||
:class => options[:css_class]
|
||||
)
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
@@ -133,7 +151,7 @@ module ActionView
|
||||
#
|
||||
# To specify the display for one object, you simply provide its name as a parameter.
|
||||
# For example, for the <tt>@user</tt> model:
|
||||
#
|
||||
#
|
||||
# error_messages_for 'user'
|
||||
#
|
||||
# To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which
|
||||
@@ -141,7 +159,7 @@ module ActionView
|
||||
#
|
||||
# error_messages_for 'user_common', 'user', :object_name => 'user'
|
||||
#
|
||||
# If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> paremeter which gives the actual
|
||||
# If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> parameter which gives the actual
|
||||
# object (or array of objects to use):
|
||||
#
|
||||
# error_messages_for 'user', :object => @question.user
|
||||
@@ -151,12 +169,14 @@ module ActionView
|
||||
# instance yourself and set it up. View the source of this method to see how easy it is.
|
||||
def error_messages_for(*params)
|
||||
options = params.extract_options!.symbolize_keys
|
||||
|
||||
if object = options.delete(:object)
|
||||
objects = [object].flatten
|
||||
else
|
||||
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
|
||||
end
|
||||
count = objects.inject(0) {|sum, object| sum + object.errors.count }
|
||||
|
||||
count = objects.inject(0) {|sum, object| sum + object.errors.count }
|
||||
unless count.zero?
|
||||
html = {}
|
||||
[:id, :class].each do |key|
|
||||
@@ -168,16 +188,25 @@ module ActionView
|
||||
end
|
||||
end
|
||||
options[:object_name] ||= params.first
|
||||
options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message)
|
||||
options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message)
|
||||
error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
|
||||
|
||||
contents = ''
|
||||
contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
|
||||
contents << content_tag(:p, options[:message]) unless options[:message].blank?
|
||||
contents << content_tag(:ul, error_messages)
|
||||
I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale|
|
||||
header_message = if options.include?(:header_message)
|
||||
options[:header_message]
|
||||
else
|
||||
object_name = options[:object_name].to_s.gsub('_', ' ')
|
||||
object_name = I18n.t(object_name, :default => object_name, :scope => [:activerecord, :models], :count => 1)
|
||||
locale.t :header, :count => count, :model => object_name
|
||||
end
|
||||
message = options.include?(:message) ? options[:message] : locale.t(:body)
|
||||
error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
|
||||
|
||||
content_tag(:div, contents, html)
|
||||
contents = ''
|
||||
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
|
||||
contents << content_tag(:p, message) unless message.blank?
|
||||
contents << content_tag(:ul, error_messages)
|
||||
|
||||
content_tag(:div, contents, html)
|
||||
end
|
||||
else
|
||||
''
|
||||
end
|
||||
|
||||
@@ -5,12 +5,12 @@ require 'action_view/helpers/tag_helper'
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
# This module provides methods for generating HTML that links views to assets such
|
||||
# as images, javascripts, stylesheets, and feeds. These methods do not verify
|
||||
# the assets exist before linking to them.
|
||||
# as images, javascripts, stylesheets, and feeds. These methods do not verify
|
||||
# the assets exist before linking to them.
|
||||
#
|
||||
# === Using asset hosts
|
||||
# By default, Rails links to these assets on the current host in the public
|
||||
# folder, but you can direct Rails to link to assets from a dedicated assets server by
|
||||
# folder, but you can direct Rails to link to assets from a dedicated assets server by
|
||||
# setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example,
|
||||
# let's say your asset host is <tt>assets.example.com</tt>.
|
||||
#
|
||||
@@ -22,16 +22,16 @@ module ActionView
|
||||
#
|
||||
# This is useful since browsers typically open at most two connections to a single host,
|
||||
# which means your assets often wait in single file for their turn to load. You can
|
||||
# alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
|
||||
# alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
|
||||
# to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com")
|
||||
# so browsers will open eight connections rather than two.
|
||||
# so browsers will open eight connections rather than two.
|
||||
#
|
||||
# image_tag("rails.png")
|
||||
# => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
|
||||
# stylesheet_link_tag("application")
|
||||
# => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
#
|
||||
# To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
|
||||
# To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
|
||||
# the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
|
||||
# your ISP.
|
||||
#
|
||||
@@ -86,7 +86,7 @@ module ActionView
|
||||
# asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
|
||||
# which then updates the URL as the timestamp is part of that, which in turn busts the cache).
|
||||
#
|
||||
# It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
|
||||
# It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
|
||||
# advantage of this feature. Here's an example for Apache:
|
||||
#
|
||||
# # Asset Expiration
|
||||
@@ -95,16 +95,17 @@ module ActionView
|
||||
# ExpiresDefault "access plus 1 year"
|
||||
# </FilesMatch>
|
||||
#
|
||||
# Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
|
||||
# Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
|
||||
# have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
|
||||
# work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
|
||||
# something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
|
||||
# something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
|
||||
# requested over and over).
|
||||
module AssetTagHelper
|
||||
ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public"
|
||||
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
|
||||
STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
|
||||
|
||||
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].map(&:to_s).freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
||||
|
||||
# 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
|
||||
@@ -154,10 +155,6 @@ module ActionView
|
||||
end
|
||||
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
|
||||
|
||||
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
||||
@@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
|
||||
@@stylesheet_expansions = {}
|
||||
|
||||
# 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
|
||||
@@ -193,7 +190,7 @@ module ActionView
|
||||
#
|
||||
# * = The application.js file is only referenced if it exists
|
||||
#
|
||||
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
|
||||
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
|
||||
# (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
|
||||
#
|
||||
# You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source:
|
||||
@@ -218,7 +215,7 @@ module ActionView
|
||||
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
|
||||
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
|
||||
# is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
|
||||
# environment).
|
||||
# environment).
|
||||
#
|
||||
# ==== Examples
|
||||
# javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
|
||||
@@ -259,6 +256,8 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
@@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
|
||||
|
||||
# Register one or more javascript files to be included when <tt>symbol</tt>
|
||||
# is passed to <tt>javascript_include_tag</tt>. This method is typically intended
|
||||
# to be called from plugin initialization to register javascript files
|
||||
@@ -274,6 +273,8 @@ module ActionView
|
||||
@@javascript_expansions.merge!(expansions)
|
||||
end
|
||||
|
||||
@@stylesheet_expansions = {}
|
||||
|
||||
# Register one or more stylesheet files to be included when <tt>symbol</tt>
|
||||
# is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
|
||||
# to be called from plugin initialization to register stylesheet files
|
||||
@@ -439,9 +440,9 @@ module ActionView
|
||||
# <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
|
||||
# image_tag("/icons/icon.gif", :class => "menu_icon") # =>
|
||||
# <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
|
||||
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
|
||||
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
|
||||
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
|
||||
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
|
||||
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
|
||||
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
|
||||
def image_tag(source, options = {})
|
||||
options.symbolize_keys!
|
||||
@@ -454,23 +455,15 @@ module ActionView
|
||||
end
|
||||
|
||||
if mouseover = options.delete(:mouseover)
|
||||
options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
|
||||
options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
|
||||
options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
|
||||
options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
|
||||
end
|
||||
|
||||
tag("img", options)
|
||||
end
|
||||
|
||||
private
|
||||
def file_exist?(path)
|
||||
@@file_exist_cache ||= {}
|
||||
if !(@@file_exist_cache[path] ||= File.exist?(path))
|
||||
@@file_exist_cache[path] = true
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence!
|
||||
|
||||
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
|
||||
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
|
||||
@@ -483,14 +476,14 @@ module ActionView
|
||||
if has_request
|
||||
[ @controller.request.protocol,
|
||||
ActionController::Base.asset_host.to_s,
|
||||
@controller.request.relative_url_root,
|
||||
ActionController::Base.relative_url_root,
|
||||
dir, source, ext, include_host ].join
|
||||
else
|
||||
[ ActionController::Base.asset_host.to_s,
|
||||
dir, source, ext, include_host ].join
|
||||
end
|
||||
|
||||
ActionView::Base.computed_public_paths[cache_key] ||=
|
||||
source = COMPUTED_PUBLIC_PATHS.fetch(cache_key) do
|
||||
begin
|
||||
source += ".#{ext}" if ext && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}"))
|
||||
|
||||
@@ -499,25 +492,27 @@ module ActionView
|
||||
else
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
if has_request
|
||||
unless source =~ %r{^#{@controller.request.relative_url_root}/}
|
||||
source = "#{@controller.request.relative_url_root}#{source}"
|
||||
unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
|
||||
source = "#{ActionController::Base.relative_url_root}#{source}"
|
||||
end
|
||||
end
|
||||
source = rewrite_asset_path(source)
|
||||
|
||||
if include_host
|
||||
host = compute_asset_host(source)
|
||||
|
||||
if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
|
||||
host = "#{@controller.request.protocol}#{host}"
|
||||
end
|
||||
|
||||
"#{host}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
rewrite_asset_path(source)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if include_host && source !~ %r{^[-a-z]+://}
|
||||
host = compute_asset_host(source)
|
||||
|
||||
if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
|
||||
host = "#{@controller.request.protocol}#{host}"
|
||||
end
|
||||
|
||||
"#{host}#{source}"
|
||||
else
|
||||
source
|
||||
end
|
||||
end
|
||||
|
||||
# Pick an asset host for this source. Returns +nil+ if no host is set,
|
||||
@@ -591,7 +586,7 @@ module ActionView
|
||||
expanded_sources = sources.collect do |source|
|
||||
determine_source(source, @@javascript_expansions)
|
||||
end.flatten
|
||||
expanded_sources << "application" if sources.include?(:defaults) && file_exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
|
||||
expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
|
||||
expanded_sources
|
||||
end
|
||||
end
|
||||
@@ -617,12 +612,21 @@ module ActionView
|
||||
end
|
||||
|
||||
def join_asset_file_contents(paths)
|
||||
paths.collect { |path| File.read(File.join(ASSETS_DIR, path.split("?").first)) }.join("\n\n")
|
||||
paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n")
|
||||
end
|
||||
|
||||
def write_asset_file_contents(joined_asset_path, asset_paths)
|
||||
FileUtils.mkdir_p(File.dirname(joined_asset_path))
|
||||
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
|
||||
|
||||
# Set mtime to the latest of the combined files to allow for
|
||||
# consistent ETag without a shared filesystem.
|
||||
mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
|
||||
File.utime(mt, mt, joined_asset_path)
|
||||
end
|
||||
|
||||
def asset_file_path(path)
|
||||
File.join(ASSETS_DIR, path.split('?').first)
|
||||
end
|
||||
|
||||
def collect_asset_files(*path)
|
||||
|
||||
@@ -17,7 +17,7 @@ module ActionView
|
||||
# # GET /posts.atom
|
||||
# def index
|
||||
# @posts = Post.find(:all)
|
||||
#
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html
|
||||
# format.atom
|
||||
@@ -29,12 +29,12 @@ module ActionView
|
||||
# atom_feed do |feed|
|
||||
# feed.title("My great blog!")
|
||||
# feed.updated((@posts.first.created_at))
|
||||
#
|
||||
#
|
||||
# for post in @posts
|
||||
# feed.entry(post) do |entry|
|
||||
# entry.title(post.title)
|
||||
# entry.content(post.body, :type => 'html')
|
||||
#
|
||||
#
|
||||
# entry.author do |author|
|
||||
# author.name("DHH")
|
||||
# end
|
||||
@@ -47,8 +47,9 @@ module ActionView
|
||||
# * <tt>:language</tt>: Defaults to "en-US".
|
||||
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
|
||||
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
|
||||
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
|
||||
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
|
||||
# * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}"
|
||||
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
|
||||
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
|
||||
# 2005 is used (as an "I don't care" value).
|
||||
#
|
||||
# Other namespaces can be added to the root element:
|
||||
@@ -81,7 +82,7 @@ module ActionView
|
||||
else
|
||||
options[:schema_date] = "2005" # The Atom spec copyright date
|
||||
end
|
||||
|
||||
|
||||
xml = options[:xml] || eval("xml", block.binding)
|
||||
xml.instruct!
|
||||
|
||||
@@ -89,10 +90,10 @@ module ActionView
|
||||
feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}
|
||||
|
||||
xml.feed(feed_opts) do
|
||||
xml.id("tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}")
|
||||
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}")
|
||||
xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
|
||||
xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
|
||||
|
||||
|
||||
yield AtomFeedBuilder.new(xml, self, options)
|
||||
end
|
||||
end
|
||||
@@ -102,7 +103,7 @@ module ActionView
|
||||
def initialize(xml, view, feed_options = {})
|
||||
@xml, @view, @feed_options = xml, view, feed_options
|
||||
end
|
||||
|
||||
|
||||
# Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
|
||||
def updated(date_or_time = nil)
|
||||
@xml.updated((date_or_time || Time.now.utc).xmlschema)
|
||||
@@ -115,9 +116,10 @@ module ActionView
|
||||
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
|
||||
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
|
||||
# * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
|
||||
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
|
||||
def entry(record, options = {})
|
||||
@xml.entry do
|
||||
@xml.id("tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
|
||||
@xml.entry do
|
||||
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
|
||||
|
||||
if options[:published] || (record.respond_to?(:created_at) && record.created_at)
|
||||
@xml.published((options[:published] || record.created_at).xmlschema)
|
||||
|
||||
@@ -32,7 +32,7 @@ module ActionView
|
||||
# <i>Topics listed alphabetically</i>
|
||||
# <% end %>
|
||||
def cache(name = {}, options = nil, &block)
|
||||
_last_render.handler.new(@controller).cache_fragment(block, name, options)
|
||||
@controller.fragment_for(output_buffer, name, options, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -122,14 +122,15 @@ module ActionView
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
def with_output_buffer(buf = '')
|
||||
self.output_buffer, old_buffer = buf, output_buffer
|
||||
yield
|
||||
output_buffer
|
||||
ensure
|
||||
self.output_buffer = old_buffer
|
||||
end
|
||||
# Use an alternate output buffer for the duration of the block.
|
||||
# Defaults to a new empty string.
|
||||
def with_output_buffer(buf = '') #:nodoc:
|
||||
self.output_buffer, old_buffer = buf, output_buffer
|
||||
yield
|
||||
output_buffer
|
||||
ensure
|
||||
self.output_buffer = old_buffer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
758
actionpack/lib/action_view/helpers/date_helper.rb
Executable file → Normal file
758
actionpack/lib/action_view/helpers/date_helper.rb
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,21 +2,28 @@ module ActionView
|
||||
module Helpers
|
||||
# Provides a set of methods for making it easier to debug Rails objects.
|
||||
module DebugHelper
|
||||
# Returns a <pre>-tag that has +object+ dumped by YAML. This creates a very
|
||||
# readable way to inspect an object.
|
||||
# Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
|
||||
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
|
||||
# Useful for inspecting an object at the time of rendering.
|
||||
#
|
||||
# ==== Example
|
||||
# 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>
|
||||
# @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %>
|
||||
# debug(@user)
|
||||
# # =>
|
||||
# <pre class='debug_dump'>--- !ruby/object:User
|
||||
# attributes:
|
||||
# updated_at:
|
||||
# username: testing
|
||||
#
|
||||
# age: 42
|
||||
# password: xyz
|
||||
# created_at:
|
||||
# attributes_cache: {}
|
||||
#
|
||||
# new_record: true
|
||||
# </pre>
|
||||
|
||||
def debug(object)
|
||||
begin
|
||||
Marshal::dump(object)
|
||||
@@ -28,4 +35,4 @@ module ActionView
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
92
actionpack/lib/action_view/helpers/form_country_helper.rb
Normal file
92
actionpack/lib/action_view/helpers/form_country_helper.rb
Normal file
@@ -0,0 +1,92 @@
|
||||
require 'action_view/helpers/form_options_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
module FormCountryHelper
|
||||
|
||||
# Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
|
||||
def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
|
||||
end
|
||||
|
||||
# Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
|
||||
# have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
|
||||
# that they will be listed above the rest of the (long) list.
|
||||
#
|
||||
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
|
||||
def country_options_for_select(selected = nil, priority_countries = nil)
|
||||
country_options = ""
|
||||
|
||||
if priority_countries
|
||||
country_options += options_for_select(priority_countries, selected)
|
||||
country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
|
||||
end
|
||||
|
||||
return country_options + options_for_select(COUNTRIES, selected)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# All the countries included in the country_options output.
|
||||
COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
|
||||
"Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
|
||||
"Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
|
||||
"Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
|
||||
"British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
|
||||
"Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
|
||||
"Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
|
||||
"Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
|
||||
"Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
|
||||
"El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
|
||||
"Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
|
||||
"French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
|
||||
"Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
|
||||
"Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
|
||||
"Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
|
||||
"Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
|
||||
"Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
|
||||
"Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
|
||||
"Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
|
||||
"Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
|
||||
"Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
|
||||
"Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
|
||||
"Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
|
||||
"Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
|
||||
"Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
|
||||
"Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
|
||||
"Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
|
||||
"Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
|
||||
"Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
|
||||
"South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
|
||||
"Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
|
||||
"Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
|
||||
"Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
|
||||
"Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
|
||||
"United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
|
||||
"Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
|
||||
"Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
|
||||
end
|
||||
|
||||
class InstanceTag #:nodoc:
|
||||
include FormCountryHelper
|
||||
|
||||
def to_country_select_tag(priority_countries, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag("select",
|
||||
add_options(
|
||||
country_options_for_select(value, priority_countries),
|
||||
options, value
|
||||
), html_options
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
class FormBuilder
|
||||
def country_select(method, priority_countries = nil, options = {}, html_options = {})
|
||||
@template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -76,7 +76,7 @@ module ActionView
|
||||
# Creates a form and a scope around a specific model object that is used as
|
||||
# a base for questioning about values for the fields.
|
||||
#
|
||||
# Rails provides succint resource-oriented form generation with +form_for+
|
||||
# Rails provides succinct resource-oriented form generation with +form_for+
|
||||
# like this:
|
||||
#
|
||||
# <% form_for @offer do |f| %>
|
||||
@@ -304,10 +304,6 @@ module ActionView
|
||||
when String, Symbol
|
||||
object_name = record_or_name_or_array
|
||||
object = args.first
|
||||
when Array
|
||||
object = record_or_name_or_array.last
|
||||
object_name = ActionController::RecordIdentifier.singular_class_name(object)
|
||||
apply_form_for_options!(record_or_name_or_array, options)
|
||||
else
|
||||
object = record_or_name_or_array
|
||||
object_name = ActionController::RecordIdentifier.singular_class_name(object)
|
||||
@@ -449,8 +445,37 @@ module ActionView
|
||||
# assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
|
||||
# integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
|
||||
# hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
|
||||
# is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything,
|
||||
# we add a hidden value with the same name as the checkbox as a work around.
|
||||
# is set to 0 which is convenient for boolean values.
|
||||
#
|
||||
# ==== Gotcha
|
||||
#
|
||||
# The HTML specification says unchecked check boxes are not successful, and
|
||||
# thus web browsers do not send them. Unfortunately this introduces a gotcha:
|
||||
# if an Invoice model has a +paid+ flag, and in the form that edits a paid
|
||||
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
|
||||
# any mass-assignment idiom like
|
||||
#
|
||||
# @invoice.update_attributes(params[:invoice])
|
||||
#
|
||||
# wouldn't update the flag.
|
||||
#
|
||||
# To prevent this the helper generates a hidden field with the same name as
|
||||
# the checkbox after the very check box. So, the client either sends only the
|
||||
# hidden field (representing the check box is unchecked), or both fields.
|
||||
# Since the HTML specification says key/value pairs have to be sent in the
|
||||
# same order they appear in the form and Rails parameters extraction always
|
||||
# gets the first occurrence of any given key, that works in ordinary forms.
|
||||
#
|
||||
# Unfortunately that workaround does not work when the check box goes
|
||||
# within an array-like parameter, as in
|
||||
#
|
||||
# <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
|
||||
# <%= form.check_box :paid %>
|
||||
# ...
|
||||
# <% end %>
|
||||
#
|
||||
# because parameter name repetition is precisely what Rails seeks to distinguish
|
||||
# the elements of the array.
|
||||
#
|
||||
# ==== Examples
|
||||
# # Let's say that @post.validated? is 1:
|
||||
@@ -503,10 +528,10 @@ module ActionView
|
||||
|
||||
def initialize(object_name, method_name, template_object, object = nil)
|
||||
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
|
||||
@template_object= template_object
|
||||
@template_object = template_object
|
||||
@object = object
|
||||
if @object_name.sub!(/\[\]$/,"")
|
||||
if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
|
||||
if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
|
||||
if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
|
||||
@auto_index = object.to_param
|
||||
else
|
||||
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
|
||||
@@ -683,7 +708,7 @@ module ActionView
|
||||
end
|
||||
|
||||
def sanitized_object_name
|
||||
@sanitized_object_name ||= @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
||||
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
||||
end
|
||||
|
||||
def sanitized_method_name
|
||||
@@ -701,6 +726,13 @@ module ActionView
|
||||
def initialize(object_name, object, template, options, proc)
|
||||
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
|
||||
@default_options = @options ? @options.slice(:index) : {}
|
||||
if @object_name.to_s.match(/\[\]$/)
|
||||
if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
|
||||
@auto_index = object.to_param
|
||||
else
|
||||
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
(field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
|
||||
@@ -713,16 +745,25 @@ module ActionView
|
||||
end
|
||||
|
||||
def fields_for(record_or_name_or_array, *args, &block)
|
||||
if options.has_key?(:index)
|
||||
index = "[#{options[:index]}]"
|
||||
elsif defined?(@auto_index)
|
||||
self.object_name = @object_name.to_s.sub(/\[\]$/,"")
|
||||
index = "[#{@auto_index}]"
|
||||
else
|
||||
index = ""
|
||||
end
|
||||
|
||||
case record_or_name_or_array
|
||||
when String, Symbol
|
||||
name = "#{object_name}[#{record_or_name_or_array}]"
|
||||
name = "#{object_name}#{index}[#{record_or_name_or_array}]"
|
||||
when Array
|
||||
object = record_or_name_or_array.last
|
||||
name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
args.unshift(object)
|
||||
else
|
||||
object = record_or_name_or_array
|
||||
name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
|
||||
args.unshift(object)
|
||||
end
|
||||
|
||||
@@ -741,8 +782,8 @@ module ActionView
|
||||
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
|
||||
end
|
||||
|
||||
def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
|
||||
@template.error_message_on(@object, method, prepend_text, append_text, css_class)
|
||||
def error_message_on(method, *args)
|
||||
@template.error_message_on(@object, method, *args)
|
||||
end
|
||||
|
||||
def error_messages(options = {})
|
||||
|
||||
@@ -133,11 +133,6 @@ module ActionView
|
||||
InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
|
||||
end
|
||||
|
||||
# Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
|
||||
def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
|
||||
InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
|
||||
end
|
||||
|
||||
# Return select and option tags for the given object and method, using
|
||||
# #time_zone_options_for_select to generate the list of option tags.
|
||||
#
|
||||
@@ -274,22 +269,6 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
|
||||
# have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
|
||||
# that they will be listed above the rest of the (long) list.
|
||||
#
|
||||
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
|
||||
def country_options_for_select(selected = nil, priority_countries = nil)
|
||||
country_options = ""
|
||||
|
||||
if priority_countries
|
||||
country_options += options_for_select(priority_countries, selected)
|
||||
country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
|
||||
end
|
||||
|
||||
return country_options + options_for_select(COUNTRIES, selected)
|
||||
end
|
||||
|
||||
# Returns a string of option tags for pretty much any time zone in the
|
||||
# world. Supply a TimeZone name as +selected+ to have it marked as the
|
||||
# selected option tag. You can also supply an array of TimeZone objects
|
||||
@@ -347,43 +326,7 @@ module ActionView
|
||||
end
|
||||
|
||||
# All the countries included in the country_options output.
|
||||
COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola",
|
||||
"Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria",
|
||||
"Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin",
|
||||
"Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil",
|
||||
"British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
|
||||
"Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
|
||||
"Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
|
||||
"Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba",
|
||||
"Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt",
|
||||
"El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)",
|
||||
"Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia",
|
||||
"French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea",
|
||||
"Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)",
|
||||
"Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq",
|
||||
"Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya",
|
||||
"Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan",
|
||||
"Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya",
|
||||
"Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of",
|
||||
"Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
|
||||
"Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of",
|
||||
"Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
|
||||
"Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger",
|
||||
"Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau",
|
||||
"Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines",
|
||||
"Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation",
|
||||
"Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
|
||||
"Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
|
||||
"Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
|
||||
"Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa",
|
||||
"South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname",
|
||||
"Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic",
|
||||
"Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste",
|
||||
"Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
|
||||
"Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
|
||||
"United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela",
|
||||
"Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara",
|
||||
"Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES")
|
||||
COUNTRIES = ActiveSupport::Deprecation::DeprecatedConstantProxy.new 'COUNTRIES', 'ActionView::Helpers::FormCountryHelper::COUNTRIES'
|
||||
end
|
||||
|
||||
class InstanceTag #:nodoc:
|
||||
@@ -406,18 +349,6 @@ module ActionView
|
||||
)
|
||||
end
|
||||
|
||||
def to_country_select_tag(priority_countries, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
value = value(object)
|
||||
content_tag("select",
|
||||
add_options(
|
||||
country_options_for_select(value, priority_countries),
|
||||
options, value
|
||||
), html_options
|
||||
)
|
||||
end
|
||||
|
||||
def to_time_zone_select_tag(priority_zones, options, html_options)
|
||||
html_options = html_options.stringify_keys
|
||||
add_default_name_and_id(html_options)
|
||||
@@ -452,10 +383,6 @@ module ActionView
|
||||
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
|
||||
end
|
||||
|
||||
def country_select(method, priority_countries = nil, options = {}, html_options = {})
|
||||
@template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options))
|
||||
end
|
||||
|
||||
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
||||
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
|
||||
end
|
||||
|
||||
@@ -129,7 +129,7 @@ module ActionView
|
||||
# label_tag 'name', nil, :class => 'small_label'
|
||||
# # => <label for="name" class="small_label">Name</label>
|
||||
def label_tag(name, text = nil, options = {})
|
||||
content_tag :label, text || name.humanize, { "for" => name }.update(options.stringify_keys)
|
||||
content_tag :label, text || name.to_s.humanize, { "for" => name }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
# Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or
|
||||
|
||||
@@ -44,13 +44,22 @@ module ActionView
|
||||
|
||||
include PrototypeHelper
|
||||
|
||||
# Returns a link that will trigger a JavaScript +function+ using the
|
||||
# Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
|
||||
# onclick handler and return false after the fact.
|
||||
#
|
||||
# The first argument +name+ is used as the link text.
|
||||
#
|
||||
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
|
||||
#
|
||||
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
|
||||
#
|
||||
#
|
||||
# Examples:
|
||||
# link_to_function "Greeting", "alert('Hello world!')"
|
||||
# Produces:
|
||||
@@ -89,13 +98,21 @@ module ActionView
|
||||
content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
|
||||
end
|
||||
|
||||
# Returns a button that'll trigger a JavaScript +function+ using the
|
||||
# Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
|
||||
# onclick handler.
|
||||
#
|
||||
# The first argument +name+ is used as the button's value or display text.
|
||||
#
|
||||
# The next arguments are optional and may include the javascript function definition and a hash of html_options.
|
||||
#
|
||||
# The +function+ argument can be omitted in favor of an +update_page+
|
||||
# block, which evaluates to a string when the template is rendered
|
||||
# (instead of making an Ajax request first).
|
||||
#
|
||||
# The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
|
||||
#
|
||||
# Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
|
||||
#
|
||||
# Examples:
|
||||
# button_to_function "Greeting", "alert('Hello world!')"
|
||||
# button_to_function "Delete", "if (confirm('Really?')) do_delete()"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Prototype JavaScript framework, version 1.6.0.1
|
||||
* (c) 2005-2007 Sam Stephenson
|
||||
/* Prototype JavaScript framework, version 1.6.0.2
|
||||
* (c) 2005-2008 Sam Stephenson
|
||||
*
|
||||
* Prototype is freely distributable under the terms of an MIT-style license.
|
||||
* For details, see the Prototype web site: http://www.prototypejs.org/
|
||||
@@ -7,7 +7,7 @@
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
var Prototype = {
|
||||
Version: '1.6.0.1',
|
||||
Version: '1.6.0.2',
|
||||
|
||||
Browser: {
|
||||
IE: !!(window.attachEvent && !window.opera),
|
||||
@@ -110,7 +110,7 @@ Object.extend(Object, {
|
||||
try {
|
||||
if (Object.isUndefined(object)) return 'undefined';
|
||||
if (object === null) return 'null';
|
||||
return object.inspect ? object.inspect() : object.toString();
|
||||
return object.inspect ? object.inspect() : String(object);
|
||||
} catch (e) {
|
||||
if (e instanceof RangeError) return '...';
|
||||
throw e;
|
||||
@@ -171,7 +171,8 @@ Object.extend(Object, {
|
||||
},
|
||||
|
||||
isArray: function(object) {
|
||||
return object && object.constructor === Array;
|
||||
return object != null && typeof object == "object" &&
|
||||
'splice' in object && 'join' in object;
|
||||
},
|
||||
|
||||
isHash: function(object) {
|
||||
@@ -578,7 +579,7 @@ var Template = Class.create({
|
||||
}
|
||||
|
||||
return before + String.interpret(ctx);
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
});
|
||||
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
|
||||
@@ -806,20 +807,20 @@ Object.extend(Enumerable, {
|
||||
function $A(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (iterable.toArray) return iterable.toArray();
|
||||
var length = iterable.length, results = new Array(length);
|
||||
var length = iterable.length || 0, results = new Array(length);
|
||||
while (length--) results[length] = iterable[length];
|
||||
return results;
|
||||
}
|
||||
|
||||
if (Prototype.Browser.WebKit) {
|
||||
function $A(iterable) {
|
||||
$A = function(iterable) {
|
||||
if (!iterable) return [];
|
||||
if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
|
||||
iterable.toArray) return iterable.toArray();
|
||||
var length = iterable.length, results = new Array(length);
|
||||
var length = iterable.length || 0, results = new Array(length);
|
||||
while (length--) results[length] = iterable[length];
|
||||
return results;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Array.from = $A;
|
||||
@@ -1298,7 +1299,7 @@ Ajax.Request = Class.create(Ajax.Base, {
|
||||
|
||||
var contentType = response.getHeader('Content-type');
|
||||
if (this.options.evalJS == 'force'
|
||||
|| (this.options.evalJS && contentType
|
||||
|| (this.options.evalJS && this.isSameOrigin() && contentType
|
||||
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
|
||||
this.evalResponse();
|
||||
}
|
||||
@@ -1316,9 +1317,18 @@ Ajax.Request = Class.create(Ajax.Base, {
|
||||
}
|
||||
},
|
||||
|
||||
isSameOrigin: function() {
|
||||
var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
|
||||
return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
|
||||
protocol: location.protocol,
|
||||
domain: document.domain,
|
||||
port: location.port ? ':' + location.port : ''
|
||||
}));
|
||||
},
|
||||
|
||||
getHeader: function(name) {
|
||||
try {
|
||||
return this.transport.getResponseHeader(name);
|
||||
return this.transport.getResponseHeader(name) || null;
|
||||
} catch (e) { return null }
|
||||
},
|
||||
|
||||
@@ -1391,7 +1401,8 @@ Ajax.Response = Class.create({
|
||||
if (!json) return null;
|
||||
json = decodeURIComponent(escape(json));
|
||||
try {
|
||||
return json.evalJSON(this.request.options.sanitizeJSON);
|
||||
return json.evalJSON(this.request.options.sanitizeJSON ||
|
||||
!this.request.isSameOrigin());
|
||||
} catch (e) {
|
||||
this.request.dispatchException(e);
|
||||
}
|
||||
@@ -1404,7 +1415,8 @@ Ajax.Response = Class.create({
|
||||
this.responseText.blank())
|
||||
return null;
|
||||
try {
|
||||
return this.responseText.evalJSON(options.sanitizeJSON);
|
||||
return this.responseText.evalJSON(options.sanitizeJSON ||
|
||||
!this.request.isSameOrigin());
|
||||
} catch (e) {
|
||||
this.request.dispatchException(e);
|
||||
}
|
||||
@@ -1608,24 +1620,28 @@ Element.Methods = {
|
||||
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
|
||||
insertions = {bottom:insertions};
|
||||
|
||||
var content, t, range;
|
||||
var content, insert, tagName, childNodes;
|
||||
|
||||
for (position in insertions) {
|
||||
for (var position in insertions) {
|
||||
content = insertions[position];
|
||||
position = position.toLowerCase();
|
||||
t = Element._insertionTranslations[position];
|
||||
insert = Element._insertionTranslations[position];
|
||||
|
||||
if (content && content.toElement) content = content.toElement();
|
||||
if (Object.isElement(content)) {
|
||||
t.insert(element, content);
|
||||
insert(element, content);
|
||||
continue;
|
||||
}
|
||||
|
||||
content = Object.toHTML(content);
|
||||
|
||||
range = element.ownerDocument.createRange();
|
||||
t.initializeRange(element, range);
|
||||
t.insert(element, range.createContextualFragment(content.stripScripts()));
|
||||
tagName = ((position == 'before' || position == 'after')
|
||||
? element.parentNode : element).tagName.toUpperCase();
|
||||
|
||||
childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
|
||||
|
||||
if (position == 'top' || position == 'after') childNodes.reverse();
|
||||
childNodes.each(insert.curry(element));
|
||||
|
||||
content.evalScripts.bind(content).defer();
|
||||
}
|
||||
@@ -1670,7 +1686,7 @@ Element.Methods = {
|
||||
},
|
||||
|
||||
descendants: function(element) {
|
||||
return $(element).getElementsBySelector("*");
|
||||
return $(element).select("*");
|
||||
},
|
||||
|
||||
firstDescendant: function(element) {
|
||||
@@ -1709,32 +1725,31 @@ Element.Methods = {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return $(element.parentNode);
|
||||
var ancestors = element.ancestors();
|
||||
return expression ? Selector.findElement(ancestors, expression, index) :
|
||||
ancestors[index || 0];
|
||||
return Object.isNumber(expression) ? ancestors[expression] :
|
||||
Selector.findElement(ancestors, expression, index);
|
||||
},
|
||||
|
||||
down: function(element, expression, index) {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return element.firstDescendant();
|
||||
var descendants = element.descendants();
|
||||
return expression ? Selector.findElement(descendants, expression, index) :
|
||||
descendants[index || 0];
|
||||
return Object.isNumber(expression) ? element.descendants()[expression] :
|
||||
element.select(expression)[index || 0];
|
||||
},
|
||||
|
||||
previous: function(element, expression, index) {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
|
||||
var previousSiblings = element.previousSiblings();
|
||||
return expression ? Selector.findElement(previousSiblings, expression, index) :
|
||||
previousSiblings[index || 0];
|
||||
return Object.isNumber(expression) ? previousSiblings[expression] :
|
||||
Selector.findElement(previousSiblings, expression, index);
|
||||
},
|
||||
|
||||
next: function(element, expression, index) {
|
||||
element = $(element);
|
||||
if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
|
||||
var nextSiblings = element.nextSiblings();
|
||||
return expression ? Selector.findElement(nextSiblings, expression, index) :
|
||||
nextSiblings[index || 0];
|
||||
return Object.isNumber(expression) ? nextSiblings[expression] :
|
||||
Selector.findElement(nextSiblings, expression, index);
|
||||
},
|
||||
|
||||
select: function() {
|
||||
@@ -1860,7 +1875,8 @@ Element.Methods = {
|
||||
do { ancestor = ancestor.parentNode; }
|
||||
while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
|
||||
}
|
||||
if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
|
||||
if (nextAncestor && nextAncestor.sourceIndex)
|
||||
return (e > a && e < nextAncestor.sourceIndex);
|
||||
}
|
||||
|
||||
while (element = element.parentNode)
|
||||
@@ -2004,7 +2020,7 @@ Element.Methods = {
|
||||
if (element) {
|
||||
if (element.tagName == 'BODY') break;
|
||||
var p = Element.getStyle(element, 'position');
|
||||
if (p == 'relative' || p == 'absolute') break;
|
||||
if (p !== 'static') break;
|
||||
}
|
||||
} while (element);
|
||||
return Element._returnOffset(valueL, valueT);
|
||||
@@ -2153,46 +2169,6 @@ Element._attributeTranslations = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (!document.createRange || Prototype.Browser.Opera) {
|
||||
Element.Methods.insert = function(element, insertions) {
|
||||
element = $(element);
|
||||
|
||||
if (Object.isString(insertions) || Object.isNumber(insertions) ||
|
||||
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
|
||||
insertions = { bottom: insertions };
|
||||
|
||||
var t = Element._insertionTranslations, content, position, pos, tagName;
|
||||
|
||||
for (position in insertions) {
|
||||
content = insertions[position];
|
||||
position = position.toLowerCase();
|
||||
pos = t[position];
|
||||
|
||||
if (content && content.toElement) content = content.toElement();
|
||||
if (Object.isElement(content)) {
|
||||
pos.insert(element, content);
|
||||
continue;
|
||||
}
|
||||
|
||||
content = Object.toHTML(content);
|
||||
tagName = ((position == 'before' || position == 'after')
|
||||
? element.parentNode : element).tagName.toUpperCase();
|
||||
|
||||
if (t.tags[tagName]) {
|
||||
var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
|
||||
if (position == 'top' || position == 'after') fragments.reverse();
|
||||
fragments.each(pos.insert.curry(element));
|
||||
}
|
||||
else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
|
||||
|
||||
content.evalScripts.bind(content).defer();
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
}
|
||||
|
||||
if (Prototype.Browser.Opera) {
|
||||
Element.Methods.getStyle = Element.Methods.getStyle.wrap(
|
||||
function(proceed, element, style) {
|
||||
@@ -2237,12 +2213,31 @@ if (Prototype.Browser.Opera) {
|
||||
}
|
||||
|
||||
else if (Prototype.Browser.IE) {
|
||||
$w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
|
||||
// IE doesn't report offsets correctly for static elements, so we change them
|
||||
// to "relative" to get the values, then change them back.
|
||||
Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
|
||||
function(proceed, element) {
|
||||
element = $(element);
|
||||
var position = element.getStyle('position');
|
||||
if (position !== 'static') return proceed(element);
|
||||
element.setStyle({ position: 'relative' });
|
||||
var value = proceed(element);
|
||||
element.setStyle({ position: position });
|
||||
return value;
|
||||
}
|
||||
);
|
||||
|
||||
$w('positionedOffset viewportOffset').each(function(method) {
|
||||
Element.Methods[method] = Element.Methods[method].wrap(
|
||||
function(proceed, element) {
|
||||
element = $(element);
|
||||
var position = element.getStyle('position');
|
||||
if (position != 'static') return proceed(element);
|
||||
if (position !== 'static') return proceed(element);
|
||||
// Trigger hasLayout on the offset parent so that IE6 reports
|
||||
// accurate offsetTop and offsetLeft values for position: fixed.
|
||||
var offsetParent = element.getOffsetParent();
|
||||
if (offsetParent && offsetParent.getStyle('position') === 'fixed')
|
||||
offsetParent.setStyle({ zoom: 1 });
|
||||
element.setStyle({ position: 'relative' });
|
||||
var value = proceed(element);
|
||||
element.setStyle({ position: position });
|
||||
@@ -2324,7 +2319,10 @@ else if (Prototype.Browser.IE) {
|
||||
};
|
||||
|
||||
Element._attributeTranslations.write = {
|
||||
names: Object.clone(Element._attributeTranslations.read.names),
|
||||
names: Object.extend({
|
||||
cellpadding: 'cellPadding',
|
||||
cellspacing: 'cellSpacing'
|
||||
}, Element._attributeTranslations.read.names),
|
||||
values: {
|
||||
checked: function(element, value) {
|
||||
element.checked = !!value;
|
||||
@@ -2444,7 +2442,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) {
|
||||
};
|
||||
}
|
||||
|
||||
if (document.createElement('div').outerHTML) {
|
||||
if ('outerHTML' in document.createElement('div')) {
|
||||
Element.Methods.replace = function(element, content) {
|
||||
element = $(element);
|
||||
|
||||
@@ -2482,45 +2480,25 @@ Element._returnOffset = function(l, t) {
|
||||
|
||||
Element._getContentFromAnonymousElement = function(tagName, html) {
|
||||
var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
|
||||
div.innerHTML = t[0] + html + t[1];
|
||||
t[2].times(function() { div = div.firstChild });
|
||||
if (t) {
|
||||
div.innerHTML = t[0] + html + t[1];
|
||||
t[2].times(function() { div = div.firstChild });
|
||||
} else div.innerHTML = html;
|
||||
return $A(div.childNodes);
|
||||
};
|
||||
|
||||
Element._insertionTranslations = {
|
||||
before: {
|
||||
adjacency: 'beforeBegin',
|
||||
insert: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element);
|
||||
},
|
||||
initializeRange: function(element, range) {
|
||||
range.setStartBefore(element);
|
||||
}
|
||||
before: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element);
|
||||
},
|
||||
top: {
|
||||
adjacency: 'afterBegin',
|
||||
insert: function(element, node) {
|
||||
element.insertBefore(node, element.firstChild);
|
||||
},
|
||||
initializeRange: function(element, range) {
|
||||
range.selectNodeContents(element);
|
||||
range.collapse(true);
|
||||
}
|
||||
top: function(element, node) {
|
||||
element.insertBefore(node, element.firstChild);
|
||||
},
|
||||
bottom: {
|
||||
adjacency: 'beforeEnd',
|
||||
insert: function(element, node) {
|
||||
element.appendChild(node);
|
||||
}
|
||||
bottom: function(element, node) {
|
||||
element.appendChild(node);
|
||||
},
|
||||
after: {
|
||||
adjacency: 'afterEnd',
|
||||
insert: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element.nextSibling);
|
||||
},
|
||||
initializeRange: function(element, range) {
|
||||
range.setStartAfter(element);
|
||||
}
|
||||
after: function(element, node) {
|
||||
element.parentNode.insertBefore(node, element.nextSibling);
|
||||
},
|
||||
tags: {
|
||||
TABLE: ['<table>', '</table>', 1],
|
||||
@@ -2532,7 +2510,6 @@ Element._insertionTranslations = {
|
||||
};
|
||||
|
||||
(function() {
|
||||
this.bottom.initializeRange = this.top.initializeRange;
|
||||
Object.extend(this.tags, {
|
||||
THEAD: this.tags.TBODY,
|
||||
TFOOT: this.tags.TBODY,
|
||||
@@ -2716,7 +2693,7 @@ document.viewport = {
|
||||
window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
|
||||
}
|
||||
};
|
||||
/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
|
||||
/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
|
||||
* part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
|
||||
* license. Please see http://www.yui-ext.com/ for more information. */
|
||||
|
||||
@@ -2959,13 +2936,13 @@ Object.extend(Selector, {
|
||||
},
|
||||
|
||||
criteria: {
|
||||
tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
|
||||
className: 'n = h.className(n, r, "#{1}", c); c = false;',
|
||||
id: 'n = h.id(n, r, "#{1}", c); c = false;',
|
||||
attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
|
||||
tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
|
||||
className: 'n = h.className(n, r, "#{1}", c); c = false;',
|
||||
id: 'n = h.id(n, r, "#{1}", c); c = false;',
|
||||
attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
|
||||
attr: function(m) {
|
||||
m[3] = (m[5] || m[6]);
|
||||
return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
|
||||
return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
|
||||
},
|
||||
pseudo: function(m) {
|
||||
if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
|
||||
@@ -2989,7 +2966,8 @@ Object.extend(Selector, {
|
||||
tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
|
||||
id: /^#([\w\-\*]+)(\b|$)/,
|
||||
className: /^\.([\w\-\*]+)(\b|$)/,
|
||||
pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
|
||||
pseudo:
|
||||
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
|
||||
attrPresence: /^\[([\w]+)\]/,
|
||||
attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
|
||||
},
|
||||
@@ -3014,7 +2992,7 @@ Object.extend(Selector, {
|
||||
|
||||
attr: function(element, matches) {
|
||||
var nodeValue = Element.readAttribute(element, matches[1]);
|
||||
return Selector.operators[matches[2]](nodeValue, matches[3]);
|
||||
return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3029,14 +3007,15 @@ Object.extend(Selector, {
|
||||
|
||||
// marks an array of nodes for counting
|
||||
mark: function(nodes) {
|
||||
var _true = Prototype.emptyFunction;
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._counted = true;
|
||||
node._countedByPrototype = _true;
|
||||
return nodes;
|
||||
},
|
||||
|
||||
unmark: function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node._counted = undefined;
|
||||
node._countedByPrototype = undefined;
|
||||
return nodes;
|
||||
},
|
||||
|
||||
@@ -3044,15 +3023,15 @@ Object.extend(Selector, {
|
||||
// "ofType" flag indicates whether we're indexing for nth-of-type
|
||||
// rather than nth-child
|
||||
index: function(parentNode, reverse, ofType) {
|
||||
parentNode._counted = true;
|
||||
parentNode._countedByPrototype = Prototype.emptyFunction;
|
||||
if (reverse) {
|
||||
for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
|
||||
var node = nodes[i];
|
||||
if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
|
||||
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
|
||||
if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
|
||||
if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3061,8 +3040,8 @@ Object.extend(Selector, {
|
||||
if (nodes.length == 0) return nodes;
|
||||
var results = [], n;
|
||||
for (var i = 0, l = nodes.length; i < l; i++)
|
||||
if (!(n = nodes[i])._counted) {
|
||||
n._counted = true;
|
||||
if (!(n = nodes[i])._countedByPrototype) {
|
||||
n._countedByPrototype = Prototype.emptyFunction;
|
||||
results.push(Element.extend(n));
|
||||
}
|
||||
return Selector.handlers.unmark(results);
|
||||
@@ -3114,7 +3093,7 @@ Object.extend(Selector, {
|
||||
|
||||
// TOKEN FUNCTIONS
|
||||
tagName: function(nodes, root, tagName, combinator) {
|
||||
tagName = tagName.toUpperCase();
|
||||
var uTagName = tagName.toUpperCase();
|
||||
var results = [], h = Selector.handlers;
|
||||
if (nodes) {
|
||||
if (combinator) {
|
||||
@@ -3127,7 +3106,7 @@ Object.extend(Selector, {
|
||||
if (tagName == "*") return nodes;
|
||||
}
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (node.tagName.toUpperCase() == tagName) results.push(node);
|
||||
if (node.tagName.toUpperCase() === uTagName) results.push(node);
|
||||
return results;
|
||||
} else return root.getElementsByTagName(tagName);
|
||||
},
|
||||
@@ -3174,16 +3153,18 @@ Object.extend(Selector, {
|
||||
return results;
|
||||
},
|
||||
|
||||
attrPresence: function(nodes, root, attr) {
|
||||
attrPresence: function(nodes, root, attr, combinator) {
|
||||
if (!nodes) nodes = root.getElementsByTagName("*");
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
var results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
if (Element.hasAttribute(node, attr)) results.push(node);
|
||||
return results;
|
||||
},
|
||||
|
||||
attr: function(nodes, root, attr, value, operator) {
|
||||
attr: function(nodes, root, attr, value, operator, combinator) {
|
||||
if (!nodes) nodes = root.getElementsByTagName("*");
|
||||
if (nodes && combinator) nodes = this[combinator](nodes);
|
||||
var handler = Selector.operators[operator], results = [];
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
var nodeValue = Element.readAttribute(node, attr);
|
||||
@@ -3262,7 +3243,7 @@ Object.extend(Selector, {
|
||||
var h = Selector.handlers, results = [], indexed = [], m;
|
||||
h.mark(nodes);
|
||||
for (var i = 0, node; node = nodes[i]; i++) {
|
||||
if (!node.parentNode._counted) {
|
||||
if (!node.parentNode._countedByPrototype) {
|
||||
h.index(node.parentNode, reverse, ofType);
|
||||
indexed.push(node.parentNode);
|
||||
}
|
||||
@@ -3300,7 +3281,7 @@ Object.extend(Selector, {
|
||||
var exclusions = new Selector(selector).findElements(root);
|
||||
h.mark(exclusions);
|
||||
for (var i = 0, results = [], node; node = nodes[i]; i++)
|
||||
if (!node._counted) results.push(node);
|
||||
if (!node._countedByPrototype) results.push(node);
|
||||
h.unmark(exclusions);
|
||||
return results;
|
||||
},
|
||||
@@ -3334,11 +3315,19 @@ Object.extend(Selector, {
|
||||
'|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
|
||||
},
|
||||
|
||||
split: function(expression) {
|
||||
var expressions = [];
|
||||
expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
|
||||
expressions.push(m[1].strip());
|
||||
});
|
||||
return expressions;
|
||||
},
|
||||
|
||||
matchElements: function(elements, expression) {
|
||||
var matches = new Selector(expression).findElements(), h = Selector.handlers;
|
||||
var matches = $$(expression), h = Selector.handlers;
|
||||
h.mark(matches);
|
||||
for (var i = 0, results = [], element; element = elements[i]; i++)
|
||||
if (element._counted) results.push(element);
|
||||
if (element._countedByPrototype) results.push(element);
|
||||
h.unmark(matches);
|
||||
return results;
|
||||
},
|
||||
@@ -3351,11 +3340,7 @@ Object.extend(Selector, {
|
||||
},
|
||||
|
||||
findChildElements: function(element, expressions) {
|
||||
var exprs = expressions.join(',');
|
||||
expressions = [];
|
||||
exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
|
||||
expressions.push(m[1].strip());
|
||||
});
|
||||
expressions = Selector.split(expressions.join(','));
|
||||
var results = [], h = Selector.handlers;
|
||||
for (var i = 0, l = expressions.length, selector; i < l; i++) {
|
||||
selector = new Selector(expressions[i].strip());
|
||||
@@ -3366,13 +3351,22 @@ Object.extend(Selector, {
|
||||
});
|
||||
|
||||
if (Prototype.Browser.IE) {
|
||||
// IE returns comment nodes on getElementsByTagName("*").
|
||||
// Filter them out.
|
||||
Selector.handlers.concat = function(a, b) {
|
||||
for (var i = 0, node; node = b[i]; i++)
|
||||
if (node.tagName !== "!") a.push(node);
|
||||
return a;
|
||||
};
|
||||
Object.extend(Selector.handlers, {
|
||||
// IE returns comment nodes on getElementsByTagName("*").
|
||||
// Filter them out.
|
||||
concat: function(a, b) {
|
||||
for (var i = 0, node; node = b[i]; i++)
|
||||
if (node.tagName !== "!") a.push(node);
|
||||
return a;
|
||||
},
|
||||
|
||||
// IE improperly serializes _countedByPrototype in (inner|outer)HTML.
|
||||
unmark: function(nodes) {
|
||||
for (var i = 0, node; node = nodes[i]; i++)
|
||||
node.removeAttribute('_countedByPrototype');
|
||||
return nodes;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function $$() {
|
||||
@@ -3850,9 +3844,9 @@ Object.extend(Event, (function() {
|
||||
var cache = Event.cache;
|
||||
|
||||
function getEventID(element) {
|
||||
if (element._eventID) return element._eventID;
|
||||
if (element._prototypeEventID) return element._prototypeEventID[0];
|
||||
arguments.callee.id = arguments.callee.id || 1;
|
||||
return element._eventID = ++arguments.callee.id;
|
||||
return element._prototypeEventID = [++arguments.callee.id];
|
||||
}
|
||||
|
||||
function getDOMEventName(eventName) {
|
||||
@@ -3880,7 +3874,7 @@ Object.extend(Event, (function() {
|
||||
return false;
|
||||
|
||||
Event.extend(event);
|
||||
handler.call(element, event)
|
||||
handler.call(element, event);
|
||||
};
|
||||
|
||||
wrapper.handler = handler;
|
||||
@@ -3962,11 +3956,12 @@ Object.extend(Event, (function() {
|
||||
if (element == document && document.createEvent && !element.dispatchEvent)
|
||||
element = document.documentElement;
|
||||
|
||||
var event;
|
||||
if (document.createEvent) {
|
||||
var event = document.createEvent("HTMLEvents");
|
||||
event = document.createEvent("HTMLEvents");
|
||||
event.initEvent("dataavailable", true, true);
|
||||
} else {
|
||||
var event = document.createEventObject();
|
||||
event = document.createEventObject();
|
||||
event.eventType = "ondataavailable";
|
||||
}
|
||||
|
||||
@@ -3995,20 +3990,21 @@ Element.addMethods({
|
||||
Object.extend(document, {
|
||||
fire: Element.Methods.fire.methodize(),
|
||||
observe: Element.Methods.observe.methodize(),
|
||||
stopObserving: Element.Methods.stopObserving.methodize()
|
||||
stopObserving: Element.Methods.stopObserving.methodize(),
|
||||
loaded: false
|
||||
});
|
||||
|
||||
(function() {
|
||||
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
|
||||
Matthias Miller, Dean Edwards and John Resig. */
|
||||
|
||||
var timer, fired = false;
|
||||
var timer;
|
||||
|
||||
function fireContentLoadedEvent() {
|
||||
if (fired) return;
|
||||
if (document.loaded) return;
|
||||
if (timer) window.clearInterval(timer);
|
||||
document.fire("dom:loaded");
|
||||
fired = true;
|
||||
document.loaded = true;
|
||||
}
|
||||
|
||||
if (document.addEventListener) {
|
||||
|
||||
@@ -22,14 +22,14 @@ module ActionView
|
||||
# number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
|
||||
#
|
||||
# number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
|
||||
# => +1.123.555.1234 x 1343
|
||||
# => +1.123.555.1234 x 1343
|
||||
def number_to_phone(number, options = {})
|
||||
number = number.to_s.strip unless number.nil?
|
||||
options = options.stringify_keys
|
||||
area_code = options["area_code"] || nil
|
||||
delimiter = options["delimiter"] || "-"
|
||||
extension = options["extension"].to_s.strip || nil
|
||||
country_code = options["country_code"] || nil
|
||||
options = options.symbolize_keys
|
||||
area_code = options[:area_code] || nil
|
||||
delimiter = options[:delimiter] || "-"
|
||||
extension = options[:extension].to_s.strip || nil
|
||||
country_code = options[:country_code] || nil
|
||||
|
||||
begin
|
||||
str = ""
|
||||
@@ -51,10 +51,10 @@ module ActionView
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
|
||||
# * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
|
||||
# * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
|
||||
# * <tt>:format</tt> - Sets the format of the output string (defaults to "%u%n"). The field types are:
|
||||
# * <tt>:format</tt> - Sets the format of the output string (defaults to "%u%n"). The field types are:
|
||||
#
|
||||
# %u The currency unit
|
||||
# %n The number
|
||||
@@ -69,16 +69,25 @@ module ActionView
|
||||
# number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u")
|
||||
# # => 1234567890,50 £
|
||||
def number_to_currency(number, options = {})
|
||||
options = options.stringify_keys
|
||||
precision = options["precision"] || 2
|
||||
unit = options["unit"] || "$"
|
||||
separator = precision > 0 ? options["separator"] || "." : ""
|
||||
delimiter = options["delimiter"] || ","
|
||||
format = options["format"] || "%u%n"
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
defaults = defaults.merge(currency)
|
||||
|
||||
precision = options[:precision] || defaults[:precision]
|
||||
unit = options[:unit] || defaults[:unit]
|
||||
separator = options[:separator] || defaults[:separator]
|
||||
delimiter = options[:delimiter] || defaults[:delimiter]
|
||||
format = options[:format] || defaults[:format]
|
||||
separator = '' if precision == 0
|
||||
|
||||
begin
|
||||
parts = number_with_precision(number, precision).split('.')
|
||||
format.gsub(/%n/, number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s).gsub(/%u/, unit)
|
||||
format.gsub(/%n/, number_with_precision(number,
|
||||
:precision => precision,
|
||||
:delimiter => delimiter,
|
||||
:separator => separator)
|
||||
).gsub(/%u/, unit)
|
||||
rescue
|
||||
number
|
||||
end
|
||||
@@ -90,96 +99,192 @@ module ActionView
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_percentage(100) # => 100.000%
|
||||
# number_to_percentage(100, :precision => 0) # => 100%
|
||||
#
|
||||
# number_to_percentage(302.24398923423, :precision => 5)
|
||||
# # => 302.24399%
|
||||
# number_to_percentage(100) # => 100.000%
|
||||
# number_to_percentage(100, :precision => 0) # => 100%
|
||||
# number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000%
|
||||
# number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
|
||||
def number_to_percentage(number, options = {})
|
||||
options = options.stringify_keys
|
||||
precision = options["precision"] || 3
|
||||
separator = options["separator"] || "."
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
defaults = defaults.merge(percentage)
|
||||
|
||||
precision = options[:precision] || defaults[:precision]
|
||||
separator = options[:separator] || defaults[:separator]
|
||||
delimiter = options[:delimiter] || defaults[:delimiter]
|
||||
|
||||
begin
|
||||
number = number_with_precision(number, precision)
|
||||
parts = number.split('.')
|
||||
if parts.at(1).nil?
|
||||
parts[0] + "%"
|
||||
else
|
||||
parts[0] + separator + parts[1].to_s + "%"
|
||||
end
|
||||
number_with_precision(number,
|
||||
:precision => precision,
|
||||
:separator => separator,
|
||||
:delimiter => delimiter) + "%"
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You
|
||||
# can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters.
|
||||
# Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can
|
||||
# customize the format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>delimiter</tt> - Sets the thousands delimiter (defaults to ",").
|
||||
# * <tt>separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_with_delimiter(12345678) # => 12,345,678
|
||||
# number_with_delimiter(12345678.05) # => 12,345,678.05
|
||||
# number_with_delimiter(12345678, ".") # => 12.345.678
|
||||
#
|
||||
# number_with_delimiter(98765432.98, " ", ",")
|
||||
# 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, :seperator => ",") # => 12,345,678
|
||||
# number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
|
||||
# # => 98 765 432,98
|
||||
def number_with_delimiter(number, delimiter=",", separator=".")
|
||||
#
|
||||
# You can still use <tt>number_with_delimiter</tt> with the old API that accepts the
|
||||
# +delimiter+ as its optional second and the +separator+ as its
|
||||
# optional third parameter:
|
||||
# number_with_delimiter(12345678, " ") # => 12 345.678
|
||||
# number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05
|
||||
def number_with_delimiter(number, *args)
|
||||
options = args.extract_options!
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' +
|
||||
'instead of separate delimiter and precision arguments.', caller)
|
||||
delimiter = args[0] || defaults[:delimiter]
|
||||
separator = args[1] || defaults[:separator]
|
||||
end
|
||||
|
||||
delimiter ||= (options[:delimiter] || defaults[:delimiter])
|
||||
separator ||= (options[:separator] || defaults[:separator])
|
||||
|
||||
begin
|
||||
parts = number.to_s.split('.')
|
||||
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
||||
parts.join separator
|
||||
parts.join(separator)
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
# Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default
|
||||
# level of precision is 3.
|
||||
# Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision of 2).
|
||||
# You can customize the format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_with_precision(111.2345) # => 111.235
|
||||
# number_with_precision(111.2345, 2) # => 111.23
|
||||
# number_with_precision(13, 5) # => 13.00000
|
||||
# number_with_precision(389.32314, 0) # => 389
|
||||
def number_with_precision(number, precision=3)
|
||||
"%01.#{precision}f" % ((Float(number) * (10 ** precision)).round.to_f / 10 ** precision)
|
||||
rescue
|
||||
number
|
||||
# number_with_precision(111.2345) # => 111.235
|
||||
# number_with_precision(111.2345, :precision => 2) # => 111.23
|
||||
# number_with_precision(13, :precision => 5) # => 13.00000
|
||||
# number_with_precision(389.32314, :precision => 0) # => 389
|
||||
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
|
||||
# # => 1.111,23
|
||||
#
|
||||
# You can still use <tt>number_with_precision</tt> with the old API that accepts the
|
||||
# +precision+ as its optional second parameter:
|
||||
# number_with_precision(number_with_precision(111.2345, 2) # => 111.23
|
||||
def number_with_precision(number, *args)
|
||||
options = args.extract_options!
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale],
|
||||
:raise => true) rescue {}
|
||||
defaults = defaults.merge(precision_defaults)
|
||||
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' +
|
||||
'instead of a separate precision argument.', caller)
|
||||
precision = args[0] || defaults[:precision]
|
||||
end
|
||||
|
||||
precision ||= (options[:precision] || defaults[:precision])
|
||||
separator ||= (options[:separator] || defaults[:separator])
|
||||
delimiter ||= (options[:delimiter] || defaults[:delimiter])
|
||||
|
||||
begin
|
||||
rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision
|
||||
number_with_delimiter("%01.#{precision}f" % rounded_number,
|
||||
:separator => separator,
|
||||
:delimiter => delimiter)
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
|
||||
STORAGE_UNITS = %w( Bytes KB MB GB TB ).freeze
|
||||
|
||||
# Formats the bytes in +size+ into a more understandable representation
|
||||
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
|
||||
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
|
||||
# reporting file sizes to users. This method returns nil if
|
||||
# +size+ cannot be converted into a number. You can change the default
|
||||
# precision of 1 using the precision parameter +precision+.
|
||||
# +size+ cannot be converted into a number. You can customize the
|
||||
# format in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
# * <tt>:precision</tt> - Sets the level of precision (defaults to 1).
|
||||
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
|
||||
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
|
||||
#
|
||||
# ==== Examples
|
||||
# number_to_human_size(123) # => 123 Bytes
|
||||
# number_to_human_size(1234) # => 1.2 KB
|
||||
# number_to_human_size(12345) # => 12.1 KB
|
||||
# number_to_human_size(1234567) # => 1.2 MB
|
||||
# number_to_human_size(1234567890) # => 1.1 GB
|
||||
# number_to_human_size(1234567890123) # => 1.1 TB
|
||||
# number_to_human_size(123) # => 123 Bytes
|
||||
# number_to_human_size(1234) # => 1.2 KB
|
||||
# number_to_human_size(12345) # => 12.1 KB
|
||||
# number_to_human_size(1234567) # => 1.2 MB
|
||||
# number_to_human_size(1234567890) # => 1.1 GB
|
||||
# number_to_human_size(1234567890123) # => 1.1 TB
|
||||
# number_to_human_size(1234567, :precision => 2) # => 1.18 MB
|
||||
# number_to_human_size(483989, :precision => 0) # => 473 KB
|
||||
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,18 MB
|
||||
#
|
||||
# You can still use <tt>number_to_human_size</tt> with the old API that accepts the
|
||||
# +precision+ as its optional second parameter:
|
||||
# number_to_human_size(1234567, 2) # => 1.18 MB
|
||||
# number_to_human_size(483989, 0) # => 4 MB
|
||||
def number_to_human_size(size, precision=1)
|
||||
size = Kernel.Float(size)
|
||||
case
|
||||
when size.to_i == 1; "1 Byte"
|
||||
when size < 1.kilobyte; "%d Bytes" % size
|
||||
when size < 1.megabyte; "%.#{precision}f KB" % (size / 1.0.kilobyte)
|
||||
when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte)
|
||||
when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte)
|
||||
else "%.#{precision}f TB" % (size / 1.0.terabyte)
|
||||
end.sub(/([0-9]\.\d*?)0+ /, '\1 ' ).sub(/\. /,' ')
|
||||
rescue
|
||||
nil
|
||||
# number_to_human_size(483989, 0) # => 473 KB
|
||||
def number_to_human_size(number, *args)
|
||||
return number.nil? ? nil : pluralize(number.to_i, "Byte") if number.to_i < 1024
|
||||
|
||||
options = args.extract_options!
|
||||
options.symbolize_keys!
|
||||
|
||||
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {}
|
||||
defaults = defaults.merge(human)
|
||||
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' +
|
||||
'instead of a separate precision argument.', caller)
|
||||
precision = args[0] || defaults[:precision]
|
||||
end
|
||||
|
||||
precision ||= (options[:precision] || defaults[:precision])
|
||||
separator ||= (options[:separator] || defaults[:separator])
|
||||
delimiter ||= (options[:delimiter] || defaults[:delimiter])
|
||||
|
||||
max_exp = STORAGE_UNITS.size - 1
|
||||
number = Float(number)
|
||||
exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
|
||||
exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
|
||||
number /= 1024 ** exponent
|
||||
unit = STORAGE_UNITS[exponent]
|
||||
|
||||
begin
|
||||
escaped_separator = Regexp.escape(separator)
|
||||
number_with_precision(number,
|
||||
:precision => precision,
|
||||
:separator => separator,
|
||||
:delimiter => delimiter
|
||||
).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + " #{unit}"
|
||||
rescue
|
||||
number
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,17 +6,13 @@ module ActionView
|
||||
# The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
|
||||
# These helper methods extend ActionView making them callable within your template files.
|
||||
module SanitizeHelper
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# This +sanitize+ helper will html encode all tags and strip all attributes that aren't specifically allowed.
|
||||
# It also strips href/src tags with invalid protocols, like javascript: especially. It does its best to counter any
|
||||
# tricks that hackers may use, like throwing in unicode/ascii/hex values to get past the javascript: filters. Check out
|
||||
# the extensive test suite.
|
||||
#
|
||||
# <%= sanitize @article.body %>
|
||||
#
|
||||
#
|
||||
# You can add or remove tags/attributes if you want to customize it a bit. See ActionView::Base for full docs on the
|
||||
# available options. You can add tags/attributes for single uses of +sanitize+ by passing either the <tt>:attributes</tt> or <tt>:tags</tt> options:
|
||||
#
|
||||
@@ -27,27 +23,27 @@ module ActionView
|
||||
# Custom Use (only the mentioned tags and attributes are allowed, nothing else)
|
||||
#
|
||||
# <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style)
|
||||
#
|
||||
#
|
||||
# Add table tags to the default allowed tags
|
||||
#
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
|
||||
# end
|
||||
#
|
||||
#
|
||||
# Remove tags to the default allowed tags
|
||||
#
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.after_initialize do
|
||||
# ActionView::Base.sanitized_allowed_tags.delete 'div'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# Change allowed default attributes
|
||||
#
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style'
|
||||
# end
|
||||
#
|
||||
#
|
||||
# Please note that sanitizing user-provided text does not guarantee that the
|
||||
# resulting markup is valid (conforming to a document type) or even well-formed.
|
||||
# The output may still contain e.g. unescaped '<', '>', '&' characters and
|
||||
@@ -62,8 +58,8 @@ module ActionView
|
||||
self.class.white_list_sanitizer.sanitize_css(style)
|
||||
end
|
||||
|
||||
# Strips all HTML tags from the +html+, including comments. This uses the
|
||||
# html-scanner tokenizer and so its HTML parsing ability is limited by
|
||||
# Strips all HTML tags from the +html+, including comments. This uses the
|
||||
# html-scanner tokenizer and so its HTML parsing ability is limited by
|
||||
# that of html-scanner.
|
||||
#
|
||||
# ==== Examples
|
||||
@@ -73,10 +69,10 @@ module ActionView
|
||||
#
|
||||
# strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
|
||||
# # => Bold no more! See more here...
|
||||
#
|
||||
#
|
||||
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
|
||||
# # => Welcome to my website!
|
||||
def strip_tags(html)
|
||||
def strip_tags(html)
|
||||
self.class.full_sanitizer.sanitize(html)
|
||||
end
|
||||
|
||||
@@ -96,21 +92,48 @@ module ActionView
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def self.extended(base)
|
||||
class << base
|
||||
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
|
||||
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
|
||||
|
||||
# we want these to be class methods on ActionView::Base, they'll get mattr_readers for these below.
|
||||
helper_def = [:sanitized_protocol_separator, :sanitized_uri_attributes, :sanitized_bad_tags, :sanitized_allowed_tags,
|
||||
:sanitized_allowed_attributes, :sanitized_allowed_css_properties, :sanitized_allowed_css_keywords,
|
||||
:sanitized_shorthand_css_properties, :sanitized_allowed_protocols, :sanitized_protocol_separator=].collect! do |prop|
|
||||
prop = prop.to_s
|
||||
"def #{prop}(#{:value if prop =~ /=$/}) white_list_sanitizer.#{prop.sub /sanitized_/, ''} #{:value if prop =~ /=$/} end"
|
||||
end.join("\n")
|
||||
eval helper_def
|
||||
end
|
||||
def sanitized_protocol_separator
|
||||
white_list_sanitizer.protocol_separator
|
||||
end
|
||||
|
||||
|
||||
def sanitized_uri_attributes
|
||||
white_list_sanitizer.uri_attributes
|
||||
end
|
||||
|
||||
def sanitized_bad_tags
|
||||
white_list_sanitizer.bad_tags
|
||||
end
|
||||
|
||||
def sanitized_allowed_tags
|
||||
white_list_sanitizer.allowed_tags
|
||||
end
|
||||
|
||||
def sanitized_allowed_attributes
|
||||
white_list_sanitizer.allowed_attributes
|
||||
end
|
||||
|
||||
def sanitized_allowed_css_properties
|
||||
white_list_sanitizer.allowed_css_properties
|
||||
end
|
||||
|
||||
def sanitized_allowed_css_keywords
|
||||
white_list_sanitizer.allowed_css_keywords
|
||||
end
|
||||
|
||||
def sanitized_shorthand_css_properties
|
||||
white_list_sanitizer.shorthand_css_properties
|
||||
end
|
||||
|
||||
def sanitized_allowed_protocols
|
||||
white_list_sanitizer.allowed_protocols
|
||||
end
|
||||
|
||||
def sanitized_protocol_separator=(value)
|
||||
white_list_sanitizer.protocol_separator = value
|
||||
end
|
||||
|
||||
# Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
|
||||
# any object that responds to +sanitize+.
|
||||
#
|
||||
@@ -184,7 +207,7 @@ module ActionView
|
||||
HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
|
||||
end
|
||||
|
||||
# Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ heleprs.
|
||||
# Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
|
||||
#
|
||||
# Rails::Initializer.run do |config|
|
||||
# config.action_view.sanitized_allowed_css_properties = 'expression'
|
||||
|
||||
@@ -193,7 +193,7 @@ module ActionView
|
||||
#
|
||||
# * <tt>:onDrop</tt> - Called when a +draggable_element+ is dropped onto
|
||||
# this element. Override this callback with a JavaScript expression to
|
||||
# change the default drop behavour. Example:
|
||||
# change the default drop behaviour. Example:
|
||||
#
|
||||
# :onDrop => "function(draggable_element, droppable_element, event) { alert('I like bananas') }"
|
||||
#
|
||||
|
||||
@@ -64,7 +64,7 @@ module ActionView
|
||||
# <% content_tag :div, :class => "strong" do -%>
|
||||
# Hello world!
|
||||
# <% end -%>
|
||||
# # => <div class="strong"><p>Hello world!</p></div>
|
||||
# # => <div class="strong">Hello world!</div>
|
||||
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
|
||||
if block_given?
|
||||
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
||||
@@ -110,12 +110,18 @@ module ActionView
|
||||
private
|
||||
BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template'
|
||||
|
||||
# Check whether we're called from an erb template.
|
||||
# We'd return a string in any other case, but erb <%= ... %>
|
||||
# can't take an <% end %> later on, so we have to use <% ... %>
|
||||
# and implicitly concat.
|
||||
def block_called_from_erb?(block)
|
||||
block && eval(BLOCK_CALLED_FROM_ERB, block)
|
||||
if RUBY_VERSION < '1.9.0'
|
||||
# Check whether we're called from an erb template.
|
||||
# We'd return a string in any other case, but erb <%= ... %>
|
||||
# can't take an <% end %> later on, so we have to use <% ... %>
|
||||
# and implicitly concat.
|
||||
def block_called_from_erb?(block)
|
||||
block && eval(BLOCK_CALLED_FROM_ERB, block)
|
||||
end
|
||||
else
|
||||
def block_called_from_erb?(block)
|
||||
block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
|
||||
end
|
||||
end
|
||||
|
||||
def content_tag_string(name, content, options, escape = true)
|
||||
|
||||
@@ -34,40 +34,69 @@ module ActionView
|
||||
end
|
||||
|
||||
if RUBY_VERSION < '1.9'
|
||||
# If +text+ is longer than +length+, +text+ will be truncated to the length of
|
||||
# +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+
|
||||
# (defaults to "...").
|
||||
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
|
||||
# (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...").
|
||||
#
|
||||
# ==== Examples
|
||||
# truncate("Once upon a time in a world far far away", 14)
|
||||
# # => Once upon a...
|
||||
#
|
||||
# truncate("Once upon a time in a world far far away")
|
||||
# # => Once upon a time in a world f...
|
||||
#
|
||||
# truncate("And they found that many people were sleeping better.", 25, "(clipped)")
|
||||
# truncate("Once upon a time in a world far far away", :length => 14)
|
||||
# # => Once upon a...
|
||||
#
|
||||
# truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)")
|
||||
# # => And they found that many (clipped)
|
||||
#
|
||||
# truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15)
|
||||
# # => And they found... (continued)
|
||||
#
|
||||
# You can still use <tt>truncate</tt> with the old API that accepts the
|
||||
# +length+ as its optional second and the +ellipsis+ as its
|
||||
# optional third parameter:
|
||||
# truncate("Once upon a time in a world far far away", 14)
|
||||
# # => Once upon a time in a world f...
|
||||
#
|
||||
# truncate("And they found that many people were sleeping better.", 15, "... (continued)")
|
||||
# # => And they found... (continued)
|
||||
def truncate(text, length = 30, truncate_string = "...")
|
||||
def truncate(text, *args)
|
||||
options = args.extract_options!
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' +
|
||||
'length and omission arguments', caller)
|
||||
|
||||
options[:length] = args[0] || 30
|
||||
options[:omission] = args[1] || "..."
|
||||
end
|
||||
options.reverse_merge!(:length => 30, :omission => "...")
|
||||
|
||||
if text
|
||||
l = length - truncate_string.chars.length
|
||||
l = options[:length] - options[:omission].chars.length
|
||||
chars = text.chars
|
||||
(chars.length > length ? chars[0...l] + truncate_string : text).to_s
|
||||
(chars.length > options[:length] ? chars[0...l] + options[:omission] : text).to_s
|
||||
end
|
||||
end
|
||||
else
|
||||
def truncate(text, length = 30, truncate_string = "...") #:nodoc:
|
||||
def truncate(text, *args) #:nodoc:
|
||||
options = args.extract_options!
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' +
|
||||
'length and omission arguments', caller)
|
||||
|
||||
options[:length] = args[0] || 30
|
||||
options[:omission] = args[1] || "..."
|
||||
end
|
||||
options.reverse_merge!(:length => 30, :omission => "...")
|
||||
|
||||
if text
|
||||
l = length - truncate_string.length
|
||||
(text.length > length ? text[0...l] + truncate_string : text).to_s
|
||||
l = options[:length].to_i - options[:omission].length
|
||||
(text.length > options[:length].to_i ? text[0...l] + options[:omission] : text).to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
|
||||
# a +highlighter+ string. The highlighter can be specialized by passing +highlighter+
|
||||
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
|
||||
# as a single-quoted string with \1 where the phrase is to be inserted (defaults to
|
||||
# '<strong class="highlight">\1</strong>')
|
||||
#
|
||||
@@ -78,52 +107,75 @@ module ActionView
|
||||
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
|
||||
# # => You searched for: ruby, rails, dhh
|
||||
#
|
||||
# highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>')
|
||||
# highlight('You searched for: rails', ['for', 'rails'], :highlighter => '<em>\1</em>')
|
||||
# # => You searched <em>for</em>: <em>rails</em>
|
||||
#
|
||||
# highlight('You searched for: rails', 'rails', "<a href='search?q=\1'>\1</a>")
|
||||
# # => You searched for: <a href='search?q=rails>rails</a>
|
||||
def highlight(text, phrases, highlighter = '<strong class="highlight">\1</strong>')
|
||||
# highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>')
|
||||
# # => You searched for: <a href="search?q=rails">rails</a>
|
||||
#
|
||||
# You can still use <tt>highlight</tt> with the old API that accepts the
|
||||
# +highlighter+ as its optional third parameter:
|
||||
# highlight('You searched for: rails', 'rails', '<a href="search?q=\1">\1</a>') # => You searched for: <a href="search?q=rails">rails</a>
|
||||
def highlight(text, phrases, *args)
|
||||
options = args.extract_options!
|
||||
unless args.empty?
|
||||
options[:highlighter] = args[0] || '<strong class="highlight">\1</strong>'
|
||||
end
|
||||
options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
|
||||
|
||||
if text.blank? || phrases.blank?
|
||||
text
|
||||
else
|
||||
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
|
||||
text.gsub(/(#{match})/i, highlighter)
|
||||
text.gsub(/(#{match})/i, options[:highlighter])
|
||||
end
|
||||
end
|
||||
|
||||
if RUBY_VERSION < '1.9'
|
||||
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
|
||||
# The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
|
||||
# defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
|
||||
# then the +excerpt_string+ will be prepended/appended accordingly. The resulting string will be stripped in any case.
|
||||
# If the +phrase+ isn't found, nil is returned.
|
||||
# The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
|
||||
# defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
|
||||
# then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The resulting string
|
||||
# will be stripped in any case. If the +phrase+ isn't found, nil is returned.
|
||||
#
|
||||
# ==== Examples
|
||||
# excerpt('This is an example', 'an', 5)
|
||||
# # => "...s is an exam..."
|
||||
# excerpt('This is an example', 'an', :radius => 5)
|
||||
# # => ...s is an exam...
|
||||
#
|
||||
# excerpt('This is an example', 'is', 5)
|
||||
# # => "This is a..."
|
||||
# excerpt('This is an example', 'is', :radius => 5)
|
||||
# # => This is a...
|
||||
#
|
||||
# excerpt('This is an example', 'is')
|
||||
# # => "This is an example"
|
||||
# # => This is an example
|
||||
#
|
||||
# excerpt('This next thing is an example', 'ex', 2)
|
||||
# # => "...next..."
|
||||
# excerpt('This next thing is an example', 'ex', :radius => 2)
|
||||
# # => ...next...
|
||||
#
|
||||
# excerpt('This is also an example', 'an', 8, '<chop> ')
|
||||
# # => "<chop> is also an example"
|
||||
def excerpt(text, phrase, radius = 100, excerpt_string = "...")
|
||||
# excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ')
|
||||
# # => <chop> is also an example
|
||||
#
|
||||
# You can still use <tt>excerpt</tt> with the old API that accepts the
|
||||
# +radius+ as its optional third and the +ellipsis+ as its
|
||||
# optional forth parameter:
|
||||
# excerpt('This is an example', 'an', 5) # => ...s is an exam...
|
||||
# excerpt('This is also an example', 'an', 8, '<chop> ') # => <chop> is also an example
|
||||
def excerpt(text, phrase, *args)
|
||||
options = args.extract_options!
|
||||
unless args.empty?
|
||||
options[:radius] = args[0] || 100
|
||||
options[:omission] = args[1] || "..."
|
||||
end
|
||||
options.reverse_merge!(:radius => 100, :omission => "...")
|
||||
|
||||
if text && phrase
|
||||
phrase = Regexp.escape(phrase)
|
||||
|
||||
if found_pos = text.chars =~ /(#{phrase})/i
|
||||
start_pos = [ found_pos - radius, 0 ].max
|
||||
end_pos = [ [ found_pos + phrase.chars.length + radius - 1, 0].max, text.chars.length ].min
|
||||
start_pos = [ found_pos - options[:radius], 0 ].max
|
||||
end_pos = [ [ found_pos + phrase.chars.length + options[:radius] - 1, 0].max, text.chars.length ].min
|
||||
|
||||
prefix = start_pos > 0 ? excerpt_string : ""
|
||||
postfix = end_pos < text.chars.length - 1 ? excerpt_string : ""
|
||||
prefix = start_pos > 0 ? options[:omission] : ""
|
||||
postfix = end_pos < text.chars.length - 1 ? options[:omission] : ""
|
||||
|
||||
prefix + text.chars[start_pos..end_pos].strip + postfix
|
||||
else
|
||||
@@ -132,16 +184,23 @@ module ActionView
|
||||
end
|
||||
end
|
||||
else
|
||||
def excerpt(text, phrase, radius = 100, excerpt_string = "...") #:nodoc:
|
||||
def excerpt(text, phrase, *args) #:nodoc:
|
||||
options = args.extract_options!
|
||||
unless args.empty?
|
||||
options[:radius] = args[0] || 100
|
||||
options[:omission] = args[1] || "..."
|
||||
end
|
||||
options.reverse_merge!(:radius => 100, :omission => "...")
|
||||
|
||||
if text && phrase
|
||||
phrase = Regexp.escape(phrase)
|
||||
|
||||
if found_pos = text =~ /(#{phrase})/i
|
||||
start_pos = [ found_pos - radius, 0 ].max
|
||||
end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min
|
||||
start_pos = [ found_pos - options[:radius], 0 ].max
|
||||
end_pos = [ [ found_pos + phrase.length + options[:radius] - 1, 0].max, text.length ].min
|
||||
|
||||
prefix = start_pos > 0 ? excerpt_string : ""
|
||||
postfix = end_pos < text.length - 1 ? excerpt_string : ""
|
||||
prefix = start_pos > 0 ? options[:omission] : ""
|
||||
postfix = end_pos < text.length - 1 ? options[:omission] : ""
|
||||
|
||||
prefix + text[start_pos..end_pos].strip + postfix
|
||||
else
|
||||
@@ -176,20 +235,31 @@ module ActionView
|
||||
# (which is 80 by default).
|
||||
#
|
||||
# ==== Examples
|
||||
# word_wrap('Once upon a time', 4)
|
||||
# # => Once\nupon\na\ntime
|
||||
#
|
||||
# word_wrap('Once upon a time', 8)
|
||||
# # => Once upon\na time
|
||||
#
|
||||
# word_wrap('Once upon a time')
|
||||
# # => Once upon a time
|
||||
#
|
||||
# word_wrap('Once upon a time', 1)
|
||||
# word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
|
||||
# # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\n a successor to the throne turned out to be more trouble than anyone could have\n imagined...
|
||||
#
|
||||
# word_wrap('Once upon a time', :line_width => 8)
|
||||
# # => Once upon\na time
|
||||
#
|
||||
# word_wrap('Once upon a time', :line_width => 1)
|
||||
# # => Once\nupon\na\ntime
|
||||
def word_wrap(text, line_width = 80)
|
||||
#
|
||||
# You can still use <tt>word_wrap</tt> with the old API that accepts the
|
||||
# +line_width+ as its optional second parameter:
|
||||
# word_wrap('Once upon a time', 8) # => Once upon\na time
|
||||
def word_wrap(text, *args)
|
||||
options = args.extract_options!
|
||||
unless args.blank?
|
||||
options[:line_width] = args[0] || 80
|
||||
end
|
||||
options.reverse_merge!(:line_width => 80)
|
||||
|
||||
text.split("\n").collect do |line|
|
||||
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
|
||||
line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line
|
||||
end * "\n"
|
||||
end
|
||||
|
||||
@@ -336,12 +406,32 @@ module ActionView
|
||||
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>.
|
||||
# Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
|
||||
#
|
||||
def auto_link(text, link = :all, href_options = {}, &block)
|
||||
#
|
||||
# You can still use <tt>auto_link</tt> with the old API that accepts the
|
||||
# +link+ as its optional second parameter and the +html_options+ hash
|
||||
# as its optional third parameter:
|
||||
# post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com."
|
||||
# auto_link(post_body, :urls) # => Once upon\na time
|
||||
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\">http://www.myblog.com</a>.
|
||||
# Please e-mail me at me@email.com."
|
||||
#
|
||||
# auto_link(post_body, :all, :target => "_blank") # => Once upon\na time
|
||||
# # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>.
|
||||
# Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>."
|
||||
def auto_link(text, *args, &block)#link = :all, href_options = {}, &block)
|
||||
return '' if text.blank?
|
||||
case link
|
||||
when :all then auto_link_email_addresses(auto_link_urls(text, href_options, &block), &block)
|
||||
when :email_addresses then auto_link_email_addresses(text, &block)
|
||||
when :urls then auto_link_urls(text, href_options, &block)
|
||||
|
||||
options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter
|
||||
unless args.empty?
|
||||
options[:link] = args[0] || :all
|
||||
options[:html] = args[1] || {}
|
||||
end
|
||||
options.reverse_merge!(:link => :all, :html => {})
|
||||
|
||||
case options[:link].to_sym
|
||||
when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), &block)
|
||||
when :email_addresses then auto_link_email_addresses(text, &block)
|
||||
when :urls then auto_link_urls(text, options[:html], &block)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -468,7 +558,7 @@ module ActionView
|
||||
[-\w]+ # subdomain or domain
|
||||
(?:\.[-\w]+)* # remaining subdomains or domain
|
||||
(?::\d+)? # port
|
||||
(?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))+)?)* # path
|
||||
(?:/(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))*)* # path
|
||||
(?:\?[\w\+@%&=.;-]+)? # query string
|
||||
(?:\#[\w\-]*)? # trailing anchor
|
||||
)
|
||||
@@ -477,8 +567,8 @@ module ActionView
|
||||
|
||||
# Turns all urls into clickable links. If a block is given, each url
|
||||
# is yielded and the result is used as the link text.
|
||||
def auto_link_urls(text, href_options = {})
|
||||
extra_options = tag_options(href_options.stringify_keys) || ""
|
||||
def auto_link_urls(text, html_options = {})
|
||||
extra_options = tag_options(html_options.stringify_keys) || ""
|
||||
text.gsub(AUTO_LINK_RE) do
|
||||
all, a, b, c, d = $&, $1, $2, $3, $4
|
||||
if a =~ /<a\s/i # don't replace URL's that are already linked
|
||||
@@ -508,4 +598,4 @@ module ActionView
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
actionpack/lib/action_view/helpers/translation_helper.rb
Normal file
22
actionpack/lib/action_view/helpers/translation_helper.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
require 'action_view/helpers/tag_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
module TranslationHelper
|
||||
def translate(*args)
|
||||
args << args.extract_options!.merge(:raise => true)
|
||||
I18n.translate *args
|
||||
|
||||
rescue I18n::MissingTranslationData => e
|
||||
keys = I18n.send :normalize_translation_keys, e.locale, e.key, e.options[:scope]
|
||||
content_tag('span', keys.join(', '), :class => 'translation_missing')
|
||||
end
|
||||
alias :t :translate
|
||||
|
||||
def localize(*args)
|
||||
I18n.localize *args
|
||||
end
|
||||
alias :l :localize
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,8 +3,8 @@ require 'action_view/helpers/javascript_helper'
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
# Provides a set of methods for making links and getting URLs that
|
||||
# depend on the routing subsystem (see ActionController::Routing).
|
||||
# This allows you to use the same format for links in views
|
||||
# depend on the routing subsystem (see ActionController::Routing).
|
||||
# This allows you to use the same format for links in views
|
||||
# and controllers.
|
||||
module UrlHelper
|
||||
include JavaScriptHelper
|
||||
@@ -33,8 +33,8 @@ module ActionView
|
||||
#
|
||||
# If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter,
|
||||
# you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing
|
||||
# a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as
|
||||
# admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route).
|
||||
# a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as
|
||||
# admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route).
|
||||
#
|
||||
# ==== Examples
|
||||
# <%= url_for(:action => 'index') %>
|
||||
@@ -62,19 +62,33 @@ module ActionView
|
||||
# <%= url_for(@workshop) %>
|
||||
# # calls @workshop.to_s
|
||||
# # => /workshops/5
|
||||
#
|
||||
# <%= url_for("http://www.example.com") %>
|
||||
# # => http://www.example.com
|
||||
#
|
||||
# <%= url_for(:back) %>
|
||||
# # if request.env["HTTP_REFERER"] is set to "http://www.example.com"
|
||||
# # => http://www.example.com
|
||||
#
|
||||
# <%= url_for(:back) %>
|
||||
# # if request.env["HTTP_REFERER"] is not set or is blank
|
||||
# # => javascript:history.back()
|
||||
def url_for(options = {})
|
||||
options ||= {}
|
||||
case options
|
||||
url = case options
|
||||
when String
|
||||
escape = true
|
||||
options
|
||||
when Hash
|
||||
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
|
||||
escape = options.key?(:escape) ? options.delete(:escape) : true
|
||||
url = @controller.send(:url_for, options)
|
||||
when String
|
||||
escape = true
|
||||
url = options
|
||||
@controller.send(:url_for, options)
|
||||
when :back
|
||||
escape = false
|
||||
@controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
|
||||
else
|
||||
escape = false
|
||||
url = polymorphic_path(options)
|
||||
polymorphic_path(options)
|
||||
end
|
||||
|
||||
escape ? escape_once(url) : url
|
||||
@@ -116,8 +130,8 @@ module ActionView
|
||||
#
|
||||
# Note that if the user has JavaScript disabled, the request will fall back
|
||||
# to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript disabled
|
||||
# clicking the link will have no effect. If you are relying on the POST
|
||||
# behavior, your should check for it in your controller's action by using the
|
||||
# clicking the link will have no effect. If you are relying on the POST
|
||||
# behavior, your should check for it in your controller's action by using the
|
||||
# request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
|
||||
#
|
||||
# You can mix and match the +html_options+ with the exception of
|
||||
@@ -141,8 +155,8 @@ module ActionView
|
||||
#
|
||||
# link_to "Profile", :controller => "profiles", :action => "show", :id => @profile
|
||||
# # => <a href="/profiles/show/1">Profile</a>
|
||||
#
|
||||
# Similarly,
|
||||
#
|
||||
# Similarly,
|
||||
#
|
||||
# link_to "Profiles", profiles_path
|
||||
# # => <a href="/profiles">Profiles</a>
|
||||
@@ -185,7 +199,7 @@ module ActionView
|
||||
# link_to "Nonsense search", searches_path(:foo => "bar", :baz => "quux")
|
||||
# # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>
|
||||
#
|
||||
# The three options specfic to +link_to+ (<tt>:confirm</tt>, <tt>:popup</tt>, and <tt>:method</tt>) are used as follows:
|
||||
# The three options specific to +link_to+ (<tt>:confirm</tt>, <tt>:popup</tt>, and <tt>:method</tt>) are used as follows:
|
||||
#
|
||||
# link_to "Visit Other Site", "http://www.rubyonrails.org/", :confirm => "Are you sure?"
|
||||
# # => <a href="http://www.rubyonrails.org/" onclick="return confirm('Are you sure?');">Visit Other Site</a>
|
||||
@@ -197,9 +211,9 @@ module ActionView
|
||||
# # => <a href="/images/9" onclick="window.open(this.href,'new_window_name','height=300,width=600');return false;">View Image</a>
|
||||
#
|
||||
# link_to "Delete Image", @image, :confirm => "Are you sure?", :method => :delete
|
||||
# # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form');
|
||||
# # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form');
|
||||
# f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;
|
||||
# var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method');
|
||||
# var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method');
|
||||
# m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete Image</a>
|
||||
def link_to(*args, &block)
|
||||
if block_given?
|
||||
@@ -211,14 +225,7 @@ module ActionView
|
||||
options = args.second || {}
|
||||
html_options = args.third
|
||||
|
||||
url = case options
|
||||
when String
|
||||
options
|
||||
when :back
|
||||
@controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
|
||||
else
|
||||
self.url_for(options)
|
||||
end
|
||||
url = url_for(options)
|
||||
|
||||
if html_options
|
||||
html_options = html_options.stringify_keys
|
||||
@@ -228,7 +235,7 @@ module ActionView
|
||||
else
|
||||
tag_options = nil
|
||||
end
|
||||
|
||||
|
||||
href_attr = "href=\"#{url}\"" unless href
|
||||
"<a #{href_attr}#{tag_options}>#{name || url}</a>"
|
||||
end
|
||||
@@ -260,7 +267,7 @@ module ActionView
|
||||
# * <tt>:confirm</tt> - This will add a JavaScript confirm
|
||||
# prompt with the question specified. If the user accepts, the link is
|
||||
# processed normally, otherwise no action is taken.
|
||||
#
|
||||
#
|
||||
# ==== Examples
|
||||
# <%= button_to "New", :action => "new" %>
|
||||
# # => "<form method="post" action="/controller/new" class="button-to">
|
||||
@@ -286,12 +293,12 @@ module ActionView
|
||||
end
|
||||
|
||||
form_method = method.to_s == 'get' ? 'get' : 'post'
|
||||
|
||||
|
||||
request_token_tag = ''
|
||||
if form_method == 'post' && protect_against_forgery?
|
||||
request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
|
||||
end
|
||||
|
||||
|
||||
if confirm = html_options.delete("confirm")
|
||||
html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
|
||||
end
|
||||
@@ -309,7 +316,7 @@ module ActionView
|
||||
# Creates a link tag of the given +name+ using a URL created by the set of
|
||||
# +options+ unless the current request URI is the same as the links, in
|
||||
# which case only the name is returned (or the given block is yielded, if
|
||||
# one exists). You can give link_to_unless_current a block which will
|
||||
# one exists). You can give link_to_unless_current a block which will
|
||||
# specialize the default behavior (e.g., show a "Start Here" link rather
|
||||
# than the link's text).
|
||||
#
|
||||
@@ -336,13 +343,13 @@ module ActionView
|
||||
# </ul>
|
||||
#
|
||||
# The implicit block given to link_to_unless_current is evaluated if the current
|
||||
# action is the action given. So, if we had a comments page and wanted to render a
|
||||
# action is the action given. So, if we had a comments page and wanted to render a
|
||||
# "Go Back" link instead of a link to the comments page, we could do something like this...
|
||||
#
|
||||
# <%=
|
||||
#
|
||||
# <%=
|
||||
# link_to_unless_current("Comment", { :controller => 'comments', :action => 'new}) do
|
||||
# link_to("Go back", { :controller => 'posts', :action => 'index' })
|
||||
# end
|
||||
# link_to("Go back", { :controller => 'posts', :action => 'index' })
|
||||
# end
|
||||
# %>
|
||||
def link_to_unless_current(name, options = {}, html_options = {}, &block)
|
||||
link_to_unless current_page?(options), name, options, html_options, &block
|
||||
@@ -359,10 +366,10 @@ module ActionView
|
||||
# # If the user is logged in...
|
||||
# # => <a href="/controller/reply/">Reply</a>
|
||||
#
|
||||
# <%=
|
||||
# <%=
|
||||
# link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) do |name|
|
||||
# link_to(name, { :controller => "accounts", :action => "signup" })
|
||||
# end
|
||||
# end
|
||||
# %>
|
||||
# # If the user is logged in...
|
||||
# # => <a href="/controller/reply/">Reply</a>
|
||||
@@ -391,10 +398,10 @@ module ActionView
|
||||
# # If the user isn't logged in...
|
||||
# # => <a href="/sessions/new/">Login</a>
|
||||
#
|
||||
# <%=
|
||||
# <%=
|
||||
# link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) do
|
||||
# link_to(@current_user.login, { :controller => "accounts", :action => "show", :id => @current_user })
|
||||
# end
|
||||
# end
|
||||
# %>
|
||||
# # If the user isn't logged in...
|
||||
# # => <a href="/sessions/new/">Login</a>
|
||||
@@ -431,20 +438,20 @@ module ActionView
|
||||
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
|
||||
#
|
||||
# ==== Examples
|
||||
# mail_to "me@domain.com"
|
||||
# mail_to "me@domain.com"
|
||||
# # => <a href="mailto:me@domain.com">me@domain.com</a>
|
||||
#
|
||||
# mail_to "me@domain.com", "My email", :encode => "javascript"
|
||||
# # => <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script>
|
||||
# mail_to "me@domain.com", "My email", :encode => "javascript"
|
||||
# # => <script type="text/javascript">eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
|
||||
#
|
||||
# mail_to "me@domain.com", "My email", :encode => "hex"
|
||||
# mail_to "me@domain.com", "My email", :encode => "hex"
|
||||
# # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
|
||||
#
|
||||
# mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email"
|
||||
# mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email"
|
||||
# # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a>
|
||||
#
|
||||
# mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com",
|
||||
# :subject => "This is an example email"
|
||||
# :subject => "This is an example email"
|
||||
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
|
||||
def mail_to(email_address, name = nil, html_options = {})
|
||||
html_options = html_options.stringify_keys
|
||||
@@ -469,7 +476,7 @@ module ActionView
|
||||
"document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
|
||||
string << sprintf("%%%x", c)
|
||||
end
|
||||
"<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"
|
||||
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>"
|
||||
elsif encode == "hex"
|
||||
email_address_encoded = ''
|
||||
email_address_obfuscated.each_byte do |c|
|
||||
|
||||
91
actionpack/lib/action_view/locale/en-US.yml
Normal file
91
actionpack/lib/action_view/locale/en-US.yml
Normal file
@@ -0,0 +1,91 @@
|
||||
"en-US":
|
||||
number:
|
||||
# Used in number_with_delimiter()
|
||||
# These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
|
||||
format:
|
||||
# Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
|
||||
separator: "."
|
||||
# Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three)
|
||||
delimiter: ","
|
||||
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
|
||||
precision: 3
|
||||
|
||||
# Used in number_to_currency()
|
||||
currency:
|
||||
format:
|
||||
# Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
|
||||
format: "%u%n"
|
||||
unit: "$"
|
||||
# These three are to override number.format and are optional
|
||||
separator: "."
|
||||
delimiter: ","
|
||||
precision: 2
|
||||
|
||||
# Used in number_to_percentage()
|
||||
percentage:
|
||||
format:
|
||||
# These three are to override number.format and are optional
|
||||
# separator:
|
||||
delimiter: ""
|
||||
# precision:
|
||||
|
||||
# Used in number_to_precision()
|
||||
precision:
|
||||
format:
|
||||
# These three are to override number.format and are optional
|
||||
# separator:
|
||||
delimiter: ""
|
||||
# precision:
|
||||
|
||||
# Used in number_to_human_size()
|
||||
human:
|
||||
format:
|
||||
# These three are to override number.format and are optional
|
||||
# separator:
|
||||
delimiter: ""
|
||||
precision: 1
|
||||
|
||||
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
|
||||
datetime:
|
||||
distance_in_words:
|
||||
half_a_minute: "half a minute"
|
||||
less_than_x_seconds:
|
||||
one: "less than 1 second"
|
||||
other: "less than {{count}} seconds"
|
||||
x_seconds:
|
||||
one: "1 second"
|
||||
other: "{{count}} seconds"
|
||||
less_than_x_minutes:
|
||||
one: "less than a minute"
|
||||
other: "less than {{count}} minutes"
|
||||
x_minutes:
|
||||
one: "1 minute"
|
||||
other: "{{count}} minutes"
|
||||
about_x_hours:
|
||||
one: "about 1 hour"
|
||||
other: "about {{count}} hours"
|
||||
x_days:
|
||||
one: "1 day"
|
||||
other: "{{count}} days"
|
||||
about_x_months:
|
||||
one: "about 1 month"
|
||||
other: "about {{count}} months"
|
||||
x_months:
|
||||
one: "1 month"
|
||||
other: "{{count}} months"
|
||||
about_x_years:
|
||||
one: "about 1 year"
|
||||
other: "about {{count}} years"
|
||||
over_x_years:
|
||||
one: "over 1 year"
|
||||
other: "over {{count}} years"
|
||||
|
||||
activerecord:
|
||||
errors:
|
||||
template:
|
||||
header:
|
||||
one: "1 error prohibited this {{model}} from being saved"
|
||||
other: "{{count}} errors prohibited this {{model}} from being saved"
|
||||
# The variable :count is also available
|
||||
body: "There were problems with the following fields:"
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
module ActionView
|
||||
# There's also a convenience method for rendering sub templates within the current controller that depends on a single object
|
||||
# (we call this kind of sub templates for partials). It relies on the fact that partials should follow the naming convention of being
|
||||
# prefixed with an underscore -- as to separate them from regular templates that could be rendered on their own.
|
||||
# There's also a convenience method for rendering sub templates within the current controller that depends on a
|
||||
# single object (we call this kind of sub templates for partials). It relies on the fact that partials should
|
||||
# follow the naming convention of being prefixed with an underscore -- as to separate them from regular
|
||||
# templates that could be rendered on their own.
|
||||
#
|
||||
# In a template for Advertiser#account:
|
||||
#
|
||||
# <%= render :partial => "account" %>
|
||||
#
|
||||
# This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable +account+ to
|
||||
# the template for display.
|
||||
# This would render "advertiser/_account.erb" and pass the instance variable @account in as a local variable
|
||||
# +account+ to the template for display.
|
||||
#
|
||||
# In another template for Advertiser#buy, we could have:
|
||||
#
|
||||
@@ -18,24 +19,24 @@ module ActionView
|
||||
# <%= render :partial => "ad", :locals => { :ad => ad } %>
|
||||
# <% end %>
|
||||
#
|
||||
# This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then render
|
||||
# "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display.
|
||||
# This would first render "advertiser/_account.erb" with @buyer passed in as the local variable +account+, then
|
||||
# render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display.
|
||||
#
|
||||
# == Rendering a collection of partials
|
||||
#
|
||||
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub
|
||||
# template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders
|
||||
# a partial by the same name as the elements contained within. So the three-lined example in "Using partials" can be rewritten
|
||||
# with a single line:
|
||||
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and
|
||||
# render a sub template for each of the elements. This pattern has been implemented as a single method that
|
||||
# accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
|
||||
# example in "Using partials" can be rewritten with a single line:
|
||||
#
|
||||
# <%= render :partial => "ad", :collection => @advertisements %>
|
||||
#
|
||||
# This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An iteration counter
|
||||
# will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the
|
||||
# example above, the template would be fed +ad_counter+.
|
||||
# This will render "advertiser/_ad.erb" and pass the local variable +ad+ to the template for display. An
|
||||
# iteration counter will automatically be made available to the template with a name of the form
|
||||
# +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
|
||||
#
|
||||
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also just keep domain objects,
|
||||
# like Active Records, in there.
|
||||
# NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also
|
||||
# just keep domain objects, like Active Records, in there.
|
||||
#
|
||||
# == Rendering shared partials
|
||||
#
|
||||
@@ -47,8 +48,9 @@ module ActionView
|
||||
#
|
||||
# == Rendering partials with layouts
|
||||
#
|
||||
# Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally
|
||||
# for the entire action, but they work in a similar fashion. Imagine a list with two types of users:
|
||||
# Partials can have their own layouts applied to them. These layouts are different than the ones that are
|
||||
# specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
|
||||
# of users:
|
||||
#
|
||||
# <%# app/views/users/index.html.erb &>
|
||||
# Here's the administrator:
|
||||
@@ -68,7 +70,7 @@ module ActionView
|
||||
#
|
||||
# <%# app/views/users/_editor.html.erb &>
|
||||
# <div id="editor">
|
||||
# Deadline: $<%= user.deadline %>
|
||||
# Deadline: <%= user.deadline %>
|
||||
# <%= yield %>
|
||||
# </div>
|
||||
#
|
||||
@@ -82,7 +84,7 @@ module ActionView
|
||||
#
|
||||
# Here's the editor:
|
||||
# <div id="editor">
|
||||
# Deadline: $<%= user.deadline %>
|
||||
# Deadline: <%= user.deadline %>
|
||||
# Name: <%= user.name %>
|
||||
# </div>
|
||||
#
|
||||
@@ -101,42 +103,83 @@ module ActionView
|
||||
# </div>
|
||||
#
|
||||
# As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
|
||||
#
|
||||
# If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
|
||||
# an array to layout and treat it as an enumerable.
|
||||
#
|
||||
# <%# app/views/users/_user.html.erb &>
|
||||
# <div class="user">
|
||||
# Budget: $<%= user.budget %>
|
||||
# <%= yield user %>
|
||||
# </div>
|
||||
#
|
||||
# <%# app/views/users/index.html.erb &>
|
||||
# <% render :layout => @users do |user| %>
|
||||
# Title: <%= user.title %>
|
||||
# <% end %>
|
||||
#
|
||||
# This will render the layout for each user and yield to the block, passing the user, each time.
|
||||
#
|
||||
# You can also yield multiple times in one layout and use block arguments to differentiate the sections.
|
||||
#
|
||||
# <%# app/views/users/_user.html.erb &>
|
||||
# <div class="user">
|
||||
# <%= yield user, :header %>
|
||||
# Budget: $<%= user.budget %>
|
||||
# <%= yield user, :footer %>
|
||||
# </div>
|
||||
#
|
||||
# <%# app/views/users/index.html.erb &>
|
||||
# <% render :layout => @users do |user, section| %>
|
||||
# <%- case section when :header -%>
|
||||
# Title: <%= user.title %>
|
||||
# <%- when :footer -%>
|
||||
# Deadline: <%= user.deadline %>
|
||||
# <%- end -%>
|
||||
# <% end %>
|
||||
module Partials
|
||||
private
|
||||
def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc:
|
||||
local_assigns ||= {}
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
case partial_path
|
||||
private
|
||||
def render_partial(options = {}) #:nodoc:
|
||||
local_assigns = options[:locals] || {}
|
||||
|
||||
case partial_path = options[:partial]
|
||||
when String, Symbol, NilClass
|
||||
pick_template(find_partial_path(partial_path)).render_partial(self, object_assigns, local_assigns)
|
||||
if options.has_key?(:collection)
|
||||
render_partial_collection(options)
|
||||
else
|
||||
_pick_partial_template(partial_path).render_partial(self, options[:object], local_assigns)
|
||||
end
|
||||
when ActionView::Helpers::FormBuilder
|
||||
builder_partial_path = partial_path.class.to_s.demodulize.underscore.sub(/_builder$/, '')
|
||||
render_partial(builder_partial_path, object_assigns, (local_assigns || {}).merge(builder_partial_path.to_sym => partial_path))
|
||||
local_assigns.merge!(builder_partial_path.to_sym => partial_path)
|
||||
render_partial(:partial => builder_partial_path, :object => options[:object], :locals => local_assigns)
|
||||
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope
|
||||
if partial_path.any?
|
||||
collection = partial_path
|
||||
render_partial_collection(nil, collection, nil, local_assigns)
|
||||
else
|
||||
""
|
||||
end
|
||||
render_partial_collection(options.except(:partial).merge(:collection => partial_path))
|
||||
else
|
||||
render_partial(ActionController::RecordIdentifier.partial_path(partial_path, controller.class.controller_path), partial_path, local_assigns)
|
||||
object = partial_path
|
||||
render_partial(
|
||||
:partial => ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path),
|
||||
:object => object,
|
||||
:locals => local_assigns
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def render_partial_collection(partial_path, collection, partial_spacer_template = nil, local_assigns = {}, as = nil) #:nodoc:
|
||||
return " " if collection.empty?
|
||||
def render_partial_collection(options = {}) #:nodoc:
|
||||
return nil if options[:collection].blank?
|
||||
|
||||
local_assigns = local_assigns ? local_assigns.clone : {}
|
||||
spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : ''
|
||||
_paths = {}
|
||||
_templates = {}
|
||||
partial = options[:partial]
|
||||
spacer = options[:spacer_template] ? render(:partial => options[:spacer_template]) : ''
|
||||
local_assigns = options[:locals] ? options[:locals].clone : {}
|
||||
as = options[:as]
|
||||
|
||||
index = 0
|
||||
collection.map do |object|
|
||||
_partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
|
||||
path = _paths[_partial_path] ||= find_partial_path(_partial_path)
|
||||
template = _templates[path] ||= pick_template(path)
|
||||
options[:collection].map do |object|
|
||||
_partial_path ||= partial ||
|
||||
ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
|
||||
template = _pick_partial_template(_partial_path)
|
||||
local_assigns[template.counter_name] = index
|
||||
result = template.render_partial(self, object, local_assigns, as)
|
||||
index += 1
|
||||
@@ -144,14 +187,17 @@ module ActionView
|
||||
end.join(spacer)
|
||||
end
|
||||
|
||||
def find_partial_path(partial_path)
|
||||
def _pick_partial_template(partial_path) #:nodoc:
|
||||
if partial_path.include?('/')
|
||||
"#{File.dirname(partial_path)}/_#{File.basename(partial_path)}"
|
||||
elsif respond_to?(:controller)
|
||||
"#{controller.class.controller_path}/_#{partial_path}"
|
||||
path = File.join(File.dirname(partial_path), "_#{File.basename(partial_path)}")
|
||||
elsif controller
|
||||
path = "#{controller.class.controller_path}/_#{partial_path}"
|
||||
else
|
||||
"_#{partial_path}"
|
||||
path = "_#{partial_path}"
|
||||
end
|
||||
|
||||
pick_template(path)
|
||||
end
|
||||
memoize :_pick_partial_template
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ module ActionView #:nodoc:
|
||||
def self.type_cast(obj)
|
||||
if obj.is_a?(String)
|
||||
if Base.warn_cache_misses && defined?(Rails) && Rails.initialized?
|
||||
Rails.logger.debug "[PERFORMANCE] Processing view path during a " +
|
||||
Base.logger.debug "[PERFORMANCE] Processing view path during a " +
|
||||
"request. This an expense disk operation that should be done at " +
|
||||
"boot. You can manually process this view path with " +
|
||||
"ActionView::Base.process_view_paths(#{obj.inspect}) and set it " +
|
||||
@@ -15,70 +15,104 @@ module ActionView #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
class Path #:nodoc:
|
||||
attr_reader :path, :paths
|
||||
delegate :to_s, :to_str, :inspect, :to => :path
|
||||
def initialize(*args)
|
||||
super(*args).map! { |obj| self.class.type_cast(obj) }
|
||||
end
|
||||
|
||||
def initialize(path)
|
||||
def <<(obj)
|
||||
super(self.class.type_cast(obj))
|
||||
end
|
||||
|
||||
def concat(array)
|
||||
super(array.map! { |obj| self.class.type_cast(obj) })
|
||||
end
|
||||
|
||||
def insert(index, obj)
|
||||
super(index, self.class.type_cast(obj))
|
||||
end
|
||||
|
||||
def push(*objs)
|
||||
super(*objs.map { |obj| self.class.type_cast(obj) })
|
||||
end
|
||||
|
||||
def unshift(*objs)
|
||||
super(*objs.map { |obj| self.class.type_cast(obj) })
|
||||
end
|
||||
|
||||
class Path #:nodoc:
|
||||
def self.eager_load_templates!
|
||||
@eager_load_templates = true
|
||||
end
|
||||
|
||||
def self.eager_load_templates?
|
||||
@eager_load_templates || false
|
||||
end
|
||||
|
||||
attr_reader :path, :paths
|
||||
delegate :to_s, :to_str, :hash, :inspect, :to => :path
|
||||
|
||||
def initialize(path, load = true)
|
||||
raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
|
||||
@path = path.freeze
|
||||
reload!
|
||||
reload! if load
|
||||
end
|
||||
|
||||
def ==(path)
|
||||
to_str == path.to_str
|
||||
end
|
||||
|
||||
def eql?(path)
|
||||
to_str == path.to_str
|
||||
end
|
||||
|
||||
def [](path)
|
||||
raise "Unloaded view path! #{@path}" unless @loaded
|
||||
@paths[path]
|
||||
end
|
||||
|
||||
def loaded?
|
||||
@loaded ? true : false
|
||||
end
|
||||
|
||||
def load
|
||||
reload! unless loaded?
|
||||
self
|
||||
end
|
||||
|
||||
# Rebuild load path directory cache
|
||||
def reload!
|
||||
@paths = {}
|
||||
|
||||
templates_in_path do |template|
|
||||
# Eager load memoized methods and freeze cached template
|
||||
template.freeze if self.class.eager_load_templates?
|
||||
|
||||
@paths[template.path] = template
|
||||
@paths[template.path_without_extension] ||= template
|
||||
end
|
||||
|
||||
@paths.freeze
|
||||
@loaded = true
|
||||
end
|
||||
|
||||
private
|
||||
def templates_in_path
|
||||
(Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
|
||||
unless File.directory?(file)
|
||||
template = Template.new(file.split("#{self}/").last, self)
|
||||
# Eager load memoized methods and freeze cached template
|
||||
template.freeze if Base.cache_template_loading
|
||||
yield template
|
||||
yield Template.new(file.split("#{self}/").last, self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(*args)
|
||||
super(*args).map! { |obj| self.class.type_cast(obj) }
|
||||
def load
|
||||
each { |path| path.load }
|
||||
end
|
||||
|
||||
def reload!
|
||||
each { |path| path.reload! }
|
||||
end
|
||||
|
||||
def <<(obj)
|
||||
super(self.class.type_cast(obj))
|
||||
end
|
||||
|
||||
def push(*objs)
|
||||
delete_paths!(objs)
|
||||
super(*objs.map { |obj| self.class.type_cast(obj) })
|
||||
end
|
||||
|
||||
def unshift(*objs)
|
||||
delete_paths!(objs)
|
||||
super(*objs.map { |obj| self.class.type_cast(obj) })
|
||||
end
|
||||
|
||||
def [](template_path)
|
||||
each do |path|
|
||||
if template = path[template_path]
|
||||
@@ -87,10 +121,5 @@ module ActionView #:nodoc:
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
def delete_paths!(paths)
|
||||
paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,11 +3,15 @@ module ActionView
|
||||
# NOTE: The template that this mixin is beening include into is frozen
|
||||
# So you can not set or modify any instance variables
|
||||
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
def self.included(base)
|
||||
@@mutex = Mutex.new
|
||||
end
|
||||
|
||||
include ActiveSupport::Memoizable
|
||||
def filename
|
||||
'compiled-template'
|
||||
end
|
||||
|
||||
def handler
|
||||
Template.handler_class_for_extension(extension)
|
||||
@@ -15,19 +19,29 @@ module ActionView
|
||||
memoize :handler
|
||||
|
||||
def compiled_source
|
||||
handler.new(nil).compile(self) if handler.compilable?
|
||||
handler.call(self)
|
||||
end
|
||||
memoize :compiled_source
|
||||
|
||||
def render(view, local_assigns = {})
|
||||
compile(local_assigns)
|
||||
|
||||
view._first_render ||= self
|
||||
view._last_render = self
|
||||
|
||||
view.send(:evaluate_assigns)
|
||||
compile(local_assigns) if handler.compilable?
|
||||
handler.new(view).render(self, local_assigns)
|
||||
view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type)
|
||||
|
||||
view.send(method_name(local_assigns), local_assigns) do |*names|
|
||||
if proc = view.instance_variable_get("@_proc_for_layout")
|
||||
view.capture(*names, &proc)
|
||||
else
|
||||
view.instance_variable_get("@content_for_#{names.first || 'layout'}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def method(local_assigns)
|
||||
def method_name(local_assigns)
|
||||
if local_assigns && local_assigns.any?
|
||||
local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
|
||||
end
|
||||
@@ -35,44 +49,49 @@ module ActionView
|
||||
end
|
||||
|
||||
private
|
||||
# Compile and evaluate the template's code
|
||||
# Compile and evaluate the template's code (if necessary)
|
||||
def compile(local_assigns)
|
||||
render_symbol = method(local_assigns)
|
||||
render_symbol = method_name(local_assigns)
|
||||
|
||||
@@mutex.synchronize do
|
||||
return false unless recompile?(render_symbol)
|
||||
|
||||
locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join
|
||||
|
||||
source = <<-end_src
|
||||
def #{render_symbol}(local_assigns)
|
||||
old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
|
||||
ensure
|
||||
self.output_buffer = old_output_buffer
|
||||
end
|
||||
end_src
|
||||
|
||||
begin
|
||||
file_name = respond_to?(:filename) ? filename : 'compiled-template'
|
||||
ActionView::Base::CompiledTemplates.module_eval(source, file_name, 0)
|
||||
rescue Exception => e # errors from template code
|
||||
if logger = ActionController::Base.logger
|
||||
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
|
||||
logger.debug "Function body: #{source}"
|
||||
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
|
||||
end
|
||||
|
||||
raise ActionView::TemplateError.new(self, {}, e)
|
||||
if recompile?(render_symbol)
|
||||
compile!(render_symbol, local_assigns)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def compile!(render_symbol, local_assigns)
|
||||
locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join
|
||||
|
||||
source = <<-end_src
|
||||
def #{render_symbol}(local_assigns)
|
||||
old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
|
||||
ensure
|
||||
self.output_buffer = old_output_buffer
|
||||
end
|
||||
end_src
|
||||
|
||||
begin
|
||||
logger = Base.logger
|
||||
logger.debug "Compiling template #{render_symbol}" if logger
|
||||
|
||||
ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
|
||||
rescue Exception => e # errors from template code
|
||||
if logger
|
||||
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
|
||||
logger.debug "Function body: #{source}"
|
||||
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
|
||||
end
|
||||
|
||||
raise ActionView::TemplateError.new(self, {}, e)
|
||||
end
|
||||
end
|
||||
|
||||
# Method to check whether template compilation is necessary.
|
||||
# The template will be compiled if the file has not been compiled yet, or
|
||||
# if local_assigns has a new key, which isn't supported by the compiled code yet.
|
||||
def recompile?(symbol)
|
||||
meth = Base::CompiledTemplates.instance_method(template.method) rescue nil
|
||||
!(meth && Base.cache_template_loading)
|
||||
!(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ module ActionView
|
||||
# NOTE: The template that this mixin is beening include into is frozen
|
||||
# So you can not set or modify any instance variables
|
||||
|
||||
include ActiveSupport::Memoizable
|
||||
extend ActiveSupport::Memoizable
|
||||
|
||||
def variable_name
|
||||
name.sub(/\A_/, '').to_sym
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module ActionView #:nodoc:
|
||||
class Template
|
||||
extend TemplateHandlers
|
||||
include ActiveSupport::Memoizable
|
||||
extend ActiveSupport::Memoizable
|
||||
include Renderable
|
||||
|
||||
attr_accessor :filename, :load_path, :base_path, :name, :format, :extension
|
||||
@@ -22,6 +22,19 @@ module ActionView #:nodoc:
|
||||
end
|
||||
memoize :format_and_extension
|
||||
|
||||
def multipart?
|
||||
format && format.include?('.')
|
||||
end
|
||||
|
||||
def content_type
|
||||
format.gsub('.', '/')
|
||||
end
|
||||
|
||||
def mime_type
|
||||
Mime::Type.lookup_by_extension(format) if format
|
||||
end
|
||||
memoize :mime_type
|
||||
|
||||
def path
|
||||
[base_path, [name, format, extension].compact.join('.')].compact.join('/')
|
||||
end
|
||||
@@ -70,7 +83,7 @@ module ActionView #:nodoc:
|
||||
load_paths = Array(load_paths) + [nil]
|
||||
load_paths.each do |load_path|
|
||||
file = [load_path, path].compact.join('/')
|
||||
return load_path, file if File.exist?(file)
|
||||
return load_path, file if File.file?(file)
|
||||
end
|
||||
raise MissingTemplate.new(load_paths, path)
|
||||
end
|
||||
@@ -79,7 +92,7 @@ module ActionView #:nodoc:
|
||||
# [base_path, name, format, extension]
|
||||
def split(file)
|
||||
if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
|
||||
if m[5] # Mulipart formats
|
||||
if m[5] # Multipart formats
|
||||
[m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
|
||||
elsif m[4] # Single format
|
||||
[m[1], m[2], m[3], m[4]]
|
||||
|
||||
@@ -7,7 +7,7 @@ module ActionView
|
||||
attr_reader :original_exception
|
||||
|
||||
def initialize(template, assigns, original_exception)
|
||||
@base_path = template.base_path
|
||||
@base_path = template.base_path.to_s
|
||||
@assigns, @source, @original_exception = assigns.dup, template.source, original_exception
|
||||
@file_path = template.filename
|
||||
@backtrace = compute_backtrace
|
||||
|
||||
@@ -1,25 +1,14 @@
|
||||
# Legacy TemplateHandler stub
|
||||
|
||||
module ActionView
|
||||
module TemplateHandlers
|
||||
module Compilable
|
||||
end
|
||||
end
|
||||
|
||||
class TemplateHandler
|
||||
def self.compilable?
|
||||
false
|
||||
end
|
||||
|
||||
def initialize(view)
|
||||
@view = view
|
||||
end
|
||||
|
||||
def render(template, local_assigns = {})
|
||||
end
|
||||
|
||||
def compile(template)
|
||||
end
|
||||
|
||||
def compilable?
|
||||
self.class.compilable?
|
||||
end
|
||||
|
||||
# Called by CacheHelper#cache
|
||||
def cache_fragment(block, name = {}, options = nil)
|
||||
def self.call(template)
|
||||
new.compile(template)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
require 'action_view/template_handler'
|
||||
require 'action_view/template_handlers/compilable'
|
||||
require 'action_view/template_handlers/builder'
|
||||
require 'action_view/template_handlers/erb'
|
||||
require 'action_view/template_handlers/rjs'
|
||||
|
||||
@@ -6,18 +6,12 @@ module ActionView
|
||||
include Compilable
|
||||
|
||||
def compile(template)
|
||||
# ActionMailer does not have a response
|
||||
"controller.respond_to?(:response) && controller.response.content_type ||= Mime::XML;" +
|
||||
"set_controller_content_type(Mime::XML);" +
|
||||
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
|
||||
"self.output_buffer = xml.target!;" +
|
||||
template.source +
|
||||
";xml.target!;"
|
||||
end
|
||||
|
||||
def cache_fragment(block, name = {}, options = nil)
|
||||
@view.fragment_for(block, name, options) do
|
||||
eval('xml.target!', block.binding)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
module ActionView
|
||||
module TemplateHandlers
|
||||
module Compilable
|
||||
def self.included(base)
|
||||
base.extend ClassMethod
|
||||
end
|
||||
|
||||
module ClassMethod
|
||||
# If a handler is mixin this module, set compilable to true
|
||||
def compilable?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def render(template, local_assigns = {})
|
||||
@view.send(:execute, template, local_assigns)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -48,14 +48,11 @@ module ActionView
|
||||
self.erb_trim_mode = '-'
|
||||
|
||||
def compile(template)
|
||||
src = ::ERB.new(template.source, nil, erb_trim_mode, '@output_buffer').src
|
||||
"__in_erb_template=true;#{src}"
|
||||
end
|
||||
src = ::ERB.new("<% __in_erb_template=true %>#{template.source}", nil, erb_trim_mode, '@output_buffer').src
|
||||
|
||||
def cache_fragment(block, name = {}, options = nil) #:nodoc:
|
||||
@view.fragment_for(block, name, options) do
|
||||
@view.response.template.output_buffer
|
||||
end
|
||||
# Ruby 1.9 prepends an encoding to the source. However this is
|
||||
# useless because you can only set an encoding on the first line
|
||||
RUBY_VERSION >= '1.9' ? src.sub(/\A#coding:.*\n/, '') : src
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,17 +7,6 @@ module ActionView
|
||||
"controller.response.content_type ||= Mime::JS;" +
|
||||
"update_page do |page|;#{template.source}\nend"
|
||||
end
|
||||
|
||||
def cache_fragment(block, name = {}, options = nil) #:nodoc:
|
||||
@view.fragment_for(block, name, options) do
|
||||
begin
|
||||
debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false
|
||||
eval('page.to_s', block.binding)
|
||||
ensure
|
||||
ActionView::Base.debug_rjs = debug_mode
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,9 +25,7 @@ module ActionView
|
||||
end
|
||||
end
|
||||
|
||||
ActionView::Base.helper_modules.each do |helper_module|
|
||||
include helper_module
|
||||
end
|
||||
include ActionView::Helpers
|
||||
include ActionController::PolymorphicRoutes
|
||||
include ActionController::RecordIdentifier
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ ActiveSupport::Deprecation.debug = true
|
||||
ActionController::Base.logger = nil
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
|
||||
ActionView::Base.cache_template_loading = true
|
||||
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
|
||||
ActionView::PathSet::Path.eager_load_templates!
|
||||
ActionController::Base.view_paths = FIXTURE_LOAD_PATH
|
||||
|
||||
# Wrap tests that use Mocha and skip if unavailable.
|
||||
|
||||
@@ -39,6 +39,11 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
|
||||
@developers = Developer.find(:all)
|
||||
render :partial => @developers
|
||||
end
|
||||
|
||||
def render_with_record_collection_and_spacer_template
|
||||
@developer = Developer.find(1)
|
||||
render :partial => @developer.projects, :spacer_template => 'test/partial_only'
|
||||
end
|
||||
end
|
||||
|
||||
class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
|
||||
@@ -81,6 +86,11 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
|
||||
assert_equal 'DavidJamisfixture_3fixture_4fixture_5fixture_6fixture_7fixture_8fixture_9fixture_10Jamis', @response.body
|
||||
end
|
||||
|
||||
def test_render_with_record_collection_and_spacer_template
|
||||
get :render_with_record_collection_and_spacer_template
|
||||
assert_equal 'Active Recordonly partialActive Controller', @response.body
|
||||
end
|
||||
|
||||
def test_rendering_partial_with_has_one_association
|
||||
mascot = Company.find(1).mascot
|
||||
get :render_with_has_one_association
|
||||
|
||||
@@ -17,6 +17,8 @@ unless defined?(ActionMailer)
|
||||
end
|
||||
end
|
||||
|
||||
ActionMailer::Base.template_root = FIXTURE_LOAD_PATH
|
||||
|
||||
class AssertSelectTest < Test::Unit::TestCase
|
||||
class AssertSelectController < ActionController::Base
|
||||
def response_with=(content)
|
||||
@@ -69,11 +71,10 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
ActionMailer::Base.deliveries = []
|
||||
end
|
||||
|
||||
|
||||
def teardown
|
||||
ActionMailer::Base.deliveries.clear
|
||||
end
|
||||
|
||||
|
||||
def assert_failure(message, &block)
|
||||
e = assert_raises(AssertionFailedError, &block)
|
||||
assert_match(message, e.message) if Regexp === message
|
||||
@@ -91,7 +92,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" }
|
||||
end
|
||||
|
||||
|
||||
def test_equality_true_false
|
||||
render_html %Q{<div id="1"></div><div id="2"></div>}
|
||||
assert_nothing_raised { assert_select "div" }
|
||||
@@ -102,7 +102,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
assert_nothing_raised { assert_select "p", false }
|
||||
end
|
||||
|
||||
|
||||
def test_equality_string_and_regexp
|
||||
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
|
||||
assert_nothing_raised { assert_select "div", "foo" }
|
||||
@@ -116,7 +115,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
assert_raises(AssertionFailedError) { assert_select "p", :text=>/foobar/ }
|
||||
end
|
||||
|
||||
|
||||
def test_equality_of_html
|
||||
render_html %Q{<p>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</p>}
|
||||
text = "\"This is not a big problem,\" he said."
|
||||
@@ -135,7 +133,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
assert_raises(AssertionFailedError) { assert_select "pre", :html=>text }
|
||||
end
|
||||
|
||||
|
||||
def test_counts
|
||||
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
|
||||
assert_nothing_raised { assert_select "div", 2 }
|
||||
@@ -166,7 +163,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_substitution_values
|
||||
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
|
||||
assert_select "div#?", /\d+/ do |elements|
|
||||
@@ -181,7 +177,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_nested_assert_select
|
||||
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
|
||||
assert_select "div" do |elements|
|
||||
@@ -200,7 +195,7 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
assert_select "#3", false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
assert_failure(/Expected at least 1 element matching \"#4\", found 0\./) do
|
||||
assert_select "div" do
|
||||
assert_select "#4"
|
||||
@@ -208,7 +203,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_assert_select_text_match
|
||||
render_html %Q{<div id="1"><span>foo</span></div><div id="2"><span>bar</span></div>}
|
||||
assert_select "div" do
|
||||
@@ -225,7 +219,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# With single result.
|
||||
def test_assert_select_from_rjs_with_single_result
|
||||
render_rjs do |page|
|
||||
@@ -255,19 +248,16 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Test css_select.
|
||||
#
|
||||
|
||||
|
||||
def test_css_select
|
||||
render_html %Q{<div id="1"></div><div id="2"></div>}
|
||||
assert 2, css_select("div").size
|
||||
assert 0, css_select("p").size
|
||||
end
|
||||
|
||||
|
||||
def test_nested_css_select
|
||||
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
|
||||
assert_select "div#?", /\d+/ do |elements|
|
||||
@@ -286,7 +276,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# With one result.
|
||||
def test_css_select_from_rjs_with_single_result
|
||||
render_rjs do |page|
|
||||
@@ -309,12 +298,10 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
assert_equal 1, css_select("#2").size
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Test assert_select_rjs.
|
||||
#
|
||||
|
||||
|
||||
# Test that we can pick up all statements in the result.
|
||||
def test_assert_select_rjs_picks_up_all_statements
|
||||
render_rjs do |page|
|
||||
@@ -381,7 +368,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
assert_raises(AssertionFailedError) { assert_select_rjs "test4" }
|
||||
end
|
||||
|
||||
|
||||
def test_assert_select_rjs_for_replace
|
||||
render_rjs do |page|
|
||||
page.replace "test1", "<div id=\"1\">foo</div>"
|
||||
@@ -479,7 +465,7 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Simple hide
|
||||
def test_assert_select_rjs_for_hide
|
||||
render_rjs do |page|
|
||||
@@ -500,7 +486,7 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Simple toggle
|
||||
def test_assert_select_rjs_for_toggle
|
||||
render_rjs do |page|
|
||||
@@ -521,7 +507,7 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Non-positioned insert.
|
||||
def test_assert_select_rjs_for_nonpositioned_insert
|
||||
render_rjs do |page|
|
||||
@@ -568,7 +554,7 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
assert_select "div", 4
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Simple selection from a single result.
|
||||
def test_nested_assert_select_rjs_with_single_result
|
||||
render_rjs do |page|
|
||||
@@ -600,7 +586,6 @@ class AssertSelectTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def test_feed_item_encoded
|
||||
render_xml <<-EOF
|
||||
<rss version="2.0">
|
||||
@@ -654,7 +639,6 @@ EOF
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Test assert_select_email
|
||||
#
|
||||
@@ -670,7 +654,6 @@ EOF
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
def render_html(html)
|
||||
@controller.response_with = html
|
||||
|
||||
@@ -7,6 +7,7 @@ module Submodule
|
||||
end
|
||||
class ContainedNonEmptyController < ActionController::Base
|
||||
def public_action
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
hide_action :hidden_action
|
||||
@@ -105,6 +106,18 @@ end
|
||||
|
||||
|
||||
class PerformActionTest < Test::Unit::TestCase
|
||||
class MockLogger
|
||||
attr_reader :logged
|
||||
|
||||
def initialize
|
||||
@logged = []
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
@logged << args.first
|
||||
end
|
||||
end
|
||||
|
||||
def use_controller(controller_class)
|
||||
@controller = controller_class.new
|
||||
|
||||
@@ -142,6 +155,13 @@ class PerformActionTest < Test::Unit::TestCase
|
||||
get :another_hidden_action
|
||||
assert_response 404
|
||||
end
|
||||
|
||||
def test_namespaced_action_should_log_module_name
|
||||
use_controller Submodule::ContainedNonEmptyController
|
||||
@controller.logger = MockLogger.new
|
||||
get :public_action
|
||||
assert_match /Processing\sSubmodule::ContainedNonEmptyController#public_action/, @controller.logger.logged[1]
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultUrlOptionsTest < Test::Unit::TestCase
|
||||
|
||||
@@ -109,7 +109,7 @@ class PageCachingTest < Test::Unit::TestCase
|
||||
|
||||
uses_mocha("should_cache_ok_at_custom_path") do
|
||||
def test_should_cache_ok_at_custom_path
|
||||
@request.expects(:path).returns("/index.html")
|
||||
@request.stubs(:path).returns("/index.html")
|
||||
get :ok
|
||||
assert_response :ok
|
||||
assert File.exist?("#{FILE_STORE_PATH}/index.html")
|
||||
@@ -148,7 +148,6 @@ class PageCachingTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class ActionCachingTestController < ActionController::Base
|
||||
caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
|
||||
caches_action :show, :cache_path => 'http://test.host/custom/show'
|
||||
@@ -489,54 +488,54 @@ class FragmentCachingTest < Test::Unit::TestCase
|
||||
|
||||
def test_fragment_cache_key
|
||||
assert_equal 'views/what a key', @controller.fragment_cache_key('what a key')
|
||||
assert_equal( "views/test.host/fragment_caching_test/some_action",
|
||||
@controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action'))
|
||||
assert_equal "views/test.host/fragment_caching_test/some_action",
|
||||
@controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action')
|
||||
end
|
||||
|
||||
def test_read_fragment__with_caching_enabled
|
||||
def test_read_fragment_with_caching_enabled
|
||||
@store.write('views/name', 'value')
|
||||
assert_equal 'value', @controller.read_fragment('name')
|
||||
end
|
||||
|
||||
def test_read_fragment__with_caching_disabled
|
||||
def test_read_fragment_with_caching_disabled
|
||||
ActionController::Base.perform_caching = false
|
||||
@store.write('views/name', 'value')
|
||||
assert_nil @controller.read_fragment('name')
|
||||
end
|
||||
|
||||
def test_fragment_exist__with_caching_enabled
|
||||
def test_fragment_exist_with_caching_enabled
|
||||
@store.write('views/name', 'value')
|
||||
assert @controller.fragment_exist?('name')
|
||||
assert !@controller.fragment_exist?('other_name')
|
||||
end
|
||||
|
||||
def test_fragment_exist__with_caching_disabled
|
||||
def test_fragment_exist_with_caching_disabled
|
||||
ActionController::Base.perform_caching = false
|
||||
@store.write('views/name', 'value')
|
||||
assert !@controller.fragment_exist?('name')
|
||||
assert !@controller.fragment_exist?('other_name')
|
||||
end
|
||||
|
||||
def test_write_fragment__with_caching_enabled
|
||||
def test_write_fragment_with_caching_enabled
|
||||
assert_nil @store.read('views/name')
|
||||
assert_equal 'value', @controller.write_fragment('name', 'value')
|
||||
assert_equal 'value', @store.read('views/name')
|
||||
end
|
||||
|
||||
def test_write_fragment__with_caching_disabled
|
||||
def test_write_fragment_with_caching_disabled
|
||||
assert_nil @store.read('views/name')
|
||||
ActionController::Base.perform_caching = false
|
||||
assert_equal nil, @controller.write_fragment('name', 'value')
|
||||
assert_nil @store.read('views/name')
|
||||
end
|
||||
|
||||
def test_expire_fragment__with_simple_key
|
||||
def test_expire_fragment_with_simple_key
|
||||
@store.write('views/name', 'value')
|
||||
@controller.expire_fragment 'name'
|
||||
assert_nil @store.read('views/name')
|
||||
end
|
||||
|
||||
def test_expire_fragment__with__regexp
|
||||
def test_expire_fragment_with_regexp
|
||||
@store.write('views/name', 'value')
|
||||
@store.write('views/another_name', 'another_value')
|
||||
@store.write('views/primalgrasp', 'will not expire ;-)')
|
||||
@@ -548,14 +547,14 @@ class FragmentCachingTest < Test::Unit::TestCase
|
||||
assert_equal 'will not expire ;-)', @store.read('views/primalgrasp')
|
||||
end
|
||||
|
||||
def test_fragment_for__with_disabled_caching
|
||||
def test_fragment_for_with_disabled_caching
|
||||
ActionController::Base.perform_caching = false
|
||||
|
||||
@store.write('views/expensive', 'fragment content')
|
||||
fragment_computed = false
|
||||
|
||||
buffer = 'generated till now -> '
|
||||
@controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer }
|
||||
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
|
||||
|
||||
assert fragment_computed
|
||||
assert_equal 'generated till now -> ', buffer
|
||||
@@ -566,53 +565,13 @@ class FragmentCachingTest < Test::Unit::TestCase
|
||||
fragment_computed = false
|
||||
|
||||
buffer = 'generated till now -> '
|
||||
@controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer}
|
||||
@controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
|
||||
|
||||
assert !fragment_computed
|
||||
assert_equal 'generated till now -> fragment content', buffer
|
||||
end
|
||||
|
||||
def test_cache_erb_fragment
|
||||
@store.write('views/expensive', 'fragment content')
|
||||
@controller.response.template.output_buffer = 'generated till now -> '
|
||||
|
||||
assert_equal( 'generated till now -> fragment content',
|
||||
ActionView::TemplateHandlers::ERB.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
|
||||
end
|
||||
|
||||
def test_cache_rxml_fragment
|
||||
@store.write('views/expensive', 'fragment content')
|
||||
xml = 'generated till now -> '
|
||||
class << xml; def target!; to_s; end; end
|
||||
|
||||
assert_equal( 'generated till now -> fragment content',
|
||||
ActionView::TemplateHandlers::Builder.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
|
||||
end
|
||||
|
||||
def test_cache_rjs_fragment
|
||||
@store.write('views/expensive', 'fragment content')
|
||||
page = 'generated till now -> '
|
||||
|
||||
assert_equal( 'generated till now -> fragment content',
|
||||
ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
|
||||
end
|
||||
|
||||
def test_cache_rjs_fragment_debug_mode_does_not_interfere
|
||||
@store.write('views/expensive', 'fragment content')
|
||||
page = 'generated till now -> '
|
||||
|
||||
begin
|
||||
debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, true
|
||||
assert_equal( 'generated till now -> fragment content',
|
||||
ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
|
||||
assert ActionView::Base.debug_rjs
|
||||
ensure
|
||||
ActionView::Base.debug_rjs = debug_mode
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class FunctionalCachingController < ActionController::Base
|
||||
def fragment_cached
|
||||
end
|
||||
@@ -629,6 +588,13 @@ class FunctionalCachingController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
def formatted_fragment_cached
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.xml
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
def rescue_action(e)
|
||||
raise e
|
||||
@@ -678,4 +644,35 @@ CACHED
|
||||
assert_match /Fragment caching in a partial/, @response.body
|
||||
assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial')
|
||||
end
|
||||
|
||||
def test_html_formatted_fragment_caching
|
||||
get :formatted_fragment_cached, :format => "html"
|
||||
assert_response :success
|
||||
expected_body = "<body>\n<p>ERB</p>\n</body>"
|
||||
|
||||
assert_equal expected_body, @response.body
|
||||
|
||||
assert_equal "<p>ERB</p>", @store.read('views/test.host/functional_caching/formatted_fragment_cached')
|
||||
end
|
||||
|
||||
def test_xml_formatted_fragment_caching
|
||||
get :formatted_fragment_cached, :format => "xml"
|
||||
assert_response :success
|
||||
expected_body = "<body>\n <p>Builder</p>\n</body>\n"
|
||||
|
||||
assert_equal expected_body, @response.body
|
||||
|
||||
assert_equal " <p>Builder</p>\n", @store.read('views/test.host/functional_caching/formatted_fragment_cached')
|
||||
end
|
||||
|
||||
def test_js_formatted_fragment_caching
|
||||
get :formatted_fragment_cached, :format => "js"
|
||||
assert_response :success
|
||||
expected_body = %(title = "Hey";\n$("element_1").visualEffect("highlight");\n) +
|
||||
%($("element_2").visualEffect("highlight");\nfooter = "Bye";)
|
||||
assert_equal expected_body, @response.body
|
||||
|
||||
assert_equal ['$("element_1").visualEffect("highlight");', '$("element_2").visualEffect("highlight");'],
|
||||
@store.read('views/test.host/functional_caching/formatted_fragment_cached')
|
||||
end
|
||||
end
|
||||
|
||||
52
actionpack/test/controller/cgi_test.rb
Executable file → Normal file
52
actionpack/test/controller/cgi_test.rb
Executable file → Normal file
@@ -53,6 +53,15 @@ class BaseCgiTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def default_test; end
|
||||
|
||||
private
|
||||
|
||||
def set_content_data(data)
|
||||
@request.env['REQUEST_METHOD'] = 'POST'
|
||||
@request.env['CONTENT_LENGTH'] = data.length
|
||||
@request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
|
||||
@request.env['RAW_POST_DATA'] = data
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequestTest < BaseCgiTest
|
||||
@@ -66,7 +75,7 @@ class CgiRequestTest < BaseCgiTest
|
||||
assert_equal "rubyonrails.org:8080", @request.host_with_port
|
||||
|
||||
@request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
|
||||
assert_equal "www.secondhost.org", @request.host
|
||||
assert_equal "www.secondhost.org", @request.host(true)
|
||||
end
|
||||
|
||||
def test_http_host_with_default_port_overrides_server_port
|
||||
@@ -155,10 +164,8 @@ end
|
||||
|
||||
class CgiRequestParamsParsingTest < BaseCgiTest
|
||||
def test_doesnt_break_when_content_type_has_charset
|
||||
data = 'flamenco=love'
|
||||
@request.env['CONTENT_LENGTH'] = data.length
|
||||
@request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
|
||||
@request.env['RAW_POST_DATA'] = data
|
||||
set_content_data 'flamenco=love'
|
||||
|
||||
assert_equal({"flamenco"=> "love"}, @request.request_parameters)
|
||||
end
|
||||
|
||||
@@ -168,6 +175,41 @@ class CgiRequestParamsParsingTest < BaseCgiTest
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequestContentTypeTest < BaseCgiTest
|
||||
def test_html_content_type_verification
|
||||
@request.env['CONTENT_TYPE'] = Mime::HTML.to_s
|
||||
assert @request.content_type.verify_request?
|
||||
end
|
||||
|
||||
def test_xml_content_type_verification
|
||||
@request.env['CONTENT_TYPE'] = Mime::XML.to_s
|
||||
assert !@request.content_type.verify_request?
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequestMethodTest < BaseCgiTest
|
||||
def test_get
|
||||
assert_equal :get, @request.request_method
|
||||
end
|
||||
|
||||
def test_post
|
||||
@request.env['REQUEST_METHOD'] = 'POST'
|
||||
assert_equal :post, @request.request_method
|
||||
end
|
||||
|
||||
def test_put
|
||||
set_content_data '_method=put'
|
||||
|
||||
assert_equal :put, @request.request_method
|
||||
end
|
||||
|
||||
def test_delete
|
||||
set_content_data '_method=delete'
|
||||
|
||||
assert_equal :delete, @request.request_method
|
||||
end
|
||||
end
|
||||
|
||||
class CgiRequestNeedsRewoundTest < BaseCgiTest
|
||||
def test_body_should_be_rewound
|
||||
data = 'foo'
|
||||
|
||||
@@ -19,6 +19,11 @@ class ContentTypeController < ActionController::Base
|
||||
render :text => "hello world!"
|
||||
end
|
||||
|
||||
def render_nil_charset_from_body
|
||||
response.charset = nil
|
||||
render :text => "hello world!"
|
||||
end
|
||||
|
||||
def render_default_for_rhtml
|
||||
end
|
||||
|
||||
@@ -85,8 +90,23 @@ class ContentTypeTest < Test::Unit::TestCase
|
||||
|
||||
def test_charset_from_body
|
||||
get :render_charset_from_body
|
||||
assert_equal "utf-16", @response.charset
|
||||
assert_equal Mime::HTML, @response.content_type
|
||||
assert_equal "utf-16", @response.charset
|
||||
end
|
||||
|
||||
def test_nil_charset_from_body
|
||||
get :render_nil_charset_from_body
|
||||
assert_equal Mime::HTML, @response.content_type
|
||||
assert_equal "utf-8", @response.charset, @response.headers.inspect
|
||||
end
|
||||
|
||||
def test_nil_default_for_rhtml
|
||||
ContentTypeController.default_charset = nil
|
||||
get :render_default_for_rhtml
|
||||
assert_equal Mime::HTML, @response.content_type
|
||||
assert_nil @response.charset, @response.headers.inspect
|
||||
ensure
|
||||
ContentTypeController.default_charset = "utf-8"
|
||||
end
|
||||
|
||||
def test_default_for_rhtml
|
||||
@@ -128,23 +148,23 @@ class AcceptBasedContentTypeTest < ActionController::TestCase
|
||||
|
||||
|
||||
def test_render_default_content_types_for_respond_to
|
||||
@request.env["HTTP_ACCEPT"] = Mime::HTML.to_s
|
||||
@request.accept = Mime::HTML.to_s
|
||||
get :render_default_content_types_for_respond_to
|
||||
assert_equal Mime::HTML, @response.content_type
|
||||
|
||||
@request.env["HTTP_ACCEPT"] = Mime::JS.to_s
|
||||
@request.accept = Mime::JS.to_s
|
||||
get :render_default_content_types_for_respond_to
|
||||
assert_equal Mime::JS, @response.content_type
|
||||
end
|
||||
|
||||
def test_render_default_content_types_for_respond_to_with_template
|
||||
@request.env["HTTP_ACCEPT"] = Mime::XML.to_s
|
||||
@request.accept = Mime::XML.to_s
|
||||
get :render_default_content_types_for_respond_to
|
||||
assert_equal Mime::XML, @response.content_type
|
||||
end
|
||||
|
||||
def test_render_default_content_types_for_respond_to_with_overwrite
|
||||
@request.env["HTTP_ACCEPT"] = Mime::RSS.to_s
|
||||
@request.accept = Mime::RSS.to_s
|
||||
get :render_default_content_types_for_respond_to
|
||||
assert_equal Mime::XML, @response.content_type
|
||||
end
|
||||
|
||||
@@ -60,7 +60,7 @@ class CookieTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def test_setting_cookie_for_fourteen_days_with_symbols
|
||||
get :authenticate_for_fourteen_days
|
||||
get :authenticate_for_fourteen_days_with_symbols
|
||||
assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"]
|
||||
end
|
||||
|
||||
|
||||
@@ -253,7 +253,14 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
||||
session :off
|
||||
|
||||
def get
|
||||
render :text => "OK", :status => 200
|
||||
respond_to do |format|
|
||||
format.html { render :text => "OK", :status => 200 }
|
||||
format.js { render :text => "JS OK", :status => 200 }
|
||||
end
|
||||
end
|
||||
|
||||
def get_with_params
|
||||
render :text => "foo: #{params[:foo]}", :status => 200
|
||||
end
|
||||
|
||||
def post
|
||||
@@ -265,6 +272,10 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
||||
cookies["cookie_3"] = "chocolate"
|
||||
render :text => "Gone", :status => 410
|
||||
end
|
||||
|
||||
def redirect
|
||||
redirect_to :action => "get"
|
||||
end
|
||||
end
|
||||
|
||||
def test_get
|
||||
@@ -274,6 +285,9 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
||||
assert_equal "OK", status_message
|
||||
assert_equal "200 OK", response.headers["Status"]
|
||||
assert_equal ["200 OK"], headers["status"]
|
||||
assert_response 200
|
||||
assert_response :success
|
||||
assert_response :ok
|
||||
assert_equal [], response.headers["cookie"]
|
||||
assert_equal [], headers["cookie"]
|
||||
assert_equal({}, cookies)
|
||||
@@ -290,6 +304,9 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
||||
assert_equal "Created", status_message
|
||||
assert_equal "201 Created", response.headers["Status"]
|
||||
assert_equal ["201 Created"], headers["status"]
|
||||
assert_response 201
|
||||
assert_response :success
|
||||
assert_response :created
|
||||
assert_equal [], response.headers["cookie"]
|
||||
assert_equal [], headers["cookie"]
|
||||
assert_equal({}, cookies)
|
||||
@@ -308,23 +325,84 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
||||
assert_equal "Gone", status_message
|
||||
assert_equal "410 Gone", response.headers["Status"]
|
||||
assert_equal ["410 Gone"], headers["status"]
|
||||
assert_equal nil, response.headers["Set-Cookie"]
|
||||
assert_response 410
|
||||
assert_response :gone
|
||||
assert_equal ["cookie_1=; path=/", "cookie_3=chocolate; path=/"], response.headers["Set-Cookie"]
|
||||
assert_equal ["cookie_1=; path=/", "cookie_3=chocolate; path=/"], headers['set-cookie']
|
||||
assert_equal [[], ["chocolate"]], response.headers["cookie"]
|
||||
assert_equal [
|
||||
CGI::Cookie::new("name" => "cookie_1", "value" => ""),
|
||||
CGI::Cookie::new("name" => "cookie_3", "value" => "chocolate")
|
||||
], response.headers["cookie"]
|
||||
assert_equal [], headers["cookie"]
|
||||
assert_equal({"cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate"}, cookies)
|
||||
assert_equal "Gone", response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_redirect
|
||||
with_test_route_set do
|
||||
get '/redirect'
|
||||
assert_equal 302, status
|
||||
assert_equal "Found", status_message
|
||||
assert_equal "302 Found", response.headers["Status"]
|
||||
assert_equal ["302 Found"], headers["status"]
|
||||
assert_response 302
|
||||
assert_response :redirect
|
||||
assert_response :found
|
||||
assert_equal "<html><body>You are being <a href=\"http://www.example.com/get\">redirected</a>.</body></html>", response.body
|
||||
assert_kind_of HTML::Document, html_document
|
||||
assert_equal 1, request_count
|
||||
end
|
||||
end
|
||||
|
||||
def test_xml_http_request_get
|
||||
with_test_route_set do
|
||||
xhr :get, '/get'
|
||||
assert_equal 200, status
|
||||
assert_equal "OK", status_message
|
||||
assert_equal "200 OK", response.headers["Status"]
|
||||
assert_equal ["200 OK"], headers["status"]
|
||||
assert_response 200
|
||||
assert_response :success
|
||||
assert_response :ok
|
||||
assert_equal "JS OK", response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_get_with_query_string
|
||||
with_test_route_set do
|
||||
get '/get_with_params?foo=bar'
|
||||
assert_equal '/get_with_params?foo=bar', request.env["REQUEST_URI"]
|
||||
assert_equal '/get_with_params?foo=bar', request.request_uri
|
||||
assert_equal nil, request.env["QUERY_STRING"]
|
||||
assert_equal 'foo=bar', request.query_string
|
||||
assert_equal 'bar', request.parameters['foo']
|
||||
|
||||
assert_equal 200, status
|
||||
assert_equal "foo: bar", response.body
|
||||
end
|
||||
end
|
||||
|
||||
def test_get_with_parameters
|
||||
with_test_route_set do
|
||||
get '/get_with_params', :foo => "bar"
|
||||
assert_equal '/get_with_params', request.env["REQUEST_URI"]
|
||||
assert_equal '/get_with_params', request.request_uri
|
||||
assert_equal 'foo=bar', request.env["QUERY_STRING"]
|
||||
assert_equal 'foo=bar', request.query_string
|
||||
assert_equal 'bar', request.parameters['foo']
|
||||
|
||||
assert_equal 200, status
|
||||
assert_equal "foo: bar", response.body
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_test_route_set
|
||||
with_routing do |set|
|
||||
set.draw do |map|
|
||||
map.with_options :controller => "IntegrationProcessTest::Integration" do |c|
|
||||
c.connect '/get', :action => "get"
|
||||
c.connect '/post', :action => "post"
|
||||
c.connect '/cookie_monster', :action => "cookie_monster"
|
||||
c.connect "/:action"
|
||||
end
|
||||
end
|
||||
yield
|
||||
|
||||
@@ -31,16 +31,8 @@ end
|
||||
class MultipleExtensions < LayoutTest
|
||||
end
|
||||
|
||||
class MabView < ActionView::TemplateHandler
|
||||
def initialize(view)
|
||||
end
|
||||
|
||||
def render(template, local_assigns)
|
||||
template.source
|
||||
end
|
||||
end
|
||||
|
||||
ActionView::Template::register_template_handler :mab, MabView
|
||||
ActionView::Template::register_template_handler :mab,
|
||||
lambda { |template| template.source.inspect }
|
||||
|
||||
class LayoutAutoDiscoveryTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@@ -49,19 +41,19 @@ class LayoutAutoDiscoveryTest < Test::Unit::TestCase
|
||||
|
||||
@request.host = "www.nextangle.com"
|
||||
end
|
||||
|
||||
|
||||
def test_application_layout_is_default_when_no_controller_match
|
||||
@controller = ProductController.new
|
||||
get :hello
|
||||
assert_equal 'layout_test.rhtml hello.rhtml', @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_controller_name_layout_name_match
|
||||
@controller = ItemController.new
|
||||
get :hello
|
||||
assert_equal 'item.rhtml hello.rhtml', @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_third_party_template_library_auto_discovers_layout
|
||||
ThirdPartyTemplateLibraryController.view_paths.reload!
|
||||
@controller = ThirdPartyTemplateLibraryController.new
|
||||
@@ -71,14 +63,14 @@ class LayoutAutoDiscoveryTest < Test::Unit::TestCase
|
||||
assert_response :success
|
||||
assert_equal 'Mab', @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_namespaced_controllers_auto_detect_layouts
|
||||
@controller = ControllerNameSpace::NestedController.new
|
||||
get :hello
|
||||
assert_equal 'layouts/controller_name_space/nested', @controller.active_layout
|
||||
assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body
|
||||
end
|
||||
|
||||
|
||||
def test_namespaced_controllers_auto_detect_layouts
|
||||
@controller = MultipleExtensions.new
|
||||
get :hello
|
||||
@@ -123,7 +115,7 @@ class ExemptFromLayoutTest < Test::Unit::TestCase
|
||||
|
||||
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')
|
||||
assert @controller.send!(:template_exempt_from_layout?, 'hello.rhtml')
|
||||
|
||||
@@ -164,19 +156,19 @@ class LayoutSetInResponseTest < Test::Unit::TestCase
|
||||
get :hello
|
||||
assert_equal 'layouts/layout_test', @response.layout
|
||||
end
|
||||
|
||||
|
||||
def test_layout_set_when_set_in_controller
|
||||
@controller = HasOwnLayoutController.new
|
||||
get :hello
|
||||
assert_equal 'layouts/item', @response.layout
|
||||
end
|
||||
|
||||
|
||||
def test_layout_set_when_using_render
|
||||
@controller = SetsLayoutInRenderController.new
|
||||
get :hello
|
||||
assert_equal 'layouts/third_party_template_library', @response.layout
|
||||
end
|
||||
|
||||
|
||||
def test_layout_is_not_set_when_none_rendered
|
||||
@controller = RendersNoLayoutController.new
|
||||
get :hello
|
||||
@@ -257,4 +249,3 @@ class LayoutSymlinkedIsRenderedTest < Test::Unit::TestCase
|
||||
assert_equal "layouts/symlinked/symlinked_layout", @response.layout
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user