aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/lib/action_view/base.rb10
-rw-r--r--actionpack/lib/action_view/body_parts/future.rb26
-rw-r--r--actionpack/lib/action_view/body_parts/open_uri.rb13
-rw-r--r--actionpack/lib/action_view/body_parts/queued.rb21
-rw-r--r--actionpack/lib/action_view/body_parts/threaded.rb21
-rw-r--r--actionpack/test/template/body_parts_test.rb142
-rw-r--r--actionpack/test/template/output_buffer_test.rb35
7 files changed, 231 insertions, 37 deletions
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index e19acc5c29..3bbd2ca530 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -288,12 +288,12 @@ module ActionView #:nodoc:
# Access the current template being rendered.
# Returns a ActionView::Template object.
def template
- @_current_render
+ Thread.current[:_current_render]
end
def template=(template) #:nodoc:
@_first_render ||= template
- @_current_render = template
+ Thread.current[:_current_render] = template
end
def with_template(current_template)
@@ -303,6 +303,12 @@ module ActionView #:nodoc:
self.template = last_template
end
+ def punctuate_body!(part)
+ flush_output_buffer
+ response.body_parts << part
+ nil
+ end
+
private
# Evaluates the local assigns and controller ivars, pushes them to the view.
def _evaluate_assigns_and_ivars #:nodoc:
diff --git a/actionpack/lib/action_view/body_parts/future.rb b/actionpack/lib/action_view/body_parts/future.rb
new file mode 100644
index 0000000000..a5291c6e8c
--- /dev/null
+++ b/actionpack/lib/action_view/body_parts/future.rb
@@ -0,0 +1,26 @@
+module ActionView
+ module BodyParts
+ class Future
+ def initialize(&block)
+ @block = block
+ @parts = []
+ end
+
+ def to_s
+ finish
+ body
+ end
+
+ protected
+ def work
+ @block.call(@parts)
+ end
+
+ def body
+ str = ''
+ @parts.each { |part| str << part.to_s }
+ str
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/body_parts/open_uri.rb b/actionpack/lib/action_view/body_parts/open_uri.rb
new file mode 100644
index 0000000000..8ebd17b4a1
--- /dev/null
+++ b/actionpack/lib/action_view/body_parts/open_uri.rb
@@ -0,0 +1,13 @@
+require 'action_view/body_parts/threaded'
+require 'open-uri'
+
+module ActionView
+ module BodyParts
+ class OpenUri < Threaded
+ def initialize(url)
+ url = URI::Generic === url ? url : URI.parse(url)
+ super(true) { |parts| parts << url.read }
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/body_parts/queued.rb b/actionpack/lib/action_view/body_parts/queued.rb
new file mode 100644
index 0000000000..f8501f6a85
--- /dev/null
+++ b/actionpack/lib/action_view/body_parts/queued.rb
@@ -0,0 +1,21 @@
+require 'action_view/body_parts/future'
+
+module ActionView
+ module BodyParts
+ class Queued < Future
+ def initialize(job, &block)
+ super(&block)
+ enqueue(job)
+ end
+
+ protected
+ def enqueue(job)
+ @receipt = submit(job)
+ end
+
+ def finish
+ @parts << redeem(@receipt)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/body_parts/threaded.rb b/actionpack/lib/action_view/body_parts/threaded.rb
new file mode 100644
index 0000000000..34800bf9c7
--- /dev/null
+++ b/actionpack/lib/action_view/body_parts/threaded.rb
@@ -0,0 +1,21 @@
+require 'action_view/body_parts/future'
+
+module ActionView
+ module BodyParts
+ class Threaded < Future
+ def initialize(concurrent = false, &block)
+ super(&block)
+ concurrent ? start : work
+ end
+
+ protected
+ def start
+ @worker = Thread.new { work }
+ end
+
+ def finish
+ @worker.join if @worker && @worker.alive?
+ end
+ end
+ end
+end
diff --git a/actionpack/test/template/body_parts_test.rb b/actionpack/test/template/body_parts_test.rb
new file mode 100644
index 0000000000..d15c8808d9
--- /dev/null
+++ b/actionpack/test/template/body_parts_test.rb
@@ -0,0 +1,142 @@
+require 'abstract_unit'
+require 'action_view/body_parts/queued'
+require 'action_view/body_parts/open_uri'
+
+class OutputBufferTest < ActionController::TestCase
+ class TestController < ActionController::Base
+ def index
+ render :text => 'foo'
+ end
+ end
+
+ tests TestController
+
+ def test_flush_output_buffer
+ # Start with the default body parts
+ get :index
+ assert_equal ['foo'], @response.body_parts
+ assert_nil @response.template.output_buffer
+
+ # Nil output buffer is skipped
+ @response.template.flush_output_buffer
+ assert_nil @response.template.output_buffer
+ assert_equal ['foo'], @response.body_parts
+
+ # Empty output buffer is skipped
+ @response.template.output_buffer = ''
+ @response.template.flush_output_buffer
+ assert_equal '', @response.template.output_buffer
+ assert_equal ['foo'], @response.body_parts
+
+ # Flushing appends the output buffer to the body parts
+ @response.template.output_buffer = 'bar'
+ @response.template.flush_output_buffer
+ assert_equal '', @response.template.output_buffer
+ assert_equal ['foo', 'bar'], @response.body_parts
+ end
+end
+
+class QueuedPartTest < ActionController::TestCase
+ class SimpleQueued < ActionView::BodyParts::Queued
+ protected
+ def submit(job)
+ job
+ end
+
+ def redeem(receipt)
+ receipt.to_s.reverse
+ end
+ end
+
+ class TestController < ActionController::Base
+ def index
+ queued_render 'foo'
+ queued_render 'bar'
+ queued_render 'baz'
+ @performed_render = true
+ end
+
+ def queued_render(job)
+ response.template.punctuate_body! SimpleQueued.new(job)
+ end
+ end
+
+ tests TestController
+
+ def test_queued_parts
+ get :index
+ assert_equal 'oofrabzab', @response.body
+ end
+end
+
+class ThreadedPartTest < ActionController::TestCase
+ class TestController < ActionController::Base
+ def index
+ append_thread_id = lambda do |parts|
+ parts << Thread.current.object_id
+ parts << '::'
+ parts << Time.now.to_i
+ sleep 1
+ end
+
+ future_render &append_thread_id
+ response.body_parts << '-'
+
+ future_render &append_thread_id
+ response.body_parts << '-'
+
+ future_render do |parts|
+ parts << ActionView::BodyParts::Threaded.new(true, &append_thread_id)
+ parts << '-'
+ parts << ActionView::BodyParts::Threaded.new(true, &append_thread_id)
+ end
+
+ @performed_render = true
+ end
+
+ def future_render(&block)
+ response.template.punctuate_body! ActionView::BodyParts::Threaded.new(true, &block)
+ end
+ end
+
+ tests TestController
+
+ def test_concurrent_threaded_parts
+ get :index
+
+ before = Time.now.to_i
+ thread_ids = @response.body.split('-').map { |part| part.split('::').first.to_i }
+ elapsed = Time.now.to_i - before
+
+ assert_equal thread_ids.size, thread_ids.uniq.size
+ assert elapsed < 1.1
+ end
+end
+
+class OpenUriPartTest < ActionController::TestCase
+ class TestController < ActionController::Base
+ def index
+ render_url 'http://localhost/foo'
+ render_url 'http://localhost/bar'
+ render_url 'http://localhost/baz'
+ @performed_render = true
+ end
+
+ def render_url(url)
+ url = URI.parse(url)
+ def url.read; path end
+ response.template.punctuate_body! ActionView::BodyParts::OpenUri.new(url)
+ end
+ end
+
+ tests TestController
+
+ def test_concurrent_open_uri_parts
+ get :index
+
+ elapsed = Benchmark.ms do
+ assert_equal '/foo/bar/baz', @response.body
+ end
+ assert elapsed < 1.1
+ end
+end
diff --git a/actionpack/test/template/output_buffer_test.rb b/actionpack/test/template/output_buffer_test.rb
deleted file mode 100644
index 6d8eab63dc..0000000000
--- a/actionpack/test/template/output_buffer_test.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'abstract_unit'
-
-class OutputBufferTest < ActionController::TestCase
- class TestController < ActionController::Base
- def index
- render :text => 'foo'
- end
- end
-
- tests TestController
-
- def test_flush_output_buffer
- # Start with the default body parts
- get :index
- assert_equal ['foo'], @response.body_parts
- assert_nil @response.template.output_buffer
-
- # Nil output buffer is skipped
- @response.template.flush_output_buffer
- assert_nil @response.template.output_buffer
- assert_equal ['foo'], @response.body_parts
-
- # Empty output buffer is skipped
- @response.template.output_buffer = ''
- @response.template.flush_output_buffer
- assert_equal '', @response.template.output_buffer
- assert_equal ['foo'], @response.body_parts
-
- # Flushing appends the output buffer to the body parts
- @response.template.output_buffer = 'bar'
- @response.template.flush_output_buffer
- assert_equal '', @response.template.output_buffer
- assert_equal ['foo', 'bar'], @response.body_parts
- end
-end