aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actioncable/CHANGELOG.md5
-rw-r--r--actioncable/README.md8
-rw-r--r--actioncable/lib/action_cable/connection/base.rb5
-rw-r--r--actioncable/lib/action_cable/engine.rb25
-rw-r--r--actioncable/lib/action_cable/server/base.rb10
-rw-r--r--actioncable/lib/action_cable/server/worker.rb48
-rw-r--r--actioncable/lib/action_cable/server/worker/active_record_connection_management.rb3
-rw-r--r--actioncable/lib/rails/generators/channel/USAGE2
-rw-r--r--actioncable/test/client_test.rb20
-rw-r--r--actioncable/test/stubs/test_server.rb4
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb11
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb1
-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.rb66
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb13
-rw-r--r--actionpack/test/abstract_unit.rb8
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb20
-rw-r--r--actionpack/test/controller/base_test.rb15
-rw-r--r--actionpack/test/controller/flash_test.rb4
-rw-r--r--actionpack/test/controller/integration_test.rb25
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_body_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_html_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_plain_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_test.rb8
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb4
-rw-r--r--actionpack/test/controller/redirect_test.rb9
-rw-r--r--actionpack/test/controller/render_test.rb5
-rw-r--r--actionpack/test/controller/render_xml_test.rb5
-rw-r--r--actionpack/test/controller/routing_test.rb242
-rw-r--r--actionpack/test/controller/test_case_test.rb17
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb15
-rw-r--r--actionpack/test/controller/url_for_test.rb15
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb4
-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
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb8
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb8
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb8
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb16
-rw-r--r--actionpack/test/dispatch/routing_test.rb80
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb4
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb4
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb4
-rw-r--r--actionpack/test/journey/router_test.rb130
-rw-r--r--actionview/lib/action_view/template/resolver.rb8
-rw-r--r--actionview/test/template/resolver_patterns_test.rb19
-rw-r--r--activejob/CHANGELOG.md12
-rw-r--r--activejob/lib/active_job.rb1
-rw-r--r--activejob/lib/active_job/async_job.rb77
-rw-r--r--activejob/lib/active_job/callbacks.rb5
-rw-r--r--activejob/lib/active_job/execution.rb6
-rw-r--r--activejob/lib/active_job/queue_adapters/async_adapter.rb105
-rw-r--r--activejob/lib/active_job/railtie.rb9
-rw-r--r--activejob/test/adapters/async.rb4
-rw-r--r--activejob/test/cases/async_job_test.rb42
-rw-r--r--activejob/test/integration/queuing_test.rb4
-rw-r--r--activejob/test/support/integration/adapters/async.rb3
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb2
-rw-r--r--activerecord/CHANGELOG.md21
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb42
-rw-r--r--activerecord/lib/active_record/core.rb8
-rw-r--r--activerecord/lib/active_record/errors.rb5
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/query_cache.rb42
-rw-r--r--activerecord/lib/active_record/railtie.rb18
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation/batches.rb38
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb27
-rw-r--r--activerecord/test/cases/adapter_test.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb32
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb84
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb22
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb32
-rw-r--r--activerecord/test/cases/associations/eager_test.rb32
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb7
-rw-r--r--activerecord/test/cases/batches_test.rb36
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb3
-rw-r--r--activerecord/test/cases/connection_management_test.rb64
-rw-r--r--activerecord/test/cases/finder_test.rb60
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb88
-rw-r--r--activerecord/test/cases/locking_test.rb2
-rw-r--r--activerecord/test/cases/query_cache_test.rb56
-rw-r--r--activerecord/test/models/tag.rb6
-rw-r--r--activesupport/CHANGELOG.md11
-rw-r--r--activesupport/lib/active_support.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb4
-rw-r--r--activesupport/lib/active_support/dependencies.rb4
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb14
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb1
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb69
-rw-r--r--activesupport/lib/active_support/executor.rb6
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb1
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb4
-rw-r--r--activesupport/lib/active_support/reloader.rb126
-rw-r--r--activesupport/test/core_ext/module_test.rb8
-rw-r--r--activesupport/test/executor_test.rb76
-rw-r--r--activesupport/test/reloader_test.rb85
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css2
-rw-r--r--guides/source/action_view_overview.md10
-rw-r--r--guides/source/active_support_core_extensions.md23
-rw-r--r--guides/source/configuring.md2
-rw-r--r--railties/CHANGELOG.md5
-rw-r--r--railties/lib/rails/application.rb6
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb22
-rw-r--r--railties/lib/rails/application/finisher.rb73
-rw-r--r--railties/lib/rails/commands/server.rb2
-rw-r--r--railties/lib/rails/console/app.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js13
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru (renamed from railties/lib/rails/generators/rails/app/templates/config.ru.tt)6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/spring.rb6
-rw-r--r--railties/test/application/console_test.rb11
-rw-r--r--railties/test/application/middleware_test.rb26
-rw-r--r--railties/test/application/rake_test.rb2
-rw-r--r--railties/test/generators/actions_test.rb18
-rw-r--r--railties/test/generators/app_generator_test.rb6
-rw-r--r--railties/test/isolation/abstract_unit.rb2
140 files changed, 2076 insertions, 935 deletions
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index a6842d77ef..946fdfb3fc 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Safely support autoloading and class unloading, by preventing concurrent
+ loads, and disconnecting all cables during reload.
+
+ *Matthew Draper*
+
* Ensure ActionCable behaves correctly for non-string queue names.
*Jay Hayes*
diff --git a/actioncable/README.md b/actioncable/README.md
index bb15ad3c70..595830feb0 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -412,7 +412,7 @@ The above will start a cable server on port 28080.
### In app
-If you are using a threaded server like Puma or Thin, the current implementation of Action Cable can run side-along with your Rails application. For example, to listen for WebSocket requests on `/cable`, mount the server at that path:
+If you are using a server that supports the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking), Action Cable can run alongside your Rails application. For example, to listen for WebSocket requests on `/cable`, mount the server at that path:
```ruby
# config/routes.rb
@@ -445,12 +445,8 @@ connection management is handled internally by utilizing Ruby’s native thread
support, which means you can use all your regular Rails models with no problems
as long as you haven’t committed any thread-safety sins.
-But this also means that Action Cable needs to run in its own server process.
-So you'll have one set of server processes for your normal web work, and another
-set of server processes for the Action Cable.
-
The Action Cable server does _not_ need to be a multi-threaded application server.
-This is because Action Cable uses the [Rack socket hijacking API](http://old.blog.phusion.nl/2013/01/23/the-new-rack-socket-hijacking-api/)
+This is because Action Cable uses the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking)
to take over control of connections from the application server. Action Cable
then manages connections internally, in a multithreaded manner, regardless of
whether the application server is multi-threaded or not. So Action Cable works
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
index 60f3ad3e06..afe0d958d7 100644
--- a/actioncable/lib/action_cable/connection/base.rb
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -48,12 +48,13 @@ module ActionCable
include InternalChannel
include Authorization
- attr_reader :server, :env, :subscriptions, :logger
- delegate :stream_event_loop, :worker_pool, :pubsub, to: :server
+ attr_reader :server, :env, :subscriptions, :logger, :worker_pool
+ delegate :stream_event_loop, :pubsub, to: :server
def initialize(server, env)
@server, @env = server, env
+ @worker_pool = server.worker_pool
@logger = new_tagged_logger
@websocket = ActionCable::Connection::WebSocket.new(env, self, stream_event_loop)
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index ae0c59dccd..c90aadaf2c 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -51,5 +51,30 @@ module ActionCable
end
end
end
+
+ initializer "action_cable.set_work_hooks" do |app|
+ ActiveSupport.on_load(:action_cable) do
+ ActionCable::Server::Worker.set_callback :work, :around, prepend: true do |_, inner|
+ app.executor.wrap do
+ # If we took a while to get the lock, we may have been halted
+ # in the meantime. As we haven't started doing any real work
+ # yet, we should pretend that we never made it off the queue.
+ unless stopping?
+ inner.call
+ end
+ end
+ end
+
+ wrap = lambda do |_, inner|
+ app.executor.wrap(&inner)
+ end
+ ActionCable::Channel::Base.set_callback :subscribe, :around, prepend: true, &wrap
+ ActionCable::Channel::Base.set_callback :unsubscribe, :around, prepend: true, &wrap
+
+ app.reloader.before_class_unload do
+ ActionCable.server.restart
+ end
+ end
+ end
end
end
diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb
index c3b64299e3..d9a2653cc2 100644
--- a/actioncable/lib/action_cable/server/base.rb
+++ b/actioncable/lib/action_cable/server/base.rb
@@ -33,6 +33,16 @@ module ActionCable
remote_connections.where(identifiers).disconnect
end
+ def restart
+ connections.each(&:close)
+
+ @mutex.synchronize do
+ worker_pool.halt if @worker_pool
+
+ @worker_pool = nil
+ end
+ end
+
# Gateway to RemoteConnections. See that class for details.
def remote_connections
@remote_connections || @mutex.synchronize { @remote_connections ||= RemoteConnections.new(self) }
diff --git a/actioncable/lib/action_cable/server/worker.rb b/actioncable/lib/action_cable/server/worker.rb
index b920b880db..49cbaec0c0 100644
--- a/actioncable/lib/action_cable/server/worker.rb
+++ b/actioncable/lib/action_cable/server/worker.rb
@@ -20,6 +20,26 @@ module ActionCable
)
end
+ # Stop processing work: any work that has not already started
+ # running will be discarded from the queue
+ def halt
+ @pool.kill
+ end
+
+ def stopping?
+ @pool.shuttingdown?
+ end
+
+ def work(connection)
+ self.connection = connection
+
+ run_callbacks :work do
+ yield
+ end
+ ensure
+ self.connection = nil
+ end
+
def async_invoke(receiver, method, *args)
@pool.post do
invoke(receiver, method, *args)
@@ -27,19 +47,15 @@ module ActionCable
end
def invoke(receiver, method, *args)
- begin
- self.connection = receiver
-
- run_callbacks :work do
+ work(receiver) do
+ begin
receiver.send method, *args
- end
- rescue Exception => e
- logger.error "There was an exception - #{e.class}(#{e.message})"
- logger.error e.backtrace.join("\n")
+ rescue Exception => e
+ logger.error "There was an exception - #{e.class}(#{e.message})"
+ logger.error e.backtrace.join("\n")
- receiver.handle_exception if receiver.respond_to?(:handle_exception)
- ensure
- self.connection = nil
+ receiver.handle_exception if receiver.respond_to?(:handle_exception)
+ end
end
end
@@ -50,14 +66,8 @@ module ActionCable
end
def run_periodic_timer(channel, callback)
- begin
- self.connection = channel.connection
-
- run_callbacks :work do
- callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback)
- end
- ensure
- self.connection = nil
+ work(channel.connection) do
+ callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback)
end
end
diff --git a/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb
index 1ac8934410..c1e4aa8103 100644
--- a/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb
+++ b/actioncable/lib/action_cable/server/worker/active_record_connection_management.rb
@@ -1,7 +1,6 @@
module ActionCable
module Server
class Worker
- # Clear active connections between units of work so that way long-running channels or connection processes do not hoard connections.
module ActiveRecordConnectionManagement
extend ActiveSupport::Concern
@@ -13,8 +12,6 @@ module ActionCable
def with_database_connections
connection.logger.tag(ActiveRecord::Base.logger) { yield }
- ensure
- ActiveRecord::Base.clear_active_connections!
end
end
end
diff --git a/actioncable/lib/rails/generators/channel/USAGE b/actioncable/lib/rails/generators/channel/USAGE
index 27a934c689..6249553c22 100644
--- a/actioncable/lib/rails/generators/channel/USAGE
+++ b/actioncable/lib/rails/generators/channel/USAGE
@@ -3,7 +3,7 @@ Description:
Stubs out a new cable channel for the server (in Ruby) and client (in CoffeeScript).
Pass the channel name, either CamelCased or under_scored, and an optional list of channel actions as arguments.
- Note: Turn on the cable connection in app/assets/javascript/cable.coffee after generating any channels.
+ Note: Turn on the cable connection in app/assets/javascript/cable.js after generating any channels.
Example:
========
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index 1b07689127..a6619d3bd2 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -127,8 +127,16 @@ class ClientTest < ActionCable::TestCase
end
@ws.close
+ wait_for_close
+ end
+
+ def wait_for_close
@closed.wait(WAIT_WHEN_EXPECTING_EVENT)
end
+
+ def closed?
+ @closed.set?
+ end
end
def faye_client(port)
@@ -220,4 +228,16 @@ class ClientTest < ActionCable::TestCase
assert_equal(0, app.connections.count)
end
end
+
+ def test_server_restart
+ with_puma_server do |port|
+ c = faye_client(port)
+ c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel')
+ assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
+
+ ActionCable.server.restart
+ c.wait_for_close
+ assert c.closed?
+ end
+ end
end
diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb
index 56d132b30a..5916cf1e83 100644
--- a/actioncable/test/stubs/test_server.rb
+++ b/actioncable/test/stubs/test_server.rb
@@ -17,4 +17,8 @@ class TestServer
def stream_event_loop
@stream_event_loop ||= ActionCable::Connection::StreamEventLoop.new
end
+
+ def worker_pool
+ @worker_pool ||= ActionCable::Server::Worker.new(max_size: 5)
+ end
end
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/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 9dcab79c3a..041eca48ca 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -4,9 +4,11 @@ module ActionDispatch
module Http
# Allows you to specify sensitive parameters which will be replaced from
# the request log by looking in the query string of the request and all
- # sub-hashes of the params hash to filter. If a block is given, each key and
- # value of the params hash and all sub-hashes is passed to it, the value
- # or key can be replaced using String#replace or similar method.
+ # sub-hashes of the params hash to filter. Filtering only certain sub-keys
+ # from a hash is possible by using the dot notation: 'credit_card.number'.
+ # If a block is given, each key and value of the params hash and all
+ # sub-hashes is passed to it, the value or key can be replaced using
+ # String#replace or similar method.
#
# env["action_dispatch.parameter_filter"] = [:password]
# => replaces the value to all keys matching /password/i with "[FILTERED]"
@@ -14,6 +16,10 @@ module ActionDispatch
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
#
+ # env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
+ # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
+ # change { file: { code: "xxxx"} }
+ #
# env["action_dispatch.parameter_filter"] = -> (k, v) do
# v.reverse! if k =~ /secret/i
# end
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/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 3b61824cc9..59edc66086 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,4 +1,3 @@
-require 'action_controller/metal/exceptions'
require 'active_support/core_ext/module/attribute_accessors'
require 'rack/utils'
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..112bde6596 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -23,74 +23,32 @@ 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!
+ default_reloader.prepare!
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
+ default_reloader.reload!
end
- def call(env)
- @validated = @condition.call
- prepare!
-
- response = @app.call(env)
- response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
+ class << self
+ attr_accessor :default_reloader # :nodoc:
- response
- rescue Exception
- cleanup!
- raise
+ 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
- def prepare! #:nodoc:
- run_callbacks :prepare if validated?
- end
-
- def cleanup! #:nodoc:
- run_callbacks :cleanup if validated?
- ensure
- @validated = true
- end
-
- private
-
- def validated? #:nodoc:
- @validated
- end
+ self.default_reloader = ActiveSupport::Reloader
end
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index ddeea24bb3..e9e6a2e597 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -39,6 +39,8 @@ module ActionDispatch
config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
+ ActionDispatch::Reloader.default_reloader = app.reloader
+
ActionDispatch.test_app = app
end
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 310e98f584..f807a8fe16 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -513,6 +513,15 @@ module ActionDispatch
route = @set.add_route(name, mapping)
named_routes[name] = route if name
+
+ if route.segment_keys.include?(:controller)
+ ActiveSupport::Deprecation.warn("Using a dynamic :controller segment in a route is deprecated and will be remove in Rails 5.1")
+ end
+
+ if route.segment_keys.include?(:action)
+ ActiveSupport::Deprecation.warn("Using a dynamic :action segment in a route is deprecated and will be remove in Rails 5.1")
+ end
+
route
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/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 1ef10ff956..7a438f85f7 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -69,7 +69,9 @@ FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
SharedTestRoutes.draw do
- get ':controller(/:action)'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action)'
+ end
end
module ActionDispatch
@@ -118,7 +120,9 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
self.app = build_app
app.routes.draw do
- get ':controller(/:action)'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action)'
+ end
end
class DeadEndRoutes < ActionDispatch::Routing::RouteSet
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 899d92f815..db71aa2160 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -177,7 +177,10 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
set.draw do
get 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
get 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
process :redirect_to_named_route
assert_raise(ActiveSupport::TestCase::Assertion) do
@@ -201,7 +204,10 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
get 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
process :redirect_to_index
# redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}>
@@ -215,7 +221,10 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
get '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
process :redirect_to_top_level_named_route
# assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return
@@ -231,7 +240,10 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
set.draw do
# this controller exists in the admin namespace as well which is the only difference from previous test
get '/user/:id', :to => 'user#index', :as => :top_level
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
process :redirect_to_top_level_named_route
# assert_redirected_to top_level_url('foo') would pass because of exact match early return
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index e3f669dbb5..577a3d5800 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -175,7 +175,10 @@ class UrlOptionsTest < ActionController::TestCase
with_routing do |set|
set.draw do
get 'from_view', :to => 'url_options#from_view', :as => :from_view
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :from_view, params: { route: "from_view_url" }
@@ -209,7 +212,10 @@ class DefaultUrlOptionsTest < ActionController::TestCase
with_routing do |set|
set.draw do
get 'from_view', :to => 'default_url_options#from_view', :as => :from_view
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :from_view, params: { route: "from_view_url" }
@@ -226,7 +232,10 @@ class DefaultUrlOptionsTest < ActionController::TestCase
scope("/:locale") do
resources :descriptions
end
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :from_view, params: { route: "description_path(1)" }
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index b063d769a4..eef48e8480 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -323,7 +323,9 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- get ':action', :to => FlashIntegrationTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => FlashIntegrationTest::TestController
+ end
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 6277407ff7..ad7166bafa 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -730,8 +730,10 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
set.draw do
get 'moved' => redirect('/method')
- match ':action', :to => controller, :via => [:get, :post], :as => :action
- get 'get/:action', :to => controller, :as => :get_action
+ ActiveSupport::Deprecation.silence do
+ match ':action', :to => controller, :via => [:get, :post], :as => :action
+ get 'get/:action', :to => controller, :as => :get_action
+ end
end
self.singleton_class.include(set.url_helpers)
@@ -1105,7 +1107,12 @@ class IntegrationRequestsWithoutSetup < ActionDispatch::IntegrationTest
def test_request
with_routing do |routes|
- routes.draw { get ':action' => FooController }
+ routes.draw do
+ ActiveSupport::Deprecation.silence do
+ get ':action' => FooController
+ end
+ end
+
get '/ok'
assert_response 200
@@ -1173,7 +1180,11 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
def test_parsed_body_without_as_option
with_routing do |routes|
- routes.draw { get ':action' => FooController }
+ routes.draw do
+ ActiveSupport::Deprecation.silence do
+ get ':action' => FooController
+ end
+ end
get '/foos_json.json', params: { foo: 'heyo' }
@@ -1184,7 +1195,11 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
private
def post_to_foos(as:)
with_routing do |routes|
- routes.draw { post ':action' => FooController }
+ routes.draw do
+ ActiveSupport::Deprecation.silence do
+ post ':action' => FooController
+ end
+ end
post "/foos_#{as}", params: { foo: 'fighters' }, as: as
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index a9dcdde4b8..0b3a26807d 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -43,7 +43,9 @@ module ContentType
test "default response is text/plain and UTF8" do
with_routing do |set|
set.draw do
- get ':controller', :action => 'index'
+ ActiveSupport::Deprecation.silence do
+ get ':controller', :action => 'index'
+ end
end
get "/content_type/base"
diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb
index f4a3db8b41..c65c245773 100644
--- a/actionpack/test/controller/new_base/render_body_test.rb
+++ b/actionpack/test/controller/new_base/render_body_test.rb
@@ -85,7 +85,7 @@ module RenderBody
test "rendering body from an action with default options renders the body with the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_body/simple"
assert_body "hello david"
@@ -95,7 +95,7 @@ module RenderBody
test "rendering body from an action with default options renders the body without the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_body/with_layout"
diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb
index e9ea57e329..bfed136496 100644
--- a/actionpack/test/controller/new_base/render_html_test.rb
+++ b/actionpack/test/controller/new_base/render_html_test.rb
@@ -88,7 +88,7 @@ module RenderHtml
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_html/simple"
assert_body "hello david"
@@ -98,7 +98,7 @@ module RenderHtml
test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_html/with_layout"
diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb
index 0881442bd0..94afe7bcfe 100644
--- a/actionpack/test/controller/new_base/render_plain_test.rb
+++ b/actionpack/test/controller/new_base/render_plain_test.rb
@@ -80,7 +80,7 @@ module RenderPlain
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_plain/simple"
assert_body "hello david"
@@ -90,7 +90,7 @@ module RenderPlain
test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
get "/render_plain/with_layout"
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index b06ce5db40..0d4c7cdb0a 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -177,7 +177,7 @@ module RenderTemplate
class TestWithLayout < Rack::TestCase
test "rendering with implicit layout" do
with_routing do |set|
- set.draw { get ':controller', :action => :index }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', :action => :index } }
get "/render_template/with_layout"
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index 963f2c2f5c..1fb852a2c4 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -57,7 +57,9 @@ module Render
test "render with blank" do
with_routing do |set|
set.draw do
- get ":controller", :action => 'index'
+ ActiveSupport::Deprecation.silence do
+ get ":controller", :action => 'index'
+ end
end
get "/render/blank_render"
@@ -70,7 +72,9 @@ module Render
test "rendering more than once raises an exception" do
with_routing do |set|
set.draw do
- get ":controller", :action => 'index'
+ ActiveSupport::Deprecation.silence do
+ get ":controller", :action => 'index'
+ end
end
assert_raises(AbstractController::DoubleRenderError) do
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index 048458178c..d4111d432c 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -83,7 +83,7 @@ module RenderText
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
ActiveSupport::Deprecation.silence do
get "/render_text/simple"
@@ -96,7 +96,7 @@ module RenderText
test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { get ':controller', action: 'index' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller', action: 'index' } }
ActiveSupport::Deprecation.silence do
get "/render_text/with_layout"
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 3ea03be74a..e10d4449f3 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -286,7 +286,10 @@ class RedirectTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :workshops
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :redirect_to_existing_record
@@ -328,7 +331,9 @@ class RedirectTest < ActionController::TestCase
def test_redirect_to_with_block_and_accepted_options
with_routing do |set|
set.draw do
- get ':controller/:action'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :redirect_to_with_block_and_options
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 83d7405e4d..b7f3e121fd 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -615,7 +615,10 @@ class HeadRenderTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :customers
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :head_with_location_object
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
index f0fd7ddc5e..137236c496 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -72,7 +72,10 @@ class RenderXmlTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :customers
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :render_with_object_location
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index a39fede5b9..c477b4156c 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -15,7 +15,9 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase
def setup
@set = ActionDispatch::Routing::RouteSet.new
@set.draw do
- get ':controller/:action/:variable/*additional'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:variable/*additional'
+ end
end
safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ])
@@ -300,7 +302,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_default_setup
- rs.draw { get '/:controller(/:action(/:id))' }
+ rs.draw { ActiveSupport::Deprecation.silence { get '/:controller(/:action(/:id))' } }
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list"))
assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10"))
@@ -323,7 +325,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_colon_first
rs.draw do
- get '/:controller/:action/:id', action: 'index', id: nil
+ ActiveSupport::Deprecation.silence do
+ get '/:controller/:action/:id', action: 'index', id: nil
+ end
+
get ':url', controller: 'content', action: 'translate'
end
@@ -331,7 +336,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_route_with_regexp_for_action
- rs.draw { get '/:controller/:action', action: /auth[-|_].+/ }
+ rs.draw { ActiveSupport::Deprecation.silence { get '/:controller/:action', action: /auth[-|_].+/ } }
assert_equal({ action: 'auth_google', controller: 'content' }, rs.recognize_path('/content/auth_google'))
assert_equal({ action: 'auth-facebook', controller: 'content' }, rs.recognize_path('/content/auth-facebook'))
@@ -342,8 +347,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_regexp_for_controller
rs.draw do
- get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/
- get '/:controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/
+ get '/:controller(/:action(/:id))'
+ end
end
assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"},
@@ -357,7 +364,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_regexp_and_captures_for_controller
rs.draw do
- get '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/
+ end
end
assert_equal({:controller => "admin/accounts", :action => "index"}, rs.recognize_path("/admin/accounts"))
assert_equal({:controller => "admin/users", :action => "index"}, rs.recognize_path("/admin/users"))
@@ -366,11 +375,13 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_regexp_and_dot
rs.draw do
- get ':controller/:action/:file',
- :controller => /admin|user/,
- :action => /upload|download/,
- :defaults => {:file => nil},
- :constraints => {:file => %r{[^/]+(\.[^/]+)?}}
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:file',
+ :controller => /admin|user/,
+ :action => /upload|download/,
+ :defaults => {:file => nil},
+ :constraints => {:file => %r{[^/]+(\.[^/]+)?}}
+ end
end
# Without a file extension
assert_equal '/user/download/file',
@@ -457,7 +468,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_without_hash
rs.draw do
- get ':controller/:action/:id', :as => 'normal'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id', :as => 'normal'
+ end
end
end
@@ -509,7 +522,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
rs.draw do
get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article',
:year => /\d+/, :month => /\d+/, :day => /\d+/
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
routes = setup_for_named_route
@@ -519,7 +535,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_changing_controller
- rs.draw { get ':controller/:action/:id' }
+ rs.draw { ActiveSupport::Deprecation.silence { get ':controller/:action/:id' } }
get URI('http://test.host/admin/user/index/10')
@@ -530,7 +546,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_paths_escaped
rs.draw do
get 'file/*path' => 'content#show_file', :as => 'path'
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
# No + to space in URI escaping, only for query params.
@@ -555,7 +574,9 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_non_controllers_cannot_be_matched
rs.draw do
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_raise(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") }
end
@@ -593,8 +614,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_backwards
rs.draw do
- get 'page/:id(/:action)' => 'pages#show'
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get 'page/:id(/:action)' => 'pages#show'
+ get ':controller(/:action(/:id))'
+ end
end
get URI('http://test.host/pages/show')
@@ -606,7 +629,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_fixnum_default
rs.draw do
get 'page(/:id)' => 'content#show_page', :id => 1
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/page', url_for(rs, { :controller => 'content', :action => 'show_page' })
@@ -623,7 +649,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_text_default
rs.draw do
get 'page/:id' => 'content#show_page', :id => 1
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/page/foo', url_for(rs, { :controller => 'content', :action => 'show_page', :id => 'foo' })
@@ -638,7 +667,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
def test_action_expiry
- rs.draw { get ':controller(/:action(/:id))' }
+ rs.draw { ActiveSupport::Deprecation.silence { get ':controller(/:action(/:id))' } }
get URI('http://test.host/content/show')
assert_equal '/content', controller.url_for(:controller => 'content', :only_path => true)
end
@@ -661,7 +690,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
:defaults => { :year => nil },
:constraints => { :year => /\d{4}/ }
)
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/test', url_for(rs, { :controller => 'post', :action => 'show' })
@@ -673,7 +705,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_set_to_nil_forgets
rs.draw do
get 'pages(/:year(/:month(/:day)))' => 'content#list_pages', :month => nil, :day => nil
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/pages/2005',
@@ -720,7 +755,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_named_route_method
rs.draw do
get 'categories' => 'content#categories', :as => 'categories'
- get ':controller(/:action(/:id))'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
assert_equal '/categories', url_for(rs, { :controller => 'content', :action => 'categories' })
@@ -736,7 +774,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
rs.draw do
get 'journal' => 'content#list_journal',
:date => nil, :user_id => nil
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/journal', url_for(rs, {
@@ -776,10 +817,12 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_subpath_recognized
rs.draw do
- get '/books/:id/edit' => 'subpath_books#edit'
- get '/items/:id/:action' => 'subpath_books'
- get '/posts/new/:action' => 'subpath_books'
- get '/posts/:id' => 'subpath_books#show'
+ ActiveSupport::Deprecation.silence do
+ get '/books/:id/edit' => 'subpath_books#edit'
+ get '/items/:id/:action' => 'subpath_books'
+ get '/posts/new/:action' => 'subpath_books'
+ get '/posts/:id' => 'subpath_books#show'
+ end
end
hash = rs.recognize_path "/books/17/edit"
@@ -801,9 +844,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_subpath_generated
rs.draw do
- get '/books/:id/edit' => 'subpath_books#edit'
- get '/items/:id/:action' => 'subpath_books'
- get '/posts/new/:action' => 'subpath_books'
+ ActiveSupport::Deprecation.silence do
+ get '/books/:id/edit' => 'subpath_books#edit'
+ get '/items/:id/:action' => 'subpath_books'
+ get '/posts/new/:action' => 'subpath_books'
+ end
end
assert_equal "/books/7/edit", url_for(rs, { :controller => "subpath_books", :id => 7, :action => "edit" })
@@ -827,8 +872,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
get 'ca' => 'ca#aa'
get 'cb' => 'cb#ab'
get 'cc' => 'cc#ac'
- get ':controller/:action/:id'
- get ':controller/:action/:id.:format'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ end
end
hash = rs.recognize_path "/cc"
@@ -839,8 +887,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
rs.draw do
get 'cb' => 'cb#ab'
get 'cc' => 'cc#ac'
- get ':controller/:action/:id'
- get ':controller/:action/:id.:format'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ end
end
hash = rs.recognize_path "/cc"
@@ -871,29 +922,34 @@ class RouteSetTest < ActiveSupport::TestCase
@default_route_set ||= begin
set = ActionDispatch::Routing::RouteSet.new
set.draw do
- get '/:controller(/:action(/:id))'
+
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action(/:id))'
+ end
end
set
end
end
def test_generate_extras
- set.draw { get ':controller/(:action(/:id))' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller/(:action(/:id))' } }
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_extra_keys
- set.draw { get ':controller/:action/:id' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller/:action/:id' } }
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal %w(that this), extras.map(&:to_s).sort
end
def test_generate_extras_not_first
set.draw do
- get ':controller/:action/:id.:format'
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
+ end
end
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
@@ -902,8 +958,10 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_not_first
set.draw do
- get ':controller/:action/:id.:format'
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
+ end
end
assert_equal "/foo/bar/15?this=hello",
url_for(set, { :controller => "foo", :action => "bar", :id => 15, :this => "hello" })
@@ -911,8 +969,10 @@ class RouteSetTest < ActiveSupport::TestCase
def test_extra_keys_not_first
set.draw do
- get ':controller/:action/:id.:format'
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
+ end
end
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal %w(that this), extras.map(&:to_s).sort
@@ -1044,7 +1104,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_draw_default_route
set.draw do
- get '/:controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal 1, set.routes.size
@@ -1059,7 +1121,10 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_with_parameter_shell
set.draw do
get 'page/:id' => 'pages#show', :id => /\d+/
- get '/:controller(/:action(/:id))'
+
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action(/:id))'
+ end
end
assert_equal({:controller => 'pages', :action => 'index'}, request_path_params('/pages'))
@@ -1314,7 +1379,9 @@ class RouteSetTest < ActiveSupport::TestCase
@set = make_set false
set.draw do
- get ':controller/:id/:action'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:id/:action'
+ end
end
get URI('http://test.host/people/7/show')
@@ -1327,7 +1394,10 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
get 'about' => "welcome#about"
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:id/:action'
+ end
end
get URI('http://test.host/welcom/get/7')
@@ -1338,7 +1408,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_generate
- set.draw { get ':controller/:action/:id' }
+ set.draw { ActiveSupport::Deprecation.silence { get ':controller/:action/:id' } }
args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" }
assert_equal "/foo/bar/7?x=y", url_for(set, args)
@@ -1349,7 +1419,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_path_prefix
set.draw do
scope "my" do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
@@ -1360,7 +1432,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_blank_path_prefix
set.draw do
scope "" do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
@@ -1372,9 +1446,11 @@ class RouteSetTest < ActiveSupport::TestCase
@set = make_set false
set.draw do
- get "/connection/manage(/:action)" => 'connection/manage#index'
- get "/connection/connection" => "connection/connection#index"
- get '/connection' => 'connection#index', :as => 'family_connection'
+ ActiveSupport::Deprecation.silence do
+ get "/connection/manage(/:action)" => 'connection/manage#index'
+ get "/connection/connection" => "connection/connection#index"
+ get '/connection' => 'connection#index', :as => 'family_connection'
+ end
end
assert_equal({ :controller => 'connection/manage',
@@ -1392,7 +1468,9 @@ class RouteSetTest < ActiveSupport::TestCase
@set = make_set false
set.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
get URI('http://test.host/books/show/10')
@@ -1407,7 +1485,10 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
get 'show_weblog/:parameter' => 'weblog#show'
- get ':controller(/:action(/:id))'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
get URI('http://test.host/weblog/show/1')
@@ -1435,7 +1516,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_expiry_determination_should_consider_values_with_to_param
@set = make_set false
- set.draw { get 'projects/:project_id/:controller/:action' }
+ set.draw { ActiveSupport::Deprecation.silence { get 'projects/:project_id/:controller/:action' } }
get URI('http://test.host/projects/1/weblog/show')
@@ -1612,7 +1693,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_assign_route_options_with_anchor_chars
set.draw do
- get '/cars/:action/:person/:car/', :controller => 'cars'
+ ActiveSupport::Deprecation.silence do
+ get '/cars/:action/:person/:car/', :controller => 'cars'
+ end
end
assert_equal '/cars/buy/1/2', url_for(set, { :controller => 'cars', :action => 'buy', :person => '1', :car => '2' })
@@ -1622,7 +1705,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_segmentation_of_dot_path
set.draw do
- get '/books/:action.rss', :controller => 'books'
+ ActiveSupport::Deprecation.silence do
+ get '/books/:action.rss', :controller => 'books'
+ end
end
assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list' })
@@ -1632,7 +1717,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_segmentation_of_dynamic_dot_path
set.draw do
- get '/books(/:action(.:format))', :controller => 'books'
+ ActiveSupport::Deprecation.silence do
+ get '/books(/:action(.:format))', :controller => 'books'
+ end
end
assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list', :format => 'rss' })
@@ -1647,7 +1734,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_slashes_are_implied
- set.draw { get("/:controller(/:action(/:id))") }
+ set.draw { ActiveSupport::Deprecation.silence { get("/:controller(/:action(/:id))") } }
assert_equal '/content', url_for(set, { :controller => 'content', :action => 'index' })
assert_equal '/content/list', url_for(set, { :controller => 'content', :action => 'list' })
@@ -1738,7 +1825,9 @@ class RouteSetTest < ActiveSupport::TestCase
:constraints => { :page => /\d+/ },
:defaults => { :page => 1 }
- get ':controller/:action/:id'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
assert_equal '/ibocorp', url_for(set, { :controller => 'ibocorp', :action => "show", :page => 1 })
@@ -1761,7 +1850,11 @@ class RouteSetTest < ActiveSupport::TestCase
:day => nil, :month => nil
get "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/
- get "blog/:controller/:action(/:id)"
+
+ ActiveSupport::Deprecation.silence do
+ get "blog/:controller/:action(/:id)"
+ end
+
get "*anything", :controller => "blog", :action => "unknown_request"
end
@@ -1850,13 +1943,20 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
get 'news(.:format)' => "news#index"
- get 'comment/:id(/:action)' => "comments#show"
- get 'ws/:controller(/:action(/:id))', :ws => true
- get 'account(/:action)' => "account#subscription"
- get 'pages/:page_id/:controller(/:action(/:id))'
- get ':controller/ping', :action => 'ping'
+ ActiveSupport::Deprecation.silence do
+ get 'comment/:id(/:action)' => "comments#show"
+ get 'ws/:controller(/:action(/:id))', :ws => true
+ get 'account(/:action)' => "account#subscription"
+ get 'pages/:page_id/:controller(/:action(/:id))'
+ get ':controller/ping', :action => 'ping'
+ end
+
get 'こんにちは/世界', :controller => 'news', :action => 'index'
- match ':controller(/:action(/:id))(.:format)', :via => :all
+
+ ActiveSupport::Deprecation.silence do
+ match ':controller(/:action(/:id))(.:format)', :via => :all
+ end
+
root :to => "news#index"
}
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 0c1393548e..ebcdda6074 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -167,7 +167,9 @@ XML
@request.delete_header 'PATH_INFO'
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
end
@@ -672,7 +674,10 @@ XML
with_routing do |set|
set.draw do
get 'file/*path', to: 'test_case_test/test#test_params'
- get ':controller/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
get :test_params, params: { path: ['hello', 'world'] }
@@ -1008,7 +1013,9 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
@request.env['PATH_INFO'] = nil
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
end
@@ -1135,7 +1142,9 @@ class AnonymousControllerTest < ActionController::TestCase
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
end
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
index dfc2712e3e..a6ca5fc868 100644
--- a/actionpack/test/controller/url_for_integration_test.rb
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -52,12 +52,15 @@ module ActionPack
get 'news(.:format)' => "news#index"
- get 'comment/:id(/:action)' => "comments#show"
- get 'ws/:controller(/:action(/:id))', :ws => true
- get 'account(/:action)' => "account#subscription"
- get 'pages/:page_id/:controller(/:action(/:id))'
- get ':controller/ping', :action => 'ping'
- get ':controller(/:action(/:id))(.:format)'
+ ActiveSupport::Deprecation.silence {
+ get 'comment/:id(/:action)' => "comments#show"
+ get 'ws/:controller(/:action(/:id))', :ws => true
+ get 'account(/:action)' => "account#subscription"
+ get 'pages/:page_id/:controller(/:action(/:id))'
+ get ':controller/ping', :action => 'ping'
+ get ':controller(/:action(/:id))(.:format)'
+ }
+
root :to => "news#index"
}
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 67212fea38..b4d2088c0a 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -4,7 +4,13 @@ module AbstractController
module Testing
class UrlForTest < ActionController::TestCase
class W
- include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { get ':controller(/:action(/:id(.:format)))' } }.url_helpers
+ include ActionDispatch::Routing::RouteSet.new.tap { |r|
+ r.draw {
+ ActiveSupport::Deprecation.silence {
+ get ':controller(/:action(/:id(.:format)))'
+ }
+ }
+ }.url_helpers
end
def teardown
@@ -260,7 +266,7 @@ module AbstractController
w = Class.new {
config = ActionDispatch::Routing::RouteSet::Config.new '/subdir'
r = ActionDispatch::Routing::RouteSet.new(config)
- r.draw { get ':controller(/:action(/:id(.:format)))' }
+ r.draw { ActiveSupport::Deprecation.silence { get ':controller(/:action(/:id(.:format)))' } }
include r.url_helpers
}
add_host!(w)
@@ -315,7 +321,10 @@ module AbstractController
with_routing do |set|
set.draw do
get 'home/sweet/home/:user', :to => 'home#index', :as => :home
- get ':controller/:action/:id'
+
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/:id'
+ end
end
# We need to create a new class in order to install the new named route.
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index 5f2abc9606..bc0d215530 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -20,7 +20,9 @@ class UrlRewriterTests < ActionController::TestCase
@rewriter = Rewriter.new(@request) #.new(@request, @params)
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- get ':controller(/:action(/:id))'
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))'
+ end
end
end
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
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index 3655c7f570..a07138b55e 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -103,7 +103,9 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- post ':action', :to => ::JsonParamsParsingTest::TestController
+ ActiveSupport::Deprecation.silence do
+ post ':action', :to => ::JsonParamsParsingTest::TestController
+ end
end
yield
end
@@ -191,7 +193,9 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing(controller)
with_routing do |set|
set.draw do
- post ':action', :to => controller
+ ActiveSupport::Deprecation.silence do
+ post ':action', :to => controller
+ end
end
yield
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index b36fbd3c76..bab4413b2a 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -159,7 +159,9 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
test "does not raise EOFError on GET request with multipart content-type" do
with_routing do |set|
set.draw do
- get ':action', controller: 'multipart_params_parsing_test/test'
+ ActiveSupport::Deprecation.silence do
+ get ':action', controller: 'multipart_params_parsing_test/test'
+ end
end
headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" }
get "/parse", headers: headers
@@ -188,7 +190,9 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- post ':action', :controller => 'multipart_params_parsing_test/test'
+ ActiveSupport::Deprecation.silence do
+ post ':action', :controller => 'multipart_params_parsing_test/test'
+ end
end
yield
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index bc6716525e..f04022a544 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -144,7 +144,9 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
test "ambiguous query string returns a bad request" do
with_routing do |set|
set.draw do
- get ':action', :to => ::QueryStringParsingTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::QueryStringParsingTest::TestController
+ end
end
get "/parse", headers: { "QUERY_STRING" => "foo[]=bar&foo[4]=bar" }
@@ -156,7 +158,9 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
def assert_parses(expected, actual)
with_routing do |set|
set.draw do
- get ':action', :to => ::QueryStringParsingTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::QueryStringParsingTest::TestController
+ end
end
@app = self.class.build_app(set) do |middleware|
middleware.use(EarlyParse)
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 365edf849a..b9f8c52378 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -140,7 +140,9 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- post ':action', to: ::UrlEncodedParamsParsingTest::TestController
+ ActiveSupport::Deprecation.silence do
+ post ':action', to: ::UrlEncodedParamsParsingTest::TestController
+ end
end
yield
end
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index fd85cc6e9f..d8cffa425f 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -133,7 +133,9 @@ module ActionDispatch
def test_inspect_routes_shows_dynamic_action_route
output = draw do
- get 'api/:action' => 'api'
+ ActiveSupport::Deprecation.silence do
+ get 'api/:action' => 'api'
+ end
end
assert_equal [
@@ -144,7 +146,9 @@ module ActionDispatch
def test_inspect_routes_shows_controller_and_action_only_route
output = draw do
- get ':controller/:action'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action'
+ end
end
assert_equal [
@@ -155,7 +159,9 @@ module ActionDispatch
def test_inspect_routes_shows_controller_and_action_route_with_constraints
output = draw do
- get ':controller(/:action(/:id))', :id => /\d+/
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action(/:id))', :id => /\d+/
+ end
end
assert_equal [
@@ -335,7 +341,9 @@ module ActionDispatch
def test_regression_route_with_controller_regexp
output = draw do
- get ':controller(/:action)', controller: /api\/[^\/]+/, format: false
+ ActiveSupport::Deprecation.silence do
+ get ':controller(/:action)', controller: /api\/[^\/]+/, format: false
+ end
end
assert_equal ["Prefix Verb URI Pattern Controller#Action",
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 5ead9357ae..09830c0c46 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -116,7 +116,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_raise(ArgumentError) do
draw do
namespace :admin do
- get '/:controller(/:action(/:id(.:format)))'
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action(/:id(.:format)))'
+ end
end
end
end
@@ -125,7 +127,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_namespace_without_controller_segment
draw do
namespace :admin do
- get 'hello/:controllers/:action'
+ ActiveSupport::Deprecation.silence do
+ get 'hello/:controllers/:action'
+ end
end
end
get '/admin/hello/foo/new'
@@ -427,7 +431,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get 'global/hide_notice'
get 'global/export', :action => :export, :as => :export_request
get '/export/:id/:file', :action => :export, :as => :export_download, :constraints => { :file => /.*/ }
- get 'global/:action'
+
+ ActiveSupport::Deprecation.silence do
+ get 'global/:action'
+ end
end
end
@@ -450,7 +457,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_local
draw do
- get "/local/:action", :controller => "local"
+ ActiveSupport::Deprecation.silence do
+ get "/local/:action", :controller => "local"
+ end
end
get '/local/dashboard'
@@ -1506,7 +1515,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_not_matching_shorthand_with_dynamic_parameters
draw do
- get ':controller/:action/admin'
+ ActiveSupport::Deprecation.silence do
+ get ':controller/:action/admin'
+ end
end
get '/finances/overview/admin'
@@ -1542,7 +1553,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_scoped_controller_with_namespace_and_action
draw do
namespace :account do
- get ':action/callback', :action => /twitter|github/, :controller => "callbacks", :as => :callback
+ ActiveSupport::Deprecation.silence do
+ get ':action/callback', :action => /twitter|github/, :controller => "callbacks", :as => :callback
+ end
end
end
@@ -1837,7 +1850,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_url_generator_for_generic_route
draw do
- get "whatever/:controller(/:action(/:id))"
+ ActiveSupport::Deprecation.silence do
+ get "whatever/:controller(/:action(/:id))"
+ end
end
get '/whatever/foo/bar'
@@ -1849,7 +1864,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_url_generator_for_namespaced_generic_route
draw do
- get "whatever/:controller(/:action(/:id))", :id => /\d+/
+ ActiveSupport::Deprecation.silence do
+ get "whatever/:controller(/:action(/:id))", :id => /\d+/
+ end
end
get '/whatever/foo/bar/show'
@@ -3125,12 +3142,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
assert_raise(ArgumentError) do
- assert_deprecated do
- draw { controller("/feeds") { get '/feeds/:service', :to => :show } }
- end
- end
-
- assert_raise(ArgumentError) do
draw { resources :feeds, :controller => '/feeds' }
end
end
@@ -3599,6 +3610,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/?id=1', root_path(params)
end
+ def test_dynamic_controller_segments_are_deprecated
+ assert_deprecated do
+ draw do
+ get '/:controller', action: 'index'
+ end
+ end
+ end
+
+ def test_dynamic_action_segments_are_deprecated
+ assert_deprecated do
+ draw do
+ get '/pages/:action', controller: 'pages'
+ end
+ end
+ end
+
private
def draw(&block)
@@ -4122,7 +4149,11 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
app.draw do
ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
get '/foo' => ok, as: :foo
- get '/post(/:action(/:id))' => ok, as: :posts
+
+ ActiveSupport::Deprecation.silence do
+ get '/post(/:action(/:id))' => ok, as: :posts
+ end
+
get '/:foo/:foo_type/bars/:id' => ok, as: :bar
get '/projects/:id.:format' => ok, as: :project
get '/pages/:id' => ok, as: :page
@@ -4292,11 +4323,16 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest
test "invalid UTF-8 encoding returns a 400 Bad Request" do
with_routing do |set|
- set.draw do
- get "/bar/:id", :to => redirect("/foo/show/%{id}")
- get "/foo/show(/:id)", :to => "test_invalid_urls/foo#show"
- get "/foo(/:action(/:id))", :controller => "test_invalid_urls/foo"
- get "/:controller(/:action(/:id))"
+ ActiveSupport::Deprecation.silence do
+ set.draw do
+ get "/bar/:id", :to => redirect("/foo/show/%{id}")
+ get "/foo/show(/:id)", :to => "test_invalid_urls/foo#show"
+
+ ActiveSupport::Deprecation.silence do
+ get "/foo(/:action(/:id))", :controller => "test_invalid_urls/foo"
+ get "/:controller(/:action(/:id))"
+ end
+ end
end
get "/%E2%EF%BF%BD%A6"
@@ -4627,7 +4663,9 @@ class TestErrorsInController < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
Routes.draw do
- get '/:controller(/:action)'
+ ActiveSupport::Deprecation.silence do
+ get '/:controller(/:action)'
+ end
end
APP = build_app Routes
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index dbb996973d..769de1a1e0 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -164,7 +164,9 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- get ':action', :to => ::CacheStoreTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::CacheStoreTest::TestController
+ end
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index f07e215e3a..09cb1d925f 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -345,7 +345,9 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set(options = {})
with_routing do |set|
set.draw do
- get ':action', :to => ::CookieStoreTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::CookieStoreTest::TestController
+ end
end
options = { :key => SessionKey }.merge!(options)
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 3fed9bad4f..18cb227dad 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -187,7 +187,9 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- get ':action', :to => ::MemCacheStoreTest::TestController
+ ActiveSupport::Deprecation.silence do
+ get ':action', :to => ::MemCacheStoreTest::TestController
+ end
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index 15d51e5d6c..75caf56d32 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
module ActionDispatch
module Journey
class TestRouter < ActiveSupport::TestCase
- attr_reader :routes, :mapper
+ attr_reader :mapper, :routes, :route_set, :router
def setup
@app = Routing::RouteSet::Dispatcher.new({})
@@ -15,36 +15,36 @@ module ActionDispatch
end
def test_dashes
- mapper.get '/foo-bar-baz', to: 'foo#bar'
+ get '/foo-bar-baz', to: 'foo#bar'
env = rails_env 'PATH_INFO' => '/foo-bar-baz'
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_unicode
- mapper.get '/ほげ', to: 'foo#bar'
+ get '/ほげ', to: 'foo#bar'
#match the escaped version of /ほげ
env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92'
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_regexp_first_precedence
- mapper.get "/whois/:domain", :domain => /\w+\.[\w\.]+/, to: "foo#bar"
- mapper.get "/whois/:id(.:format)", to: "foo#baz"
+ get "/whois/:domain", :domain => /\w+\.[\w\.]+/, to: "foo#bar"
+ get "/whois/:id(.:format)", to: "foo#baz"
env = rails_env 'PATH_INFO' => '/whois/example.com'
list = []
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
list << r
end
assert_equal 2, list.length
@@ -55,7 +55,7 @@ module ActionDispatch
end
def test_required_parts_verified_are_anchored
- mapper.get "/foo/:id", :id => /\d/, anchor: false, to: "foo#bar"
+ get "/foo/:id", :id => /\d/, anchor: false, to: "foo#bar"
assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
@@ -63,7 +63,7 @@ module ActionDispatch
end
def test_required_parts_are_verified_when_building
- mapper.get "/foo/:id", :id => /\d+/, anchor: false, to: "foo#bar"
+ get "/foo/:id", :id => /\d+/, anchor: false, to: "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
assert_equal '/foo/10', path
@@ -74,7 +74,7 @@ module ActionDispatch
end
def test_only_required_parts_are_verified
- mapper.get "/foo(/:id)", :id => /\d/, :to => "foo#bar"
+ get "/foo(/:id)", :id => /\d/, :to => "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :id => '10' }, { })
assert_equal '/foo/10', path
@@ -88,8 +88,7 @@ module ActionDispatch
def test_knows_what_parts_are_missing_from_named_route
route_name = "gorby_thunderhorse"
- mapper = ActionDispatch::Routing::Mapper.new @route_set
- mapper.get "/foo/:id", :as => route_name, :id => /\d+/, :to => "foo#bar"
+ get "/foo/:id", :as => route_name, :id => /\d+/, :to => "foo#bar"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(route_name, { }, { })
@@ -109,19 +108,16 @@ module ActionDispatch
end
def test_X_Cascade
- mapper.get "/messages(.:format)", to: "foo#bar"
- resp = @router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' }))
+ get "/messages(.:format)", to: "foo#bar"
+ resp = router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' }))
assert_equal ['Not Found'], resp.last
assert_equal 'pass', resp[1]['X-Cascade']
assert_equal 404, resp.first
end
def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes
- route_set = Routing::RouteSet.new
- mapper = Routing::Mapper.new route_set
-
app = lambda { |env| [200, {}, ['success!']] }
- mapper.get '/weblog', :to => app
+ get '/weblog', :to => app
env = rack_env('SCRIPT_NAME' => '', 'PATH_INFO' => '/weblog')
resp = route_set.call env
@@ -130,38 +126,38 @@ module ActionDispatch
end
def test_defaults_merge_correctly
- mapper.get '/foo(/:id)', to: "foo#bar", id: nil
+ get '/foo(/:id)', to: "foo#bar", id: nil
env = rails_env 'PATH_INFO' => '/foo/10'
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal({:id => '10', :controller => "foo", :action => "bar"}, params)
end
env = rails_env 'PATH_INFO' => '/foo'
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal({:id => nil, :controller => "foo", :action => "bar"}, params)
end
end
def test_recognize_with_unbound_regexp
- mapper.get "/foo", anchor: false, to: "foo#bar"
+ get "/foo", anchor: false, to: "foo#bar"
env = rails_env 'PATH_INFO' => '/foo/bar'
- @router.recognize(env) { |*_| }
+ router.recognize(env) { |*_| }
assert_equal '/foo', env.env['SCRIPT_NAME']
assert_equal '/bar', env.env['PATH_INFO']
end
def test_bound_regexp_keeps_path_info
- mapper.get "/foo", to: "foo#bar"
+ get "/foo", to: "foo#bar"
env = rails_env 'PATH_INFO' => '/foo'
before = env.env['SCRIPT_NAME']
- @router.recognize(env) { |*_| }
+ router.recognize(env) { |*_| }
assert_equal before, env.env['SCRIPT_NAME']
assert_equal '/foo', env.env['PATH_INFO']
@@ -174,41 +170,41 @@ module ActionDispatch
"/messages/:id/edit(.:format)",
"/messages/:id(.:format)"
].each do |path|
- mapper.get path, to: "foo#bar"
+ get path, to: "foo#bar"
end
env = rails_env 'PATH_INFO' => '/messages/unknown/path'
yielded = false
- @router.recognize(env) do |*whatever|
+ router.recognize(env) do |*whatever|
yielded = true
end
assert_not yielded
end
def test_required_part_in_recall
- mapper.get "/messages/:a/:b", to: "foo#bar"
+ get "/messages/:a/:b", to: "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar", :a => 'a' }, { :b => 'b' })
assert_equal "/messages/a/b", path
end
def test_splat_in_recall
- mapper.get "/*path", to: "foo#bar"
+ get "/*path", to: "foo#bar"
path, _ = @formatter.generate(nil, { :controller => "foo", :action => "bar" }, { :path => 'b' })
assert_equal "/b", path
end
def test_recall_should_be_used_when_scoring
- mapper.get "/messages/:action(/:id(.:format))", to: 'foo#bar'
- mapper.get "/messages/:id(.:format)", to: 'bar#baz'
+ get "/messages/:action(/:id(.:format))", to: 'foo#bar'
+ get "/messages/:id(.:format)", to: 'bar#baz'
path, _ = @formatter.generate(nil, { :controller => "foo", :id => 10 }, { :action => 'index' })
assert_equal "/messages/index/10", path
end
def test_nil_path_parts_are_ignored
- mapper.get "/:controller(/:action(.:format))", to: "tasks#lol"
+ get "/:controller(/:action(.:format))", to: "tasks#lol"
params = { :controller => "tasks", :format => nil }
extras = { :action => 'lol' }
@@ -220,14 +216,14 @@ module ActionDispatch
def test_generate_slash
params = [ [:controller, "tasks"],
[:action, "show"] ]
- mapper.get "/", Hash[params]
+ get "/", Hash[params]
path, _ = @formatter.generate(nil, Hash[params], {})
assert_equal '/', path
end
def test_generate_calls_param_proc
- mapper.get '/:controller(/:action)', to: "foo#bar"
+ get '/:controller(/:action)', to: "foo#bar"
parameterized = []
params = [ [:controller, "tasks"],
@@ -243,7 +239,7 @@ module ActionDispatch
end
def test_generate_id
- mapper.get '/:controller(/:action)', to: 'foo#bar'
+ get '/:controller(/:action)', to: 'foo#bar'
path, params = @formatter.generate(
nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
@@ -252,7 +248,7 @@ module ActionDispatch
end
def test_generate_escapes
- mapper.get '/:controller(/:action)', to: "foo#bar"
+ get '/:controller(/:action)', to: "foo#bar"
path, _ = @formatter.generate(nil,
{ :controller => "tasks",
@@ -262,7 +258,7 @@ module ActionDispatch
end
def test_generate_escapes_with_namespaced_controller
- mapper.get '/:controller(/:action)', to: "foo#bar"
+ get '/:controller(/:action)', to: "foo#bar"
path, _ = @formatter.generate(
nil, { :controller => "admin/tasks",
@@ -272,7 +268,7 @@ module ActionDispatch
end
def test_generate_extra_params
- mapper.get '/:controller(/:action)', to: "foo#bar"
+ get '/:controller(/:action)', to: "foo#bar"
path, params = @formatter.generate(
nil, { :id => 1,
@@ -285,7 +281,7 @@ module ActionDispatch
end
def test_generate_missing_keys_no_matches_different_format_keys
- mapper.get '/:controller/:action/:name', to: "foo#bar"
+ get '/:controller/:action/:name', to: "foo#bar"
primarty_parameters = {
:id => 1,
:controller => "tasks",
@@ -311,7 +307,7 @@ module ActionDispatch
end
def test_generate_uses_recall_if_needed
- mapper.get '/:controller(/:action(/:id))', to: "foo#bar"
+ get '/:controller(/:action(/:id))', to: "foo#bar"
path, params = @formatter.generate(
nil,
@@ -322,7 +318,7 @@ module ActionDispatch
end
def test_generate_with_name
- mapper.get '/:controller(/:action)', to: 'foo#bar', as: 'tasks'
+ get '/:controller(/:action)', to: 'foo#bar', as: 'tasks'
path, params = @formatter.generate(
"tasks",
@@ -338,13 +334,13 @@ module ActionDispatch
'/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" },
}.each do |request_path, expected|
define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
- mapper.get "/:controller(/:action(/:id))", to: 'foo#bar'
+ get "/:controller(/:action(/:id))", to: 'foo#bar'
route = @routes.first
env = rails_env 'PATH_INFO' => request_path
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal route, r
assert_equal({ :action => "bar" }.merge(expected), params)
called = true
@@ -359,13 +355,13 @@ module ActionDispatch
:splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }]
}.each do |name, (request_path, expected)|
define_method("test_recognize_#{name}") do
- mapper.get '/:segment/*splat', to: 'foo#bar'
+ get '/:segment/*splat', to: 'foo#bar'
env = rails_env 'PATH_INFO' => request_path
called = false
route = @routes.first
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected.merge(:controller=>"foo", :action=>"bar"), params)
called = true
@@ -376,7 +372,7 @@ module ActionDispatch
end
def test_namespaced_controller
- mapper.get "/:controller(/:action(/:id))", { :controller => /.+?/ }
+ get "/:controller(/:action(/:id))", { :controller => /.+?/ }
route = @routes.first
env = rails_env 'PATH_INFO' => '/admin/users/show/10'
@@ -387,7 +383,7 @@ module ActionDispatch
:id => '10'
}
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
@@ -396,13 +392,13 @@ module ActionDispatch
end
def test_recognize_literal
- mapper.get "/books(/:action(.:format))", controller: "books"
+ get "/books(/:action(.:format))", controller: "books"
route = @routes.first
env = rails_env 'PATH_INFO' => '/books/list.rss'
expected = { :controller => 'books', :action => 'list', :format => 'rss' }
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
@@ -412,7 +408,7 @@ module ActionDispatch
end
def test_recognize_head_route
- mapper.match "/books(/:action(.:format))", via: 'head', to: 'foo#bar'
+ match "/books(/:action(.:format))", via: 'head', to: 'foo#bar'
env = rails_env(
'PATH_INFO' => '/books/list.rss',
@@ -420,7 +416,7 @@ module ActionDispatch
)
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -428,13 +424,13 @@ module ActionDispatch
end
def test_recognize_head_request_as_get_route
- mapper.get "/books(/:action(.:format))", to: 'foo#bar'
+ get "/books(/:action(.:format))", to: 'foo#bar'
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "HEAD"
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -442,13 +438,13 @@ module ActionDispatch
end
def test_recognize_cares_about_get_verbs
- mapper.match "/books(/:action(.:format))", to: "foo#bar", via: :get
+ match "/books(/:action(.:format))", to: "foo#bar", via: :get
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "POST"
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -456,13 +452,13 @@ module ActionDispatch
end
def test_recognize_cares_about_post_verbs
- mapper.match "/books(/:action(.:format))", to: "foo#bar", via: :post
+ match "/books(/:action(.:format))", to: "foo#bar", via: :post
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => "POST"
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -470,14 +466,14 @@ module ActionDispatch
end
def test_multi_verb_recognition
- mapper.match "/books(/:action(.:format))", to: "foo#bar", via: [:post, :get]
+ match "/books(/:action(.:format))", to: "foo#bar", via: [:post, :get]
%w( POST GET ).each do |verb|
env = rails_env 'PATH_INFO' => '/books/list.rss',
"REQUEST_METHOD" => verb
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -488,7 +484,7 @@ module ActionDispatch
"REQUEST_METHOD" => 'PUT'
called = false
- @router.recognize(env) do |r, params|
+ router.recognize(env) do |r, params|
called = true
end
@@ -497,6 +493,18 @@ module ActionDispatch
private
+ def get *args
+ ActiveSupport::Deprecation.silence do
+ mapper.get(*args)
+ end
+ end
+
+ def match *args
+ ActiveSupport::Deprecation.silence do
+ mapper.match(*args)
+ end
+ end
+
def rails_env env, klass = ActionDispatch::Request
klass.new(rack_env(env))
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index b6de0b03bf..f33acc2103 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -245,8 +245,12 @@ module ActionView
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
query.gsub!(/:action/, partial)
- details.each do |ext, variants|
- query.gsub!(/:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
+ details.each do |ext, candidates|
+ if ext == :variants && candidates == :any
+ query.gsub!(/:#{ext}/, "*")
+ else
+ query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
+ end
end
File.expand_path(query, @path)
diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb
index 575eb9bd28..1a091bd692 100644
--- a/actionview/test/template/resolver_patterns_test.rb
+++ b/actionview/test/template/resolver_patterns_test.rb
@@ -3,17 +3,17 @@ require 'abstract_unit'
class ResolverPatternsTest < ActiveSupport::TestCase
def setup
path = File.expand_path("../../fixtures/", __FILE__)
- pattern = ":prefix/{:formats/,}:action{.:formats,}{.:handlers,}"
+ pattern = ":prefix/{:formats/,}:action{.:formats,}{+:variants,}{.:handlers,}"
@resolver = ActionView::FileSystemResolver.new(path, pattern)
end
def test_should_return_empty_list_for_unknown_path
- templates = @resolver.find_all("unknown", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ templates = @resolver.find_all("unknown", "custom_pattern", false, {locale: [], formats: [:html], variants: [], handlers: [:erb]})
assert_equal [], templates, "expected an empty list of templates"
end
def test_should_return_template_for_declared_path
- templates = @resolver.find_all("path", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ templates = @resolver.find_all("path", "custom_pattern", false, {locale: [], formats: [:html], variants: [], handlers: [:erb]})
assert_equal 1, templates.size, "expected one template"
assert_equal "Hello custom patterns!", templates.first.source
assert_equal "custom_pattern/path", templates.first.virtual_path
@@ -21,11 +21,22 @@ class ResolverPatternsTest < ActiveSupport::TestCase
end
def test_should_return_all_templates_when_ambiguous_pattern
- templates = @resolver.find_all("another", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
+ templates = @resolver.find_all("another", "custom_pattern", false, {locale: [], formats: [:html], variants: [], handlers: [:erb]})
assert_equal 2, templates.size, "expected two templates"
assert_equal "Another template!", templates[0].source
assert_equal "custom_pattern/another", templates[0].virtual_path
assert_equal "Hello custom patterns!", templates[1].source
assert_equal "custom_pattern/another", templates[1].virtual_path
end
+
+ def test_should_return_all_variants_for_any
+ templates = @resolver.find_all("hello_world", "test", false, {locale: [], formats: [:html, :text], variants: :any, handlers: [:erb]})
+ assert_equal 3, templates.size, "expected three templates"
+ assert_equal "Hello phone!", templates[0].source
+ assert_equal "test/hello_world", templates[0].virtual_path
+ assert_equal "Hello texty phone!", templates[1].source
+ assert_equal "test/hello_world", templates[1].virtual_path
+ assert_equal "Hello world!", templates[2].source
+ assert_equal "test/hello_world", templates[2].virtual_path
+ end
end
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 913164fbc5..efe46ce5ab 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,15 @@
+* Enable class reloading prior to job dispatch, and ensure Active Record
+ connections are returned to the pool when jobs are run in separate threads.
+
+ *Matthew Draper*
+
+* Tune the async adapter for low-footprint dev/test usage. Use a single
+ thread pool for all queues and limit to 0 to #CPU total threads, down from
+ 2 to 10*#CPU per queue.
+
+ *Jeremy Daer*
+
+
## Rails 5.0.0.beta3 (February 24, 2016) ##
* Change the default adapter from inline to async. It's a better default as tests will then not mistakenly
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index f7e05f7cd3..4b9065cf50 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -32,7 +32,6 @@ module ActiveJob
autoload :Base
autoload :QueueAdapters
autoload :ConfiguredJob
- autoload :AsyncJob
autoload :TestCase
autoload :TestHelper
end
diff --git a/activejob/lib/active_job/async_job.rb b/activejob/lib/active_job/async_job.rb
deleted file mode 100644
index 417ace21d2..0000000000
--- a/activejob/lib/active_job/async_job.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'concurrent/map'
-require 'concurrent/scheduled_task'
-require 'concurrent/executor/thread_pool_executor'
-require 'concurrent/utility/processor_counter'
-
-module ActiveJob
- # == Active Job Async Job
- #
- # When enqueuing jobs with Async Job each job will be executed asynchronously
- # on a +concurrent-ruby+ thread pool. All job data is retained in memory.
- # Because job data is not saved to a persistent datastore there is no
- # additional infrastructure needed and jobs process quickly. The lack of
- # persistence, however, means that all unprocessed jobs will be lost on
- # application restart. Therefore in-memory queue adapters are unsuitable for
- # most production environments but are excellent for development and testing.
- #
- # Read more about Concurrent Ruby {here}[https://github.com/ruby-concurrency/concurrent-ruby].
- #
- # To use Async Job set the queue_adapter config to +:async+.
- #
- # Rails.application.config.active_job.queue_adapter = :async
- #
- # Async Job supports job queues specified with +queue_as+. Queues are created
- # automatically as needed and each has its own thread pool.
- class AsyncJob
-
- DEFAULT_EXECUTOR_OPTIONS = {
- min_threads: [2, Concurrent.processor_count].max,
- max_threads: Concurrent.processor_count * 10,
- auto_terminate: true,
- idletime: 60, # 1 minute
- max_queue: 0, # unlimited
- fallback_policy: :caller_runs # shouldn't matter -- 0 max queue
- }.freeze
-
- QUEUES = Concurrent::Map.new do |hash, queue_name| #:nodoc:
- hash.compute_if_absent(queue_name) { ActiveJob::AsyncJob.create_thread_pool }
- end
-
- class << self
- # Forces jobs to process immediately when testing the Active Job gem.
- # This should only be called from within unit tests.
- def perform_immediately! #:nodoc:
- @perform_immediately = true
- end
-
- # Allows jobs to run asynchronously when testing the Active Job gem.
- # This should only be called from within unit tests.
- def perform_asynchronously! #:nodoc:
- @perform_immediately = false
- end
-
- def create_thread_pool #:nodoc:
- if @perform_immediately
- Concurrent::ImmediateExecutor.new
- else
- Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS)
- end
- end
-
- def enqueue(job_data, queue: 'default') #:nodoc:
- QUEUES[queue].post(job_data) { |job| ActiveJob::Base.execute(job) }
- end
-
- def enqueue_at(job_data, timestamp, queue: 'default') #:nodoc:
- delay = timestamp - Time.current.to_f
- if delay > 0
- Concurrent::ScheduledTask.execute(delay, args: [job_data], executor: QUEUES[queue]) do |job|
- ActiveJob::Base.execute(job)
- end
- else
- enqueue(job_data, queue: queue)
- end
- end
- end
- end
-end
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
index 2b6149e84e..a6591c6a05 100644
--- a/activejob/lib/active_job/callbacks.rb
+++ b/activejob/lib/active_job/callbacks.rb
@@ -17,6 +17,11 @@ module ActiveJob
extend ActiveSupport::Concern
include ActiveSupport::Callbacks
+ class << self
+ include ActiveSupport::Callbacks
+ define_callbacks :execute
+ end
+
included do
define_callbacks :perform
define_callbacks :enqueue
diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb
index 79d232da4a..7c4151fc90 100644
--- a/activejob/lib/active_job/execution.rb
+++ b/activejob/lib/active_job/execution.rb
@@ -17,8 +17,10 @@ module ActiveJob
end
def execute(job_data) #:nodoc:
- job = deserialize(job_data)
- job.perform_now
+ ActiveJob::Callbacks.run_callbacks(:execute) do
+ job = deserialize(job_data)
+ job.perform_now
+ end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/async_adapter.rb b/activejob/lib/active_job/queue_adapters/async_adapter.rb
index 3d3c749883..922bc4afce 100644
--- a/activejob/lib/active_job/queue_adapters/async_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/async_adapter.rb
@@ -1,22 +1,113 @@
-require 'active_job/async_job'
+require 'securerandom'
+require 'concurrent/scheduled_task'
+require 'concurrent/executor/thread_pool_executor'
+require 'concurrent/utility/processor_counter'
module ActiveJob
module QueueAdapters
# == Active Job Async adapter
#
- # When enqueuing jobs with the Async adapter the job will be executed
- # asynchronously using {AsyncJob}[http://api.rubyonrails.org/classes/ActiveJob/AsyncJob.html].
+ # The Async adapter runs jobs with an in-process thread pool.
#
- # To use +AsyncJob+ set the queue_adapter config to +:async+.
+ # This is the default queue adapter. It's well-suited for dev/test since
+ # it doesn't need an external infrastructure, but it's a poor fit for
+ # production since it drops pending jobs on restart.
#
- # Rails.application.config.active_job.queue_adapter = :async
+ # To use this adapter, set queue adapter to +:async+:
+ #
+ # config.active_job.queue_adapter = :async
+ #
+ # To configure the adapter's thread pool, instantiate the adapter and
+ # pass your own config:
+ #
+ # config.active_job.queue_adapter = ActiveJob::QueueAdapters::AsyncAdapter.new \
+ # min_threads: 1,
+ # max_threads: 2 * Concurrent.processor_count,
+ # idletime: 600.seconds
+ #
+ # The adapter uses a {Concurrent Ruby}[https://github.com/ruby-concurrency/concurrent-ruby] thread pool to schedule and execute
+ # jobs. Since jobs share a single thread pool, long-running jobs will block
+ # short-lived jobs. Fine for dev/test; bad for production.
class AsyncAdapter
+ # See {Concurrent::ThreadPoolExecutor}[http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
+ def initialize(**executor_options)
+ @scheduler = Scheduler.new(**executor_options)
+ end
+
def enqueue(job) #:nodoc:
- ActiveJob::AsyncJob.enqueue(job.serialize, queue: job.queue_name)
+ @scheduler.enqueue JobWrapper.new(job), queue_name: job.queue_name
end
def enqueue_at(job, timestamp) #:nodoc:
- ActiveJob::AsyncJob.enqueue_at(job.serialize, timestamp, queue: job.queue_name)
+ @scheduler.enqueue_at JobWrapper.new(job), timestamp, queue_name: job.queue_name
+ end
+
+ # Gracefully stop processing jobs. Finishes in-progress work and handles
+ # any new jobs following the executor's fallback policy (`caller_runs`).
+ # Waits for termination by default. Pass `wait: false` to continue.
+ def shutdown(wait: true) #:nodoc:
+ @scheduler.shutdown wait: wait
+ end
+
+ # Used for our test suite.
+ def immediate=(immediate) #:nodoc:
+ @scheduler.immediate = immediate
+ end
+
+ # Note that we don't actually need to serialize the jobs since we're
+ # performing them in-process, but we do so anyway for parity with other
+ # adapters and deployment environments. Otherwise, serialization bugs
+ # may creep in undetected.
+ class JobWrapper #:nodoc:
+ def initialize(job)
+ job.provider_job_id = SecureRandom.uuid
+ @job_data = job.serialize
+ end
+
+ def perform
+ Base.execute @job_data
+ end
+ end
+
+ class Scheduler #:nodoc:
+ DEFAULT_EXECUTOR_OPTIONS = {
+ min_threads: 0,
+ max_threads: Concurrent.processor_count,
+ auto_terminate: true,
+ idletime: 60, # 1 minute
+ max_queue: 0, # unlimited
+ fallback_policy: :caller_runs # shouldn't matter -- 0 max queue
+ }.freeze
+
+ attr_accessor :immediate
+
+ def initialize(**options)
+ self.immediate = false
+ @immediate_executor = Concurrent::ImmediateExecutor.new
+ @async_executor = Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS.merge(options))
+ end
+
+ def enqueue(job, queue_name:)
+ executor.post(job, &:perform)
+ end
+
+ def enqueue_at(job, timestamp, queue_name:)
+ delay = timestamp - Time.current.to_f
+ if delay > 0
+ Concurrent::ScheduledTask.execute(delay, args: [job], executor: executor, &:perform)
+ else
+ enqueue(job, queue_name: queue_name)
+ end
+ end
+
+ def shutdown(wait: true)
+ @async_executor.shutdown
+ @async_executor.wait_for_termination if wait
+ end
+
+ def executor
+ immediate ? @immediate_executor : @async_executor
+ end
end
end
end
diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb
index b249ec09dd..a47caa4a7e 100644
--- a/activejob/lib/active_job/railtie.rb
+++ b/activejob/lib/active_job/railtie.rb
@@ -19,5 +19,14 @@ module ActiveJob
end
end
+ initializer "active_job.set_reloader_hook" do |app|
+ ActiveSupport.on_load(:active_job) do
+ ActiveJob::Callbacks.singleton_class.set_callback(:execute, :around, prepend: true) do |_, inner|
+ app.reloader.wrap do
+ inner.call
+ end
+ end
+ end
+ end
end
end
diff --git a/activejob/test/adapters/async.rb b/activejob/test/adapters/async.rb
index 5fcfb89566..08eb9658cd 100644
--- a/activejob/test/adapters/async.rb
+++ b/activejob/test/adapters/async.rb
@@ -1,4 +1,2 @@
-require 'active_job/async_job'
-
ActiveJob::Base.queue_adapter = :async
-ActiveJob::AsyncJob.perform_immediately!
+ActiveJob::Base.queue_adapter.immediate = true
diff --git a/activejob/test/cases/async_job_test.rb b/activejob/test/cases/async_job_test.rb
deleted file mode 100644
index 2642cfc608..0000000000
--- a/activejob/test/cases/async_job_test.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'helper'
-require 'jobs/hello_job'
-require 'jobs/queue_as_job'
-
-class AsyncJobTest < ActiveSupport::TestCase
- def using_async_adapter?
- ActiveJob::Base.queue_adapter.is_a? ActiveJob::QueueAdapters::AsyncAdapter
- end
-
- setup do
- ActiveJob::AsyncJob.perform_asynchronously!
- end
-
- teardown do
- ActiveJob::AsyncJob::QUEUES.clear
- ActiveJob::AsyncJob.perform_immediately!
- end
-
- test "#create_thread_pool returns a thread_pool" do
- thread_pool = ActiveJob::AsyncJob.create_thread_pool
- assert thread_pool.is_a? Concurrent::ExecutorService
- assert_not thread_pool.is_a? Concurrent::ImmediateExecutor
- end
-
- test "#create_thread_pool returns an ImmediateExecutor after #perform_immediately! is called" do
- ActiveJob::AsyncJob.perform_immediately!
- thread_pool = ActiveJob::AsyncJob.create_thread_pool
- assert thread_pool.is_a? Concurrent::ImmediateExecutor
- end
-
- test "enqueuing without specifying a queue uses the default queue" do
- skip unless using_async_adapter?
- HelloJob.perform_later
- assert ActiveJob::AsyncJob::QUEUES.key? 'default'
- end
-
- test "enqueuing to a queue that does not exist creates the queue" do
- skip unless using_async_adapter?
- QueueAsJob.perform_later
- assert ActiveJob::AsyncJob::QUEUES.key? QueueAsJob::MY_QUEUE.to_s
- end
-end
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index d8425c9706..40f27500a5 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -57,13 +57,13 @@ class QueuingTest < ActiveSupport::TestCase
end
test 'should supply a provider_job_id when available for immediate jobs' do
- skip unless adapter_is?(:delayed_job, :sidekiq, :qu, :que, :queue_classic)
+ skip unless adapter_is?(:async, :delayed_job, :sidekiq, :qu, :que, :queue_classic)
test_job = TestJob.perform_later @id
assert test_job.provider_job_id, 'Provider job id should be set by provider'
end
test 'should supply a provider_job_id when available for delayed jobs' do
- skip unless adapter_is?(:delayed_job, :sidekiq, :que, :queue_classic)
+ skip unless adapter_is?(:async, :delayed_job, :sidekiq, :que, :queue_classic)
delayed_test_job = TestJob.set(wait: 1.minute).perform_later @id
assert delayed_test_job.provider_job_id, 'Provider job id should by set for delayed jobs by provider'
end
diff --git a/activejob/test/support/integration/adapters/async.rb b/activejob/test/support/integration/adapters/async.rb
index 42beb12b1f..44ab98437a 100644
--- a/activejob/test/support/integration/adapters/async.rb
+++ b/activejob/test/support/integration/adapters/async.rb
@@ -1,9 +1,10 @@
module AsyncJobsManager
def setup
ActiveJob::Base.queue_adapter = :async
+ ActiveJob::Base.queue_adapter.immediate = false
end
def clear_jobs
- ActiveJob::AsyncJob::QUEUES.clear
+ ActiveJob::Base.queue_adapter.shutdown
end
end
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 262ca72327..a0ef38b0b2 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -2,7 +2,7 @@ if ENV['AJ_ADAPTER'] == 'delayed_job'
generate "delayed_job:active_record", "--quiet"
end
-rake("db:migrate")
+rails_command("db:migrate")
initializer 'activejob.rb', <<-CODE
require "#{File.expand_path("../jobs_manager.rb", __FILE__)}"
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 39fe217777..7438ac8a9d 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,24 @@
+* Added a configuration option to have active record raise an ArgumentError
+ if the order or limit is ignored in a batch query, rather than logging a
+ warning message.
+
+ *Scott Ringwelski*
+
+* Honour the order of the joining model in a `has_many :through` association when eager loading.
+
+ Example:
+
+ The below will now follow the order of `by_lines` when eager loading `authors`.
+
+ class Article < ActiveRecord::Base
+ has_many :by_lines, -> { order(:position) }
+ has_many :authors, through: :by_lines
+ end
+
+ Fixes #17864.
+
+ *Yasyf Mohamedali*, *Joel Turkel*
+
* Ensure that the Suppressor runs before validations.
This moves the suppressor up to be run before validations rather than after
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index ab3846ae65..baa497dc98 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -137,7 +137,6 @@ module ActiveRecord
eager_autoload do
autoload :AbstractAdapter
- autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool"
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 6c83058202..b0203909ce 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -38,12 +38,7 @@ module ActiveRecord
}
end
- record_offset = {}
- @preloaded_records.each_with_index do |record,i|
- record_offset[record] = i
- end
-
- through_records.each_with_object({}) { |(lhs,center),records_by_owner|
+ through_records.each_with_object({}) do |(lhs,center), records_by_owner|
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
@@ -53,13 +48,25 @@ module ActiveRecord
target_records_from_association(association)
}.compact
- rhs_records.sort_by { |rhs| record_offset[rhs] }
+ # Respect the order on `reflection_scope` if it exists, else use the natural order.
+ if reflection_scope.values[:order].present?
+ @id_map ||= id_to_index_map @preloaded_records
+ rhs_records.sort_by { |rhs| @id_map[rhs] }
+ else
+ rhs_records
+ end
end
- }
+ end
end
private
+ def id_to_index_map(ids)
+ id_map = {}
+ ids.each_with_index { |id, index| id_map[id] = index }
+ id_map
+ end
+
def reset_association(owners, association_name)
should_reset = (through_scope != through_reflection.klass.unscoped) ||
(reflection.options[:source_type] && through_reflection.collection?)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index ccd2899489..e389d818fd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -951,24 +951,5 @@ module ActiveRecord
owner_to_pool && owner_to_pool[owner.name]
end
end
-
- class ConnectionManagement
- def initialize(app)
- @app = app
- end
-
- def call(env)
- testing = env['rack.test']
-
- status, headers, body = @app.call(env)
- proxy = ::Rack::BodyProxy.new(body) do
- ActiveRecord::Base.clear_active_connections! unless testing
- end
- [status, headers, proxy]
- rescue Exception
- ActiveRecord::Base.clear_active_connections! unless testing
- raise
- end
- end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 33dbab41cb..0bdfd4f900 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -65,7 +65,7 @@ module ActiveRecord
if @query_cache_enabled && !locked?(arel)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- cache_sql(sql, binds) { super(sql, name, binds, preparable: visitor.preparable) }
+ cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 6ecdab6eb0..ca795cb1ad 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -188,7 +188,10 @@ module ActiveRecord
transaction = begin_transaction options
yield
rescue Exception => error
- rollback_transaction if transaction
+ if transaction
+ rollback_transaction
+ after_failure_actions(transaction, error)
+ end
raise
ensure
unless error
@@ -214,7 +217,16 @@ module ActiveRecord
end
private
+
NULL_TRANSACTION = NullTransaction.new
+
+ # Deallocate invalidated prepared statements outside of the transaction
+ def after_failure_actions(transaction, error)
+ return unless transaction.is_a?(RealTransaction)
+ return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired)
+ @connection.clear_cache!
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 5ef434734a..fcc1ef9d5f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -27,7 +27,6 @@ module ActiveRecord
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
autoload :ConnectionHandler
- autoload :ConnectionManagement
end
autoload_under 'abstract' do
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 57d8867bb4..ed8cfae657 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -131,7 +131,7 @@ module ActiveRecord
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
result = execute(sql, name)
@connection.next_result while @connection.more_results?
- ActiveRecord::Result.new(result.fields, result.to_a)
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
end
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index beaeef3c78..61c9628de3 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -598,25 +598,41 @@ module ActiveRecord
@connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
- pgerror = e.cause
+ raise unless is_cached_plan_failure?(e)
- # Get the PG code for the failure. Annoyingly, the code for
- # prepared statements whose return value may have changed is
- # FEATURE_NOT_SUPPORTED. Check here for more details:
- # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
- begin
- code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
- rescue
- raise e
- end
- if FEATURE_NOT_SUPPORTED == code
+ # Nothing we can do if we are in a transaction because all commands
+ # will raise InFailedSQLTransaction
+ if in_transaction?
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
+ else
+ # outside of transactions we can simply flush this query and retry
@statements.delete sql_key(sql)
retry
- else
- raise e
end
end
+ # Annoyingly, the code for prepared statements whose return value may
+ # have changed is FEATURE_NOT_SUPPORTED.
+ #
+ # This covers various different error types so we need to do additional
+ # work to classify the exception definitively as a
+ # ActiveRecord::PreparedStatementCacheExpired
+ #
+ # Check here for more details:
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
+ CACHED_PLAN_HEURISTIC = 'cached plan must not change result type'.freeze
+ def is_cached_plan_failure?(e)
+ pgerror = e.cause
+ code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
+ rescue
+ false
+ end
+
+ def in_transaction?
+ open_transactions > 0
+ end
+
# Returns the statement identifier for the client side cache
# of statements
def sql_key(sql)
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 24fd0aaecf..86ec8000fb 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -72,6 +72,14 @@ module ActiveRecord
##
# :singleton-method:
+ # Specifies if an error should be raised on query limit or order being
+ # ignored when doing batch queries. Useful in applications where the
+ # limit or scope being ignored is error-worthy, rather than a warning.
+ mattr_accessor :error_on_ignored_order_or_limit, instance_writer: false
+ self.error_on_ignored_order_or_limit = false
+
+ ##
+ # :singleton-method:
# Specify whether or not to use timestamps for migration versions
mattr_accessor :timestamped_migrations, instance_writer: false
self.timestamped_migrations = true
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 87f32c042c..2ec9bf3d67 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -139,6 +139,11 @@ module ActiveRecord
class NoDatabaseError < StatementInvalid
end
+ # Raised when Postgres returns 'cached plan must not change result type' and
+ # we cannot retry gracefully (e.g. inside a transaction)
+ class PreparedStatementCacheExpired < StatementInvalid
+ end
+
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
# the page before the other.
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index b63caa4473..efa2a4df02 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -67,7 +67,7 @@ module ActiveRecord
case sql
when /\A\s*rollback/mi
RED
- when /\s*.*?select .*for update/mi, /\A\s*lock/mi
+ when /select .*for update/mi, /\A\s*lock/mi
WHITE
when /\A\s*select/i
BLUE
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index dcb2bd3d84..f451ed1764 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -23,34 +23,26 @@ module ActiveRecord
end
end
- def initialize(app)
- @app = app
- end
-
- def call(env)
- connection = ActiveRecord::Base.connection
- enabled = connection.query_cache_enabled
- connection_id = ActiveRecord::Base.connection_id
- connection.enable_query_cache!
-
- response = @app.call(env)
- response[2] = Rack::BodyProxy.new(response[2]) do
- restore_query_cache_settings(connection_id, enabled)
+ def self.install_executor_hooks(executor = ActiveSupport::Executor)
+ executor.to_run do
+ connection = ActiveRecord::Base.connection
+ enabled = connection.query_cache_enabled
+ connection_id = ActiveRecord::Base.connection_id
+ connection.enable_query_cache!
+
+ @restore_query_cache_settings = lambda do
+ ActiveRecord::Base.connection_id = connection_id
+ ActiveRecord::Base.connection.clear_query_cache
+ ActiveRecord::Base.connection.disable_query_cache! unless enabled
+ end
end
- response
- rescue Exception => e
- restore_query_cache_settings(connection_id, enabled)
- raise e
- end
-
- private
+ executor.to_complete do
+ @restore_query_cache_settings.call if defined?(@restore_query_cache_settings)
- def restore_query_cache_settings(connection_id, enabled)
- ActiveRecord::Base.connection_id = connection_id
- ActiveRecord::Base.connection.clear_query_cache
- ActiveRecord::Base.connection.disable_query_cache! unless enabled
+ # FIXME: This should be skipped when env['rack.test']
+ ActiveRecord::Base.clear_active_connections!
+ end
end
-
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index f4200e96b7..4c074c93ed 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -16,12 +16,6 @@ module ActiveRecord
config.app_generators.orm :active_record, :migration => true,
:timestamps => true
- config.app_middleware.insert_after ::ActionDispatch::Callbacks,
- ActiveRecord::QueryCache
-
- config.app_middleware.insert_after ::ActionDispatch::Callbacks,
- ActiveRecord::ConnectionAdapters::ConnectionManagement
-
config.action_dispatch.rescue_responses.merge!(
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
@@ -153,11 +147,9 @@ end_warning
end
end
- initializer "active_record.set_reloader_hooks" do |app|
- hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
-
+ initializer "active_record.set_reloader_hooks" do
ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.send(hook) do
+ ActiveSupport::Reloader.before_class_unload do
if ActiveRecord::Base.connected?
ActiveRecord::Base.clear_cache!
ActiveRecord::Base.clear_reloadable_connections!
@@ -166,6 +158,12 @@ end_warning
end
end
+ initializer "active_record.set_executor_hooks" do
+ ActiveSupport.on_load(:active_record) do
+ ActiveRecord::QueryCache.install_executor_hooks
+ end
+ end
+
initializer "active_record.add_watchable_files" do |app|
path = app.paths["db"].first
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 956fe7c51e..43f573f193 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -995,7 +995,7 @@ module ActiveRecord
end
def constraints
- [source_type_info]
+ @reflection.constraints + [source_type_info]
end
def source_type_info
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 243ef0eae9..b99807adf3 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -2,6 +2,8 @@ require "active_record/relation/batches/batch_enumerator"
module ActiveRecord
module Batches
+ ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size"
+
# Looping through a collection of records from the database
# (using the Scoping::Named::ClassMethods.all method, for example)
# is very inefficient since it will try to instantiate all the objects at once.
@@ -31,6 +33,9 @@ module ActiveRecord
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
+ #
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
@@ -48,13 +53,13 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_each(start: nil, finish: nil, batch_size: 1000)
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
if block_given?
- find_in_batches(start: start, finish: finish, batch_size: batch_size) do |records|
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
records.each { |record| yield record }
end
else
- enum_for(:find_each, start: start, finish: finish, batch_size: batch_size) do
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
relation = self
apply_limits(relation, start, finish).size
end
@@ -83,6 +88,9 @@ module ActiveRecord
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
+ #
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
@@ -100,16 +108,16 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_in_batches(start: nil, finish: nil, batch_size: 1000)
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
relation = self
unless block_given?
- return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size) do
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
total = apply_limits(relation, start, finish).size
(total - 1).div(batch_size) + 1
end
end
- in_batches(of: batch_size, start: start, finish: finish, load: true) do |batch|
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
yield batch.to_a
end
end
@@ -140,6 +148,8 @@ module ActiveRecord
# * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
#
# This is especially useful if you want to work with the
# ActiveRecord::Relation object instead of the array of records, or if
@@ -171,14 +181,14 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control the batch
# sizes.
- def in_batches(of: 1000, start: nil, finish: nil, load: false)
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
relation = self
unless block_given?
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
end
- if logger && (arel.orders.present? || arel.taken.present?)
- logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
+ if arel.orders.present? || arel.taken.present?
+ act_on_order_or_limit_ignored(error_on_ignore)
end
relation = relation.reorder(batch_order).limit(of)
@@ -219,5 +229,15 @@ module ActiveRecord
def batch_order
"#{quoted_table_name}.#{quoted_primary_key} ASC"
end
+
+ def act_on_order_or_limit_ignored(error_on_ignore)
+ raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
+
+ if raise_error
+ raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ elsif logger
+ logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 0037398554..c3053f0b13 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -255,13 +255,13 @@ module ActiveRecord
# Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
# Person.where(["user_name = :u", { u: user_name }]).third_to_last
def third_to_last
- find_nth(-3)
+ find_nth_from_last 3
end
# Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def third_to_last!
- find_nth!(-3)
+ find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
# Find the second-to-last record.
@@ -271,13 +271,13 @@ module ActiveRecord
# Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
# Person.where(["user_name = :u", { u: user_name }]).second_to_last
def second_to_last
- find_nth(-2)
+ find_nth_from_last 2
end
# Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def second_to_last!
- find_nth!(-2)
+ find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
# Returns true if a record exists in the table that matches the +id+ or
@@ -561,6 +561,25 @@ module ActiveRecord
relation.limit(limit).to_a
end
+ def find_nth_from_last(index)
+ if loaded?
+ @records[-index]
+ else
+ relation = if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
+ else
+ self
+ end
+
+ relation.to_a[-index]
+ # TODO: can be made more performant on large result sets by
+ # for instance, last(index)[-index] (which would require
+ # refactoring the last(n) finder method to make test suite pass),
+ # or by using a combination of reverse_order, limit, and offset,
+ # e.g., reverse_order.offset(index-1).first
+ end
+ end
+
private
def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 0ee147cdba..a9f1993842 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -11,7 +11,8 @@ module ActiveRecord
##
# PostgreSQL does not support null bytes in strings
- unless current_adapter?(:PostgreSQLAdapter)
+ unless current_adapter?(:PostgreSQLAdapter) ||
+ (current_adapter?(:SQLite3Adapter) && !ActiveRecord::Base.connection.prepared_statements)
def test_update_prepared_statement
b = Book.create(name: "my \x00 book")
b.reload
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 4fc7414b18..b783b5fcd9 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -2,26 +2,20 @@ require "cases/helper"
require 'models/developer'
require 'models/computer'
-module ActiveRecord
- module ConnectionAdapters
- class Mysql2Adapter
- class ExplainTest < ActiveRecord::Mysql2TestCase
- fixtures :developers
+class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase
+ fixtures :developers
- def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- end
+ def test_explain_for_one_query
+ explain = Developer.where(id: 1).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %r(developers |.* const), explain
+ end
- def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
- assert_match %r(audit_logs |.* ALL), explain
- end
- end
- end
+ def test_explain_with_eager_loading
+ explain = Developer.where(id: 1).includes(:audit_logs).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %r(developers |.* const), explain
+ assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
+ assert_match %r(audit_logs |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
index 4efd728754..00d23740b6 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -1,10 +1,22 @@
require "cases/helper"
+require "support/ddl_helper"
class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
+ include DdlHelper
+
def setup
@conn = ActiveRecord::Base.connection
end
+ def test_exec_query_nothing_raises_with_no_result_queries
+ assert_nothing_raised do
+ with_example_table do
+ @conn.exec_query('INSERT INTO ex (number) VALUES (1)')
+ @conn.exec_query('DELETE FROM ex WHERE number = 1')
+ end
+ end
+ end
+
def test_columns_for_distinct_zero_orders
assert_equal "posts.id",
@conn.columns_for_distinct("posts.id", [])
@@ -41,4 +53,10 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
assert_equal "posts.id, posts.created_at AS alias_0",
@conn.columns_for_distinct("posts.id", [order])
end
+
+ private
+
+ def with_example_table(definition = 'id int auto_increment primary key, number int, data varchar(255)', &block)
+ super(@conn, 'ex', definition, &block)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index 6f72fa6e0f..cec6081aec 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -57,9 +57,11 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase
assert_match %r{t\.bit_varying\s+"a_bit_varying",\s+limit: 4,\s+default: "0011"$}, output
end
- def test_assigning_invalid_hex_string_raises_exception
- assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" }
- assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "FF" }
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_assigning_invalid_hex_string_raises_exception
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" }
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "F" }
+ end
end
def test_roundtrip
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index d559de3e28..f8403bfe1a 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -125,14 +125,16 @@ module ActiveRecord
assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
- def test_statement_key_is_logged
- bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
- @connection.exec_query('SELECT $1::integer', 'SQL', [bind], prepare: true)
- name = @subscriber.payloads.last[:statement_name]
- assert name
- res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)")
- plan = res.column_types['QUERY PLAN'].deserialize res.rows.first.first
- assert_operator plan.length, :>, 0
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_statement_key_is_logged
+ bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
+ @connection.exec_query('SELECT $1::integer', 'SQL', [bind], prepare: true)
+ name = @subscriber.payloads.last[:statement_name]
+ assert name
+ res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)")
+ plan = res.column_types['QUERY PLAN'].deserialize res.rows.first.first
+ assert_operator plan.length, :>, 0
+ end
end
# Must have PostgreSQL >= 9.2, or with_manual_interventions set to
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 4d0fd640aa..29bf2c15ea 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -6,15 +6,15 @@ class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase
fixtures :developers
def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
+ explain = Developer.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = \$?1), explain
assert_match %(QUERY PLAN), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
+ explain = Developer.where(id: 1).includes(:audit_logs).explain
assert_match %(QUERY PLAN), explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = \$?1), explain
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 31e87722d9..fbdaab2fdd 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -5,6 +5,7 @@ require 'support/connection_helper'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapterTest < ActiveRecord::PostgreSQLTestCase
+ self.use_transactional_tests = false
include DdlHelper
include ConnectionHelper
@@ -239,30 +240,6 @@ module ActiveRecord
@connection.drop_table 'ex2', if_exists: true
end
- def test_exec_insert_number
- with_example_table do
- insert(@connection, 'number' => 10)
-
- result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
-
- assert_equal 1, result.rows.length
- assert_equal 10, result.rows.last.last
- end
- end
-
- def test_exec_insert_string
- with_example_table do
- str = 'いただきます!'
- insert(@connection, 'number' => 10, 'data' => str)
-
- result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
-
- value = result.rows.last.last
-
- assert_equal str, value
- end
- end
-
def test_table_alias_length
assert_nothing_raised do
@connection.table_alias_length
@@ -286,33 +263,35 @@ module ActiveRecord
end
end
- def test_exec_with_binds
- with_example_table do
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [bind_param(1)])
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_exec_with_binds
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ bind = Relation::QueryAttribute.new("id", 1, Type::Value.new)
+ result = @connection.exec_query('SELECT id, data FROM ex WHERE id = $1', nil, [bind])
- assert_equal [[1, 'foo']], result.rows
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
+
+ assert_equal [[1, 'foo']], result.rows
+ end
end
- end
- def test_exec_typecasts_bind_vals
- with_example_table do
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ def test_exec_typecasts_bind_vals
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [bind])
+ bind = Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)
+ result = @connection.exec_query('SELECT id, data FROM ex WHERE id = $1', nil, [bind])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [[1, 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
+ end
end
end
@@ -438,19 +417,6 @@ module ActiveRecord
end
private
- def insert(ctx, data)
- binds = data.map { |name, value|
- bind_param(value, name)
- }
- columns = binds.map(&:name)
-
- bind_subs = columns.length.times.map { |x| "$#{x + 1}" }
-
- sql = "INSERT INTO ex (#{columns.join(", ")})
- VALUES (#{bind_subs.join(', ')})"
-
- ctx.exec_insert(sql, 'SQL', binds)
- end
def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block)
super(@connection, 'ex', definition, &block)
@@ -459,10 +425,6 @@ module ActiveRecord
def connection_without_insert_returning
ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
end
-
- def bind_param(value, name = nil)
- ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new)
- end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index a0afd922b2..285a92f60e 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -55,20 +55,22 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase
set_session_auth
USERS.each do |u|
set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
+ assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1")
set_session_auth
end
end
end
- def test_auth_with_bind
- assert_nothing_raised do
- set_session_auth
- USERS.each do |u|
- @connection.clear_cache!
- set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_auth_with_bind
+ assert_nothing_raised do
set_session_auth
+ USERS.each do |u|
+ @connection.clear_cache!
+ set_session_auth u
+ assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)])
+ set_session_auth
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index f50fe88b9b..00ebabc9c5 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -172,16 +172,18 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
end
- def test_schema_change_with_prepared_stmt
- altered = false
- @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
- @connection.exec_query "alter table developers add column zomg int", 'sql', []
- altered = true
- @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
- ensure
- # We are not using DROP COLUMN IF EXISTS because that syntax is only
- # supported by pg 9.X
- @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_schema_change_with_prepared_stmt
+ altered = false
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
+ @connection.exec_query "alter table developers add column zomg int", 'sql', []
+ altered = true
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
+ ensure
+ # We are not using DROP COLUMN IF EXISTS because that syntax is only
+ # supported by pg 9.X
+ @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
+ end
end
def test_data_source_exists?
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index 2aec322582..a1a6e5f16a 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -2,26 +2,20 @@ require "cases/helper"
require 'models/developer'
require 'models/computer'
-module ActiveRecord
- module ConnectionAdapters
- class SQLite3Adapter
- class ExplainTest < ActiveRecord::SQLite3TestCase
- fixtures :developers
+class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase
+ fixtures :developers
- def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- end
+ def test_explain_for_one_query
+ explain = Developer.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\?|1)), explain
+ assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ end
- def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
- assert_match(/(SCAN )?TABLE audit_logs/, explain)
- end
- end
- end
+ def test_explain_with_eager_loading
+ explain = Developer.where(id: 1).includes(:audit_logs).explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\?|1)), explain
+ assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
+ assert_match(/(SCAN )?TABLE audit_logs/, explain)
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index ac478cbb01..3ee84fb66c 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -749,6 +749,38 @@ class EagerAssociationTest < ActiveRecord::TestCase
}
end
+ def test_eager_has_many_through_with_order
+ tag = OrderedTag.create(name: 'Foo')
+ post1 = Post.create!(title: 'Beaches', body: "I like beaches!")
+ post2 = Post.create!(title: 'Pools', body: "I like pools!")
+
+ Tagging.create!(taggable_type: 'Post', taggable_id: post1.id, tag: tag)
+ Tagging.create!(taggable_type: 'Post', taggable_id: post2.id, tag: tag)
+
+ tag_with_includes = OrderedTag.includes(:tagged_posts).find(tag.id)
+ assert_equal(tag_with_includes.taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title))
+ end
+
+ def test_eager_has_many_through_multiple_with_order
+ tag1 = OrderedTag.create!(name: 'Bar')
+ tag2 = OrderedTag.create!(name: 'Foo')
+
+ post1 = Post.create!(title: 'Beaches', body: "I like beaches!")
+ post2 = Post.create!(title: 'Pools', body: "I like pools!")
+
+ Tagging.create!(taggable: post1, tag: tag1)
+ Tagging.create!(taggable: post2, tag: tag1)
+ Tagging.create!(taggable: post2, tag: tag2)
+ Tagging.create!(taggable: post1, tag: tag2)
+
+ tags_with_includes = OrderedTag.where(id: [tag1, tag2].map(&:id)).includes(:tagged_posts).order(:id).to_a
+ tag1_with_includes = tags_with_includes.first
+ tag2_with_includes = tags_with_includes.last
+
+ assert_equal([post2, post1].map(&:title), tag1_with_includes.tagged_posts.map(&:title))
+ assert_equal([post1, post2].map(&:title), tag2_with_includes.tagged_posts.map(&:title))
+ end
+
def test_eager_with_default_scope
developer = EagerDeveloperWithDefaultScope.where(:name => 'David').first
projects = Project.order(:id).to_a
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index c850875619..1d892a0956 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -363,6 +363,13 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal posts(:welcome, :thinking).sort_by(&:id), tags(:general).tagged_posts.sort_by(&:id)
end
+ def test_has_many_polymorphic_associations_merges_through_scope
+ Tag.has_many :null_taggings, -> { none }, class_name: :Tagging
+ Tag.has_many :null_tagged_posts, :through => :null_taggings, :source => 'taggable', :source_type => 'Post'
+ assert_equal [], tags(:general).null_tagged_posts
+ refute_equal [], tags(:general).tagged_posts
+ end
+
def test_eager_has_many_polymorphic_with_source_type
tag_with_include = Tag.all.merge!(:includes => :tagged_posts).find(tags(:general).id)
desired = posts(:welcome, :thinking)
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 84aac3e721..91ff5146fd 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -164,6 +164,42 @@ class EachTest < ActiveRecord::TestCase
assert_equal posts(:welcome).id, posts.first.id
end
+ def test_find_in_batches_should_error_on_ignore_the_order
+ assert_raise(ArgumentError) do
+ PostWithDefaultScope.find_in_batches(error_on_ignore: true){}
+ end
+ end
+
+ def test_find_in_batches_should_not_error_if_config_overriden
+ # Set the config option which will be overriden
+ prev = ActiveRecord::Base.error_on_ignored_order_or_limit
+ ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ assert_nothing_raised do
+ PostWithDefaultScope.find_in_batches(error_on_ignore: false){}
+ end
+ ensure
+ # Set back to default
+ ActiveRecord::Base.error_on_ignored_order_or_limit = prev
+ end
+
+ def test_find_in_batches_should_error_on_config_specified_to_error
+ # Set the config option
+ prev = ActiveRecord::Base.error_on_ignored_order_or_limit
+ ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ assert_raise(ArgumentError) do
+ PostWithDefaultScope.find_in_batches(){}
+ end
+ ensure
+ # Set back to default
+ ActiveRecord::Base.error_on_ignored_order_or_limit = prev
+ end
+
+ def test_find_in_batches_should_not_error_by_default
+ assert_nothing_raised do
+ PostWithDefaultScope.find_in_batches(){}
+ end
+ end
+
def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order
special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort
posts = []
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index cd9c76f1f0..fa924fe4cb 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -31,7 +31,8 @@ module ActiveRecord
ActiveSupport::Notifications.unsubscribe(@subscription)
end
- if ActiveRecord::Base.connection.supports_statement_cache?
+ if ActiveRecord::Base.connection.supports_statement_cache? &&
+ ActiveRecord::Base.connection.prepared_statements
def test_bind_from_join_in_subquery
subquery = Author.joins(:thinking_posts).where(name: 'David')
scope = Author.from(subquery, 'authors').where(id: 1)
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index d43668e57c..c4c2c69d1c 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -19,7 +19,7 @@ module ActiveRecord
def setup
@env = {}
@app = App.new
- @management = ConnectionManagement.new(@app)
+ @management = middleware(@app)
# make sure we have an active connection
assert ActiveRecord::Base.connection
@@ -27,17 +27,12 @@ module ActiveRecord
end
def test_app_delegation
- manager = ConnectionManagement.new(@app)
+ manager = middleware(@app)
manager.call @env
assert_equal [@env], @app.calls
end
- def test_connections_are_active_after_call
- @management.call(@env)
- assert ActiveRecord::Base.connection_handler.active_connections?
- end
-
def test_body_responds_to_each
_, _, body = @management.call(@env)
bits = []
@@ -52,45 +47,40 @@ module ActiveRecord
end
def test_active_connections_are_not_cleared_on_body_close_during_test
- @env['rack.test'] = true
- _, _, body = @management.call(@env)
- body.close
- assert ActiveRecord::Base.connection_handler.active_connections?
+ executor.wrap do
+ _, _, body = @management.call(@env)
+ body.close
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
end
def test_connections_closed_if_exception
app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
- explosive = ConnectionManagement.new(app)
+ explosive = middleware(app)
assert_raises(NotImplementedError) { explosive.call(@env) }
assert !ActiveRecord::Base.connection_handler.active_connections?
end
def test_connections_not_closed_if_exception_and_test
- @env['rack.test'] = true
- app = Class.new(App) { def call(env); raise; end }.new
- explosive = ConnectionManagement.new(app)
- assert_raises(RuntimeError) { explosive.call(@env) }
- assert ActiveRecord::Base.connection_handler.active_connections?
- end
-
- def test_connections_closed_if_exception_and_explicitly_not_test
- @env['rack.test'] = false
- app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
- explosive = ConnectionManagement.new(app)
- assert_raises(NotImplementedError) { explosive.call(@env) }
- assert !ActiveRecord::Base.connection_handler.active_connections?
+ executor.wrap do
+ app = Class.new(App) { def call(env); raise; end }.new
+ explosive = middleware(app)
+ assert_raises(RuntimeError) { explosive.call(@env) }
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
end
test "doesn't clear active connections when running in a test case" do
- @env['rack.test'] = true
- @management.call(@env)
- assert ActiveRecord::Base.connection_handler.active_connections?
+ executor.wrap do
+ @management.call(@env)
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
end
test "proxy is polite to its body and responds to it" do
body = Class.new(String) { def to_path; "/path"; end }.new
app = lambda { |_| [200, {}, body] }
- response_body = ConnectionManagement.new(app).call(@env)[2]
+ response_body = middleware(app).call(@env)[2]
assert response_body.respond_to?(:to_path)
assert_equal "/path", response_body.to_path
end
@@ -98,9 +88,23 @@ module ActiveRecord
test "doesn't mutate the original response" do
original_response = [200, {}, 'hi']
app = lambda { |_| original_response }
- ConnectionManagement.new(app).call(@env)[2]
+ middleware(app).call(@env)[2]
assert_equal 'hi', original_response.last
end
+
+ private
+ def executor
+ @executor ||= Class.new(ActiveSupport::Executor).tap do |exe|
+ ActiveRecord::QueryCache.install_executor_hooks(exe)
+ end
+ end
+
+ def middleware(app)
+ lambda do |env|
+ a, b, c = executor.wrap { app.call(env) }
+ [a, b, Rack::BodyProxy.new(c) { }]
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 3e31874455..692c6bf2d0 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -486,6 +486,66 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_second_to_last
+ assert_equal topics(:fourth).title, Topic.second_to_last.title
+
+ # test with offset
+ assert_equal topics(:fourth), Topic.offset(1).second_to_last
+ assert_equal topics(:fourth), Topic.offset(2).second_to_last
+ assert_equal topics(:fourth), Topic.offset(3).second_to_last
+ assert_equal nil, Topic.offset(4).second_to_last
+ assert_equal nil, Topic.offset(5).second_to_last
+
+ #test with limit
+ # assert_equal nil, Topic.limit(1).second # TODO: currently failing
+ assert_equal nil, Topic.limit(1).second_to_last
+ end
+
+ def test_second_to_last_have_primary_key_order_by_default
+ expected = topics(:fourth)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.second_to_last
+ end
+
+ def test_model_class_responds_to_second_to_last_bang
+ assert Topic.second_to_last!
+ Topic.delete_all
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
+ Topic.second_to_last!
+ end
+ end
+
+ def test_third_to_last
+ assert_equal topics(:third).title, Topic.third_to_last.title
+
+ # test with offset
+ assert_equal topics(:third), Topic.offset(1).third_to_last
+ assert_equal topics(:third), Topic.offset(2).third_to_last
+ assert_equal nil, Topic.offset(3).third_to_last
+ assert_equal nil, Topic.offset(4).third_to_last
+ assert_equal nil, Topic.offset(5).third_to_last
+
+ # test with limit
+ # assert_equal nil, Topic.limit(1).third # TODO: currently failing
+ assert_equal nil, Topic.limit(1).third_to_last
+ # assert_equal nil, Topic.limit(2).third # TODO: currently failing
+ assert_equal nil, Topic.limit(2).third_to_last
+ end
+
+ def test_third_to_last_have_primary_key_order_by_default
+ expected = topics(:third)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.third_to_last
+ end
+
+ def test_model_class_responds_to_third_to_last_bang
+ assert Topic.third_to_last!
+ Topic.delete_all
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
+ Topic.third_to_last!
+ end
+ end
+
def test_last_bang_present
assert_nothing_raised do
assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last!
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index 5ba9a1029a..9fc75b7377 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -1,7 +1,9 @@
require 'cases/helper'
+require 'support/connection_helper'
class HotCompatibilityTest < ActiveRecord::TestCase
self.use_transactional_tests = false
+ include ConnectionHelper
setup do
@klass = Class.new(ActiveRecord::Base) do
@@ -51,4 +53,90 @@ class HotCompatibilityTest < ActiveRecord::TestCase
record.reload
assert_equal 'bar', record.foo
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ test "cleans up after prepared statement failure in a transaction" do
+ with_two_connections do |original_connection, ddl_connection|
+ record = @klass.create! bar: 'bar'
+
+ # prepare the reload statement in a transaction
+ @klass.transaction do
+ record.reload
+ end
+
+ assert get_prepared_statement_cache(@klass.connection).any?,
+ "expected prepared statement cache to have something in it"
+
+ # add a new column
+ ddl_connection.add_column :hot_compatibilities, :baz, :string
+
+ assert_raise(ActiveRecord::PreparedStatementCacheExpired) do
+ @klass.transaction do
+ record.reload
+ end
+ end
+
+ assert_empty get_prepared_statement_cache(@klass.connection),
+ "expected prepared statement cache to be empty but it wasn't"
+ end
+ end
+
+ test "cleans up after prepared statement failure in nested transactions" do
+ with_two_connections do |original_connection, ddl_connection|
+ record = @klass.create! bar: 'bar'
+
+ # prepare the reload statement in a transaction
+ @klass.transaction do
+ record.reload
+ end
+
+ assert get_prepared_statement_cache(@klass.connection).any?,
+ "expected prepared statement cache to have something in it"
+
+ # add a new column
+ ddl_connection.add_column :hot_compatibilities, :baz, :string
+
+ assert_raise(ActiveRecord::PreparedStatementCacheExpired) do
+ @klass.transaction do
+ @klass.transaction do
+ @klass.transaction do
+ record.reload
+ end
+ end
+ end
+ end
+
+ assert_empty get_prepared_statement_cache(@klass.connection),
+ "expected prepared statement cache to be empty but it wasn't"
+ end
+ end
+ end
+
+ private
+
+ def get_prepared_statement_cache(connection)
+ connection.instance_variable_get(:@statements)
+ .instance_variable_get(:@cache)[Process.pid]
+ end
+
+ # Rails will automatically clear the prepared statements on the connection
+ # that runs the migration, so we use two connections to simulate what would
+ # actually happen on a production system; we'd have one connection running the
+ # migration from the rake task ("ddl_connection" here), and we'd have another
+ # connection in a web worker.
+ def with_two_connections
+ run_without_connection do |original_connection|
+ ActiveRecord::Base.establish_connection(original_connection.merge(pool_size: 2))
+ begin
+ ddl_connection = ActiveRecord::Base.connection_pool.checkout
+ begin
+ yield original_connection, ddl_connection
+ ensure
+ ActiveRecord::Base.connection_pool.checkin ddl_connection
+ end
+ ensure
+ ActiveRecord::Base.clear_all_connections!
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 4fe76e563a..6c59d7337a 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -441,7 +441,7 @@ unless in_memory_db?
def test_lock_sending_custom_lock_statement
Person.transaction do
person = Person.find(1)
- assert_sql(/LIMIT \$\d FOR SHARE NOWAIT/) do
+ assert_sql(/LIMIT \$?\d FOR SHARE NOWAIT/) do
person.lock!('FOR SHARE NOWAIT')
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index d84653e4c9..e53239cdee 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -16,7 +16,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_exceptional_middleware_clears_and_disables_cache_on_error
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
Task.find 1
Task.find 1
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
@@ -31,7 +31,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_exceptional_middleware_leaves_enabled_cache_alone
ActiveRecord::Base.connection.enable_query_cache!
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
raise "lol borked"
}
assert_raises(RuntimeError) { mw.call({}) }
@@ -42,7 +42,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_exceptional_middleware_assigns_original_connection_id_on_error
connection_id = ActiveRecord::Base.connection_id
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
ActiveRecord::Base.connection_id = self.object_id
raise "lol borked"
}
@@ -53,7 +53,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_middleware_delegates
called = false
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
called = true
[200, {}, nil]
}
@@ -62,7 +62,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_middleware_caches
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
Task.find 1
Task.find 1
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
@@ -74,50 +74,13 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_enabled_during_call
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
[200, {}, nil]
}
mw.call({})
end
- def test_cache_on_during_body_write
- streaming = Class.new do
- def each
- yield ActiveRecord::Base.connection.query_cache_enabled
- end
- end
-
- mw = ActiveRecord::QueryCache.new lambda { |env|
- [200, {}, streaming.new]
- }
- body = mw.call({}).last
- body.each { |x| assert x, 'cache should be on' }
- body.close
- assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
- end
-
- def test_cache_off_after_close
- mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] }
- body = mw.call({}).last
-
- assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled'
- body.close
- assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
- end
-
- def test_cache_clear_after_close
- mw = ActiveRecord::QueryCache.new lambda { |env|
- Post.first
- [200, {}, nil]
- }
- body = mw.call({}).last
-
- assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty'
- body.close
- assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
- end
-
def test_cache_passing_a_relation
post = Post.first
Post.cache do
@@ -244,6 +207,13 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_equal 0, Post.where(title: 'rollback').to_a.count
end
end
+
+ private
+ def middleware(&app)
+ executor = Class.new(ActiveSupport::Executor)
+ ActiveRecord::QueryCache.install_executor_hooks executor
+ lambda { |env| executor.wrap { app.call(env) } }
+ end
end
class QueryCacheExpiryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb
index 80d4725f7e..b48b9a2155 100644
--- a/activerecord/test/models/tag.rb
+++ b/activerecord/test/models/tag.rb
@@ -5,3 +5,9 @@ class Tag < ActiveRecord::Base
has_many :tagged_posts, :through => :taggings, :source => 'taggable', :source_type => 'Post'
end
+
+class OrderedTag < Tag
+ self.table_name = "tags"
+
+ has_many :taggings, -> { order('taggings.id DESC') }, foreign_key: 'tag_id'
+end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 1a169d36be..58528c442a 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,14 @@
+* Deprecate `Module.local_constants`. Please use `Module.constants(false)` instead.
+
+ *Yuichiro Kaneko*
+
+* Publish ActiveSupport::Executor and ActiveSupport::Reloader APIs to allow
+ components and libraries to manage, and participate in, the execution of
+ application code, and the application reloading process.
+
+ *Matthew Draper*
+
+
## Rails 5.0.0.beta3 (February 24, 2016) ##
* Deprecate arguments on `assert_nothing_raised`.
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 94fe893149..72777baecd 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -33,10 +33,13 @@ module ActiveSupport
autoload :Concern
autoload :Dependencies
autoload :DescendantsTracker
+ autoload :ExecutionWrapper
+ autoload :Executor
autoload :FileUpdateChecker
autoload :EventedFileUpdateChecker
autoload :LogSubscriber
autoload :Notifications
+ autoload :Reloader
eager_autoload do
autoload :BacktraceCleaner
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index f1d26ef28f..fa692e1b0e 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -57,6 +57,10 @@ class Module
end
def local_constants #:nodoc:
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Module#local_constants is deprecated and will be removed in Rails 5.1.
+ Use Module#constants(false) instead.
+ MSG
constants(false)
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index fd9fbff96a..0da01b0fe8 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -143,7 +143,7 @@ module ActiveSupport #:nodoc:
next unless mod.is_a?(Module)
# Get a list of the constants that were added
- new_constants = mod.local_constants - original_constants
+ new_constants = mod.constants(false) - original_constants
# @stack[namespace] returns an Array of the constants that are being evaluated
# for that namespace. For instance, if parent.rb requires child.rb, the first
@@ -171,7 +171,7 @@ module ActiveSupport #:nodoc:
@watching << namespaces.map do |namespace|
module_name = Dependencies.to_constant_name(namespace)
original_constants = Dependencies.qualified_const_defined?(module_name) ?
- Inflector.constantize(module_name).local_constants : []
+ Inflector.constantize(module_name).constants(false) : []
@stack[module_name] << original_constants
module_name
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
index 47bcecbb35..f1865ca2f8 100644
--- a/activesupport/lib/active_support/dependencies/interlock.rb
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -19,14 +19,12 @@ module ActiveSupport #:nodoc:
end
end
- # Attempt to obtain an "unloading" (exclusive) lock. If possible,
- # execute the supplied block while holding the lock. If there is
- # concurrent activity, return immediately (without executing the
- # block) instead of waiting.
- def attempt_unloading
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], no_wait: true) do
- yield
- end
+ def start_unloading
+ @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload])
+ end
+
+ def done_unloading
+ @lock.stop_exclusive(compatible: [:load, :unload])
end
def start_running
diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index 315be85fb3..63f4f7e277 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -37,6 +37,7 @@ module ActiveSupport
def execute_if_updated
if updated?
+ yield if block_given?
execute
true
end
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
new file mode 100644
index 0000000000..e784556abf
--- /dev/null
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -0,0 +1,69 @@
+require 'active_support/callbacks'
+
+module ActiveSupport
+ class ExecutionWrapper
+ include ActiveSupport::Callbacks
+
+ define_callbacks :run
+ define_callbacks :complete
+
+ def self.to_run(*args, &block)
+ set_callback(:run, *args, &block)
+ end
+
+ def self.to_complete(*args, &block)
+ set_callback(:complete, *args, &block)
+ end
+
+ # Run this execution.
+ #
+ # Returns an instance, whose +complete!+ method *must* be invoked
+ # after the work has been performed.
+ #
+ # Where possible, prefer +wrap+.
+ def self.run!
+ new.tap(&:run!)
+ end
+
+ # Perform the work in the supplied block as an execution.
+ def self.wrap
+ return yield if active?
+
+ state = run!
+ begin
+ yield
+ ensure
+ state.complete!
+ end
+ end
+
+ class << self # :nodoc:
+ attr_accessor :active
+ end
+
+ def self.inherited(other) # :nodoc:
+ super
+ other.active = Concurrent::Hash.new(0)
+ end
+
+ self.active = Concurrent::Hash.new(0)
+
+ def self.active? # :nodoc:
+ @active[Thread.current] > 0
+ end
+
+ def run! # :nodoc:
+ self.class.active[Thread.current] += 1
+ run_callbacks(:run)
+ end
+
+ # Complete this in-flight execution. This method *must* be called
+ # exactly once on the result of any call to +run!+.
+ #
+ # Where possible, prefer +wrap+.
+ def complete!
+ run_callbacks(:complete)
+ self.class.active.delete Thread.current if (self.class.active[Thread.current] -= 1) == 0
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/executor.rb b/activesupport/lib/active_support/executor.rb
new file mode 100644
index 0000000000..602fb11a44
--- /dev/null
+++ b/activesupport/lib/active_support/executor.rb
@@ -0,0 +1,6 @@
+require 'active_support/execution_wrapper'
+
+module ActiveSupport
+ class Executor < ExecutionWrapper
+ end
+end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 1fa9335080..dc7434fcac 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -81,6 +81,7 @@ module ActiveSupport
# Execute the block given if updated.
def execute_if_updated
if updated?
+ yield if block_given?
execute
true
else
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 82aacf3b24..6cc7c90c12 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -64,8 +64,8 @@ module I18n
end
app.reloaders << reloader
- ActionDispatch::Reloader.to_prepare do
- reloader.execute_if_updated
+ app.reloader.to_run do
+ reloader.execute_if_updated { require_unload_lock! }
# TODO: remove the following line as soon as the return value of
# callbacks is ignored, that is, returning `false` does not
# display a deprecation warning or halts the callback chain.
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
new file mode 100644
index 0000000000..639b987ba8
--- /dev/null
+++ b/activesupport/lib/active_support/reloader.rb
@@ -0,0 +1,126 @@
+require 'active_support/execution_wrapper'
+
+module ActiveSupport
+ #--
+ # This class defines several callbacks:
+ #
+ # to_prepare -- Run once at application startup, and also from
+ # +to_run+.
+ #
+ # to_run -- Run before a work run that is reloading. If
+ # +reload_classes_only_on_change+ is true (the default), the class
+ # unload will have already occurred.
+ #
+ # to_complete -- Run after a work run that has reloaded. If
+ # +reload_classes_only_on_change+ is false, the class unload will
+ # have occurred after the work run, but before this callback.
+ #
+ # before_class_unload -- Run immediately before the classes are
+ # unloaded.
+ #
+ # after_class_unload -- Run immediately after the classes are
+ # unloaded.
+ #
+ class Reloader < ExecutionWrapper
+ Null = Class.new(ExecutionWrapper) # :nodoc:
+
+ define_callbacks :prepare
+
+ define_callbacks :class_unload
+
+ def self.to_prepare(*args, &block)
+ set_callback(:prepare, *args, &block)
+ end
+
+ def self.before_class_unload(*args, &block)
+ set_callback(:class_unload, *args, &block)
+ end
+
+ def self.after_class_unload(*args, &block)
+ set_callback(:class_unload, :after, *args, &block)
+ end
+
+ to_run(:after) { self.class.prepare! }
+
+ # Initiate a manual reload
+ def self.reload!
+ executor.wrap do
+ new.tap(&:run!).complete!
+ end
+ prepare!
+ end
+
+ def self.run! # :nodoc:
+ if check!
+ super
+ else
+ Null.run!
+ end
+ end
+
+ # Run the supplied block as a work unit, reloading code as needed
+ def self.wrap
+ executor.wrap do
+ super
+ end
+ end
+
+ class << self
+ attr_accessor :executor
+ attr_accessor :check
+ end
+ self.executor = Executor
+ self.check = lambda { false }
+
+ def self.check! # :nodoc:
+ @should_reload ||= check.call
+ end
+
+ def self.reloaded! # :nodoc:
+ @should_reload = false
+ end
+
+ def self.prepare! # :nodoc:
+ new.run_callbacks(:prepare)
+ end
+
+ def initialize
+ super
+ @locked = false
+ end
+
+ # Acquire the ActiveSupport::Dependencies::Interlock unload lock,
+ # ensuring it will be released automatically
+ def require_unload_lock!
+ unless @locked
+ ActiveSupport::Dependencies.interlock.start_unloading
+ @locked = true
+ end
+ end
+
+ # Release the unload lock if it has been previously obtained
+ def release_unload_lock!
+ if @locked
+ @locked = false
+ ActiveSupport::Dependencies.interlock.done_unloading
+ end
+ end
+
+ def run! # :nodoc:
+ super
+ release_unload_lock!
+ end
+
+ def class_unload!(&block) # :nodoc:
+ require_unload_lock!
+ run_callbacks(:class_unload, &block)
+ end
+
+ def complete! # :nodoc:
+ super
+ self.class.reloaded!
+ ensure
+ release_unload_lock!
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 0ed66f8c37..cbe025624c 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -328,7 +328,13 @@ class ModuleTest < ActiveSupport::TestCase
end
def test_local_constants
- assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s)
+ ActiveSupport::Deprecation.silence do
+ assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s)
+ end
+ end
+
+ def test_test_local_constants_is_deprecated
+ assert_deprecated { Ab.local_constants.sort.map(&:to_s) }
end
end
diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb
new file mode 100644
index 0000000000..6db6db4fa8
--- /dev/null
+++ b/activesupport/test/executor_test.rb
@@ -0,0 +1,76 @@
+require 'abstract_unit'
+
+class ExecutorTest < ActiveSupport::TestCase
+ def test_wrap_invokes_callbacks
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+
+ executor.wrap do
+ called << :body
+ end
+
+ assert_equal [:run, :body, :complete], called
+ end
+
+ def test_callbacks_share_state
+ result = false
+ executor.to_run { @foo = true }
+ executor.to_complete { result = @foo }
+
+ executor.wrap { }
+
+ assert result
+ end
+
+ def test_separated_calls_invoke_callbacks
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+
+ state = executor.run!
+ called << :body
+ state.complete!
+
+ assert_equal [:run, :body, :complete], called
+ end
+
+ def test_avoids_double_wrapping
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+
+ executor.wrap do
+ called << :early
+ executor.wrap do
+ called << :body
+ end
+ called << :late
+ end
+
+ assert_equal [:run, :early, :body, :late, :complete], called
+ end
+
+ def test_separate_classes_can_wrap
+ other_executor = Class.new(ActiveSupport::Executor)
+
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+ other_executor.to_run { called << :other_run }
+ other_executor.to_complete { called << :other_complete }
+
+ executor.wrap do
+ other_executor.wrap do
+ called << :body
+ end
+ end
+
+ assert_equal [:run, :other_run, :body, :other_complete, :complete], called
+ end
+
+ private
+ def executor
+ @executor ||= Class.new(ActiveSupport::Executor)
+ end
+end
diff --git a/activesupport/test/reloader_test.rb b/activesupport/test/reloader_test.rb
new file mode 100644
index 0000000000..958cb49993
--- /dev/null
+++ b/activesupport/test/reloader_test.rb
@@ -0,0 +1,85 @@
+require 'abstract_unit'
+
+class ReloaderTest < ActiveSupport::TestCase
+ def test_prepare_callback
+ prepared = false
+ reloader.to_prepare { prepared = true }
+
+ assert !prepared
+ reloader.prepare!
+ assert prepared
+
+ prepared = false
+ reloader.wrap do
+ assert prepared
+ prepared = false
+ end
+ assert !prepared
+ end
+
+ def test_only_run_when_check_passes
+ r = new_reloader { true }
+ invoked = false
+ r.to_run { invoked = true }
+ r.wrap { }
+ assert invoked
+
+ r = new_reloader { false }
+ invoked = false
+ r.to_run { invoked = true }
+ r.wrap { }
+ assert !invoked
+ end
+
+ def test_full_reload_sequence
+ called = []
+ reloader.to_prepare { called << :prepare }
+ reloader.to_run { called << :reloader_run }
+ reloader.to_complete { called << :reloader_complete }
+ reloader.executor.to_run { called << :executor_run }
+ reloader.executor.to_complete { called << :executor_complete }
+
+ reloader.wrap { }
+ assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete], called
+
+ called = []
+ reloader.reload!
+ assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called
+
+ reloader.check = lambda { false }
+
+ called = []
+ reloader.wrap { }
+ assert_equal [:executor_run, :executor_complete], called
+
+ called = []
+ reloader.reload!
+ assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called
+ end
+
+ def test_class_unload_block
+ called = []
+ reloader.before_class_unload { called << :before_unload }
+ reloader.after_class_unload { called << :after_unload }
+ reloader.to_run do
+ class_unload! do
+ called << :unload
+ end
+ end
+ reloader.wrap { called << :body }
+
+ assert_equal [:before_unload, :unload, :after_unload, :body], called
+ end
+
+ private
+ def new_reloader(&check)
+ Class.new(ActiveSupport::Reloader).tap do |r|
+ r.check = check
+ r.executor = Class.new(ActiveSupport::Executor)
+ end
+ end
+
+ def reloader
+ @reloader ||= new_reloader { true }
+ end
+end
diff --git a/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css
index 6d2edb2eb8..bc7afd3898 100644
--- a/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css
@@ -90,7 +90,7 @@
}
.syntaxhighlighter .script {
color: #222 !important;
- background-color: none !important;
+ background-color: transparent !important;
}
.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
color: gray !important;
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 29e0943741..ce6f943e49 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -260,7 +260,7 @@ With the `as` option we can specify a different name for the local variable. For
<%= render partial: "product", as: "item" %>
```
-The `object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere (eg. in a different instance variable or in a local variable).
+The `object` option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere (e.g. in a different instance variable or in a local variable).
For example, instead of:
@@ -442,7 +442,7 @@ image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png
#### image_url
-Computes the url to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host.
+Computes the URL to an image asset in the `app/assets/images` directory. This will call `image_path` internally and merge with your current host or your asset host.
```ruby
image_url("edit.png") # => http://www.example.com/assets/edit.png
@@ -493,7 +493,7 @@ javascript_path "common" # => /assets/common.js
#### javascript_url
-Computes the url to a JavaScript asset in the `app/assets/javascripts` directory. This will call `javascript_path` internally and merge with your current host or your asset host.
+Computes the URL to a JavaScript asset in the `app/assets/javascripts` directory. This will call `javascript_path` internally and merge with your current host or your asset host.
```ruby
javascript_url "common" # => http://www.example.com/assets/common.js
@@ -530,7 +530,7 @@ stylesheet_path "application" # => /assets/application.css
#### stylesheet_url
-Computes the url to a stylesheet asset in the `app/assets/stylesheets` directory. This will call `stylesheet_path` internally and merge with your current host or your asset host.
+Computes the URL to a stylesheet asset in the `app/assets/stylesheets` directory. This will call `stylesheet_path` internally and merge with your current host or your asset host.
```ruby
stylesheet_url "application" # => http://www.example.com/assets/application.css
@@ -1247,7 +1247,7 @@ file_field_tag 'attachment'
#### form_tag
-Starts a form tag that points the action to a url configured with `url_for_options` just like `ActionController::Base#url_for`.
+Starts a form tag that points the action to a URL configured with `url_for_options` just like `ActionController::Base#url_for`.
```html+erb
<%= form_tag '/articles' do %>
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index e66b9a4301..b994c863d1 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -709,29 +709,6 @@ M.parents # => [X::Y, X, Object]
NOTE: Defined in `active_support/core_ext/module/introspection.rb`.
-### Constants
-
-The method `local_constants` returns the names of the constants that have been
-defined in the receiver module:
-
-```ruby
-module X
- X1 = 1
- X2 = 2
- module Y
- Y1 = :y1
- X1 = :overrides_X1_above
- end
-end
-
-X.local_constants # => [:X1, :X2, :Y]
-X::Y.local_constants # => [:Y1, :X1]
-```
-
-The names are returned as symbols.
-
-NOTE: Defined in `active_support/core_ext/module/introspection.rb`.
-
#### Qualified Constant Names
The standard methods `const_defined?`, `const_get`, and `const_set` accept
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 41985c3661..1d3ff6e6e7 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -281,6 +281,8 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.schema_format` controls the format for dumping the database schema to a file. The options are `:ruby` (the default) for a database-independent version that depends on migrations, or `:sql` for a set of (potentially database-dependent) SQL statements.
+* `config.active_record.error_on_ignored_order_or_limit` specifies if an error should be raised if the order or limit of a query is ignored during a batch query. The options are true (raise error) or false (warn). Default is false.
+
* `config.active_record.timestamped_migrations` controls whether migrations are numbered with serial integers or with timestamps. The default is true, to use timestamps, which are preferred if there are multiple developers working on the same application.
* `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is true by default.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index f3da431b09..fa764ef02d 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,8 @@
+* The application generator writes a new file `config/spring.rb`, which tells
+ Spring to watch additional common files.
+
+ *Xavier Noria*
+
* The tasks in the rails task namespace is deprecated in favor of app namespace.
(e.g. `rails:update` and `rails:template` tasks is renamed to `app:update` and `app:template`.)
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index bff33ff42e..d05d610ee1 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -113,7 +113,7 @@ module Rails
attr_accessor :assets, :sandbox
alias_method :sandbox?, :sandbox
- attr_reader :reloaders
+ attr_reader :reloaders, :reloader, :executor
delegate :default_url_options, :default_url_options=, to: :routes
@@ -131,6 +131,10 @@ module Rails
@message_verifiers = {}
@ran_load_hooks = false
+ @executor = Class.new(ActiveSupport::Executor)
+ @reloader = Class.new(ActiveSupport::Reloader)
+ @reloader.executor = @executor
+
# are these actually used?
@initial_variable_values = initial_variable_values
@block = block
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 4f1cc0703d..381e548730 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -34,22 +34,10 @@ module Rails
# handling: presumably their code is not threadsafe
middleware.use ::Rack::Lock
-
- elsif config.allow_concurrency == :unsafe
- # Do nothing, even if we know this is dangerous. This is the
- # historical behaviour for true.
-
- else
- # Default concurrency setting: enabled, but safe
-
- unless config.cache_classes && config.eager_load
- # Without cache_classes + eager_load, the load interlock
- # is required for proper operation
-
- middleware.use ::ActionDispatch::LoadInterlock
- end
end
+ middleware.use ::ActionDispatch::Executor, app.executor
+
middleware.use ::Rack::Runtime
middleware.use ::Rack::MethodOverride unless config.api_only
middleware.use ::ActionDispatch::RequestId
@@ -61,7 +49,7 @@ module Rails
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
unless config.cache_classes
- middleware.use ::ActionDispatch::Reloader, lambda { reload_dependencies? }
+ middleware.use ::ActionDispatch::Reloader, app.reloader
end
middleware.use ::ActionDispatch::Callbacks
@@ -83,10 +71,6 @@ module Rails
private
- def reload_dependencies?
- config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any?
- end
-
def load_rack_cache
rack_cache = config.action_dispatch.rack_cache
return unless rack_cache
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 64ec539564..34f2265108 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -38,16 +38,16 @@ module Rails
app.routes.define_mounted_helper(:main_app)
end
- initializer :add_to_prepare_blocks do
+ initializer :add_to_prepare_blocks do |app|
config.to_prepare_blocks.each do |block|
- ActionDispatch::Reloader.to_prepare(&block)
+ app.reloader.to_prepare(&block)
end
end
# This needs to happen before eager load so it happens
# in exactly the same point regardless of config.cache_classes
- initializer :run_prepare_callbacks do
- ActionDispatch::Reloader.prepare!
+ initializer :run_prepare_callbacks do |app|
+ app.reloader.prepare!
end
initializer :eager_load! do
@@ -62,13 +62,47 @@ module Rails
ActiveSupport.run_load_hooks(:after_initialize, self)
end
+ initializer :configure_executor_for_concurrency do |app|
+ if config.allow_concurrency == false
+ # User has explicitly opted out of concurrent request
+ # handling: presumably their code is not threadsafe
+
+ mutex = Mutex.new
+ app.executor.to_run(prepend: true) do
+ mutex.lock
+ end
+ app.executor.to_complete(:after) do
+ mutex.unlock
+ end
+
+ elsif config.allow_concurrency == :unsafe
+ # Do nothing, even if we know this is dangerous. This is the
+ # historical behaviour for true.
+
+ else
+ # Default concurrency setting: enabled, but safe
+
+ unless config.cache_classes && config.eager_load
+ # Without cache_classes + eager_load, the load interlock
+ # is required for proper operation
+
+ app.executor.to_run(prepend: true) do
+ ActiveSupport::Dependencies.interlock.start_running
+ end
+ app.executor.to_complete(:after) do
+ ActiveSupport::Dependencies.interlock.done_running
+ end
+ end
+ end
+ end
+
# Set routes reload after the finisher hook to ensure routes added in
# the hook are taken into account.
- initializer :set_routes_reloader_hook do
+ initializer :set_routes_reloader_hook do |app|
reloader = routes_reloader
reloader.execute_if_updated
self.reloaders << reloader
- ActionDispatch::Reloader.to_prepare do
+ app.reloader.to_run do
# We configure #execute rather than #execute_if_updated because if
# autoloaded constants are cleared we need to reload routes also in
# case any was used there, as in
@@ -78,18 +112,27 @@ module Rails
# This means routes are also reloaded if i18n is updated, which
# might not be necessary, but in order to be more precise we need
# some sort of reloaders dependency support, to be added.
+ require_unload_lock!
reloader.execute
end
end
# Set clearing dependencies after the finisher hook to ensure paths
# added in the hook are taken into account.
- initializer :set_clear_dependencies_hook, group: :all do
+ initializer :set_clear_dependencies_hook, group: :all do |app|
callback = lambda do
- ActiveSupport::Dependencies.interlock.unloading do
- ActiveSupport::DescendantsTracker.clear
- ActiveSupport::Dependencies.clear
+ ActiveSupport::DescendantsTracker.clear
+ ActiveSupport::Dependencies.clear
+ end
+
+ if config.cache_classes
+ app.reloader.check = lambda { false }
+ elsif config.reload_classes_only_on_change
+ app.reloader.check = lambda do
+ app.reloaders.map(&:updated?).any?
end
+ else
+ app.reloader.check = lambda { true }
end
if config.reload_classes_only_on_change
@@ -99,15 +142,19 @@ module Rails
# Prepend this callback to have autoloaded constants cleared before
# any other possible reloading, in case they need to autoload fresh
# constants.
- ActionDispatch::Reloader.to_prepare(prepend: true) do
+ app.reloader.to_run(prepend: true) do
# In addition to changes detected by the file watcher, if routes
# or i18n have been updated we also need to clear constants,
# that's why we run #execute rather than #execute_if_updated, this
# callback has to clear autoloaded constants after any update.
- reloader.execute
+ class_unload! do
+ reloader.execute
+ end
end
else
- ActionDispatch::Reloader.to_cleanup(&callback)
+ app.reloader.to_complete do
+ class_unload!(&callback)
+ end
end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 27cbaf360a..d7597a13e1 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -114,8 +114,6 @@ module Rails
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
puts "=> Run `rails server -h` for more startup options"
-
- puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
end
def create_cache_file
diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb
index ac5836a588..9ad77e0a80 100644
--- a/railties/lib/rails/console/app.rb
+++ b/railties/lib/rails/console/app.rb
@@ -29,8 +29,7 @@ module Rails
# reloads the environment
def reload!(print=true)
puts "Reloading..." if print
- ActionDispatch::Reloader.cleanup!
- ActionDispatch::Reloader.prepare!
+ Rails.application.reloader.reload!
true
end
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 7bab3fdf74..f4d2aa761b 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -80,6 +80,7 @@ module Rails
template "secrets.yml"
template "cable.yml" unless options[:skip_action_cable]
template "puma.rb" unless options[:skip_puma]
+ template "spring.rb" if spring_install?
directory "environments"
directory "initializers"
@@ -332,7 +333,7 @@ module Rails
def delete_action_cable_files_skipping_action_cable
if options[:skip_action_cable]
remove_file 'config/cable.yml'
- remove_file 'app/assets/javascripts/cable.coffee'
+ remove_file 'app/assets/javascripts/cable.js'
remove_dir 'app/channels'
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
deleted file mode 100644
index af08f58e34..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-# Action Cable provides the framework to deal with WebSockets in Rails.
-# You can generate new channels where WebSocket features live using the rails generate channel command.
-#
-#= require action_cable
-#= require_self
-#= require_tree ./channels
-
-@App ||= {}
-App.cable = ActionCable.createConsumer()
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js
new file mode 100644
index 0000000000..71ee1e66de
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js
@@ -0,0 +1,13 @@
+// Action Cable provides the framework to deal with WebSockets in Rails.
+// You can generate new channels where WebSocket features live using the rails generate channel command.
+//
+//= require action_cable
+//= require_self
+//= require_tree ./channels
+
+(function() {
+ this.App || (this.App = {});
+
+ App.cable = ActionCable.createConsumer();
+
+}).call(this);
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru.tt b/railties/lib/rails/generators/rails/app/templates/config.ru
index 343c0833d7..bd83b25412 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru
@@ -1,10 +1,4 @@
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
-<%- unless options[:skip_action_cable] -%>
-
-# Action Cable requires that all classes are loaded in advance
-Rails.application.eager_load!
-<%- end -%>
-
run Rails.application
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 0fc1179339..5205c0ef0a 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -44,6 +44,9 @@ Rails.application.configure do
# Action Cable endpoint configuration
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
+
+ # Don't mount Action Cable in the main server process.
+ # config.action_cable.mount_path = nil
<%- end -%>
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
@@ -91,9 +94,5 @@ Rails.application.configure do
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
-
- # Don't mount Action Cable in the main server process.
- # config.action_cable.mount_path = nil
- # config.action_cable.url = "ws://example.com"
<%- end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/spring.rb b/railties/lib/rails/generators/rails/app/templates/config/spring.rb
new file mode 100644
index 0000000000..c9119b40c0
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/spring.rb
@@ -0,0 +1,6 @@
+%w(
+ .ruby-version
+ .rbenv-vars
+ tmp/restart.txt
+ tmp/caching-dev.txt
+).each { |path| Spring.watch(path) }
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 7bf123d12b..ea68e63f8f 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -52,12 +52,11 @@ class ConsoleTest < ActiveSupport::TestCase
a = b = c = nil
# TODO: These should be defined on the initializer
- ActionDispatch::Reloader.to_cleanup { a = b = c = 1 }
- ActionDispatch::Reloader.to_cleanup { b = c = 2 }
- ActionDispatch::Reloader.to_prepare { c = 3 }
+ ActiveSupport::Reloader.to_complete { a = b = c = 1 }
+ ActiveSupport::Reloader.to_complete { b = c = 2 }
+ ActiveSupport::Reloader.to_prepare { c = 3 }
- # Hide Reloading... output
- silence_stream(STDOUT) { irb_context.reload! }
+ irb_context.reload!(false)
assert_equal 1, a
assert_equal 2, b
@@ -81,7 +80,7 @@ class ConsoleTest < ActiveSupport::TestCase
MODEL
assert !User.new.respond_to?(:age)
- silence_stream(STDOUT) { irb_context.reload! }
+ irb_context.reload!(false)
assert User.new.respond_to?(:age)
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 1434522cce..5869ff64bc 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -26,7 +26,7 @@ module ApplicationTests
assert_equal [
"Rack::Sendfile",
"ActionDispatch::Static",
- "ActionDispatch::LoadInterlock",
+ "ActionDispatch::Executor",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
"Rack::MethodOverride",
@@ -38,8 +38,6 @@ module ApplicationTests
"ActionDispatch::Reloader",
"ActionDispatch::Callbacks",
"ActiveRecord::Migration::CheckPending",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement",
- "ActiveRecord::QueryCache",
"ActionDispatch::Cookies",
"ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash",
@@ -57,7 +55,7 @@ module ApplicationTests
assert_equal [
"Rack::Sendfile",
"ActionDispatch::Static",
- "ActionDispatch::LoadInterlock",
+ "ActionDispatch::Executor",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
"ActionDispatch::RequestId",
@@ -67,8 +65,6 @@ module ApplicationTests
"ActionDispatch::RemoteIp",
"ActionDispatch::Reloader",
"ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement",
- "ActiveRecord::QueryCache",
"Rack::Head",
"Rack::ConditionalGet",
"Rack::ETag"
@@ -114,23 +110,12 @@ module ApplicationTests
test "removing Active Record omits its middleware" do
use_frameworks []
boot!
- assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement")
- assert !middleware.include?("ActiveRecord::QueryCache")
assert !middleware.include?("ActiveRecord::Migration::CheckPending")
end
- test "includes interlock if cache_classes is set but eager_load is not" do
- add_to_config "config.cache_classes = true"
- boot!
- assert_not_includes middleware, "Rack::Lock"
- assert_includes middleware, "ActionDispatch::LoadInterlock"
- end
-
- test "includes interlock if cache_classes is off" do
- add_to_config "config.cache_classes = false"
+ test "includes executor" do
boot!
- assert_not_includes middleware, "Rack::Lock"
- assert_includes middleware, "ActionDispatch::LoadInterlock"
+ assert_includes middleware, "ActionDispatch::Executor"
end
test "does not include lock if cache_classes is set and so is eager_load" do
@@ -138,21 +123,18 @@ module ApplicationTests
add_to_config "config.eager_load = true"
boot!
assert_not_includes middleware, "Rack::Lock"
- assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
test "does not include lock if allow_concurrency is set to :unsafe" do
add_to_config "config.allow_concurrency = :unsafe"
boot!
assert_not_includes middleware, "Rack::Lock"
- assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
test "includes lock if allow_concurrency is disabled" do
add_to_config "config.allow_concurrency = false"
boot!
assert_includes middleware, "Rack::Lock"
- assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
test "removes static asset server if public_file_server.enabled is disabled" do
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 92ae3edc08..dc3c4755c7 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -118,7 +118,7 @@ module ApplicationTests
end
def test_code_statistics_sanity
- assert_match "Code LOC: 16 Test LOC: 0 Code to Test Ratio: 1:0.0",
+ assert_match "Code LOC: 18 Test LOC: 0 Code to Test Ratio: 1:0.0",
Dir.chdir(app_path){ `bin/rails stats` }
end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 6158748194..58394a11f0 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -201,7 +201,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rake_should_run_rake_command_with_default_env
+ def test_rails_should_run_rake_command_with_default_env
assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rake, 'log:clear'
@@ -209,13 +209,13 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rake_with_env_option_should_run_rake_command_in_env
+ def test_rails_with_env_option_should_run_rake_command_in_env
assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do
action :rake, 'log:clear', env: 'production'
end
end
- def test_rake_with_rails_env_variable_should_run_rake_command_in_env
+ test "rails command with RAILS_ENV variable should run rake command in env" do
assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do
with_rails_env "production" do
action :rake, 'log:clear'
@@ -223,7 +223,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_env_option_should_win_over_rails_env_variable_when_running_rake
+ test "env option should win over RAILS_ENV variable when running rake" do
assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do
with_rails_env "staging" do
action :rake, 'log:clear', env: 'production'
@@ -231,7 +231,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rake_with_sudo_option_should_run_rake_command_with_sudo
+ test "rails command with sudo option should run rake command with sudo" do
assert_called_with(generator, :run, ["sudo rails log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rake, 'log:clear', sudo: true
@@ -239,7 +239,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rails_command_should_run_rails_command_with_default_env
+ test "rails command should run rails_command with default env" do
assert_called_with(generator, :run, ["rails log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rails_command, 'log:clear'
@@ -247,13 +247,13 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rails_command_with_env_option_should_run_rails_command_in_env
+ test "rails command with env option should run rails_command with same env" do
assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do
action :rails_command, 'log:clear', env: 'production'
end
end
- def test_rails_command_with_rails_env_variable_should_run_rails_command_in_env
+ test "rails command with RAILS_ENV variable should run rails_command in env" do
assert_called_with(generator, :run, ['rails log:clear RAILS_ENV=production', verbose: false]) do
with_rails_env "production" do
action :rails_command, 'log:clear'
@@ -269,7 +269,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rails_command_with_sudo_option_should_run_rails_command_with_sudo
+ test "rails command with sudo option should run rails_command with sudo" do
assert_called_with(generator, :run, ["sudo rails log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rails_command, 'log:clear', sudo: true
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 63655044da..11b795bc9f 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -28,6 +28,7 @@ DEFAULT_APP_FILES = %w(
config/locales
config/cable.yml
config/puma.rb
+ config/spring.rb
db
lib
lib/tasks
@@ -463,7 +464,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-action-cable"]
assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/
assert_no_file "config/cable.yml"
- assert_no_file "app/assets/javascripts/cable.coffee"
+ assert_no_file "app/assets/javascripts/cable.js"
assert_no_file "app/channels"
assert_file "Gemfile" do |content|
assert_no_match(/redis/, content)
@@ -669,7 +670,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_spring_no_fork
jruby_skip "spring doesn't run on JRuby"
- assert_called_with(Process, :respond_to?, [:fork], returns: false) do
+ assert_called_with(Process, :respond_to?, [[:fork], [:fork]], returns: false) do
run_generator
assert_file "Gemfile" do |content|
@@ -681,6 +682,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_skip_spring
run_generator [destination_root, "--skip-spring"]
+ assert_no_file 'config/spring.rb'
assert_file "Gemfile" do |content|
assert_no_match(/spring/, content)
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 66a8cf54af..f72218b65c 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -124,7 +124,7 @@ module TestHelpers
routes = File.read("#{app_path}/config/routes.rb")
if routes =~ /(\n\s*end\s*)\Z/
File.open("#{app_path}/config/routes.rb", 'w') do |f|
- f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)', via: :all\n" + $1
+ f.puts $` + "\nActiveSupport::Deprecation.silence { match ':controller(/:action(/:id))(.:format)', via: :all }\n" + $1
end
end