Rails 2.3.3.1

Update to latest Rails.
A little bit of jiggery-pokery is involved, since they
neglected to re-include vendored Rack in this release.
This commit is contained in:
Jacques Distler 2009-08-04 10:16:03 -05:00
parent 329fafafce
commit 664552ac02
257 changed files with 4346 additions and 1682 deletions

View file

@ -58,7 +58,7 @@ class AdminControllerTest < ActionController::TestCase
process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki',
'web_address' => 'my_wiki'
assert_redirected_to :web => @wiki.webs.keys.first, :action => 'show', :id => 'HomePage'
assert_redirected_to :web => @wiki.webs.keys.first, :controller => 'wiki', :action => 'show', :id => 'HomePage'
assert_equal wiki_before, @wiki
# and no new web should be created either
assert_equal old_size, @wiki.webs.size
@ -68,7 +68,7 @@ class AdminControllerTest < ActionController::TestCase
def test_create_system_no_form_and_wiki_already_initialized
assert @wiki.setup?
process('create_system')
assert_redirected_to :web => @wiki.webs.keys.first, :action => 'show', :id => 'HomePage'
assert_redirected_to :web => @wiki.webs.keys.first, :controller => 'wiki', :action => 'show', :id => 'HomePage'
assert(@response.has_flash_object?(:error))
end
@ -78,7 +78,7 @@ class AdminControllerTest < ActionController::TestCase
process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2'
assert_redirected_to :web => 'wiki2', :action => 'new', :id => 'HomePage'
assert_redirected_to :web => 'wiki2', :controller => 'wiki', :action => 'new', :id => 'HomePage'
wiki2 = @wiki.webs['wiki2']
assert wiki2
assert_equal 'Wiki Two', wiki2.name
@ -90,7 +90,7 @@ class AdminControllerTest < ActionController::TestCase
process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2'
assert_redirected_to :web => 'wiki2', :action => 'new', :id => 'HomePage'
assert_redirected_to :web => 'wiki2', :controller => 'wiki', :action => 'new', :id => 'HomePage'
end
def test_create_web_failed_authentication
@ -98,7 +98,7 @@ class AdminControllerTest < ActionController::TestCase
process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2'
assert_redirected_to :web => nil, :action => 'create_web'
assert_redirected_to :controller => 'admin', :action => 'create_web'
assert_nil @wiki.webs['wiki2']
end
@ -108,7 +108,6 @@ class AdminControllerTest < ActionController::TestCase
assert_response :success
end
def test_edit_web_no_form
process 'edit_web', 'web' => 'wiki1'
# this action simply renders a form
@ -125,7 +124,7 @@ class AdminControllerTest < ActionController::TestCase
'brackets_only' => 'on', 'count_pages' => 'on', 'allow_uploads' => 'on',
'max_upload_size' => '300')
assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage'
assert_redirected_to :web => 'renamed_wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
@web = Web.find(@web.id)
assert_equal 'renamed_wiki1', @web.address
assert_equal 'Renamed Wiki1', @web.name
@ -164,7 +163,7 @@ class AdminControllerTest < ActionController::TestCase
# safe_mode, published, brackets_only, count_pages, allow_uploads not set
# and should become false
assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage'
assert_redirected_to :web => 'renamed_wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
@web = Web.find(@web.id)
assert !@web.safe_mode?
assert !@web.published?
@ -237,7 +236,7 @@ class AdminControllerTest < ActionController::TestCase
# third pass does not destroy HomePage
r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd')
assert_redirected_to :action => 'list'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'list'
@web.pages(true)
assert_equal page_order, @web.select.sort,
"Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}"
@ -271,14 +270,14 @@ class AdminControllerTest < ActionController::TestCase
# third pass does does nothing, since there are no pages in the
# 'leaves' category.
r = process('remove_orphaned_pages_in_category', 'web' => 'wiki1', 'category' => 'leaves', 'system_password_orphaned_in_category' => 'pswd')
assert_redirected_to :action => 'list'
assert_redirected_to :controller => 'wiki', :web => 'wiki1', :action => 'list'
@web.pages(true)
assert_equal page_order, @web.select.sort,
"Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}"
# fourth pass destroys Oak
r = process('remove_orphaned_pages_in_category', 'web' => 'wiki1', 'category' => 'trees', 'system_password_orphaned_in_category' => 'pswd')
assert_redirected_to :action => 'list'
assert_redirected_to :controller => 'wiki', :web => 'wiki1', :action => 'list'
@web.pages(true)
page_order.delete(@oak)
assert_equal page_order, @web.select.sort,

View file

@ -39,7 +39,7 @@ class WikiControllerTest < ActionController::TestCase
set_web_property :password, 'pswd'
get :authenticate, :web => 'wiki1', :password => 'pswd'
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
assert_equal 'pswd', @response.cookies['wiki1']
end
@ -47,7 +47,7 @@ class WikiControllerTest < ActionController::TestCase
set_web_property :password, 'pswd'
r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password')
assert_redirected_to :action => 'login', :web => 'wiki1'
assert_redirected_to :action => 'login', :controller => 'wiki', :web => 'wiki1'
assert_nil r.cookies['web_address']
end
@ -72,7 +72,7 @@ class WikiControllerTest < ActionController::TestCase
r = process('cancel_edit', 'web' => 'wiki1', 'id' => 'Oak')
assert_redirected_to :action => 'show', :id => 'Oak'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'Oak'
assert !Page.find(@oak.id).locked?(Time.now)
end
@ -85,7 +85,7 @@ class WikiControllerTest < ActionController::TestCase
def test_edit_page_locked_page
@home.lock(Time.now, 'Locky')
process 'edit', 'web' => 'wiki1', 'id' => 'HomePage'
assert_redirected_to :action => 'locked'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'locked', :id => 'HomePage'
end
def test_edit_page_break_lock
@ -233,19 +233,19 @@ class WikiControllerTest < ActionController::TestCase
# delete extra web fixture
webs(:instiki).destroy
process('index')
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
end
def test_index_multiple_webs
@wiki.create_web('Test Wiki 2', 'wiki2')
process('index')
assert_redirected_to :action => 'web_list'
assert_redirected_to :controller => 'wiki', :action => 'web_list'
end
def test_index_multiple_webs_web_explicit
@wiki.create_web('Test Wiki 2', 'wiki2')
process('index', 'web' => 'wiki2')
assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage'
assert_redirected_to :web => 'wiki2', :controller => 'wiki', :action => 'show', :id => 'HomePage'
end
def test_index_wiki_not_initialized
@ -598,7 +598,7 @@ class WikiControllerTest < ActionController::TestCase
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page',
'author' => 'AuthorOfNewPage'
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'NewPage'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'NewPage'
assert_equal 'AuthorOfNewPage', r.cookies['author']
assert_match @eternity, r.headers["Set-Cookie"][0]
new_page = @wiki.read_page('wiki1', 'NewPage')
@ -610,7 +610,7 @@ class WikiControllerTest < ActionController::TestCase
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => "Contents of a new page\r\n\000",
'author' => 'AuthorOfNewPage'
assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'NewPage', :content => ''
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'new', :id => 'NewPage', :content => ''
assert_equal 'AuthorOfNewPage', r.cookies['author']
assert_match @eternity, r.headers["Set-Cookie"][0]
end
@ -619,7 +619,7 @@ class WikiControllerTest < ActionController::TestCase
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => "Contents of a new page\r\n&#xfffe;",
'author' => 'AuthorOfNewPage'
assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'NewPage'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'new', :id => 'NewPage', :content => ''
assert_equal 'AuthorOfNewPage', r.cookies['author']
assert_match @eternity, r.headers["Set-Cookie"][0]
end
@ -628,7 +628,7 @@ class WikiControllerTest < ActionController::TestCase
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => "Contents of a new page\r\n&#65535;",
'author' => 'AuthorOfNewPage'
assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'NewPage'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'new', :id => 'NewPage', :content => ''
assert_equal 'AuthorOfNewPage', r.cookies['author']
assert_match @eternity, r.headers["Set-Cookie"][0]
end
@ -640,7 +640,7 @@ class WikiControllerTest < ActionController::TestCase
r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage',
'author' => 'Batman'
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
assert_equal 'Batman', r.cookies['author']
home_page = @wiki.read_page('wiki1', 'HomePage')
assert_equal current_revisions+1, home_page.revisions.size
@ -656,7 +656,7 @@ class WikiControllerTest < ActionController::TestCase
r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => "Revised HomePage\000",
'author' => 'Batman'
assert_redirected_to :web => 'wiki1', :action => 'edit', :id => 'HomePage',
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'edit', :id => 'HomePage',
:content => 'HisWay would be MyWay $\sin(x)\begin{svg}<svg/>\end{svg}\includegraphics[width' +
'=3em]{foo}$ in kinda ThatWay in HisWay though MyWay \OverThere -- see SmartEng' +
'ine in that SmartEngineGUI'
@ -688,7 +688,10 @@ class WikiControllerTest < ActionController::TestCase
r = process 'save', {'web' => 'wiki1', 'id' => 'HomePage',
'content' => @home.revisions.last.content.dup + "\n Try viagra.\n",
'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/HomePage'}
assert_redirected_to :action => 'edit', :web => 'wiki1', :id => 'HomePage'
assert_redirected_to :action => 'edit', :controller => 'wiki', :web => 'wiki1', :id => 'HomePage',
:content => 'HisWay would be MyWay $\sin(x)\begin{svg}<svg/>\end{svg}\includegraphics[width=3e'+
'm]{foo}$ in kinda ThatWay in HisWay though MyWay \OverThere -- see SmartEngine in'+
" that SmartEngineGUI\n Try viagra.\n"
assert r.flash[:error].to_s == "Your edit was blocked by spam filtering"
end
@ -700,7 +703,10 @@ class WikiControllerTest < ActionController::TestCase
'content' => @home.revisions.last.content.dup,
'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/HomePage'}
assert_redirected_to :action => 'edit', :web => 'wiki1', :id => 'HomePage'
assert_redirected_to :action => 'edit', :controller => 'wiki', :web => 'wiki1', :id => 'HomePage',
:content => 'HisWay would be MyWay $\sin(x)\begin{svg}<svg/>\end{svg}\includegraphics[width=3e'+
'm]{foo}$ in kinda ThatWay in HisWay though MyWay \OverThere -- see SmartEngine in'+
' that SmartEngineGUI'
assert r.flash[:error].to_s == "You have tried to save page 'HomePage' without changing its content"
revisions_after = @home.revisions.size
@ -717,7 +723,7 @@ class WikiControllerTest < ActionController::TestCase
'content' => @liquor.revisions.last.content.dup, 'new_name' => 'booze',
'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/booze'}
assert_redirected_to :action => 'show', :web => 'wiki1', :id => 'booze'
assert_redirected_to :action => 'show', :controller => 'wiki', :web => 'wiki1', :id => 'booze'
revisions_after = @liquor.revisions.size
assert_equal revisions_before + 1, revisions_after
@ -741,20 +747,20 @@ class WikiControllerTest < ActionController::TestCase
def test_save_invalid_author_name
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page',
'author' => 'foo.bar'
assert_redirected_to :action => 'new', :web => 'wiki1', :id => 'NewPage'
assert_redirected_to :action => 'new', :controller => 'wiki', :web => 'wiki1', :id => 'NewPage'
assert r.flash[:error].to_s == 'Your name cannot contain a "."'
r = process 'save', 'web' => 'wiki1', 'id' => 'AnotherPage', 'content' => 'Contents of a new page',
'author' => "\000"
assert_redirected_to :action => 'new', :web => 'wiki1', :id => 'AnotherPage'
assert_redirected_to :action => 'new', :controller => 'wiki', :web => 'wiki1', :id => 'AnotherPage'
assert r.flash[:error].to_s == "Your name was not valid utf-8"
end
def test_search
r = process 'search', 'web' => 'wiki1', 'query' => '\s[A-Z]ak'
assert_redirected_to :action => 'show', :id => 'Oak'
assert_redirected_to :web => 'wiki1', :action => 'show', :controller => 'wiki', :id => 'Oak'
end
def test_search_multiple_results
@ -873,7 +879,7 @@ class WikiControllerTest < ActionController::TestCase
def test_show_page_nonexistant_page
process('show', 'id' => 'UnknownPage', 'web' => 'wiki1')
assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'UnknownPage'
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'new', :id => 'UnknownPage'
end
def test_show_no_page

