actionpack 3.0.20 → 3.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (161) hide show
  1. data/CHANGELOG +88 -142
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +5 -6
  4. data/lib/abstract_controller.rb +1 -0
  5. data/lib/abstract_controller/asset_paths.rb +2 -2
  6. data/lib/abstract_controller/base.rb +24 -19
  7. data/lib/abstract_controller/callbacks.rb +19 -19
  8. data/lib/abstract_controller/helpers.rb +11 -13
  9. data/lib/abstract_controller/layouts.rb +4 -5
  10. data/lib/abstract_controller/railties/routes_helpers.rb +18 -0
  11. data/lib/abstract_controller/rendering.rb +34 -31
  12. data/lib/abstract_controller/url_for.rb +27 -0
  13. data/lib/abstract_controller/view_paths.rb +31 -6
  14. data/lib/action_controller.rb +5 -3
  15. data/lib/action_controller/base.rb +15 -16
  16. data/lib/action_controller/caching.rb +2 -2
  17. data/lib/action_controller/caching/actions.rb +11 -12
  18. data/lib/action_controller/caching/fragments.rb +41 -19
  19. data/lib/action_controller/caching/pages.rb +3 -9
  20. data/lib/action_controller/caching/sweeping.rb +0 -1
  21. data/lib/action_controller/deprecated.rb +1 -1
  22. data/lib/action_controller/log_subscriber.rb +1 -1
  23. data/lib/action_controller/metal.rb +78 -20
  24. data/lib/action_controller/metal/compatibility.rb +0 -9
  25. data/lib/action_controller/metal/conditional_get.rb +9 -9
  26. data/lib/action_controller/metal/data_streaming.rb +145 -0
  27. data/lib/action_controller/metal/force_ssl.rb +35 -0
  28. data/lib/action_controller/metal/head.rb +1 -1
  29. data/lib/action_controller/metal/helpers.rb +37 -44
  30. data/lib/action_controller/metal/hide_actions.rb +2 -3
  31. data/lib/action_controller/metal/http_authentication.rb +41 -38
  32. data/lib/action_controller/metal/implicit_render.rb +13 -13
  33. data/lib/action_controller/metal/instrumentation.rb +2 -2
  34. data/lib/action_controller/metal/mime_responds.rb +25 -19
  35. data/lib/action_controller/metal/params_wrapper.rb +224 -0
  36. data/lib/action_controller/metal/redirecting.rb +6 -2
  37. data/lib/action_controller/metal/renderers.rb +50 -36
  38. data/lib/action_controller/metal/rendering.rb +34 -25
  39. data/lib/action_controller/metal/request_forgery_protection.rb +18 -36
  40. data/lib/action_controller/metal/responder.rb +47 -12
  41. data/lib/action_controller/metal/streaming.rb +244 -138
  42. data/lib/action_controller/metal/testing.rb +0 -9
  43. data/lib/action_controller/metal/url_for.rb +12 -14
  44. data/lib/action_controller/railtie.rb +19 -37
  45. data/lib/action_controller/railties/paths.rb +24 -0
  46. data/lib/action_controller/record_identifier.rb +4 -10
  47. data/lib/action_controller/test_case.rb +36 -19
  48. data/lib/action_controller/vendor/html-scanner/html/node.rb +5 -5
  49. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +3 -3
  50. data/lib/action_controller/vendor/html-scanner/html/selector.rb +2 -0
  51. data/lib/action_dispatch.rb +4 -1
  52. data/lib/action_dispatch/http/cache.rb +5 -32
  53. data/lib/action_dispatch/http/filter_parameters.rb +3 -1
  54. data/lib/action_dispatch/http/mime_negotiation.rb +22 -3
  55. data/lib/action_dispatch/http/mime_type.rb +45 -5
  56. data/lib/action_dispatch/http/rack_cache.rb +58 -0
  57. data/lib/action_dispatch/http/request.rb +27 -41
  58. data/lib/action_dispatch/http/response.rb +56 -54
  59. data/lib/action_dispatch/http/upload.rb +1 -11
  60. data/lib/action_dispatch/http/url.rb +102 -42
  61. data/lib/action_dispatch/middleware/callbacks.rb +8 -25
  62. data/lib/action_dispatch/middleware/closed_error.rb +7 -0
  63. data/lib/action_dispatch/middleware/cookies.rb +37 -15
  64. data/lib/action_dispatch/middleware/flash.rb +80 -11
  65. data/lib/action_dispatch/middleware/params_parser.rb +2 -2
  66. data/lib/action_dispatch/middleware/reloader.rb +76 -0
  67. data/lib/action_dispatch/middleware/session/abstract_store.rb +56 -226
  68. data/lib/action_dispatch/middleware/session/cookie_store.rb +20 -44
  69. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -46
  70. data/lib/action_dispatch/middleware/show_exceptions.rb +15 -2
  71. data/lib/action_dispatch/middleware/stack.rb +50 -17
  72. data/lib/action_dispatch/middleware/static.rb +41 -29
  73. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +3 -3
  74. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +3 -3
  75. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +3 -3
  76. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +4 -2
  77. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +2 -6
  78. data/lib/action_dispatch/railtie.rb +8 -0
  79. data/lib/action_dispatch/routing.rb +13 -1
  80. data/lib/action_dispatch/routing/mapper.rb +345 -227
  81. data/lib/action_dispatch/routing/polymorphic_routes.rb +33 -13
  82. data/lib/action_dispatch/routing/redirection.rb +110 -0
  83. data/lib/action_dispatch/routing/route.rb +15 -13
  84. data/lib/action_dispatch/routing/route_set.rb +116 -90
  85. data/lib/action_dispatch/routing/routes_proxy.rb +35 -0
  86. data/lib/action_dispatch/routing/url_for.rb +25 -1
  87. data/lib/action_dispatch/testing/assertions/response.rb +8 -10
  88. data/lib/action_dispatch/testing/assertions/routing.rb +15 -15
  89. data/lib/action_dispatch/testing/assertions/selector.rb +13 -220
  90. data/lib/action_dispatch/testing/integration.rb +37 -28
  91. data/lib/action_dispatch/testing/performance_test.rb +1 -3
  92. data/lib/action_dispatch/testing/test_process.rb +1 -1
  93. data/lib/action_dispatch/testing/test_request.rb +9 -3
  94. data/lib/action_dispatch/testing/test_response.rb +4 -111
  95. data/lib/action_pack.rb +1 -1
  96. data/lib/action_pack/version.rb +3 -3
  97. data/lib/action_view.rb +39 -24
  98. data/lib/action_view/base.rb +61 -86
  99. data/lib/action_view/buffers.rb +43 -0
  100. data/lib/action_view/context.rb +21 -24
  101. data/lib/action_view/flows.rb +79 -0
  102. data/lib/action_view/helpers.rb +8 -6
  103. data/lib/action_view/helpers/active_model_helper.rb +0 -23
  104. data/lib/action_view/helpers/asset_paths.rb +79 -0
  105. data/lib/action_view/helpers/asset_tag_helper.rb +30 -500
  106. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +147 -0
  107. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +101 -0
  108. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +200 -0
  109. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +152 -0
  110. data/lib/action_view/helpers/atom_feed_helper.rb +2 -2
  111. data/lib/action_view/helpers/cache_helper.rb +11 -19
  112. data/lib/action_view/helpers/capture_helper.rb +19 -8
  113. data/lib/action_view/helpers/controller_helper.rb +21 -0
  114. data/lib/action_view/helpers/csrf_helper.rb +22 -4
  115. data/lib/action_view/helpers/date_helper.rb +36 -22
  116. data/lib/action_view/helpers/form_helper.rb +199 -113
  117. data/lib/action_view/helpers/form_options_helper.rb +10 -11
  118. data/lib/action_view/helpers/form_tag_helper.rb +94 -22
  119. data/lib/action_view/helpers/javascript_helper.rb +24 -107
  120. data/lib/action_view/helpers/number_helper.rb +36 -33
  121. data/lib/action_view/helpers/output_safety_helper.rb +38 -0
  122. data/lib/action_view/helpers/record_tag_helper.rb +6 -6
  123. data/lib/action_view/helpers/rendering_helper.rb +90 -0
  124. data/lib/action_view/helpers/sanitize_helper.rb +2 -2
  125. data/lib/action_view/helpers/sprockets_helper.rb +69 -0
  126. data/lib/action_view/helpers/tag_helper.rb +34 -12
  127. data/lib/action_view/helpers/text_helper.rb +30 -145
  128. data/lib/action_view/helpers/translation_helper.rb +10 -17
  129. data/lib/action_view/helpers/url_helper.rb +70 -67
  130. data/lib/action_view/locale/en.yml +1 -1
  131. data/lib/action_view/lookup_context.rb +36 -14
  132. data/lib/action_view/{paths.rb → path_set.rb} +9 -8
  133. data/lib/action_view/railtie.rb +12 -4
  134. data/lib/action_view/renderer/abstract_renderer.rb +36 -0
  135. data/lib/action_view/{render/partials.rb → renderer/partial_renderer.rb} +147 -146
  136. data/lib/action_view/renderer/renderer.rb +54 -0
  137. data/lib/action_view/renderer/streaming_template_renderer.rb +106 -0
  138. data/lib/action_view/renderer/template_renderer.rb +74 -0
  139. data/lib/action_view/template.rb +91 -54
  140. data/lib/action_view/template/error.rb +11 -8
  141. data/lib/action_view/template/handler.rb +9 -1
  142. data/lib/action_view/template/handlers.rb +9 -9
  143. data/lib/action_view/template/handlers/builder.rb +4 -4
  144. data/lib/action_view/template/handlers/erb.rb +21 -41
  145. data/lib/action_view/template/resolver.rb +171 -57
  146. data/lib/action_view/template/text.rb +0 -4
  147. data/lib/action_view/test_case.rb +32 -16
  148. data/lib/action_view/testing/resolvers.rb +16 -10
  149. data/lib/sprockets/railtie.rb +100 -0
  150. metadata +162 -140
  151. checksums.yaml +0 -7
  152. data/lib/action_controller/deprecated/base.rb +0 -143
  153. data/lib/action_controller/deprecated/dispatcher.rb +0 -28
  154. data/lib/action_controller/deprecated/url_writer.rb +0 -14
  155. data/lib/action_dispatch/routing/deprecated_mapper.rb +0 -525
  156. data/lib/action_view/helpers/prototype_helper.rb +0 -851
  157. data/lib/action_view/helpers/raw_output_helper.rb +0 -18
  158. data/lib/action_view/helpers/scriptaculous_helper.rb +0 -263
  159. data/lib/action_view/render/layouts.rb +0 -83
  160. data/lib/action_view/render/rendering.rb +0 -67
  161. data/lib/action_view/template/handlers/rjs.rb +0 -17
