template_streaming 0.0.11 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ == 0.1.0 2011-05-16
2
+
3
+ * Replace prelayouts with :stream => true option to #render.
4
+ * Controller-level #stream to stream selected actions.
5
+ * Inject errors during rendering to end of body.
6
+ * Autoflushing.
7
+ * Fix page and action caching for streamed responses.
8
+ * New Relic support.
9
+
1
10
  == 0.0.11 2010-05-10
2
11
 
3
12
  * Ensure authenticity token is initialized properly.
@@ -1,6 +1,6 @@
1
1
  # Template Streaming
2
2
 
3
- Rails plugin which enables progressive rendering for templates.
3
+ Progressive rendering for Rails.
4
4
 
5
5
  ## Background
6
6
 
@@ -8,33 +8,45 @@ A typical Rails client-side profile looks something like this:
8
8
 
9
9
  ![Typical Rails Profile][slow-profile]
10
10
 
11
- In almost all cases, this is highly suboptimal, as many resources, such as
12
- external stylesheets, are static and could be loaded by the client while it's
13
- waiting for the server response.
11
+ This is highly suboptimal. Many resources, such as external stylesheets, are
12
+ completely static and could be loaded by the client while it's waiting for the
13
+ server response.
14
14
 
15
- The trick is to output the response *progressively*--flushing the stylesheet
16
- link tags out to the client before it has rendered the rest of the
17
- page. Depending on how other external resources such javascripts and images are
18
- used, they too may be flushed out early, significantly reducing the time for the
19
- page to become interactive.
15
+ The trick is to *stream* the response--flushing the markup for the static
16
+ resources to the client before it has rendered the rest of the page. In
17
+ addition to being able to render styles and images earlier, the browser can
18
+ download javascripts, making the page responsive to input events sooner.
20
19
 
21
- The problem is Rails has never been geared to allow this. Most Rails
22
- applications use layouts, which require rendering the content of the page before
23
- the layout. Since the global stylesheet tag is usually in the layout, we can't
24
- simply flush the rendering buffer from a helper method.
20
+ The main barrier to this in Rails is that layouts are rendered before the
21
+ content of the page. The control flow must thus be altered to render the page in
22
+ the order the client needs to receive it - layout first.
25
23
 
26
- Until now.
27
-
28
- Template Streaming circumvents the template rendering order by introducing
29
- *prelayouts*. A prelayout wraps a layout, and is rendered *before* the layout
30
- and its content. By using the provided `flush` helper prior to yielding in the
31
- prelayout, one can now output content early in the rendering process, giving
32
- profiles that look more like:
24
+ With streaming, your profiles can look more like this:
33
25
 
34
26
  ![Progressive Rendering Profile][fast-profile]
35
27
 
36
- [slow-profile]: http://github.com/oggy/template_streaming/raw/master/doc/slow-profile.png
37
- [fast-profile]: http://github.com/oggy/template_streaming/raw/master/doc/fast-profile.png
28
+ [slow-profile]: https://github.com/oggy/template_streaming/raw/master/doc/slow-profile.png
29
+ [fast-profile]: https://github.com/oggy/template_streaming/raw/master/doc/fast-profile.png
30
+
31
+ ## How
32
+
33
+ Just add the `template_streaming` gem to your application, and add a `stream`
34
+ call for the actions you'd like to stream. For example, to stream just the
35
+ `index` action of your `HomeController`, it would look like this:
36
+
37
+ class HomeController
38
+ stream :only => :index
39
+
40
+ def index
41
+ ...
42
+ end
43
+ end
44
+
45
+ To stream everything, just add `stream` to your `ApplicationController`.
46
+
47
+ Now you may pepper `flush` calls strategically throughout your views to force a
48
+ flush, such as just after the stylesheet and javascript tags. `flush` may occur
49
+ in both templates and their layouts.
38
50
 
39
51
  ## API
40
52
 