View file

@ -1,3 +1,7 @@
*2.3.3 (July 12, 2009)*
* No changes, just a version bump.
*2.3.2 [Final] (March 15, 2009)*
* Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones]

View file

@ -4,7 +4,6 @@ require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/sshpublisher'
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
@ -55,7 +54,7 @@ spec = Gem::Specification.new do |s|
s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 2.3.2' + PKG_BUILD)
s.add_dependency('actionpack', '= 2.3.3' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'
@ -76,12 +75,14 @@ end
desc "Publish the API documentation"
task :pgem => [:package] do
require 'rake/contrib/sshpublisher'
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload
end

View file

@ -674,7 +674,7 @@ module ActionMailer #:nodoc:
def perform_delivery_smtp(mail)
destinations = mail.destinations
mail.ready_to_send
sender = (mail['return-path'] && mail['return-path'].spec) || mail.from
sender = (mail['return-path'] && mail['return-path'].spec) || mail['from']
smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
smtp.enable_starttls_auto if smtp_settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto)

View file

@ -2,7 +2,7 @@ module ActionMailer
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 2
TINY = 3
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -1,9 +1,6 @@
require 'rubygems'
require 'test/unit'
gem 'mocha', '>= 0.9.5'
require 'mocha'
$:.unshift "#{File.dirname(__FILE__)}/../lib"
$:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
$:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib"

View file

@ -1,3 +1,8 @@
*2.3.3 (July 12, 2009)*
* Fixed that TestResponse.cookies was returning cookies unescaped #1867 [Doug McInnes]
*2.3.2 [Final] (March 15, 2009)*
* Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #<Post:0x23150b8>") [DHH]

View file

@ -4,7 +4,6 @@ require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/sshpublisher'
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
@ -80,7 +79,8 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
s.requirements << 'none'
s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD)
s.add_dependency('activesupport', '= 2.3.3' + PKG_BUILD)
s.add_dependency('rack', '~> 1.0.0')
s.require_path = 'lib'
s.autorequire = 'action_controller'
@ -136,12 +136,14 @@ task :update_js => [ :update_scriptaculous ]
desc "Publish the API documentation"
task :pgem => [:package] do
require 'rake/contrib/sshpublisher'
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
end

View file

@ -32,10 +32,10 @@ rescue LoadError
end
begin
gem 'rack', '~> 1.0.0'
gem 'rack', '~> 1.1.0'
require 'rack'
rescue Gem::LoadError
require 'action_controller/vendor/rack-1.0/rack'
require 'action_controller/vendor/rack-1.1.pre/rack'
end
module ActionController
@ -45,7 +45,6 @@ module ActionController
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
end
autoload :AbstractRequest, 'action_controller/request'
autoload :Base, 'action_controller/base'
autoload :Benchmarking, 'action_controller/benchmarking'
autoload :Caching, 'action_controller/caching'

View file

@ -63,7 +63,10 @@ module ActionController
# Support partial arguments for hash redirections
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
if options.all? {|(key, value)| @response.redirected_to[key] == value}
::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", caller)
return true
end
end
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
@ -82,6 +85,9 @@ module ActionController
# # assert that the "new" view template was rendered
# assert_template "new"
#
# # assert that the "new" view template was rendered with Symbol
# assert_template :new
#
# # assert that the "_customer" partial was rendered twice
# assert_template :partial => '_customer', :count => 2
#
@ -91,7 +97,7 @@ module ActionController
def assert_template(options = {}, message = nil)
clean_backtrace do
case options
when NilClass, String
when NilClass, String, Symbol
rendered = @response.rendered[:template].to_s
msg = build_message(message,
"expecting <?> but rendering with <?>",
@ -100,7 +106,7 @@ module ActionController
if options.nil?
@response.rendered[:template].blank?
else
rendered.to_s.match(options)
rendered.to_s.match(options.to_s)
end
end
when Hash
@ -123,6 +129,8 @@ module ActionController
assert @response.rendered[:partials].empty?,
"Expected no partials to be rendered"
end
else
raise ArgumentError
end
end
end

View file

@ -491,6 +491,10 @@ module ActionController #:nodoc:
filtered_parameters[key] = '[FILTERED]'
elsif value.is_a?(Hash)
filtered_parameters[key] = filter_parameters(value)
elsif value.is_a?(Array)
filtered_parameters[key] = value.collect do |item|
filter_parameters(item)
end
elsif block_given?
key = key.dup
value = value.dup if value
@ -950,8 +954,9 @@ module ActionController #:nodoc:
response.content_type ||= Mime::JS
render_for_text(js, options[:status])
elsif json = options[:json]
json = json.to_json unless json.is_a?(String)
elsif options.include?(:json)
json = options[:json]
json = ActiveSupport::JSON.encode(json) unless json.is_a?(String)
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
response.content_type ||= Mime::JSON
render_for_text(json, options[:status])

View file

@ -27,7 +27,7 @@ module ActionController #:nodoc:
autoload :Actions, 'action_controller/caching/actions'
autoload :Fragments, 'action_controller/caching/fragments'
autoload :Pages, 'action_controller/caching/pages'
autoload :Sweeper, 'action_controller/caching/sweeping'
autoload :Sweeper, 'action_controller/caching/sweeper'
autoload :Sweeping, 'action_controller/caching/sweeping'
def self.included(base) #:nodoc:

View file

@ -61,7 +61,9 @@ module ActionController #:nodoc:
filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
around_filter(cache_filter, filter_options)
around_filter(filter_options) do |controller, action|
cache_filter.filter(controller, action)
end
end
end
@ -83,6 +85,12 @@ module ActionController #:nodoc:
@options = options
end
def filter(controller, action)
should_continue = before(controller)
action.call if should_continue
after(controller)
end
def before(controller)
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
if cache = controller.read_fragment(cache_path.path, @options[:store_options])

View file

@ -0,0 +1,45 @@
require 'active_record'
module ActionController #:nodoc:
module Caching
class Sweeper < ActiveRecord::Observer #:nodoc:
attr_accessor :controller
def before(controller)
self.controller = controller
callback(:before) if controller.perform_caching
end
def after(controller)
callback(:after) if controller.perform_caching
# Clean up, so that the controller can be collected after this request
self.controller = nil
end
protected
# gets the action cache path for the given options.
def action_path_for(options)
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
end
# Retrieve instance variables set in the controller.
def assigns(key)
controller.instance_variable_get("@#{key}")
end
private
def callback(timing)
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
__send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
end
def method_missing(method, *arguments, &block)
return if @controller.nil?
@controller.__send__(method, *arguments, &block)
end
end
end
end

View file

@ -51,47 +51,5 @@ module ActionController #:nodoc:
end
end
end
if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
class Sweeper < ActiveRecord::Observer #:nodoc:
attr_accessor :controller
def before(controller)
self.controller = controller
callback(:before) if controller.perform_caching
end
def after(controller)
callback(:after) if controller.perform_caching
# Clean up, so that the controller can be collected after this request
self.controller = nil
end
protected
# gets the action cache path for the given options.
def action_path_for(options)
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
end
# Retrieve instance variables set in the controller.
def assigns(key)
controller.instance_variable_get("@#{key}")
end
private
def callback(timing)
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
__send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
end
def method_missing(method, *arguments, &block)
return if @controller.nil?
@controller.__send__(method, *arguments, &block)
end
end
end
end
end

View file

@ -1,4 +1,19 @@
require 'erb'
module ActionController
# The Failsafe middleware is usually the top-most middleware in the Rack
# middleware chain. It returns the underlying middleware's response, but if
# the underlying middle raises an exception then Failsafe will log the
# exception into the Rails log file, and will attempt to return an error
# message response.
#
# Failsafe is a last resort for logging errors and for telling the HTTP
# client that something went wrong. Do not confuse this with the
# ActionController::Rescue module, which is responsible for catching
# exceptions at deeper levels. Unlike Failsafe, which is as simple as
# possible, Rescue provides features that allow developers to hook into
# the error handling logic, and can customize the error message response
# based on the HTTP client's IP.
class Failsafe
cattr_accessor :error_file_path
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
@ -11,7 +26,7 @@ module ActionController
@app.call(env)
rescue Exception => exception
# Reraise exception in test environment
if env["rack.test"]
if defined?(Rails) && Rails.env.test?
raise exception
else
failsafe_response(exception)
@ -21,18 +36,37 @@ module ActionController
private
def failsafe_response(exception)
log_failsafe_exception(exception)
[500, {'Content-Type' => 'text/html'}, failsafe_response_body]
[500, {'Content-Type' => 'text/html'}, [failsafe_response_body]]
rescue Exception => failsafe_error # Logger or IO errors
$stderr.puts "Error during failsafe response: #{failsafe_error}"
end
def failsafe_response_body
error_path = "#{self.class.error_file_path}/500.html"
if File.exist?(error_path)
File.read(error_path)
error_template_path = "#{self.class.error_file_path}/500.html"
if File.exist?(error_template_path)
begin
result = render_template(error_template_path)
rescue Exception
result = nil
end
else
"<html><body><h1>500 Internal Server Error</h1></body></html>"
result = nil
end
if result.nil?
result = "<html><body><h1>500 Internal Server Error</h1>" <<
"If you are the administrator of this website, then please read this web " <<
"application's log file to find out what went wrong.</body></html>"
end
result
end
# The default 500.html uses the h() method.
def h(text) # :nodoc:
ERB::Util.h(text)
end
def render_template(filename)
ERB.new(File.read(filename)).result(binding)
end
def log_failsafe_exception(exception)

View file

@ -120,6 +120,11 @@ module ActionController #:nodoc:
(@used.keys - keys).each{ |k| @used.delete(k) }
end
def store(session, key = "flash")
return if self.empty?
session[key] = self
end
private
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
# use() # marks the entire flash as used
@ -139,7 +144,10 @@ module ActionController #:nodoc:
protected
def perform_action_with_flash
perform_action_without_flash
remove_instance_variable(:@_flash) if defined? @_flash
if defined? @_flash
@_flash.store(session)
remove_instance_variable(:@_flash)
end
end
def reset_session_with_flash
@ -151,8 +159,8 @@ module ActionController #:nodoc:
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
def flash #:doc:
unless defined? @_flash
@_flash = session["flash"] ||= FlashHash.new
if !defined?(@_flash)
@_flash = session["flash"] || FlashHash.new
@_flash.sweep
end

View file

@ -183,7 +183,7 @@ module ActionController
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
end
# Raises error unless the request credentials response value matches the expected value.
# Returns false unless the request credentials response value matches the expected value.
# First try the password as a ha1 digest password. If this fails, then try it as a plain
# text password.
def validate_digest_response(request, realm, &password_procedure)
@ -192,9 +192,12 @@ module ActionController
if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque]
password = password_procedure.call(credentials[:username])
return false unless password
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
[true, false].any? do |password_is_ha1|
expected = expected_response(request.env['REQUEST_METHOD'], request.env['REQUEST_URI'], credentials, password, password_is_ha1)
expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1)
expected == credentials[:response]
end
end