@@ -2,6 +2,7 @@ require 'active_support/core_ext/class/attribute'
2
2
  require 'active_support/core_ext/object/blank'
3
3
 
4
4
  module ActionController
5
+ # See <tt>Renderers.add</tt>
5
6
  def self.add_renderer(key, &block)
6
7
  Renderers.add(key, &block)
7
8
  end
@@ -15,30 +16,12 @@ module ActionController
15
16
  end
16
17
 
17
18
  module ClassMethods
18
- def _write_render_options
19
- renderers = _renderers.map do |name, value|
20
- <<-RUBY_EVAL
21
- if options.key?(:#{name})
22
- _process_options(options)
23
- return _render_option_#{name}(options.delete(:#{name}), options)
24
- end
25
- RUBY_EVAL
26
- end
27
-
28
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
29
- def _handle_render_options(options)
30
- #{renderers.join}
31
- end
32
- RUBY_EVAL
33
- end
34
-
35
19
  def use_renderers(*args)
36
20
  new = _renderers.dup
37
21
  args.each do |key|
38
22
  new[key] = RENDERERS[key]
39
23
  end
40
24
  self._renderers = new.freeze
41
- _write_render_options
42
25
  end
43
26
  alias use_renderer use_renderers
44
27
  end
@@ -47,26 +30,64 @@ module ActionController
47
30
  _handle_render_options(options) || super
48
31
  end
49
32
 
33
+ def _handle_render_options(options)
34
+ _renderers.each do |name, value|
35
+ if options.key?(name.to_sym)
36
+ _process_options(options)
37
+ return send("_render_option_#{name}", options.delete(name.to_sym), options)
38
+ end
39
+ end
40
+ nil
41
+ end
42
+
43
+ # Hash of available renderers, mapping a renderer name to its proc.
44
+ # Default keys are :json, :js, :xml.
50
45
  RENDERERS = {}
46
+
47
+ # Adds a new renderer to call within controller actions.
48
+ # A renderer is invoked by passing its name as an option to
49
+ # <tt>AbstractController::Rendering#render</tt>. To create a renderer
50
+ # pass it a name and a block. The block takes two arguments, the first
51
+ # is the value paired with its key and the second is the remaining
52
+ # hash of options passed to +render+.
53
+ #
54
+ # === Example
55
+ # Create a csv renderer:
56
+ #
57
+ # ActionController::Renderers.add :csv do |obj, options|
58
+ # filename = options[:filename] || 'data'
59
+ # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
60
+ # send_data str, :type => Mime::CSV,
61
+ # :disposition => "attachment; filename=#{filename}.csv"
62
+ # end
63
+ #
64
+ # Note that we used Mime::CSV for the csv mime type as it comes with Rails.
65
+ # For a custom renderer, you'll need to register a mime type with
66
+ # <tt>Mime::Type.register</tt>.
67
+ #
68
+ # To use the csv renderer in a controller action:
69
+ #
70
+ # def show
71
+ # @csvable = Csvable.find(params[:id])
72
+ # respond_to do |format|
73
+ # format.html
74
+ # format.csv { render :csv => @csvable, :filename => @csvable.name }
75
+ # }
76
+ # end
77
+ # To use renderers and their mime types in more concise ways, see
78
+ # <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
79
+ # <tt>ActionController::MimeResponds#respond_with</tt>
51
80
  def self.add(key, &block)
52
81
  define_method("_render_option_#{key}", &block)
53
82
  RENDERERS[key] = block
54
- All._write_render_options
55
83
  end
56
84
 
57
85
  module All
58
86
  extend ActiveSupport::Concern
59
87
  include Renderers
60
88
 
61
- INCLUDED = []
62
89
  included do
63
90
  self._renderers = RENDERERS
64
- _write_render_options
65
- INCLUDED << self
66
- end
67
-
68
- def self._write_render_options
69
- INCLUDED.each(&:_write_render_options)
70
91
  end
71
92
  end
72
93
 
@@ -74,24 +95,17 @@ module ActionController
74
95
  json = json.to_json(options) unless json.kind_of?(String)
75
96
  json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
76
97
  self.content_type ||= Mime::JSON
77
- self.response_body = json
98
+ json
78
99
  end
79
100
 
80
101
  add :js do |js, options|
81
102
  self.content_type ||= Mime::JS
82
- self.response_body = js.respond_to?(:to_js) ? js.to_js(options) : js
103
+ js.respond_to?(:to_js) ? js.to_js(options) : js
83
104
  end
84
105
 
85
106
  add :xml do |xml, options|
86
107
  self.content_type ||= Mime::XML
87
- self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
88
- end
89
-
90
- add :update do |proc, options|
91
- view_context = self.view_context
92
- generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc)
93
- self.content_type = Mime::JS
94
- self.response_body = generator.to_s
108
+ xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml
95
109
  end