@@ -48,9 +60,12 @@ This has several implications:
48
60
  * Anything that needs to inspect or modify the body should be moved to a
49
61
  middleware.
50
62
  * Modifications to cookies (this includes the flash and session if using the
51
- cookie store!) should not be made in the view.
52
- * An exception during rendering cannot simply replace the body with a
53
- stacktrace or 500 page. (Solution to come.)
63
+ cookie store) must not be made in the view. In fact, these objects will be
64
+ frozen when streaming.
65
+ * An exception during rendering cannot result in a 500 response, as the headers
66
+ will have already been sent. Instead, the innermost partial which contains an
67
+ error will simply render nothing, and error information is injected into the
68
+ foot of the page in development mode.
54
69
 
55
70
  ### Helpers
56
71
 
@@ -58,32 +73,76 @@ This has several implications:
58
73
  client immediately.
59
74
  * `push(data)` - send the given data to the client immediately.
60
75
 
61
- These can only do their job if the underlying web server supports progressive
62
- rendering via Rack. This has been tested successfully with [Mongrel][mongrel]
63
- and [Passenger][passenger]. [Thin][thin] is only supported if the [Event Machine
64
- Flush][event-machine-flush] gem is installed. WEBrick does not support
65
- progressive rendering. [Please send me][contact] reports of success with other
76
+ ## Support
77
+
78
+ Template Streaming currently only supports Rails 2.3.11. Rails 3.0 support is
79
+ planned in the near future. Rails 3.1 will ship with support for streaming. This
80
+ gem will be updated to meet the API of Rails 3.1 as it evolves, to help you
81
+ migrate.
82
+
83
+ Streaming also requires a web server that does not buffer Rack responses. It has
84
+ been tested **successfully** with [Passenger][passenger], [Unicorn][unicorn],
85
+ and [Mongrel][mongrel]. Note that Unicorn requires the `:tcp_nopush => false`
86
+ configuration option. [Thin][thin] is only supported if the
87
+ [Event Machine Flush][event-machine-flush] gem is installed. WEBrick does
88
+ **not** support streaming. [Please send me][contact] your experiences with other
66
89
  web servers!
67
90
 
68
- [mongrel]: http://github.com/fauna/mongrel
69
91
  [passenger]: http://www.modrails.com
70
- [thin]: http://github.com/macournoyer/thin
71
- [event-machine-flush]: http://github.com/oggy/event_machine_flush
92
+ [unicorn]: http://unicorn.bogomips.org/
93
+ [mongrel]: https://github.com/fauna/mongrel
94
+ [thin]: https://github.com/macournoyer/thin
95
+ [event-machine-flush]: https://github.com/oggy/event_machine_flush
72
96
  [contact]: mailto:george.ogata@gmail.com
73
97
 
74
98
  ### Controller
75
99
 
76
- * `when_streaming_template` - defines a callback to be called during a `render`
77
- call when a template is streamed. This is *before* the body is rendered, or
78
- any data is sent to the client.
100
+ Class methods:
101
+
102
+ * `stream` - stream responses for these actions. Takes `:only` or `:except`
103
+ options, like `before_filter`.
104
+
105
+ * `when_streaming_template` - registers a callback to be called during `render`
106
+ when rendering progressively. This is before the body is rendered, or any
107
+ data is sent to the client.
108
+
109
+ Instance methods:
79
110
 
80
- ## Example
111
+ * `render` has been modified to accept a `:stream` option. If true, the
112
+ response will be streamed, otherwise it won't. This overrides the setting set
113
+ by the `stream` method above.
114
+
115
+ ### Error Recovery
116
+
117
+ As mentioned above, headers are sent to the client before view rendering starts,
118
+ which means it's not possible to send an error response in the event of an
119
+ uncaught exception. Instead, the innermost template which raised the error
120
+ simply renders nothing. This has the added advantage of minimizing the impact on
121
+ your visitors, as the rest of the page will render fine.
122
+
123
+ When an error is swallowed like this, it is passed to an error hander callback,
124
+ which you can set as follows.
125
+
126
+ TemplateStreaming.on_streaming_error do |controller, exception|
127
+ ...
128
+ end
129
+
130
+ This is where you should hook in your error notification system. Errors are also
131
+ logged to the application log.
132
+
133
+ In addition, in development mode, error information is injected into the foot of
134
+ the page. This is presented over the top of the rendered page, so the result
135
+ looks much like when not streaming.
136
+
137
+ ## Streaming Templates Effectively
81
138
 