View file

@ -292,9 +292,7 @@ module ActionController
"rack.errors" => StringIO.new,
"rack.multithread" => true,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.test" => true
"rack.run_once" => false
)
(headers || {}).each do |key, value|
@ -311,12 +309,7 @@ module ActionController
ActionController::Base.clear_last_instantiation!
app = @application
# Rack::Lint doesn't accept String headers or bodies in Ruby 1.9
unless RUBY_VERSION >= '1.9.0' && Rack.release <= '0.9.0'
app = Rack::Lint.new(app)
end
app = Rack::Lint.new(@application)
status, headers, body = app.call(env)
@request_count += 1
@ -333,7 +326,7 @@ module ActionController
end
@body = ""
if body.is_a?(String)
if body.respond_to?(:to_str)
@body << body
else
body.each { |part| @body << part }
@ -416,7 +409,7 @@ module ActionController
def multipart_requestify(params, first=true)
returning Hash.new do |p|
params.each do |key, value|
k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]"
k = first ? key.to_s : "[#{key.to_s}]"
if Hash === value
multipart_requestify(value, false).each do |subkey, subvalue|
p[k + subkey] = subvalue

View file

@ -7,7 +7,6 @@ use "ActionController::Failsafe"
use lambda { ActionController::Base.session_store },
lambda { ActionController::Base.session_options }
use "ActionController::RewindableInput"
use "ActionController::ParamsParser"
use "Rack::MethodOverride"
use "Rack::Head"

View file

@ -1,14 +1,45 @@
module ActionController
class Reloader
class BodyWrapper
def initialize(body)
@body = body
end
def close
@body.close if @body.respond_to?(:close)
ensure
Dispatcher.cleanup_application
end
def method_missing(*args, &block)
@body.send(*args, &block)
end
def respond_to?(symbol, include_private = false)
symbol == :close || @body.respond_to?(symbol, include_private)
end
end
def initialize(app)
@app = app
end
def call(env)
Dispatcher.reload_application
@app.call(env)
ensure
Dispatcher.cleanup_application
status, headers, body = @app.call(env)
# We do not want to call 'cleanup_application' in an ensure block
# because the returned Rack response body may lazily generate its data. This
# is for example the case if one calls
#
# render :text => lambda { ... code here which refers to application models ... }
#
# in an ActionController.
#
# Instead, we will want to cleanup the application code after the request is
# completely finished. So we wrap the body in a BodyWrapper class so that
# when the Rack handler calls #close during the end of the request, we get to
# run our cleanup code.
[status, headers, BodyWrapper.new(body)]
end
end
end

View file

@ -95,6 +95,10 @@ module ActionController
end
end
def media_type
content_type.to_s
end
# Returns the accepted MIME type for the request.
def accepts
@accepts ||= begin
@ -383,7 +387,7 @@ EOM
alias_method :params, :parameters
def path_parameters=(parameters) #:nodoc:
@env["rack.routing_args"] = parameters
@env["action_controller.request.path_parameters"] = parameters
@symbolized_path_parameters = @parameters = nil
end
@ -399,7 +403,7 @@ EOM
#
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
@env["rack.routing_args"] ||= {}
@env["action_controller.request.path_parameters"] ||= {}
end
# The request body is an IO input stream. If the RAW_POST_DATA environment

View file

@ -151,8 +151,8 @@ module ActionController # :nodoc:
if @body.respond_to?(:call)
@writer = lambda { |x| callback.call(x) }
@body.call(self, self)
elsif @body.is_a?(String)
@body.each_line(&callback)
elsif @body.respond_to?(:to_str)
yield @body
else
@body.each(&callback)
end

View file

@ -1,28 +0,0 @@
module ActionController
class RewindableInput
class RewindableIO < ActiveSupport::BasicObject
def initialize(io)
@io = io
@rewindable = io.is_a?(::StringIO)
end
def method_missing(method, *args, &block)
unless @rewindable
@io = ::StringIO.new(@io.read)
@rewindable = true
end
@io.__send__(method, *args, &block)
end
end
def initialize(app)
@app = app
end
def call(env)
env['rack.input'] = RewindableIO.new(env['rack.input'])
@app.call(env)
end
end
end

View file

@ -305,6 +305,7 @@ module ActionController
end
def add_route(path, options = {})
options.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) }
route = builder.build(path, options)
routes << route
route
@ -436,7 +437,7 @@ module ActionController
def recognize(request)
params = recognize_path(request.path, extract_request_environment(request))
request.path_parameters = params.with_indifferent_access
"#{params[:controller].camelize}Controller".constantize
"#{params[:controller].to_s.camelize}Controller".constantize
end
def recognize_path(path, environment={})

View file

