Permalink
Browse files

Merge commit 'origin/master' into savepoints

  • Loading branch information...
2 parents ccb96f2 + 4e60eeb commit 13c6c3cfc59ff0b400b294dce15f32752b0fb5f5 @FooBarWidget FooBarWidget committed Dec 9, 2008
Showing with 5,998 additions and 2,376 deletions.
  1. +4 −0 actionmailer/test/abstract_unit.rb
  2. +6 −0 actionmailer/test/mail_service_test.rb
  3. +21 −75 actionpack/README
  4. +6 −2 actionpack/lib/action_controller.rb
  5. +17 −9 actionpack/lib/action_controller/caching/fragments.rb
  6. +12 −14 actionpack/lib/action_controller/caching/pages.rb
  7. +52 −164 actionpack/lib/action_controller/cgi_process.rb
  8. +13 −70 actionpack/lib/action_controller/dispatcher.rb
  9. +52 −0 actionpack/lib/action_controller/failsafe.rb
  10. +98 −58 actionpack/lib/action_controller/integration.rb
  11. +2 −9 actionpack/lib/action_controller/rack_process.rb
  12. +42 −24 actionpack/lib/action_controller/rescue.rb
  13. +6 −1 actionpack/lib/action_controller/resources.rb
  14. +4 −2 actionpack/lib/action_controller/routing.rb
  15. +10 −0 actionpack/lib/action_controller/session/active_record_store.rb
  16. +2 −2 actionpack/lib/action_controller/templates/rescues/diagnostics.erb
  17. +2 −2 actionpack/lib/action_controller/templates/rescues/template_error.erb
  18. +8 −7 actionpack/lib/action_view/base.rb
  19. +1 −1 actionpack/lib/action_view/helpers/tag_helper.rb
  20. +9 −1 actionpack/lib/action_view/paths.rb
  21. +1 −1 actionpack/lib/action_view/template.rb
  22. +2 −0 actionpack/lib/action_view/template_handlers/erb.rb
  23. +1 −0 actionpack/lib/action_view/template_handlers/rjs.rb
  24. +2 −2 actionpack/test/controller/caching_test.rb
  25. +0 −262 actionpack/test/controller/cgi_test.rb
  26. +18 −20 actionpack/test/controller/dispatcher_test.rb
  27. +9 −38 actionpack/test/controller/integration_test.rb
  28. +8 −10 actionpack/test/controller/rack_test.rb
  29. +18 −8 actionpack/test/controller/render_test.rb
  30. +2 −2 actionpack/test/controller/request_test.rb
  31. +44 −49 actionpack/test/controller/session_fixation_test.rb
  32. +13 −13 actionpack/test/controller/view_paths_test.rb
  33. +178 −144 actionpack/test/controller/webservice_test.rb
  34. +1 −0 actionpack/test/fixtures/test/_one.html.erb
  35. +1 −0 actionpack/test/fixtures/test/_two.html.erb
  36. +1 −0 actionpack/test/fixtures/test/render_explicit_html_template.js.rjs
  37. +1 −0 actionpack/test/fixtures/test/render_implicit_html_template.js.rjs
  38. +0 −6 actionpack/test/template/render_test.rb
  39. +2 −0 activemodel/lib/active_model/errors.rb
  40. +2 −0 activerecord/CHANGELOG
  41. +4 −1 activerecord/lib/active_record/associations/association_proxy.rb
  42. +42 −14 activerecord/lib/active_record/base.rb
  43. +2 −0 activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
  44. +9 −6 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  45. +2 −2 activerecord/lib/active_record/dirty.rb
  46. +3 −1 activerecord/lib/active_record/migration.rb
  47. +2 −0 activerecord/lib/active_record/schema_dumper.rb
  48. +10 −8 activerecord/lib/active_record/validations.rb
  49. +12 −0 activerecord/test/cases/dirty_test.rb
  50. +2 −0 activeresource/lib/active_resource/base.rb
  51. +4 −0 activesupport/CHANGELOG
  52. +2 −0 activesupport/lib/active_support/buffered_logger.rb
  53. +10 −9 activesupport/lib/active_support/core_ext/array/conversions.rb
  54. +11 −8 activesupport/lib/active_support/core_ext/exception.rb
  55. +2 −0 activesupport/lib/active_support/core_ext/logger.rb
  56. +1 −1 activesupport/lib/active_support/inflector.rb
  57. +3 −2 activesupport/lib/active_support/locale/en.yml
  58. +8 −0 activesupport/lib/active_support/multibyte/chars.rb
  59. +10 −9 activesupport/test/core_ext/array_ext_test.rb
  60. +15 −6 activesupport/test/i18n_test.rb
  61. +4 −0 activesupport/test/multibyte_chars_test.rb
  62. +2 −0 railties/CHANGELOG
  63. +2 −1 railties/Rakefile
  64. +26 −0 railties/doc/guides/asciidoc.conf
  65. +9 −4 railties/doc/guides/html/2_2_release_notes.html
  66. +10 −10 railties/doc/guides/html/actioncontroller_basics.html
  67. +451 −3 railties/doc/guides/html/activerecord_validations_callbacks.html
  68. +12 −9 railties/doc/guides/html/association_basics.html
  69. +5 −0 railties/doc/guides/html/authors.html
  70. +104 −20 railties/doc/guides/html/caching_with_rails.html
  71. +125 −2 railties/doc/guides/html/command_line.html
  72. +155 −147 railties/doc/guides/html/configuring.html
  73. +815 −497 railties/doc/guides/html/creating_plugins.html
  74. +65 −27 railties/doc/guides/html/finders.html
  75. +25 −8 railties/doc/guides/html/getting_started_with_rails.html
  76. +1,086 −0 railties/doc/guides/html/i18n.html
  77. +13 −0 railties/doc/guides/html/index.html
  78. +2 −1 railties/doc/guides/html/migrations.html
  79. +4 −4 railties/doc/guides/html/routing_outside_in.html
  80. +5 −4 railties/doc/guides/source/2_2_release_notes.txt
  81. +1 −1 railties/doc/guides/source/actioncontroller_basics/cookies.txt
  82. +2 −2 railties/doc/guides/source/actioncontroller_basics/filters.txt
  83. +2 −2 railties/doc/guides/source/actioncontroller_basics/parameter_filtering.txt
  84. +3 −3 railties/doc/guides/source/actioncontroller_basics/request_response_objects.txt
  85. +1 −1 railties/doc/guides/source/actioncontroller_basics/streaming.txt
  86. +1 −1 railties/doc/guides/source/actioncontroller_basics/verification.txt
  87. +284 −4 railties/doc/guides/source/activerecord_validations_callbacks.txt
  88. +15 −9 railties/doc/guides/source/association_basics.txt
  89. +6 −0 railties/doc/guides/source/authors.txt
  90. +99 −20 railties/doc/guides/source/caching_with_rails.txt
  91. +108 −2 railties/doc/guides/source/command_line.txt
  92. +127 −158 railties/doc/guides/source/configuring.txt
  93. +1 −1 railties/doc/guides/source/creating_plugins/acts_as_yaffle.txt
  94. +88 −30 railties/doc/guides/source/creating_plugins/appendix.txt
  95. +6 −2 railties/doc/guides/source/creating_plugins/controllers.txt
  96. +4 −29 railties/doc/guides/source/creating_plugins/core_ext.txt
  97. +50 −0 railties/doc/guides/source/creating_plugins/gems.txt
  98. +144 −0 railties/doc/guides/source/creating_plugins/generator_commands.txt
  99. +98 −0 railties/doc/guides/source/creating_plugins/generators.txt
  100. +1 −3 railties/doc/guides/source/creating_plugins/helpers.txt
  101. +15 −7 railties/doc/guides/source/creating_plugins/index.txt
  102. +0 −156 railties/doc/guides/source/creating_plugins/migration_generator.txt
  103. +213 −0 railties/doc/guides/source/creating_plugins/migrations.txt
  104. +4 −6 railties/doc/guides/source/creating_plugins/models.txt
  105. +0 −69 railties/doc/guides/source/creating_plugins/odds_and_ends.txt
  106. +18 −0 railties/doc/guides/source/creating_plugins/rdoc.txt
  107. +11 −11 railties/doc/guides/source/creating_plugins/{custom_route.txt → routes.txt}
  108. +84 −0 railties/doc/guides/source/creating_plugins/setup.txt
  109. +27 −0 railties/doc/guides/source/creating_plugins/tasks.txt
  110. +165 −0 railties/doc/guides/source/creating_plugins/tests.txt
  111. +51 −25 railties/doc/guides/source/finders.txt
  112. +24 −8 railties/doc/guides/source/getting_started_with_rails.txt
  113. +542 −0 railties/doc/guides/source/i18n.txt
  114. +10 −0 railties/doc/guides/source/index.txt
  115. +1 −1 railties/doc/guides/source/migrations/index.txt
  116. +2 −0 railties/doc/guides/source/migrations/rakeing_around.txt
  117. +4 −4 railties/doc/guides/source/routing_outside_in.txt
  118. +4 −0 railties/doc/guides/source/stylesheets/base.css
  119. +3 −3 railties/doc/guides/source/testing_rails_applications.txt
  120. +1 −2 railties/lib/initializer.rb
  121. +4 −2 railties/lib/rails/backtrace_cleaner.rb
  122. +1 −1 railties/lib/rails_generator/generators/applications/app/app_generator.rb
  123. +21 −12 railties/lib/rails_generator/generators/applications/app/template_runner.rb
  124. +1 −1 railties/lib/tasks/databases.rake
  125. +6 −0 railties/lib/tasks/framework.rake