96
110
  end
97
111
  end
@@ -2,7 +2,6 @@ module ActionController
2
2
  module Rendering
3
3
  extend ActiveSupport::Concern
4
4
 
5
- include ActionController::RackDelegation
6
5
  include AbstractController::Rendering
7
6
 
8
7
  # Before processing, set the request formats in current controller formats.
@@ -19,38 +18,48 @@ module ActionController
19
18
  response_body
20
19
  end
21
20
 
22
- private
23
-
24
- # Normalize arguments by catching blocks and setting them on :update.
25
- def _normalize_args(action=nil, options={}, &blk) #:nodoc:
26
- options = super
27
- options[:update] = blk if block_given?
28
- options
21
+ # Overwrite render_to_string because body can now be set to a rack body.
22
+ def render_to_string(*)
23
+ if self.response_body = super
24
+ string = ""
25
+ response_body.each { |r| string << r }
26
+ string
29
27
  end
28
+ ensure
29
+ self.response_body = nil
30
+ end
30
31
 
31
- # Normalize both text and status options.
32
- def _normalize_options(options) #:nodoc:
33
- if options.key?(:text) && options[:text].respond_to?(:to_text)
34
- options[:text] = options[:text].to_text
35
- end
32
+ private
33
+
34
+ # Normalize arguments by catching blocks and setting them on :update.
35
+ def _normalize_args(action=nil, options={}, &blk) #:nodoc:
36
+ options = super
37
+ options[:update] = blk if block_given?
38
+ options
39
+ end
36
40
 