@ -161,7 +161,7 @@ module ActionController #:nodoc:
content_type = content_type.to_s.strip # fixes a problem with extra '\r' with some browsers
headers.merge!(
'Content-Length' => options[:length],
'Content-Length' => options[:length].to_s,
'Content-Type' => content_type,
'Content-Disposition' => disposition,
'Content-Transfer-Encoding' => 'binary'

View file

@ -1,3 +1,4 @@
require 'rack/session/abstract/id'
module ActionController #:nodoc:
class TestRequest < Request #:nodoc:
attr_accessor :cookies, :session_options
@ -13,6 +14,8 @@ module ActionController #:nodoc:
@query_parameters = {}
@session = TestSession.new
default_rack_options = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
@session_options ||= {:id => generate_sid(default_rack_options[:sidbits])}.merge(default_rack_options)
initialize_default_values
initialize_containers
@ -110,6 +113,7 @@ module ActionController #:nodoc:
end
def recycle!
@env["action_controller.request.request_parameters"] = {}
self.query_parameters = {}
self.path_parameters = {}
@headers, @request_method, @accepts, @content_type = nil, nil, nil, nil
@ -120,6 +124,10 @@ module ActionController #:nodoc:
end
private
def generate_sid(sidbits)
"%0#{sidbits / 4}x" % rand(2**sidbits - 1)
end
def initialize_containers
@cookies = {}
end
@ -250,7 +258,7 @@ module ActionController #:nodoc:
def cookies
cookies = {}
Array(headers['Set-Cookie']).each do |cookie|
key, value = cookie.split(";").first.split("=")
key, value = cookie.split(";").first.split("=").map {|val| Rack::Utils.unescape(val)}
cookies[key] = value
end
cookies

View file

@ -1,64 +0,0 @@
require 'thread'
module Rack
# Rack::Reloader checks on every request, but at most every +secs+
# seconds, if a file loaded changed, and reloads it, logging to
# rack.errors.
#
# It is recommended you use ShowExceptions to catch SyntaxErrors etc.
class Reloader
def initialize(app, secs=10)
@app = app
@secs = secs # reload every @secs seconds max
@last = Time.now
end
def call(env)
if Time.now > @last + @secs
Thread.exclusive {
reload!(env['rack.errors'])
@last = Time.now
}
end
@app.call(env)
end
def reload!(stderr=$stderr)
need_reload = $LOADED_FEATURES.find_all { |loaded|
begin
if loaded =~ /\A[.\/]/ # absolute filename or 1.9
abs = loaded
else
abs = $LOAD_PATH.map { |path| ::File.join(path, loaded) }.
find { |file| ::File.exist? file }
end
if abs
::File.mtime(abs) > @last - @secs rescue false
else
false
end
end
}
need_reload.each { |l|
$LOADED_FEATURES.delete l
}
need_reload.each { |to_load|
begin
if require to_load
stderr.puts "#{self.class}: reloaded `#{to_load}'"
end
rescue LoadError, SyntaxError => e
raise e # Possibly ShowExceptions
end
}
stderr.flush
need_reload
end
end
end

View file

@ -3,7 +3,8 @@
# Rack is freely distributable under the terms of an MIT-style license.
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
$:.unshift(File.expand_path(File.dirname(__FILE__)))
path = File.expand_path(File.dirname(__FILE__))
$:.unshift(path) unless $:.include?(path)
# The Rack main module, serving as a namespace for all core Rack
@ -14,7 +15,7 @@ $:.unshift(File.expand_path(File.dirname(__FILE__)))
module Rack
# The Rack protocol version number implemented.
VERSION = [0,1]
VERSION = [1,0]
# Return the Rack protocol version as a dotted string.
def self.version
@ -23,7 +24,7 @@ module Rack
# Return the Rack release as a dotted string.
def self.release
"1.0 bundled"
"1.0"
end
autoload :Builder, "rack/builder"

View file

@ -26,6 +26,8 @@ module Rack
headers = Utils::HeaderHash.new(headers)
if etag_matches?(env, headers) || modified_since?(env, headers)
status = 304
headers.delete('Content-Type')
headers.delete('Content-Length')
body = []
end
[status, headers, body]

View file

@ -33,17 +33,15 @@ module Rack
case encoding
when "gzip"
headers['Content-Encoding'] = "gzip"
headers.delete('Content-Length')
mtime = headers.key?("Last-Modified") ?
Time.httpdate(headers["Last-Modified"]) : Time.now
body = self.class.gzip(body, mtime)
size = Rack::Utils.bytesize(body)
headers = headers.merge("Content-Encoding" => "gzip", "Content-Length" => size.to_s)
[status, headers, [body]]
[status, headers, GzipStream.new(body, mtime)]
when "deflate"
body = self.class.deflate(body)
size = Rack::Utils.bytesize(body)
headers = headers.merge("Content-Encoding" => "deflate", "Content-Length" => size.to_s)
[status, headers, [body]]
headers['Content-Encoding'] = "deflate"
headers.delete('Content-Length')
[status, headers, DeflateStream.new(body)]
when "identity"
[status, headers, body]
when nil
@ -52,34 +50,47 @@ module Rack
end
end
def self.gzip(body, mtime)
io = StringIO.new
gzip = Zlib::GzipWriter.new(io)
gzip.mtime = mtime
class GzipStream
def initialize(body, mtime)
@body = body
@mtime = mtime
end
# TODO: Add streaming
body.each { |part| gzip << part }
def each(&block)
@writer = block
gzip =::Zlib::GzipWriter.new(self)
gzip.mtime = @mtime
@body.each { |part| gzip << part }
@body.close if @body.respond_to?(:close)
gzip.close
@writer = nil
end
gzip.close
return io.string
def write(data)
@writer.call(data)
end
end
DEFLATE_ARGS = [
Zlib::DEFAULT_COMPRESSION,
# drop the zlib header which causes both Safari and IE to choke
-Zlib::MAX_WBITS,
Zlib::DEF_MEM_LEVEL,
Zlib::DEFAULT_STRATEGY
]
class DeflateStream
DEFLATE_ARGS = [
Zlib::DEFAULT_COMPRESSION,
# drop the zlib header which causes both Safari and IE to choke
-Zlib::MAX_WBITS,
Zlib::DEF_MEM_LEVEL,
Zlib::DEFAULT_STRATEGY
]
# Loosely based on Mongrel's Deflate handler
def self.deflate(body)
deflater = Zlib::Deflate.new(*DEFLATE_ARGS)
def initialize(body)
@body = body
end
# TODO: Add streaming
body.each { |part| deflater << part }
return deflater.finish
def each
deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
@body.each { |part| yield deflater.deflate(part) }
@body.close if @body.respond_to?(:close)
yield deflater.finish
nil
end
end
end
end

View file

@ -10,16 +10,37 @@ module Rack
module Handler
def self.get(server)
return unless server
server = server.to_s
if klass = @handlers[server]
obj = Object
klass.split("::").each { |x| obj = obj.const_get(x) }
obj
else
Rack::Handler.const_get(server.capitalize)
try_require('rack/handler', server)
const_get(server)
end
end
# Transforms server-name constants to their canonical form as filenames,
# then tries to require them but silences the LoadError if not found
#
# Naming convention:
#
# Foo # => 'foo'
# FooBar # => 'foo_bar.rb'
# FooBAR # => 'foobar.rb'
# FOObar # => 'foobar.rb'
# FOOBAR # => 'foobar.rb'
# FooBarBaz # => 'foo_bar_baz.rb'
def self.try_require(prefix, const_name)
file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }.
gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
require(::File.join(prefix, file))
rescue LoadError
end
def self.register(server, klass)
@handlers ||= {}
@handlers[server] = klass

View file

