aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml3
-rw-r--r--README.md4
-rw-r--r--actioncable/test/connection/client_socket_test.rb1
-rw-r--r--actionpack/CHANGELOG.md10
-rw-r--r--actionpack/lib/abstract_controller/collector.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb12
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb2
-rw-r--r--actionpack/lib/action_controller/renderer.rb15
-rw-r--r--actionpack/lib/action_controller/test_case.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb18
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb31
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb80
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb22
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/request_encoder.rb2
-rw-r--r--actionpack/test/controller/integration_test.rb14
-rw-r--r--actionpack/test/controller/resources_test.rb1
-rw-r--r--actionpack/test/controller/routing_test.rb3
-rw-r--r--actionpack/test/controller/test_case_test.rb25
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb71
-rw-r--r--actionpack/test/dispatch/exception_wrapper_test.rb24
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb10
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb6
-rw-r--r--actionview/CHANGELOG.md11
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb1
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb2
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb4
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb17
-rw-r--r--actionview/test/template/url_helper_test.rb9
-rw-r--r--activejob/CHANGELOG.md7
-rw-r--r--activejob/lib/active_job/logging.rb10
-rw-r--r--activejob/lib/active_job/queue_adapters/inline_adapter.rb2
-rw-r--r--activejob/test/cases/logging_test.rb23
-rw-r--r--activemodel/CHANGELOG.md8
-rw-r--r--activemodel/lib/active_model/callbacks.rb16
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--activemodel/lib/active_model/secure_password.rb12
-rw-r--r--activemodel/test/cases/errors_test.rb6
-rw-r--r--activemodel/test/cases/secure_password_test.rb13
-rw-r--r--activemodel/test/models/user.rb4
-rw-r--r--activerecord/CHANGELOG.md15
-rw-r--r--activerecord/lib/active_record/associations.rb16
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb9
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb3
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/relation.rb6
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb1
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb18
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb7
-rw-r--r--activerecord/test/cases/arel/insert_manager_test.rb2
-rw-r--r--activerecord/test/cases/arel/select_manager_test.rb11
-rw-r--r--activerecord/test/cases/callbacks_test.rb27
-rw-r--r--activerecord/test/cases/defaults_test.rb30
-rw-r--r--activerecord/test/cases/finder_test.rb15
-rw-r--r--activerecord/test/cases/fixtures_test.rb169
-rw-r--r--activerecord/test/cases/inheritance_test.rb1
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb1
-rw-r--r--activerecord/test/cases/primary_keys_test.rb1
-rw-r--r--activerecord/test/cases/query_cache_test.rb1
-rw-r--r--activerecord/test/cases/quoting_test.rb26
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb38
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb5
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb673
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb233
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb340
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb132
-rw-r--r--activerecord/test/cases/transactions_test.rb1
-rw-r--r--activerecord/test/models/post.rb1
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb13
-rw-r--r--activerecord/test/schema/oracle_specific_schema.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb1
-rw-r--r--activestorage/CHANGELOG.md14
-rw-r--r--activestorage/app/controllers/active_storage/disk_controller.rb28
-rw-r--r--activestorage/app/jobs/active_storage/purge_job.rb3
-rw-r--r--activestorage/app/models/active_storage/attachment.rb18
-rw-r--r--activestorage/app/models/active_storage/blob.rb33
-rw-r--r--activestorage/db/migrate/20170806125915_create_active_storage_tables.rb1
-rw-r--r--activestorage/lib/active_storage/attached.rb29
-rw-r--r--activestorage/lib/active_storage/attached/changes.rb16
-rw-r--r--activestorage/lib/active_storage/attached/changes/create_many.rb46
-rw-r--r--activestorage/lib/active_storage/attached/changes/create_one.rb68
-rw-r--r--activestorage/lib/active_storage/attached/changes/create_one_of_many.rb10
-rw-r--r--activestorage/lib/active_storage/attached/changes/delete_many.rb23
-rw-r--r--activestorage/lib/active_storage/attached/changes/delete_one.rb19
-rw-r--r--activestorage/lib/active_storage/attached/macros.rb122
-rw-r--r--activestorage/lib/active_storage/attached/many.rb26
-rw-r--r--activestorage/lib/active_storage/attached/model.rb140
-rw-r--r--activestorage/lib/active_storage/attached/one.rb33
-rw-r--r--activestorage/lib/active_storage/engine.rb2
-rw-r--r--activestorage/lib/active_storage/reflection.rb4
-rw-r--r--activestorage/lib/active_storage/service/disk_service.rb8
-rw-r--r--activestorage/test/controllers/disk_controller_test.rb19
-rw-r--r--activestorage/test/jobs/purge_job_test.rb37
-rw-r--r--activestorage/test/models/attached/many_test.rb540
-rw-r--r--activestorage/test/models/attached/one_test.rb478
-rw-r--r--activestorage/test/models/attached_test.rb56
-rw-r--r--activestorage/test/models/attachments_test.rb459
-rw-r--r--activestorage/test/models/blob_test.rb30
-rw-r--r--activestorage/test/models/presence_validation_test.rb4
-rw-r--r--activestorage/test/models/reflection_test.rb33
-rw-r--r--activestorage/test/test_helper.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_query.rb7
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb12
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb14
-rw-r--r--activesupport/test/test_case_test.rb16
-rw-r--r--guides/assets/stylesheets/main.css11
-rw-r--r--guides/source/2_2_release_notes.md6
-rw-r--r--guides/source/2_3_release_notes.md4
-rw-r--r--guides/source/3_0_release_notes.md4
-rw-r--r--guides/source/3_1_release_notes.md2
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/4_0_release_notes.md4
-rw-r--r--guides/source/4_1_release_notes.md2
-rw-r--r--guides/source/4_2_release_notes.md6
-rw-r--r--guides/source/5_0_release_notes.md2
-rw-r--r--guides/source/5_1_release_notes.md2
-rw-r--r--guides/source/5_2_release_notes.md2
-rw-r--r--guides/source/_welcome.html.erb22
-rw-r--r--guides/source/action_cable_overview.md2
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailer_basics.md2
-rw-r--r--guides/source/action_view_overview.md2
-rw-r--r--guides/source/active_job_basics.md2
-rw-r--r--guides/source/active_model_basics.md28
-rw-r--r--guides/source/active_record_basics.md2
-rw-r--r--guides/source/active_record_callbacks.md10
-rw-r--r--guides/source/active_record_migrations.md2
-rw-r--r--guides/source/active_record_postgresql.md2
-rw-r--r--guides/source/active_record_querying.md8
-rw-r--r--guides/source/active_record_validations.md20
-rw-r--r--guides/source/active_storage_overview.md2
-rw-r--r--guides/source/active_support_core_extensions.md2
-rw-r--r--guides/source/active_support_instrumentation.md2
-rw-r--r--guides/source/api_app.md2
-rw-r--r--guides/source/api_documentation_guidelines.md2
-rw-r--r--guides/source/asset_pipeline.md2
-rw-r--r--guides/source/association_basics.md6
-rw-r--r--guides/source/autoloading_and_reloading_constants.md2
-rw-r--r--guides/source/caching_with_rails.md2
-rw-r--r--guides/source/command_line.md6
-rw-r--r--guides/source/configuring.md2
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md2
-rw-r--r--guides/source/debugging_rails_applications.md2
-rw-r--r--guides/source/development_dependencies_install.md2
-rw-r--r--guides/source/engines.md5
-rw-r--r--guides/source/form_helpers.md2
-rw-r--r--guides/source/generators.md2
-rw-r--r--guides/source/getting_started.md4
-rw-r--r--guides/source/i18n.md2
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/layout.html.erb2
-rw-r--r--guides/source/layouts_and_rendering.md2
-rw-r--r--guides/source/maintenance_policy.md2
-rw-r--r--guides/source/plugins.md6
-rw-r--r--guides/source/rails_application_templates.md2
-rw-r--r--guides/source/rails_on_rack.md2
-rw-r--r--guides/source/routing.md2
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md2
-rw-r--r--guides/source/security.md2
-rw-r--r--guides/source/testing.md2
-rw-r--r--guides/source/threading_and_code_execution.md2
-rw-r--r--guides/source/upgrading_ruby_on_rails.md4
-rw-r--r--guides/source/working_with_javascript_in_rails.md2
-rw-r--r--railties/CHANGELOG.md19
-rw-r--r--railties/RDOC_MAIN.rdoc6
-rw-r--r--railties/lib/rails/commands/notes/notes_command.rb2
-rw-r--r--railties/lib/rails/commands/server/server_command.rb47
-rw-r--r--railties/lib/rails/generators.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/locales/en.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt2
-rw-r--r--railties/lib/rails/tasks/log.rake1
-rw-r--r--railties/test/application/rake/notes_test.rb114
-rw-r--r--railties/test/commands/server_test.rb17
-rw-r--r--railties/test/railties/engine_test.rb1
205 files changed, 3482 insertions, 1785 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 7327f1e631..3e3b963a47 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -52,6 +52,9 @@ Layout/EndAlignment:
Layout/EmptyLineAfterMagicComment:
Enabled: true
+Layout/EmptyLinesAroundBlockBody:
+ Enabled: true
+
# In a regular class definition, no empty lines around the body.
Layout/EmptyLinesAroundClassBody:
Enabled: true
diff --git a/README.md b/README.md
index c7ecaf15cf..bcf3e71a3b 100644
--- a/README.md
+++ b/README.md
@@ -77,8 +77,8 @@ and may also be used independently outside Rails.
5. Follow the guidelines to start developing your application. You may find
the following resources handy:
- * [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html)
- * [Ruby on Rails Guides](http://guides.rubyonrails.org)
+ * [Getting Started with Rails](https://guides.rubyonrails.org/getting_started.html)
+ * [Ruby on Rails Guides](https://guides.rubyonrails.org)
* [The API Documentation](http://api.rubyonrails.org)
* [Ruby on Rails Tutorial](https://www.railstutorial.org/book)
diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb
index 9176c7ac8b..a7db32c3e4 100644
--- a/actioncable/test/connection/client_socket_test.rb
+++ b/actioncable/test/connection/client_socket_test.rb
@@ -41,7 +41,6 @@ class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase
# Internal hax = :(
client = connection.websocket.send(:websocket)
client.instance_variable_get("@stream").stub(:write, proc { raise "foo" }) do
-
assert_not_called(client, :client_gone) do
client.write("boo")
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 7645b2b0e7..f7e9104627 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,13 @@
+* `ActionDispatch::Http::UploadedFile` now delegates `to_path` to its tempfile.
+
+ This allows uploaded file objects to be passed directly to `File.read`
+ without raising a `TypeError`:
+
+ uploaded_file = ActionDispatch::Http::UploadedFile.new(tempfile: tmp_file)
+ File.read(uploaded_file)
+
+ *Aaron Kromer*
+
* Pass along arguments to underlying `get` method in `follow_redirect!`
Now all arguments passed to `follow_redirect!` are passed to the underlying
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index 297ec5ca40..d4a078ab32 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -26,7 +26,7 @@ module AbstractController
def method_missing(symbol, &block)
unless mime_constant = Mime[symbol]
raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \
- "http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
+ "https://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
"If you meant to respond to a variant like :tablet or :phone, not a custom format, " \
"be sure to nest your variant response within a format response: " \
"format.html { |html| html.tablet { ... } }"
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 01676f3237..a871ccd533 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -56,8 +56,9 @@ module ActionController
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
- # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
- # get "/notes/1.xml"
+ # authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
+ #
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
#
# assert_equal 200, status
# end
@@ -389,10 +390,9 @@ module ActionController
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
- # get(
- # "/notes/1.xml", nil,
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
- # )
+ # authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
+ #
+ # get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
#
# assert_equal 200, status
# end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 4bae795438..edfef39771 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -54,7 +54,7 @@ module ActionController #:nodoc:
# <tt>csrf_meta_tags</tt> in the HTML +head+.
#
# Learn more about CSRF attacks and securing your application in the
- # {Ruby on Rails Security Guide}[http://guides.rubyonrails.org/security.html].
+ # {Ruby on Rails Security Guide}[https://guides.rubyonrails.org/security.html].
module RequestForgeryProtection
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
index 49c5b782f0..2d1523f0fc 100644
--- a/actionpack/lib/action_controller/renderer.rb
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -71,6 +71,21 @@ module ActionController
end
# Render templates with any options from ActionController::Base#render_to_string.
+ #
+ # The primary options are:
+ # * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt> for details.
+ # * <tt>:file</tt> - Renders an explicit template file. Add <tt>:locals</tt> to pass in, if so desired.
+ # It shouldn’t be used directly with unsanitized user input due to lack of validation.
+ # * <tt>:inline</tt> - Renders a ERB template string.
+ # * <tt>:plain</tt> - Renders provided text and sets the content type as <tt>text/plain</tt>.
+ # * <tt>:html</tt> - Renders the provided HTML safe string, otherwise
+ # performs HTML escape on the string first. Sets the content type as <tt>text/html</tt>.
+ # * <tt>:json</tt> - Renders the provided hash or object in JSON. You don't
+ # need to call <tt>.to_json<tt> on the object you want to render.
+ # * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>.
+ #
+ # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, the default is
+ # to render a partial and use the second parameter as the locals hash.
def render(*args)
raise "missing controller" unless controller
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 33b0bcbefe..5d784ceb31 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -109,7 +109,7 @@ module ActionController
when :xml
data = non_path_parameters.to_xml
when :url_encoded_form
- data = Rack::Utils.build_nested_query(non_path_parameters)
+ data = non_path_parameters.to_query
else
@custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
data = non_path_parameters.to_query
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 0b162dc7f1..827f022ca2 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -65,6 +65,11 @@ module ActionDispatch
@tempfile.path
end
+ # Shortcut for +tempfile.to_path+.
+ def to_path
+ @tempfile.to_path
+ end
+
# Shortcut for +tempfile.rewind+.
def rewind
@tempfile.rewind
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 33edad8bd9..077a83b112 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -152,23 +152,13 @@ module ActionDispatch
end
def create_template(request, wrapper)
- traces = wrapper.traces
-
- trace_to_show = "Application Trace"
- if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error"
- trace_to_show = "Full Trace"
- end
-
- if source_to_show = traces[trace_to_show].first
- source_to_show_id = source_to_show[:id]
- end
-
DebugView.new([RESCUES_TEMPLATE_PATH],
request: request,
+ exception_wrapper: wrapper,
exception: wrapper.exception,
- traces: traces,
- show_source_idx: source_to_show_id,
- trace_to_show: trace_to_show,
+ traces: wrapper.traces,
+ show_source_idx: wrapper.source_to_show_id,
+ trace_to_show: wrapper.trace_to_show,
routes_inspector: routes_inspector(wrapper.exception),
source_extracts: wrapper.source_extracts,
line_number: wrapper.line_number,
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index f05c69137b..fb2b2bd3b0 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -31,11 +31,12 @@ module ActionDispatch
"ActionController::MissingExactTemplate" => "missing_exact_template",
)
- attr_reader :backtrace_cleaner, :exception, :line_number, :file
+ attr_reader :backtrace_cleaner, :exception, :wrapped_causes, :line_number, :file
def initialize(backtrace_cleaner, exception)
@backtrace_cleaner = backtrace_cleaner
@exception = original_exception(exception)
+ @wrapped_causes = wrapped_causes_for(exception, backtrace_cleaner)
expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError)
end
@@ -66,7 +67,11 @@ module ActionDispatch
full_trace_with_ids = []
full_trace.each_with_index do |trace, idx|
- trace_with_id = { id: idx, trace: trace }
+ trace_with_id = {
+ exception_object_id: @exception.object_id,
+ id: idx,
+ trace: trace
+ }
if application_trace.include?(trace)
application_trace_with_ids << trace_with_id
@@ -99,6 +104,18 @@ module ActionDispatch
end
end
+ def trace_to_show
+ if traces["Application Trace"].empty? && rescue_template != "routing_error"
+ "Full Trace"
+ else
+ "Application Trace"
+ end
+ end
+
+ def source_to_show_id
+ (traces[trace_to_show].first || {})[:id]
+ end
+
private
def backtrace
@@ -113,6 +130,16 @@ module ActionDispatch
end
end
+ def causes_for(exception)
+ return enum_for(__method__, exception) unless block_given?
+
+ yield exception while exception = exception.cause
+ end
+
+ def wrapped_causes_for(exception, backtrace_cleaner)
+ causes_for(exception).map { |cause| self.class.new(backtrace_cleaner, cause) }
+ end
+
def clean_backtrace(*args)
if backtrace_cleaner
backtrace_cleaner.clean(backtrace, *args)
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 8130bfe2e7..277074f216 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -41,7 +41,6 @@ module ActionDispatch
rescue SystemCallError
false
end
-
}
return ::Rack::Utils.escape_path(match).b
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
index e7b913bbe4..88a8e6ad83 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
@@ -1,6 +1,8 @@
-<% @source_extracts.each_with_index do |source_extract, index| %>
+<% error_index = local_assigns[:error_index] || 0 %>
+
+<% source_extracts.each_with_index do |source_extract, index| %>
<% if source_extract[:code] %>
- <div class="source <%="hidden" if @show_source_idx != index%>" id="frame-source-<%=index%>">
+ <div class="source <%= "hidden" if show_source_idx != index %>" id="frame-source-<%= error_index %>-<%= index %>">
<div class="info">
Extracted source (around line <strong>#<%= source_extract[:line_number] %></strong>):
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
index ab57b11c7d..835ca8d260 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
@@ -1,52 +1,62 @@
-<% names = @traces.keys %>
+<% names = traces.keys %>
+<% error_index = local_assigns[:error_index] || 0 %>
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
-<div id="traces">
+<div id="traces-<%= error_index %>">
<% names.each do |name| %>
<%
- show = "show('#{name.gsub(/\s/, '-')}');"
- hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
+ show = "show('#{name.gsub(/\s/, '-')}-#{error_index}');"
+ hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}-#{error_index}');"}
%>
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
<% end %>
- <% @traces.each do |name, trace| %>
- <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == @trace_to_show) ? 'block' : 'none' %>;">
- <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
+ <% traces.each do |name, trace| %>
+ <div id="<%= "#{name.gsub(/\s/, '-')}-#{error_index}" %>" style="display: <%= (name == trace_to_show) ? 'block' : 'none' %>;">
+ <code style="font-size: 11px;">
+ <% trace.each do |frame| %>
+ <a class="trace-frames trace-frames-<%= error_index %>" data-exception-object-id="<%= frame[:exception_object_id] %>" data-frame-id="<%= frame[:id] %>" href="#">
+ <%= frame[:trace] %>
+ </a>
+ <br>
+ <% end %>
+ </code>
</div>
<% end %>
<script type="text/javascript">
- var traceFrames = document.getElementsByClassName('trace-frames');
- var selectedFrame, currentSource = document.getElementById('frame-source-0');
-
- // Add click listeners for all stack frames
- for (var i = 0; i < traceFrames.length; i++) {
- traceFrames[i].addEventListener('click', function(e) {
- e.preventDefault();
- var target = e.target;
- var frame_id = target.dataset.frameId;
-
- if (selectedFrame) {
- selectedFrame.className = selectedFrame.className.replace("selected", "");
- }
-
- target.className += " selected";
- selectedFrame = target;
-
- // Change the extracted source code
- changeSourceExtract(frame_id);
- });
-
- function changeSourceExtract(frame_id) {
- var el = document.getElementById('frame-source-' + frame_id);
- if (currentSource && el) {
- currentSource.className += " hidden";
- el.className = el.className.replace(" hidden", "");
- currentSource = el;
+ (function() {
+ var traceFrames = document.getElementsByClassName('trace-frames-<%= error_index %>');
+ var selectedFrame, currentSource = document.getElementById('frame-source-<%= error_index %>-0');
+
+ // Add click listeners for all stack frames
+ for (var i = 0; i < traceFrames.length; i++) {
+ traceFrames[i].addEventListener('click', function(e) {
+ e.preventDefault();
+ var target = e.target;
+ var frame_id = target.dataset.frameId;
+
+ if (selectedFrame) {
+ selectedFrame.className = selectedFrame.className.replace("selected", "");
+ }
+
+ target.className += " selected";
+ selectedFrame = target;
+
+ // Change the extracted source code
+ changeSourceExtract(frame_id);
+ });
+
+ function changeSourceExtract(frame_id) {
+ var el = document.getElementById('frame-source-<%= error_index %>-' + frame_id);
+ if (currentSource && el) {
+ currentSource.className += " hidden";
+ el.className = el.className.replace(" hidden", "");
+ currentSource = el;
+ }
}
}
- }
+ })();
</script>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
index f154021ae6..bde26f46c2 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
@@ -10,7 +10,25 @@
<div id="container">
<h2><%= h @exception.message %></h2>
- <%= render template: "rescues/_source" %>
- <%= render template: "rescues/_trace" %>
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx, error_index: 0 %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show, error_index: 0 %>
+
+ <% if @exception.cause %>
+ <h2>Exception Causes</h2>
+ <% end %>
+
+ <% @exception_wrapper.wrapped_causes.each.with_index(1) do |wrapper, index| %>
+ <div class="details">
+ <a class="summary" href="#" style="color: #F0F0F0; text-decoration: none; background: #C52F24; border-bottom: none;" onclick="return toggle(<%= wrapper.exception.object_id %>)">
+ <%= wrapper.exception.class.name %>: <%= h wrapper.exception.message %>
+ </a>
+ </div>
+
+ <div id="<%= wrapper.exception.object_id %>" style="display: none;">
+ <%= render "rescues/source", source_extracts: wrapper.source_extracts, show_source_idx: wrapper.source_to_show_id, error_index: index %>
+ <%= render "rescues/trace", traces: wrapper.traces, trace_to_show: wrapper.trace_to_show, error_index: index %>
+ </div>
+ <% end %>
+
<%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
index 88d97cc28f..e8454acfad 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
@@ -15,7 +15,7 @@
<% end %>
</h2>
- <%= render template: "rescues/_source" %>
- <%= render template: "rescues/_trace" %>
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
<%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
index 2a65fd06ad..22eb6e9b4e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb
@@ -5,7 +5,7 @@
<div id="container">
<h2><%= h @exception.message %></h2>
- <%= render template: "rescues/_source" %>
- <%= render template: "rescues/_trace" %>
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
<%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
index 55dd5ddc7b..2b8f3f2a5e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb
@@ -14,7 +14,7 @@
</p>
<% end %>
- <%= render template: "rescues/_trace" %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
<% if @routes_inspector %>
<h2>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
index 5060da9369..324ef1567a 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
@@ -11,10 +11,10 @@
</p>
<pre><code><%= h @exception.message %></code></pre>
- <%= render template: "rescues/_source" %>
+ <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
<p><%= @exception.sub_template_message %></p>
- <%= render template: "rescues/_trace" %>
+ <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
<%= render template: "rescues/_request_and_response" %>
</div>
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index bae50f6a43..cba49d1a0b 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -159,7 +159,7 @@ module ActionDispatch
"No routes were found for this grep pattern."
end
- @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ @buffer << "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
end
end
@@ -258,7 +258,7 @@ module ActionDispatch
<li>Please add some routes in <tt>config/routes.rb</tt>.</li>
<li>
For more information about routes, please see the Rails guide
- <a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
+ <a href="https://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
</li>
</ul>
MESSAGE
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index d9dd24935b..aeaae3c1dd 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -309,7 +309,7 @@ module ActionDispatch
hash = check_part(:controller, controller, path_params, {}) do |part|
translate_controller(part) {
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup
- message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
+ message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
raise ArgumentError, message
}
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 1134279a7f..07d3a41173 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -584,7 +584,7 @@ module ActionDispatch
"You may have defined two routes with the same name using the `:as` option, or " \
"you may be overriding a route already defined by a resource with the same naming. " \
"For the latter, you can restrict the routes created with `resources` as explained here: \n" \
- "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
+ "https://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
end
route = @set.add_route(name, mapping)
diff --git a/actionpack/lib/action_dispatch/testing/request_encoder.rb b/actionpack/lib/action_dispatch/testing/request_encoder.rb
index 01246b7a2e..9889f61951 100644
--- a/actionpack/lib/action_dispatch/testing/request_encoder.rb
+++ b/actionpack/lib/action_dispatch/testing/request_encoder.rb
@@ -34,7 +34,7 @@ module ActionDispatch
end
def encode_params(params)
- @param_encoder.call(params)
+ @param_encoder.call(params) if params
end
def self.parser(content_type)
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 41812a82e1..39ede1442a 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -1079,6 +1079,20 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
end
end
+ def test_get_request_with_json_excludes_null_query_string
+ with_routing do |routes|
+ routes.draw do
+ ActiveSupport::Deprecation.silence do
+ get ":action" => FooController
+ end
+ end
+
+ get "/foos_json", as: :json
+
+ assert_equal "http://www.example.com/foos_json", request.url
+ end
+ end
+
private
def post_to_foos(as:)
with_routing do |routes|
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 30bea64c55..3688fdbeee 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -66,7 +66,6 @@ class ResourcesTest < ActionController::TestCase
member_methods.each_key do |action|
assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", action: action, id: "1"
end
-
end
end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 9d0a8b4f00..a7033b2d30 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -937,7 +937,6 @@ class RouteSetTest < ActiveSupport::TestCase
@default_route_set ||= begin
set = ActionDispatch::Routing::RouteSet.new
set.draw do
-
ActiveSupport::Deprecation.silence do
get "/:controller(/:action(/:id))"
end
@@ -1342,11 +1341,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_namespace
set.draw do
-
namespace "api" do
get "inventory" => "products#inventory"
end
-
end
params = request_path_params("/api/inventory", method: :get)
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 663832e9bb..dda2686a9b 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -220,7 +220,7 @@ XML
params = Hash[:page, { name: "page name" }, "some key", 123]
post :render_raw_post, params: params.dup
- assert_equal Rack::Utils.build_nested_query(params), @response.body
+ assert_equal params.to_query, @response.body
end
def test_params_round_trip
@@ -231,12 +231,25 @@ XML
assert_equal params.merge(controller_info), JSON.parse(@response.body)
end
+ def test_handle_to_params
+ klass = Class.new do
+ def to_param
+ "bar"
+ end
+ end
+
+ post :test_params, params: { foo: klass.new }
+
+ assert_equal JSON.parse(@response.body)["foo"], "bar"
+ end
+
+
def test_body_stream
params = Hash[:page, { name: "page name" }, "some key", 123]
post :render_body, params: params.dup
- assert_equal Rack::Utils.build_nested_query(params), @response.body
+ assert_equal params.to_query, @response.body
end
def test_document_body_and_params_with_post
@@ -388,7 +401,13 @@ XML
process :test_xml_output, params: { response_as: "text/html" }
# <area> auto-closes, so the <p> becomes a sibling
- assert_select "root > area + p"
+ if defined?(JRUBY_VERSION)
+ # https://github.com/sparklemotion/nokogiri/issues/1653
+ # HTML parser "fixes" "broken" markup in slightly different ways
+ assert_select "root > map > area + p"
+ else
+ assert_select "root > area + p"
+ end
end
def test_should_not_impose_childless_html_tags_in_xml
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 045567ff83..44b79c0e5d 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -26,6 +26,18 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
raise StandardError.new "error in framework"
end
+ def raise_nested_exceptions
+ begin
+ raise "First error"
+ rescue
+ begin
+ raise "Second error"
+ rescue
+ raise "Third error"
+ end
+ end
+ end
+
def call(env)
env["action_dispatch.show_detailed_exceptions"] = @detailed
req = ActionDispatch::Request.new(env)
@@ -74,6 +86,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
when %r{/framework_raises}
method_that_raises
+ when %r{/nested_exceptions}
+ raise_nested_exceptions
else
raise "puke!"
end
@@ -440,8 +454,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/original_syntax_error", headers: { "action_dispatch.backtrace_cleaner" => ActiveSupport::BacktraceCleaner.new }
assert_response 500
- assert_select "#Application-Trace" do
- assert_select "pre code", /syntax error, unexpected/
+ assert_select "#Application-Trace-0" do
+ assert_select "code", /syntax error, unexpected/
end
end
@@ -454,9 +468,9 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_select "#container h2", /^Missing template/
- assert_select "#Application-Trace"
- assert_select "#Framework-Trace"
- assert_select "#Full-Trace"
+ assert_select "#Application-Trace-0"
+ assert_select "#Framework-Trace-0"
+ assert_select "#Full-Trace-0"
assert_select "h2", /Request/
end
@@ -467,8 +481,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/syntax_error_into_view", headers: { "action_dispatch.backtrace_cleaner" => ActiveSupport::BacktraceCleaner.new }
assert_response 500
- assert_select "#Application-Trace" do
- assert_select "pre code", /syntax error, unexpected/
+ assert_select "#Application-Trace-0" do
+ assert_select "code", /syntax error, unexpected/
end
end
@@ -497,13 +511,13 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
# assert application trace refers to line that calls method_that_raises is first
- assert_select "#Application-Trace" do
- assert_select "pre code a:first", %r{test/dispatch/debug_exceptions_test\.rb:\d+:in `call}
+ assert_select "#Application-Trace-0" do
+ assert_select "code a:first", %r{test/dispatch/debug_exceptions_test\.rb:\d+:in `call}
end
# assert framework trace that threw the error is first
- assert_select "#Framework-Trace" do
- assert_select "pre code a:first", /method_that_raises/
+ assert_select "#Framework-Trace-0" do
+ assert_select "code a:first", /method_that_raises/
end
end
end
@@ -523,4 +537,39 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_response 500
assert_match(/puke/, body)
end
+
+ test "debug exceptions app shows all the nested exceptions in source view" do
+ @app = DevelopmentApp
+ Rails.stub :root, Pathname.new(".") do
+ cleaner = ActiveSupport::BacktraceCleaner.new.tap do |bc|
+ bc.add_silencer { |line| line !~ %r{test/dispatch/debug_exceptions_test.rb} }
+ end
+
+ get "/nested_exceptions", headers: { "action_dispatch.backtrace_cleaner" => cleaner }
+
+ # Assert correct error
+ assert_response 500
+ assert_select "h2", /Third error/
+
+ # assert source view line shows the last error
+ assert_select "div.source:not(.hidden)" do
+ assert_select "pre .line.active", /raise "Third error"/
+ end
+
+ # assert application trace refers to line that raises the last exception
+ assert_select "#Application-Trace-0" do
+ assert_select "code a:first", %r{in `rescue in rescue in raise_nested_exceptions'}
+ end
+
+ # assert the second application trace refers to the line that raises the second exception
+ assert_select "#Application-Trace-1" do
+ assert_select "code a:first", %r{in `rescue in raise_nested_exceptions'}
+ end
+
+ # assert the third application trace refers to the line that raises the first exception
+ assert_select "#Application-Trace-2" do
+ assert_select "code a:first", %r{in `raise_nested_exceptions'}
+ end
+ end
+ end
end
diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb
index f6e70382a8..600280d6b3 100644
--- a/actionpack/test/dispatch/exception_wrapper_test.rb
+++ b/actionpack/test/dispatch/exception_wrapper_test.rb
@@ -108,11 +108,27 @@ module ActionDispatch
wrapper = ExceptionWrapper.new(@cleaner, exception)
assert_equal({
- "Application Trace" => [ id: 0, trace: "lib/file.rb:42:in `index'" ],
- "Framework Trace" => [ id: 1, trace: "/gems/rack.rb:43:in `index'" ],
+ "Application Trace" => [
+ exception_object_id: exception.object_id,
+ id: 0,
+ trace: "lib/file.rb:42:in `index'"
+ ],
+ "Framework Trace" => [
+ exception_object_id: exception.object_id,
+ id: 1,
+ trace: "/gems/rack.rb:43:in `index'"
+ ],
"Full Trace" => [
- { id: 0, trace: "lib/file.rb:42:in `index'" },
- { id: 1, trace: "/gems/rack.rb:43:in `index'" }
+ {
+ exception_object_id: exception.object_id,
+ id: 0,
+ trace: "lib/file.rb:42:in `index'"
+ },
+ {
+ exception_object_id: exception.object_id,
+ id: 1,
+ trace: "/gems/rack.rb:43:in `index'"
+ }
]
}, wrapper.traces)
end
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index 9150d5010b..f1f6547889 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -368,7 +368,7 @@ module ActionDispatch
assert_equal [
"No routes were found for this grep pattern.",
- "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
], output
end
@@ -380,7 +380,7 @@ module ActionDispatch
"",
"Please add some routes in config/routes.rb.",
"",
- "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
], output
end
@@ -434,7 +434,7 @@ module ActionDispatch
assert_equal [
"No routes were found for this controller.",
- "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
], output
end
@@ -445,7 +445,7 @@ module ActionDispatch
assert_equal [
"No routes were found for this grep pattern.",
- "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
], output
end
@@ -457,7 +457,7 @@ module ActionDispatch
"",
"Please add some routes in config/routes.rb.",
"",
- "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html."
+ "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
], output
end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 5a584b12e5..21169fcb5c 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -103,6 +103,12 @@ module ActionDispatch
assert_predicate uf, :eof?
end
+ def test_delegate_to_path_to_tempfile
+ tf = Class.new { def to_path; "/any/file/path" end; }
+ uf = Http::UploadedFile.new(tempfile: tf.new)
+ assert_equal "/any/file/path", uf.to_path
+ end
+
def test_respond_to?
tf = Class.new { def read; yield end }
uf = Http::UploadedFile.new(tempfile: tf.new)
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 7d3ca7735e..6d45cc1d8a 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,14 @@
+* Fix issue with `button_to`'s `to_form_params`
+
+ `button_to` was throwing exception when invoked with `params` hash that
+ contains symbol and string keys. The reason for the exception was that
+ `to_form_params` was comparing the given symbol and string keys.
+
+ The issue is fixed by turning all keys to strings inside
+ `to_form_params` before comparing them.
+
+ *Georgi Georgiev*
+
* Mark arrays of translations as trusted safe by using the `_html` suffix.
Example:
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index ae993f9aa2..4de4fafde0 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -672,8 +672,6 @@ module ActionView
# <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
# time_tag Date.yesterday, 'Yesterday' # =>
# <time datetime="2010-11-03">Yesterday</time>
- # time_tag Date.today, pubdate: true # =>
- # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
# time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # =>
# <time datetime="2010-W44">November 04, 2010</time>
#
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 2d5c5684c1..07f3d98322 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1658,6 +1658,7 @@ module ActionView
@nested_child_index = {}
@object_name, @object, @template, @options = object_name, object, template, options
@default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
+ @default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object)
convert_to_legacy_options(@options)
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index d02f641867..7884a8d997 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -820,7 +820,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def select(method, choices = nil, options = {}, html_options = {}, &block)
- @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options), &block)
+ @template.select(@object_name, method, choices, objectify_options(options), @default_html_options.merge(html_options), &block)
end
# Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders:
@@ -832,7 +832,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
- @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options))
end
# Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders:
@@ -844,7 +844,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
- @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options))
+ @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_html_options.merge(html_options))
end
# Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders:
@@ -856,7 +856,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
- @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
+ @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_html_options.merge(html_options))
end
# Wraps ActionView::Helpers::FormOptionsHelper#collection_check_boxes for form builders:
@@ -868,7 +868,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
- @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block)
+ @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
end
# Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders:
@@ -880,7 +880,7 @@ module ActionView
#
# Please refer to the documentation of the base helper for details.
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
- @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options), &block)
+ @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
end
end
end
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 275a2dffb4..cb0c99c4cf 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -126,7 +126,7 @@ module ActionView
attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
# Vendors the full, link and white list sanitizers.
- # Provided strictly for compatibility and can be removed in Rails 5.1.
+ # Provided strictly for compatibility and can be removed in Rails 6.
def sanitizer_vendor
Rails::Html::Sanitizer
end
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 345484ba92..790721a0b7 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -8,7 +8,7 @@ module ActionView
@choices = block_given? ? template_object.capture { yield || "" } : choices
@choices = @choices.to_a if @choices.is_a?(Range)
- @html_options = html_options.except(:skip_default_ids, :allow_method_names_outside_object)
+ @html_options = html_options
super(object_name, method_name, template_object, options)
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index cae62f2312..52bffaab84 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -634,7 +634,7 @@ module ActionView
# suitable for use as the names and values of form input fields:
#
# to_form_params(name: 'David', nationality: 'Danish')
- # # => [{name: :name, value: 'David'}, {name: 'nationality', value: 'Danish'}]
+ # # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
#
# to_form_params(country: { name: 'Denmark' })
# # => [{name: 'country[name]', value: 'Denmark'}]
@@ -666,7 +666,7 @@ module ActionView
params.push(*to_form_params(value, array_prefix))
end
else
- params << { name: namespace, value: attribute.to_param }
+ params << { name: namespace.to_s, value: attribute.to_param }
end
params.sort_by { |pair| pair[:name] }
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
index ed1683bad2..f07e494ee3 100644
--- a/actionview/test/template/form_helper/form_with_test.rb
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -464,6 +464,23 @@ class FormWithActsLikeFormForTest < FormWithTest
end
end
+ def test_form_with_with_collection_select
+ post = Post.new
+ def post.active; false; end
+ form_with(model: post) do |f|
+ concat f.collection_select(:active, [true, false], :to_s, :to_s)
+ end
+
+ expected = whole_form("/posts") do
+ "<select name='post[active]' id='post_active'>" \
+ "<option value='true'>true</option>\n" \
+ "<option selected='selected' value='false'>false</option>" \
+ "</select>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_with_with_collection_radio_buttons
post = Post.new
def post.active; false; end
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 08cb5dfea7..9d91dbb72b 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -77,11 +77,18 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_to_form_params_with_hash
assert_equal(
- [{ name: :name, value: "David" }, { name: :nationality, value: "Danish" }],
+ [{ name: "name", value: "David" }, { name: "nationality", value: "Danish" }],
to_form_params(name: "David", nationality: "Danish")
)
end
+ def test_to_form_params_with_hash_having_symbol_and_string_keys
+ assert_equal(
+ [{ name: "name", value: "David" }, { name: "nationality", value: "Danish" }],
+ to_form_params("name" => "David", :nationality => "Danish")
+ )
+ end
+
def test_to_form_params_with_nested_hash
assert_equal(
[{ name: "country[name]", value: "Denmark" }],
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 3ecbc2ff38..8526741383 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,10 @@
+* Move `enqueue`/`enqueue_at` notifications to an around callback.
+
+ Improves timing accuracy over the old after callback by including
+ time spent writing to the adapter's IO implementation.
+
+ *Zach Kemp*
+
* Allow `queue` option to `assert_no_enqueued_jobs`.
Example:
diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb
index 3312857ac7..9ffd60ad53 100644
--- a/activejob/lib/active_job/logging.rb
+++ b/activejob/lib/active_job/logging.rb
@@ -27,13 +27,13 @@ module ActiveJob
end
end
- after_enqueue do |job|
+ around_enqueue do |job, block|
if job.scheduled_at
- ActiveSupport::Notifications.instrument "enqueue_at.active_job",
- adapter: job.class.queue_adapter, job: job
+ ActiveSupport::Notifications.instrument("enqueue_at.active_job",
+ adapter: job.class.queue_adapter, job: job, &block)
else
- ActiveSupport::Notifications.instrument "enqueue.active_job",
- adapter: job.class.queue_adapter, job: job
+ ActiveSupport::Notifications.instrument("enqueue.active_job",
+ adapter: job.class.queue_adapter, job: job, &block)
end
end
end
diff --git a/activejob/lib/active_job/queue_adapters/inline_adapter.rb b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
index 3d0b590212..ca04dc943c 100644
--- a/activejob/lib/active_job/queue_adapters/inline_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/inline_adapter.rb
@@ -16,7 +16,7 @@ module ActiveJob
end
def enqueue_at(*) #:nodoc:
- raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html"
+ raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at https://guides.rubyonrails.org/active_job_basics.html"
end
end
end
diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb
index 1f8c4a5573..a1107a07fd 100644
--- a/activejob/test/cases/logging_test.rb
+++ b/activejob/test/cases/logging_test.rb
@@ -45,6 +45,14 @@ class LoggingTest < ActiveSupport::TestCase
ActiveJob::Base.logger = logger
end
+ def subscribed
+ [].tap do |events|
+ ActiveSupport::Notifications.subscribed(-> (*args) { events << args }, /enqueue.*\.active_job/) do
+ yield
+ end
+ end
+ end
+
def test_uses_active_job_as_tag
HelloJob.perform_later "Cristian"
assert_match(/\[ActiveJob\]/, @logger.messages)
@@ -86,8 +94,11 @@ class LoggingTest < ActiveSupport::TestCase
end
def test_enqueue_job_logging
- HelloJob.perform_later "Cristian"
+ events = subscribed { HelloJob.perform_later "Cristian" }
assert_match(/Enqueued HelloJob \(Job ID: .*?\) to .*?:.*Cristian/, @logger.messages)
+ assert_equal(events.count, 1)
+ key, * = events.first
+ assert_equal(key, "enqueue.active_job")
end
def test_perform_job_logging
@@ -110,15 +121,21 @@ class LoggingTest < ActiveSupport::TestCase
end
def test_enqueue_at_job_logging
- HelloJob.set(wait_until: 24.hours.from_now).perform_later "Cristian"
+ events = subscribed { HelloJob.set(wait_until: 24.hours.from_now).perform_later "Cristian" }
assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages)
+ assert_equal(events.count, 1)
+ key, * = events.first
+ assert_equal(key, "enqueue_at.active_job")
rescue NotImplementedError
skip
end
def test_enqueue_in_job_logging
- HelloJob.set(wait: 2.seconds).perform_later "Cristian"
+ events = subscribed { HelloJob.set(wait: 2.seconds).perform_later "Cristian" }
assert_match(/Enqueued HelloJob \(Job ID: .*\) to .*? at.*Cristian/, @logger.messages)
+ assert_equal(events.count, 1)
+ key, * = events.first
+ assert_equal(key, "enqueue_at.active_job")
rescue NotImplementedError
skip
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 1a464c2ffd..8f80838a33 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -6,13 +6,13 @@
Example:
class User < ActiveRecord::Base
- has_secure_password :activation_token, validations: false
+ has_secure_password :recovery_password, validations: false
end
user = User.new()
- user.activation_token = "a_new_token"
- user.activation_token_digest # => "$2a$10$0Budk0Fi/k2CDm2PEwa3Be..."
- user.authenticate_activation_token('a_new_token') # => user
+ user.recovery_password = "42password"
+ user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uX..."
+ user.authenticate_recovery_password('42password') # => user
*Unathi Chonco*
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 8fa9680cb1..fde3381df2 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -127,26 +127,28 @@ module ActiveModel
private
def _define_before_model_callback(klass, callback)
- klass.define_singleton_method("before_#{callback}") do |*args, &block|
- set_callback(:"#{callback}", :before, *args, &block)
+ klass.define_singleton_method("before_#{callback}") do |*args, **options, &block|
+ options.assert_valid_keys(:if, :unless, :prepend)
+ set_callback(:"#{callback}", :before, *args, options, &block)
end
end
def _define_around_model_callback(klass, callback)
- klass.define_singleton_method("around_#{callback}") do |*args, &block|
- set_callback(:"#{callback}", :around, *args, &block)
+ klass.define_singleton_method("around_#{callback}") do |*args, **options, &block|
+ options.assert_valid_keys(:if, :unless, :prepend)
+ set_callback(:"#{callback}", :around, *args, options, &block)
end
end
def _define_after_model_callback(klass, callback)
- klass.define_singleton_method("after_#{callback}") do |*args, &block|
- options = args.extract_options!
+ klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
+ options.assert_valid_keys(:if, :unless, :prepend)
options[:prepend] = true
conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
v != false
}
options[:if] = Array(options[:if]) << conditional
- set_callback(:"#{callback}", :after, *(args << options), &block)
+ set_callback(:"#{callback}", :after, *args, options, &block)
end
end
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 56404a036c..edc30ee64d 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -328,7 +328,7 @@ module ActiveModel
# person.errors.added? :name, "is too long" # => false
def added?(attribute, message = :invalid, options = {})
if message.is_a? Symbol
- self.details[attribute].map { |e| e[:error] }.include? message
+ self.details[attribute.to_sym].map { |e| e[:error] }.include? message
else
message = message.call if message.respond_to?(:call)
message = normalize_message(attribute, message, options)
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 7f3763fa56..51d54f34f3 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -17,7 +17,7 @@ module ActiveModel
module ClassMethods
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a +XXX_digest+ attribute.
- # Where +XXX+ is the attribute name of your desired password/token or defaults to +password+
+ # Where +XXX+ is the attribute name of your desired password.
#
# The following validations are added automatically:
# * Password must be present on creation
@@ -38,10 +38,10 @@ module ActiveModel
#
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
#
- # # Schema: User(name:string, password_digest:string, activation_token_digest:string)
+ # # Schema: User(name:string, password_digest:string, recovery_password_digest:string)
# class User < ActiveRecord::Base
# has_secure_password
- # has_secure_password :activation_token, validations: false
+ # has_secure_password :recovery_password, validations: false
# end
#
# user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
@@ -50,12 +50,12 @@ module ActiveModel
# user.save # => false, confirmation doesn't match
# user.password_confirmation = 'mUc3m00RsqyRe'
# user.save # => true
- # user.activation_token = "a_new_token"
- # user.activation_token_digest # => "$2a$10$0Budk0Fi/k2CDm2PEwa3BeXO5tPOA85b6xazE9rp8nF2MIJlsUik."
+ # user.recovery_password = "42password"
+ # user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
# user.save # => true
# user.authenticate('notright') # => false
# user.authenticate('mUc3m00RsqyRe') # => user
- # user.authenticate_activation_token('a_new_token') # => user
+ # user.authenticate_recovery_password('42password') # => user
# User.find_by(name: 'david').try(:authenticate, 'notright') # => false
# User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
def has_secure_password(attribute = :password, validations: true)
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 6ff3be1308..41ff6443fe 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -185,6 +185,12 @@ class ErrorsTest < ActiveModel::TestCase
assert person.errors.added?(:name, :blank)
end
+ test "added? returns true when string attribute is used with a symbol message" do
+ person = Person.new
+ person.errors.add(:name, :blank)
+ assert person.errors.added?("name", :blank)
+ end
+
test "added? handles proc messages" do
person = Person.new
message = Proc.new { "cannot be blank" }
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index bc23316ad5..9ef1148be8 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -186,13 +186,16 @@ class SecurePasswordTest < ActiveModel::TestCase
test "authenticate" do
@user.password = "secret"
- @user.activation_token = "new_token"
+ @user.recovery_password = "42password"
- assert_not @user.authenticate("wrong")
- assert @user.authenticate("secret")
+ assert_equal false, @user.authenticate("wrong")
+ assert_equal @user, @user.authenticate("secret")
- assert !@user.authenticate_activation_token("wrong")
- assert @user.authenticate_activation_token("new_token")
+ assert_equal false, @user.authenticate_password("wrong")
+ assert_equal @user, @user.authenticate_password("secret")
+
+ assert_equal false, @user.authenticate_recovery_password("wrong")
+ assert_equal @user, @user.authenticate_recovery_password("42password")
end
test "Password digest cost defaults to bcrypt default cost when min_cost is false" do
diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb
index 1ff3379153..bb1b187694 100644
--- a/activemodel/test/models/user.rb
+++ b/activemodel/test/models/user.rb
@@ -7,7 +7,7 @@ class User
define_model_callbacks :create
has_secure_password
- has_secure_password :activation_token, validations: false
+ has_secure_password :recovery_password, validations: false
- attr_accessor :password_digest, :activation_token_digest
+ attr_accessor :password_digest, :recovery_password_digest
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index fad09182c9..456f569718 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,18 @@
+* Don't impose primary key order if limit() has already been supplied.
+
+ Fixes #23607
+
+ *Brian Christian*
+
+* Add environment & load_config dependency to `bin/rake db:seed` to enable
+ seed load in environments without Rails and custom DB configuration
+
+ *Tobias Bielohlawek*
+
+* Fix default value for mysql time types with specified precision.
+
+ *Nikolay Kondratyev*
+
* Fix `touch` option to behave consistently with `Persistence#touch` method.
*Ryuta Kamizono*
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 3b581d6fe8..1ee52945ea 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1232,9 +1232,9 @@ module ActiveRecord
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>)
# * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
- # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
- # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
- # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
+ # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new(firm_id: id)</tt>)
+ # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new(firm_id: id); c.save; c</tt>)
+ # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new(firm_id: id); c.save!</tt>)
# * <tt>Firm#clients.reload</tt>
# The declaration can also include an +options+ hash to specialize the behavior of the association.
#
@@ -1405,9 +1405,9 @@ module ActiveRecord
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
# * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>)
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
- # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
- # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
- # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
+ # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new(account_id: id)</tt>)
+ # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new(account_id: id); b.save; b</tt>)
+ # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new(account_id: id); b.save!; b</tt>)
# * <tt>Account#reload_beneficiary</tt>
#
# === Scopes
@@ -1746,8 +1746,8 @@ module ActiveRecord
# * <tt>Developer#projects.size</tt>
# * <tt>Developer#projects.find(id)</tt>
# * <tt>Developer#projects.exists?(...)</tt>
- # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
- # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
+ # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new(developer_id: id)</tt>)
+ # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new(developer_id: id); c.save; c</tt>)
# * <tt>Developer#projects.reload</tt>
# The declaration may include an +options+ hash to specialize the behavior of the association.
#
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index d316db2145..b76005b587 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -14,10 +14,8 @@ module ActiveRecord
i[column.name] = column.alias
}
}
- @name_and_alias_cache = tables.each_with_object({}) { |table, h|
- h[table.node] = table.columns.map { |column|
- [column.name, column.alias]
- }
+ @columns_cache = tables.each_with_object({}) { |table, h|
+ h[table.node] = table.columns
}
end
@@ -25,9 +23,8 @@ module ActiveRecord
@tables.flat_map(&:column_aliases)
end
- # An array of [column_name, alias] pairs for the table
def column_aliases(node)
- @name_and_alias_cache[node]
+ @columns_cache[node]
end
def column_alias(node, column)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 3cabb21983..3ad72a3646 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -54,8 +54,8 @@ module ActiveRecord
length = column_names_with_alias.length
while index < length
- column_name, alias_name = column_names_with_alias[index]
- hash[column_name] = row[alias_name]
+ column = column_names_with_alias[index]
+ hash[column.name] = row[column.alias]
index += 1
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index aec5fa6ba1..98b1348135 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -130,6 +130,7 @@ module ActiveRecord
end
def quoted_time(value) # :nodoc:
+ value = value.change(year: 2000, month: 1, day: 1)
quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "")
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 199674f531..3be0906f2a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -741,22 +741,13 @@ module ActiveRecord
# ====== Creating an index with a specific operator class
#
# add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
- #
- # generates:
- #
- # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
+ # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
#
# add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
- #
- # generates:
- #
- # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
#
# add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
- #
- # generates:
- #
- # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
#
# Note: only supported by PostgreSQL
#
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 07acb5425e..284b38ed7b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -558,17 +558,6 @@ module ActiveRecord
@max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin)
end
- def with_multi_statements
- previous_flags = @config[:flags]
- @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
- reconnect!
-
- yield
- ensure
- @config[:flags] = previous_flags
- reconnect!
- end
-
def initialize_type_map(m = type_map)
super
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
index 4106ce01be..d89eeb7f54 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -62,6 +62,42 @@ module ActiveRecord
@connection.abandon_results!
end
+ def supports_set_server_option?
+ @connection.respond_to?(:set_server_option)
+ end
+
+ def multi_statements_enabled?(flags)
+ if flags.is_a?(Array)
+ flags.include?("MULTI_STATEMENTS")
+ else
+ (flags & Mysql2::Client::MULTI_STATEMENTS) != 0
+ end
+ end
+
+ def with_multi_statements
+ previous_flags = @config[:flags]
+
+ unless multi_statements_enabled?(previous_flags)
+ if supports_set_server_option?
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
+ else
+ @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
+ reconnect!
+ end
+ end
+
+ yield
+ ensure
+ unless multi_statements_enabled?(previous_flags)
+ if supports_set_server_option?
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
+ else
+ @config[:flags] = previous_flags
+ reconnect!
+ end
+ end
+ end
+
def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
# made since we established the connection
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index ce50590651..2087938d7c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -80,8 +80,8 @@ module ActiveRecord
def new_column_from_field(table_name, field)
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\(\))?\z/i.match?(field[:Default])
- default, default_function = nil, "CURRENT_TIMESTAMP"
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(field[:Default])
+ default, default_function = nil, field[:Default]
else
default, default_function = field[:Default], nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index 58e5138e02..24e7bc65fa 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -7,6 +7,10 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name)
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
+ # Indexes SQLite creates implicitly for internal use start with "sqlite_".
+ # See https://www.sqlite.org/fileformat2.html#intschema
+ next if row["name"].starts_with?("sqlite_")
+
index_sql = query_value(<<-SQL, "SCHEMA")
SELECT sql
FROM sqlite_master
@@ -40,7 +44,7 @@ module ActiveRecord
where: where,
orders: orders
)
- end
+ end.compact
end
def create_schema_dumper(options)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 844af952c1..bee74dc33d 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -457,9 +457,6 @@ module ActiveRecord
def copy_table_indexes(from, to, rename = {})
indexes(from).each do |index|
name = index.name
- # indexes sqlite creates for internal use start with `sqlite_` and
- # don't need to be copied
- next if name.starts_with?("sqlite_")
if to == "a#{from}"
name = "t#{name}"
elsif from == "a#{to}"
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 24449e8df3..8b7d18fb3d 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -217,7 +217,7 @@ db_namespace = namespace :db do
task setup: ["db:schema:load_if_ruby", "db:structure:load_if_sql", :seed]
desc "Loads the seed data from db/seeds.rb"
- task :seed do
+ task seed: :load_config do
db_namespace["abort_if_pending_migrations"].invoke
ActiveRecord::Tasks::DatabaseTasks.load_seed
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 7ab9bb2d2d..e7224658c4 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -217,7 +217,7 @@ module ActiveRecord
# are needed by the next ones when eager loading is going on.
#
# Please see further details in the
- # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
+ # {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
def explain
exec_explain(collecting_queries_for_explain { exec_queries })
end
@@ -309,10 +309,10 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping
- previous, klass.current_scope = klass.current_scope(true), self
+ previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass
yield
ensure
- klass.current_scope = previous
+ klass.current_scope = previous unless @delegate_to_klass
end
def _exec_scope(*args, &block) # :nodoc:
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index c5562c1ff0..93f3b67686 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -550,7 +550,7 @@ module ActiveRecord
end
def ordered_relation
- if order_values.empty? && primary_key
+ if order_values.empty? && primary_key && limit_value.blank?
order(arel_attribute(primary_key).asc)
else
self
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index b092399657..562e04194c 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -10,7 +10,6 @@ module ActiveRecord
def spawn #:nodoc:
clone
end
- alias :all :spawn
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 976c5dde58..6fc9df5083 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -157,15 +157,19 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
end
def test_indexes_in_create
- ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
- ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
+ assert_called_with(
+ ActiveRecord::Base.connection,
+ :data_source_exists?,
+ [:temp],
+ returns: false
+ ) do
+ expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) AS SELECT id, name, zip FROM a_really_complicated_query"
+ actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
+ t.index :zip
+ end
- expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) AS SELECT id, name, zip FROM a_really_complicated_query"
- actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
- t.index :zip
+ assert_equal expected, actual
end
-
- assert_equal expected, actual
end
private
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
index fa54f39992..00a075e063 100644
--- a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
@@ -45,9 +45,10 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
end
def stub_version(full_version_string)
- @connection.stubs(:full_version).returns(full_version_string)
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- yield
+ @connection.stub(:full_version, full_version_string) do
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ yield
+ end
ensure
@connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
end
diff --git a/activerecord/test/cases/arel/insert_manager_test.rb b/activerecord/test/cases/arel/insert_manager_test.rb
index ae10ccf56c..2376ad8d37 100644
--- a/activerecord/test/cases/arel/insert_manager_test.rb
+++ b/activerecord/test/cases/arel/insert_manager_test.rb
@@ -220,7 +220,6 @@ module Arel
end
describe "select" do
-
it "accepts a select query in place of a VALUES clause" do
table = Table.new :users
@@ -238,7 +237,6 @@ module Arel
INSERT INTO "users" ("id", "name") (SELECT 1, "aaron")
}
end
-
end
end
end
diff --git a/activerecord/test/cases/arel/select_manager_test.rb b/activerecord/test/cases/arel/select_manager_test.rb
index f318577b94..6b10d0b612 100644
--- a/activerecord/test/cases/arel/select_manager_test.rb
+++ b/activerecord/test/cases/arel/select_manager_test.rb
@@ -244,8 +244,6 @@ module Arel
@m2 = Arel::SelectManager.new table
@m2.project Arel.star
@m2.where(table[:age].gt(99))
-
-
end
it "should union two managers" do
@@ -266,7 +264,6 @@ module Arel
( SELECT * FROM "users" WHERE "users"."age" < 18 UNION ALL SELECT * FROM "users" WHERE "users"."age" > 99 )
}
end
-
end
describe "intersect" do
@@ -279,8 +276,6 @@ module Arel
@m2 = Arel::SelectManager.new table
@m2.project Arel.star
@m2.where(table[:age].lt(99))
-
-
end
it "should interect two managers" do
@@ -293,7 +288,6 @@ module Arel
( SELECT * FROM "users" WHERE "users"."age" > 18 INTERSECT SELECT * FROM "users" WHERE "users"."age" < 99 )
}
end
-
end
describe "except" do
@@ -318,7 +312,6 @@ module Arel
( SELECT * FROM "users" WHERE "users"."age" BETWEEN 18 AND 60 EXCEPT SELECT * FROM "users" WHERE "users"."age" BETWEEN 40 AND 99 )
}
end
-
end
describe "with" do
@@ -647,7 +640,6 @@ module Arel
end
describe "joins" do
-
it "returns inner join sql" do
table = Table.new :users
aliaz = table.alias
@@ -1002,7 +994,6 @@ module Arel
end
describe "update" do
-
it "creates an update statement" do
table = Table.new :users
manager = Arel::SelectManager.new
@@ -1075,7 +1066,6 @@ module Arel
UPDATE "users" SET "id" = 1 WHERE "users"."id" IN (SELECT "users"."id" FROM "users" WHERE "users"."foo" = 10 LIMIT 42)
}
end
-
end
describe "project" do
@@ -1097,7 +1087,6 @@ module Arel
manager.project "*"
manager.to_sql.must_be_like %{ SELECT * }
end
-
end
describe "projections" do
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index b9ba51c730..253c3099d6 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -476,4 +476,31 @@ class CallbacksTest < ActiveRecord::TestCase
child.save
assert child.after_save_called
end
+
+ def test_before_save_doesnt_allow_on_option
+ exception = assert_raises ArgumentError do
+ Class.new(ActiveRecord::Base) do
+ before_save(on: :create) {}
+ end
+ end
+ assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
+ end
+
+ def test_around_save_doesnt_allow_on_option
+ exception = assert_raises ArgumentError do
+ Class.new(ActiveRecord::Base) do
+ around_save(on: :create) {}
+ end
+ end
+ assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
+ end
+
+ def test_after_save_doesnt_allow_on_option
+ exception = assert_raises ArgumentError do
+ Class.new(ActiveRecord::Base) do
+ after_save(on: :create) {}
+ end
+ end
+ assert_equal "Unknown key: :on. Valid keys are: :if, :unless, :prepend", exception.message
+ end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 1c96aaabe2..0f957d41cf 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -106,21 +106,31 @@ if current_adapter?(:Mysql2Adapter)
class MysqlDefaultExpressionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
- if ActiveRecord::Base.connection.version >= "5.6.0"
+ if subsecond_precision_supported?
test "schema dump datetime includes default expression" do
output = dump_table_schema("datetime_defaults")
- assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
+ assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP(?:\(\))?" }/i, output
end
- end
- test "schema dump timestamp includes default expression" do
- output = dump_table_schema("timestamp_defaults")
- assert_match %r/t\.timestamp\s+"modified_timestamp",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
- end
+ test "schema dump datetime includes precise default expression" do
+ output = dump_table_schema("datetime_defaults")
+ assert_match %r/t\.datetime\s+"precise_datetime",.+default: -> { "CURRENT_TIMESTAMP\(6\)" }/i, output
+ end
- test "schema dump timestamp without default expression" do
- output = dump_table_schema("timestamp_defaults")
- assert_match %r/t\.timestamp\s+"nullable_timestamp"$/, output
+ test "schema dump timestamp includes default expression" do
+ output = dump_table_schema("timestamp_defaults")
+ assert_match %r/t\.timestamp\s+"modified_timestamp",\s+default: -> { "CURRENT_TIMESTAMP(?:\(\))?" }/i, output
+ end
+
+ test "schema dump timestamp includes precise default expression" do
+ output = dump_table_schema("timestamp_defaults")
+ assert_match %r/t\.timestamp\s+"precise_timestamp",.+default: -> { "CURRENT_TIMESTAMP\(6\)" }/i, output
+ end
+
+ test "schema dump timestamp without default expression" do
+ output = dump_table_schema("timestamp_defaults")
+ assert_match %r/t\.timestamp\s+"nullable_timestamp"$/, output
+ end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 39ffe12ec6..2f256dd36a 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -1265,6 +1265,21 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_first_and_last_with_limit_for_order_without_primary_key
+ # While Topic.first should impose an ordering by primary key,
+ # Topic.limit(n).first should not
+
+ Topic.first.touch # PostgreSQL changes the default order if no order clause is used
+
+ assert_equal Topic.limit(1).to_a.first, Topic.limit(1).first
+ assert_equal Topic.limit(2).to_a.first, Topic.limit(2).first
+ assert_equal Topic.limit(2).to_a.first(2), Topic.limit(2).first(2)
+
+ assert_equal Topic.limit(1).to_a.last, Topic.limit(1).last
+ assert_equal Topic.limit(2).to_a.last, Topic.limit(2).last
+ assert_equal Topic.limit(2).to_a.last(2), Topic.limit(2).last(2)
+ end
+
def test_finder_with_offset_string
assert_nothing_raised { Topic.offset("3").to_a }
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index ee88bd8144..c65523d8c1 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "cases/helper"
+require "support/connection_helper"
require "models/admin"
require "models/admin/account"
require "models/admin/randomly_named_c1"
@@ -32,6 +33,8 @@ require "models/treasure"
require "tempfile"
class FixturesTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
self.use_instantiated_fixtures = true
self.use_transactional_tests = false
@@ -114,9 +117,96 @@ class FixturesTest < ActiveRecord::TestCase
end
end
end
+
+ def test_bulk_insert_with_a_multi_statement_query_in_a_nested_transaction
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a"] },
+ ]
+ }
+
+ assert_difference "TrafficLight.count" do
+ ActiveRecord::Base.transaction do
+ conn = ActiveRecord::Base.connection
+ assert_equal 1, conn.open_transactions
+ conn.insert_fixtures_set(fixtures)
+ assert_equal 1, conn.open_transactions
+ end
+ end
+ end
end
if current_adapter?(:Mysql2Adapter)
+ def test_bulk_insert_with_multi_statements_enabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(flags: %w[MULTI_STATEMENTS])
+ )
+
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a"] },
+ ]
+ }
+
+ ActiveRecord::Base.connection.stub(:supports_set_server_option?, false) do
+ assert_nothing_raised do
+ conn = ActiveRecord::Base.connection
+ conn.execute("SELECT 1; SELECT 2;")
+ conn.raw_connection.abandon_results!
+ end
+
+ assert_difference "TrafficLight.count" do
+ ActiveRecord::Base.transaction do
+ conn = ActiveRecord::Base.connection
+ assert_equal 1, conn.open_transactions
+ conn.insert_fixtures_set(fixtures)
+ assert_equal 1, conn.open_transactions
+ end
+ end
+
+ assert_nothing_raised do
+ conn = ActiveRecord::Base.connection
+ conn.execute("SELECT 1; SELECT 2;")
+ conn.raw_connection.abandon_results!
+ end
+ end
+ end
+ end
+
+ def test_bulk_insert_with_multi_statements_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(
+ orig_connection.merge(flags: [])
+ )
+
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a"] },
+ ]
+ }
+
+ ActiveRecord::Base.connection.stub(:supports_set_server_option?, false) do
+ assert_raises(ActiveRecord::StatementInvalid) do
+ conn = ActiveRecord::Base.connection
+ conn.execute("SELECT 1; SELECT 2;")
+ conn.raw_connection.abandon_results!
+ end
+
+ assert_difference "TrafficLight.count" do
+ conn = ActiveRecord::Base.connection
+ conn.insert_fixtures_set(fixtures)
+ end
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ conn = ActiveRecord::Base.connection
+ conn.execute("SELECT 1; SELECT 2;")
+ conn.raw_connection.abandon_results!
+ end
+ end
+ end
+ end
+
def test_insert_fixtures_set_raises_an_error_when_max_allowed_packet_is_smaller_than_fixtures_set_size
conn = ActiveRecord::Base.connection
mysql_margin = 2
@@ -128,10 +218,10 @@ class FixturesTest < ActiveRecord::TestCase
]
}
- conn.stubs(:max_allowed_packet).returns(packet_size - mysql_margin)
-
- error = assert_raises(ActiveRecord::ActiveRecordError) { conn.insert_fixtures_set(fixtures) }
- assert_match(/Fixtures set is too large #{packet_size}\./, error.message)
+ conn.stub(:max_allowed_packet, packet_size - mysql_margin) do
+ error = assert_raises(ActiveRecord::ActiveRecordError) { conn.insert_fixtures_set(fixtures) }
+ assert_match(/Fixtures set is too large #{packet_size}\./, error.message)
+ end
end
def test_insert_fixture_set_when_max_allowed_packet_is_bigger_than_fixtures_set_size
@@ -143,10 +233,10 @@ class FixturesTest < ActiveRecord::TestCase
]
}
- conn.stubs(:max_allowed_packet).returns(packet_size)
-
- assert_difference "TrafficLight.count" do
- conn.insert_fixtures_set(fixtures)
+ conn.stub(:max_allowed_packet, packet_size) do
+ assert_difference "TrafficLight.count" do
+ conn.insert_fixtures_set(fixtures)
+ end
end
end
@@ -164,12 +254,13 @@ class FixturesTest < ActiveRecord::TestCase
]
}
- conn.stubs(:max_allowed_packet).returns(packet_size)
+ conn.stub(:max_allowed_packet, packet_size) do
+ conn.insert_fixtures_set(fixtures)
- conn.insert_fixtures_set(fixtures)
- assert_equal 2, subscriber.events.size
- assert_operator subscriber.events.first.bytesize, :<, packet_size
- assert_operator subscriber.events.second.bytesize, :<, packet_size
+ assert_equal 2, subscriber.events.size
+ assert_operator subscriber.events.first.bytesize, :<, packet_size
+ assert_operator subscriber.events.second.bytesize, :<, packet_size
+ end
ensure
ActiveSupport::Notifications.unsubscribe(subscription)
end
@@ -188,10 +279,10 @@ class FixturesTest < ActiveRecord::TestCase
]
}
- conn.stubs(:max_allowed_packet).returns(packet_size)
-
- assert_difference ["TrafficLight.count", "Comment.count"], +1 do
- conn.insert_fixtures_set(fixtures)
+ conn.stub(:max_allowed_packet, packet_size) do
+ assert_difference ["TrafficLight.count", "Comment.count"], +1 do
+ conn.insert_fixtures_set(fixtures)
+ end
end
assert_equal 1, subscriber.events.size
ensure
@@ -833,44 +924,58 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
self.use_instantiated_fixtures = false
def test_transaction_created_on_connection_notification
- connection = stub(transaction_open?: false)
- connection.expects(:begin_transaction).with(joinable: false)
- pool = connection.stubs(:pool).returns(ActiveRecord::ConnectionAdapters::ConnectionPool.new(ActiveRecord::Base.connection_pool.spec))
- pool.stubs(:lock_thread=).with(false)
- fire_connection_notification(connection)
+ connection = Class.new do
+ attr_accessor :pool
+
+ def transaction_open?; end
+ def begin_transaction(*args); end
+ def rollback_transaction(*args); end
+ end.new
+
+ connection.pool = Class.new do
+ def lock_thread=(lock_thread); end
+ end.new
+
+ assert_called_with(connection, :begin_transaction, [joinable: false]) do
+ fire_connection_notification(connection)
+ end
end
def test_notification_established_transactions_are_rolled_back
- # Mocha is not thread-safe so define our own stub to test
connection = Class.new do
attr_accessor :rollback_transaction_called
attr_accessor :pool
+
def transaction_open?; true; end
def begin_transaction(*args); end
def rollback_transaction(*args)
@rollback_transaction_called = true
end
end.new
+
connection.pool = Class.new do
- def lock_thread=(lock_thread); false; end
+ def lock_thread=(lock_thread); end
end.new
+
fire_connection_notification(connection)
teardown_fixtures
+
assert(connection.rollback_transaction_called, "Expected <mock connection>#rollback_transaction to be called but was not")
end
private
def fire_connection_notification(connection)
- ActiveRecord::Base.connection_handler.stubs(:retrieve_connection).with("book").returns(connection)
- message_bus = ActiveSupport::Notifications.instrumenter
- payload = {
- spec_name: "book",
- config: nil,
- connection_id: connection.object_id
- }
+ assert_called_with(ActiveRecord::Base.connection_handler, :retrieve_connection, ["book"], returns: connection) do
+ message_bus = ActiveSupport::Notifications.instrumenter
+ payload = {
+ spec_name: "book",
+ config: nil,
+ connection_id: connection.object_id
+ }
- message_bus.instrument("!connection.active_record", payload) {}
+ message_bus.instrument("!connection.active_record", payload) {}
+ end
end
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 4a0ad0442a..3d3189900f 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -91,7 +91,6 @@ class InheritanceTest < ActiveRecord::TestCase
end
ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise e }) do
-
exception = assert_raises NameError do
Company.send :compute_type, "InvalidModel"
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 32af90caef..ec01a2965d 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -669,7 +669,6 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
@child_1.stub(:id, "ABC1X") do
@child_2.stub(:id, "ABC2X") do
-
@pirate.attributes = {
association_getter => [
{ id: @child_1.id, name: "Grace OMalley" },
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 60dac91ec9..4ed7469039 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -305,6 +305,7 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
test "schema dump primary key includes type and options" do
schema = dump_table_schema "barcodes"
assert_match %r{create_table "barcodes", primary_key: "code", id: :string, limit: 42}, schema
+ assert_no_match %r{t\.index \["code"\]}, schema
end
if current_adapter?(:Mysql2Adapter) && subsecond_precision_supported?
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 1c05571f1b..69be091869 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -482,7 +482,6 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_not ActiveRecord::Base.connection.query_cache_enabled
}.join
}.call({})
-
end
end
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 92eb0c814f..f875dc96f7 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -71,8 +71,8 @@ module ActiveRecord
with_timezone_config default: :utc do
t = Time.now.change(usec: 0)
- expected = t.getutc.change(year: 2000, month: 1, day: 1)
- expected = expected.to_s(:db).sub("2000-01-01 ", "")
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getutc.to_s(:db).slice(11..-1)
assert_equal expected, @quoter.quoted_time(t)
end
@@ -89,6 +89,28 @@ module ActiveRecord
end
end
+ def test_quoted_time_dst_utc
+ with_timezone_config default: :utc do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getutc.to_s(:db).slice(11..-1)
+
+ assert_equal expected, @quoter.quoted_time(t)
+ end
+ end
+
+ def test_quoted_time_dst_local
+ with_timezone_config default: :local do
+ t = Time.new(2000, 7, 1, 0, 0, 0, "+04:30")
+
+ expected = t.change(year: 2000, month: 1, day: 1)
+ expected = expected.getlocal.to_s(:db).slice(11..-1)
+
+ assert_equal expected, @quoter.quoted_time(t)
+ end
+ end
+
def test_quoted_time_crazy
with_timezone_config default: :asdfasdf do
t = Time.now.change(usec: 0)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 15f4cea1a6..75b68b521c 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -297,29 +297,33 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_includes_extensions
connection = ActiveRecord::Base.connection
- connection.stubs(:extensions).returns(["hstore"])
- output = perform_schema_dump
- assert_match "# These are extensions that must be enabled", output
- assert_match %r{enable_extension "hstore"}, output
-
- connection.stubs(:extensions).returns([])
- output = perform_schema_dump
- assert_no_match "# These are extensions that must be enabled", output
- assert_no_match %r{enable_extension}, output
+ connection.stub(:extensions, ["hstore"]) do
+ output = perform_schema_dump
+ assert_match "# These are extensions that must be enabled", output
+ assert_match %r{enable_extension "hstore"}, output
+ end
+
+ connection.stub(:extensions, []) do
+ output = perform_schema_dump
+ assert_no_match "# These are extensions that must be enabled", output
+ assert_no_match %r{enable_extension}, output
+ end
end
def test_schema_dump_includes_extensions_in_alphabetic_order
connection = ActiveRecord::Base.connection
- connection.stubs(:extensions).returns(["hstore", "uuid-ossp", "xml2"])
- output = perform_schema_dump
- enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
- assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
+ connection.stub(:extensions, ["hstore", "uuid-ossp", "xml2"]) do
+ output = perform_schema_dump
+ enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
+ assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
+ end
- connection.stubs(:extensions).returns(["uuid-ossp", "xml2", "hstore"])
- output = perform_schema_dump
- enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
- assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
+ connection.stub(:extensions, ["uuid-ossp", "xml2", "hstore"]) do
+ output = perform_schema_dump
+ enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
+ assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
+ end
end
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index f18f1ed981..544adc9b39 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -254,6 +254,11 @@ class RelationScopingTest < ActiveRecord::TestCase
end
end
+ def test_scoping_works_in_the_scope_block
+ expected = SpecialPostWithDefaultScope.unscoped.to_a
+ assert_equal expected, SpecialPostWithDefaultScope.unscoped_all
+ end
+
def test_circular_joins_with_scoping_does_not_crash
posts = Post.joins(comments: :post).scoping do
Post.first(10)
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 60c7cb1bb4..b9cc08c446 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -6,10 +6,18 @@ require "active_record/tasks/database_tasks"
module ActiveRecord
module DatabaseTasksSetupper
def setup
- @mysql_tasks, @postgresql_tasks, @sqlite_tasks = stub, stub, stub
- ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
- ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).returns @postgresql_tasks
- ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+ @mysql_tasks, @postgresql_tasks, @sqlite_tasks = Array.new(
+ 3,
+ Class.new do
+ def create; end
+ def drop; end
+ def purge; end
+ def charset; end
+ def collation; end
+ def structure_dump(*); end
+ def structure_load(*); end
+ end.new
+ )
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
@@ -18,6 +26,16 @@ module ActiveRecord
def teardown
$stdout, $stderr = @original_stdout, @original_stderr
end
+
+ def with_stubbed_new
+ ActiveRecord::Tasks::MySQLDatabaseTasks.stub(:new, @mysql_tasks) do
+ ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stub(:new, @postgresql_tasks) do
+ ActiveRecord::Tasks::SQLiteDatabaseTasks.stub(:new, @sqlite_tasks) do
+ yield
+ end
+ end
+ end
+ end
end
ADAPTERS_TASKS = {
@@ -37,6 +55,7 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
ActiveRecord::Base.protected_environments = [current_env]
+
assert_raise(ActiveRecord::ProtectedEnvironmentError) do
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
end
@@ -62,11 +81,12 @@ module ActiveRecord
end
def test_raises_an_error_if_no_migrations_have_been_made
- ActiveRecord::InternalMetadata.stubs(:table_exists?).returns(false)
- ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
+ ActiveRecord::InternalMetadata.stub(:table_exists?, false) do
+ ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
- assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
- ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
end
end
end
@@ -79,11 +99,11 @@ module ActiveRecord
end
instance = klazz.new
- klazz.stubs(:new).returns instance
-
- assert_called_with(instance, :structure_dump, ["awesome-file.sql", nil]) do
- ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :foo }, "awesome-file.sql")
+ klazz.stub(:new, instance) do
+ assert_called_with(instance, :structure_dump, ["awesome-file.sql", nil]) do
+ ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz)
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :foo }, "awesome-file.sql")
+ end
end
end
@@ -99,8 +119,11 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_create") do
- eval("@#{v}").expects(:create)
- ActiveRecord::Tasks::DatabaseTasks.create "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create "adapter" => k
+ end
+ end
end
end
end
@@ -120,59 +143,89 @@ module ActiveRecord
def setup
@configurations = { "development" => { "database" => "my-db" } }
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
- # To refrain from connecting to a newly created empty DB in sqlite3_mem tests
- ActiveRecord::Base.connection_handler.stubs(:establish_connection)
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_ignores_configurations_without_databases
@configurations["development"].merge!("database" => nil)
- assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
end
end
def test_ignores_remote_databases
@configurations["development"].merge!("host" => "my.server.tld")
- $stderr.stubs(:puts).returns(nil)
- assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
end
end
def test_warning_for_remote_databases
@configurations["development"].merge!("host" => "my.server.tld")
- assert_called_with($stderr, :puts, ["This task only modifies local databases. my-db is on a remote host."]) do
+ with_stubbed_configurations_establish_connection do
ActiveRecord::Tasks::DatabaseTasks.create_all
+
+ assert_match "This task only modifies local databases. my-db is on a remote host.",
+ $stderr.string
end
end
def test_creates_configurations_with_local_ip
@configurations["development"].merge!("host" => "127.0.0.1")
- assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
end
end
def test_creates_configurations_with_local_host
@configurations["development"].merge!("host" => "localhost")
- assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
end
end
def test_creates_configurations_with_blank_hosts
@configurations["development"].merge!("host" => nil)
- assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
- ActiveRecord::Tasks::DatabaseTasks.create_all
+ with_stubbed_configurations_establish_connection do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :create) do
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
end
end
+
+ private
+
+ def with_stubbed_configurations_establish_connection
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ # To refrain from connecting to a newly created empty DB in
+ # sqlite3_mem tests
+ ActiveRecord::Base.connection_handler.stub(
+ :establish_connection,
+ nil
+ ) do
+ yield
+ end
+ end
+ end
end
class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
@@ -182,70 +235,94 @@ module ActiveRecord
"test" => { "database" => "test-db" },
"production" => { "url" => "prod-db-url" }
}
-
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_creates_current_environment_database
- assert_called_with(
- ActiveRecord::Tasks::DatabaseTasks,
- :create,
- ["database" => "test-db"],
- ) do
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("test")
- )
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ ["database" => "test-db"],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
end
end
def test_creates_current_environment_database_with_url
- assert_called_with(
- ActiveRecord::Tasks::DatabaseTasks,
- :create,
- ["url" => "prod-db-url"],
- ) do
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("production")
- )
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ ["url" => "prod-db-url"],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
end
end
def test_creates_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
end
def test_creates_test_and_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "test-db")
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ],
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
ensure
ENV["RAILS_ENV"] = old_env
end
def test_establishes_connection_for_the_given_environments
- ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true
+ ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do
+ assert_called_with(ActiveRecord::Base, :establish_connection, [:development]) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
- ActiveRecord::Base.expects(:establish_connection).with(:development)
+ private
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("development")
- )
- end
+ def with_stubbed_configurations_establish_connection
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ yield
+ end
+ end
+ end
end
class DatabaseTasksCreateCurrentThreeTierTest < ActiveRecord::TestCase
@@ -255,78 +332,108 @@ module ActiveRecord
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } }
}
-
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_creates_current_environment_database
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "secondary-test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("test")
- )
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
end
def test_creates_current_environment_database_with_url
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("url" => "prod-db-url")
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("url" => "secondary-prod-db-url")
-
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("production")
- )
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["url" => "prod-db-url"],
+ ["url" => "secondary-prod-db-url"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
end
def test_creates_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "secondary-dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "test-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "secondary-test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
end
def test_creates_test_and_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "secondary-dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "test-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:create).
- with("database" => "secondary-test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("development")
- )
+
+ with_stubbed_configurations_establish_connection do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :create,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
ensure
ENV["RAILS_ENV"] = old_env
end
def test_establishes_connection_for_the_given_environments_config
- ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true
+ ActiveRecord::Tasks::DatabaseTasks.stub(:create, nil) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [:development]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
+ end
- ActiveRecord::Base.expects(:establish_connection).with(:development)
+ private
- ActiveRecord::Tasks::DatabaseTasks.create_current(
- ActiveSupport::StringInquirer.new("development")
- )
- end
+ def with_stubbed_configurations_establish_connection
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ yield
+ end
+ end
+ end
end
class DatabaseTasksDropTest < ActiveRecord::TestCase
@@ -334,8 +441,11 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_drop") do
- eval("@#{v}").expects(:drop)
- ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop "adapter" => k
+ end
+ end
end
end
end
@@ -344,59 +454,72 @@ module ActiveRecord
def setup
@configurations = { development: { "database" => "my-db" } }
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_ignores_configurations_without_databases
@configurations[:development].merge!("database" => nil)
- assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
end
end
def test_ignores_remote_databases
@configurations[:development].merge!("host" => "my.server.tld")
- $stderr.stubs(:puts).returns(nil)
- assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_not_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
end
end
def test_warning_for_remote_databases
@configurations[:development].merge!("host" => "my.server.tld")
- assert_called_with(
- $stderr,
- :puts,
- ["This task only modifies local databases. my-db is on a remote host."],
- ) do
+ ActiveRecord::Base.stub(:configurations, @configurations) do
ActiveRecord::Tasks::DatabaseTasks.drop_all
+
+ assert_match "This task only modifies local databases. my-db is on a remote host.",
+ $stderr.string
end
end
def test_drops_configurations_with_local_ip
@configurations[:development].merge!("host" => "127.0.0.1")
- assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
end
end
def test_drops_configurations_with_local_host
@configurations[:development].merge!("host" => "localhost")
- assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
end
end
def test_drops_configurations_with_blank_hosts
@configurations[:development].merge!("host" => nil)
- assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
- ActiveRecord::Tasks::DatabaseTasks.drop_all
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called(ActiveRecord::Tasks::DatabaseTasks, :drop) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
end
end
end
@@ -408,50 +531,65 @@ module ActiveRecord
"test" => { "database" => "test-db" },
"production" => { "url" => "prod-db-url" }
}
-
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
end
def test_drops_current_environment_database
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("test")
- )
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
+ ["database" => "test-db"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
end
def test_drops_current_environment_database_with_url
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("url" => "prod-db-url")
-
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("production")
- )
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called_with(ActiveRecord::Tasks::DatabaseTasks, :drop,
+ ["url" => "prod-db-url"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
end
def test_drops_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
end
def test_drops_testand_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "test-db")
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
ensure
ENV["RAILS_ENV"] = old_env
end
@@ -464,64 +602,81 @@ module ActiveRecord
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "prod-db-url" }, "secondary" => { "url" => "secondary-prod-db-url" } }
}
-
- ActiveRecord::Base.stubs(:configurations).returns(@configurations)
end
def test_drops_current_environment_database
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "secondary-test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("test")
- )
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("test")
+ )
+ end
+ end
end
def test_drops_current_environment_database_with_url
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("url" => "prod-db-url")
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("url" => "secondary-prod-db-url")
-
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("production")
- )
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["url" => "prod-db-url"],
+ ["url" => "secondary-prod-db-url"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("production")
+ )
+ end
+ end
end
def test_drops_test_and_development_databases_when_env_was_not_specified
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "secondary-dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "test-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "secondary-test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("development")
- )
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
end
def test_drops_testand_development_databases_when_rails_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "secondary-dev-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "test-db")
- ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
- with("database" => "secondary-test-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop_current(
- ActiveSupport::StringInquirer.new("development")
- )
+
+ ActiveRecord::Base.stub(:configurations, @configurations) do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :drop,
+ [
+ ["database" => "dev-db"],
+ ["database" => "secondary-dev-db"],
+ ["database" => "test-db"],
+ ["database" => "secondary-test-db"]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new("development")
+ )
+ end
+ end
ensure
ENV["RAILS_ENV"] = old_env
end
@@ -649,10 +804,10 @@ module ActiveRecord
end
def test_migrate_raise_error_on_failed_check_target_version
- ActiveRecord::Tasks::DatabaseTasks.stubs(:check_target_version).raises("foo")
-
- e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
- assert_equal "foo", e.message
+ ActiveRecord::Tasks::DatabaseTasks.stub(:check_target_version, -> { raise "foo" }) do
+ e = assert_raise(RuntimeError) { ActiveRecord::Tasks::DatabaseTasks.migrate }
+ assert_equal "foo", e.message
+ end
end
def test_migrate_clears_schema_cache_afterward
@@ -667,8 +822,11 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_purge") do
- eval("@#{v}").expects(:purge)
- ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :purge) do
+ ActiveRecord::Tasks::DatabaseTasks.purge "adapter" => k
+ end
+ end
end
end
end
@@ -680,13 +838,16 @@ module ActiveRecord
"test" => { "database" => "test-db" },
"production" => { "database" => "prod-db" }
}
- ActiveRecord::Base.stubs(:configurations).returns(configurations)
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:purge).
- with("database" => "prod-db")
-
- assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do
- ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
+ ActiveRecord::Base.stub(:configurations, configurations) do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "prod-db"]
+ ) do
+ assert_called_with(ActiveRecord::Base, :establish_connection, [:production]) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_current("production")
+ end
+ end
end
end
end
@@ -694,12 +855,15 @@ module ActiveRecord
class DatabaseTasksPurgeAllTest < ActiveRecord::TestCase
def test_purge_all_local_configurations
configurations = { development: { "database" => "my-db" } }
- ActiveRecord::Base.stubs(:configurations).returns(configurations)
-
- ActiveRecord::Tasks::DatabaseTasks.expects(:purge).
- with("database" => "my-db")
-
- ActiveRecord::Tasks::DatabaseTasks.purge_all
+ ActiveRecord::Base.stub(:configurations, configurations) do
+ assert_called_with(
+ ActiveRecord::Tasks::DatabaseTasks,
+ :purge,
+ ["database" => "my-db"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge_all
+ end
+ end
end
end
@@ -708,8 +872,11 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_charset") do
- eval("@#{v}").expects(:charset)
- ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :charset) do
+ ActiveRecord::Tasks::DatabaseTasks.charset "adapter" => k
+ end
+ end
end
end
end
@@ -719,8 +886,11 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_collation") do
- eval("@#{v}").expects(:collation)
- ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => k
+ with_stubbed_new do
+ assert_called(eval("@#{v}"), :collation) do
+ ActiveRecord::Tasks::DatabaseTasks.collation "adapter" => k
+ end
+ end
end
end
end
@@ -832,8 +1002,14 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_structure_dump") do
- eval("@#{v}").expects(:structure_dump).with("awesome-file.sql", nil)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => k }, "awesome-file.sql")
+ with_stubbed_new do
+ assert_called_with(
+ eval("@#{v}"), :structure_dump,
+ ["awesome-file.sql", nil]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => k }, "awesome-file.sql")
+ end
+ end
end
end
end
@@ -843,8 +1019,15 @@ module ActiveRecord
ADAPTERS_TASKS.each do |k, v|
define_method("test_#{k}_structure_load") do
- eval("@#{v}").expects(:structure_load).with("awesome-file.sql", nil)
- ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => k }, "awesome-file.sql")
+ with_stubbed_new do
+ assert_called_with(
+ eval("@#{v}"),
+ :structure_load,
+ ["awesome-file.sql", nil]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => k }, "awesome-file.sql")
+ end
+ end
end
end
end
@@ -859,16 +1042,18 @@ module ActiveRecord
class DatabaseTasksCheckSchemaFileDefaultsTest < ActiveRecord::TestCase
def test_check_schema_file_defaults
- ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns("/tmp")
- assert_equal "/tmp/schema.rb", ActiveRecord::Tasks::DatabaseTasks.schema_file
+ ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "/tmp") do
+ assert_equal "/tmp/schema.rb", ActiveRecord::Tasks::DatabaseTasks.schema_file
+ end
end
end
class DatabaseTasksCheckSchemaFileSpecifiedFormatsTest < ActiveRecord::TestCase
{ ruby: "schema.rb", sql: "structure.sql" }.each_pair do |fmt, filename|
define_method("test_check_schema_file_for_#{fmt}_format") do
- ActiveRecord::Tasks::DatabaseTasks.stubs(:db_dir).returns("/tmp")
- assert_equal "/tmp/#{filename}", ActiveRecord::Tasks::DatabaseTasks.schema_file(fmt)
+ ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "/tmp") do
+ assert_equal "/tmp/#{filename}", ActiveRecord::Tasks::DatabaseTasks.schema_file(fmt)
+ end
end
end
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 6cddfaefeb..eeb4222d97 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -7,15 +7,11 @@ if current_adapter?(:Mysql2Adapter)
module ActiveRecord
class MysqlDBCreateTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def create_database(*); end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -25,59 +21,97 @@ if current_adapter?(:Mysql2Adapter)
end
def test_establishes_connection_without_database
- ActiveRecord::Base.expects(:establish_connection).
- with("adapter" => "mysql2", "database" => nil)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [ "adapter" => "mysql2", "database" => nil ],
+ [ "adapter" => "mysql2", "database" => "my-app-db" ],
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_creates_database_with_no_default_options
- @connection.expects(:create_database).
- with("my-app-db", {})
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(@connection, :create_database, ["my-app-db", {}]) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_creates_database_with_given_encoding
- @connection.expects(:create_database).
- with("my-app-db", charset: "latin1")
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("encoding" => "latin1")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(@connection, :create_database, ["my-app-db", charset: "latin1"]) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("encoding" => "latin1")
+ end
+ end
end
def test_creates_database_with_given_collation
- @connection.expects(:create_database).
- with("my-app-db", collation: "latin1_swedish_ci")
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("collation" => "latin1_swedish_ci")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :create_database,
+ ["my-app-db", collation: "latin1_swedish_ci"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge("collation" => "latin1_swedish_ci")
+ end
+ end
end
def test_establishes_connection_to_database
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ ["adapter" => "mysql2", "database" => nil],
+ [@configuration]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_when_database_created_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
- assert_equal "Created database 'my-app-db'\n", $stdout.string
+ assert_equal "Created database 'my-app-db'\n", $stdout.string
+ end
end
def test_create_when_database_exists_outputs_info_to_stderr
- ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::Tasks::DatabaseAlreadyExists
- )
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Base.connection.stub(
+ :create_database,
+ proc { raise ActiveRecord::Tasks::DatabaseAlreadyExists }
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+
+ assert_equal "Database 'my-app-db' already exists\n", $stderr.string
+ end
+ end
+ end
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ private
- assert_equal "Database 'my-app-db' already exists\n", $stderr.string
- end
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ ActiveRecord::Base.stub(:connection, @connection) do
+ yield
+ end
+ end
+ end
end
class MysqlDBCreateWithInvalidPermissionsTest < ActiveRecord::TestCase
def setup
- @connection = stub("Connection", create_database: true)
@error = Mysql2::Error.new("Invalid permissions")
@configuration = {
"adapter" => "mysql2",
@@ -85,10 +119,6 @@ if current_adapter?(:Mysql2Adapter)
"username" => "pat",
"password" => "wossname"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).raises(@error)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -98,23 +128,21 @@ if current_adapter?(:Mysql2Adapter)
end
def test_raises_error
- assert_raises(Mysql2::Error) do
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.stub(:establish_connection, -> * { raise @error }) do
+ assert_raises(Mysql2::Error, "Invalid permissions") do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
end
end
end
class MySQLDBDropTest < ActiveRecord::TestCase
def setup
- @connection = stub(drop_database: true)
+ @connection = Class.new { def drop_database(name); end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -124,91 +152,122 @@ if current_adapter?(:Mysql2Adapter)
end
def test_establishes_connection_to_mysql_database
- ActiveRecord::Base.expects(:establish_connection).with @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Base.expects(:establish_connection).with @configuration
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
end
def test_drops_database
- @connection.expects(:drop_database).with("my-app-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(@connection, :drop_database, ["my-app-db"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
end
def test_when_database_dropped_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
- assert_equal "Dropped database 'my-app-db'\n", $stdout.string
+ assert_equal "Dropped database 'my-app-db'\n", $stdout.string
+ end
end
+
+ private
+
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ ActiveRecord::Base.stub(:connection, @connection) do
+ yield
+ end
+ end
+ end
end
class MySQLPurgeTest < ActiveRecord::TestCase
def setup
- @connection = stub(recreate_database: true)
+ @connection = Class.new { def recreate_database(*); end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "test-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_establishes_connection_to_the_appropriate_database
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
end
def test_recreates_database_with_no_default_options
- @connection.expects(:recreate_database).
- with("test-db", {})
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(@connection, :recreate_database, ["test-db", {}]) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
end
def test_recreates_database_with_the_given_options
- @connection.expects(:recreate_database).
- with("test-db", charset: "latin", collation: "latin1_swedish_ci")
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge(
- "encoding" => "latin", "collation" => "latin1_swedish_ci")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :recreate_database,
+ ["test-db", charset: "latin", collation: "latin1_swedish_ci"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge(
+ "encoding" => "latin", "collation" => "latin1_swedish_ci")
+ end
+ end
end
+
+ private
+
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ ActiveRecord::Base.stub(:connection, @connection) do
+ yield
+ end
+ end
+ end
end
class MysqlDBCharsetTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def charset; end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_charset
- @connection.expects(:charset)
- ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called(@connection, :charset) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ end
end
end
class MysqlDBCollationTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def collation; end }.new
@configuration = {
"adapter" => "mysql2",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_collation
- @connection.expects(:collation)
- ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(@connection, :collation) do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
end
end
@@ -245,15 +304,15 @@ if current_adapter?(:Mysql2Adapter)
def test_structure_dump_with_ignore_tables
filename = "awesome-file.sql"
- ActiveRecord::SchemaDumper.expects(:ignore_tables).returns(["foo", "bar"])
-
- assert_called_with(
- Kernel,
- :system,
- ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--ignore-table=test-db.foo", "--ignore-table=test-db.bar", "test-db"],
- returns: true
- ) do
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ ActiveRecord::SchemaDumper.stub(:ignore_tables, ["foo", "bar"]) do
+ assert_called_with(
+ Kernel,
+ :system,
+ ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--ignore-table=test-db.foo", "--ignore-table=test-db.bar", "test-db"],
+ returns: true
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
end
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index a1a3700f07..00005e7a0d 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -7,15 +7,11 @@ if current_adapter?(:PostgreSQLAdapter)
module ActiveRecord
class PostgreSQLDBCreateTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def create_database(*); end }.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -25,82 +21,141 @@ if current_adapter?(:PostgreSQLAdapter)
end
def test_establishes_connection_to_postgresql_database
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "postgresql",
- "database" => "postgres",
- "schema_search_path" => "public"
- )
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ],
+ [
+ "adapter" => "postgresql",
+ "database" => "my-app-db"
+ ]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_creates_database_with_default_encoding
- @connection.expects(:create_database).
- with("my-app-db", @configuration.merge("encoding" => "utf8"))
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :create_database,
+ ["my-app-db", @configuration.merge("encoding" => "utf8")]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_creates_database_with_given_encoding
- @connection.expects(:create_database).
- with("my-app-db", @configuration.merge("encoding" => "latin"))
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.
- merge("encoding" => "latin")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :create_database,
+ ["my-app-db", @configuration.merge("encoding" => "latin")]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.
+ merge("encoding" => "latin")
+ end
+ end
end
def test_creates_database_with_given_collation_and_ctype
- @connection.expects(:create_database).
- with("my-app-db", @configuration.merge("encoding" => "utf8", "collation" => "ja_JP.UTF8", "ctype" => "ja_JP.UTF8"))
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration.
- merge("collation" => "ja_JP.UTF8", "ctype" => "ja_JP.UTF8")
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :create_database,
+ [
+ "my-app-db",
+ @configuration.merge(
+ "encoding" => "utf8",
+ "collation" => "ja_JP.UTF8",
+ "ctype" => "ja_JP.UTF8"
+ )
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.
+ merge("collation" => "ja_JP.UTF8", "ctype" => "ja_JP.UTF8")
+ end
+ end
end
def test_establishes_connection_to_new_database
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ],
+ [
+ @configuration
+ ]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
end
def test_db_create_with_error_prints_message
- ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
-
- $stderr.stubs(:puts).returns(true)
- $stderr.expects(:puts).
- with("Couldn't create database for #{@configuration.inspect}")
-
- assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
+ ActiveRecord::Base.stub(:connection, @connection) do
+ ActiveRecord::Base.stub(:establish_connection, -> * { raise Exception }) do
+ assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
+ assert_match "Couldn't create database for #{@configuration.inspect}", $stderr.string
+ end
+ end
end
def test_when_database_created_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
- assert_equal "Created database 'my-app-db'\n", $stdout.string
+ assert_equal "Created database 'my-app-db'\n", $stdout.string
+ end
end
def test_create_when_database_exists_outputs_info_to_stderr
- ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::Tasks::DatabaseAlreadyExists
- )
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Base.connection.stub(
+ :create_database,
+ proc { raise ActiveRecord::Tasks::DatabaseAlreadyExists }
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
- assert_equal "Database 'my-app-db' already exists\n", $stderr.string
+ assert_equal "Database 'my-app-db' already exists\n", $stderr.string
+ end
+ end
end
+
+ private
+
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:connection, @connection) do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ yield
+ end
+ end
+ end
end
class PostgreSQLDBDropTest < ActiveRecord::TestCase
def setup
- @connection = stub(drop_database: true)
+ @connection = Class.new { def drop_database(*); end }.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -110,125 +165,192 @@ if current_adapter?(:PostgreSQLAdapter)
end
def test_establishes_connection_to_postgresql_database
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "postgresql",
- "database" => "postgres",
- "schema_search_path" => "public"
- )
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ ActiveRecord::Base.expects(:establish_connection).with(
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ )
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
end
def test_drops_database
- @connection.expects(:drop_database).with("my-app-db")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ with_stubbed_connection_establish_connection do
+ assert_called_with(
+ @connection,
+ :drop_database,
+ ["my-app-db"]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
end
def test_when_database_dropped_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ with_stubbed_connection_establish_connection do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
- assert_equal "Dropped database 'my-app-db'\n", $stdout.string
+ assert_equal "Dropped database 'my-app-db'\n", $stdout.string
+ end
end
+
+ private
+
+ def with_stubbed_connection_establish_connection
+ ActiveRecord::Base.stub(:connection, @connection) do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ yield
+ end
+ end
+ end
end
class PostgreSQLPurgeTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true, drop_database: true)
+ @connection = Class.new do
+ def create_database(*); end
+ def drop_database(*); end
+ end.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:clear_active_connections!).returns(true)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_clears_active_connections
- ActiveRecord::Base.expects(:clear_active_connections!)
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ assert_called(ActiveRecord::Base, :clear_active_connections!) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
+ end
end
def test_establishes_connection_to_postgresql_database
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "postgresql",
- "database" => "postgres",
- "schema_search_path" => "public"
- )
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ],
+ [
+ "adapter" => "postgresql",
+ "database" => "my-app-db"
+ ]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
end
def test_drops_database
- @connection.expects(:drop_database).with("my-app-db")
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ assert_called_with(@connection, :drop_database, ["my-app-db"]) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
+ end
end
def test_creates_database
- @connection.expects(:create_database).
- with("my-app-db", @configuration.merge("encoding" => "utf8"))
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ assert_called_with(
+ @connection,
+ :create_database,
+ ["my-app-db", @configuration.merge("encoding" => "utf8")]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
+ end
end
def test_establishes_connection
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ with_stubbed_connection do
+ assert_called_with(
+ ActiveRecord::Base,
+ :establish_connection,
+ [
+ [
+ "adapter" => "postgresql",
+ "database" => "postgres",
+ "schema_search_path" => "public"
+ ],
+ [
+ @configuration
+ ]
+ ]
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
end
+
+ private
+
+ def with_stubbed_connection
+ ActiveRecord::Base.stub(:connection, @connection) do
+ yield
+ end
+ end
end
class PostgreSQLDBCharsetTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new do
+ def create_database(*); end
+ def encoding; end
+ end.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_charset
- @connection.expects(:encoding)
- ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called(@connection, :encoding) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ end
end
end
class PostgreSQLDBCollationTest < ActiveRecord::TestCase
def setup
- @connection = stub(create_database: true)
+ @connection = Class.new { def collation; end }.new
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_collation
- @connection.expects(:collation)
- ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called(@connection, :collation) do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
end
end
class PostgreSQLStructureDumpTest < ActiveRecord::TestCase
def setup
- @connection = stub(schema_search_path: nil, structure_dump: true)
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
@filename = "/tmp/awesome-file.sql"
FileUtils.touch(@filename)
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def teardown
@@ -247,12 +369,12 @@ if current_adapter?(:PostgreSQLAdapter)
end
def test_structure_dump_header_comments_removed
- Kernel.stubs(:system).returns(true)
- File.write(@filename, "-- header comment\n\n-- more header comment\n statement \n-- lower comment\n")
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
+ Kernel.stub(:system, true) do
+ File.write(@filename, "-- header comment\n\n-- more header comment\n statement \n-- lower comment\n")
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)
- assert_equal [" statement \n", "-- lower comment\n"], File.readlines(@filename).first(2)
+ assert_equal [" statement \n", "-- lower comment\n"], File.readlines(@filename).first(2)
+ end
end
def test_structure_dump_with_extra_flags
@@ -358,14 +480,10 @@ if current_adapter?(:PostgreSQLAdapter)
class PostgreSQLStructureLoadTest < ActiveRecord::TestCase
def setup
- @connection = stub
@configuration = {
"adapter" => "postgresql",
"database" => "my-app-db"
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_structure_load
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index d368a7a6ee..7eb062b456 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -9,16 +9,10 @@ if current_adapter?(:SQLite3Adapter)
class SqliteDBCreateTest < ActiveRecord::TestCase
def setup
@database = "db_create.sqlite3"
- @connection = stub :connection
@configuration = {
"adapter" => "sqlite3",
"database" => @database
}
-
- File.stubs(:exist?).returns(false)
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
-
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
end
@@ -28,63 +22,62 @@ if current_adapter?(:SQLite3Adapter)
end
def test_db_checks_database_exists
- File.expects(:exist?).with(@database).returns(false)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ assert_called_with(File, :exist?, [@database], returns: false) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ end
+ end
end
def test_when_db_created_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ ActiveRecord::Base.stub(:establish_connection, nil) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
- assert_equal "Created database '#{@database}'\n", $stdout.string
+ assert_equal "Created database '#{@database}'\n", $stdout.string
+ end
end
def test_db_create_when_file_exists
- File.stubs(:exist?).returns(true)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ File.stub(:exist?, true) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
- assert_equal "Database '#{@database}' already exists\n", $stderr.string
+ assert_equal "Database '#{@database}' already exists\n", $stderr.string
+ end
end
def test_db_create_with_file_does_nothing
- File.stubs(:exist?).returns(true)
- $stderr.stubs(:puts).returns(nil)
+ File.stub(:exist?, true) do
+ ActiveRecord::Base.expects(:establish_connection).never
- ActiveRecord::Base.expects(:establish_connection).never
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ end
end
def test_db_create_establishes_a_connection
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ assert_called_with(ActiveRecord::Base, :establish_connection, [@configuration]) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root"
+ end
end
def test_db_create_with_error_prints_message
- ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
-
- $stderr.stubs(:puts).returns(true)
- $stderr.expects(:puts).
- with("Couldn't create database for #{@configuration.inspect}")
-
- assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" }
+ ActiveRecord::Base.stub(:establish_connection, proc { raise Exception }) do
+ assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" }
+ assert_match "Couldn't create database for #{@configuration.inspect}", $stderr.string
+ end
end
end
class SqliteDBDropTest < ActiveRecord::TestCase
def setup
@database = "db_create.sqlite3"
- @path = stub(to_s: "/absolute/path", absolute?: true)
@configuration = {
"adapter" => "sqlite3",
"database" => @database
}
-
- Pathname.stubs(:new).returns(@path)
- File.stubs(:join).returns("/former/relative/path")
- FileUtils.stubs(:rm).returns(true)
+ @path = Class.new do
+ def to_s; "/absolute/path" end
+ def absolute?; true end
+ end.new
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
@@ -95,77 +88,76 @@ if current_adapter?(:SQLite3Adapter)
end
def test_creates_path_from_database
- Pathname.expects(:new).with(@database).returns(@path)
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ assert_called_with(Pathname, :new, [@database], returns: @path) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ end
end
def test_removes_file_with_absolute_path
- File.stubs(:exist?).returns(true)
- @path.stubs(:absolute?).returns(true)
-
- FileUtils.expects(:rm).with("/absolute/path")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ Pathname.stub(:new, @path) do
+ assert_called_with(FileUtils, :rm, ["/absolute/path"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ end
+ end
end
def test_generates_absolute_path_with_given_root
- @path.stubs(:absolute?).returns(false)
-
- File.expects(:join).with("/rails/root", @path).
- returns("/former/relative/path")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ Pathname.stub(:new, @path) do
+ @path.stub(:absolute?, false) do
+ assert_called_with(File, :join, ["/rails/root", @path],
+ returns: "/former/relative/path"
+ ) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ end
+ end
+ end
end
def test_removes_file_with_relative_path
- File.stubs(:exist?).returns(true)
- @path.stubs(:absolute?).returns(false)
-
- FileUtils.expects(:rm).with("/former/relative/path")
-
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ File.stub(:join, "/former/relative/path") do
+ @path.stub(:absolute?, false) do
+ assert_called_with(FileUtils, :rm, ["/former/relative/path"]) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ end
+ end
+ end
end
def test_when_db_dropped_successfully_outputs_info_to_stdout
- ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
+ FileUtils.stub(:rm, nil) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root"
- assert_equal "Dropped database '#{@database}'\n", $stdout.string
+ assert_equal "Dropped database '#{@database}'\n", $stdout.string
+ end
end
end
class SqliteDBCharsetTest < ActiveRecord::TestCase
def setup
@database = "db_create.sqlite3"
- @connection = stub :connection
+ @connection = Class.new { def encoding; end }.new
@configuration = {
"adapter" => "sqlite3",
"database" => @database
}
-
- File.stubs(:exist?).returns(false)
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_charset
- @connection.expects(:encoding)
- ActiveRecord::Tasks::DatabaseTasks.charset @configuration, "/rails/root"
+ ActiveRecord::Base.stub(:connection, @connection) do
+ assert_called(@connection, :encoding) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration, "/rails/root"
+ end
+ end
end
end
class SqliteDBCollationTest < ActiveRecord::TestCase
def setup
@database = "db_create.sqlite3"
- @connection = stub :connection
@configuration = {
"adapter" => "sqlite3",
"database" => @database
}
-
- File.stubs(:exist?).returns(false)
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_db_retrieves_collation
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 5b685ca564..46463ac414 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -573,7 +573,6 @@ class TransactionTest < ActiveRecord::TestCase
assert_called(Topic.connection, :begin_db_transaction) do
Topic.connection.stub(:commit_db_transaction, -> { raise("OH NOES") }) do
assert_called(Topic.connection, :rollback_db_transaction) do
-
e = assert_raise RuntimeError do
Topic.transaction do
# do nothing
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 54eb5e6783..640cdb33b4 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -253,6 +253,7 @@ class SpecialPostWithDefaultScope < ActiveRecord::Base
self.inheritance_column = :disabled
self.table_name = "posts"
default_scope { where(id: [1, 5, 6]) }
+ scope :unscoped_all, -> { unscoped { all } }
end
class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index e634e9e6b1..8371ba9528 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -1,16 +1,17 @@
# frozen_string_literal: true
ActiveRecord::Schema.define do
-
- if ActiveRecord::Base.connection.version >= "5.6.0"
+ if subsecond_precision_supported?
create_table :datetime_defaults, force: true do |t|
t.datetime :modified_datetime, default: -> { "CURRENT_TIMESTAMP" }
+ t.datetime :precise_datetime, precision: 6, default: -> { "CURRENT_TIMESTAMP(6)" }
end
- end
- create_table :timestamp_defaults, force: true do |t|
- t.timestamp :nullable_timestamp
- t.timestamp :modified_timestamp, default: -> { "CURRENT_TIMESTAMP" }
+ create_table :timestamp_defaults, force: true do |t|
+ t.timestamp :nullable_timestamp
+ t.timestamp :modified_timestamp, default: -> { "CURRENT_TIMESTAMP" }
+ t.timestamp :precise_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6)" }
+ end
end
create_table :binary_fields, force: true do |t|
diff --git a/activerecord/test/schema/oracle_specific_schema.rb b/activerecord/test/schema/oracle_specific_schema.rb
index e236571caa..bc1e45ca80 100644
--- a/activerecord/test/schema/oracle_specific_schema.rb
+++ b/activerecord/test/schema/oracle_specific_schema.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
ActiveRecord::Schema.define do
-
execute "drop table test_oracle_defaults" rescue nil
execute "drop sequence test_oracle_defaults_seq" rescue nil
execute "drop sequence companies_nonstd_seq" rescue nil
@@ -38,5 +37,4 @@ create sequence test_oracle_defaults_seq minvalue 10000
)
SQL
execute "create sequence defaults_seq minvalue 10000"
-
end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index f15178d695..975824ed51 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
ActiveRecord::Schema.define do
-
enable_extension!("uuid-ossp", ActiveRecord::Base.connection)
enable_extension!("pgcrypto", ActiveRecord::Base.connection) if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md
index 4aa551781b..0e9c2b5619 100644
--- a/activestorage/CHANGELOG.md
+++ b/activestorage/CHANGELOG.md
@@ -1,3 +1,17 @@
+* Uploaded files assigned to a record are persisted to storage when the record
+ is saved instead of immediately.
+
+ In Rails 5.2, the following causes an uploaded file in `params[:avatar]` to
+ be stored:
+
+ ```ruby
+ @user.avatar = params[:avatar]
+ ```
+
+ In Rails 6, the uploaded file is stored when `@user` is successfully saved.
+
+ *George Claghorn*
+
* Add the ability to reflect on defined attachments using the existing
ActiveRecord reflection mechanism.
diff --git a/activestorage/app/controllers/active_storage/disk_controller.rb b/activestorage/app/controllers/active_storage/disk_controller.rb
index 63918eb6f4..75cc11d6ff 100644
--- a/activestorage/app/controllers/active_storage/disk_controller.rb
+++ b/activestorage/app/controllers/active_storage/disk_controller.rb
@@ -5,30 +5,20 @@
# Always go through the BlobsController, or your own authenticated controller, rather than directly
# to the service url.
class ActiveStorage::DiskController < ActiveStorage::BaseController
- include ActionController::Live
-
skip_forgery_protection
def show
if key = decode_verified_key
- response.headers["Content-Type"] = params[:content_type] || DEFAULT_SEND_FILE_TYPE
- response.headers["Content-Disposition"] = params[:disposition] || DEFAULT_SEND_FILE_DISPOSITION
-
- disk_service.download key do |chunk|
- response.stream.write chunk
- end
+ serve_file disk_service.path_for(key), content_type: params[:content_type], disposition: params[:disposition]
else
head :not_found
end
- ensure
- response.stream.close
end
def update
if token = decode_verified_token
if acceptable_content?(token)
disk_service.upload token[:key], request.body, checksum: token[:checksum]
- head :no_content
else
head :unprocessable_entity
end
@@ -37,8 +27,6 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
end
rescue ActiveStorage::IntegrityError
head :unprocessable_entity
- ensure
- response.stream.close
end
private
@@ -51,6 +39,20 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
end
+ def serve_file(path, content_type:, disposition:)
+ Rack::File.new(nil).serving(request, path).tap do |(status, headers, body)|
+ self.status = status
+ self.response_body = body
+
+ headers.each do |name, value|
+ response.headers[name] = value
+ end
+
+ response.headers["Content-Type"] = content_type || DEFAULT_SEND_FILE_TYPE
+ response.headers["Content-Disposition"] = disposition || DEFAULT_SEND_FILE_DISPOSITION
+ end
+ end
+
def decode_verified_token
ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
diff --git a/activestorage/app/jobs/active_storage/purge_job.rb b/activestorage/app/jobs/active_storage/purge_job.rb
index 98874d2250..b021b5f2d0 100644
--- a/activestorage/app/jobs/active_storage/purge_job.rb
+++ b/activestorage/app/jobs/active_storage/purge_job.rb
@@ -2,8 +2,7 @@
# Provides asynchronous purging of ActiveStorage::Blob records via ActiveStorage::Blob#purge_later.
class ActiveStorage::PurgeJob < ActiveStorage::BaseJob
- # FIXME: Limit this to a custom ActiveStorage error
- retry_on StandardError
+ discard_on ActiveRecord::RecordNotFound, ActiveRecord::InvalidForeignKey
def perform(blob)
blob.purge
diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb
index c59877a9a5..1c4dc25094 100644
--- a/activestorage/app/models/active_storage/attachment.rb
+++ b/activestorage/app/models/active_storage/attachment.rb
@@ -15,17 +15,18 @@ class ActiveStorage::Attachment < ActiveRecord::Base
delegate_missing_to :blob
after_create_commit :analyze_blob_later, :identify_blob
+ after_destroy_commit :purge_dependent_blob_later
- # Synchronously purges the blob (deletes it from the configured service) and destroys the attachment.
+ # Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge].
def purge
+ delete
blob.purge
- destroy
end
- # Destroys the attachment and asynchronously purges the blob (deletes it from the configured service).
+ # Deletes the attachment and {enqueues a background job}[rdoc-ref:ActiveStorage::Blob#purge_later] to purge the blob.
def purge_later
+ delete
blob.purge_later
- destroy
end
private
@@ -36,4 +37,13 @@ class ActiveStorage::Attachment < ActiveRecord::Base
def analyze_blob_later
blob.analyze_later unless blob.analyzed?
end
+
+ def purge_dependent_blob_later
+ blob.purge_later if dependent == :purge_later
+ end
+
+
+ def dependent
+ record.attachment_reflections[name]&.options[:dependent]
+ end
end
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index 73029f21a1..bf87598a66 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -48,15 +48,17 @@ class ActiveStorage::Blob < ActiveRecord::Base
# Returns a new, unsaved blob instance after the +io+ has been uploaded to the service.
# When providing a content type, pass <tt>identify: false</tt> to bypass automatic content type inference.
def build_after_upload(io:, filename:, content_type: nil, metadata: nil, identify: true)
- new.tap do |blob|
- blob.filename = filename
- blob.content_type = content_type
- blob.metadata = metadata
-
+ new(filename: filename, content_type: content_type, metadata: metadata).tap do |blob|
blob.upload(io, identify: identify)
end
end
+ def build_after_unfurling(io:, filename:, content_type: nil, metadata: nil, identify: true) #:nodoc:
+ new(filename: filename, content_type: content_type, metadata: metadata).tap do |blob|
+ blob.unfurl(io, identify: identify)
+ end
+ end
+
# Returns a saved blob instance after the +io+ has been uploaded to the service. Note, the blob is first built,
# then the +io+ is uploaded, then the blob is saved. This is done this way to avoid uploading (which may take
# time), while having an open database transaction.
@@ -152,12 +154,19 @@ class ActiveStorage::Blob < ActiveRecord::Base
# Normally, you do not have to call this method directly at all. Use the factory class methods of +build_after_upload+
# and +create_after_upload!+.
def upload(io, identify: true)
+ unfurl io, identify: identify
+ upload_without_unfurling io
+ end
+
+ def unfurl(io, identify: true) #:nodoc:
self.checksum = compute_checksum_in_chunks(io)
self.content_type = extract_content_type(io) if content_type.nil? || identify
self.byte_size = io.size
self.identified = true
+ end
- service.upload(key, io, checksum: checksum)
+ def upload_without_unfurling(io) #:nodoc:
+ service.upload key, io, checksum: checksum
end
# Downloads the file associated with this blob. If no block is given, the entire file is read into memory and returned.
@@ -184,8 +193,8 @@ class ActiveStorage::Blob < ActiveRecord::Base
end
- # Deletes the file on the service that's associated with this blob. This should only be done if the blob is going to be
- # deleted as well or you will essentially have a dead reference. It's recommended to use the +#purge+ and +#purge_later+
+ # Deletes the files on the service associated with the blob. This should only be done if the blob is going to be
+ # deleted as well or you will essentially have a dead reference. It's recommended to use #purge and #purge_later
# methods in most circumstances.
def delete
service.delete(key)
@@ -194,14 +203,14 @@ class ActiveStorage::Blob < ActiveRecord::Base
# Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted
# blobs. Note, though, that deleting the file off the service will initiate a HTTP connection to the service, which may
- # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use +#purge_later+ instead.
+ # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use #purge_later instead.
def purge
- delete
destroy
+ delete
end
- # Enqueues an ActiveStorage::PurgeJob job that'll call +purge+. This is the recommended way to purge blobs when the call
- # needs to be made from a transaction, a callback, or any other real-time scenario.
+ # Enqueues an ActiveStorage::PurgeJob to call #purge. This is the recommended way to purge blobs from a transaction,
+ # an Active Record callback, or in any other real-time scenario.
def purge_later
ActiveStorage::PurgeJob.perform_later(self)
end
diff --git a/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb
index 9e31e3966a..cfaf01cd5e 100644
--- a/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb
+++ b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb
@@ -20,6 +20,7 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
t.datetime :created_at, null: false
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
+ t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end
diff --git a/activestorage/lib/active_storage/attached.rb b/activestorage/lib/active_storage/attached.rb
index c08fd56652..b540f85fbe 100644
--- a/activestorage/lib/active_storage/attached.rb
+++ b/activestorage/lib/active_storage/attached.rb
@@ -1,40 +1,25 @@
# frozen_string_literal: true
-require "action_dispatch"
-require "action_dispatch/http/upload"
require "active_support/core_ext/module/delegation"
module ActiveStorage
# Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many
# classes that both provide proxy access to the blob association for a record.
class Attached
- attr_reader :name, :record, :dependent
+ attr_reader :name, :record
- def initialize(name, record, dependent:)
- @name, @record, @dependent = name, record, dependent
+ def initialize(name, record)
+ @name, @record = name, record
end
private
- def create_blob_from(attachable)
- case attachable
- when ActiveStorage::Blob
- attachable
- when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
- ActiveStorage::Blob.create_after_upload! \
- io: attachable.open,
- filename: attachable.original_filename,
- content_type: attachable.content_type
- when Hash
- ActiveStorage::Blob.create_after_upload!(attachable)
- when String
- ActiveStorage::Blob.find_signed(attachable)
- else
- nil
- end
+ def change
+ record.attachment_changes[name]
end
end
end
+require "active_storage/attached/model"
require "active_storage/attached/one"
require "active_storage/attached/many"
-require "active_storage/attached/macros"
+require "active_storage/attached/changes"
diff --git a/activestorage/lib/active_storage/attached/changes.rb b/activestorage/lib/active_storage/attached/changes.rb
new file mode 100644
index 0000000000..1db3906a63
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ module Attached::Changes #:nodoc:
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :CreateOne
+ autoload :CreateMany
+ autoload :CreateOneOfMany
+
+ autoload :DeleteOne
+ autoload :DeleteMany
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/create_many.rb b/activestorage/lib/active_storage/attached/changes/create_many.rb
new file mode 100644
index 0000000000..a7a8553e0f
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/create_many.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Attached::Changes::CreateMany #:nodoc:
+ attr_reader :name, :record, :attachables
+
+ def initialize(name, record, attachables)
+ @name, @record, @attachables = name, record, Array(attachables)
+ end
+
+ def attachments
+ @attachments ||= subchanges.collect(&:attachment)
+ end
+
+ def blobs
+ @blobs ||= subchanges.collect(&:blob)
+ end
+
+ def upload
+ subchanges.each(&:upload)
+ end
+
+ def save
+ assign_associated_attachments
+ reset_associated_blobs
+ end
+
+ private
+ def subchanges
+ @subchanges ||= attachables.collect { |attachable| build_subchange_from(attachable) }
+ end
+
+ def build_subchange_from(attachable)
+ ActiveStorage::Attached::Changes::CreateOneOfMany.new(name, record, attachable)
+ end
+
+
+ def assign_associated_attachments
+ record.public_send("#{name}_attachments=", attachments)
+ end
+
+ def reset_associated_blobs
+ record.public_send("#{name}_blobs").reset
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/create_one.rb b/activestorage/lib/active_storage/attached/changes/create_one.rb
new file mode 100644
index 0000000000..5812fd2b08
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/create_one.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require "action_dispatch"
+require "action_dispatch/http/upload"
+
+module ActiveStorage
+ class Attached::Changes::CreateOne #:nodoc:
+ attr_reader :name, :record, :attachable
+
+ def initialize(name, record, attachable)
+ @name, @record, @attachable = name, record, attachable
+ end
+
+ def attachment
+ @attachment ||= find_or_build_attachment
+ end
+
+ def blob
+ @blob ||= find_or_build_blob
+ end
+
+ def upload
+ case attachable
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
+ blob.upload_without_unfurling(attachable.open)
+ when Hash
+ blob.upload_without_unfurling(attachable.fetch(:io))
+ end
+ end
+
+ def save
+ record.public_send("#{name}_attachment=", attachment)
+ end
+
+ private
+ def find_or_build_attachment
+ find_attachment || build_attachment
+ end
+
+ def find_attachment
+ if record.public_send("#{name}_blob") == blob
+ record.public_send("#{name}_attachment")
+ end
+ end
+
+ def build_attachment
+ ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
+ end
+
+ def find_or_build_blob
+ case attachable
+ when ActiveStorage::Blob
+ attachable
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
+ ActiveStorage::Blob.build_after_unfurling \
+ io: attachable.open,
+ filename: attachable.original_filename,
+ content_type: attachable.content_type
+ when Hash
+ ActiveStorage::Blob.build_after_unfurling(attachable)
+ when String
+ ActiveStorage::Blob.find_signed(attachable)
+ else
+ raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/create_one_of_many.rb b/activestorage/lib/active_storage/attached/changes/create_one_of_many.rb
new file mode 100644
index 0000000000..7268e87316
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/create_one_of_many.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne #:nodoc:
+ private
+ def find_attachment
+ record.public_send("#{name}_attachments").detect { |attachment| attachment.blob_id == blob.id }
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/delete_many.rb b/activestorage/lib/active_storage/attached/changes/delete_many.rb
new file mode 100644
index 0000000000..6cbd1158dc
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/delete_many.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Attached::Changes::DeleteMany #:nodoc:
+ attr_reader :name, :record
+
+ def initialize(name, record)
+ @name, @record = name, record
+ end
+
+ def attachments
+ ActiveStorage::Attachment.none
+ end
+
+ def blobs
+ ActiveStorage::Blob.none
+ end
+
+ def save
+ record.public_send("#{name}_attachments=", [])
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/delete_one.rb b/activestorage/lib/active_storage/attached/changes/delete_one.rb
new file mode 100644
index 0000000000..2f7d356613
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/delete_one.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Attached::Changes::DeleteOne #:nodoc:
+ attr_reader :name, :record
+
+ def initialize(name, record)
+ @name, @record = name, record
+ end
+
+ def attachment
+ nil
+ end
+
+ def save
+ record.public_send("#{name}_attachment=", nil)
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/macros.rb b/activestorage/lib/active_storage/attached/macros.rb
deleted file mode 100644
index 6ad9fc43d7..0000000000
--- a/activestorage/lib/active_storage/attached/macros.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveStorage
- # Provides the class-level DSL for declaring that an Active Record model has attached blobs.
- module Attached::Macros
- # Specifies the relation between a single attachment and the model.
- #
- # class User < ActiveRecord::Base
- # has_one_attached :avatar
- # end
- #
- # There is no column defined on the model side, Active Storage takes
- # care of the mapping between your records and the attachment.
- #
- # To avoid N+1 queries, you can include the attached blobs in your query like so:
- #
- # User.with_attached_avatar
- #
- # Under the covers, this relationship is implemented as a +has_one+ association to a
- # ActiveStorage::Attachment record and a +has_one-through+ association to a
- # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
- # and +avatar_blob+. But you shouldn't need to work with these associations directly in
- # most circumstances.
- #
- # The system has been designed to having you go through the ActiveStorage::Attached::One
- # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
- #
- # If the +:dependent+ option isn't set, the attachment will be purged
- # (i.e. destroyed) whenever the record is destroyed.
- def has_one_attached(name, dependent: :purge_later)
- generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}
- @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
- end
-
- def #{name}=(attachable)
- #{name}.attach(attachable)
- end
- CODE
-
- has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: false
- has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
-
- scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
-
- if dependent == :purge_later
- after_destroy_commit { public_send(name).purge_later }
- else
- before_destroy { public_send(name).detach }
- end
-
- ActiveRecord::Reflection.add_attachment_reflection(
- self,
- name,
- ActiveRecord::Reflection.create(:has_one_attached, name, nil, { dependent: dependent }, self)
- )
- end
-
- # Specifies the relation between multiple attachments and the model.
- #
- # class Gallery < ActiveRecord::Base
- # has_many_attached :photos
- # end
- #
- # There are no columns defined on the model side, Active Storage takes
- # care of the mapping between your records and the attachments.
- #
- # To avoid N+1 queries, you can include the attached blobs in your query like so:
- #
- # Gallery.where(user: Current.user).with_attached_photos
- #
- # Under the covers, this relationship is implemented as a +has_many+ association to a
- # ActiveStorage::Attachment record and a +has_many-through+ association to a
- # ActiveStorage::Blob record. These associations are available as +photos_attachments+
- # and +photos_blobs+. But you shouldn't need to work with these associations directly in
- # most circumstances.
- #
- # The system has been designed to having you go through the ActiveStorage::Attached::Many
- # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
- #
- # If the +:dependent+ option isn't set, all the attachments will be purged
- # (i.e. destroyed) whenever the record is destroyed.
- def has_many_attached(name, dependent: :purge_later)
- generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}
- @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
- end
-
- def #{name}=(attachables)
- #{name}.attach(attachables)
- end
- CODE
-
- has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: false do
- def purge
- each(&:purge)
- reset
- end
-
- def purge_later
- each(&:purge_later)
- reset
- end
- end
- has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
-
- scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
-
- if dependent == :purge_later
- after_destroy_commit { public_send(name).purge_later }
- else
- before_destroy { public_send(name).detach }
- end
-
- ActiveRecord::Reflection.add_attachment_reflection(
- self,
- name,
- ActiveRecord::Reflection.create(:has_many_attached, name, nil, { dependent: dependent }, self)
- )
- end
- end
-end
diff --git a/activestorage/lib/active_storage/attached/many.rb b/activestorage/lib/active_storage/attached/many.rb
index d61acb6fad..25f88284df 100644
--- a/activestorage/lib/active_storage/attached/many.rb
+++ b/activestorage/lib/active_storage/attached/many.rb
@@ -9,22 +9,29 @@ module ActiveStorage
#
# All methods called on this proxy object that aren't listed here will automatically be delegated to +attachments+.
def attachments
- record.public_send("#{name}_attachments")
+ change.present? ? change.attachments : record.public_send("#{name}_attachments")
end
- # Associates one or several attachments with the current record, saving them to the database.
+ # Returns all attached blobs.
+ def blobs
+ change.present? ? change.blobs : record.public_send("#{name}_blobs")
+ end
+
+ # Attaches one or more +attachables+ to the record.
+ #
+ # If the record is persisted and unchanged, the attachments are saved to
+ # the database immediately. Otherwise, they'll be saved to the DB when the
+ # record is next saved.
#
# document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
# document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
# document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
# document.images.attach([ first_blob, second_blob ])
def attach(*attachables)
- attachables.flatten.collect do |attachable|
- if record.new_record?
- attachments.build(record: record, blob: create_blob_from(attachable))
- else
- attachments.create!(record: record, blob: create_blob_from(attachable))
- end
+ if record.persisted? && !record.changed?
+ record.update(name => blobs + attachables.flatten)
+ else
+ record.public_send("#{name}=", blobs + attachables.flatten)
end
end
@@ -41,7 +48,7 @@ module ActiveStorage
# Deletes associated attachments without purging them, leaving their respective blobs in place.
def detach
- attachments.destroy_all if attached?
+ attachments.delete_all if attached?
end
##
@@ -50,7 +57,6 @@ module ActiveStorage
# Directly purges each associated attachment (i.e. destroys the blobs and
# attachments and deletes the files on the service).
-
##
# :method: purge_later
#
diff --git a/activestorage/lib/active_storage/attached/model.rb b/activestorage/lib/active_storage/attached/model.rb
new file mode 100644
index 0000000000..ae7f0685f2
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/model.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ # Provides the class-level DSL for declaring an Active Record model's attachments.
+ module Attached::Model
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Specifies the relation between a single attachment and the model.
+ #
+ # class User < ActiveRecord::Base
+ # has_one_attached :avatar
+ # end
+ #
+ # There is no column defined on the model side, Active Storage takes
+ # care of the mapping between your records and the attachment.
+ #
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
+ #
+ # User.with_attached_avatar
+ #
+ # Under the covers, this relationship is implemented as a +has_one+ association to a
+ # ActiveStorage::Attachment record and a +has_one-through+ association to a
+ # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
+ # and +avatar_blob+. But you shouldn't need to work with these associations directly in
+ # most circumstances.
+ #
+ # The system has been designed to having you go through the ActiveStorage::Attached::One
+ # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
+ #
+ # If the +:dependent+ option isn't set, the attachment will be purged
+ # (i.e. destroyed) whenever the record is destroyed.
+ def has_one_attached(name, dependent: :purge_later)
+ generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}
+ @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self)
+ end
+
+ def #{name}=(attachable)
+ attachment_changes["#{name}"] =
+ if attachable.nil?
+ ActiveStorage::Attached::Changes::DeleteOne.new("#{name}", self)
+ else
+ ActiveStorage::Attached::Changes::CreateOne.new("#{name}", self, attachable)
+ end
+ end
+ CODE
+
+ has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: :destroy
+ has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
+
+ scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
+
+ after_save { attachment_changes[name.to_s]&.save }
+
+ after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
+
+ ActiveRecord::Reflection.add_attachment_reflection(
+ self,
+ name,
+ ActiveRecord::Reflection.create(:has_one_attached, name, nil, { dependent: dependent }, self)
+ )
+ end
+
+ # Specifies the relation between multiple attachments and the model.
+ #
+ # class Gallery < ActiveRecord::Base
+ # has_many_attached :photos
+ # end
+ #
+ # There are no columns defined on the model side, Active Storage takes
+ # care of the mapping between your records and the attachments.
+ #
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
+ #
+ # Gallery.where(user: Current.user).with_attached_photos
+ #
+ # Under the covers, this relationship is implemented as a +has_many+ association to a
+ # ActiveStorage::Attachment record and a +has_many-through+ association to a
+ # ActiveStorage::Blob record. These associations are available as +photos_attachments+
+ # and +photos_blobs+. But you shouldn't need to work with these associations directly in
+ # most circumstances.
+ #
+ # The system has been designed to having you go through the ActiveStorage::Attached::Many
+ # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
+ #
+ # If the +:dependent+ option isn't set, all the attachments will be purged
+ # (i.e. destroyed) whenever the record is destroyed.
+ def has_many_attached(name, dependent: :purge_later)
+ generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}
+ @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self)
+ end
+
+ def #{name}=(attachables)
+ attachment_changes["#{name}"] =
+ if attachables.nil? || Array(attachables).none?
+ ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
+ else
+ ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables)
+ end
+ end
+ CODE
+
+ has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy do
+ def purge
+ each(&:purge)
+ reset
+ end
+
+ def purge_later
+ each(&:purge_later)
+ reset
+ end
+ end
+ has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
+
+ scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
+
+ after_save { attachment_changes[name.to_s]&.save }
+
+ after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
+
+ ActiveRecord::Reflection.add_attachment_reflection(
+ self,
+ name,
+ ActiveRecord::Reflection.create(:has_many_attached, name, nil, { dependent: dependent }, self)
+ )
+ end
+ end
+
+ def attachment_changes #:nodoc:
+ @attachment_changes ||= {}
+ end
+
+ def reload(*) #:nodoc:
+ super.tap { @attachment_changes = nil }
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/one.rb b/activestorage/lib/active_storage/attached/one.rb
index f992cb5f84..c039226fcd 100644
--- a/activestorage/lib/active_storage/attached/one.rb
+++ b/activestorage/lib/active_storage/attached/one.rb
@@ -10,30 +10,28 @@ module ActiveStorage
# You don't have to call this method to access the attachment's methods as
# they are all available at the model level.
def attachment
- record.public_send("#{name}_attachment")
+ change.present? ? change.attachment : record.public_send("#{name}_attachment")
end
def blank?
- attachment.blank?
+ !attached?
end
- # Associates a given attachment with the current record, saving it to the database.
+ # Attaches an +attachable+ to the record.
+ #
+ # If the record is persisted and unchanged, the attachment is saved to
+ # the database immediately. Otherwise, it'll be saved to the DB when the
+ # record is next saved.
#
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
# person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
# person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
def attach(attachable)
- blob_was = blob if attached?
- blob = create_blob_from(attachable)
-
- unless blob == blob_was
- transaction do
- detach
- write_attachment build_attachment(blob: blob)
- end
-
- blob_was.purge_later if blob_was && dependent == :purge_later
+ if record.persisted? && !record.changed?
+ record.update(name => attachable)
+ else
+ record.public_send("#{name}=", attachable)
end
end
@@ -51,7 +49,7 @@ module ActiveStorage
# Deletes the attachment without purging it, leaving its blob in place.
def detach
if attached?
- attachment.destroy
+ attachment.delete
write_attachment nil
end
end
@@ -69,16 +67,11 @@ module ActiveStorage
def purge_later
if attached?
attachment.purge_later
+ write_attachment nil
end
end
private
- delegate :transaction, to: :record
-
- def build_attachment(blob:)
- ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
- end
-
def write_attachment(attachment)
record.public_send("#{name}_attachment=", attachment)
end
diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb
index 93c5c55b95..9d6a27eabe 100644
--- a/activestorage/lib/active_storage/engine.rb
+++ b/activestorage/lib/active_storage/engine.rb
@@ -62,7 +62,7 @@ module ActiveStorage
require "active_storage/attached"
ActiveSupport.on_load(:active_record) do
- extend ActiveStorage::Attached::Macros
+ include ActiveStorage::Attached::Model
end
end
diff --git a/activestorage/lib/active_storage/reflection.rb b/activestorage/lib/active_storage/reflection.rb
index 04a1b20882..ce248c88b5 100644
--- a/activestorage/lib/active_storage/reflection.rb
+++ b/activestorage/lib/active_storage/reflection.rb
@@ -19,8 +19,8 @@ module ActiveStorage
end
module ReflectionExtension # :nodoc:
- def add_attachment_reflection(ar, name, reflection)
- ar.attachment_reflections.merge!(name.to_s => reflection)
+ def add_attachment_reflection(model, name, reflection)
+ model.attachment_reflections = model.attachment_reflections.merge(name.to_s => reflection)
end
private
diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb
index b1b6f1ddcf..9f304b7e01 100644
--- a/activestorage/lib/active_storage/service/disk_service.rb
+++ b/activestorage/lib/active_storage/service/disk_service.rb
@@ -117,11 +117,11 @@ module ActiveStorage
{ "Content-Type" => content_type }
end
- private
- def path_for(key)
- File.join root, folder_for(key), key
- end
+ def path_for(key) #:nodoc:
+ File.join root, folder_for(key), key
+ end
+ private
def folder_for(key)
[ key[0..1], key[2..3] ].join("/")
end
diff --git a/activestorage/test/controllers/disk_controller_test.rb b/activestorage/test/controllers/disk_controller_test.rb
index 9af7c83bdf..c053052f6f 100644
--- a/activestorage/test/controllers/disk_controller_test.rb
+++ b/activestorage/test/controllers/disk_controller_test.rb
@@ -6,8 +6,8 @@ require "database/setup"
class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "showing blob inline" do
blob = create_blob
-
get blob.service_url
+ assert_response :ok
assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
assert_equal "text/plain", response.headers["Content-Type"]
assert_equal "Hello world!", response.body
@@ -15,13 +15,22 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "showing blob as attachment" do
blob = create_blob
-
get blob.service_url(disposition: :attachment)
+ assert_response :ok
assert_equal "attachment; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
assert_equal "text/plain", response.headers["Content-Type"]
assert_equal "Hello world!", response.body
end
+ test "showing blob range inline" do
+ blob = create_blob
+ get blob.service_url, headers: { "Range" => "bytes=5-9" }
+ assert_response :partial_content
+ assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
+ assert_equal "text/plain", response.headers["Content-Type"]
+ assert_equal " worl", response.body
+ end
+
test "directly uploading blob with integrity" do
data = "Something else entirely!"
@@ -58,4 +67,10 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
assert_response :unprocessable_entity
assert_not blob.service.exist?(blob.key)
end
+
+ test "directly uploading blob with invalid token" do
+ put update_rails_disk_service_url(encoded_token: "invalid"),
+ params: "Something else entirely!", headers: { "Content-Type" => "text/plain" }
+ assert_response :not_found
+ end
end
diff --git a/activestorage/test/jobs/purge_job_test.rb b/activestorage/test/jobs/purge_job_test.rb
new file mode 100644
index 0000000000..ed4100b78d
--- /dev/null
+++ b/activestorage/test/jobs/purge_job_test.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::PurgeJobTest < ActiveJob::TestCase
+ setup { @blob = create_blob }
+
+ test "purges" do
+ assert_difference -> { ActiveStorage::Blob.count }, -1 do
+ ActiveStorage::PurgeJob.perform_now @blob
+ end
+
+ assert_not ActiveStorage::Blob.exists?(@blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(@blob.key)
+ end
+
+ test "ignores missing blob" do
+ @blob.purge
+
+ perform_enqueued_jobs do
+ assert_nothing_raised do
+ ActiveStorage::PurgeJob.perform_later @blob
+ end
+ end
+ end
+
+ test "ignores attached blob" do
+ User.create! name: "DHH", avatar: @blob
+
+ perform_enqueued_jobs do
+ assert_nothing_raised do
+ ActiveStorage::PurgeJob.perform_later @blob
+ end
+ end
+ end
+end
diff --git a/activestorage/test/models/attached/many_test.rb b/activestorage/test/models/attached/many_test.rb
new file mode 100644
index 0000000000..334254d099
--- /dev/null
+++ b/activestorage/test/models/attached/many_test.rb
@@ -0,0 +1,540 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
+ include ActiveJob::TestHelper
+
+ setup do
+ @user = User.create!(name: "Josh")
+ end
+
+ teardown { ActiveStorage::Blob.all.each(&:delete) }
+
+ test "attaching existing blobs to an existing record" do
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching existing blobs from signed IDs to an existing record" do
+ @user.highlights.attach create_blob(filename: "funky.jpg").signed_id, create_blob(filename: "town.jpg").signed_id
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching new blobs from Hashes to an existing record" do
+ @user.highlights.attach(
+ { io: StringIO.new("STUFF"), filename: "funky.jpg", content_type: "image/jpg" },
+ { io: StringIO.new("THINGS"), filename: "town.jpg", content_type: "image/jpeg" })
+
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching new blobs from uploaded files to an existing record" do
+ @user.highlights.attach fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4")
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching existing blobs to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ assert_not @user.highlights.first.persisted?
+ assert_not @user.highlights.second.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.highlights.reload.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching existing blobs from signed IDs to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.highlights.attach create_blob(filename: "funky.jpg").signed_id, create_blob(filename: "town.jpg").signed_id
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ assert_not @user.highlights.first.persisted?
+ assert_not @user.highlights.second.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.highlights.reload.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching new blobs from Hashes to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.highlights.attach(
+ { io: StringIO.new("STUFF"), filename: "funky.jpg", content_type: "image/jpg" },
+ { io: StringIO.new("THINGS"), filename: "town.jpg", content_type: "image/jpeg" })
+
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ assert_not @user.highlights.first.persisted?
+ assert_not @user.highlights.second.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.highlights.reload.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching new blobs from uploaded files to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.highlights.attach fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4")
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ assert_not @user.highlights.first.persisted?
+ assert_not @user.highlights.second.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "racecar.jpg", @user.highlights.reload.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching existing blobs to an existing record one at a time" do
+ @user.highlights.attach create_blob(filename: "funky.jpg")
+ @user.highlights.attach create_blob(filename: "town.jpg")
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+
+ @user.reload
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "updating an existing record to attach existing blobs" do
+ @user.update! highlights: [ create_file_blob(filename: "racecar.jpg"), create_file_blob(filename: "video.mp4") ]
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ end
+
+ test "updating an existing record to attach existing blobs from signed IDs" do
+ @user.update! highlights: [ create_blob(filename: "funky.jpg").signed_id, create_blob(filename: "town.jpg").signed_id ]
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "successfully updating an existing record to attach new blobs from uploaded files" do
+ @user.highlights = [ fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4") ]
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.second.key)
+
+ @user.save!
+ assert ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ assert ActiveStorage::Blob.service.exist?(@user.highlights.second.key)
+ end
+
+ test "unsuccessfully updating an existing record to attach new blobs from uploaded files" do
+ assert_not @user.update(name: "", highlights: [ fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4") ])
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.second.key)
+ end
+
+ test "replacing existing, dependent attachments on an existing record via assign and attach" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |old_blobs|
+ @user.highlights.attach old_blobs
+
+ @user.highlights = []
+ assert_not @user.highlights.attached?
+
+ perform_enqueued_jobs do
+ @user.highlights.attach create_blob(filename: "whenever.jpg"), create_blob(filename: "wherever.jpg")
+ end
+
+ assert_equal "whenever.jpg", @user.highlights.first.filename.to_s
+ assert_equal "wherever.jpg", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.exists?(old_blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(old_blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(old_blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(old_blobs.second.key)
+ end
+ end
+
+ test "replacing existing, independent attachments on an existing record via assign and attach" do
+ @user.vlogs.attach create_blob(filename: "funky.mp4"), create_blob(filename: "town.mp4")
+
+ @user.vlogs = []
+ assert_not @user.vlogs.attached?
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.vlogs.attach create_blob(filename: "whenever.mp4"), create_blob(filename: "wherever.mp4")
+ end
+
+ assert_equal "whenever.mp4", @user.vlogs.first.filename.to_s
+ assert_equal "wherever.mp4", @user.vlogs.second.filename.to_s
+ end
+
+ test "successfully updating an existing record to replace existing, dependent attachments" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |old_blobs|
+ @user.highlights.attach old_blobs
+
+ perform_enqueued_jobs do
+ @user.update! highlights: [ create_blob(filename: "whenever.jpg"), create_blob(filename: "wherever.jpg") ]
+ end
+
+ assert_equal "whenever.jpg", @user.highlights.first.filename.to_s
+ assert_equal "wherever.jpg", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.exists?(old_blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(old_blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(old_blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(old_blobs.second.key)
+ end
+ end
+
+ test "successfully updating an existing record to replace existing, independent attachments" do
+ @user.vlogs.attach create_blob(filename: "funky.mp4"), create_blob(filename: "town.mp4")
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.update! vlogs: [ create_blob(filename: "whenever.mp4"), create_blob(filename: "wherever.mp4") ]
+ end
+
+ assert_equal "whenever.mp4", @user.vlogs.first.filename.to_s
+ assert_equal "wherever.mp4", @user.vlogs.second.filename.to_s
+ end
+
+ test "unsuccessfully updating an existing record to replace existing attachments" do
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+
+ assert_no_enqueued_jobs do
+ assert_not @user.update(name: "", highlights: [ fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4") ])
+ end
+
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.second.key)
+ end
+
+ test "updating an existing record to attach one new blob and one previously-attached blob" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs.first
+
+ perform_enqueued_jobs do
+ assert_no_changes -> { @user.highlights_attachments.first.id } do
+ @user.update! highlights: blobs
+ end
+ end
+
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ end
+ end
+
+ test "updating an existing record to remove dependent attachments" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+
+ assert_enqueued_with job: ActiveStorage::PurgeJob, args: [ blobs.first ] do
+ assert_enqueued_with job: ActiveStorage::PurgeJob, args: [ blobs.second ] do
+ @user.update! highlights: []
+ end
+ end
+
+ assert_not @user.highlights.attached?
+ end
+ end
+
+ test "updating an existing record to remove independent attachments" do
+ [ create_blob(filename: "funky.mp4"), create_blob(filename: "town.mp4") ].tap do |blobs|
+ @user.vlogs.attach blobs
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.update! vlogs: []
+ end
+
+ assert_not @user.vlogs.attached?
+ end
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to an existing record" do
+ perform_enqueued_jobs do
+ @user.highlights.attach fixture_file_upload("racecar.jpg")
+ end
+
+ assert @user.highlights.reload.first.analyzed?
+ assert_equal 4104, @user.highlights.first.metadata[:width]
+ assert_equal 2736, @user.highlights.first.metadata[:height]
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to an existing record via update" do
+ perform_enqueued_jobs do
+ @user.update! highlights: [ fixture_file_upload("racecar.jpg") ]
+ end
+
+ assert @user.highlights.reload.first.analyzed?
+ assert_equal 4104, @user.highlights.first.metadata[:width]
+ assert_equal 2736, @user.highlights.first.metadata[:height]
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to an existing record" do
+ perform_enqueued_jobs do
+ @user.highlights.attach directly_upload_file_blob(filename: "racecar.jpg")
+ end
+
+ assert @user.highlights.reload.first.analyzed?
+ assert_equal 4104, @user.highlights.first.metadata[:width]
+ assert_equal 2736, @user.highlights.first.metadata[:height]
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to an existing record via update" do
+ perform_enqueued_jobs do
+ @user.update! highlights: [ directly_upload_file_blob(filename: "racecar.jpg") ]
+ end
+
+ assert @user.highlights.reload.first.analyzed?
+ assert_equal 4104, @user.highlights.first.metadata[:width]
+ assert_equal 2736, @user.highlights.first.metadata[:height]
+ end
+
+ test "attaching existing blobs to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+ assert user.new_record?
+ assert_equal "funky.jpg", user.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.highlights.second.filename.to_s
+
+ user.save!
+ assert_equal "funky.jpg", user.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.highlights.second.filename.to_s
+ end
+ end
+
+ test "attaching an existing blob from a signed ID to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach create_blob(filename: "funky.jpg").signed_id
+ assert user.new_record?
+ assert_equal "funky.jpg", user.avatar.filename.to_s
+
+ user.save!
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+ end
+
+ test "attaching new blobs from Hashes to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.highlights.attach(
+ { io: StringIO.new("STUFF"), filename: "funky.jpg", content_type: "image/jpg" },
+ { io: StringIO.new("THINGS"), filename: "town.jpg", content_type: "image/jpg" })
+
+ assert user.new_record?
+ assert user.highlights.first.new_record?
+ assert user.highlights.second.new_record?
+ assert user.highlights.first.blob.new_record?
+ assert user.highlights.second.blob.new_record?
+ assert_equal "funky.jpg", user.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+
+ user.save!
+ assert user.highlights.first.persisted?
+ assert user.highlights.second.persisted?
+ assert user.highlights.first.blob.persisted?
+ assert user.highlights.second.blob.persisted?
+ assert_equal "funky.jpg", user.reload.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.highlights.second.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+ end
+ end
+
+ test "attaching new blobs from uploaded files to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.highlights.attach fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4")
+ assert user.new_record?
+ assert user.highlights.first.new_record?
+ assert user.highlights.second.new_record?
+ assert user.highlights.first.blob.new_record?
+ assert user.highlights.second.blob.new_record?
+ assert_equal "racecar.jpg", user.highlights.first.filename.to_s
+ assert_equal "video.mp4", user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+
+ user.save!
+ assert user.highlights.first.persisted?
+ assert user.highlights.second.persisted?
+ assert user.highlights.first.blob.persisted?
+ assert user.highlights.second.blob.persisted?
+ assert_equal "racecar.jpg", user.reload.highlights.first.filename.to_s
+ assert_equal "video.mp4", user.highlights.second.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+ end
+ end
+
+ test "creating a record with existing blobs attached" do
+ user = User.create!(name: "Jason", highlights: [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ])
+ assert_equal "funky.jpg", user.reload.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.reload.highlights.second.filename.to_s
+ end
+
+ test "creating a record with an existing blob from signed IDs attached" do
+ user = User.create!(name: "Jason", highlights: [
+ create_blob(filename: "funky.jpg").signed_id, create_blob(filename: "town.jpg").signed_id ])
+ assert_equal "funky.jpg", user.reload.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.reload.highlights.second.filename.to_s
+ end
+
+ test "creating a record with new blobs from uploaded files attached" do
+ User.new(name: "Jason", highlights: [ fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4") ]).tap do |user|
+ assert user.new_record?
+ assert user.highlights.first.new_record?
+ assert user.highlights.second.new_record?
+ assert user.highlights.first.blob.new_record?
+ assert user.highlights.second.blob.new_record?
+ assert_equal "racecar.jpg", user.highlights.first.filename.to_s
+ assert_equal "video.mp4", user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+
+ user.save!
+ assert_equal "racecar.jpg", user.highlights.first.filename.to_s
+ assert_equal "video.mp4", user.highlights.second.filename.to_s
+ end
+ end
+
+ test "creating a record with an unexpected object attached" do
+ error = assert_raises(ArgumentError) { User.create!(name: "Jason", highlights: :foo) }
+ assert_equal "Could not find or build blob: expected attachable, got :foo", error.message
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to a new record" do
+ perform_enqueued_jobs do
+ user = User.create!(name: "Jason", highlights: [ fixture_file_upload("racecar.jpg") ])
+ assert user.highlights.reload.first.analyzed?
+ assert_equal 4104, user.highlights.first.metadata[:width]
+ assert_equal 2736, user.highlights.first.metadata[:height]
+ end
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to a new record" do
+ perform_enqueued_jobs do
+ user = User.create!(name: "Jason", highlights: [ directly_upload_file_blob(filename: "racecar.jpg") ])
+ assert user.highlights.reload.first.analyzed?
+ assert_equal 4104, user.highlights.first.metadata[:width]
+ assert_equal 2736, user.highlights.first.metadata[:height]
+ end
+ end
+
+ test "detaching" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+ assert @user.highlights.attached?
+
+ perform_enqueued_jobs do
+ @user.highlights.detach
+ end
+
+ assert_not @user.highlights.attached?
+ assert ActiveStorage::Blob.exists?(blobs.first.id)
+ assert ActiveStorage::Blob.exists?(blobs.second.id)
+ assert ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert ActiveStorage::Blob.service.exist?(blobs.second.key)
+ end
+ end
+
+ test "purging" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+ assert @user.highlights.attached?
+
+ @user.highlights.purge
+ assert_not @user.highlights.attached?
+ assert_not ActiveStorage::Blob.exists?(blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.second.key)
+ end
+ end
+
+ test "purging later" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+ assert @user.highlights.attached?
+
+ perform_enqueued_jobs do
+ @user.highlights.purge_later
+ end
+
+ assert_not @user.highlights.attached?
+ assert_not ActiveStorage::Blob.exists?(blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.second.key)
+ end
+ end
+
+ test "purging dependent attachment later on destroy" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+
+ perform_enqueued_jobs do
+ @user.destroy!
+ end
+
+ assert_not ActiveStorage::Blob.exists?(blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.second.key)
+ end
+ end
+
+ test "not purging independent attachment on destroy" do
+ [ create_blob(filename: "funky.mp4"), create_blob(filename: "town.mp4") ].tap do |blobs|
+ @user.vlogs.attach blobs
+
+ assert_no_enqueued_jobs do
+ @user.destroy!
+ end
+ end
+ end
+
+ test "clearing change on reload" do
+ @user.highlights = [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ]
+ assert @user.highlights.attached?
+
+ @user.reload
+ assert_not @user.highlights.attached?
+ end
+
+ test "overriding attached reader" do
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+
+ begin
+ User.class_eval do
+ def highlights
+ super.reverse
+ end
+ end
+
+ assert_equal "town.jpg", @user.highlights.first.filename.to_s
+ assert_equal "funky.jpg", @user.highlights.second.filename.to_s
+ ensure
+ User.send(:remove_method, :highlights)
+ end
+ end
+end
diff --git a/activestorage/test/models/attached/one_test.rb b/activestorage/test/models/attached/one_test.rb
new file mode 100644
index 0000000000..3333fd9323
--- /dev/null
+++ b/activestorage/test/models/attached/one_test.rb
@@ -0,0 +1,478 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase
+ include ActiveJob::TestHelper
+
+ setup do
+ @user = User.create!(name: "Josh")
+ end
+
+ teardown { ActiveStorage::Blob.all.each(&:delete) }
+
+ test "attaching an existing blob to an existing record" do
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ end
+
+ test "attaching an existing blob from a signed ID to an existing record" do
+ @user.avatar.attach create_blob(filename: "funky.jpg").signed_id
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ end
+
+ test "attaching a new blob from a Hash to an existing record" do
+ @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+ end
+
+ test "attaching a new blob from an uploaded file to an existing record" do
+ @user.avatar.attach fixture_file_upload("racecar.jpg")
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ end
+
+ test "attaching an existing blob to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ assert_not @user.avatar.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.reload.avatar.filename.to_s
+ end
+
+ test "attaching an existing blob from a signed ID to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.avatar.attach create_blob(filename: "funky.jpg").signed_id
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ assert_not @user.avatar.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.reload.avatar.filename.to_s
+ end
+
+ test "attaching a new blob from a Hash to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+ assert_not @user.avatar.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "town.jpg", @user.reload.avatar.filename.to_s
+ end
+
+ test "attaching a new blob from an uploaded file to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.avatar.attach fixture_file_upload("racecar.jpg")
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ assert_not @user.avatar.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "racecar.jpg", @user.reload.avatar.filename.to_s
+ end
+
+ test "updating an existing record to attach an existing blob" do
+ @user.update! avatar: create_blob(filename: "funky.jpg")
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ end
+
+ test "updating an existing record to attach an existing blob from a signed ID" do
+ @user.update! avatar: create_blob(filename: "funky.jpg").signed_id
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ end
+
+ test "successfully updating an existing record to attach a new blob from an uploaded file" do
+ @user.avatar = fixture_file_upload("racecar.jpg")
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.avatar.key)
+
+ @user.save!
+ assert ActiveStorage::Blob.service.exist?(@user.avatar.key)
+ end
+
+ test "unsuccessfully updating an existing record to attach a new blob from an uploaded file" do
+ assert_not @user.update(name: "", avatar: fixture_file_upload("racecar.jpg"))
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.avatar.key)
+ end
+
+ test "successfully replacing an existing, dependent attachment on an existing record" do
+ create_blob(filename: "funky.jpg").tap do |old_blob|
+ @user.avatar.attach old_blob
+
+ perform_enqueued_jobs do
+ @user.avatar.attach create_blob(filename: "town.jpg")
+ end
+
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.exists?(old_blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(old_blob.key)
+ end
+ end
+
+ test "replacing an existing, independent attachment on an existing record" do
+ @user.cover_photo.attach create_blob(filename: "funky.jpg")
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.cover_photo.attach create_blob(filename: "town.jpg")
+ end
+
+ assert_equal "town.jpg", @user.cover_photo.filename.to_s
+ end
+
+ test "replacing an attached blob on an existing record with itself" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ assert_no_changes -> { @user.reload.avatar_attachment.id } do
+ assert_no_enqueued_jobs do
+ @user.avatar.attach blob
+ end
+ end
+
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(@user.avatar.key)
+ end
+ end
+
+ test "successfully updating an existing record to replace an existing, dependent attachment" do
+ create_blob(filename: "funky.jpg").tap do |old_blob|
+ @user.avatar.attach old_blob
+
+ perform_enqueued_jobs do
+ @user.update! avatar: create_blob(filename: "town.jpg")
+ end
+
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.exists?(old_blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(old_blob.key)
+ end
+ end
+
+ test "successfully updating an existing record to replace an existing, independent attachment" do
+ @user.cover_photo.attach create_blob(filename: "funky.jpg")
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.update! cover_photo: create_blob(filename: "town.jpg")
+ end
+
+ assert_equal "town.jpg", @user.cover_photo.filename.to_s
+ end
+
+ test "unsuccessfully updating an existing record to replace an existing attachment" do
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+
+ assert_no_enqueued_jobs do
+ assert_not @user.update(name: "", avatar: fixture_file_upload("racecar.jpg"))
+ end
+
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.avatar.key)
+ end
+
+ test "updating an existing record to replace an attached blob with itself" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ assert_no_enqueued_jobs do
+ assert_no_changes -> { @user.reload.avatar_attachment.id } do
+ @user.update! avatar: blob
+ end
+ end
+ end
+ end
+
+ test "removing a dependent attachment from an existing record" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ assert_enqueued_with job: ActiveStorage::PurgeJob, args: [ blob ] do
+ @user.avatar.attach nil
+ end
+
+ assert_not @user.avatar.attached?
+ end
+ end
+
+ test "removing an independent attachment from an existing record" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.cover_photo.attach blob
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.cover_photo.attach nil
+ end
+
+ assert_not @user.cover_photo.attached?
+ end
+ end
+
+ test "updating an existing record to remove a dependent attachment" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ assert_enqueued_with job: ActiveStorage::PurgeJob, args: [ blob ] do
+ @user.update! avatar: nil
+ end
+
+ assert_not @user.avatar.attached?
+ end
+ end
+
+ test "updating an existing record to remove an independent attachment" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.cover_photo.attach blob
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.update! cover_photo: nil
+ end
+
+ assert_not @user.cover_photo.attached?
+ end
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to an existing record" do
+ perform_enqueued_jobs do
+ @user.avatar.attach fixture_file_upload("racecar.jpg")
+ end
+
+ assert @user.avatar.reload.analyzed?
+ assert_equal 4104, @user.avatar.metadata[:width]
+ assert_equal 2736, @user.avatar.metadata[:height]
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to an existing record via update" do
+ perform_enqueued_jobs do
+ @user.update! avatar: fixture_file_upload("racecar.jpg")
+ end
+
+ assert @user.avatar.reload.analyzed?
+ assert_equal 4104, @user.avatar.metadata[:width]
+ assert_equal 2736, @user.avatar.metadata[:height]
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to an existing record" do
+ perform_enqueued_jobs do
+ @user.avatar.attach directly_upload_file_blob(filename: "racecar.jpg")
+ end
+
+ assert @user.avatar.reload.analyzed?
+ assert_equal 4104, @user.avatar.metadata[:width]
+ assert_equal 2736, @user.avatar.metadata[:height]
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to an existing record via updates" do
+ perform_enqueued_jobs do
+ @user.update! avatar: directly_upload_file_blob(filename: "racecar.jpg")
+ end
+
+ assert @user.avatar.reload.analyzed?
+ assert_equal 4104, @user.avatar.metadata[:width]
+ assert_equal 2736, @user.avatar.metadata[:height]
+ end
+
+ test "attaching an existing blob to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach create_blob(filename: "funky.jpg")
+ assert user.new_record?
+ assert_equal "funky.jpg", user.avatar.filename.to_s
+
+ user.save!
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+ end
+
+ test "attaching an existing blob from a signed ID to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach create_blob(filename: "funky.jpg").signed_id
+ assert user.new_record?
+ assert_equal "funky.jpg", user.avatar.filename.to_s
+
+ user.save!
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+ end
+
+ test "attaching a new blob from a Hash to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
+ assert user.new_record?
+ assert user.avatar.attachment.new_record?
+ assert user.avatar.blob.new_record?
+ assert_equal "town.jpg", user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.avatar.key)
+
+ user.save!
+ assert user.avatar.attachment.persisted?
+ assert user.avatar.blob.persisted?
+ assert_equal "town.jpg", user.reload.avatar.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(user.avatar.key)
+ end
+ end
+
+ test "attaching a new blob from an uploaded file to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach fixture_file_upload("racecar.jpg")
+ assert user.new_record?
+ assert user.avatar.attachment.new_record?
+ assert user.avatar.blob.new_record?
+ assert_equal "racecar.jpg", user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.avatar.key)
+
+ user.save!
+ assert user.avatar.attachment.persisted?
+ assert user.avatar.blob.persisted?
+ assert_equal "racecar.jpg", user.reload.avatar.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(user.avatar.key)
+ end
+ end
+
+ test "creating a record with an existing blob attached" do
+ user = User.create!(name: "Jason", avatar: create_blob(filename: "funky.jpg"))
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+
+ test "creating a record with an existing blob from a signed ID attached" do
+ user = User.create!(name: "Jason", avatar: create_blob(filename: "funky.jpg").signed_id)
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+
+ test "creating a record with a new blob from an uploaded file attached" do
+ User.new(name: "Jason", avatar: fixture_file_upload("racecar.jpg")).tap do |user|
+ assert user.new_record?
+ assert user.avatar.attachment.new_record?
+ assert user.avatar.blob.new_record?
+ assert_equal "racecar.jpg", user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.avatar.key)
+
+ user.save!
+ assert_equal "racecar.jpg", user.reload.avatar.filename.to_s
+ end
+ end
+
+ test "creating a record with an unexpected object attached" do
+ error = assert_raises(ArgumentError) { User.create!(name: "Jason", avatar: :foo) }
+ assert_equal "Could not find or build blob: expected attachable, got :foo", error.message
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to a new record" do
+ perform_enqueued_jobs do
+ user = User.create!(name: "Jason", avatar: fixture_file_upload("racecar.jpg"))
+ assert user.avatar.reload.analyzed?
+ assert_equal 4104, user.avatar.metadata[:width]
+ assert_equal 2736, user.avatar.metadata[:height]
+ end
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to a new record" do
+ perform_enqueued_jobs do
+ user = User.create!(name: "Jason", avatar: directly_upload_file_blob(filename: "racecar.jpg"))
+ assert user.avatar.reload.analyzed?
+ assert_equal 4104, user.avatar.metadata[:width]
+ assert_equal 2736, user.avatar.metadata[:height]
+ end
+ end
+
+ test "detaching" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+ assert @user.avatar.attached?
+
+ perform_enqueued_jobs do
+ @user.avatar.detach
+ end
+
+ assert_not @user.avatar.attached?
+ assert ActiveStorage::Blob.exists?(blob.id)
+ assert ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "purging" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+ assert @user.avatar.attached?
+
+ @user.avatar.purge
+ assert_not @user.avatar.attached?
+ assert_not ActiveStorage::Blob.exists?(blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "purging later" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+ assert @user.avatar.attached?
+
+ perform_enqueued_jobs do
+ @user.avatar.purge_later
+ end
+
+ assert_not @user.avatar.attached?
+ assert_not ActiveStorage::Blob.exists?(blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "purging dependent attachment later on destroy" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ perform_enqueued_jobs do
+ @user.destroy!
+ end
+
+ assert_not ActiveStorage::Blob.exists?(blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "not purging independent attachment on destroy" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.cover_photo.attach blob
+
+ assert_no_enqueued_jobs do
+ @user.destroy!
+ end
+ end
+ end
+
+ test "clearing change on reload" do
+ @user.avatar = create_blob(filename: "funky.jpg")
+ assert @user.avatar.attached?
+
+ @user.reload
+ assert_not @user.avatar.attached?
+ end
+
+ test "overriding attached reader" do
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+
+ begin
+ User.class_eval do
+ def avatar
+ super.filename.to_s.reverse
+ end
+ end
+
+ assert_equal "gpj.yknuf", @user.avatar
+ ensure
+ User.send(:remove_method, :avatar)
+ end
+ end
+end
diff --git a/activestorage/test/models/attached_test.rb b/activestorage/test/models/attached_test.rb
deleted file mode 100644
index b10d2bebe3..0000000000
--- a/activestorage/test/models/attached_test.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-require "test_helper"
-require "database/setup"
-
-class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
- setup do
- @user = User.create!(name: "Josh")
- end
-
- teardown { ActiveStorage::Blob.all.each(&:purge) }
-
- test "overriding has_one_attached methods works" do
- # attach blob before messing with getter, which breaks `#attach`
- @user.avatar.attach create_blob(filename: "funky.jpg")
-
- # inherited only
- assert_equal "funky.jpg", @user.avatar.filename.to_s
-
- begin
- User.class_eval do
- def avatar
- super.filename.to_s.reverse
- end
- end
-
- # override with super
- assert_equal "funky.jpg".reverse, @user.avatar
- ensure
- User.send(:remove_method, :avatar)
- end
- end
-
- test "overriding has_many_attached methods works" do
- # attach blobs before messing with getter, which breaks `#attach`
- @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
-
- # inherited only
- assert_equal "funky.jpg", @user.highlights.first.filename.to_s
- assert_equal "wonky.jpg", @user.highlights.second.filename.to_s
-
- begin
- User.class_eval do
- def highlights
- super.reverse
- end
- end
-
- # override with super
- assert_equal "wonky.jpg", @user.highlights.first.filename.to_s
- assert_equal "funky.jpg", @user.highlights.second.filename.to_s
- ensure
- User.send(:remove_method, :highlights)
- end
- end
-end
diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb
deleted file mode 100644
index ce83ec27d2..0000000000
--- a/activestorage/test/models/attachments_test.rb
+++ /dev/null
@@ -1,459 +0,0 @@
-# frozen_string_literal: true
-
-require "test_helper"
-require "database/setup"
-
-class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
- include ActiveJob::TestHelper
-
- setup { @user = User.create!(name: "DHH") }
-
- teardown { ActiveStorage::Blob.all.each(&:purge) }
-
- test "attach existing blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- assert_equal "funky.jpg", @user.avatar.filename.to_s
- end
-
- test "attach existing blob from a signed ID" do
- @user.avatar.attach create_blob(filename: "funky.jpg").signed_id
- assert_equal "funky.jpg", @user.avatar.filename.to_s
- end
-
- test "attach new blob from a Hash" do
- @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
- assert_equal "town.jpg", @user.avatar.filename.to_s
- end
-
- test "attach new blob from an UploadedFile" do
- file = file_fixture "racecar.jpg"
- @user.avatar.attach Rack::Test::UploadedFile.new file.to_s
- assert_equal "racecar.jpg", @user.avatar.filename.to_s
- end
-
- test "replace attached blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
-
- perform_enqueued_jobs do
- assert_no_difference -> { ActiveStorage::Blob.count } do
- @user.avatar.attach create_blob(filename: "town.jpg")
- end
- end
-
- assert_equal "town.jpg", @user.avatar.filename.to_s
- end
-
- test "replace attached blob unsuccessfully" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
-
- perform_enqueued_jobs do
- assert_raises do
- @user.avatar.attach nil
- end
- end
-
- assert_equal "funky.jpg", @user.reload.avatar.filename.to_s
- assert ActiveStorage::Blob.service.exist?(@user.avatar.key)
- end
-
- test "replace attached blob with itself" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
-
- assert_no_changes -> { @user.reload.avatar.blob } do
- assert_no_changes -> { @user.reload.avatar.attachment } do
- assert_no_enqueued_jobs do
- @user.avatar.attach @user.avatar.blob
- end
- end
- end
- end
-
- test "replaced attached blob with itself by signed ID" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
-
- assert_no_changes -> { @user.reload.avatar.blob } do
- assert_no_changes -> { @user.reload.avatar.attachment } do
- assert_no_enqueued_jobs do
- @user.avatar.attach @user.avatar.blob.signed_id
- end
- end
- end
- end
-
- test "replace independent attached blob" do
- @user.cover_photo.attach create_blob(filename: "funky.jpg")
-
- perform_enqueued_jobs do
- assert_difference -> { ActiveStorage::Blob.count }, +1 do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- @user.cover_photo.attach create_blob(filename: "town.jpg")
- end
- end
- end
-
- assert_equal "town.jpg", @user.cover_photo.filename.to_s
- end
-
- test "attach blob to new record" do
- user = User.new(name: "Jason")
-
- assert_no_changes -> { user.new_record? } do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- user.avatar.attach create_blob(filename: "funky.jpg")
- end
- end
-
- assert_predicate user.avatar, :attached?
- assert_equal "funky.jpg", user.avatar.filename.to_s
-
- assert_difference -> { ActiveStorage::Attachment.count }, +1 do
- user.save!
- end
-
- assert_predicate user.reload.avatar, :attached?
- assert_equal "funky.jpg", user.avatar.filename.to_s
- end
-
- test "build new record with attached blob" do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- @user = User.new(name: "Jason", avatar: { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" })
- end
-
- assert_predicate @user, :new_record?
- assert_predicate @user.avatar, :attached?
- assert_equal "town.jpg", @user.avatar.filename.to_s
-
- @user.save!
- assert_predicate @user.reload.avatar, :attached?
- assert_equal "town.jpg", @user.avatar.filename.to_s
- end
-
- test "access underlying associations of new blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- assert_equal @user, @user.avatar_attachment.record
- assert_equal @user.avatar_attachment.blob, @user.avatar_blob
- assert_equal "funky.jpg", @user.avatar_attachment.blob.filename.to_s
- end
-
- test "identify newly-attached, directly-uploaded blob" do
- blob = directly_upload_file_blob(content_type: "application/octet-stream")
-
- @user.avatar.attach(blob)
-
- assert_equal "image/jpeg", @user.avatar.reload.content_type
- assert_predicate @user.avatar, :identified?
- end
-
- test "identify and analyze newly-attached, directly-uploaded blob" do
- blob = directly_upload_file_blob(content_type: "application/octet-stream")
-
- perform_enqueued_jobs do
- @user.avatar.attach blob
- end
-
- assert_equal true, @user.avatar.reload.metadata[:identified]
- assert_equal 4104, @user.avatar.metadata[:width]
- assert_equal 2736, @user.avatar.metadata[:height]
- end
-
- test "identify newly-attached blob only once" do
- blob = create_file_blob
- assert_predicate blob, :identified?
-
- # The blob's backing file is a PNG image. Fudge its content type so we can tell if it's identified when we attach it.
- blob.update! content_type: "application/octet-stream"
-
- @user.avatar.attach blob
- assert_equal "application/octet-stream", blob.content_type
- end
-
- test "analyze newly-attached blob" do
- perform_enqueued_jobs do
- @user.avatar.attach create_file_blob
- end
-
- assert_equal 4104, @user.avatar.reload.metadata[:width]
- assert_equal 2736, @user.avatar.metadata[:height]
- end
-
- test "analyze attached blob only once" do
- blob = create_file_blob
-
- perform_enqueued_jobs do
- @user.avatar.attach blob
- end
-
- assert_predicate blob.reload, :analyzed?
-
- @user.avatar.detach
-
- assert_no_enqueued_jobs do
- @user.reload.avatar.attach blob
- end
- end
-
- test "preserve existing metadata when analyzing a newly-attached blob" do
- blob = create_file_blob(metadata: { foo: "bar" })
-
- perform_enqueued_jobs do
- @user.avatar.attach blob
- end
-
- assert_equal "bar", blob.reload.metadata[:foo]
- end
-
- test "detach blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- avatar_blob_id = @user.avatar.blob.id
- avatar_key = @user.avatar.key
-
- @user.avatar.detach
- assert_not_predicate @user.avatar, :attached?
- assert ActiveStorage::Blob.exists?(avatar_blob_id)
- assert ActiveStorage::Blob.service.exist?(avatar_key)
- end
-
- test "purge attached blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- avatar_key = @user.avatar.key
-
- @user.avatar.purge
- assert_not_predicate @user.avatar, :attached?
- assert_not ActiveStorage::Blob.service.exist?(avatar_key)
- end
-
- test "purge attached blob later when the record is destroyed" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- avatar_key = @user.avatar.key
-
- perform_enqueued_jobs do
- @user.reload.destroy
-
- assert_nil ActiveStorage::Blob.find_by(key: avatar_key)
- assert_not ActiveStorage::Blob.service.exist?(avatar_key)
- end
- end
-
- test "delete attachment for independent blob when record is destroyed" do
- @user.cover_photo.attach create_blob(filename: "funky.jpg")
-
- @user.destroy
- assert_not ActiveStorage::Attachment.exists?(record: @user, name: "cover_photo")
- end
-
- test "find with attached blob" do
- records = %w[alice bob].map do |name|
- User.create!(name: name).tap do |user|
- user.avatar.attach create_blob(filename: "#{name}.jpg")
- end
- end
-
- users = User.where(id: records.map(&:id)).with_attached_avatar.all
-
- assert_equal "alice.jpg", users.first.avatar.filename.to_s
- assert_equal "bob.jpg", users.second.avatar.filename.to_s
- end
-
-
- test "attach existing blobs" do
- @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
-
- assert_equal "funky.jpg", @user.highlights.first.filename.to_s
- assert_equal "wonky.jpg", @user.highlights.second.filename.to_s
- end
-
- test "attach new blobs" do
- @user.highlights.attach(
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" })
-
- assert_equal "town.jpg", @user.highlights.first.filename.to_s
- assert_equal "country.jpg", @user.highlights.second.filename.to_s
- end
-
- test "attach blobs to new record" do
- user = User.new(name: "Jason")
-
- assert_no_changes -> { user.new_record? } do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- user.highlights.attach(
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" })
- end
- end
-
- assert_predicate user.highlights, :attached?
- assert_equal "town.jpg", user.highlights.first.filename.to_s
- assert_equal "country.jpg", user.highlights.second.filename.to_s
-
- assert_difference -> { ActiveStorage::Attachment.count }, +2 do
- user.save!
- end
-
- assert_predicate user.reload.highlights, :attached?
- assert_equal "town.jpg", user.highlights.first.filename.to_s
- assert_equal "country.jpg", user.highlights.second.filename.to_s
- end
-
- test "build new record with attached blobs" do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- @user = User.new(name: "Jason", highlights: [
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }])
- end
-
- assert_predicate @user, :new_record?
- assert_predicate @user.highlights, :attached?
- assert_equal "town.jpg", @user.highlights.first.filename.to_s
- assert_equal "country.jpg", @user.highlights.second.filename.to_s
-
- @user.save!
- assert_predicate @user.reload.highlights, :attached?
- assert_equal "town.jpg", @user.highlights.first.filename.to_s
- assert_equal "country.jpg", @user.highlights.second.filename.to_s
- end
-
- test "find attached blobs" do
- @user.highlights.attach(
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" })
-
- highlights = User.where(id: @user.id).with_attached_highlights.first.highlights
-
- assert_equal "town.jpg", highlights.first.filename.to_s
- assert_equal "country.jpg", highlights.second.filename.to_s
- end
-
- test "access underlying associations of new blobs" do
- @user.highlights.attach(
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" })
-
- assert_equal @user, @user.highlights_attachments.first.record
- assert_equal @user.highlights_attachments.collect(&:blob).sort, @user.highlights_blobs.sort
- assert_equal "town.jpg", @user.highlights_attachments.first.blob.filename.to_s
- end
-
- test "analyze newly-attached blobs" do
- perform_enqueued_jobs do
- @user.highlights.attach(
- create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg"),
- create_file_blob(filename: "video.mp4", content_type: "video/mp4"))
- end
-
- assert_equal 4104, @user.highlights.first.metadata[:width]
- assert_equal 2736, @user.highlights.first.metadata[:height]
-
- assert_equal 640, @user.highlights.second.metadata[:width]
- assert_equal 480, @user.highlights.second.metadata[:height]
- end
-
- test "analyze attached blobs only once" do
- blobs = [
- create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg"),
- create_file_blob(filename: "video.mp4", content_type: "video/mp4")
- ]
-
- perform_enqueued_jobs do
- @user.highlights.attach(blobs)
- end
-
- assert blobs.each(&:reload).all?(&:analyzed?)
-
- @user.highlights.attachments.destroy_all
-
- assert_no_enqueued_jobs do
- @user.highlights.attach(blobs)
- end
- end
-
- test "preserve existing metadata when analyzing newly-attached blobs" do
- blobs = [
- create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: { foo: "bar" }),
- create_file_blob(filename: "video.mp4", content_type: "video/mp4", metadata: { foo: "bar" })
- ]
-
- perform_enqueued_jobs do
- @user.highlights.attach(blobs)
- end
-
- blobs.each do |blob|
- assert_equal "bar", blob.reload.metadata[:foo]
- end
- end
-
- test "detach blobs" do
- @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
- highlight_blob_ids = @user.highlights.collect { |highlight| highlight.blob.id }
- highlight_keys = @user.highlights.collect(&:key)
-
- @user.highlights.detach
- assert_not_predicate @user.highlights, :attached?
-
- assert ActiveStorage::Blob.exists?(highlight_blob_ids.first)
- assert ActiveStorage::Blob.exists?(highlight_blob_ids.second)
-
- assert ActiveStorage::Blob.service.exist?(highlight_keys.first)
- assert ActiveStorage::Blob.service.exist?(highlight_keys.second)
- end
-
- test "purge attached blobs" do
- @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
- highlight_keys = @user.highlights.collect(&:key)
-
- @user.highlights.purge
- assert_not_predicate @user.highlights, :attached?
- assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first)
- assert_not ActiveStorage::Blob.service.exist?(highlight_keys.second)
- end
-
- test "purge attached blobs later when the record is destroyed" do
- @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
- highlight_keys = @user.highlights.collect(&:key)
-
- perform_enqueued_jobs do
- @user.reload.destroy
-
- assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.first)
- assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first)
-
- assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.second)
- assert_not ActiveStorage::Blob.service.exist?(highlight_keys.second)
- end
- end
-
- test "delete attachments for independent blobs when the record is destroyed" do
- @user.vlogs.attach create_blob(filename: "funky.mp4"), create_blob(filename: "wonky.mp4")
-
- @user.destroy
- assert_not ActiveStorage::Attachment.exists?(record: @user, name: "vlogs")
- end
-
- test "selectively purge one attached blob of many" do
- first_blob = create_blob(filename: "funky.jpg")
- second_blob = create_blob(filename: "wonky.jpg")
- attachments = @user.highlights.attach(first_blob, second_blob)
-
- assert_difference -> { ActiveStorage::Blob.count }, -1 do
- @user.highlights.where(id: attachments.first.id).purge
- end
-
- assert_not ActiveStorage::Blob.exists?(key: first_blob.key)
- assert ActiveStorage::Blob.exists?(key: second_blob.key)
- end
-
- test "selectively purge one attached blob of many later" do
- first_blob = create_blob(filename: "funky.jpg")
- second_blob = create_blob(filename: "wonky.jpg")
- attachments = @user.highlights.attach(first_blob, second_blob)
-
- perform_enqueued_jobs do
- assert_difference -> { ActiveStorage::Blob.count }, -1 do
- @user.highlights.where(id: attachments.first.id).purge_later
- end
- end
-
- assert_not ActiveStorage::Blob.exists?(key: first_blob.key)
- assert ActiveStorage::Blob.exists?(key: second_blob.key)
- end
-end
diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb
index 40b30acd3e..c2e7aae13a 100644
--- a/activestorage/test/models/blob_test.rb
+++ b/activestorage/test/models/blob_test.rb
@@ -7,21 +7,15 @@ require "active_support/testing/method_call_assertions"
class ActiveStorage::BlobTest < ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
- test ".unattached scope returns not attached blobs" do
- class UserWithHasOneAttachedDependentFalse < User
- has_one_attached :avatar, dependent: false
+ test "unattached scope" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ User.create! name: "DHH", avatar: blobs.first
+ assert_includes ActiveStorage::Blob.unattached, blobs.second
+ assert_not_includes ActiveStorage::Blob.unattached, blobs.first
+
+ User.create! name: "Jason", avatar: blobs.second
+ assert_not_includes ActiveStorage::Blob.unattached, blobs.second
end
-
- ActiveStorage::Blob.delete_all
- blob_1 = create_blob filename: "funky.jpg"
- blob_2 = create_blob filename: "town.jpg"
-
- user = UserWithHasOneAttachedDependentFalse.create!
- user.avatar.attach blob_1
-
- assert_equal [blob_2], ActiveStorage::Blob.unattached
- user.destroy
- assert_equal [blob_1, blob_2].map(&:id).sort, ActiveStorage::Blob.unattached.pluck(:id).sort
end
test "create after upload sets byte size and checksum" do
@@ -180,6 +174,14 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
assert_not ActiveStorage::Blob.service.exist?(variant.key)
end
+ test "purge fails when attachments exist" do
+ create_blob.tap do |blob|
+ User.create! name: "DHH", avatar: blob
+ assert_raises(ActiveRecord::InvalidForeignKey) { blob.purge }
+ assert ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
private
def expected_url_for(blob, disposition: :inline, filename: nil)
filename ||= blob.filename
diff --git a/activestorage/test/models/presence_validation_test.rb b/activestorage/test/models/presence_validation_test.rb
index aa804506dd..13ba3c900d 100644
--- a/activestorage/test/models/presence_validation_test.rb
+++ b/activestorage/test/models/presence_validation_test.rb
@@ -12,7 +12,7 @@ class ActiveStorage::PresenceValidationTest < ActiveSupport::TestCase
test "validates_presence_of has_one_attached" do
Admin.validates_presence_of :avatar
- a = Admin.new
+ a = Admin.new(name: "DHH")
assert_predicate a, :invalid?
a.avatar.attach create_blob(filename: "funky.jpg")
@@ -21,7 +21,7 @@ class ActiveStorage::PresenceValidationTest < ActiveSupport::TestCase
test "validates_presence_of has_many_attached" do
Admin.validates_presence_of :highlights
- a = Admin.new
+ a = Admin.new(name: "DHH")
assert_predicate a, :invalid?
a.highlights.attach create_blob(filename: "funky.jpg")
diff --git a/activestorage/test/models/reflection_test.rb b/activestorage/test/models/reflection_test.rb
index da866ca996..98606b0617 100644
--- a/activestorage/test/models/reflection_test.rb
+++ b/activestorage/test/models/reflection_test.rb
@@ -3,27 +3,32 @@
require "test_helper"
class ActiveStorage::ReflectionTest < ActiveSupport::TestCase
- test "allows reflecting for all attachment" do
- expected_classes =
- User.reflect_on_all_attachments.all? do |reflection|
- reflection.is_a?(ActiveStorage::Reflection::HasOneAttachedReflection) ||
- reflection.is_a?(ActiveStorage::Reflection::HasManyAttachedReflection)
- end
-
- assert expected_classes
- end
-
- test "allows reflecting on a singular has_one_attached attachment" do
+ test "reflecting on a singular attachment" do
reflection = User.reflect_on_attachment(:avatar)
-
+ assert_equal User, reflection.active_record
assert_equal :avatar, reflection.name
assert_equal :has_one_attached, reflection.macro
+ assert_equal :purge_later, reflection.options[:dependent]
end
- test "allows reflecting on a singular has_many_attached attachment" do
- reflection = User.reflect_on_attachment(:highlights)
+ test "reflection on a singular attachment with the same name as an attachment on another model" do
+ reflection = Group.reflect_on_attachment(:avatar)
+ assert_equal Group, reflection.active_record
+ end
+ test "reflecting on a collection attachment" do
+ reflection = User.reflect_on_attachment(:highlights)
+ assert_equal User, reflection.active_record
assert_equal :highlights, reflection.name
assert_equal :has_many_attached, reflection.macro
+ assert_equal :purge_later, reflection.options[:dependent]
+ end
+
+ test "reflecting on all attachments" do
+ reflections = User.reflect_on_all_attachments.sort_by(&:name)
+ assert_equal [ User ], reflections.collect(&:active_record).uniq
+ assert_equal %i[ avatar cover_photo highlights vlogs ], reflections.collect(&:name)
+ assert_equal %i[ has_one_attached has_one_attached has_many_attached has_many_attached ], reflections.collect(&:macro)
+ assert_equal [ :purge_later, false, :purge_later, false ], reflections.collect { |reflection| reflection.options[:dependent] }
end
end
diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb
index 573a8e0b0b..7b7926ac79 100644
--- a/activestorage/test/test_helper.rb
+++ b/activestorage/test/test_helper.rb
@@ -79,6 +79,10 @@ class ActiveSupport::TestCase
def extract_metadata_from(blob)
blob.tap(&:analyze).metadata
end
+
+ def fixture_file_upload(filename)
+ Rack::Test::UploadedFile.new file_fixture(filename).to_s
+ end
end
require "global_id"
@@ -86,9 +90,15 @@ GlobalID.app = "ActiveStorageExampleApp"
ActiveRecord::Base.send :include, GlobalID::Identification
class User < ActiveRecord::Base
+ validates :name, presence: true
+
has_one_attached :avatar
has_one_attached :cover_photo, dependent: false
has_many_attached :highlights
has_many_attached :vlogs, dependent: false
end
+
+class Group < ActiveRecord::Base
+ has_one_attached :avatar
+end
diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb
index abb461966a..bac6ff9c97 100644
--- a/activesupport/lib/active_support/core_ext/object/to_query.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_query.rb
@@ -75,11 +75,14 @@ class Hash
#
# This method is also aliased as +to_param+.
def to_query(namespace = nil)
- collect do |key, value|
+ query = collect do |key, value|
unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
end
- end.compact.sort! * "&"
+ end.compact
+
+ query.sort! unless namespace.to_s.include?("[]")
+ query.join("&")
end
alias_method :to_param, :to_query
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 6a56da384f..b27ac7ce99 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -113,11 +113,23 @@ module ActiveSupport
# post :create, params: { article: invalid_attributes }
# end
#
+ # A lambda can be passed in and evaluated.
+ #
+ # assert_no_difference -> { Article.count } do
+ # post :create, params: { article: invalid_attributes }
+ # end
+ #
# An error message can be specified.
#
# assert_no_difference 'Article.count', 'An Article should not be created' do
# post :create, params: { article: invalid_attributes }
# end
+ #
+ # An array of expressions can also be passed in and evaluated.
+ #
+ # assert_no_difference [ 'Article.count', -> { Post.count } ] do
+ # post :create, params: { article: invalid_attributes }
+ # end
def assert_no_difference(expression, message = nil, &block)
assert_difference expression, 0, message, &block
end
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index 7593bcfa4d..b0b7ef0913 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -77,6 +77,20 @@ class ToQueryTest < ActiveSupport::TestCase
assert_equal "name=Nakshay&type=human", hash.to_query
end
+ def test_hash_not_sorted_lexicographically_for_nested_structure
+ params = {
+ "foo" => {
+ "contents" => [
+ { "name" => "gorby", "id" => "123" },
+ { "name" => "puff", "d" => "true" }
+ ]
+ }
+ }
+ expected = "foo[contents][][name]=gorby&foo[contents][][id]=123&foo[contents][][name]=puff&foo[contents][][d]=true"
+
+ assert_equal expected, URI.decode(params.to_query)
+ end
+
private
def assert_query_equal(expected, actual)
assert_equal expected.split("&"), actual.to_query.split("&")
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 19901fad99..8698c66e6d 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -52,6 +52,22 @@ class AssertionsTest < ActiveSupport::TestCase
assert_equal "Object Changed.\n\"@object.num\" didn't change by 0.\nExpected: 0\n Actual: 1", error.message
end
+ def test_assert_no_difference_with_multiple_expressions_pass
+ another_object = @object.dup
+ assert_no_difference ["@object.num", -> { another_object.num }] do
+ # ...
+ end
+ end
+
+ def test_assert_no_difference_with_multiple_expressions_fail
+ another_object = @object.dup
+ assert_raises(Minitest::Assertion) do
+ assert_no_difference ["@object.num", -> { another_object.num }], "Another Object Changed" do
+ another_object.increment
+ end
+ end
+ end
+
def test_assert_difference
assert_difference "@object.num", +1 do
@object.increment
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index 00d4bcb21e..cd355b1d1a 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -33,6 +33,13 @@ pre, code {
overflow: auto;
color: #222;
}
+
+p code {
+ background: #eee;
+ border-radius: 2px;
+ padding: 1px 3px;
+}
+
pre, tt, code {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
@@ -632,7 +639,9 @@ div.code_container {
margin: 0.25em 0 1.5em 0;
}
-.note code, .info code, .todo code {border:none; background: none; padding: 0;}
+.note code, .info code, .todo code {
+ background: #fff;
+}
#mainCol ul li {
list-style:none;
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index 005331977e..78a7c64afc 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -1,11 +1,11 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 2.2 Release Notes
===============================
Rails 2.2 delivers a number of new and improved features. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the [list of commits](https://github.com/rails/rails/commits/2-2-stable) in the main Rails repository on GitHub.
-Along with Rails, 2.2 marks the launch of the [Ruby on Rails Guides](http://guides.rubyonrails.org/), the first results of the ongoing [Rails Guides hackfest](http://hackfest.rubyonrails.org/guide). This site will deliver high-quality documentation of the major features of Rails.
+Along with Rails, 2.2 marks the launch of the [Ruby on Rails Guides](https://guides.rubyonrails.org/), the first results of the ongoing [Rails Guides hackfest](http://hackfest.rubyonrails.org/guide). This site will deliver high-quality documentation of the major features of Rails.
--------------------------------------------------------------------------------
@@ -31,7 +31,7 @@ Along with thread safety, a lot of work has been done to make Rails work well wi
Documentation
-------------
-The internal documentation of Rails, in the form of code comments, has been improved in numerous places. In addition, the [Ruby on Rails Guides](http://guides.rubyonrails.org/) project is the definitive source for information on major Rails components. In its first official release, the Guides page includes:
+The internal documentation of Rails, in the form of code comments, has been improved in numerous places. In addition, the [Ruby on Rails Guides](https://guides.rubyonrails.org/) project is the definitive source for information on major Rails components. In its first official release, the Guides page includes:
* [Getting Started with Rails](getting_started.html)
* [Rails Database Migrations](active_record_migrations.html)
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 2b8c9351e8..f85415ee42 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 2.3 Release Notes
===============================
@@ -52,7 +52,7 @@ After some versions without an upgrade, Rails 2.3 offers some new features for R
Documentation
-------------
-The [Ruby on Rails guides](http://guides.rubyonrails.org/) project has published several additional guides for Rails 2.3. In addition, a [separate site](http://edgeguides.rubyonrails.org/) maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the [Rails wiki](http://newwiki.rubyonrails.org/) and early planning for a Rails Book.
+The [Ruby on Rails guides](https://guides.rubyonrails.org/) project has published several additional guides for Rails 2.3. In addition, a [separate site](http://edgeguides.rubyonrails.org/) maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the [Rails wiki](http://newwiki.rubyonrails.org/) and early planning for a Rails Book.
* More Information: [Rails Documentation Projects](https://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects)
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index f4b5eb3c4c..9d15dfb2aa 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 3.0 Release Notes
===============================
@@ -153,7 +153,7 @@ More information: - [New Action Mailer API in Rails 3](http://lindsaar.net/2010/
Documentation
-------------
-The documentation in the Rails tree is being updated with all the API changes, additionally, the [Rails Edge Guides](http://edgeguides.rubyonrails.org/) are being updated one by one to reflect the changes in Rails 3.0. The guides at [guides.rubyonrails.org](http://guides.rubyonrails.org/) however will continue to contain only the stable version of Rails (at this point, version 2.3.5, until 3.0 is released).
+The documentation in the Rails tree is being updated with all the API changes, additionally, the [Rails Edge Guides](http://edgeguides.rubyonrails.org/) are being updated one by one to reflect the changes in Rails 3.0. The guides at [guides.rubyonrails.org](https://guides.rubyonrails.org/) however will continue to contain only the stable version of Rails (at this point, version 2.3.5, until 3.0 is released).
More Information: - [Rails Documentation Projects](https://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects)
diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md
index 17d4ac23b6..8c3dc3454d 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 3.1 Release Notes
===============================
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index ae6eb27f35..d4c9bf357d 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 3.2 Release Notes
===============================
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index a1a6a225b2..eaae695dff 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 4.0 Release Notes
===============================
@@ -55,7 +55,7 @@ $ ruby /path/to/rails/railties/bin/rails new myapp --dev
Major Features
--------------
-[![Rails 4.0](images/4_0_release_notes/rails4_features.png)](http://guides.rubyonrails.org/images/4_0_release_notes/rails4_features.png)
+[![Rails 4.0](images/4_0_release_notes/rails4_features.png)](https://guides.rubyonrails.org/images/4_0_release_notes/rails4_features.png)
### Upgrade
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
index 2c5e665e33..0c7bd01cac 100644
--- a/guides/source/4_1_release_notes.md
+++ b/guides/source/4_1_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 4.1 Release Notes
===============================
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index 7105df5634..f7c40d19e9 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 4.2 Release Notes
===============================
@@ -446,7 +446,7 @@ Please refer to the [Changelog][action-pack] for detailed changes.
moved to the `responders` gem (version 2.0). Add `gem 'responders', '~> 2.0'`
to your `Gemfile` to continue using these features.
([Pull Request](https://github.com/rails/rails/pull/16526),
- [More Details](http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#responders))
+ [More Details](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#responders))
* Removed deprecated `AbstractController::Helpers::ClassMethods::MissingHelperError`
in favor of `AbstractController::Helpers::MissingHelperError`.
@@ -545,7 +545,7 @@ Please refer to the [Changelog][action-pack] for detailed changes.
served if the client supports it and a pre-generated gzip file (`.gz`) is on disk.
By default the asset pipeline generates `.gz` files for all compressible assets.
Serving gzip files minimizes data transfer and speeds up asset requests. Always
- [use a CDN](http://guides.rubyonrails.org/asset_pipeline.html#cdns) if you are
+ [use a CDN](https://guides.rubyonrails.org/asset_pipeline.html#cdns) if you are
serving assets from your Rails server in production.
([Pull Request](https://github.com/rails/rails/pull/16466))
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index 04d4bd75cd..e57ef03518 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 5.0 Release Notes
===============================
diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md
index 68c120fd78..d26d3d3b95 100644
--- a/guides/source/5_1_release_notes.md
+++ b/guides/source/5_1_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 5.1 Release Notes
===============================
diff --git a/guides/source/5_2_release_notes.md b/guides/source/5_2_release_notes.md
index ab24c7e590..c5b914fffc 100644
--- a/guides/source/5_2_release_notes.md
+++ b/guides/source/5_2_release_notes.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails 5.2 Release Notes
===============================
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 5dd6bfdd23..bf00ee08e5 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -6,7 +6,7 @@
</p>
<p>
If you are looking for the ones for the stable version, please check
- <a href="http://guides.rubyonrails.org">http://guides.rubyonrails.org</a> instead.
+ <a href="https://guides.rubyonrails.org">https://guides.rubyonrails.org</a> instead.
</p>
<% else %>
<p>
@@ -16,14 +16,14 @@
<% end %>
<p>
The guides for earlier releases:
-<a href="http://guides.rubyonrails.org/v5.2/">Rails 5.2</a>,
-<a href="http://guides.rubyonrails.org/v5.1/">Rails 5.1</a>,
-<a href="http://guides.rubyonrails.org/v5.0/">Rails 5.0</a>,
-<a href="http://guides.rubyonrails.org/v4.2/">Rails 4.2</a>,
-<a href="http://guides.rubyonrails.org/v4.1/">Rails 4.1</a>,
-<a href="http://guides.rubyonrails.org/v4.0/">Rails 4.0</a>,
-<a href="http://guides.rubyonrails.org/v3.2/">Rails 3.2</a>,
-<a href="http://guides.rubyonrails.org/v3.1/">Rails 3.1</a>,
-<a href="http://guides.rubyonrails.org/v3.0/">Rails 3.0</a>, and
-<a href="http://guides.rubyonrails.org/v2.3/">Rails 2.3</a>.
+<a href="https://guides.rubyonrails.org/v5.2/">Rails 5.2</a>,
+<a href="https://guides.rubyonrails.org/v5.1/">Rails 5.1</a>,
+<a href="https://guides.rubyonrails.org/v5.0/">Rails 5.0</a>,
+<a href="https://guides.rubyonrails.org/v4.2/">Rails 4.2</a>,
+<a href="https://guides.rubyonrails.org/v4.1/">Rails 4.1</a>,
+<a href="https://guides.rubyonrails.org/v4.0/">Rails 4.0</a>,
+<a href="https://guides.rubyonrails.org/v3.2/">Rails 3.2</a>,
+<a href="https://guides.rubyonrails.org/v3.1/">Rails 3.1</a>,
+<a href="https://guides.rubyonrails.org/v3.0/">Rails 3.0</a>, and
+<a href="https://guides.rubyonrails.org/v2.3/">Rails 2.3</a>.
</p>
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index c250db2e0c..14c859994c 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Action Cable Overview
=====================
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index dee0182a30..7ce1f5c2a3 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Action Controller Overview
==========================
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 6e75d4c9da..6c5f03ab38 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Action Mailer Basics
====================
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 5e70f68f9e..495ae9d267 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Action View Overview
====================
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index a86dd00901..4dc69ef911 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Job Basics
=================
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 4b0ea32d7c..2e1bb1a23d 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Model Basics
===================
@@ -459,17 +459,18 @@ features out of the box.
`ActiveModel::SecurePassword` provides a way to securely store any
password in an encrypted form. When you include this module, a
`has_secure_password` class method is provided which defines
-a `password` accessor with certain validations on it.
+a `password` accessor with certain validations on it by default.
#### Requirements
`ActiveModel::SecurePassword` depends on [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'),
so include this gem in your `Gemfile` to use `ActiveModel::SecurePassword` correctly.
-In order to make this work, the model must have an accessor named `password_digest`.
-The `has_secure_password` will add the following validations on the `password` accessor:
+In order to make this work, the model must have an accessor named `XXX_digest`.
+Where `XXX` is the attribute name of your desired password.
+The following validations are added automatically:
1. Password should be present.
-2. Password should be equal to its confirmation (provided `password_confirmation` is passed along).
+2. Password should be equal to its confirmation (provided `XXX_confirmation` is passed along).
3. The maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends)
#### Examples
@@ -478,7 +479,9 @@ The `has_secure_password` will add the following validations on the `password` a
class Person
include ActiveModel::SecurePassword
has_secure_password
- attr_accessor :password_digest
+ has_secure_password :recovery_password, validations: false
+
+ attr_accessor :password_digest, :recovery_password_digest
end
person = Person.new
@@ -502,4 +505,17 @@ person.valid? # => true
# When all validations are passed.
person.password = person.password_confirmation = 'aditya'
person.valid? # => true
+
+person.recovery_password = "42password"
+
+person.authenticate('aditya') # => person
+person.authenticate('notright') # => false
+person.authenticate_password('aditya') # => person
+person.authenticate_password('notright') # => false
+
+person.authenticate_recovery_password('42password') # => person
+person.authenticate_recovery_password('notright') # => false
+
+person.password_digest # => "$2a$04$gF8RfZdoXHvyTjHhiU4ZsO.kQqV9oonYZu31PRE4hLQn3xM2qkpIy"
+person.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
```
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index 182bc865f0..d90ea2e26a 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Record Basics
====================
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 0f74daace6..5b06ff78bb 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Record Callbacks
=======================
@@ -184,9 +184,9 @@ class Company < ApplicationRecord
after_touch :log_when_employees_or_company_touched
private
- def log_when_employees_or_company_touched
- puts 'Employee/Company was touched'
- end
+ def log_when_employees_or_company_touched
+ puts 'Employee/Company was touched'
+ end
end
>> @employee = Employee.last
@@ -194,8 +194,8 @@ end
# triggers @employee.company.touch
>> @employee.touch
-Employee/Company was touched
An Employee was touched
+Employee/Company was touched
=> true
```
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index cce93546ef..cfa444fda0 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Record Migrations
========================
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index 3f1875a159..16c1567c69 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Record and PostgreSQL
============================
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 944cee8a23..6233708ad5 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Record Query Interface
=============================
@@ -1777,6 +1777,12 @@ Client.pluck(:name)
# => ["David", "Jeremy", "Jose"]
```
+You are not limited to querying fields from a single table, you can query multiple tables as well.
+
+```
+Client.joins(:comments, :categories).pluck("clients.email, comments.title, categories.name")
+```
+
Furthermore, unlike `select` and other `Relation` scopes, `pluck` triggers an immediate
query, and thus cannot be chained with any further scopes, although it can work with
scopes already constructed earlier:
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 831fcf3a12..afe837a97c 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Record Validations
=========================
@@ -1133,24 +1133,6 @@ person.errors.full_messages
# => ["Name cannot contain the characters !@#%*()_-+="]
```
-An equivalent to `errors#add` is to use `<<` to append a message to the `errors.messages` array for an attribute:
-
-```ruby
- class Person < ApplicationRecord
- def a_method_used_for_validation_purposes
- errors.messages[:name] << "cannot contain the characters !@#%*()_-+="
- end
- end
-
- person = Person.create(name: "!@#")
-
- person.errors[:name]
- # => ["cannot contain the characters !@#%*()_-+="]
-
- person.errors.to_a
- # => ["Name cannot contain the characters !@#%*()_-+="]
-```
-
### `errors.details`
You can specify a validator type to the returned error details hash using the
diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md
index e6c8b503a8..182d784595 100644
--- a/guides/source/active_storage_overview.md
+++ b/guides/source/active_storage_overview.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Storage Overview
=======================
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 470ddadeb5..dfd21915b0 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Support Core Extensions
==============================
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index ac40fda11d..3568c47dd8 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Support Instrumentation
==============================
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index 04173ae631..85367c50e7 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Using Rails for API-only Applications
=====================================
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 10b89433e7..6efd9296dc 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
API Documentation Guidelines
============================
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 893a93b250..bf046a3341 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
The Asset Pipeline
==================
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index e7408b5a7f..008c7345e9 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Active Record Associations
==========================
@@ -96,7 +96,7 @@ end
![belongs_to Association Diagram](images/association_basics/belongs_to.png)
-NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `author` association in the `Book` model, you would be told that there was an "uninitialized constant Book::Authors". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too.
+NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `author` association in the `Book` model and tried to create the instance by `Book.create(authors: @author)`, you would be told that there was an "uninitialized constant Book::Authors". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too.
The corresponding migration might look like this:
@@ -600,7 +600,7 @@ NOTE: If you wish to [enforce referential integrity at the database level](/acti
#### Creating Join Tables for `has_and_belongs_to_many` Associations
-If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical book of the class names. So a join between author and book models will give the default join table name of "authors_books" because "a" outranks "b" in lexical ordering.
+If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical order of the class names. So a join between author and book models will give the default join table name of "authors_books" because "a" outranks "b" in lexical ordering.
WARNING: The precedence between model names is calculated using the `<=>` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings).
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index 7dc1a5e456..6298651e4a 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Autoloading and Reloading Constants
===================================
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index e74f5c615d..8aaa71c557 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Caching with Rails: An Overview
===============================
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index b86db5b367..2f07417316 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
The Rails Command Line
======================
@@ -309,7 +309,7 @@ about code. In unit testing, we take a little part of code, say a method of a mo
and test its inputs and outputs. Unit tests are your friend. The sooner you make
peace with the fact that your quality of life will drastically increase when you unit
test your code, the better. Seriously. Please visit
-[the testing guide](http://guides.rubyonrails.org/testing.html) for an in-depth
+[the testing guide](https://guides.rubyonrails.org/testing.html) for an in-depth
look at unit testing.
Let's see the interface Rails created for us.
@@ -669,7 +669,7 @@ default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
- # http://guides.rubyonrails.org/configuring.html#database-pooling
+ # https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index c08a4a4771..6e4f1f9648 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Configuring Rails Applications
==============================
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 6c0c7aefc1..3147b00f3b 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Contributing to Ruby on Rails
=============================
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index b7476a4ab2..88d205e1ab 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Debugging Rails Applications
============================
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index d5dca88c64..057bcf2c1b 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Development Dependencies Install
================================
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 50a92eb3e6..1e93a19c84 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Getting Started with Engines
============================
@@ -313,6 +313,9 @@ The engine that this guide covers provides submitting articles and commenting
functionality and follows a similar thread to the [Getting Started
Guide](getting_started.html), with some new twists.
+NOTE: For this section, make sure to run the commands in the root of the
+`blorgh` engine's directory.
+
### Generating an Article Resource
The first thing to generate for a blog engine is the `Article` model and related
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 0ee64c855e..e1dbe39137 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Action View Form Helpers
========================
diff --git a/guides/source/generators.md b/guides/source/generators.md
index d613898170..89424a161b 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Creating and Customizing Rails Generators & Templates
=====================================================
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 0cc3f9b276..88a13cdd70 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Getting Started with Rails
==========================
@@ -305,7 +305,7 @@ Open the file `config/routes.rb` in your editor.
Rails.application.routes.draw do
get 'welcome/index'
- # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+ # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
```
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index ec7582fa62..7843df5b18 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Rails Internationalization (I18n) API
=====================================
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 65222aabda..c41eae18cf 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
The Rails Initialization Process
================================
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 4ed2793fe3..e8a1bd4f3d 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -30,7 +30,7 @@
</span>
<ul class="more-info-links s-hidden">
<li class="more-info"><a href="https://weblog.rubyonrails.org/">Blog</a></li>
- <li class="more-info"><a href="http://guides.rubyonrails.org/">Guides</a></li>
+ <li class="more-info"><a href="https://guides.rubyonrails.org/">Guides</a></li>
<li class="more-info"><a href="http://api.rubyonrails.org/">API</a></li>
<li class="more-info"><a href="https://stackoverflow.com/questions/tagged/ruby-on-rails">Ask for help</a></li>
<li class="more-info"><a href="https://github.com/rails/rails">Contribute on GitHub</a></li>
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index ba3ef4679b..00da65b784 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Layouts and Rendering in Rails
==============================
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 2604d289e9..b14b7a2c90 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Maintenance Policy for Ruby on Rails
====================================
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index 4b27b6c8af..7c9784dfe3 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
The Basics of Creating Rails Plugins
====================================
@@ -455,7 +455,7 @@ gem "yaffle", git: "https://github.com/rails/yaffle.git"
After running `bundle install`, your gem functionality will be available to the application.
When the gem is ready to be shared as a formal release, it can be published to [RubyGems](https://rubygems.org).
-For more information about publishing gems to RubyGems, see: [Publishing your gem](http://guides.rubygems.org/publishing).
+For more information about publishing gems to RubyGems, see: [Publishing your gem](https://guides.rubygems.org/publishing).
RDoc Documentation
------------------
@@ -481,4 +481,4 @@ $ bundle exec rake rdoc
* [Developing a RubyGem using Bundler](https://github.com/radar/guides/blob/master/gem-development.md)
* [Using .gemspecs as Intended](http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/)
-* [Gemspec Reference](http://guides.rubygems.org/specification-reference/)
+* [Gemspec Reference](https://guides.rubygems.org/specification-reference/)
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 928c987af0..bc68a555c5 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Rails Application Templates
===========================
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 9a785349c2..c33851a0f9 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Rails on Rack
=============
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 6d7a1ad12c..23a5538d9b 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Rails Routing from the Outside In
=================================
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index de63e193f4..f5c0ba5b2d 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Ruby on Rails Guides Guidelines
===============================
diff --git a/guides/source/security.md b/guides/source/security.md
index 680e9ad796..4e12a831a9 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Securing Rails Applications
===========================
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 8961ec3f76..b3c16f3e7c 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Testing Rails Applications
==========================
diff --git a/guides/source/threading_and_code_execution.md b/guides/source/threading_and_code_execution.md
index e4febc7507..d3a81fe6a8 100644
--- a/guides/source/threading_and_code_execution.md
+++ b/guides/source/threading_and_code_execution.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Threading and Code Execution in Rails
=====================================
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index a40d1bbf7e..e3a254f82b 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Upgrading Ruby on Rails
=======================
@@ -1379,7 +1379,7 @@ Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already
#### Cache
-The caching method changed between Rails 3.x and 4.0. You should [change the cache namespace](http://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-store) and roll out with a cold cache.
+The caching method changed between Rails 3.x and 4.0. You should [change the cache namespace](https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-store) and roll out with a cold cache.
### Helpers Loading Order
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index a922bdc16b..36f5039883 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -1,4 +1,4 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.**
Working with JavaScript in Rails
================================
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 83a57a8c6a..81cb5a6c9f 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,22 @@
+* Deprecate `rails notes` subcommands in favor of passing an `annotations` argument to `rails notes`.
+
+ The following subcommands are replaced by passing `--annotations` or `-a` to `rails notes`:
+ - `rails notes:custom ANNOTATION=custom` is deprecated in favor of using `rails notes -a custom`.
+ - `rails notes:optimize` is deprecated in favor of using `rails notes -a OPTIMIZE`.
+ - `rails notes:todo` is deprecated in favor of using`rails notes -a TODO`.
+ - `rails notes:fixme` is deprecated in favor of using `rails notes -a FIXME`.
+
+ *Annie-Claude Côté*
+
+* Deprecate `SOURCE_ANNOTATION_DIRECTORIES` environment variable used by `rails notes`
+ through `Rails::SourceAnnotationExtractor::Annotation` in favor of using `config.annotations.register_directories`.
+
+ *Annie-Claude Côté*
+
+* Deprecate `rake notes` in favor of `rails notes`.
+
+ *Annie-Claude Côté*
+
* Don't generate unused files in `app:update` task
Skip the assets' initializer when sprockets isn't loaded.
diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc
index a4a4b6b235..89fc6bcbce 100644
--- a/railties/RDOC_MAIN.rdoc
+++ b/railties/RDOC_MAIN.rdoc
@@ -77,15 +77,15 @@ and may also be used independently outside \Rails.
5. Follow the guidelines to start developing your application. You may find the following resources handy:
* The \README file created within your application.
- * {Getting Started with \Rails}[http://guides.rubyonrails.org/getting_started.html].
- * {Ruby on \Rails Guides}[http://guides.rubyonrails.org].
+ * {Getting Started with \Rails}[https://guides.rubyonrails.org/getting_started.html].
+ * {Ruby on \Rails Guides}[https://guides.rubyonrails.org].
* {The API Documentation}[http://api.rubyonrails.org].
* {Ruby on \Rails Tutorial}[https://www.railstutorial.org/book].
== Contributing
We encourage you to contribute to Ruby on \Rails! Please check out the
-{Contributing to Ruby on \Rails guide}[http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how to proceed. {Join us!}[http://contributors.rubyonrails.org]
+{Contributing to Ruby on \Rails guide}[https://guides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how to proceed. {Join us!}[http://contributors.rubyonrails.org]
Trying to report a possible security vulnerability in \Rails? Please
check out our {security policy}[http://rubyonrails.org/security/] for
diff --git a/railties/lib/rails/commands/notes/notes_command.rb b/railties/lib/rails/commands/notes/notes_command.rb
index a0faaeff8f..64b339b3cd 100644
--- a/railties/lib/rails/commands/notes/notes_command.rb
+++ b/railties/lib/rails/commands/notes/notes_command.rb
@@ -28,7 +28,7 @@ module Rails
def deprecation_warning
return if source_annotation_directories.empty?
- ActiveSupport::Deprecation.warn("`SOURCE_ANNOTATION_DIRECTORIES` will be deprecated in Rails 6.1. You can add default directories by using config.annotations.register_directories instead.")
+ ActiveSupport::Deprecation.warn("`SOURCE_ANNOTATION_DIRECTORIES` is deprecated and will be removed in Rails 6.1. You can add default directories by using config.annotations.register_directories instead.")
end
def source_annotation_directories
diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb
index cf17f0ef12..2c5440d9ec 100644
--- a/railties/lib/rails/commands/server/server_command.rb
+++ b/railties/lib/rails/commands/server/server_command.rb
@@ -97,10 +97,6 @@ module Rails
end
end
- def restart_command
- "bin/rails server #{ARGV.join(' ')}"
- end
-
def use_puma?
server.to_s == "Rack::Handler::Puma"
end
@@ -132,16 +128,18 @@ module Rails
desc: "Specifies the Rack server used to run the application (thin/puma/webrick).", banner: :name
class_option :pid, aliases: "-P", type: :string, default: DEFAULT_PID_PATH,
desc: "Specifies the PID file."
- class_option "dev-caching", aliases: "-C", type: :boolean, default: nil,
+ class_option :dev_caching, aliases: "-C", type: :boolean, default: nil,
desc: "Specifies whether to perform caching in development."
- class_option "restart", type: :boolean, default: nil, hide: true
- class_option "early_hints", type: :boolean, default: nil, desc: "Enables HTTP/2 early hints."
+ class_option :restart, type: :boolean, default: nil, hide: true
+ class_option :early_hints, type: :boolean, default: nil, desc: "Enables HTTP/2 early hints."
+ class_option :log_to_stdout, type: :boolean, default: nil, optional: true,
+ desc: "Whether to log to stdout. Enabled by default in development when not daemonized."
- def initialize(args = [], local_options = {}, config = {})
- @original_options = local_options
+ def initialize(args, local_options, *)
super
- @using = deprecated_positional_rack_server(using) || options[:using]
- @log_stdout = options[:daemon].blank? && (options[:environment] || Rails.env) == "development"
+
+ @original_options = local_options - %w( --restart )
+ deprecate_positional_rack_server_and_rewrite_to_option(@original_options)
end
def perform
@@ -169,7 +167,7 @@ module Rails
{
user_supplied_options: user_supplied_options,
server: using,
- log_stdout: @log_stdout,
+ log_stdout: log_to_stdout?,
Port: port,
Host: host,
DoNotReverseLookup: true,
@@ -177,7 +175,7 @@ module Rails
environment: environment,
daemonize: options[:daemon],
pid: pid,
- caching: options["dev-caching"],
+ caching: options[:dev_caching],
restart_cmd: restart_command,
early_hints: early_hints
}
@@ -210,7 +208,7 @@ module Rails
name = :Port
when :binding
name = :Host
- when :"dev-caching"
+ when :dev_caching
name = :caching
when :daemonize
name = :daemon
@@ -252,13 +250,19 @@ module Rails
end
def restart_command
- "bin/rails server #{using} #{@original_options.join(" ")} --restart"
+ "bin/rails server #{@original_options.join(" ")} --restart"
end
def early_hints
options[:early_hints]
end
+ def log_to_stdout?
+ options.fetch(:log_to_stdout) do
+ options[:daemon].blank? && environment == "development"
+ end
+ end
+
def pid
File.expand_path(options[:pid])
end
@@ -271,14 +275,19 @@ module Rails
FileUtils.rm_f(options[:pid]) if options[:restart]
end
- def deprecated_positional_rack_server(value)
- if value
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ def deprecate_positional_rack_server_and_rewrite_to_option(original_options)
+ if using
+ ActiveSupport::Deprecation.warn(<<~MSG)
Passing the Rack server name as a regular argument is deprecated
and will be removed in the next Rails version. Please, use the -u
option instead.
MSG
- value
+
+ original_options.concat [ "-u", using ]
+ else
+ # Use positional internally to get around Thor's immutable options.
+ # TODO: Replace `using` occurences with `options[:using]` after deprecation removal.
+ @using = options[:using]
end
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index f8460bd4ee..2a41403557 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -258,7 +258,6 @@ module Rails
namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }]
lookups.each do |namespace|
-
klass = namespaces[namespace]
return klass if klass
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 097ed26f9c..d5d87f3dfd 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -321,7 +321,7 @@ module Rails
end
def display_upgrade_guide_info
- say "\nAfter this, check Rails upgrade guide at http://guides.rubyonrails.org/upgrading_ruby_on_rails.html for more details about upgrading your app."
+ say "\nAfter this, check Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html for more details about upgrading your app."
end
remove_task :display_upgrade_guide_info
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
index 917b52e535..33f422c622 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
@@ -29,7 +29,7 @@ test:
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
index d40117a27f..681c765e93 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
@@ -65,7 +65,7 @@ test:
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
index 563be77710..af69f12059 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
@@ -59,7 +59,7 @@ test:
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
index 2a67bdca25..97f9a92ff3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
@@ -32,7 +32,7 @@ test:
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
index 70df04079d..df8a6ad627 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
@@ -48,7 +48,7 @@ test:
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
index 04afaa0596..1dc508b14f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
@@ -37,7 +37,7 @@ test:
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
index 6da0601b24..8d9d33ba6c 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
@@ -38,7 +38,7 @@ test:
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
index 4ce4d276b7..dcd57425e2 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
@@ -18,7 +18,7 @@ default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
- # http://guides.rubyonrails.org/configuring.html#database-pooling
+ # https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
@@ -64,7 +64,7 @@ test:
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
index 049de65f22..0246fb0d02 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
@@ -31,7 +31,7 @@ test:
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
-# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml
index decc5a8573..cf9b342d0a 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml
@@ -27,7 +27,7 @@
# 'true': 'foo'
#
# To learn more, please read the Rails Internationalization guide
-# available at http://guides.rubyonrails.org/i18n.html.
+# available at https://guides.rubyonrails.org/i18n.html.
en:
hello: "Hello world"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
index a5eccf816b..f6146e7259 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
@@ -4,8 +4,9 @@
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
-threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
-threads threads_count, threads_count
+max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
+min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
+threads min_threads_count, max_threads_count
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt
index 787824f888..c06383a172 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt
@@ -1,3 +1,3 @@
Rails.application.routes.draw do
- # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+ # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake
index e219277d23..ec56957204 100644
--- a/railties/lib/rails/tasks/log.rake
+++ b/railties/lib/rails/tasks/log.rake
@@ -1,7 +1,6 @@
# frozen_string_literal: true
namespace :log do
-
##
# Truncates all/specified log files
# ENV['LOGS']
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index d73e5cdfa3..9e22ba84b5 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -30,19 +30,22 @@ module ApplicationTests
app_file "config/locales/en.yaml", "# TODO: note in yaml"
app_file "app/views/home/index.ruby", "# TODO: note in ruby"
- run_rake_notes do |output, lines|
- assert_match(/note in erb/, output)
- assert_match(/note in js/, output)
- assert_match(/note in css/, output)
- assert_match(/note in rake/, output)
- assert_match(/note in builder/, output)
- assert_match(/note in yml/, output)
- assert_match(/note in yaml/, output)
- assert_match(/note in ruby/, output)
-
- assert_equal 9, lines.size
- assert_equal [4], lines.map(&:size).uniq
+ stderr = capture(:stderr) do
+ run_rake_notes do |output, lines|
+ assert_match(/note in erb/, output)
+ assert_match(/note in js/, output)
+ assert_match(/note in css/, output)
+ assert_match(/note in rake/, output)
+ assert_match(/note in builder/, output)
+ assert_match(/note in yml/, output)
+ assert_match(/note in yaml/, output)
+ assert_match(/note in ruby/, output)
+
+ assert_equal 9, lines.size
+ assert_equal [4], lines.map(&:size).uniq
+ end
end
+ assert_match(/DEPRECATION WARNING: This rake task is deprecated and will be removed in Rails 6.1/, stderr)
end
test "notes finds notes in default directories" do
@@ -54,17 +57,20 @@ module ApplicationTests
app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
- run_rake_notes do |output, lines|
- assert_match(/note in app directory/, output)
- assert_match(/note in config directory/, output)
- assert_match(/note in db directory/, output)
- assert_match(/note in lib directory/, output)
- assert_match(/note in test directory/, output)
- assert_no_match(/note in some_other directory/, output)
-
- assert_equal 5, lines.size
- assert_equal [4], lines.map(&:size).uniq
+ stderr = capture(:stderr) do
+ run_rake_notes do |output, lines|
+ assert_match(/note in app directory/, output)
+ assert_match(/note in config directory/, output)
+ assert_match(/note in db directory/, output)
+ assert_match(/note in lib directory/, output)
+ assert_match(/note in test directory/, output)
+ assert_no_match(/note in some_other directory/, output)
+
+ assert_equal 5, lines.size
+ assert_equal [4], lines.map(&:size).uniq
+ end
end
+ assert_match(/DEPRECATION WARNING: This rake task is deprecated and will be removed in Rails 6.1/, stderr)
end
test "notes finds notes in custom directories" do
@@ -76,18 +82,22 @@ module ApplicationTests
app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
- run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bin/rails notes" do |output, lines|
- assert_match(/note in app directory/, output)
- assert_match(/note in config directory/, output)
- assert_match(/note in db directory/, output)
- assert_match(/note in lib directory/, output)
- assert_match(/note in test directory/, output)
+ stderr = capture(:stderr) do
+ run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bin/rake notes" do |output, lines|
+ assert_match(/note in app directory/, output)
+ assert_match(/note in config directory/, output)
+ assert_match(/note in db directory/, output)
+ assert_match(/note in lib directory/, output)
+ assert_match(/note in test directory/, output)
- assert_match(/note in some_other directory/, output)
+ assert_match(/note in some_other directory/, output)
- assert_equal 6, lines.size
- assert_equal [4], lines.map(&:size).uniq
+ assert_equal 6, lines.size
+ assert_equal [4], lines.map(&:size).uniq
+ end
end
+ assert_match(/DEPRECATION WARNING: This rake task is deprecated and will be removed in Rails 6.1/, stderr)
+ assert_match(/DEPRECATION WARNING: `SOURCE_ANNOTATION_DIRECTORIES` is deprecated and will be removed in Rails 6.1/, stderr)
end
test "custom rake task finds specific notes in specific directories" do
@@ -122,11 +132,14 @@ module ApplicationTests
app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss"
app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass"
- run_rake_notes do |output, lines|
- assert_match(/note in scss/, output)
- assert_match(/note in sass/, output)
- assert_equal 2, lines.size
+ stderr = capture(:stderr) do
+ run_rake_notes do |output, lines|
+ assert_match(/note in scss/, output)
+ assert_match(/note in sass/, output)
+ assert_equal 2, lines.size
+ end
end
+ assert_match(/DEPRECATION WARNING: This rake task is deprecated and will be removed in Rails 6.1/, stderr)
end
test "register additional directories" do
@@ -134,38 +147,27 @@ module ApplicationTests
app_file "spec/models/user_spec.rb", "# TODO: note in model spec"
add_to_config ' config.annotations.register_directories("spec") '
- run_rake_notes do |output, lines|
- assert_match(/note in spec/, output)
- assert_match(/note in model spec/, output)
- assert_equal 2, lines.size
+ stderr = capture(:stderr) do
+ run_rake_notes do |output, lines|
+ assert_match(/note in spec/, output)
+ assert_match(/note in model spec/, output)
+ assert_equal 2, lines.size
+ end
end
+ assert_match(/DEPRECATION WARNING: This rake task is deprecated and will be removed in Rails 6.1/, stderr)
end
private
- def run_rake_notes(command = "bin/rails notes")
- boot_rails
- load_tasks
-
+ def run_rake_notes(command = "bin/rake notes")
Dir.chdir(app_path) do
output = `#{command}`
- lines = output.scan(/\[([0-9\s]+)\]\s/).flatten
+
+ lines = output.scan(/\[([0-9\s]+)\]\s/).flatten
yield output, lines
end
end
-
- def load_tasks
- require "rake"
- require "rdoc/task"
- require "rake/testtask"
-
- Rails.application.load_tasks
- end
-
- def boot_rails
- require "#{app_path}/config/environment"
- end
end
end
end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index e7a56b3e6d..e5b1da6ea4 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -143,10 +143,22 @@ class Rails::Command::ServerCommandTest < ActiveSupport::TestCase
options = parse_arguments(args)
assert_equal true, options[:log_stdout]
+ args = ["-e", "development", "-d"]
+ options = parse_arguments(args)
+ assert_equal false, options[:log_stdout]
+
args = ["-e", "production"]
options = parse_arguments(args)
assert_equal false, options[:log_stdout]
+ args = ["-e", "development", "--no-log-to-stdout"]
+ options = parse_arguments(args)
+ assert_equal false, options[:log_stdout]
+
+ args = ["-e", "production", "--log-to-stdout"]
+ options = parse_arguments(args)
+ assert_equal true, options[:log_stdout]
+
with_rack_env "development" do
args = []
options = parse_arguments(args)
@@ -245,10 +257,9 @@ class Rails::Command::ServerCommandTest < ActiveSupport::TestCase
args = %w(-p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C)
ARGV.replace args
- options = parse_arguments(args)
- expected = "bin/rails server -p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C --restart"
+ expected = "bin/rails server -p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C --restart"
- assert_equal expected, options[:restart_cmd]
+ assert_equal expected, parse_arguments(args)[:restart_cmd]
ensure
ARGV.replace original_args
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 9a3ddc8d5e..4ac8f8d741 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -570,7 +570,6 @@ YAML
get("/arunagw")
assert_equal "arunagw", last_response.body
-
end
test "it provides routes as default endpoint" do