37
- if options[:status]
38
- options[:status] = Rack::Utils.status_code(options[:status])
39
- end
41
+ # Normalize both text and status options.
42
+ def _normalize_options(options) #:nodoc:
43
+ if options.key?(:text) && options[:text].respond_to?(:to_text)
44
+ options[:text] = options[:text].to_text
45
+ end
40
46
 
41
- super
47
+ if options[:status]
48
+ options[:status] = Rack::Utils.status_code(options[:status])
42
49
  end
43
50
 
44
- # Process controller specific options, as status, content-type and location.
45
- def _process_options(options) #:nodoc:
46
- status, content_type, location = options.values_at(:status, :content_type, :location)
51
+ super
52
+ end
47
53
 
48
- self.status = status if status
49
- self.content_type = content_type if content_type
50
- self.headers["Location"] = url_for(location) if location
54
+ # Process controller specific options, as status, content-type and location.
55
+ def _process_options(options) #:nodoc:
56
+ status, content_type, location = options.values_at(:status, :content_type, :location)
51
57
 
52
- super
53
- end
58
+ self.status = status if status
59
+ self.content_type = content_type if content_type
60
+ self.headers["Location"] = url_for(location) if location
54
61
 
62
+ super
63
+ end
55
64
  end
56
65
  end
@@ -4,45 +4,27 @@ module ActionController #:nodoc:
4
4
  class InvalidAuthenticityToken < ActionControllerError #:nodoc:
5
5
  end
6
6
 
7
- # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current
8
- # web application, not a forged link from another site, is done by embedding a token based on a random
9
- # string stored in the session (which an attacker wouldn't know) in all forms and Ajax requests generated
10
- # by Rails and then verifying the authenticity of that token in the controller. Only HTML/JavaScript
11
- # requests are checked, so this will not protect your XML API (presumably you'll have a different
12
- # authentication scheme there anyway). Also, GET requests are not protected as these should be
13
- # idempotent anyway.
7
+ # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
8
+ # by including a token in the rendered html for your application. This token is
9
+ # stored as a random string in the session, to which an attacker does not have
10
+ # access. When a request reaches your application, \Rails then verifies the received
11
+ # token with the token in the session. Only HTML and javascript requests are checked,
12
+ # so this will not protect your XML API (presumably you'll have a different
13
+ # authentication scheme there anyway). Also, GET requests are not protected as these
14
+ # should be idempotent.
14
15
  #
15
- # This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an
16
- # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the
17
- # error message in production by editing public/422.html. A call to this method in ApplicationController is
18
- # generated by default in post-Rails 2.0 applications.
16
+ # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
17
+ # which will check the token and raise an ActionController::InvalidAuthenticityToken
18
+ # if it doesn't match what was expected. A call to this method is generated for new
19
+ # \Rails applications by default. You can customize the error message by editing
20
+ # public/422.html.
19
21
  #
20
- # The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form
21
- # manually (without the use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to
22
- # include a hidden field named like that and set its value to what is returned by
23
- # <tt>form_authenticity_token</tt>.
24
- #
25
- # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails
26
- # 1.x, add this to config/environments/test.rb:
27
- #
28
- # # Disable request forgery protection in test environment
29
- # config.action_controller.allow_forgery_protection = false
30
- #
31
- # == Learn more about CSRF (Cross-Site Request Forgery) attacks
32
- #
33
- # Here are some resources:
34
- # * http://isc.sans.org/diary.html?storyid=1750
35
- # * http://en.wikipedia.org/wiki/Cross-site_request_forgery
36
- #
37
- # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application.
38
- # There are a few guidelines you should follow:
39
- #
40
- # * Keep your GET requests safe and idempotent. More reading material:
41
- # * http://www.xml.com/pub/a/2002/04/24/deviant.html
42
- # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
43
- # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look
44
- # for "Expires: at end of session"
22
+ # The token parameter is named <tt>authenticity_token</tt> by default. The name and
23
+ # value of this token must be added to every layout that renders forms by including
24
+ # <tt>csrf_meta_tags</tt> in the html +head+.
45
25
  #