@ -15,7 +15,7 @@ module Rack
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({"rack.version" => [0,1],
env.update({"rack.version" => [1,0],
"rack.input" => $stdin,
"rack.errors" => $stderr,

View file

@ -1,6 +1,17 @@
require 'fcgi'
require 'socket'
require 'rack/content_length'
require 'rack/rewindable_input'
class FCGI::Stream
alias _rack_read_without_buffer read
def read(n, buffer=nil)
buf = _rack_read_without_buffer n
buffer.replace(buf.to_s) if buffer
buf
end
end
module Rack
module Handler
@ -13,34 +24,18 @@ module Rack
}
end
module ProperStream # :nodoc:
def each # This is missing by default.
while line = gets
yield line
end
end
def read(*args)
if args.empty?
super || "" # Empty string on EOF.
else
super
end
end
end
def self.serve(request, app)
app = Rack::ContentLength.new(app)
env = request.env
env.delete "HTTP_CONTENT_LENGTH"
request.in.extend ProperStream
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({"rack.version" => [0,1],
"rack.input" => request.in,
rack_input = RewindableInput.new(request.in)
env.update({"rack.version" => [1,0],
"rack.input" => rack_input,
"rack.errors" => request.err,
"rack.multithread" => false,
@ -57,12 +52,16 @@ module Rack
env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
status, headers, body = app.call(env)
begin
send_headers request.out, status, headers
send_body request.out, body
status, headers, body = app.call(env)
begin
send_headers request.out, status, headers
send_body request.out, body
ensure
body.close if body.respond_to? :close
end
ensure
body.close if body.respond_to? :close
rack_input.close
request.finish
end
end

View file

@ -15,7 +15,7 @@ module Rack
env = ENV.to_hash
env.delete "HTTP_CONTENT_LENGTH"
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({"rack.version" => [0,1],
env.update({"rack.version" => [1,0],
"rack.input" => StringIO.new($stdin.read.to_s),
"rack.errors" => $stderr,
"rack.multithread" => false,

View file

@ -10,7 +10,7 @@ module Rack
server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0',
options[:Port] || 8080)
# Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
# Use is similar to #run, replacing the app argument with a hash of
# Use is similar to #run, replacing the app argument with a hash of
# { path=>app, ... } or an instance of Rack::URLMap.
if options[:map]
if app.is_a? Hash
@ -45,7 +45,7 @@ module Rack
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({"rack.version" => [0,1],
env.update({"rack.version" => [1,0],
"rack.input" => request.body || StringIO.new(""),
"rack.errors" => $stderr,

View file

@ -7,14 +7,14 @@ module Rack
module Handler
class SCGI < ::SCGI::Processor
attr_accessor :app
def self.run(app, options=nil)
new(options.merge(:app=>app,
:host=>options[:Host],
:port=>options[:Port],
:socket=>options[:Socket])).listen
end
def initialize(settings = {})
@app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app]))
@log = Object.new
@ -22,7 +22,7 @@ module Rack
def @log.error(*args); end
super(settings)
end
def process_request(request, input_body, socket)
env = {}.replace(request)
env.delete "HTTP_CONTENT_TYPE"
@ -32,7 +32,7 @@ module Rack
env["PATH_INFO"] = env["REQUEST_PATH"]
env["QUERY_STRING"] ||= ""
env["SCRIPT_NAME"] = ""
env.update({"rack.version" => [0,1],
env.update({"rack.version" => [1,0],
"rack.input" => StringIO.new(input_body),
"rack.errors" => $stderr,

View file

@ -23,7 +23,7 @@ module Rack
env = req.meta_vars
env.delete_if { |k, v| v.nil? }
env.update({"rack.version" => [0,1],
env.update({"rack.version" => [1,0],
"rack.input" => StringIO.new(req.body.to_s),
"rack.errors" => $stderr,

View file

@ -88,9 +88,9 @@ module Rack
## within the application. This may be an
## empty string, if the request URL targets
## the application root and does not have a
## trailing slash. This information should be
## decoded by the server if it comes from a
## URL.
## trailing slash. This value may be
## percent-encoded when I originating from
## a URL.
## <tt>QUERY_STRING</tt>:: The portion of the request URL that
## follows the <tt>?</tt>, if any. May be
@ -111,19 +111,48 @@ module Rack
## In addition to this, the Rack environment must include these
## Rack-specific variables:
## <tt>rack.version</tt>:: The Array [0,1], representing this version of Rack.
## <tt>rack.version</tt>:: The Array [1,0], representing this version of Rack.
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
## <tt>rack.input</tt>:: See below, the input stream.
## <tt>rack.errors</tt>:: See below, the error stream.
## <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
## <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
## <tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
##
## Additional environment specifications have approved to
## standardized middleware APIs. None of these are required to
## be implemented by the server.
## <tt>rack.session</tt>:: A hash like interface for storing request session data.
## The store must implement:
if session = env['rack.session']
## store(key, value) (aliased as []=);
assert("session #{session.inspect} must respond to store and []=") {
session.respond_to?(:store) && session.respond_to?(:[]=)
}
## fetch(key, default = nil) (aliased as []);
assert("session #{session.inspect} must respond to fetch and []") {
session.respond_to?(:fetch) && session.respond_to?(:[])
}
## delete(key);
assert("session #{session.inspect} must respond to delete") {
session.respond_to?(:delete)
}
## clear;
assert("session #{session.inspect} must respond to clear") {
session.respond_to?(:clear)
}
end
## The server or the application can store their own data in the
## environment, too. The keys must contain at least one dot,
## and should be prefixed uniquely. The prefix <tt>rack.</tt>
## is reserved for use with the Rack core distribution and must
## not be used otherwise.
## is reserved for use with the Rack core distribution and other
## accepted specifications and must not be used otherwise.
##
%w[REQUEST_METHOD SERVER_NAME SERVER_PORT
@ -202,9 +231,12 @@ module Rack
end
## === The Input Stream
##
## The input stream is an IO-like object which contains the raw HTTP
## POST data. If it is a file then it must be opened in binary mode.
def check_input(input)
## The input stream must respond to +gets+, +each+ and +read+.
[:gets, :each, :read].each { |method|
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
[:gets, :each, :read, :rewind].each { |method|
assert("rack.input #{input} does not respond to ##{method}") {
input.respond_to? method
}
@ -222,10 +254,6 @@ module Rack
@input.size
end
def rewind
@input.rewind
end
## * +gets+ must be called without arguments and return a string,
## or +nil+ on EOF.
def gets(*args)
@ -237,21 +265,44 @@ module Rack
v
end
## * +read+ must be called without or with one integer argument
## and return a string, or +nil+ on EOF.
## * +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
## If given, +length+ must be an non-negative Integer (>= 0) or +nil+, and +buffer+ must
## be a String and may not be nil. If +length+ is given and not nil, then this method
## reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
## then this method reads all data until EOF.
## When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
## if +length+ is not given or is nil.
## If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
## newly created String object.
def read(*args)
assert("rack.input#read called with too many arguments") {
args.size <= 1
args.size <= 2
}
if args.size == 1
assert("rack.input#read called with non-integer argument") {
args.first.kind_of? Integer
if args.size >= 1
assert("rack.input#read called with non-integer and non-nil length") {
args.first.kind_of?(Integer) || args.first.nil?
}
assert("rack.input#read called with a negative length") {
args.first.nil? || args.first >= 0
}
end
if args.size >= 2
assert("rack.input#read called with non-String buffer") {
args[1].kind_of?(String)
}
end
v = @input.read(*args)
assert("rack.input#read didn't return a String") {
assert("rack.input#read didn't return nil or a String") {
v.nil? or v.instance_of? String
}
if args[0].nil?
assert("rack.input#read(nil) returned nil on EOF") {
!v.nil?
}
end
v
end
@ -266,6 +317,23 @@ module Rack
}
end
## * +rewind+ must be called without arguments. It rewinds the input
## stream back to the beginning. It must not raise Errno::ESPIPE:
## that is, it may not be a pipe or a socket. Therefore, handler
## developers must buffer the input data into some rewindable object
## if the underlying input stream is not rewindable.
def rewind(*args)
assert("rack.input#rewind called with arguments") { args.size == 0 }
assert("rack.input#rewind raised Errno::ESPIPE") {
begin
@input.rewind
true
rescue Errno::ESPIPE
false
end
}
end
## * +close+ must never be called on the input stream.
def close(*args)
assert("rack.input#close must not be called") { false }
@ -316,13 +384,14 @@ module Rack
## === The Status
def check_status(status)
## The status, if parsed as integer (+to_i+), must be greater than or equal to 100.
## This is an HTTP status. When parsed as integer (+to_i+), it must be
## greater than or equal to 100.
assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
end
## === The Headers
def check_headers(header)
## The header must respond to each, and yield values of key and value.
## The header must respond to +each+, and yield values of key and value.
assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
header.respond_to? :each
}
@ -344,7 +413,8 @@ module Rack
## The values of the header must be Strings,
assert("a header value must be a String, but the value of " +
"'#{key}' is a #{value.class}") { value.kind_of? String }
## consisting of lines (for multiple header values) seperated by "\n".
## consisting of lines (for multiple header values, e.g. multiple
## <tt>Set-Cookie</tt> values) seperated by "\n".
value.split("\n").each { |item|
## The lines must not contain characters below 037.
assert("invalid header value #{key}: #{item.inspect}") {
@ -416,7 +486,7 @@ module Rack
## === The Body
def each
@closed = false
## The Body must respond to #each
## The Body must respond to +each+
@body.each { |part|
## and must only yield String values.
assert("Body yielded non-string value #{part.inspect}") {
@ -425,14 +495,19 @@ module Rack
yield part
}
##
## If the Body responds to #close, it will be called after iteration.
## The Body itself should not be an instance of String, as this will
## break in Ruby 1.9.
##
## If the Body responds to +close+, it will be called after iteration.
# XXX howto: assert("Body has not been closed") { @closed }
##
## If the Body responds to #to_path, it must return a String
## If the Body responds to +to_path+, it must return a String
## identifying the location of a file whose contents are identical
## to that produced by calling #each.
## to that produced by calling +each+; this may be used by the
## server as an alternative, possibly more efficient way to
## transport the response.
if @body.respond_to?(:to_path)
assert("The file identified by body.to_path does not exist") {

View file

@ -40,7 +40,7 @@ module Rack
end
DEFAULT_ENV = {
"rack.version" => [0,1],
"rack.version" => [1,0],
"rack.input" => StringIO.new,
"rack.errors" => StringIO.new,
"rack.multithread" => true,
@ -73,14 +73,17 @@ module Rack
# Return the Rack environment used for a request to +uri+.
def self.env_for(uri="", opts={})
uri = URI(uri)
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
env = DEFAULT_ENV.dup
env["REQUEST_METHOD"] = opts[:method] || "GET"
env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET"
env["SERVER_NAME"] = uri.host || "example.org"
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
env["QUERY_STRING"] = uri.query.to_s
env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
env["rack.url_scheme"] = uri.scheme || "http"
env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
env["SCRIPT_NAME"] = opts[:script_name] || ""
@ -90,6 +93,27 @@ module Rack
env["rack.errors"] = StringIO.new
end
if params = opts[:params]
if env["REQUEST_METHOD"] == "GET"
params = Utils.parse_nested_query(params) if params.is_a?(String)
params.update(Utils.parse_nested_query(env["QUERY_STRING"]))
env["QUERY_STRING"] = Utils.build_nested_query(params)
elsif !opts.has_key?(:input)
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
if params.is_a?(Hash)
if data = Utils::Multipart.build_multipart(params)
opts[:input] = data
opts["CONTENT_LENGTH"] ||= data.length.to_s
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}"
else
opts[:input] = Utils.build_nested_query(params)
end
else
opts[:input] = params
end
end
end
opts[:input] ||= ""
if String === opts[:input]
env["rack.input"] = StringIO.new(opts[:input])
@ -125,7 +149,7 @@ module Rack
@body = ""
body.each { |part| @body << part }
@errors = errors.string
@errors = errors.string if errors.respond_to?(:string)
end
# Status

View file

@ -0,0 +1,106 @@
# Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
# All files in this distribution are subject to the terms of the Ruby license.
require 'pathname'
module Rack
# High performant source reloader
#
# This class acts as Rack middleware.
#
# What makes it especially suited for use in a production environment is that
# any file will only be checked once and there will only be made one system
# call stat(2).
#
# Please note that this will not reload files in the background, it does so
# only when actively called.
#
# It is performing a check/reload cycle at the start of every request, but
# also respects a cool down time, during which nothing will be done.
class Reloader
def initialize(app, cooldown = 10, backend = Stat)
@app = app
@cooldown = cooldown
@last = (Time.now - cooldown)
@cache = {}
@mtimes = {}
extend backend
end
def call(env)
if @cooldown and Time.now > @last + @cooldown
if Thread.list.size > 1
Thread.exclusive{ reload! }
else
reload!
end
@last = Time.now
end
@app.call(env)
end
def reload!(stderr = $stderr)
rotation do |file, mtime|
previous_mtime = @mtimes[file] ||= mtime
safe_load(file, mtime, stderr) if mtime > previous_mtime
end
end
# A safe Kernel::load, issuing the hooks depending on the results
def safe_load(file, mtime, stderr = $stderr)
load(file)
stderr.puts "#{self.class}: reloaded `#{file}'"
file
rescue LoadError, SyntaxError => ex
stderr.puts ex
ensure
@mtimes[file] = mtime
end
module Stat
def rotation
files = [$0, *$LOADED_FEATURES].uniq
paths = ['./', *$LOAD_PATH].uniq
files.map{|file|
next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
found, stat = figure_path(file, paths)
next unless found and stat and mtime = stat.mtime
@cache[file] = found
yield(found, mtime)
}.compact
end
# Takes a relative or absolute +file+ name, a couple possible +paths+ that
# the +file+ might reside in. Returns the full path and File::Stat for the
# path.
def figure_path(file, paths)
found = @cache[file]
found = file if !found and Pathname.new(file).absolute?
found, stat = safe_stat(found)
return found, stat if found
paths.each do |possible_path|
path = ::File.join(possible_path, file)
found, stat = safe_stat(path)
return ::File.expand_path(found), stat if found
end
end
def safe_stat(file)
return unless file
stat = ::File.stat(file)
return file, stat if stat.file?
rescue Errno::ENOENT, Errno::ENOTDIR
@cache.delete(file) and false
end
end
end
end

View file

@ -17,7 +17,7 @@ module Rack
# The environment of the request.
attr_reader :env
def self.new(env)
def self.new(env, *args)
if self == Rack::Request
env["rack.request"] ||= super
else
@ -38,6 +38,8 @@ module Rack
def query_string; @env["QUERY_STRING"].to_s end
def content_length; @env['CONTENT_LENGTH'] end
def content_type; @env['CONTENT_TYPE'] end
def session; @env['rack.session'] ||= {} end
def session_options; @env['rack.session.options'] ||= {} end
# The media type (type/subtype) portion of the CONTENT_TYPE header
# without any media type parameters. e.g., when CONTENT_TYPE is
@ -46,7 +48,7 @@ module Rack
# For more information on the use of media types in HTTP, see:
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
def media_type
content_type && content_type.split(/\s*[;,]\s*/, 2)[0].downcase
content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
end
# The media type parameters provided in CONTENT_TYPE as a Hash, or
@ -92,6 +94,14 @@ module Rack
'multipart/form-data'
]
# The set of media-types. Requests that do not indicate
# one of the media types presents in this list will not be eligible
# for param parsing like soap attachments or generic multiparts
PARSEABLE_DATA_MEDIA_TYPES = [
'multipart/related',
'multipart/mixed'
]
# Determine whether the request body contains form-data by checking
# the request media_type against registered form-data media-types:
# "application/x-www-form-urlencoded" and "multipart/form-data". The
@ -101,6 +111,12 @@ module Rack
FORM_DATA_MEDIA_TYPES.include?(media_type)
end
# Determine whether the request body contains data by checking
# the request media_type against registered parse-data media-types
def parseable_data?
PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
end
# Returns the data recieved in the query string.
def GET
if @env["rack.request.query_string"] == query_string
@ -119,7 +135,7 @@ module Rack
def POST
if @env["rack.request.form_input"].eql? @env["rack.input"]
@env["rack.request.form_hash"]
elsif form_data?
elsif form_data? || parseable_data?
@env["rack.request.form_input"] = @env["rack.input"]
unless @env["rack.request.form_hash"] =
Utils::Multipart.parse_multipart(env)
@ -131,12 +147,7 @@ module Rack
@env["rack.request.form_vars"] = form_vars
@env["rack.request.form_hash"] = Utils.parse_nested_query(form_vars)
begin
@env["rack.input"].rewind if @env["rack.input"].respond_to?(:rewind)
rescue Errno::ESPIPE
# Handles exceptions raised by input streams that cannot be rewound
# such as when using plain CGI under Apache
end
@env["rack.input"].rewind
end
@env["rack.request.form_hash"]
else
@ -212,10 +223,12 @@ module Rack
url
end
def path
script_name + path_info
end
def fullpath
path = script_name + path_info
path << "?" << query_string unless query_string.empty?
path
query_string.empty? ? path : "#{path}?#{query_string}"
end
def accept_encoding

View file

@ -95,6 +95,10 @@ module Rack
:expires => Time.at(0) }.merge(value))
end
def redirect(target, status=302)
self.status = status
self["Location"] = target
end
def finish(&block)
@block = block
@ -120,7 +124,7 @@ module Rack
#
def write(str)
s = str.to_s
@length += s.size
@length += Rack::Utils.bytesize(s)
@writer.call s
header["Content-Length"] = @length.to_s

View file

@ -0,0 +1,98 @@
require 'tempfile'
module Rack
# Class which can make any IO object rewindable, including non-rewindable ones. It does
# this by buffering the data into a tempfile, which is rewindable.
#
# rack.input is required to be rewindable, so if your input stream IO is non-rewindable
# by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class
# to easily make it rewindable.
#
# Don't forget to call #close when you're done. This frees up temporary resources that
# RewindableInput uses, though it does *not* close the original IO object.
class RewindableInput
def initialize(io)
@io = io
@rewindable_io = nil
@unlinked = false
end
def gets
make_rewindable unless @rewindable_io
@rewindable_io.gets
end
def read(*args)
make_rewindable unless @rewindable_io
@rewindable_io.read(*args)
end
def each(&block)
make_rewindable unless @rewindable_io
@rewindable_io.each(&block)
end
def rewind
make_rewindable unless @rewindable_io
@rewindable_io.rewind
end
# Closes this RewindableInput object without closing the originally
# wrapped IO oject. Cleans up any temporary resources that this RewindableInput
# has created.
#
# This method may be called multiple times. It does nothing on subsequent calls.
def close
if @rewindable_io
if @unlinked
@rewindable_io.close
else
@rewindable_io.close!
end
@rewindable_io = nil
end
end
private
# Ruby's Tempfile class has a bug. Subclass it and fix it.
class Tempfile < ::Tempfile
def _close
@tmpfile.close if @tmpfile
@data[1] = nil if @data
@tmpfile = nil
end
end
def make_rewindable
# Buffer all data into a tempfile. Since this tempfile is private to this
# RewindableInput object, we chmod it so that nobody else can read or write
# it. On POSIX filesystems we also unlink the file so that it doesn't
# even have a file entry on the filesystem anymore, though we can still
# access it because we have the file handle open.
@rewindable_io = Tempfile.new('RackRewindableInput')
@rewindable_io.chmod(0000)
if filesystem_has_posix_semantics?
@rewindable_io.unlink
@unlinked = true
end
buffer = ""
while @io.read(1024 * 4, buffer)
entire_buffer_written_out = false
while !entire_buffer_written_out
written = @rewindable_io.write(buffer)
entire_buffer_written_out = written == buffer.size
if !entire_buffer_written_out
buffer.slice!(0 .. written - 1)
end
end
end
@rewindable_io.rewind
end
def filesystem_has_posix_semantics?
RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
end
end
end

View file

@ -30,7 +30,7 @@ module Rack
location = location.chomp('/')
[host, location, app]
}.sort_by { |(h, l, a)| [-l.size, h.to_s.size] } # Longest path first
}.sort_by { |(h, l, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
end
def call(env)

View file

@ -1,3 +1,5 @@
# -*- encoding: binary -*-
require 'set'
require 'tempfile'
@ -63,7 +65,7 @@ module Rack
module_function :parse_nested_query
def normalize_params(params, name, v = nil)
name =~ %r([\[\]]*([^\[\]]+)\]*)
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
k = $1 || ''
after = $' || ''
@ -73,12 +75,12 @@ module Rack
params[k] = v
elsif after == "[]"
params[k] ||= []
raise TypeError unless params[k].is_a?(Array)
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
params[k] << v
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
child_key = $1
params[k] ||= []
raise TypeError unless params[k].is_a?(Array)
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
normalize_params(params[k].last, child_key, v)
else
@ -86,6 +88,7 @@ module Rack
end
else
params[k] ||= {}
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
params[k] = normalize_params(params[k], after, v)
end
@ -104,6 +107,25 @@ module Rack
end
module_function :build_query
def build_nested_query(value, prefix = nil)
case value
when Array
value.map { |v|
build_nested_query(v, "#{prefix}[]")
}.join("&")
when Hash
value.map { |k, v|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
}.join("&")
when String
raise ArgumentError, "value must be a Hash" if prefix.nil?
"#{prefix}=#{escape(value)}"
else
prefix
end
end
module_function :build_nested_query
# Escape ampersands, brackets and quotes to their HTML/XML entities.
def escape_html(string)
string.to_s.gsub("&", "&amp;").
@ -288,11 +310,39 @@ module Rack
# Usually, Rack::Request#POST takes care of calling this.
module Multipart
class UploadedFile
# The filename, *not* including the path, of the "uploaded" file
attr_reader :original_filename
# The content type of the "uploaded" file
attr_accessor :content_type
def initialize(path, content_type = "text/plain", binary = false)
raise "#{path} file does not exist" unless ::File.exist?(path)
@content_type = content_type
@original_filename = ::File.basename(path)
@tempfile = Tempfile.new(@original_filename)
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
@tempfile.binmode if binary
FileUtils.copy_file(path, @tempfile.path)
end
def path
@tempfile.path
end
alias_method :local_path, :path
def method_missing(method_name, *args, &block) #:nodoc:
@tempfile.__send__(method_name, *args, &block)
end
end
EOL = "\r\n"
MULTIPART_BOUNDARY = "AaB03x"
def self.parse_multipart(env)
unless env['CONTENT_TYPE'] =~
%r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
%r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
nil
else
boundary = "--#{$1}"
@ -301,13 +351,16 @@ module Rack
buf = ""
content_length = env['CONTENT_LENGTH'].to_i
input = env['rack.input']
input.rewind
boundary_size = boundary.size + EOL.size
boundary_size = Utils.bytesize(boundary) + EOL.size
bufsize = 16384
content_length -= boundary_size
status = input.read(boundary_size)
read_buffer = ''
status = input.read(boundary_size, read_buffer)
raise EOFError, "bad content body" unless status == boundary + EOL
rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
@ -318,15 +371,15 @@ module Rack
filename = content_type = name = nil
until head && buf =~ rx
if !head && i = buf.index("\r\n\r\n")
if !head && i = buf.index(EOL+EOL)
head = buf.slice!(0, i+2) # First \r\n
buf.slice!(0, 2) # Second \r\n
filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
content_type = head[/Content-Type: (.*)\r\n/ni, 1]
name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1]
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
if filename
if content_type || filename
body = Tempfile.new("RackMultipart")
body.binmode if body.respond_to?(:binmode)
end
@ -339,7 +392,7 @@ module Rack
body << buf.slice!(0, buf.size - (boundary_size+4))
end
c = input.read(bufsize < content_length ? bufsize : content_length)
c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
raise EOFError, "bad content body" if c.nil? || c.empty?
buf << c
content_length -= c.size
@ -368,6 +421,12 @@ module Rack
data = {:filename => filename, :type => content_type,
:name => name, :tempfile => body, :head => head}
elsif !filename && content_type
body.rewind
# Generic multipart cases, not coming from a form
data = {:type => content_type,
:name => name, :tempfile => body, :head => head}
else
data = body
end
@ -377,16 +436,81 @@ module Rack
break if buf.empty? || content_length == -1
}
begin
input.rewind if input.respond_to?(:rewind)
rescue Errno::ESPIPE
# Handles exceptions raised by input streams that cannot be rewound
# such as when using plain CGI under Apache
end
input.rewind
params
end
end
def self.build_multipart(params, first = true)
if first
unless params.is_a?(Hash)
raise ArgumentError, "value must be a Hash"
end
multipart = false
query = lambda { |value|
case value
when Array
value.each(&query)
when Hash
value.values.each(&query)
when UploadedFile
multipart = true
end
}
params.values.each(&query)
return nil unless multipart
end
flattened_params = Hash.new
params.each do |key, value|
k = first ? key.to_s : "[#{key}]"
case value
when Array
value.map { |v|
build_multipart(v, false).each { |subkey, subvalue|
flattened_params["#{k}[]#{subkey}"] = subvalue
}
}
when Hash
build_multipart(value, false).each { |subkey, subvalue|
flattened_params[k + subkey] = subvalue
}
else
flattened_params[k] = value
end
end
if first
flattened_params.map { |name, file|
if file.respond_to?(:original_filename)
::File.open(file.path, "rb") do |f|
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
<<-EOF
--#{MULTIPART_BOUNDARY}\r
Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
Content-Type: #{file.content_type}\r
Content-Length: #{::File.stat(file.path).size}\r
\r
#{f.read}\r
EOF
end
else
<<-EOF
--#{MULTIPART_BOUNDARY}\r
Content-Disposition: form-data; name="#{name}"\r
\r
#{file}\r
EOF
end
}.join + "--#{MULTIPART_BOUNDARY}--\r"
else
flattened_params
end
end
end
end
end

View file

@ -2,7 +2,7 @@ module ActionPack #:nodoc:
module VERSION #:nodoc:
MAJOR = 2
MINOR = 3
TINY = 2
TINY = 3
STRING = [MAJOR, MINOR, TINY].join('.')
end

View file

@ -11,7 +11,7 @@ module ActionView #:nodoc:
autoload :FormHelper, 'action_view/helpers/form_helper'
autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper'
autoload :FormTagHelper, 'action_view/helpers/form_tag_helper'
autoload :JavascriptHelper, 'action_view/helpers/javascript_helper'
autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper'
autoload :NumberHelper, 'action_view/helpers/number_helper'
autoload :PrototypeHelper, 'action_view/helpers/prototype_helper'
autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper'

View file

@ -272,14 +272,17 @@ module ActionView
# javascript_include_tag :all, :cache => true, :recursive => true
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
cache = options.delete("cache")
concat = options.delete("concat")
cache = concat || options.delete("cache")
recursive = options.delete("recursive")
if ActionController::Base.perform_caching && cache
if concat || (ActionController::Base.perform_caching && cache)
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : JAVASCRIPTS_DIR, joined_javascript_name)
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path)
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive))
end
javascript_src_tag(joined_javascript_name, options)
else
expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
@ -410,16 +413,25 @@ module ActionView
# The <tt>:recursive</tt> option is also available for caching:
#
# stylesheet_link_tag :all, :cache => true, :recursive => true
#
# To force concatenation (even in development mode) set <tt>:concat</tt> to true. This is useful if
# you have too many stylesheets for IE to load.
#
# stylesheet_link_tag :all, :concat => true
#
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
cache = options.delete("cache")
concat = options.delete("concat")
cache = concat || options.delete("cache")
recursive = options.delete("recursive")
if ActionController::Base.perform_caching && cache
if concat || (ActionController::Base.perform_caching && cache)
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : STYLESHEETS_DIR, joined_stylesheet_name)
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path)
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive))
end
stylesheet_tag(joined_stylesheet_name, options)
else
expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
@ -679,4 +691,4 @@ module ActionView
end
end
end
end
end

View file

@ -493,7 +493,8 @@ module ActionView
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
# it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
# onto the HTML as an HTML element attribute as in the example shown.
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
# target labels for radio_button tags (where the value is used in the ID of the input tag).
#
# ==== Examples
# label(:post, :title)
@ -505,6 +506,9 @@ module ActionView
# label(:post, :title, "A short title", :class => "title_label")
# # => <label for="post_title" class="title_label">A short title</label>
#
# label(:post, :privacy, "Public Post", :value => "public")
# # => <label for="post_privacy_public">Public Post</label>
#
def label(object_name, method, text = nil, options = {})
InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
end
@ -720,8 +724,9 @@ module ActionView
def to_label_tag(text = nil, options = {})
options = options.stringify_keys
tag_value = options.delete("value")
name_and_id = options.dup
add_default_name_and_id(name_and_id)
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options["for"] ||= name_and_id["id"]
content = (text.blank? ? nil : text.to_s) || method_name.humanize
@ -753,11 +758,7 @@ module ActionView
checked = self.class.radio_button_checked?(value(object), tag_value)
end
options["checked"] = "checked" if checked
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
options["id"] ||= defined?(@auto_index) ?
"#{tag_id_with_index(@auto_index)}_#{pretty_tag_value}" :
"#{tag_id}_#{pretty_tag_value}"
add_default_name_and_id(options)
add_default_name_and_id_for_value(tag_value, options)
tag("input", options)
end
@ -858,6 +859,17 @@ module ActionView
end
private
def add_default_name_and_id_for_value(tag_value, options)
if tag_value
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
specified_id = options["id"]
add_default_name_and_id(options)
options["id"] += "_#{pretty_tag_value}" unless specified_id
else
add_default_name_and_id(options)
end
end
def add_default_name_and_id(options)
if options.has_key?("index")
options["name"] ||= tag_name_with_index(options["index"])
@ -905,6 +917,7 @@ module ActionView
attr_accessor :object_name, :object, :options
def initialize(object_name, object, template, options, proc)
@nested_child_index = {}
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
@default_options = @options ? @options.slice(:index) : {}
if @object_name.to_s.match(/\[\]$/)
@ -1007,7 +1020,7 @@ module ActionView
explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
children.map do |child|
fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index}]", child, args, block)
fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block)
end.join
else
fields_for_nested_model(name, explicit_object || association, args, block)
@ -1025,9 +1038,9 @@ module ActionView
end
end
def nested_child_index
@nested_child_index ||= -1
@nested_child_index += 1
def nested_child_index(name)
@nested_child_index[name] ||= -1
@nested_child_index[name] += 1
end
end
end
@ -1036,4 +1049,4 @@ module ActionView
cattr_accessor :default_form_builder
self.default_form_builder = ::ActionView::Helpers::FormBuilder
end
end
end

View file

@ -230,6 +230,8 @@ module ActionView
#
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
def options_for_select(container, selected = nil)
return container if String === container
container = container.to_a if Hash === container
selected, disabled = extract_selected_and_disabled(selected)

View file

@ -230,6 +230,8 @@ module ActionView
# * <tt>:rows</tt> - Specify the number of rows in the textarea
# * <tt>:cols</tt> - Specify the number of columns in the textarea
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * <tt>:escape</tt> - By default, the contents of the text input are HTML escaped.
# If you need unescaped contents, set this to false.
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
@ -257,7 +259,10 @@ module ActionView
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
end
content_tag :textarea, content, { "name" => name, "id" => name }.update(options.stringify_keys)
escape = options.key?("escape") ? options.delete("escape") : true
content = html_escape(content) if escape
content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
end
# Creates a check box form input tag.
@ -353,7 +358,8 @@ module ActionView
disable_with << ";#{options.delete('onclick')}" if options['onclick']
options["onclick"] = "if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }"
options["onclick"] << "else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }"
options["onclick"] << "else { hiddenCommit = document.createElement('input');hiddenCommit.type = 'hidden';"
options["onclick"] << "hiddenCommit.value = this.value;hiddenCommit.name = this.name;this.form.appendChild(hiddenCommit); }"
options["onclick"] << "this.setAttribute('originalValue', this.value);this.disabled = true;#{disable_with};"
options["onclick"] << "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());"
options["onclick"] << "if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;"
@ -444,10 +450,10 @@ module ActionView
''
when /^post$/i, "", nil
html_options["method"] = "post"
protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0') : ''
protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0;display:inline') : ''
else
html_options["method"] = "post"
content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0')
content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0;display:inline')
end
end

View file

@ -686,7 +686,7 @@ module ActionView
# Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
# expression as an argument to another JavaScriptGenerator method.
def literal(code)
ActiveSupport::JSON::Variable.new(code.to_s)
::ActiveSupport::JSON::Variable.new(code.to_s)
end
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
@ -973,7 +973,7 @@ module ActionView
def loop_on_multiple_args(method, ids)
record(ids.size>1 ?
"#{javascript_object_for(ids)}.each(#{method})" :
"#{method}(#{ids.first.to_json})")
"#{method}(#{::ActiveSupport::JSON.encode(ids.first)})")
end
def page
@ -997,7 +997,7 @@ module ActionView
end
def javascript_object_for(object)
object.respond_to?(:to_json) ? object.to_json : object.inspect
::ActiveSupport::JSON.encode(object)
end
def arguments_for_call(arguments, block = nil)
@ -1139,7 +1139,7 @@ module ActionView
class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
def initialize(generator, id)
@id = id
super(generator, "$(#{id.to_json})")
super(generator, "$(#{::ActiveSupport::JSON.encode(id)})")
end
# Allows access of element attributes through +attribute+. Examples:
@ -1211,7 +1211,7 @@ module ActionView
enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
else
add_variable_assignment!(variable)
append_enumerable_function!("eachSlice(#{number.to_json});")
append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
end
end
@ -1232,7 +1232,7 @@ module ActionView
def pluck(variable, property)
add_variable_assignment!(variable)
append_enumerable_function!("pluck(#{property.to_json});")
append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
end
def zip(variable, *arguments, &block)
@ -1296,7 +1296,7 @@ module ActionView
class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
def initialize(generator, pattern)
super(generator, "$$(#{pattern.to_json})")
super(generator, "$$(#{::ActiveSupport::JSON.encode(pattern)})")
end
end
end

View file

@ -43,7 +43,7 @@ module ActionView
# You can change the behaviour with various options, see
# http://script.aculo.us for more documentation.
def visual_effect(name, element_id = false, js_options = {})
element = element_id ? element_id.to_json : "element"
element = element_id ? ActiveSupport::JSON.encode(element_id) : "element"
js_options[:queue] = if js_options[:queue].is_a?(Hash)
'{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
@ -138,7 +138,7 @@ module ActionView
end
def sortable_element_js(element_id, options = {}) #:nodoc:
options[:with] ||= "Sortable.serialize(#{element_id.to_json})"
options[:with] ||= "Sortable.serialize(#{ActiveSupport::JSON.encode(element_id)})"
options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
@ -149,7 +149,7 @@ module ActionView
options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
%(Sortable.create(#{element_id.to_json}, #{options_for_javascript(options)});)
%(Sortable.create(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
end
# Makes the element with the DOM ID specified by +element_id+ draggable.
@ -164,7 +164,7 @@ module ActionView
end
def draggable_element_js(element_id, options = {}) #:nodoc:
%(new Draggable(#{element_id.to_json}, #{options_for_javascript(options)});)
%(new Draggable(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
end
# Makes the element with the DOM ID specified by +element_id+ receive
@ -219,7 +219,7 @@ module ActionView
# Confirmation happens during the onDrop callback, so it can be removed from the options
options.delete(:confirm) if options[:confirm]
%(Droppables.add(#{element_id.to_json}, #{options_for_javascript(options)});)
%(Droppables.add(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
end
end
end

View file

@ -271,8 +271,8 @@ module ActionView
end
# Returns the text with all the Markdown codes turned into HTML tags.
# <i>This method requires BlueCloth[http://www.deveiate.org/projects/BlueCloth]
# to be available</i>.
# <i>This method requires BlueCloth[http://www.deveiate.org/projects/BlueCloth] or another
# Markdown library to be installed.</i>.
#
# ==== Examples
# markdown("We are using __Markdown__ now!")
@ -288,7 +288,7 @@ module ActionView
# markdown('![The ROR logo](http://rubyonrails.com/images/rails.png "Ruby on Rails")')
# # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>'
def markdown(text)
text.blank? ? "" : BlueCloth.new(text).to_html
text.blank? ? "" : Markdown.new(text).to_html
end
# Returns +text+ transformed into HTML using simple formatting rules.

View file

@ -1,4 +1,4 @@
require 'action_view/helpers/javascript_helper'
#require 'action_view/helpers/javascript_helper'
module ActionView
module Helpers #:nodoc:

View file

@ -61,7 +61,7 @@ module ActionView #:nodoc:
end
end
return Template.new(original_template_path, original_template_path.to_s =~ /\A\// ? "" : ".") if File.file?(original_template_path)
return Template.new(original_template_path) if File.file?(original_template_path)
raise MissingTemplate.new(self, original_template_path, format)
end

View file

@ -107,9 +107,8 @@ module ActionView #:nodoc:
attr_accessor :locale, :name, :format, :extension
delegate :to_s, :to => :path
def initialize(template_path, load_path)
@template_path = template_path.dup
@load_path, @filename = load_path, File.join(load_path, template_path)
def initialize(template_path, load_path = nil)
@template_path, @load_path = template_path.dup, load_path
@base_path, @name, @locale, @format, @extension = split(template_path)
@base_path.to_s.gsub!(/\/$/, '') # Push to split method
@ -180,6 +179,12 @@ module ActionView #:nodoc:
@@exempt_from_layout.any? { |exempted| path =~ exempted }
end
def filename
# no load_path means this is an "absolute pathed" template
load_path ? File.join(load_path, template_path) : template_path
end
memoize :filename
def source
File.read(filename)
end
@ -212,46 +217,30 @@ module ActionView #:nodoc:
end
def valid_locale?(locale)
I18n.available_locales.include?(locale.to_sym)
locale && I18n.available_locales.include?(locale.to_sym)
end
# Returns file split into an array
# [base_path, name, locale, format, extension]
def split(file)
if m = file.to_s.match(/^(.*\/)?([^\.]+)\.(.*)$/)
base_path = m[1]
name = m[2]
extensions = m[3]
else
return
[m[1], m[2], *parse_extensions(m[3])]
end
end
# returns parsed extensions as an array
# [locale, format, extension]
def parse_extensions(extensions)
exts = extensions.split(".")
if extension = valid_extension?(exts.last) && exts.pop || nil
locale = valid_locale?(exts.first) && exts.shift || nil
format = exts.join('.') if exts.any? # join('.') is needed for multipart templates
else # no extension, just format
format = exts.last
end
locale = nil
format = nil
extension = nil
if m = extensions.split(".")
if valid_locale?(m[0]) && m[1] && valid_extension?(m[2]) # All three
locale = m[0]
format = m[1]
extension = m[2]
elsif m[0] && m[1] && valid_extension?(m[2]) # Multipart formats
format = "#{m[0]}.#{m[1]}"
extension = m[2]
elsif valid_locale?(m[0]) && valid_extension?(m[1]) # locale and extension
locale = m[0]
extension = m[1]
elsif valid_extension?(m[1]) # format and extension
format = m[0]
extension = m[1]
elsif valid_extension?(m[0]) # Just extension
extension = m[0]
else # No extension
format = m[0]
end
end
[base_path, name, locale, format, extension]
[locale, format, extension]
end
end
end

View file

@ -8,7 +8,7 @@ require 'yaml'
require 'stringio'
require 'test/unit'
gem 'mocha', '>= 0.9.5'
gem 'mocha', '>= 0.9.7'
require 'mocha'
begin

View file

@ -27,9 +27,9 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
end
def call_reset_session
session[:bar]
session[:foo]
reset_session
session[:bar] = "baz"
session[:foo] = "baz"
head :ok
end
@ -86,7 +86,7 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
get '/get_session_value'
assert_response :success
assert_equal 'foo: nil', response.body
assert_equal 'foo: "baz"', response.body
get '/get_session_id'
assert_response :success

View file

@ -11,6 +11,9 @@ class ActionPackAssertionsController < ActionController::Base
# a standard template
def hello_xml_world() render :template => "test/hello_xml_world"; end
# a standard partial
def partial() render :partial => 'test/partial'; end
# a redirect to an internal location
def redirect_internal() redirect_to "/nothing"; end
@ -332,6 +335,30 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
assert @response.rendered[:template]
assert 'hello_world', @response.rendered[:template].to_s
end
def test_assert_template_with_partial
get :partial
assert_template :partial => '_partial'
end
def test_assert_template_with_nil
get :nothing
assert_template nil
end
def test_assert_template_with_string
get :hello_world
assert_template 'hello_world'
end
def test_assert_template_with_symbol
get :hello_world
assert_template :hello_world
end
def test_assert_template_with_bad_argument
assert_raise(ArgumentError) { assert_template 1 }
end
# check the redirection location
def test_redirection_location

View file

@ -1,5 +1,6 @@
require 'fileutils'
require 'abstract_unit'
require 'active_record_unit'
CACHE_DIR = 'test_cache'
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
@ -7,6 +8,10 @@ FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
ActionController::Base.page_cache_directory = FILE_STORE_PATH
ActionController::Base.cache_store = :file_store, FILE_STORE_PATH
# Force sweeper classes to load
ActionController::Caching::Sweeper
ActionController::Caching::Sweeping
class PageCachingTestController < ActionController::Base
caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? }
caches_page :found, :not_found
@ -152,6 +157,7 @@ class ActionCachingTestController < ActionController::Base
caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
caches_action :with_layout
caches_action :layout_false, :layout => false
caches_action :record_not_found, :four_oh_four, :simple_runtime_error
layout 'talk_from_action.erb'
@ -174,6 +180,18 @@ class ActionCachingTestController < ActionController::Base
render :text => @cache_this, :layout => true
end
def record_not_found
raise ActiveRecord::RecordNotFound, "oops!"
end
def four_oh_four
render :text => "404'd!", :status => 404
end
def simple_runtime_error
raise "oops!"
end
alias_method :show, :index
alias_method :edit, :index
alias_method :destroy, :index
@ -456,6 +474,27 @@ class ActionCacheTest < ActionController::TestCase
assert_response :success
end
def test_record_not_found_returns_404_for_multiple_requests
get :record_not_found
assert_response 404
get :record_not_found
assert_response 404
end
def test_four_oh_four_returns_404_for_multiple_requests
get :four_oh_four
assert_response 404
get :four_oh_four
assert_response 404
end
def test_simple_runtime_error_returns_500_for_multiple_requests
get :simple_runtime_error
assert_response 500
get :simple_runtime_error
assert_response 500
end
private
def content_to_cache
assigns(:cache_this)

View file

@ -6,6 +6,10 @@ class CookieTest < ActionController::TestCase
cookies["user_name"] = "david"
end
def set_with_with_escapable_characters
cookies["that & guy"] = "foo & bar => baz"
end
def authenticate_for_fourteen_days
cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) }
end
@ -53,6 +57,12 @@ class CookieTest < ActionController::TestCase
assert_equal({"user_name" => "david"}, @response.cookies)
end
def test_setting_with_escapable_characters
get :set_with_with_escapable_characters
assert_equal ["that+%26+guy=foo+%26+bar+%3D%3E+baz; path=/"], @response.headers["Set-Cookie"]
assert_equal({"that & guy" => "foo & bar => baz"}, @response.cookies)
end
def test_setting_cookie_for_fourteen_days
get :authenticate_for_fourteen_days
assert_equal ["user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT"], @response.headers["Set-Cookie"]

View file

@ -25,7 +25,8 @@ class DispatcherTest < Test::Unit::TestCase
def test_clears_dependencies_after_dispatch_if_in_loading_mode
ActiveSupport::Dependencies.expects(:clear).once
dispatch(false)
# Close the response so dependencies kicks in
dispatch(false).last.close
end
def test_reloads_routes_before_dispatch_if_in_loading_mode
@ -49,13 +50,14 @@ class DispatcherTest < Test::Unit::TestCase
Dispatcher.any_instance.expects(:dispatch).raises('b00m')
ActionController::Failsafe.any_instance.expects(:log_failsafe_exception)
response = nil
assert_nothing_raised do
assert_equal [
500,
{"Content-Type" => "text/html"},
"<html><body><h1>500 Internal Server Error</h1></body></html>"
], dispatch
response = dispatch
end
assert_equal 3, response.size
assert_equal 500, response[0]
assert_equal({"Content-Type" => "text/html"}, response[1])
assert_match /500 Internal Server Error/, response[2].join
end
def test_prepare_callbacks
@ -94,7 +96,7 @@ class DispatcherTest < Test::Unit::TestCase
def dispatch(cache_classes = true)
ActionController::Routing::RouteSet.any_instance.stubs(:call).returns([200, {}, 'response'])
Dispatcher.define_dispatcher_callbacks(cache_classes)
Dispatcher.new.call({})
Dispatcher.new.call({'rack.input' => StringIO.new('')})
end
def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)

View file

@ -0,0 +1,60 @@
require 'abstract_unit'
require 'stringio'
require 'logger'
class FailsafeTest < ActionController::TestCase
FIXTURE_PUBLIC = "#{File.dirname(__FILE__)}/../fixtures/failsafe".freeze
def setup
@old_error_file_path = ActionController::Failsafe.error_file_path
ActionController::Failsafe.error_file_path = FIXTURE_PUBLIC
@app = mock
@log_io = StringIO.new
@logger = Logger.new(@log_io)
@failsafe = ActionController::Failsafe.new(@app)
@failsafe.stubs(:failsafe_logger).returns(@logger)
end
def teardown
ActionController::Failsafe.error_file_path = @old_error_file_path
end
def app_will_raise_error!
@app.expects(:call).then.raises(RuntimeError.new("Printer on fire"))
end
def test_calls_app_and_returns_its_return_value
@app.expects(:call).returns([200, { "Content-Type" => "text/html" }, "ok"])
assert_equal [200, { "Content-Type" => "text/html" }, "ok"], @failsafe.call({})
end
def test_writes_to_log_file_on_exception
app_will_raise_error!
@failsafe.call({})
assert_match /Printer on fire/, @log_io.string # Logs exception message.
assert_match /failsafe_test\.rb/, @log_io.string # Logs backtrace.
end
def test_returns_500_internal_server_error_on_exception
app_will_raise_error!
response = @failsafe.call({})
assert_equal 3, response.size # It is a valid Rack response.
assert_equal 500, response[0] # Status is 500.
end
def test_renders_error_page_file_with_erb
app_will_raise_error!
response = @failsafe.call({})
assert_equal 500, response[0]
assert_equal "hello my world", response[2].join
end
def test_returns_a_default_message_if_erb_rendering_failed
app_will_raise_error!
@failsafe.expects(:render_template).raises(RuntimeError.new("Harddisk is crashing"))
response = @failsafe.call({})
assert_equal 500, response[0]
assert_match /500 Internal Server Error/, response[2].join
assert_match %r(please read this web application's log file), response[2].join
end
end

View file

@ -23,7 +23,8 @@ class FilterParamTest < Test::Unit::TestCase
[{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
[{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
[{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']]
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'],
[{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, %w(foo)]]
test_hashes.each do |before_filter, after_filter, filter_words|
FilterParamController.filter_parameter_logging(*filter_words)

Some files were not shown because too many files have changed in this diff Show more