aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorMatthew Draper <matthew@trebex.net>2016-02-22 11:55:52 +1030
committerMatthew Draper <matthew@trebex.net>2016-03-02 02:14:20 +1030
commitd3c9d808e3e242155a44fd2a89ef272cfade8fe8 (patch)
treef24199aec76f44fa8b6536c0664382672ad10e10 /actionpack
parent664a13e6fb8281107da0da75e7cd91bba1425f76 (diff)
downloadrails-d3c9d808e3e242155a44fd2a89ef272cfade8fe8.tar.gz
rails-d3c9d808e3e242155a44fd2a89ef272cfade8fe8.tar.bz2
rails-d3c9d808e3e242155a44fd2a89ef272cfade8fe8.zip
Publish AS::Executor and AS::Reloader APIs
These should allow external code to run blocks of user code to do "work", at a similar unit size to a web request, without needing to get intimate with ActionDipatch.
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb11
-rw-r--r--actionpack/lib/action_dispatch/middleware/executor.rb19
-rw-r--r--actionpack/lib/action_dispatch/middleware/load_interlock.rb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb74
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb13
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb14
-rw-r--r--actionpack/test/dispatch/executor_test.rb134
-rw-r--r--actionpack/test/dispatch/reloader_test.rb67
9 files changed, 250 insertions, 105 deletions
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 1e4df07d6e..01d49475de 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -51,8 +51,8 @@ module ActionDispatch
autoload :Cookies
autoload :DebugExceptions
autoload :ExceptionWrapper
+ autoload :Executor
autoload :Flash
- autoload :LoadInterlock
autoload :ParamsParser
autoload :PublicExceptions
autoload :Reloader
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index f80df78582..c782779b34 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -7,7 +7,16 @@ module ActionDispatch
define_callbacks :call
class << self
- delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
+ def to_prepare(*args, &block)
+ ActiveSupport::Reloader.to_prepare(*args, &block)
+ end
+
+ def to_cleanup(*args, &block)
+ ActiveSupport::Reloader.to_complete(*args, &block)
+ end
+
+ deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead',
+ to_cleanup: 'use ActiveSupport::Reloader.to_complete instead'
def before(*args, &block)
set_callback(:call, :before, *args, &block)
diff --git a/actionpack/lib/action_dispatch/middleware/executor.rb b/actionpack/lib/action_dispatch/middleware/executor.rb
new file mode 100644
index 0000000000..06245b403b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/executor.rb
@@ -0,0 +1,19 @@
+require 'rack/body_proxy'
+
+module ActionDispatch
+ class Executor
+ def initialize(app, executor)
+ @app, @executor = app, executor
+ end
+
+ def call(env)
+ state = @executor.run!
+ begin
+ response = @app.call(env)
+ returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
+ ensure
+ state.complete! unless returned
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/load_interlock.rb b/actionpack/lib/action_dispatch/middleware/load_interlock.rb
deleted file mode 100644
index 07f498319c..0000000000
--- a/actionpack/lib/action_dispatch/middleware/load_interlock.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'active_support/dependencies'
-require 'rack/body_proxy'
-
-module ActionDispatch
- class LoadInterlock
- def initialize(app)
- @app = app
- end
-
- def call(env)
- interlock = ActiveSupport::Dependencies.interlock
- interlock.start_running
- response = @app.call(env)
- body = Rack::BodyProxy.new(response[2]) { interlock.done_running }
- response[2] = body
- response
- ensure
- interlock.done_running unless body
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index af9a29eb07..e5cc588bd6 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -23,74 +23,36 @@ module ActionDispatch
# middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
# or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
#
- class Reloader
- include ActiveSupport::Callbacks
- include ActiveSupport::Deprecation::Reporting
-
- define_callbacks :prepare
- define_callbacks :cleanup
-
- # Add a prepare callback. Prepare callbacks are run before each request, prior
- # to ActionDispatch::Callback's before callbacks.
+ class Reloader < Executor
def self.to_prepare(*args, &block)
- unless block_given?
- warn "to_prepare without a block is deprecated. Please use a block"
- end
- set_callback(:prepare, *args, &block)
+ ActiveSupport::Reloader.to_prepare(*args, &block)
end
- # Add a cleanup callback. Cleanup callbacks are run after each request is
- # complete (after #close is called on the response body).
def self.to_cleanup(*args, &block)
- unless block_given?
- warn "to_cleanup without a block is deprecated. Please use a block"
- end
- set_callback(:cleanup, *args, &block)
+ ActiveSupport::Reloader.to_complete(*args, &block)
end
- # Execute all prepare callbacks.
def self.prepare!
- new(nil).prepare!
+ if defined? Rails.application.reloader
+ Rails.application.reloader.prepare!
+ else
+ ActiveSupport::Reloader.prepare!
+ end
end
- # Execute all cleanup callbacks.
def self.cleanup!
- new(nil).cleanup!
- end
-
- def initialize(app, condition=nil)
- @app = app
- @condition = condition || lambda { true }
- @validated = true
- end
-
- def call(env)
- @validated = @condition.call
- prepare!
-
- response = @app.call(env)
- response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
-
- response
- rescue Exception
- cleanup!
- raise
- end
-
- def prepare! #:nodoc:
- run_callbacks :prepare if validated?
- end
-
- def cleanup! #:nodoc:
- run_callbacks :cleanup if validated?
- ensure
- @validated = true
+ if defined? Rails.application.reloader
+ Rails.application.reloader.reload!
+ else
+ ActiveSupport::Reloader.reload!
+ end
end
- private
-
- def validated? #:nodoc:
- @validated
+ class << self
+ deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead',
+ to_cleanup: 'use ActiveSupport::Reloader.to_complete instead',
+ prepare!: 'use Rails.application.reloader.prepare! instead',
+ cleanup!: 'use Rails.application.reloader.reload! instead of cleanup + prepare'
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index f4534b4173..e871ddd289 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -455,17 +455,24 @@ module ActionDispatch
def before_setup # :nodoc:
@app = nil
@integration_session = nil
+ @execution_context = nil
+ super
+ end
+
+ def after_teardown # :nodoc:
+ remove!
super
end
def integration_session
- @integration_session ||= create_session(app)
+ @integration_session ||= create_session(app).tap { @execution_context = app.respond_to?(:executor) && app.executor.run! }
end
# Reset the current session. This is useful for testing multiple sessions
# in a single test case.
def reset!
- @integration_session = create_session(app)
+ remove!
+ integration_session
end
def create_session(app)
@@ -481,6 +488,8 @@ module ActionDispatch
end
def remove! # :nodoc:
+ @execution_context.complete! if @execution_context
+ @execution_context = nil
@integration_session = nil
end
diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb
index 5ba76d9ab9..7b707df7f6 100644
--- a/actionpack/test/dispatch/callbacks_test.rb
+++ b/actionpack/test/dispatch/callbacks_test.rb
@@ -37,13 +37,19 @@ class DispatcherTest < ActiveSupport::TestCase
def test_to_prepare_and_cleanup_delegation
prepared = cleaned = false
- ActionDispatch::Callbacks.to_prepare { prepared = true }
- ActionDispatch::Callbacks.to_prepare { cleaned = true }
+ assert_deprecated do
+ ActionDispatch::Callbacks.to_prepare { prepared = true }
+ ActionDispatch::Callbacks.to_prepare { cleaned = true }
+ end
- ActionDispatch::Reloader.prepare!
+ assert_deprecated do
+ ActionDispatch::Reloader.prepare!
+ end
assert prepared
- ActionDispatch::Reloader.cleanup!
+ assert_deprecated do
+ ActionDispatch::Reloader.cleanup!
+ end
assert cleaned
end
diff --git a/actionpack/test/dispatch/executor_test.rb b/actionpack/test/dispatch/executor_test.rb
new file mode 100644
index 0000000000..28bb232ecd
--- /dev/null
+++ b/actionpack/test/dispatch/executor_test.rb
@@ -0,0 +1,134 @@
+require 'abstract_unit'
+
+class ExecutorTest < ActiveSupport::TestCase
+ class MyBody < Array
+ def initialize(&block)
+ @on_close = block
+ end
+
+ def foo
+ "foo"
+ end
+
+ def bar
+ "bar"
+ end
+
+ def close
+ @on_close.call if @on_close
+ end
+ end
+
+ def test_returned_body_object_always_responds_to_close
+ body = call_and_return_body
+ assert_respond_to body, :close
+ end
+
+ def test_returned_body_object_always_responds_to_close_even_if_called_twice
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+ end
+
+ def test_returned_body_object_behaves_like_underlying_object
+ body = call_and_return_body do
+ b = MyBody.new
+ b << "hello"
+ b << "world"
+ [200, { "Content-Type" => "text/html" }, b]
+ end
+ assert_equal 2, body.size
+ assert_equal "hello", body[0]
+ assert_equal "world", body[1]
+ assert_equal "foo", body.foo
+ assert_equal "bar", body.bar
+ end
+
+ def test_it_calls_close_on_underlying_object_when_close_is_called_on_body
+ close_called = false
+ body = call_and_return_body do
+ b = MyBody.new do
+ close_called = true
+ end
+ [200, { "Content-Type" => "text/html" }, b]
+ end
+ body.close
+ assert close_called
+ end
+
+ def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object
+ body = call_and_return_body do
+ [200, { "Content-Type" => "text/html" }, MyBody.new]
+ end
+ assert_respond_to body, :size
+ assert_respond_to body, :each
+ assert_respond_to body, :foo
+ assert_respond_to body, :bar
+ end
+
+ def test_run_callbacks_are_called_before_close
+ running = false
+ executor.to_run { running = true }
+
+ body = call_and_return_body
+ assert running
+
+ running = false
+ body.close
+ assert !running
+ end
+
+ def test_complete_callbacks_are_called_on_close
+ completed = false
+ executor.to_complete { completed = true }
+
+ body = call_and_return_body
+ assert !completed
+
+ body.close
+ assert completed
+ end
+
+ def test_complete_callbacks_are_called_on_exceptions
+ completed = false
+ executor.to_complete { completed = true }
+
+ begin
+ call_and_return_body do
+ raise "error"
+ end
+ rescue
+ end
+
+ assert completed
+ end
+
+ def test_callbacks_execute_in_shared_context
+ result = false
+ executor.to_run { @in_shared_context = true }
+ executor.to_complete { result = @in_shared_context }
+
+ call_and_return_body.close
+ assert result
+ assert !defined?(@in_shared_context) # it's not in the test itself
+ end
+
+ private
+ def call_and_return_body(&block)
+ app = middleware(block || proc { [200, {}, 'response'] })
+ _, _, body = app.call({'rack.input' => StringIO.new('')})
+ body
+ end
+
+ def middleware(inner_app)
+ ActionDispatch::Executor.new(inner_app, executor)
+ end
+
+ def executor
+ @executor ||= Class.new(ActiveSupport::Executor)
+ end
+end
diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb
index 62e8197e20..fe8a4a3a17 100644
--- a/actionpack/test/dispatch/reloader_test.rb
+++ b/actionpack/test/dispatch/reloader_test.rb
@@ -4,15 +4,17 @@ class ReloaderTest < ActiveSupport::TestCase
Reloader = ActionDispatch::Reloader
teardown do
- Reloader.reset_callbacks :prepare
- Reloader.reset_callbacks :cleanup
+ ActiveSupport::Reloader.reset_callbacks :prepare
+ ActiveSupport::Reloader.reset_callbacks :complete
end
def test_prepare_callbacks
a = b = c = nil
- Reloader.to_prepare { |*args| a = b = c = 1 }
- Reloader.to_prepare { |*args| b = c = 2 }
- Reloader.to_prepare { |*args| c = 3 }
+ assert_deprecated do
+ Reloader.to_prepare { |*args| a = b = c = 1 }
+ Reloader.to_prepare { |*args| b = c = 2 }
+ Reloader.to_prepare { |*args| c = 3 }
+ end
# Ensure to_prepare callbacks are not run when defined
assert_nil a || b || c
@@ -60,9 +62,15 @@ class ReloaderTest < ActiveSupport::TestCase
def test_condition_specifies_when_to_reload
i, j = 0, 0, 0, 0
- Reloader.to_prepare { |*args| i += 1 }
- Reloader.to_cleanup { |*args| j += 1 }
- app = Reloader.new(lambda { |env| [200, {}, []] }, lambda { i < 3 })
+ assert_deprecated do
+ Reloader.to_prepare { |*args| i += 1 }
+ Reloader.to_cleanup { |*args| j += 1 }
+ end
+
+ x = Class.new(ActiveSupport::Reloader)
+ x.check = lambda { i < 3 }
+
+ app = Reloader.new(lambda { |env| [200, {}, []] }, x)
5.times do
resp = app.call({})
resp[2].close
@@ -109,7 +117,9 @@ class ReloaderTest < ActiveSupport::TestCase
def test_cleanup_callbacks_are_called_when_body_is_closed
cleaned = false
- Reloader.to_cleanup { cleaned = true }
+ assert_deprecated do
+ Reloader.to_cleanup { cleaned = true }
+ end
body = call_and_return_body
assert !cleaned
@@ -120,7 +130,9 @@ class ReloaderTest < ActiveSupport::TestCase
def test_prepare_callbacks_arent_called_when_body_is_closed
prepared = false
- Reloader.to_prepare { prepared = true }
+ assert_deprecated do
+ Reloader.to_prepare { prepared = true }
+ end
body = call_and_return_body
prepared = false
@@ -131,31 +143,43 @@ class ReloaderTest < ActiveSupport::TestCase
def test_manual_reloading
prepared = cleaned = false
- Reloader.to_prepare { prepared = true }
- Reloader.to_cleanup { cleaned = true }
+ assert_deprecated do
+ Reloader.to_prepare { prepared = true }
+ Reloader.to_cleanup { cleaned = true }
+ end
- Reloader.prepare!
+ assert_deprecated do
+ Reloader.prepare!
+ end
assert prepared
assert !cleaned
prepared = cleaned = false
- Reloader.cleanup!
- assert !prepared
+ assert_deprecated do
+ Reloader.cleanup!
+ end
+ assert prepared
assert cleaned
end
def test_prepend_prepare_callback
i = 10
- Reloader.to_prepare { i += 1 }
- Reloader.to_prepare(:prepend => true) { i = 0 }
+ assert_deprecated do
+ Reloader.to_prepare { i += 1 }
+ Reloader.to_prepare(:prepend => true) { i = 0 }
+ end
- Reloader.prepare!
+ assert_deprecated do
+ Reloader.prepare!
+ end
assert_equal 1, i
end
def test_cleanup_callbacks_are_called_on_exceptions
cleaned = false
- Reloader.to_cleanup { cleaned = true }
+ assert_deprecated do
+ Reloader.to_cleanup { cleaned = true }
+ end
begin
call_and_return_body do
@@ -169,8 +193,11 @@ class ReloaderTest < ActiveSupport::TestCase
private
def call_and_return_body(&block)
+ x = Class.new(ActiveSupport::Reloader)
+ x.check = lambda { true }
+
@response ||= 'response'
- @reloader ||= Reloader.new(block || proc {[200, {}, @response]})
+ @reloader ||= Reloader.new(block || proc {[200, {}, @response]}, x)
@reloader.call({'rack.input' => StringIO.new('')})[2]
end
end