26
+ # Learn more about CSRF attacks and securing your application in the
27
+ # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
46
28
  module RequestForgeryProtection
47
29
  extend ActiveSupport::Concern
48
30
 
@@ -1,7 +1,7 @@
1
1
  require 'active_support/json'
2
2
 
3
3
  module ActionController #:nodoc:
4
- # Responder is responsible for exposing a resource to different mime requests,
4
+ # Responsible for exposing a resource to different mime requests,
5
5
  # usually depending on the HTTP verb. The responder is triggered when
6
6
  # <code>respond_with</code> is called. The simplest case to study is a GET request:
7
7
  #
@@ -24,10 +24,10 @@ module ActionController #:nodoc:
24
24
  #
25
25
  # === Builtin HTTP verb semantics
26
26
  #
27
- # The default Rails responder holds semantics for each HTTP verb. Depending on the
27
+ # The default \Rails responder holds semantics for each HTTP verb. Depending on the
28
28
  # content type, verb and the resource status, it will behave differently.
29
29
  #
30
- # Using Rails default responder, a POST request for creating an object could
30
+ # Using \Rails default responder, a POST request for creating an object could
31
31
  # be written as:
32
32
  #
33
33
  # def create
@@ -77,8 +77,37 @@ module ActionController #:nodoc:
77
77
  #
78
78
  # respond_with(@project, :manager, @task)
79
79
  #
80
- # Check <code>polymorphic_url</code> documentation for more examples.
80
+ # === Custom options
81
81
  #
82
+ # <code>respond_with</code> also allow you to pass options that are forwarded
83
+ # to the underlying render call. Those options are only applied success
84
+ # scenarios. For instance, you can do the following in the create method above:
85
+ #
86
+ # def create
87
+ # @project = Project.find(params[:project_id])
88
+ # @task = @project.comments.build(params[:task])
89
+ # flash[:notice] = 'Task was successfully created.' if @task.save
90
+ # respond_with(@project, @task, :status => 201)
91
+ # end
92
+ #
93
+ # This will return status 201 if the task was saved with success. If not,
94
+ # it will simply ignore the given options and return status 422 and the
95
+ # resource errors. To customize the failure scenario, you can pass a
96
+ # a block to <code>respond_with</code>:
97
+ #
98
+ # def create
99
+ # @project = Project.find(params[:project_id])
100
+ # @task = @project.comments.build(params[:task])
101
+ # respond_with(@project, @task, :status => 201) do |format|
102
+ # if @task.save
103
+ # flash[:notice] = 'Task was successfully created.'
104
+ # else
105
+ # format.html { render "some_special_template" }
106
+ # end
107
+ # end
108
+ # end
109
+ #
110
+ # Using <code>respond_with</code> with a block follows the same syntax as <code>respond_to</code>.
82
111
  class Responder
83
112
  attr_reader :controller, :request, :format, :resource, :resources, :options
84
113
 
@@ -115,7 +144,7 @@ module ActionController #:nodoc:
115
144
  # Main entry point for responder responsible to dispatch to the proper format.
116
145
  #
117
146
  def respond
118
- method = :"to_#{format}"
147
+ method = "to_#{format}"
119
148
  respond_to?(method) ? send(method) : to_format
120
149
  end
121
150
 
@@ -133,14 +162,18 @@ module ActionController #:nodoc:
133
162
  # responds to :to_format and display it.
134
163
  #
135
164
  def to_format
136
- default_render
165
+ if get? || !has_errors?
166
+ default_render
167
+ else
168
+ display_errors
169
+ end
137
170
  rescue ActionView::MissingTemplate => e
138
171
  api_behavior(e)
139
172
  end
140
173
 
141
174
  protected
142
175
 
143
- # This is the common behavior for "navigation" requests, like :html, :iphone and so forth.
176
+ # This is the common behavior for formats associated with browsing, like :html, :iphone and so forth.
144
177
  def navigation_behavior(error)
145
178
  if get?
146
179
  raise error
@@ -151,14 +184,12 @@ module ActionController #:nodoc:
151
184
  end
152
185
  end
153
186
 
154
- # This is the common behavior for "API" requests, like :xml and :json.
187
+ # This is the common behavior for formats associated with APIs, such as :xml and :json.
155
188
  def api_behavior(error)
156
189
  raise error unless resourceful?
157
190
 
158
191
  if get?
159
192
  display resource
160
- elsif has_errors?
161
- display resource.errors, :status => :unprocessable_entity
162
193
  elsif post?
163
194
  display resource, :status => :created, :location => api_location
164
195
  elsif has_empty_resource_definition?
@@ -171,7 +202,7 @@ module ActionController #:nodoc:
171
202
  # Checks whether the resource responds to the current format or not.
172
203
  #
173
204
  def resourceful?
174
- resource.respond_to?(:"to_#{format}")
205
+ resource.respond_to?("to_#{format}")
175
206
  end
176
207
 
177
208
  # Returns the resource location by retrieving it from the options or