82
139
  Conventional wisdom says to put your external stylesheets in the HEAD of your
83
140
  page, and your external javascripts at the bottom of the BODY (markup in
84
141
  [HAML][haml]):
85
142
 
86
- ### `app/views/prelayouts/application.html.haml`
143
+ [haml]: http://haml-lang.com
144
+
145
+ ### `app/views/layouts/application.html.haml`
87
146
 
88
147
  !!! 5
89
148
  %html
@@ -91,65 +150,101 @@ page, and your external javascripts at the bottom of the BODY (markup in
91
150
  = stylesheet_link_tag 'one'
92
151
  = stylesheet_link_tag 'two'
93
152
  - flush
94
- = yield
153
+ %body
154
+ = yield
155
+ = javascript_include_tag 'one'
156
+ = javascript_include_tag 'two'
157
+
158
+ When streaming, however, you can do better: put the javascripts at the top of
159
+ the page too, and fetch them *asynchronously*. This can be done by appending a
160
+ script tag to the HEAD of the page in a small piece of inline javascript:
95
161
 
96
162
  ### `app/views/layouts/application.html.haml`
97
163
 
164
+ !!! 5
165
+ %html
166
+ %head
167
+ = stylesheet_link_tag 'one'
168
+ = stylesheet_link_tag 'two'
169
+ = javascript_tag do
170
+ = File.read(Rails.public_path + '/javascripts/get_script.js')
171
+ $.getScript('#{javascript_path('jquery')}');
172
+ $.getScript('#{javascript_path('application')}');
98
173
  %body
99
- = yield
100
- = javascript_include_tag 'one'
101
- = javascript_include_tag 'two'
174
+ - flush
175
+ = yield
176
+
177
+ ### `public/javascripts/get_script.js`
178
+
179
+ //
180
+ // Credit: Sam Cole [https://gist.github.com/364746]
181
+ //
182
+ window.$ = {
183
+ getScript: function(script_src, callback) {
184
+ var done = false;
185
+ var head = document.getElementsByTagName("head")[0] || document.documentElement;
186
+ var script = document.createElement("script");
187
+ script.src = script_src;
188
+ script.onload = script.onreadystatechange = function() {
189
+ if ( !done && (!this.readyState ||
190
+ this.readyState === "loaded" || this.readyState === "complete") ) {
191
+ if(callback) callback();
102
192
 
103
- With progressive rendering, however, this could be improved. As [Stoyan Stefanov
104
- writes][stefanov], you can put your javascripts in the HEAD of your page if you
105
- fetch them via AJAX and append them to the HEAD of your page dynamically. This
106
- also reduces the time for the page to become interactive (e.g., scrollable),
107
- giving an even greater perceived performance boost.
193
+ // Handle memory leak in IE
194
+ script.onload = script.onreadystatechange = null;
195
+ if ( head && script.parentNode ) {
196
+ head.removeChild( script );
197
+ }
108
198
 
109
- Of course, rather than using an external library for the AJAX call, we can save
110
- ourselves a roundtrip by defining a `getScript` function ourselves in a small
111
- piece of inline javascript. This is done by `define_get_script`
112
- below. `get_script` then includes a call to this function which fetches the
113
- script asynchronously, and then appends the script tag to the HEAD.
199
+ done = true;
200
+ }
201
+ };
202
+ head.insertBefore( script, head.firstChild );
203
+ }
204
+ };
205
+
206
+ If you have inline javascript that depends on the fetched scripts, you'll need
207
+ to delay its execution until the scripts have been run. You can do this by
208
+ wrapping the javascript in a function, with a guard which will delay execution
209
+ until the script is loaded, unless the script has already been loaded. Example:
114
210
 
115
- ### `app/views/prelayouts/application.html.haml`
211
+ ### Layout
116
212
 
117
213
  !!! 5
118
214
  %html
119
215
  %head
120
- = define_get_script
121
216
  = stylesheet_link_tag 'one'
122
217
  = stylesheet_link_tag 'two'
123
- = get_script 'one'
124
- = get_script 'two'
125
- - flush
126
- = yield
127
-
128
- ### `app/views/layouts/application.html.haml`
129
-
218
+ = javascript_tag do
219
+ = File.read(Rails.public_path + '/javascripts/get_script.js')
220
+ $.getScript('#{javascript_path('jquery')}', function() {
221
+ window.script_loaded = 1;
222
+
223
+ // If the inline code has been loaded (but not yet run), run it
224
+ // now. Otherwise, it will be run immediately when it's available.
225
+ if (window.inline)
226
+ inline();
227
+ });
130
228
  %body
131
- = yield
229
+ - flush
230
+ = yield
132
231
 
133
- ### `app/helpers/application_helper.rb`
232
+ ### View
134
233
 
135
- module ApplicationHelper
136
- def define_get_script
137
- javascript_tag do
138
- File.read(Rails.public_path + '/javascripts/get_script.js')
139
- end
140
- end
234
+ - javascript_tag do
235
+ window.inline() {
236
+ // ... inline javascript code ...
237
+ }
141
238
 
142
- def get_script(url)
143
- javascript_tag do
144
- "$.getScript('#{javascript_path(url)}');"
145
- end
146
- end
147
- end
239
+ // If the script is already loaded, run it now. Otherwise, the callback
240
+ // above will run it after the script is loaded.
241
+ if (window.script_loaded)
242
+ inline();
148
243
 
149
- ### `public/javascripts/get_script.js`
244
+ ### In `public/javascripts/get_script.js`
150
245
 
151
246
  //
152
- // Written by Sam Cole. See http://gist.github.com/364746 for more info.
247
+ // Credit: Sam Cole [https://gist.github.com/364746]
153
248
  //
154
249
  window.$ = {
155
250
  getScript: function(script_src, callback) {
@@ -175,20 +270,14 @@ script asynchronously, and then appends the script tag to the HEAD.
175
270
  }
176
271
  };
177
272
 
178
- The second profile was created using this code.
179
-
180
- [haml]: http://haml-lang.com
181
- [stefanov]: http://www.yuiblog.com/blog/2008/07/22/non-blocking-scripts
182
- [get-script]: http://gist.github.com/364746
183
-
184
- ## Note on Patches/Pull Requests
273
+ ## Contributing
185
274
 
186
- * Bug reports: http://github.com/oggy/template_streaming/issues
187
- * Source: http://github.com/oggy/template_streaming
275
+ * [Bug reports](https://github.com/oggy/template_streaming/issues)
276
+ * [Source](https://github.com/oggy/template_streaming)
188
277
  * Patches: Fork on Github, send pull request.
189
- * Ensure patch includes tests.
278
+ * Include tests where practical.
190
279
  * Leave the version alone, or bump it in a separate commit.
191
280
 
192
281
  ## Copyright
193
282
 
194
- Copyright (c) 2010 George Ogata. See LICENSE for details.
283
+ Copyright (c) George Ogata. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,22 +1 @@
1
- gem 'ritual'
2
1
  require 'ritual'
3
-
4
- spec_task :spec do |t|
5
- t.libs << 'lib' << 'spec'
6
- t.spec_files = FileList['spec/**/*_spec.rb']
7
- end
8
-
9
- spec_task :rcov do |t|
10
- t.libs << 'lib' << 'spec'
11
- t.pattern = 'spec/**/*_spec.rb'
12
- t.rcov = true
13
- end
14
-
15
- rdoc_task do |t|
16
- t.rdoc_dir = 'rdoc'
17
- t.title = "Template Streaming #{version}"
18
- t.rdoc_files.include('README*')
19
- t.rdoc_files.include('lib/**/*.rb')
20
- end
21
-
22
- task :default => :spec
@@ -5,43 +5,17 @@ module TemplateStreaming
5
5
  send "#{key}=", value
6
6
  end
7
7
  end
8
-
9
- #
10
- # If true, always reference the flash before returning from the
11
- # action when rendering progressively.
12
- #
13
- # This is required for the flash to work with progressive
14
- # rendering, but unlike standard Rails behavior, will cause the
15
- # flash to be swept even if it's never referenced in the
16
- # views. This usually isn't an issue, as flash messages are
17
- # typically rendered in the layout, causing a reference anyway.
18
- #
19
- # Default: true.
20
- #
21
- attr_accessor :autosweep_flash
22
-
23
- #
24
- # If true, always set the authenticity token before returning from
25
- # the action when rendering progressively.
26
- #
27
- # This is required for the authenticity token to work with
28
- # progressive rendering, but unlike standard Rails behavior, will
29
- # cause the token to be set (and thus the session updated) even if
30
- # it's never referenced in views.
31
- #
32
- # Default: true.
33
- #
34
- attr_accessor :set_authenticity_token
35
8
  end
36
9
 
37
- self.autosweep_flash = true
38
- self.set_authenticity_token = true
10
+ STREAMING_KEY = 'template_streaming.streaming'.freeze
39
11
 
40
12
  module Controller
41
13
  def self.included(base)
42
14
  base.class_eval do
15
+ extend ClassMethods
43
16
  alias_method_chain :render, :template_streaming
44
17
  alias_method_chain :render_to_string, :template_streaming
18
+ alias_method_chain :flash, :template_streaming
45
19
  helper_method :flush, :push
46
20
 
47
21
  include ActiveSupport::Callbacks
@@ -49,11 +23,39 @@ module TemplateStreaming
49
23
  end
50
24
  end
51
25
 
26
+ module ClassMethods
27
+ def stream(options={})
28
+ before_filter :action_streams, options
29
+ end
30
+ end
31
+
32
+ def action_streams
33
+ @action_streams = true
34
+ end
35
+
36
+ def action_streams?
37
+ @action_streams
38
+ end
39
+
52
40
  def render_with_template_streaming(*args, &block)
53
- with_template_streaming_condition(*args) do |condition|
54
- if condition
41
+ options = args.first { |a| a.is_a?(Hash) }
42
+ if options && options.size == 1 && options.key?(:stream)
43
+ # Need to set the default values, since the standard #render won't.
44
+ options[:template] = default_template
45
+ options[:layout] = true
46
+ end
47
+ push_render_stack_frame do |stack_height|
48
+ if start_streaming_template?(stack_height, *args)
49
+ @streaming_template = true
50
+ @template.streaming_template = true
55
51
  @performed_render = true
56
- @streaming_body = StreamingBody.new(progressive_rendering_threshold) do
52
+ @streaming_body = StreamingBody.new(template_streaming_threshold) do
53
+ cookies.freeze
54
+ if self.class.session_store.sent_with_headers?
55
+ session.freeze
56
+ flash.freeze
57
+ end
58
+
57
59
  @performed_render = false
58
60
  last_piece = render_without_template_streaming(*args, &block)
59
61
  # The original render will clobber our response.body, so
@@ -62,33 +64,44 @@ module TemplateStreaming
62
64
  end
63
65
  response.body = @streaming_body
64
66
  response.prepare!
65
- flash if TemplateStreaming.autosweep_flash
66
- form_authenticity_token if TemplateStreaming.set_authenticity_token
67
- run_callbacks :when_streaming_template
67
+ form_authenticity_token # generate now
68
68
 
69
- # Normally, @_flash is removed after #perform_action, which
70
- # means calling #flash in the view would cause a new
71
- # FlashHash to be constructed. On top of that, the flash is
72
- # swept on construction, which results in sweeping the flash
73
- # twice, obliterating its contents.
69
+ # Normally, the flash is swept on first reference. This
70
+ # means we need to ensure it's referenced before the session
71
+ # is persisted. In the case of the cookie store, that's when
72
+ # the headers are sent, so we force a reference now.
74
73
  #
75
- # So, we preserve the flash here under a different ivar, and
76
- # override the #flash helper to return it.
77
- if defined?(@_flash)
78
- @template_streaming_flash = @_flash
79
- end
74
+ # But alas, that's not all. @_flash is removed after
75
+ # #perform_action, which means calling #flash in the view
76
+ # would cause the flash to be referenced again, sweeping the
77
+ # flash a second time. To prevent this, we preserve the
78
+ # flash in a separate ivar, and patch #flash to return this
79
+ # if we're streaming.
80
+ #
81
+ flash # ensure sweep
82
+ @template_streaming_flash = @_flash
83
+ request.env[STREAMING_KEY] = true
84
+
85
+ run_callbacks :when_streaming_template
80
86
  else
81
87
  render_without_template_streaming(*args, &block)
82
88
  end
83
89
  end
84
90
  end
85
91
 
92
+ # Mark the case when it's a layout for a toplevel render. This is
93
+ # done here, as it's called after the option wrangling in
94
+ # AC::Base#render, and nowhere else.
95
+ def pick_layout(options)
96
+ result = super
97
+ options[:toplevel_render_with_layout] = true if result
98
+ result
99
+ end
100
+
86
101
  # Override to ensure calling render_to_string from a helper
87
102
  # doesn't trigger template streaming.
88
103
  def render_to_string_with_template_streaming(*args, &block) # :nodoc
89
- # Ensure renders within a render_to_string aren't considered
90
- # top-level.
91
- with_template_streaming_condition do
104
+ push_render_stack_frame do
92
105
  render_to_string_without_template_streaming(*args, &block)
93
106
  end
94
107
  end
@@ -98,7 +111,7 @@ module TemplateStreaming
98
111
  # immediately.
99
112
  #
100
113
  def flush
101
- unless @template.output_buffer.nil?
114
+ if @streaming_body && !@template.output_buffer.nil?
102
115
  push @template.output_buffer.slice!(0..-1)
103
116
  end
104
117
  end
@@ -107,35 +120,50 @@ module TemplateStreaming
107
120
  # Push the given data to the client immediately.
108
121
  #
109
122
  def push(data)
110
- @streaming_body.push(data)
111
- flush_thin
123
+ if @streaming_body
124
+ @streaming_body.push(data)
125
+ flush_thin
126
+ end
112
127
  end
113
128
 
114
129
  def template_streaming_flash # :nodoc:
115
130
  @template_streaming_flash
116
131
  end
117
132
 
133
+ def streaming_template?
134
+ @streaming_template
135
+ end
136
+
118
137
  private # --------------------------------------------------------
119
138
 
120
- #
121
- # Yield true if we should intercept this render call, false
122
- # otherwise.
123
- #
124
- def with_template_streaming_condition(*args)
139
+ def push_render_stack_frame
125
140
  @render_stack_height ||= 0
126
141
  @render_stack_height += 1
127
142
  begin
128
- # Only install our StreamingBody in the toplevel #render call.
129
- @render_stack_height == 1 or
130
- return yield(false)
143
+ yield @render_stack_height
144
+ ensure
145
+ @render_stack_height -= 1
146
+ end
147
+ end
148
+
149
+ def start_streaming_template?(render_stack_height, *render_args)
150
+ render_stack_height == 1 or
151
+ return false
152
+
153
+ return false if rendered_action_cache
131
154
 
132
- if (options = args.last).is_a?(Hash)
133
- yield((UNSTREAMABLE_KEYS & options.keys).empty?)
155
+ (render_options = render_args.last).is_a?(Hash) or
156
+ render_options = {}
157
+
158
+ if !(UNSTREAMABLE_KEYS & render_options.keys).empty? || render_args.first == :update
159
+ false
160
+ else
161
+ explicit_option = render_options[:stream]
162
+ if explicit_option.nil?
163
+ action_streams?
134
164
  else
135
- yield(args.first != :update)
165
+ explicit_option
136
166
  end
137
- ensure
138
- @render_stack_height -= 1
139
167
  end
140
168
  end
141
169
 
@@ -145,7 +173,7 @@ module TemplateStreaming
145
173
  # The number of bytes that must be received by the client before
146
174
  # anything will be rendered.
147
175
  #
148
- def progressive_rendering_threshold
176
+ def template_streaming_threshold
149
177
  content_type = response.header['Content-type']
150
178
  content_type.nil? || content_type =~ %r'\Atext/html' or
151
179
  return 0
@@ -170,6 +198,15 @@ module TemplateStreaming
170
198
  connection = request.env['template_streaming.thin_connection'] and
171
199
  EventMachineFlush.flush(connection)
172
200
  end
201
+
202
+ def flash_with_template_streaming # :nodoc:
203
+ if defined?(@template_streaming_flash)
204
+ # Flash has been swept - don't use the standard #flash or it'll sweep again.
205
+ @template_streaming_flash
206
+ else
207
+ flash_without_template_streaming
208
+ end
209
+ end
173
210
  end
174
211
 
175
212
  # Only prepare once.
@@ -196,53 +233,83 @@ module TemplateStreaming
196
233
 
197
234
  module View
198
235
  def self.included(base)
236
+ base.alias_method_chain :render, :template_streaming
199
237
  base.alias_method_chain :_render_with_layout, :template_streaming
200
- base.alias_method_chain :flash, :template_streaming
201
238
  end
202
239
 
203
- def _render_with_layout_with_template_streaming(options, local_assigns, &block)
204
- with_prelayout prelayout_for(options), local_assigns do
205
- _render_with_layout_without_template_streaming(options, local_assigns, &block)
240
+ def render_with_template_streaming(*args, &block)
241
+ options = args.first
242
+ if streaming_template? && options.is_a?(Hash)
243
+ # These branches exist to handle the case where AC::Base#render calls
244
+ # AV::Base#render for rendering a partial with a layout. AC::Base
245
+ # renders the partial then the layout separately, but we need to render
246
+ # them together, in the reverse order (layout first). We do this by
247
+ # standard-rendering the layout with a block that renders the partial.
248
+ if options[:toplevel_render_with_layout] && (partial = options[:partial])
249
+ # Don't render yet - we need to do the layout first.
250
+ options.delete(:toplevel_render_with_layout)
251
+ return DeferredPartialRender.new(args)
252
+ elsif options[:text].is_a?(DeferredPartialRender)
253
+ render = options.delete(:text)
254
+ # We patch the case of rendering :partial with :layout while
255
+ # streaming in _render_with_layout.
256
+ return render(render.args.first.merge(:layout => options[:layout]))
257
+ end
206
258
  end
259
+ render_without_template_streaming(*args, &block)
260
+ end
261
+
262
+ DeferredPartialRender = Struct.new(:args)
263
+
264
+ attr_writer :streaming_template
265
+
266
+ def streaming_template?
267
+ @streaming_template
207
268
  end
208
269
 
209
- def with_prelayout(prelayout, locals, &block)
210
- if prelayout
270
+ def _render_with_layout_with_template_streaming(options, local_assigns, &block)
271
+ if !streaming_template?
272
+ _render_with_layout_without_template_streaming(options, local_assigns, &block)
273
+ elsif block_given?
274
+ # The standard method doesn't properly restore @_proc_for_layout. Do it ourselves.
275
+ original_proc_for_layout = @_proc_for_layout
211
276
  begin
212
- @_proc_for_layout = lambda do
213
- # nil out @_proc_for_layout else rendering with the layout will call it again.
214
- @_proc_for_layout, original_proc_for_layout = nil, @_proc_for_layout
215
- begin
216
- block.call
217
- ensure
218
- @_proc_for_layout = original_proc_for_layout
219
- end
220
- end
221
- render(:file => prelayout, :locals => locals)
277
+ _render_with_layout_without_template_streaming(options, local_assigns, &block)
222
278
  ensure
223
- @_proc_for_layout = nil
279
+ @_proc_for_layout = original_proc_for_layout
280
+ end
281
+ elsif options[:layout].is_a?(ActionView::Template)
282
+ # Toplevel render call, from the controller.
283
+ layout = options.delete(:layout)
284
+ with_render_proc_for_layout(options) do
285
+ render(options.merge(:file => layout.path_without_format_and_extension))
224
286
  end
225
287
  else
226
- yield
288
+ layout = options.delete(:layout)
289
+ with_render_proc_for_layout(options) do
290
+ if (options[:inline] || options[:file] || options[:text])
291
+ render(:file => layout, :locals => local_assigns)
292
+ else
293
+ render(options.merge(:partial => layout))
294
+ end
295
+ end
227
296
  end
228
297
  end
229
298
 
230
- def prelayout_for(options)
231
- layout = options[:layout] or
232
- return nil
233
- # Views can call #render with :layout to render a layout
234
- # *partial* which we don't want to interfere with. Only the
235
- # interlal toplevel #render calls :layout with an
236
- # ActionView::Template
237
- layout.is_a?(ActionView::Template) or
238
- return nil
239
- view_paths.find_template('pre' + layout.path_without_format_and_extension, layout.format)
240
- rescue ActionView::MissingTemplate
241
- end
242
-
243
- def flash_with_template_streaming # :nodoc:
244
- # Override ActionView::Base#flash to prevent a double-sweep.
245
- controller.instance_eval { @template_streaming_flash || flash }
299
+ def with_render_proc_for_layout(options)
300
+ original_proc_for_layout = @_proc_for_layout
301
+ @_proc_for_layout = lambda do |*args|
302
+ if args.empty?
303
+ render(options)
304
+ else
305
+ instance_variable_get(:"@content_for_#{args.first}")
306
+ end
307
+ end
308
+ begin
309
+ yield
310
+ ensure
311
+ @_proc_for_layout = original_proc_for_layout
312
+ end
246
313
  end
247
314
  end
248
315
 
@@ -275,10 +342,24 @@ module TemplateStreaming
275
342
  end
276
343
  end
277
344
 
345
+ module AbstractSessionStoreExtension
346
+ def sent_with_headers?
347
+ false
348
+ end
349
+ end
350
+
351
+ module CookieSessionStoreExtension
352
+ def sent_with_headers?
353
+ true
354
+ end
355
+ end
356
+
278
357
  ActionView::Base.send :include, View
279
358
  ActionController::Base.send :include, Controller
280
359
  ActionController::Response.send :include, Response
281
360
  ActionController::Dispatcher.middleware.insert 0, Rack::Chunked
361
+ ActionController::Session::AbstractStore.extend AbstractSessionStoreExtension
362
+ ActionController::Session::CookieStore.extend CookieSessionStoreExtension
282
363
  end
283
364
 
284
365
  # Please let there be a better way to do this...
@@ -311,3 +392,7 @@ if defined?(Thin)
311
392
  end
312
393
  end
313
394
  end
395
+
396
+ require 'template_streaming/error_recovery'
397
+ require 'template_streaming/caching'
398
+ require 'template_streaming/autoflushing'