Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Body parts: future rendering, threaded future, queued future, open-ur…

…i example
  • Loading branch information...
commit 7c1714cbd0b37d1fc5ca2ac3e08980943454d516 1 parent 79b0b1a
@jeremy jeremy authored
View
10 actionpack/lib/action_view/base.rb
@@ -288,12 +288,12 @@ def template_format
# 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 @@ def with_template(current_template)
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:
View
26 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
View
13 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
View
21 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
View
21 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
View
142 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
View
35 actionpack/test/template/output_buffer_test.rb
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.