@@ -187,7 +218,7 @@ module ActionController #:nodoc:
187
218
  # controller.
188
219
  #
189
220
  def default_render
190
- @default_response.call
221
+ @default_response.call(options)
191
222
  end
192
223
 
193
224
  # Display is just a shortcut to render a resource with the current format.
@@ -211,6 +242,10 @@ module ActionController #:nodoc:
211
242
  controller.render given_options.merge!(options).merge!(format => resource)
212
243
  end
213
244
 
245
+ def display_errors
246
+ controller.render format => resource.errors, :status => :unprocessable_entity
247
+ end
248
+
214
249
  # Check whether the resource has errors.
215
250
  #
216
251
  def has_errors?
@@ -1,157 +1,263 @@
1
1
  require 'active_support/core_ext/file/path'
2
+ require 'rack/chunked'
2
3
 
3
4
  module ActionController #:nodoc:
4
- # Methods for sending arbitrary data and for streaming files to the browser,
5
- # instead of rendering.
5
+ # Allows views to be streamed back to the client as they are rendered.
6
+ #
7
+ # The default way Rails renders views is by first rendering the template
8
+ # and then the layout. The response is sent to the client after the whole
9
+ # template is rendered, all queries are made, and the layout is processed.
10
+ #
11
+ # Streaming inverts the rendering flow by rendering the layout first and
12
+ # streaming each part of the layout as they are processed. This allows the
13
+ # header of the HTML (which is usually in the layout) to be streamed back
14
+ # to client very quickly, allowing JavaScripts and stylesheets to be loaded
15
+ # earlier than usual.
16
+ #
17
+ # This approach was introduced in Rails 3.1 and is still improving. Several
18
+ # Rack middlewares may not work and you need to be careful when streaming.
19
+ # Those points are going to be addressed soon.
20
+ #
21
+ # In order to use streaming, you will need to use a Ruby version that
22
+ # supports fibers (fibers are supported since version 1.9.2 of the main
23
+ # Ruby implementation).
24
+ #
25
+ # == Examples
26
+ #
27
+ # Streaming can be added to a controller easily, all you need to do is
28
+ # call +stream+ in the controller class:
29
+ #
30
+ # class PostsController
31
+ # stream
32
+ # end
33
+ #
34
+ # The +stream+ method accepts the same options as +before_filter+ and friends:
35
+ #
36
+ # class PostsController
37
+ # stream :only => :index
38
+ # end
39
+ #
40
+ # You can also selectively turn on streaming for specific actions:
41
+ #
42
+ # class PostsController
43
+ # def index
44
+ # @posts = Post.scoped
45
+ # render :stream => true
46
+ # end
47
+ # end
48
+ #
49
+ # == When to use streaming
50
+ #
51
+ # Streaming may be considered to be overkill for lightweight actions like
52
+ # +new+ or +edit+. The real benefit of streaming is on expensive actions
53
+ # that, for example, do a lot of queries on the database.
54
+ #
55
+ # In such actions, you want to delay queries execution as much as you can.
56
+ # For example, imagine the following +dashboard+ action:
57
+ #
58
+ # def dashboard
59
+ # @posts = Post.all
60
+ # @pages = Page.all
61
+ # @articles = Article.all
62
+ # end
63
+ #
64
+ # Most of the queries here are happening in the controller. In order to benefit
65
+ # from streaming you would want to rewrite it as:
66
+ #
67
+ # def dashboard
68
+ # # Allow lazy execution of the queries
69
+ # @posts = Post.scoped
70
+ # @pages = Page.scoped
71
+ # @articles = Article.scoped
72
+ # render :stream => true
73
+ # end
74
+ #
75
+ # == Communication between layout and template
76
+ #
77
+ # When streaming, rendering happens top-down instead of inside-out.
78
+ # Rails starts with the layout, and the template is rendered later,
79
+ # when its +yield+ is reached.
80
+ #
81
+ # This means that, if your application currently relies on instance
82
+ # variables set in the template to be used in the layout, they won't
83
+ # work once you move to streaming. The proper way to communicate
84
+ # between layout and template, regardless of whether you use streaming
85
+ # or not, is by using +content_for+, +provide+ and +yield+.
86
+ #
87
+ # Take a simple example where the layout expects the template to tell
88
+ # which title to use:
89
+ #
90
+ # <html>
91
+ # <head><title><%= yield :title %></title></head>
92
+ # <body><%= yield %></body>
93
+ # </html>
94
+ #
95
+ # You would use +content_for+ in your template to specify the title:
96
+ #
97
+ # <%= content_for :title, "Main" %>
98
+ # Hello
99
+ #
100
+ # And the final result would be:
101
+ #
102
+ # <html>
103
+ # <head><title>Main</title></head>
104
+ # <body>Hello</body>
105
+ # </html>
106
+ #
107
+ # However, if +content_for+ is called several times, the final result
108
+ # would have all calls concatenated. For instance, if we have the following
109
+ # template:
110
+ #
111
+ # <%= content_for :title, "Main" %>
112
+ # Hello
113
+ # <%= content_for :title, " page" %>
114
+ #
115
+ # The final result would be:
116
+ #
117
+ # <html>
118
+ # <head><title>Main page</title></head>
119
+ # <body>Hello</body>
120
+ # </html>
121
+ #
122
+ # This means that, if you have <code>yield :title</code> in your layout
123
+ # and you want to use streaming, you would have to render the whole template
124
+ # (and eventually trigger all queries) before streaming the title and all
125
+ # assets, which kills the purpose of streaming. For this reason Rails 3.1
126
+ # introduces a new helper called +provide+ that does the same as +content_for+
127
+ # but tells the layout to stop searching for other entries and continue rendering.
128
+ #
129
+ # For instance, the template above using +provide+ would be:
130
+ #
131
+ # <%= provide :title, "Main" %>
132
+ # Hello
133
+ # <%= content_for :title, " page" %>
134
+ #
135
+ # Giving:
136
+ #
137
+ # <html>
138
+ # <head><title>Main</title></head>
139
+ # <body>Hello</body>
140
+ # </html>
141
+ #
142
+ # That said, when streaming, you need to properly check your templates
143
+ # and choose when to use +provide+ and +content_for+.
144
+ #
145
+ # == Headers, cookies, session and flash
146
+ #
147
+ # When streaming, the HTTP headers are sent to the client right before
148
+ # it renders the first line. This means that, modifying headers, cookies,
149
+ # session or flash after the template starts rendering will not propagate
150
+ # to the client.
151
+ #
152
+ # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+
153
+ # will be raised, showing those objects are closed for modification.
154
+ #
155
+ # == Middlewares
156
+ #
157
+ # Middlewares that need to manipulate the body won't work with streaming.
158
+ # You should disable those middlewares whenever streaming in development
159
+ # or production. For instance, +Rack::Bug+ won't work when streaming as it
160
+ # needs to inject contents in the HTML body.
161
+ #
162
+ # Also +Rack::Cache+ won't work with streaming as it does not support
163
+ # streaming bodies yet. Whenever streaming Cache-Control is automatically
164
+ # set to "no-cache".
165
+ #
166
+ # == Errors
167
+ #
168
+ # When it comes to streaming, exceptions get a bit more complicated. This
169
+ # happens because part of the template was already rendered and streamed to
170
+ # the client, making it impossible to render a whole exception page.
171
+ #
172
+ # Currently, when an exception happens in development or production, Rails
173
+ # will automatically stream to the client:
174
+ #
175
+ # "><script type="text/javascript">window.location = "/500.html"</script></html>
176
+ #
177
+ # The first two characters (">) are required in case the exception happens
178
+ # while rendering attributes for a given tag. You can check the real cause
179
+ # for the exception in your logger.
180
+ #
181
+ # == Web server support
182
+ #
183
+ # Not all web servers support streaming out-of-the-box. You need to check
184
+ # the instructions for each of them.
185
+ #
186
+ # ==== Unicorn
187
+ #
188
+ # Unicorn supports streaming but it needs to be configured. For this, you
189
+ # need to create a config file as follow:
190
+ #
191
+ # # unicorn.config.rb
192
+ # listen 3000, :tcp_nopush => false
193
+ #
194
+ # And use it on initialization:
195
+ #
196
+ # unicorn_rails --config-file unicorn.config.rb
197
+ #
198
+ # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
199
+ # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
200
+ #
201
+ # If you are using Unicorn with Nginx, you may need to tweak Nginx.
202
+ # Streaming should work out of the box on Rainbows.
203
+ #
204
+ # ==== Passenger
205
+ #
206
+ # To be described.
207
+ #
6
208
  module Streaming