View
4 actionmailer/test/abstract_unit.rb
@@ -9,6 +9,10 @@
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+# Bogus template processors
+ActionView::Template.register_template_handler :haml, lambda { |template| "Look its HAML!" }
+ActionView::Template.register_template_handler :bak, lambda { |template| "Lame backup" }
+
$:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers"
ActionMailer::Base.template_root = "#{File.dirname(__FILE__)}/fixtures"
View
6 actionmailer/test/mail_service_test.rb
@@ -389,6 +389,8 @@ def test_custom_template
end
def test_custom_templating_extension
+ assert ActionView::Template.template_handler_extensions.include?("haml"), "haml extension was not registered"
+
# N.b., custom_templating_extension.text.plain.haml is expected to be in fixtures/test_mailer directory
expected = new_mail
expected.to = @recipient
@@ -799,6 +801,8 @@ def test_explicitly_multipart_with_invalid_content_type
end
def test_implicitly_multipart_messages
+ assert ActionView::Template.template_handler_extensions.include?("bak"), "bak extension was not registered"
+
mail = TestMailer.create_implicitly_multipart_example(@recipient)
assert_equal 3, mail.parts.length
assert_equal "1.0", mail.mime_version
@@ -812,6 +816,8 @@ def test_implicitly_multipart_messages
end
def test_implicitly_multipart_messages_with_custom_order
+ assert ActionView::Template.template_handler_extensions.include?("bak"), "bak extension was not registered"
+
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
assert_equal 3, mail.parts.length
assert_equal "text/html", mail.parts[0].content_type
View
96 actionpack/README
@@ -10,7 +10,7 @@ Action Pack implements these actions as public methods on Action Controllers
and uses Action Views to implement the template rendering. Action Controllers
are then responsible for handling all the actions relating to a certain part
of an application. This grouping usually consists of actions for lists and for
-CRUDs revolving around a single (or a few) model objects. So ContactController
+CRUDs revolving around a single (or a few) model objects. So ContactsController
would be responsible for listing contacts, creating, deleting, and updating
contacts. A WeblogController could be responsible for both posts and comments.
@@ -33,7 +33,7 @@ A short rundown of the major features:
* Actions grouped in controller as methods instead of separate command objects
and can therefore share helper methods
- BlogController < ActionController::Base
+ CustomersController < ActionController::Base
def show
@customer = find_customer
end
@@ -42,7 +42,7 @@ A short rundown of the major features:
@customer = find_customer
@customer.attributes = params[:customer]
@customer.save ?
- redirect_to(:action => "display") :
+ redirect_to(:action => "show") :
render(:action => "edit")
end
@@ -59,7 +59,7 @@ A short rundown of the major features:
Title: <%= post.title %>
<% end %>
- All post titles: <%= @post.collect{ |p| p.title }.join ", " %>
+ All post titles: <%= @posts.collect{ |p| p.title }.join ", " %>
<% unless @person.is_client? %>
Not for clients to see...
@@ -123,7 +123,7 @@ A short rundown of the major features:
<%= text_field "post", "title", "size" => 30 %>
<%= html_date_select(Date.today) %>
<%= link_to "New post", :controller => "post", :action => "new" %>
- <%= truncate(post.title, 25) %>
+ <%= truncate(post.title, :length => 25) %>
{Learn more}[link:classes/ActionView/Helpers.html]
@@ -177,21 +177,6 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
-* Pagination for navigating lists of results
-
- # controller
- def list
- @pages, @people =
- paginate :people, :order => 'last_name, first_name'
- end
-
- # view
- <%= link_to "Previous page", { :page => @pages.current.previous } if @pages.current.previous %>
- <%= link_to "Next page", { :page => @pages.current.next } if @pages.current.next %>
-
- {Learn more}[link:classes/ActionController/Pagination.html]
-
-
* Easy testing of both controller and rendered template through ActionController::TestCase
class LoginControllerTest < ActionController::TestCase
@@ -215,11 +200,11 @@ A short rundown of the major features:
If Active Record is used as the model, you'll have the database debugging
as well:
- Processing WeblogController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
- Params: {"controller"=>"weblog", "action"=>"create",
+ Processing PostsController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
+ Params: {"controller"=>"posts", "action"=>"create",
"post"=>{"title"=>"this is good"} }
SQL (0.000627) INSERT INTO posts (title) VALUES('this is good')
- Redirected to http://test/weblog/display/5
+ Redirected to http://example.com/posts/5
Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%)
You specify a logger through a class method, such as:
@@ -256,30 +241,6 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionController/Caching.html]
-* Component requests from one controller to another
-
- class WeblogController < ActionController::Base
- # Performs a method and then lets hello_world output its render
- def delegate_action
- do_other_stuff_before_hello_world
- render_component :controller => "greeter", :action => "hello_world"
- end
- end
-
- class GreeterController < ActionController::Base
- def hello_world
- render_text "Hello World!"
- end
- end
-
- The same can be done in a view to do a partial rendering:
-
- Let's see a greeting:
- <%= render_component :controller => "greeter", :action => "hello_world" %>
-
- {Learn more}[link:classes/ActionController/Components.html]
-
-
* Powerful debugging mechanism for local requests
All exceptions raised on actions performed on the request of a local user
@@ -336,7 +297,7 @@ A short rundown of the major features:
class WeblogController < ActionController::Base
def create
post = Post.create(params[:post])
- redirect_to :action => "display", :id => post.id
+ redirect_to :action => "show", :id => post.id
end
end
@@ -362,7 +323,7 @@ methods:
@posts = Post.find(:all)
end
- def display
+ def show
@post = Post.find(params[:id])
end
@@ -372,7 +333,7 @@ methods:
def create
@post = Post.create(params[:post])
- redirect_to :action => "display", :id => @post.id
+ redirect_to :action => "show", :id => @post.id
end
end
@@ -385,47 +346,32 @@ request from the web-server (like to be Apache).
And the templates look like this:
- weblog/layout.erb:
+ weblog/layout.html.erb:
<html><body>
<%= yield %>
</body></html>
- weblog/index.erb:
+ weblog/index.html.erb:
<% for post in @posts %>
- <p><%= link_to(post.title, :action => "display", :id => post.id %></p>
+ <p><%= link_to(post.title, :action => "show", :id => post.id) %></p>
<% end %>
- weblog/display.erb:
+ weblog/show.html.erb:
<p>
- <b><%= post.title %></b><br/>
- <b><%= post.content %></b>
+ <b><%= @post.title %></b><br/>
+ <b><%= @post.content %></b>
</p>
- weblog/new.erb:
+ weblog/new.html.erb:
<%= form "post" %>
This simple setup will list all the posts in the system on the index page,
which is called by accessing /weblog/. It uses the form builder for the Active
Record model to make the new screen, which in turn hands everything over to
the create action (that's the default target for the form builder when given a
-new model). After creating the post, it'll redirect to the display page using
-an URL such as /weblog/display/5 (where 5 is the id of the post).
-
-
-== Examples
-
-Action Pack ships with three examples that all demonstrate an increasingly
-detailed view of the possibilities. First is blog_controller that is just a
-single file for the whole MVC (but still split into separate parts). Second is
-the debate_controller that uses separate template files and multiple screens.
-Third is the address_book_controller that uses the layout feature to separate
-template casing from content.
-
-Please note that you might need to change the "shebang" line to
-#!/usr/local/env ruby, if your Ruby is not placed in /usr/local/bin/ruby
+new model). After creating the post, it'll redirect to the show page using
+an URL such as /weblog/5 (where 5 is the id of the post).
-Also note that these examples are all for demonstrating using Action Pack on
-its own. Not for when it's used inside of Rails.
== Download
@@ -460,4 +406,4 @@ And as Jim from Rake says:
Feel free to submit commits or feature requests. If you send a patch,
remember to update the corresponding unit tests. If fact, I prefer
- new feature to be submitted in the form of new unit tests.
+ new feature to be submitted in the form of new unit tests.
View
8 actionpack/lib/action_controller.rb
@@ -46,10 +46,9 @@ def self.load_all!
autoload :Base, 'action_controller/base'
autoload :Benchmarking, 'action_controller/benchmarking'
autoload :Caching, 'action_controller/caching'
- autoload :CgiRequest, 'action_controller/cgi_process'
- autoload :CgiResponse, 'action_controller/cgi_process'
autoload :Cookies, 'action_controller/cookies'
autoload :Dispatcher, 'action_controller/dispatcher'
+ autoload :Failsafe, 'action_controller/failsafe'
autoload :Filters, 'action_controller/filters'
autoload :Flash, 'action_controller/flash'
autoload :Helpers, 'action_controller/helpers'
@@ -89,6 +88,11 @@ module Assertions
module Http
autoload :Headers, 'action_controller/headers'
end
+
+ # DEPRECATE: Remove CGI support
+ autoload :CgiRequest, 'action_controller/cgi_process'
+ autoload :CgiResponse, 'action_controller/cgi_process'
+ autoload :CGIHandler, 'action_controller/cgi_process'
end
class CGI
View
26 actionpack/lib/action_controller/caching/fragments.rb
@@ -83,15 +83,23 @@ def fragment_exist?(key, options = nil)
end
end
- # Name can take one of three forms:
- # * String: This would normally take the form of a path like "pages/45/notes"
- # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
- # * Regexp: Will destroy all the matched fragments, example:
- # %r{pages/\d*/notes}
- # Ensure you do not specify start and finish in the regex (^$) because
- # the actual filename matched looks like ./cache/filename/path.cache
- # Regexp expiration is only supported on caches that can iterate over
- # all keys (unlike memcached).
+ # Removes fragments from the cache.
+ #
+ # +key+ can take one of three forms:
+ # * String - This would normally take the form of a path, like
+ # <tt>"pages/45/notes"</tt>.
+ # * Hash - Treated as an implicit call to +url_for+, like
+ # <tt>{:controller => "pages", :action => "notes", :id => 45}</tt>
+ # * Regexp - Will remove any fragment that matches, so
+ # <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
+ # don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
+ # the actual filename matched looks like
+ # <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
+ # only supported on caches that can iterate over all keys (unlike
+ # memcached).
+ #
+ # +options+ is passed through to the cache store's <tt>delete</tt>
+ # method (or <tt>delete_matched</tt>, for Regexp keys.)
def expire_fragment(key, options = nil)
return unless cache_configured?
View
26 actionpack/lib/action_controller/caching/pages.rb
@@ -33,28 +33,26 @@ module Caching
#
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
# expired.
- #
- # == Setting the cache directory
- #
- # The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
- # For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
- # this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
- # web server to look in the new location for cached files.
- #
- # == Setting the cache extension
- #
- # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
- # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
- # If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
- # extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
module Pages
def self.included(base) #:nodoc:
base.extend(ClassMethods)
base.class_eval do
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
+ ##
+ # :singleton-method:
+ # The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
+ # For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
+ # this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
+ # web server to look in the new location for cached files.
cattr_accessor :page_cache_directory
@@page_cache_extension = '.html'
+ ##
+ # :singleton-method:
+ # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
+ # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
+ # If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
+ # extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
cattr_accessor :page_cache_extension
end
end
View
216 actionpack/lib/action_controller/cgi_process.rb
@@ -1,184 +1,72 @@
require 'action_controller/cgi_ext'
module ActionController #:nodoc:
- class Base
- # Process a request extracted from a CGI object and return a response. Pass false as <tt>session_options</tt> to disable
- # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
- #
- # * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
- # (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
- # lib/action_controller/session.
- # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
- # * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ cookie, or
- # automatically generated for a new session.
- # * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
- # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
- # an ArgumentError is raised.
- # * <tt>:session_expires</tt> - the time the current session expires, as a Time object. If not set, the session will continue
- # indefinitely.
- # * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
- # server.
- # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
- # * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
- # * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
- # the query string or POST parameters. This protects against session fixation attacks.
- def self.process_cgi(cgi = CGI.new, session_options = {})
- new.process_cgi(cgi, session_options)
- end
-
- def process_cgi(cgi, session_options = {}) #:nodoc:
- process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
- end
- end
-
- class CgiRequest < AbstractRequest #:nodoc:
- attr_accessor :cgi, :session_options
- class SessionFixationAttempt < StandardError #:nodoc:
- end
-
- DEFAULT_SESSION_OPTIONS = {
- :database_manager => CGI::Session::CookieStore, # store data in cookie
- :prefix => "ruby_sess.", # prefix session file names
- :session_path => "/", # available to all paths in app
- :session_key => "_session_id",
- :cookie_only => true,
- :session_http_only=> true
- }
-
- def initialize(cgi, session_options = {})
- @cgi = cgi
- @session_options = session_options
- @env = @cgi.__send__(:env_table)
- super()
- end
-
- def query_string
- qs = @cgi.query_string if @cgi.respond_to?(:query_string)
- if !qs.blank?
- qs
- else
- super
- end
- end
-
- def body_stream #:nodoc:
- @cgi.stdinput
- end
-
- def cookies
- @cgi.cookies.freeze
- end
-
- def session
- unless defined?(@session)
- if @session_options == false
- @session = Hash.new
- else
- stale_session_check! do
- if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
- raise SessionFixationAttempt
- end
- case value = session_options_with_string_keys['new_session']
- when true
- @session = new_session
- when false
- begin
- @session = CGI::Session.new(@cgi, session_options_with_string_keys)
- # CGI::Session raises ArgumentError if 'new_session' == false
- # and no session cookie or query param is present.
- rescue ArgumentError
- @session = Hash.new
- end
- when nil
- @session = CGI::Session.new(@cgi, session_options_with_string_keys)
- else
- raise ArgumentError, "Invalid new_session option: #{value}"
- end
- @session['__valid_session']
- end
+ class CGIHandler
+ module ProperStream
+ def each
+ while line = gets
+ yield line
end
end
- @session
- end
- def reset_session
- @session.delete if defined?(@session) && @session.is_a?(CGI::Session)
- @session = new_session
- end
-
- def method_missing(method_id, *arguments)
- @cgi.__send__(method_id, *arguments) rescue super
- end
-
- private
- # Delete an old session if it exists then create a new one.
- def new_session
- if @session_options == false
- Hash.new
+ def read(*args)
+ if args.empty?
+ super || ""
else
- CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
- CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
+ super
end
end
+ end
- def cookie_only?
- session_options_with_string_keys['cookie_only']
- end
+ def self.dispatch_cgi(app, cgi, out = $stdout)
+ env = cgi.__send__(:env_table)
+ env.delete "HTTP_CONTENT_LENGTH"
- def stale_session_check!
- yield
- rescue ArgumentError => argument_error
- if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
- begin
- # Note that the regexp does not allow $1 to end with a ':'
- $1.constantize
- rescue LoadError, NameError => const_error
- raise ActionController::SessionRestoreError, <<-end_msg
-Session contains objects whose class definition isn\'t available.
-Remember to require the classes for all objects kept in the session.
-(Original exception: #{const_error.message} [#{const_error.class}])
-end_msg
- end
+ cgi.stdinput.extend ProperStream
- retry
- else
- raise
- end
- end
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
- def session_options_with_string_keys
- @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
- end
- end
+ env.update({
+ "rack.version" => [0,1],
+ "rack.input" => cgi.stdinput,
+ "rack.errors" => $stderr,
+ "rack.multithread" => false,
+ "rack.multiprocess" => true,
+ "rack.run_once" => false,
+ "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
+ })
- class CgiResponse < AbstractResponse #:nodoc:
- def initialize(cgi)
- @cgi = cgi
- super()
- end
-
- def out(output = $stdout)
- output.binmode if output.respond_to?(:binmode)
- output.sync = false if output.respond_to?(:sync=)
+ env["QUERY_STRING"] ||= ""
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
+ env["REQUEST_PATH"] ||= "/"
+ env.delete "PATH_INFO" if env["PATH_INFO"] == ""
+ status, headers, body = app.call(env)
begin
- output.write(@cgi.header(@headers))
-
- if @cgi.__send__(:env_table)['REQUEST_METHOD'] == 'HEAD'
- return
- elsif @body.respond_to?(:call)
- # Flush the output now in case the @body Proc uses
- # #syswrite.
- output.flush if output.respond_to?(:flush)
- @body.call(self, output)
- else
- output.write(@body)
- end
-
- output.flush if output.respond_to?(:flush)
- rescue Errno::EPIPE, Errno::ECONNRESET
- # lost connection to parent process, ignore output
+ out.binmode if out.respond_to?(:binmode)
+ out.sync = false if out.respond_to?(:sync=)
+
+ headers['Status'] = status.to_s
+ out.write(cgi.header(headers))
+
+ body.each { |part|
+ out.write part
+ out.flush if out.respond_to?(:flush)
+ }
+ ensure
+ body.close if body.respond_to?(:close)
end
end
end
+
+ class CgiRequest #:nodoc:
+ DEFAULT_SESSION_OPTIONS = {
+ :database_manager => CGI::Session::CookieStore,
+ :prefix => "ruby_sess.",
+ :session_path => "/",
+ :session_key => "_session_id",
+ :cookie_only => true,
+ :session_http_only => true
+ }
+ end
end
View
83 actionpack/lib/action_controller/dispatcher.rb
@@ -24,8 +24,7 @@ def define_dispatcher_callbacks(cache_classes)
end
end
- # Backward-compatible class method takes CGI-specific args. Deprecated
- # in favor of Dispatcher.new(output, request, response).dispatch.
+ # DEPRECATE: Remove CGI support
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
new(output).dispatch_cgi(cgi, session_options)
end
@@ -43,60 +42,19 @@ def to_prepare(identifier = nil, &block)
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
@prepare_dispatch_callbacks.replace_or_append!(callback)
end
-
- # If the block raises, send status code as a last-ditch response.
- def failsafe_response(fallback_output, status, originating_exception = nil)
- yield
- rescue Exception => exception
- begin
- log_failsafe_exception(status, originating_exception || exception)
- body = failsafe_response_body(status)
- fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
- nil
- rescue Exception => failsafe_error # Logger or IO errors
- $stderr.puts "Error during failsafe response: #{failsafe_error}"
- $stderr.puts "(originally #{originating_exception})" if originating_exception
- end
- end
-
- private
- def failsafe_response_body(status)
- error_path = "#{error_file_path}/#{status.to_s[0..2]}.html"
-
- if File.exist?(error_path)
- File.read(error_path)
- else
- "<html><body><h1>#{status}</h1></body></html>"
- end
- end
-
- def log_failsafe_exception(status, exception)
- message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: #{status}\n"
- message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
- failsafe_logger.fatal message
- end
-
- def failsafe_logger
- if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
- ::RAILS_DEFAULT_LOGGER
- else
- Logger.new($stderr)
- end
- end
end
cattr_accessor :middleware
self.middleware = MiddlewareStack.new
-
- cattr_accessor :error_file_path
- self.error_file_path = Rails.public_path if defined?(Rails.public_path)
+ self.middleware.use "ActionController::Failsafe"
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
+ # DEPRECATE: Remove arguments
def initialize(output = $stdout, request = nil, response = nil)
@output, @request, @response = output, request, response
- @app = @@middleware.build(lambda { |env| self._call(env) })
+ @app = @@middleware.build(lambda { |env| self.dup._call(env) })
end
def dispatch_unlocked
@@ -120,14 +78,9 @@ def dispatch
end
end
+ # DEPRECATE: Remove CGI support
def dispatch_cgi(cgi, session_options)
- if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
- @request = CgiRequest.new(cgi, session_options)
- @response = CgiResponse.new(cgi)
- dispatch
- end
- rescue Exception => exception
- failsafe_rescue exception
+ CGIHandler.dispatch_cgi(self, cgi, @output)
end
def call(env)
@@ -160,34 +113,24 @@ def flush_logger
Base.logger.flush
end
- def mark_as_test_request!
- @test_request = true
- self
- end
-
- def test_request?
- @test_request
- end
-
def checkin_connections
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
- return if test_request?
+ # TODO: This callback should have direct access to env
+ return if @request.key?("action_controller.test")
ActiveRecord::Base.clear_active_connections!
end
protected
def handle_request
@controller = Routing::Routes.recognize(@request)
- @controller.process(@request, @response).out(@output)
+ @controller.process(@request, @response).out
end
def failsafe_rescue(exception)
- self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
- if @controller ||= (::ApplicationController rescue Base)
- @controller.process_with_exception(@request, @response, exception).out(@output)
- else
- raise exception
- end
+ if @controller ||= (::ApplicationController rescue Base)
+ @controller.process_with_exception(@request, @response, exception).out
+ else
+ raise exception
end
end
end
View
52 actionpack/lib/action_controller/failsafe.rb
@@ -0,0 +1,52 @@
+module ActionController
+ class Failsafe
+ cattr_accessor :error_file_path
+ self.error_file_path = Rails.public_path if defined?(Rails.public_path)
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ @app.call(env)
+ rescue Exception => exception
+ # Reraise exception in test environment
+ if env["action_controller.test"]
+ raise exception
+ else
+ failsafe_response(exception)
+ end
+ end
+
+ private
+ def failsafe_response(exception)
+ log_failsafe_exception(exception)
+ [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)
+ else
+ "<html><body><h1>500 Internal Server Error</h1></body></html>"
+ end
+ end
+
+ def log_failsafe_exception(exception)
+ message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: 500 Internal Server Error\n"
+ message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
+ failsafe_logger.fatal(message)
+ end
+
+ def failsafe_logger
+ if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
+ ::RAILS_DEFAULT_LOGGER
+ else
+ Logger.new($stderr)
+ end
+ end
+ end
+end
View
156 actionpack/lib/action_controller/integration.rb
@@ -9,13 +9,17 @@ module Integration #:nodoc:
# multiple sessions and run them side-by-side, you can also mimic (to some
# limited extent) multiple simultaneous users interacting with your system.
#
- # Typically, you will instantiate a new session using IntegrationTest#open_session,
- # rather than instantiating Integration::Session directly.
+ # Typically, you will instantiate a new session using
+ # IntegrationTest#open_session, rather than instantiating
+ # Integration::Session directly.
class Session
include Test::Unit::Assertions
include ActionController::TestCase::Assertions
include ActionController::TestProcess
+ # Rack application to use
+ attr_accessor :application
+
# The integer HTTP status code of the last request.
attr_reader :status
@@ -57,7 +61,8 @@ class MultiPartNeededException < Exception
end
# Create and initialize a new Session instance.
- def initialize
+ def initialize(app)
+ @application = app
reset!
end
@@ -76,11 +81,13 @@ def reset!
self.host = "www.example.com"
self.remote_addr = "127.0.0.1"
- self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+ self.accept = "text/xml,application/xml,application/xhtml+xml," +
+ "text/html;q=0.9,text/plain;q=0.8,image/png," +
+ "*/*;q=0.5"
unless defined? @named_routes_configured
# install the named routes in this session instance.
- klass = class<<self; self; end
+ klass = class << self; self; end
Routing::Routes.install_helpers(klass)
# the helpers are made protected by default--we make them public for
@@ -94,7 +101,7 @@ def reset!
#
# session.https!
# session.https!(false)
- def https!(flag=true)
+ def https!(flag = true)
@https = flag
end
@@ -164,40 +171,48 @@ def redirect?
# Performs a GET request with the given parameters.
#
- # - +path+: The URI (as a String) on which you want to perform a GET request.
- # - +parameters+: The HTTP parameters that you want to pass. This may be +nil+,
+ # - +path+: The URI (as a String) on which you want to perform a GET
+ # request.
+ # - +parameters+: The HTTP parameters that you want to pass. This may
+ # be +nil+,
# a Hash, or a String that is appropriately encoded
- # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
+ # (<tt>application/x-www-form-urlencoded</tt> or
+ # <tt>multipart/form-data</tt>).
# - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
# automatically be upcased, with the prefix 'HTTP_' added if needed.
#
- # This method returns an AbstractResponse object, which one can use to inspect
- # the details of the response. Furthermore, if this method was called from an
- # ActionController::IntegrationTest object, then that object's <tt>@response</tt>
- # instance variable will point to the same response object.
+ # This method returns an AbstractResponse object, which one can use to
+ # inspect the details of the response. Furthermore, if this method was
+ # called from an ActionController::IntegrationTest object, then that
+ # object's <tt>@response</tt> instance variable will point to the same
+ # response object.
#
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
# +put+, +delete+, and +head+.
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
- # Performs a POST request with the given parameters. See get() for more details.
+ # Performs a POST request with the given parameters. See get() for more
+ # details.
def post(path, parameters = nil, headers = nil)
process :post, path, parameters, headers
end
- # Performs a PUT request with the given parameters. See get() for more details.
+ # Performs a PUT request with the given parameters. See get() for more
+ # details.
def put(path, parameters = nil, headers = nil)
process :put, path, parameters, headers
end
- # Performs a DELETE request with the given parameters. See get() for more details.
+ # Performs a DELETE request with the given parameters. See get() for
+ # more details.
def delete(path, parameters = nil, headers = nil)
process :delete, path, parameters, headers
end
- # Performs a HEAD request with the given parameters. See get() for more details.
+ # Performs a HEAD request with the given parameters. See get() for more
+ # details.
def head(path, parameters = nil, headers = nil)
process :head, path, parameters, headers
end
@@ -212,7 +227,8 @@ def head(path, parameters = nil, headers = nil)
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['X-Requested-With'] = 'XMLHttpRequest'
- headers['Accept'] ||= 'text/javascript, text/html, application/xml, text/xml, */*'
+ headers['Accept'] ||= 'text/javascript, text/html, application/xml, ' +
+ 'text/xml, */*'
process(request_method, path, parameters, headers)
end
@@ -221,7 +237,9 @@ def xml_http_request(request_method, path, parameters = nil, headers = nil)
# Returns the URL for the given options, according to the rules specified
# in the application's routes.
def url_for(options)
- controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
+ controller ?
+ controller.url_for(options) :
+ generic_url_rewriter.rewrite(options)
end
private
@@ -247,17 +265,34 @@ def process(method, path, parameters = nil, headers = nil)
data = nil
end
+ env["QUERY_STRING"] ||= ""
+
+ data = data.is_a?(IO) ? data : StringIO.new(data || '')
+
env.update(
- "REQUEST_METHOD" => method.to_s.upcase,
+ "REQUEST_METHOD" => method.to_s.upcase,
+ "SERVER_NAME" => host,
+ "SERVER_PORT" => (https? ? "443" : "80"),
+ "HTTPS" => https? ? "on" : "off",
+ "rack.url_scheme" => https? ? "https" : "http",
+ "SCRIPT_NAME" => "",
+
"REQUEST_URI" => path,
"HTTP_HOST" => host,
"REMOTE_ADDR" => remote_addr,
- "SERVER_PORT" => (https? ? "443" : "80"),
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
"HTTP_COOKIE" => encode_cookies,
- "HTTPS" => https? ? "on" : "off",
- "HTTP_ACCEPT" => accept
+ "HTTP_ACCEPT" => accept,
+
+ "rack.version" => [0,1],
+ "rack.input" => data,
+ "rack.errors" => StringIO.new,
+ "rack.multithread" => true,
+ "rack.multiprocess" => true,
+ "rack.run_once" => false,
+
+ "action_controller.test" => true
)
(headers || {}).each do |key, value|
@@ -272,48 +307,43 @@ def process(method, path, parameters = nil, headers = nil)
ActionController::Base.clear_last_instantiation!
- env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
- @status, @headers, result_body = ActionController::Dispatcher.new.mark_as_test_request!.call(env)
+ app = Rack::Lint.new(@application)
+
+ status, headers, body = app.call(env)
@request_count += 1
- @controller = ActionController::Base.last_instantiation
- @request = @controller.request
- @response = @controller.response
+ if @controller = ActionController::Base.last_instantiation
+ @request = @controller.request
+ @response = @controller.response
- # Decorate the response with the standard behavior of the TestResponse
- # so that things like assert_response can be used in integration
- # tests.
- @response.extend(TestResponseBehavior)
+ # Decorate the response with the standard behavior of the
+ # TestResponse so that things like assert_response can be
+ # used in integration tests.
+ @response.extend(TestResponseBehavior)
+ end
@html_document = nil
- # Inject status back in for backwords compatibility with CGI
- @headers['Status'] = @status
-
- @status, @status_message = @status.split(/ /)
- @status = @status.to_i
+ @status = status.to_i
+ @status_message = StatusCodes::STATUS_CODES[@status]
- cgi_headers = Hash.new { |h,k| h[k] = [] }
- @headers.each do |key, value|
- cgi_headers[key.downcase] << value
- end
- cgi_headers['set-cookie'] = cgi_headers['set-cookie'].first
- @headers = cgi_headers
+ @headers = Rack::Utils::HeaderHash.new(headers)
- @response.headers['cookie'] ||= []
- (@headers['set-cookie'] || []).each do |cookie|
+ (@headers['Set-Cookie'] || []).each do |cookie|
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
@cookies[name] = value
-
- # Fake CGI cookie header
- # DEPRECATE: Use response.headers["Set-Cookie"] instead
- @response.headers['cookie'] << CGI::Cookie::new("name" => name, "value" => value)
end
- return status
+ @body = ""
+ body.each { |part| @body << part }
+
+ return @status
rescue MultiPartNeededException
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
- status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
+ status = process(method, path,
+ multipart_body(parameters, boundary),
+ (headers || {}).merge(
+ {"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
return status
end
@@ -335,7 +365,7 @@ def generic_url_rewriter
"SERVER_PORT" => https? ? "443" : "80",
"HTTPS" => https? ? "on" : "off"
}
- ActionController::UrlRewriter.new(ActionController::RackRequest.new(env), {})
+ UrlRewriter.new(RackRequest.new(env), {})
end
def name_with_prefix(prefix, name)
@@ -349,9 +379,13 @@ def requestify(parameters, prefix=nil)
raise MultiPartNeededException
elsif Hash === parameters
return nil if parameters.empty?
- parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
+ parameters.map { |k,v|
+ requestify(v, name_with_prefix(prefix, k))
+ }.join("&")
elsif Array === parameters
- parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
+ parameters.map { |v|
+ requestify(v, name_with_prefix(prefix, ""))
+ }.join("&")
elsif prefix.nil?
parameters
else
@@ -458,20 +492,25 @@ def reset!
# can use this method to open multiple sessions that ought to be tested
# simultaneously.
def open_session
- session = Integration::Session.new
+ application = ActionController::Dispatcher.new
+ session = Integration::Session.new(application)
# delegate the fixture accessors back to the test instance
extras = Module.new { attr_accessor :delegate, :test_result }
if self.class.respond_to?(:fixture_table_names)
self.class.fixture_table_names.each do |table_name|
name = table_name.tr(".", "_")
next unless respond_to?(name)
- extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) }
+ extras.__send__(:define_method, name) { |*args|
+ delegate.send(name, *args)
+ }
end
end
# delegate add_assertion to the test case
- extras.__send__(:define_method, :add_assertion) { test_result.add_assertion }
+ extras.__send__(:define_method, :add_assertion) {
+ test_result.add_assertion
+ }
session.extend(extras)
session.delegate = self
session.test_result = @_result
@@ -599,7 +638,8 @@ def run(*args) #:nodoc:
# would potentially have to set their values for both Test::Unit::TestCase
# ActionController::IntegrationTest, since by the time the value is set on
# TestCase, IntegrationTest has already been defined and cannot inherit
- # changes to those variables. So, we make those two attributes copy-on-write.
+ # changes to those variables. So, we make those two attributes
+ # copy-on-write.
class << self
def use_transactional_fixtures=(flag) #:nodoc:
View
11 actionpack/lib/action_controller/rack_process.rb
@@ -55,14 +55,7 @@ def key?(key)
end
def cookies
- return {} unless @env["HTTP_COOKIE"]
-
- unless @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
- @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
- @env["rack.request.cookie_hash"] = CGI::Cookie::parse(@env["rack.request.cookie_string"])
- end
-
- @env["rack.request.cookie_hash"]
+ Rack::Request.new(@env).cookies
end
def server_port
@@ -164,7 +157,7 @@ def status
@status || super
end
- def out(output = $stdout, &block)
+ def out(&block)
# Nasty hack because CGI sessions are closed after the normal
# prepare! statement
set_cookies!
View
66 actionpack/lib/action_controller/rescue.rb
@@ -1,13 +1,19 @@
module ActionController #:nodoc:
- # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
- # (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
- # is already implemented by the Action Controller, but the public view should be tailored to your specific application.
- #
- # The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such
- # file exists, an empty response is sent with the correct status code.
+ # Actions that fail to perform as expected throw exceptions. These
+ # exceptions can either be rescued for the public view (with a nice
+ # user-friendly explanation) or for the developers view (with tons of
+ # debugging information). The developers view is already implemented by
+ # the Action Controller, but the public view should be tailored to your
+ # specific application.
#
- # You can override what constitutes a local request by overriding the <tt>local_request?</tt> method in your own controller.
- # Custom rescue behavior is achieved by overriding the <tt>rescue_action_in_public</tt> and <tt>rescue_action_locally</tt> methods.
+ # The default behavior for public exceptions is to render a static html
+ # file with the name of the error code thrown. If no such file exists, an
+ # empty response is sent with the correct status code.
+ #
+ # You can override what constitutes a local request by overriding the
+ # <tt>local_request?</tt> method in your own controller. Custom rescue
+ # behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
+ # and <tt>rescue_action_locally</tt> methods.
module Rescue
LOCALHOST = '127.0.0.1'.freeze
@@ -32,6 +38,9 @@ module Rescue
'ActionView::TemplateError' => 'template_error'
}
+ RESCUES_TEMPLATE_PATH = ActionView::PathSet::Path.new(
+ "#{File.dirname(__FILE__)}/templates", true)
+
def self.included(base) #:nodoc:
base.cattr_accessor :rescue_responses
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
@@ -56,12 +65,15 @@ def process_with_exception(request, response, exception) #:nodoc:
end
protected
- # Exception handler called when the performance of an action raises an exception.
+ # Exception handler called when the performance of an action raises
+ # an exception.
def rescue_action(exception)
- rescue_with_handler(exception) || rescue_action_without_handler(exception)
+ rescue_with_handler(exception) ||
+ rescue_action_without_handler(exception)
end
- # Overwrite to implement custom logging of errors. By default logs as fatal.
+ # Overwrite to implement custom logging of errors. By default
+ # logs as fatal.
def log_error(exception) #:doc:
ActiveSupport::Deprecation.silence do
if ActionView::TemplateError === exception
@@ -75,16 +87,19 @@ def log_error(exception) #:doc:
end
end
- # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
- # default will call render_optional_error_file. Override this method to provide more user friendly error messages.
+ # Overwrite to implement public exception handling (for requests
+ # answering false to <tt>local_request?</tt>). By default will call
+ # render_optional_error_file. Override this method to provide more
+ # user friendly error messages.
def rescue_action_in_public(exception) #:doc:
render_optional_error_file response_code_for_rescue(exception)
end
-
- # Attempts to render a static error page based on the <tt>status_code</tt> thrown,
- # or just return headers if no such file exists. For example, if a 500 error is
- # being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
- # If the file doesn't exist, the body of the response will be left empty.
+
+ # Attempts to render a static error page based on the
+ # <tt>status_code</tt> thrown, or just return headers if no such file
+ # exists. For example, if a 500 error is being handled Rails will first
+ # attempt to render the file at <tt>public/500.html</tt>. If the file
+ # doesn't exist, the body of the response will be left empty.
def render_optional_error_file(status_code)
status = interpret_status(status_code)
path = "#{Rails.public_path}/#{status[0,3]}.html"
@@ -106,11 +121,13 @@ def local_request? #:doc:
# a controller action.
def rescue_action_locally(exception)
@template.instance_variable_set("@exception", exception)
- @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
- @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
+ @template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH)
+ @template.instance_variable_set("@contents",
+ @template.render(:file => template_path_for_local_rescue(exception)))
response.content_type = Mime::HTML
- render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
+ render_for_file(rescues_path("layout"),
+ response_code_for_rescue(exception))
end
def rescue_action_without_handler(exception)
@@ -138,7 +155,7 @@ def perform_action_with_rescue #:nodoc:
end
def rescues_path(template_name)
- "#{File.dirname(__FILE__)}/templates/rescues/#{template_name}.erb"
+ RESCUES_TEMPLATE_PATH["rescues/#{template_name}.erb"]
end
def template_path_for_local_rescue(exception)
@@ -150,8 +167,9 @@ def response_code_for_rescue(exception)
end
def clean_backtrace(exception)
- defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
- Rails.backtrace_cleaner.clean(exception.backtrace) : exception.backtrace
+ defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
+ Rails.backtrace_cleaner.clean(exception.backtrace) :
+ exception.backtrace
end
end
end
View
7 actionpack/lib/action_controller/resources.rb
@@ -283,7 +283,12 @@ def initialize(entity, options)
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
# * <tt>:controller</tt> - Specify the controller name for the routes.
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
- # * <tt>:requirements</tt> - Set custom routing parameter requirements.
+ # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
+ # regular expressions (which must match for the route to match) or extra parameters. For example:
+ #
+ # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
+ #
+ # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
# * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
# # products_path == '/productos'
View
6 actionpack/lib/action_controller/routing.rb
@@ -83,9 +83,11 @@ module ActionController
# This sets up +blog+ as the default controller if no other is specified.
# This means visiting '/' would invoke the blog controller.
#
- # More formally, you can define defaults in a route with the <tt>:defaults</tt> key.
+ # More formally, you can include arbitrary parameters in the route, thus:
#
- # map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
+ # map.connect ':controller/:action/:id', :action => 'show', :page => 'Dashboard'
+ #
+ # This will pass the :page parameter to all incoming requests that match this route.
#
# Note: The default routes, as provided by the Rails generator, make all actions in every
# controller accessible via GET requests. You should consider removing them or commenting
View
10 actionpack/lib/action_controller/session/active_record_store.rb
@@ -56,6 +56,8 @@ def model
class ActiveRecordStore
# The default Active Record class.
class Session < ActiveRecord::Base
+ ##
+ # :singleton-method:
# Customizable data column name. Defaults to 'data'.
cattr_accessor :data_column_name
self.data_column_name = 'data'
@@ -166,17 +168,25 @@ def raise_on_session_data_overflow!
# binary session data in a +text+ column. For higher performance,
# store in a +blob+ column instead and forgo the Base64 encoding.
class SqlBypass
+ ##
+ # :singleton-method:
# Use the ActiveRecord::Base.connection by default.
cattr_accessor :connection
+ ##
+ # :singleton-method:
# The table name defaults to 'sessions'.
cattr_accessor :table_name
@@table_name = 'sessions'
+ ##
+ # :singleton-method:
# The session id field defaults to 'session_id'.
cattr_accessor :session_id_column
@@session_id_column = 'session_id'
+ ##
+ # :singleton-method:
# The data field defaults to 'data'.
cattr_accessor :data_column
@@data_column = 'data'
View
4 actionpack/lib/action_controller/templates/rescues/diagnostics.erb
@@ -6,6 +6,6 @@
</h1>
<pre><%=h @exception.clean_message %></pre>
-<%= render(:file => @rescues_path + "/_trace.erb") %>
+<%= render :file => @rescues_path["rescues/_trace.erb"] %>
-<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
+<%= render :file => @rescues_path["rescues/_request_and_response.erb"] %>
View
4 actionpack/lib/action_controller/templates/rescues/template_error.erb
@@ -15,7 +15,7 @@
<% @real_exception = @exception
@exception = @exception.original_exception || @exception %>
-<%= render(:file => @rescues_path + "/_trace.erb") %>
+<%= render :file => @rescues_path["rescues/_trace.erb"] %>
<% @exception = @real_exception %>
-<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
+<%= render :file => @rescues_path["rescues/_request_and_response.erb"] %>
View
15 actionpack/lib/action_view/base.rb
@@ -5,7 +5,7 @@ class ActionViewError < StandardError #:nodoc:
class MissingTemplate < ActionViewError #:nodoc:
def initialize(paths, path, template_format = nil)
full_template_path = path.include?('.') ? path : "#{path}.erb"
- display_paths = paths.join(':')
+ display_paths = paths.compact.join(":")
template_type = (path =~ /layouts/i) ? 'layout' : 'template'
super("Missing #{template_type} #{full_template_path} in view path #{display_paths}")
end
@@ -183,13 +183,17 @@ def self.exempt_from_layout(*extensions)
@@exempt_from_layout.merge(regexps)
end
+ @@debug_rjs = false
+ ##
+ # :singleton-method:
# Specify whether RJS responses should be wrapped in a try/catch block
# that alert()s the caught exception (and then re-raises it).
- @@debug_rjs = false
cattr_accessor :debug_rjs
- # A warning will be displayed whenever an action results in a cache miss on your view paths.
@@warn_cache_misses = false
+ ##
+ # :singleton-method:
+ # A warning will be displayed whenever an action results in a cache miss on your view paths.
cattr_accessor :warn_cache_misses
attr_internal :request
@@ -275,7 +279,7 @@ def template_format
if defined? @template_format
@template_format
elsif controller && controller.respond_to?(:request)
- @template_format = controller.request.template_format
+ @template_format = controller.request.template_format.to_sym
else
@template_format = :html
end
@@ -327,9 +331,6 @@ def _pick_template(template_path)
elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) &&
(template = self.view_paths["#{template_file_name}.#{first_render.format_and_extension}"])
template
- elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"]
- @template_format = :html
- template
else
template = Template.new(template_path, view_paths)
View
2 actionpack/lib/action_view/helpers/tag_helper.rb
@@ -97,7 +97,7 @@ def cdata_section(content)
# Returns an escaped version of +html+ without affecting existing escaped entities.
#
# ==== Examples
- # escape_once("1 > 2 &amp; 3")
+ # escape_once("1 < 2 &amp; 3")
# # => "1 &lt; 2 &amp; 3"
#
# escape_once("&lt;&lt; Accept & Checkout")
View
10 actionpack/lib/action_view/paths.rb
@@ -41,14 +41,22 @@ def unshift(*objs)
class Path #:nodoc:
attr_reader :path, :paths
- delegate :to_s, :to_str, :hash, :inspect, :to => :path
+ delegate :hash, :inspect, :to => :path
def initialize(path, load = false)
raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
@path = path.freeze
reload! if load
end
+ def to_s
+ if defined?(RAILS_ROOT)
+ path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
+ else
+ path.to_s
+ end
+ end
+
def ==(path)
to_str == path.to_str
end
View
2 actionpack/lib/action_view/template.rb
@@ -9,9 +9,9 @@ class Template
def initialize(template_path, load_paths = [])
template_path = template_path.dup
+ @load_path, @filename = find_full_path(template_path, load_paths)
@base_path, @name, @format, @extension = split(template_path)
@base_path.to_s.gsub!(/\/$/, '') # Push to split method
- @load_path, @filename = find_full_path(template_path, load_paths)
# Extend with partial super powers
extend RenderablePartial if @name =~ /^_/
View
2 actionpack/lib/action_view/template_handlers/erb.rb
@@ -3,6 +3,8 @@ module TemplateHandlers
class ERB < TemplateHandler
include Compilable
+ ##
+ # :singleton-method:
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERb documentation for suitable values.
cattr_accessor :erb_trim_mode
View
1 actionpack/lib/action_view/template_handlers/rjs.rb
@@ -4,6 +4,7 @@ class RJS < TemplateHandler
include Compilable
def compile(template)
+ "@template_format = :html;" +
"controller.response.content_type ||= Mime::JS;" +
"update_page do |page|;#{template.source}\nend"
end
View
4 actionpack/test/controller/caching_test.rb
@@ -67,7 +67,7 @@ def setup
def teardown
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
-
+ ActionController::Routing::Routes.clear!
ActionController::Base.perform_caching = false
end
@@ -401,7 +401,7 @@ def test_forbidden_is_not_cached
def test_xml_version_of_resource_is_treated_as_different_cache
with_routing do |set|
- ActionController::Routing::Routes.draw do |map|
+ set.draw do |map|
map.connect ':controller/:action.:format'
map.connect ':controller/:action'
end
View
262 actionpack/test/controller/cgi_test.rb
@@ -1,262 +0,0 @@
-require 'abstract_unit'
-
-class BaseCgiTest < Test::Unit::TestCase
- def setup
- @request_hash = {
- "HTTP_MAX_FORWARDS" => "10",
- "SERVER_NAME" => "glu.ttono.us:8007",
- "FCGI_ROLE" => "RESPONDER",
- "AUTH_TYPE" => "Basic",
- "HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
- "HTTP_ACCEPT_CHARSET" => "UTF-8",
- "HTTP_ACCEPT_ENCODING" => "gzip, deflate",
- "HTTP_CACHE_CONTROL" => "no-cache, max-age=0",
- "HTTP_PRAGMA" => "no-cache",
- "HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)",
- "PATH_INFO" => "/homepage/",
- "HTTP_ACCEPT_LANGUAGE" => "en",
- "HTTP_NEGOTIATE" => "trans",
- "HTTP_HOST" => "glu.ttono.us:8007",
- "HTTP_REFERER" => "http://www.google.com/search?q=glu.ttono.us",
- "HTTP_FROM" => "googlebot",
- "SERVER_PROTOCOL" => "HTTP/1.1",
- "REDIRECT_URI" => "/dispatch.fcgi",
- "SCRIPT_NAME" => "/dispatch.fcgi",
- "SERVER_ADDR" => "207.7.108.53",
- "REMOTE_ADDR" => "207.7.108.53",
- "REMOTE_HOST" => "google.com",
- "REMOTE_IDENT" => "kevin",
- "REMOTE_USER" => "kevin",
- "SERVER_SOFTWARE" => "lighttpd/1.4.5",
- "HTTP_COOKIE" => "_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes",
- "HTTP_X_FORWARDED_SERVER" => "glu.ttono.us",
- "REQUEST_URI" => "/admin",
- "DOCUMENT_ROOT" => "/home/kevinc/sites/typo/public",
- "PATH_TRANSLATED" => "/home/kevinc/sites/typo/public/homepage/",
- "SERVER_PORT" => "8007",
- "QUERY_STRING" => "",
- "REMOTE_PORT" => "63137",
- "GATEWAY_INTERFACE" => "CGI/1.1",
- "HTTP_X_FORWARDED_FOR" => "65.88.180.234",
- "HTTP_ACCEPT" => "*/*",
- "SCRIPT_FILENAME" => "/home/kevinc/sites/typo/public/dispatch.fcgi",
- "REDIRECT_STATUS" => "200",
- "REQUEST_METHOD" => "GET"
- }
- # some Nokia phone browsers omit the space after the semicolon separator.
- # some developers have grown accustomed to using comma in cookie values.
- @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}
- @cgi = CGI.new
- class << @cgi; attr_accessor :env_table end
- @cgi.env_table = @request_hash
- @request = ActionController::CgiRequest.new(@cgi)
- end
-
- def default_test; end
-
- private
-
- def set_content_data(data)
- @request.env['REQUEST_METHOD'] = 'POST'
- @request.env['CONTENT_LENGTH'] = data.length
- @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
- @request.env['RAW_POST_DATA'] = data
- end
-end
-
-class CgiRequestTest < BaseCgiTest
- def test_proxy_request
- assert_equal 'glu.ttono.us', @request.host_with_port
- end
-
- def test_http_host
- @request_hash.delete "HTTP_X_FORWARDED_HOST"
- @request_hash['HTTP_HOST'] = "rubyonrails.org:8080"
- assert_equal "rubyonrails.org:8080", @request.host_with_port
-
- @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
- assert_equal "www.secondhost.org", @request.host(true)
- end
-
- def test_http_host_with_default_port_overrides_server_port
- @request_hash.delete "HTTP_X_FORWARDED_HOST"
- @request_hash['HTTP_HOST'] = "rubyonrails.org"
- assert_equal "rubyonrails.org", @request.host_with_port
- end
-
- def test_host_with_port_defaults_to_server_name_if_no_host_headers
- @request_hash.delete "HTTP_X_FORWARDED_HOST"
- @request_hash.delete "HTTP_HOST"
- assert_equal "glu.ttono.us:8007", @request.host_with_port
- end
-
- def test_host_with_port_falls_back_to_server_addr_if_necessary
- @request_hash.delete "HTTP_X_FORWARDED_HOST"
- @request_hash.delete "HTTP_HOST"
- @request_hash.delete "SERVER_NAME"
- assert_equal "207.7.108.53:8007", @request.host_with_port
- end
-
- def test_host_with_port_if_http_standard_port_is_specified
- @request_hash['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80"
- assert_equal "glu.ttono.us", @request.host_with_port
- end
-
- def test_host_with_port_if_https_standard_port_is_specified
- @request_hash['HTTP_X_FORWARDED_PROTO'] = "https"
- @request_hash['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443"
- assert_equal "glu.ttono.us", @request.host_with_port
- end
-
- def test_host_if_ipv6_reference
- @request_hash.delete "HTTP_X_FORWARDED_HOST"
- @request_hash['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]"
- assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
- end
-
- def test_host_if_ipv6_reference_with_port
- @request_hash.delete "HTTP_X_FORWARDED_HOST"
- @request_hash['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008"
- assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
- end
-
- def test_cgi_environment_variables
- assert_equal "Basic", @request.auth_type
- assert_equal 0, @request.content_length
- assert_equal nil, @request.content_type
- assert_equal "CGI/1.1", @request.gateway_interface
- assert_equal "*/*", @request.accept
- assert_equal "UTF-8", @request.accept_charset
- assert_equal "gzip, deflate", @request.accept_encoding
- assert_equal "en", @request.accept_language
- assert_equal "no-cache, max-age=0", @request.cache_control
- assert_equal "googlebot", @request.from
- assert_equal "glu.ttono.us", @request.host
- assert_equal "trans", @request.negotiate
- assert_equal "no-cache", @request.pragma
- assert_equal "http://www.google.com/search?q=glu.ttono.us", @request.referer
- assert_equal "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)", @request.user_agent
- assert_equal "/homepage/", @request.path_info
- assert_equal "/home/kevinc/sites/typo/public/homepage/", @request.path_translated
- assert_equal "", @request.query_string
- assert_equal "207.7.108.53", @request.remote_addr
- assert_equal "google.com", @request.remote_host
- assert_equal "kevin", @request.remote_ident
- assert_equal "kevin", @request.remote_user
- assert_equal :get, @request.request_method
- assert_equal "/dispatch.fcgi", @request.script_name
- assert_equal "glu.ttono.us:8007", @request.server_name
- assert_equal 8007, @request.server_port
- assert_equal "HTTP/1.1", @request.server_protocol
- assert_equal "lighttpd", @request.server_software
- end
-
- def test_cookie_syntax_resilience
- cookies = CGI::Cookie::parse(@request_hash["HTTP_COOKIE"]);
- assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect
- assert_equal ["yes"], cookies["is_admin"], cookies.inspect
-
- alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]);
- assert_equal ["c84ace847,96670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect
- assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect
- end
-end
-
-class CgiRequestParamsParsingTest < BaseCgiTest
- def test_doesnt_break_when_content_type_has_charset
- set_content_data 'flamenco=love'
-
- assert_equal({"flamenco"=> "love"}, @request.request_parameters)
- end
-
- def test_doesnt_interpret_request_uri_as_query_string_when_missing
- @request.env['REQUEST_URI'] = 'foo'
- assert_equal({}, @request.query_parameters)
- end
-end
-
-class CgiRequestContentTypeTest < BaseCgiTest
- def test_html_content_type_verification
- @request.env['CONTENT_TYPE'] = Mime::HTML.to_s
- assert @request.content_type.verify_request?
- end
-
- def test_xml_content_type_verification
- @request.env['CONTENT_TYPE'] = Mime::XML.to_s
- assert !@request.content_type.verify_request?
- end
-end
-
-class CgiRequestMethodTest < BaseCgiTest
- def test_get
- assert_equal :get, @request.request_method
- end
-
- def test_post
- @request.env['REQUEST_METHOD'] = 'POST'
- assert_equal :post, @request.request_method
- end
-
- def test_put
- set_content_data '_method=put'
-
- assert_equal :put, @request.request_method
- end
-
- def test_delete
- set_content_data '_method=delete'
-
- assert_equal :delete, @request.request_method
- end
-end
-
-class CgiRequestNeedsRewoundTest < BaseCgiTest
- def test_body_should_be_rewound
- data = 'foo'
- fake_cgi = Struct.new(:env_table, :query_string, :stdinput).new(@request_hash, '', StringIO.new(data))
- fake_cgi.env_table['CONTENT_LENGTH'] = data.length
- fake_cgi.env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
-
- # Read the request body by parsing params.
- request = ActionController::CgiRequest.new(fake_cgi)
- request.request_parameters
-
- # Should have rewound the body.
- assert_equal 0, request.body.pos
- end
-end
-
-uses_mocha 'CGI Response' do
- class CgiResponseTest < BaseCgiTest
- def setup
- super
- @cgi.expects(:header).returns("HTTP/1.0 200 OK\nContent-Type: text/html\n")
- @response = ActionController::CgiResponse.new(@cgi)
- @output = StringIO.new('')
- end
-
- def test_simple_output
- @response.body = "Hello, World!"
-
- @response.out(@output)
- assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\nHello, World!", @output.string
- end
-
- def test_head_request
- @cgi.env_table['REQUEST_METHOD'] = 'HEAD'
- @response.body = "Hello, World!"
-
- @response.out(@output)
- assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n", @output.string
- end
-
- def test_streaming_block
- @response.body = Proc.new do |response, output|
- 5.times { |n| output.write(n) }
- end
-
- @response.out(@output)
- assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n01234", @output.string
- end
- end
-end
View
38 actionpack/test/controller/dispatcher_test.rb
@@ -6,7 +6,6 @@ class DispatcherTest < Test::Unit::TestCase
Dispatcher = ActionController::Dispatcher
def setup
- @output = StringIO.new
ENV['REQUEST_METHOD'] = 'GET'
# Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks
@@ -16,7 +15,7 @@ def setup
Dispatcher.stubs(:require_dependency)
- @dispatcher = Dispatcher.new(@output)
+ @dispatcher = Dispatcher.new
end
def teardown
@@ -25,17 +24,17 @@ def teardown
def test_clears_dependencies_after_dispatch_if_in_loading_mode
ActiveSupport::Dependencies.expects(:clear).once
- dispatch(@output, false)
+ dispatch(false)
end
def test_reloads_routes_before_dispatch_if_in_loading_mode
ActionController::Routing::Routes.expects(:reload).once
- dispatch(@output, false)
+ dispatch(false)
end
def test_clears_asset_tag_cache_before_dispatch_if_in_loading_mode
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.expects(:clear).once
- dispatch(@output, false)
+ dispatch(false)
end
def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode