Permalink
Browse files

Add autoflushing.

Set TemplateStreaming.autoflush to have flush called automatically
before and after each partial. The value is the minimum time between
flushes, in seconds.
  • Loading branch information...
1 parent 816dfd0 commit 2074201110708fd99f2dc99528c9bf462535f045 @oggy committed Apr 21, 2011
Showing with 164 additions and 0 deletions.
  1. +1 −0 lib/template_streaming.rb
  2. +88 −0 lib/template_streaming/autoflushing.rb
  3. +75 −0 spec/autoflushing_spec.rb
@@ -395,3 +395,4 @@ def pre_process_with_template_streaming(*args, &block)
require 'template_streaming/error_recovery'
require 'template_streaming/caching'
+require 'template_streaming/autoflushing'
@@ -0,0 +1,88 @@
+module TemplateStreaming
+ class << self
+ #
+ # If non-nil, #flush will automatically be called when rendering
+ # progressively before and after each render call.
+ #
+ # The value of this attribute should be a number, which is the
+ # number of milliseconds since the last flush that should elapse
+ # for the autoflush to occur. 0 means force a flush every time.
+ #
+ attr_accessor :autoflush
+ end
+
+ module Autoflushing
+ module View
+ def self.included(base)
+ base.alias_method_chain :render, :template_streaming_autoflushing
+ end
+
+ def render_with_template_streaming_autoflushing(*args, &block)
+ with_autoflushing do
+ render_without_template_streaming_autoflushing(*args, &block)
+ end
+ end
+
+ def capture(*args, &block)
+ if block == @_proc_for_layout
+ # Rendering the content of a progressive layout - inject autoflushing.
+ with_autoflushing do
+ super
+ end
+ else
+ super
+ end
+ end
+
+ def with_autoflushing
+ controller.flush if TemplateStreaming.autoflush
+ fragment = yield
+ if TemplateStreaming.autoflush
+ controller.push(fragment)
+ ''
+ else
+ fragment
+ end
+ end
+ end
+
+ class Middleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ response = @app.call(env)
+ if env[PROGRESSIVE_KEY] && TemplateStreaming.autoflush
+ response[2] = BodyProxy.new(response[2])
+ end
+ response
+ end
+
+ class BodyProxy
+ def initialize(body)
+ @body = body
+ end
+
+ def each
+ buffered_chunks = []
+ autoflush_due_at = Time.now.to_f
+ @body.each do |chunk|
+ buffered_chunks << chunk
+ if Time.now.to_f >= autoflush_due_at
+ yield buffered_chunks.join
+ buffered_chunks.clear
+ autoflush_due_at = Time.now.to_f + TemplateStreaming.autoflush
+ end
+ end
+ unless buffered_chunks.empty?
+ yield buffered_chunks.join
+ end
+ end
+ end
+ end
+
+ ActionView::Base.send :include, View
+ ActionController::Dispatcher.middleware.use Middleware
+ end
+end
View
@@ -0,0 +1,75 @@
+require 'spec/spec_helper'
+
+describe TemplateStreaming::Autoflushing do
+ include ProgressiveRenderingTest
+
+ describe "when rendering progressively" do
+ describe "when autoflushing is on" do
+ use_attribute_value TemplateStreaming, :autoflush, 0
+
+ it "should automatically flush" do
+ layout "[<%= yield %>]"
+ view "(<%= render :partial => 'partial' %>)"
+ partial 'partial'
+ action { render :progressive => true, :layout => 'layout' }
+ run
+ received.should == chunks('[', '(', 'partial', ')', ']', :end => true)
+ end
+
+ it "should autoflush correctly for partials with layouts where the partial is given as an option" do
+ layout "[<%= yield %>]"
+ view "(<%= render :partial => 'partial' %>)"
+ partial "{<%= render :layout => 'subpartial_layout', :partial => 'subpartial' %>}"
+ template 'test/_subpartial_layout', '<<%= yield %>>'
+ template 'test/_subpartial', 'subpartial'
+ action { render :progressive => true, :layout => 'layout' }
+ run
+ received.should == chunks('[', '(', '{', '<', 'subpartial', '>', '}', ')', ']', :end => true)
+ end
+
+ it "should autoflush correctly for partials with layouts where the partial is given as a block" do
+ layout "[<%= yield %>]"
+ view "(<%= render :partial => 'partial' %>)"
+ partial "{<% render :layout => 'subpartial_layout' do %>`<%= render :partial => 'subpartial' %>'<% end %>}"
+ template 'test/_subpartial_layout', '<<%= yield %>>'
+ template 'test/_subpartial', 'subpartial'
+ action { render :progressive => true, :layout => 'layout' }
+ run
+ received.should == chunks('[', '(', '{', '<', '`', 'subpartial', '\'', '>', '}', ')', ']', :end => true)
+ end
+
+ it "should autoflush correctly for views with multiple partials" do
+ layout "[<%= yield %>][<%= yield %>]"
+ view "(<%= render :partial => 'partial' %>)(<%= render :partial => 'partial' %>)"
+ partial 'partial'
+ action { render :progressive => true, :layout => 'layout' }
+ run
+ received.should == chunks('[', '(', 'partial', ')(', 'partial', ')', '][', '(', 'partial', ')(', 'partial', ')', ']', :end => true)
+ end
+
+ it "should flush correctly when some of the automatic flushes are throttled" do
+ with_attribute_value TemplateStreaming, :autoflush, 0.2 do
+ data.t = Time.now
+ Time.stub(:now).and_return(data.t)
+ view <<-EOS.gsub(/^ *\|/, '')
+ |<%= 1 -%>
+ |<%= Time.stub(:now).and_return(data.t + 0.1); render :partial => 'a' -%>
+ |<%= 2 -%>
+ |<%= Time.stub(:now).and_return(data.t + 0.2); render :partial => 'b' -%>
+ |<%= 3 -%>
+ |<%= Time.stub(:now).and_return(data.t + 0.3); render :partial => 'c' -%>
+ |<%= 4 -%>
+ EOS
+ action { render :progressive => true, :layout => nil }
+ template 'test/_a', 'a'
+ template 'test/_b', 'b'
+ template 'test/_c', 'c'
+ template 'test/_d', 'd'
+ action { render :progressive => true, :layout => nil }
+ run
+ received.should == chunks('1', 'a2b3', 'c4', :end => true)
+ end
+ end
+ end
+ end
+end

0 comments on commit 2074201

Please sign in to comment.