7
209
  extend ActiveSupport::Concern
8
210
 
9
- include ActionController::Rendering
211
+ include AbstractController::Rendering
212
+ attr_internal :stream
10
213
 
11
- DEFAULT_SEND_FILE_OPTIONS = {
12
- :type => 'application/octet-stream'.freeze,
13
- :disposition => 'attachment'.freeze,
14
- }.freeze
15
-
16
- protected
17
- # Sends the file. This uses a server-appropriate method (such as X-Sendfile)
18
- # via the Rack::Sendfile middleware. The header to use is set via
19
- # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile".
20
- # Your server can also configure this for you by setting the X-Sendfile-Type header.
21
- #
22
- # Be careful to sanitize the path parameter if it is coming from a web
23
- # page. <tt>send_file(params[:path])</tt> allows a malicious user to
24
- # download any file on your server.
25
- #
26
- # Options:
27
- # * <tt>:filename</tt> - suggests a filename for the browser to use.
28
- # Defaults to <tt>File.basename(path)</tt>.
29
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
30
- # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
31
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
32
- # Valid values are 'inline' and 'attachment' (default).
33
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
34
- # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
35
- # the URL, which is necessary for i18n filenames on certain browsers
36
- # (setting <tt>:filename</tt> overrides this option).
37
- #
38
- # The default Content-Type and Content-Disposition headers are
39
- # set to download arbitrary binary files in as many browsers as
40
- # possible. IE versions 4, 5, 5.5, and 6 are all known to have
41
- # a variety of quirks (especially when downloading over SSL).
42
- #
43
- # Simple download:
44
- #
45
- # send_file '/path/to.zip'
46
- #
47
- # Show a JPEG in the browser:
48
- #
49
- # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
50
- #
51
- # Show a 404 page in the browser:
52
- #
53
- # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
54
- #
55
- # Read about the other Content-* HTTP headers if you'd like to
56
- # provide the user with more information (such as Content-Description) in
57
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
58
- #
59
- # Also be aware that the document may be cached by proxies and browsers.
60
- # The Pragma and Cache-Control headers declare how the file may be cached
61
- # by intermediaries. They default to require clients to validate with
62
- # the server before releasing cached responses. See
63
- # http://www.mnot.net/cache_docs/ for an overview of web caching and
64
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
65
- # for the Cache-Control header spec.
66
- def send_file(path, options = {}) #:doc:
67
- raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
68
-
69
- options[:filename] ||= File.basename(path) unless options[:url_based_filename]
70
- send_file_headers! options
71
-
72
- if options[:x_sendfile]
73
- ActiveSupport::Deprecation.warn(":x_sendfile is no longer needed in send_file", caller)
214
+ module ClassMethods
215
+ # Render streaming templates. It accepts :only, :except, :if and :unless as options
216
+ # to specify when to stream, as in ActionController filters.
217
+ def stream(options={})
218
+ if defined?(Fiber)
219
+ before_filter :_stream_filter, options
220
+ else
221
+ raise "You cannot use streaming if Fiber is not available."
74
222
  end
75
-
76
- self.status = options[:status] || 200
77
- self.content_type = options[:content_type] if options.key?(:content_type)
78
- self.response_body = File.open(path, "rb")
79
- end
80
-
81
- # Sends the given binary data to the browser. This method is similar to
82
- # <tt>render :text => data</tt>, but also allows you to specify whether
83
- # the browser should display the response as a file attachment (i.e. in a
84
- # download dialog) or as inline data. You may also set the content type,
85
- # the apparent file name, and other things.
86
- #
87
- # Options:
88
- # * <tt>:filename</tt> - suggests a filename for the browser to use.
89
- # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
90
- # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
91
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
92
- # Valid values are 'inline' and 'attachment' (default).
93
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
94
- #
95
- # Generic data download:
96
- #
97
- # send_data buffer
98
- #
99
- # Download a dynamically-generated tarball:
100
- #
101
- # send_data generate_tgz('dir'), :filename => 'dir.tgz'
102
- #
103
- # Display an image Active Record in the browser:
104
- #
105
- # send_data image.data, :type => image.content_type, :disposition => 'inline'
106
- #
107
- # See +send_file+ for more information on HTTP Content-* headers and caching.
108
- #
109
- # <b>Tip:</b> if you want to stream large amounts of on-the-fly generated
110
- # data to the browser, then use <tt>render :text => proc { ... }</tt>
111
- # instead. See ActionController::Base#render for more information.
112
- def send_data(data, options = {}) #:doc:
113
- send_file_headers! options.dup
114
- render options.slice(:status, :content_type).merge(:text => data)
115
223
  end
224
+ end
116
225
 
117
- private
118
- def send_file_headers!(options)
119
- options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
120
- [:type, :disposition].each do |arg|
121
- raise ArgumentError, ":#{arg} option required" if options[arg].nil?
122
- end
123
-
124
- if options.key?(:length)
125
- ActiveSupport::Deprecation.warn("You do not need to provide the file's length", caller)
126
- end
226
+ protected
127
227
 
128
- disposition = options[:disposition]
129
- disposition += %(; filename="#{options[:filename]}") if options[:filename]
228
+ # Mark following render calls as streaming.
229
+ def _stream_filter #:nodoc:
230
+ self.stream = true
231
+ end
130
232
 
131
- content_type = options[:type]
233
+ # Consider the stream option when normalazing options.
234
+ def _normalize_options(options) #:nodoc:
235
+ super
236
+ options[:stream] = self.stream unless options.key?(:stream)
237
+ end
132
238
 
133
- if content_type.is_a?(Symbol)
134
- extension = Mime[content_type]
135
- raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
136
- self.content_type = extension
239
+ # Set proper cache control and transfer encoding when streaming
240
+ def _process_options(options) #:nodoc:
241
+ super
242
+ if options[:stream]
243
+ if env["HTTP_VERSION"] == "HTTP/1.0"
244
+ options.delete(:stream)
137
245
  else
138
- self.content_type = content_type
246
+ headers["Cache-Control"] ||= "no-cache"
247
+ headers["Transfer-Encoding"] = "chunked"
248
+ headers.delete("Content-Length")
139
249
  end
250
+ end
251
+ end
140
252
 
141
- headers.merge!(
142
- 'Content-Disposition' => disposition,
143
- 'Content-Transfer-Encoding' => 'binary'
144
- )
145
-
146
- response.sending_file = true
147
-
148
- # Fix a problem with IE 6.0 on opening downloaded files:
149
- # If Cache-Control: no-cache is set (which Rails does by default),
150
- # IE removes the file it just downloaded from its cache immediately
151
- # after it displays the "open/save" dialog, which means that if you
152
- # hit "open" the file isn't there anymore when the application that
153
- # is called for handling the download is run, so let's workaround that
154
- response.cache_control[:public] ||= false
253
+ # Call render_to_body if we are streaming instead of usual +render+.
254
+ def _render_template(options) #:nodoc:
255
+ if options.delete(:stream)
256
+ Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
257
+ else
258
+ super
155
259
  end
260
+ end
156
261
  end
157
262
  end
263
+