aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--Gemfile.lock2
-rw-r--r--actionmailer/CHANGELOG.md6
-rw-r--r--actionmailer/lib/action_mailer/delivery_job.rb6
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb2
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb5
-rw-r--r--actionmailer/test/message_delivery_test.rb20
-rw-r--r--actionpack/CHANGELOG.md27
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb2
-rw-r--r--actionpack/lib/action_controller/api.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/caching.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb6
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb12
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb8
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb6
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb2
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb14
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb219
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb6
-rw-r--r--actionpack/lib/action_controller/test_case.rb227
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb17
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb26
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb45
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/load_interlock.rb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb31
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb6
-rw-r--r--actionpack/lib/action_dispatch/request/utils.rb49
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb17
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb26
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb11
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb31
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb3
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb4
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb24
-rw-r--r--actionpack/test/controller/api/conditional_get_test.rb4
-rw-r--r--actionpack/test/controller/api/params_wrapper_test.rb2
-rw-r--r--actionpack/test/controller/base_test.rb4
-rw-r--r--actionpack/test/controller/caching_test.rb12
-rw-r--r--actionpack/test/controller/content_type_test.rb26
-rw-r--r--actionpack/test/controller/default_url_options_with_before_action_test.rb2
-rw-r--r--actionpack/test/controller/filters_test.rb35
-rw-r--r--actionpack/test/controller/force_ssl_test.rb16
-rw-r--r--actionpack/test/controller/helper_test.rb21
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb8
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb4
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb6
-rw-r--r--actionpack/test/controller/integration_test.rb26
-rw-r--r--actionpack/test/controller/live_stream_test.rb32
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb183
-rw-r--r--actionpack/test/controller/new_base/base_test.rb6
-rw-r--r--actionpack/test/controller/new_base/content_negotiation_test.rb2
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb18
-rw-r--r--actionpack/test/controller/new_base/render_test.rb6
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb53
-rw-r--r--actionpack/test/controller/parameters/mutators_test.rb4
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_test.rb2
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb15
-rw-r--r--actionpack/test/controller/permitted_params_test.rb4
-rw-r--r--actionpack/test/controller/redirect_test.rb4
-rw-r--r--actionpack/test/controller/render_json_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb8
-rw-r--r--actionpack/test/controller/request/test_request_test.rb6
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb5
-rw-r--r--actionpack/test/controller/rescue_test.rb18
-rw-r--r--actionpack/test/controller/resources_test.rb4
-rw-r--r--actionpack/test/controller/routing_test.rb6
-rw-r--r--actionpack/test/controller/send_file_test.rb2
-rw-r--r--actionpack/test/controller/test_case_test.rb97
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb3
-rw-r--r--actionpack/test/controller/webservice_test.rb12
-rw-r--r--actionpack/test/dispatch/live_response_test.rb14
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb24
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/response_test.rb9
-rw-r--r--actionpack/test/dispatch/routing/concerns_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb12
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb4
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb10
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb4
-rw-r--r--actionpack/test/dispatch/test_request_test.rb20
-rw-r--r--actionpack/test/dispatch/test_response_test.rb7
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb2
-rw-r--r--actionview/CHANGELOG.md23
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb38
-rw-r--r--actionview/lib/action_view/digestor.rb14
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb29
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb14
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb27
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb9
-rw-r--r--actionview/lib/action_view/lookup_context.rb15
-rw-r--r--actionview/lib/action_view/path_set.rb9
-rw-r--r--actionview/lib/action_view/railtie.rb8
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb2
-rw-r--r--actionview/lib/action_view/rendering.rb2
-rw-r--r--actionview/lib/action_view/routing_url_for.rb10
-rw-r--r--actionview/lib/action_view/template.rb34
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb11
-rw-r--r--actionview/lib/action_view/template/resolver.rb18
-rw-r--r--actionview/lib/action_view/test_case.rb4
-rw-r--r--actionview/lib/action_view/view_paths.rb6
-rw-r--r--actionview/test/actionpack/abstract/layouts_test.rb4
-rw-r--r--actionview/test/actionpack/abstract/render_test.rb3
-rw-r--r--actionview/test/actionpack/controller/render_test.rb38
-rw-r--r--actionview/test/actionpack/controller/view_paths_test.rb4
-rw-r--r--actionview/test/fixtures/actionpack/layouts/standard.text.erb1
-rw-r--r--actionview/test/fixtures/digestor/events/_completed.html.erb0
-rw-r--r--actionview/test/fixtures/digestor/events/index.html.erb1
-rw-r--r--actionview/test/template/digestor_test.rb53
-rw-r--r--actionview/test/template/template_test.rb8
-rw-r--r--actionview/test/template/text_helper_test.rb12
-rw-r--r--actionview/test/template/url_helper_test.rb7
-rw-r--r--activejob/lib/active_job/test_helper.rb4
-rw-r--r--activemodel/README.rdoc2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb2
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb8
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb33
-rw-r--r--activemodel/test/cases/helper.rb5
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb128
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb2
-rw-r--r--activerecord/CHANGELOG.md129
-rw-r--r--activerecord/Rakefile9
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations.rb16
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb16
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb9
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb9
-rw-r--r--activerecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/callbacks.rb14
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb12
-rw-r--r--activerecord/lib/active_record/core.rb16
-rw-r--r--activerecord/lib/active_record/enum.rb28
-rw-r--r--activerecord/lib/active_record/errors.rb9
-rw-r--r--activerecord/lib/active_record/fixtures.rb4
-rw-r--r--activerecord/lib/active_record/locale/en.yml4
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb13
-rw-r--r--activerecord/lib/active_record/persistence.rb9
-rw-r--r--activerecord/lib/active_record/relation.rb26
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb3
-rw-r--r--activerecord/lib/active_record/relation/merger.rb4
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/transactions.rb19
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/quoting_test.rb21
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb24
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb46
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb102
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb46
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb28
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb19
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb32
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations_test.rb17
-rw-r--r--activerecord/test/cases/attributes_test.rb2
-rw-r--r--activerecord/test/cases/autosave_association_test.rb39
-rw-r--r--activerecord/test/cases/base_test.rb14
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb70
-rw-r--r--activerecord/test/cases/connection_pool_test.rb28
-rw-r--r--activerecord/test/cases/counter_cache_test.rb13
-rw-r--r--activerecord/test/cases/helper.rb3
-rw-r--r--activerecord/test/cases/locking_test.rb2
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb45
-rw-r--r--activerecord/test/cases/relations_test.rb19
-rw-r--r--activerecord/test/cases/transactions_test.rb11
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb19
-rw-r--r--activerecord/test/models/aircraft.rb1
-rw-r--r--activerecord/test/models/book.rb8
-rw-r--r--activerecord/test/models/member_detail.rb7
-rw-r--r--activerecord/test/models/post.rb16
-rw-r--r--activerecord/test/models/ship.rb12
-rw-r--r--activerecord/test/models/topic.rb2
-rw-r--r--activerecord/test/schema/schema.rb5
-rw-r--r--activesupport/CHANGELOG.md29
-rw-r--r--activesupport/activesupport.gemspec1
-rw-r--r--activesupport/lib/active_support/callbacks.rb14
-rw-r--r--activesupport/lib/active_support/concurrency/latch.rb24
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb142
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb43
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb3
-rw-r--r--activesupport/lib/active_support/dependencies.rb101
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb47
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb16
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb37
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb24
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb18
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb4
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb2
-rw-r--r--activesupport/lib/active_support/message_verifier.rb4
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb4
-rw-r--r--activesupport/lib/active_support/testing/method_call_assertions.rb35
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/test/abstract_unit.rb6
-rw-r--r--activesupport/test/caching_test.rb74
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb52
-rw-r--r--activesupport/test/core_ext/object/json_gem_encoding_test.rb66
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb29
-rw-r--r--activesupport/test/dependencies_test.rb11
-rw-r--r--activesupport/test/deprecation_test.rb16
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb11
-rw-r--r--activesupport/test/json/encoding_test.rb134
-rw-r--r--activesupport/test/json/encoding_test_cases.rb88
-rw-r--r--activesupport/test/log_subscriber_test.rb9
-rw-r--r--activesupport/test/multibyte_unicode_database_test.rb5
-rw-r--r--activesupport/test/share_lock_test.rb333
-rw-r--r--activesupport/test/testing/method_call_assertions_test.rb98
-rw-r--r--activesupport/test/time_zone_test.rb1
-rw-r--r--activesupport/test/xml_mini_test.rb1
-rw-r--r--guides/Rakefile2
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb2
-rw-r--r--guides/bug_report_templates/action_controller_master.rb2
-rw-r--r--guides/source/action_view_overview.md2
-rw-r--r--guides/source/active_job_basics.md7
-rw-r--r--guides/source/active_record_migrations.md18
-rw-r--r--guides/source/active_record_querying.md12
-rw-r--r--guides/source/active_record_validations.md5
-rw-r--r--guides/source/association_basics.md13
-rw-r--r--guides/source/caching_with_rails.md213
-rw-r--r--guides/source/configuring.md18
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md6
-rw-r--r--guides/source/credits.html.erb2
-rw-r--r--guides/source/documents.yaml4
-rw-r--r--guides/source/form_helpers.md5
-rw-r--r--guides/source/getting_started.md12
-rw-r--r--guides/source/routing.md16
-rw-r--r--guides/source/testing.md18
-rw-r--r--guides/source/working_with_javascript_in_rails.md2
-rw-r--r--railties/CHANGELOG.md5
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb30
-rw-r--r--railties/lib/rails/application/finisher.rb6
-rw-r--r--railties/lib/rails/commands/server.rb12
-rw-r--r--railties/lib/rails/commands/test.rb6
-rw-r--r--railties/lib/rails/engine/commands.rb6
-rw-r--r--railties/lib/rails/generators/actions.rb4
-rw-r--r--railties/lib/rails/generators/base.rb6
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup14
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb31
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb1
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css3
-rw-r--r--railties/lib/rails/test_help.rb4
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb1
-rw-r--r--railties/test/application/assets_test.rb1
-rw-r--r--railties/test/application/middleware_test.rb33
-rw-r--r--railties/test/application/per_request_digest_cache_test.rb63
-rw-r--r--railties/test/application/rake/notes_test.rb1
-rw-r--r--railties/test/generators/api_app_generator_test.rb1
-rw-r--r--railties/test/generators/plugin_generator_test.rb54
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb4
-rw-r--r--railties/test/generators/scaffold_generator_test.rb4
307 files changed, 4024 insertions, 1680 deletions
diff --git a/.travis.yml b/.travis.yml
index 2237ca85d9..605d1ff247 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,14 +23,10 @@ env:
rvm:
- 2.2.2
- ruby-head
- - rbx-2
- - jruby-head
matrix:
allow_failures:
- env: "GEM=ar:mysql"
- rvm: ruby-head
- - rvm: rbx-2
- - rvm: jruby-head
fast_finish: true
notifications:
email: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 960a77af92..897bdeff83 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -85,6 +85,7 @@ PATH
activesupport (= 5.0.0.alpha)
arel (= 7.0.0.alpha)
activesupport (5.0.0.alpha)
+ concurrent-ruby (~> 0.9.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -135,6 +136,7 @@ GEM
execjs
coffee-script-source (1.9.0)
columnize (0.9.0)
+ concurrent-ruby (0.9.0)
connection_pool (2.1.1)
dalli (2.7.2)
dante (0.1.5)
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index f325cbef81..7c1b0b215a 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,9 +1,3 @@
-* `deliver_later` now respects the current locale.
-
- Fixes #20774.
-
- *Johannes Opper*
-
* Add `config.action_mailer.deliver_later_queue_name` configuration to set the
mailer queue name.
diff --git a/actionmailer/lib/action_mailer/delivery_job.rb b/actionmailer/lib/action_mailer/delivery_job.rb
index f008671a72..52772af2d3 100644
--- a/actionmailer/lib/action_mailer/delivery_job.rb
+++ b/actionmailer/lib/action_mailer/delivery_job.rb
@@ -6,10 +6,8 @@ module ActionMailer
class DeliveryJob < ActiveJob::Base # :nodoc:
queue_as { ActionMailer::Base.deliver_later_queue_name }
- def perform(mailer, mail_method, delivery_method, locale, *args) #:nodoc:
- I18n.with_locale(locale) do
- mailer.constantize.public_send(mail_method, *args).send(delivery_method)
- end
+ def perform(mailer, mail_method, delivery_method, *args) #:nodoc:
+ mailer.constantize.public_send(mail_method, *args).send(delivery_method)
end
end
end
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index c2479bf651..ff2cb0fd01 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -87,7 +87,7 @@ module ActionMailer
private
def enqueue_delivery(delivery_method, options={})
- args = @mailer.name, @mail_method.to_s, delivery_method.to_s, I18n.locale.to_s, *@args
+ args = @mailer.name, @mail_method.to_s, delivery_method.to_s, *@args
ActionMailer::DeliveryJob.set(options).perform_later(*args)
end
end
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 010e44d045..5ffde06a80 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'action_view'
require 'action_controller'
+require 'active_support/deprecation'
class I18nTestMailer < ActionMailer::Base
configure do |c|
@@ -54,7 +55,9 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
def test_send_mail
Mail::SMTP.any_instance.expects(:deliver!)
with_translation 'de', email_subject: '[Anmeldung] Willkommen' do
- get '/test/send_mail'
+ ActiveSupport::Deprecation.silence do
+ get '/test/send_mail'
+ end
assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
end
end
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index 7ee4b01067..862ce26187 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -57,20 +57,20 @@ class MessageDeliveryTest < ActiveSupport::TestCase
end
test 'should enqueue the email with :deliver_now delivery method' do
- assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 'en', 1, 2, 3]) do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
@mail.deliver_later
end
end
test 'should enqueue the email with :deliver_now! delivery method' do
- assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now!', 'en', 1, 2, 3]) do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now!', 1, 2, 3]) do
@mail.deliver_later!
end
end
test 'should enqueue a delivery with a delay' do
travel_to Time.new(2004, 11, 24, 01, 04, 44) do
- assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f+600.seconds, args: ['DelayedMailer', 'test_message', 'deliver_now', 'en', 1, 2, 3]) do
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f+600.seconds, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
@mail.deliver_later wait: 600.seconds
end
end
@@ -78,28 +78,20 @@ class MessageDeliveryTest < ActiveSupport::TestCase
test 'should enqueue a delivery at a specific time' do
later_time = Time.now.to_f + 3600
- assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ['DelayedMailer', 'test_message', 'deliver_now', 'en', 1, 2, 3]) do
+ assert_performed_with(job: ActionMailer::DeliveryJob, at: later_time, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3]) do
@mail.deliver_later wait_until: later_time
end
end
test 'should enqueue the job on the correct queue' do
- assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 'en', 1, 2, 3], queue: "test_queue") do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "test_queue") do
@mail.deliver_later
end
end
test 'can override the queue when enqueuing mail' do
- assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 'en', 1, 2, 3], queue: "another_queue") do
+ assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 1, 2, 3], queue: "another_queue") do
@mail.deliver_later(queue: :another_queue)
end
end
-
- test 'should enqueue a delivery with the correct locale' do
- I18n.with_locale('de') do
- assert_performed_with(job: ActionMailer::DeliveryJob, args: ['DelayedMailer', 'test_message', 'deliver_now', 'de', 1, 2, 3]) do
- @mail.deliver_later
- end
- end
- end
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 46856ffc5d..6a68c057ac 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,27 @@
+* Fix `ActionController::Parameters#fetch` overwriting `KeyError` returned by
+ default block.
+
+ *Jonas Schuber Erlandsson*, *Roque Pinel*
+
+* `ActionController::Parameters` no longer inherits from
+ `HashWithIndifferentAccess`
+
+ Inheriting from `HashWithIndifferentAccess` allowed users to call any
+ enumerable methods on `Parameters` object, resulting in a risk of losing the
+ `permitted?` status or even getting back a pure `Hash` object instead of
+ a `Parameters` object with proper sanitization.
+
+ By not inheriting from `HashWithIndifferentAccess`, we are able to make
+ sure that all methods that are defined in `Parameters` object will return
+ a proper `Parameters` object with a correct `permitted?` flag.
+
+ *Prem Sichanugrist*
+
+* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
+ from the concurrent-ruby gem.
+
+ *Jerry D'Antonio*
+
* Add ability to filter parameters based on parent keys.
# matches {credit_card: {code: "xxxx"}}
@@ -164,7 +188,8 @@
*arthurnn*
* `ActionController#translate` supports symbols as shortcuts.
- When shortcut is given it also lookups without action name.
+ When a shortcut is given it also performs the lookup without the action
+ name.
*Max Melentiev*
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index ebd02bd9a1..784092867c 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -96,7 +96,7 @@ module AbstractController
# ==== Returns
# * <tt>String</tt>
def controller_path
- @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
+ @controller_path ||= name.sub(/Controller$/, ''.freeze).underscore unless anonymous?
end
# Refresh the cached action_methods when a new action_method is added.
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 109eff10eb..d84c238a62 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -181,7 +181,7 @@ module AbstractController
end
def default_helper_module!
- module_name = name.sub(/Controller$/, '')
+ module_name = name.sub(/Controller$/, ''.freeze)
module_path = module_name.underscore
helper module_path
rescue LoadError => e
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index 3af63b8892..b4594bf302 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -90,7 +90,7 @@ module ActionController
# Shortcut helper that returns all the ActionController::API modules except
# the ones passed as arguments:
#
- # class MetalController
+ # class MyAPIBaseController < ActionController::Metal
# ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left|
# include left
# end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 2c3b3f4e05..17371a5392 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -183,7 +183,7 @@ module ActionController
# Shortcut helper that returns all the modules included in
# ActionController::Base except the ones passed as arguments:
#
- # class MetalController
+ # class MyBaseController < ActionController::Metal
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
# include left
# end
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index de85e0c1a7..a4e4992cfe 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -8,7 +8,7 @@ module ActionController
#
# You can read more about each approach by clicking the modules below.
#
- # Note: To turn off all caching, set
+ # Note: To turn off all caching provided by Action Controller, set
# config.action_controller.perform_caching = false
#
# == \Caching stores
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 87609d8aa7..4c9f14e409 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -25,7 +25,7 @@ module ActionController
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
- message << " (#{additions.join(" | ")})" unless additions.blank?
+ message << " (#{additions.join(" | ".freeze)})" unless additions.blank?
message
end
end
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 1abd8d3a33..e6d7f958bb 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -85,6 +85,10 @@ module ActionController #:nodoc:
@to_path = path
end
+ def body
+ File.binread(to_path)
+ end
+
# Stream the file's contents if Rack::Sendfile isn't present.
def each
File.open(to_path, 'rb') do |file|
@@ -126,7 +130,7 @@ module ActionController #:nodoc:
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
send_file_headers! options
- render options.slice(:status, :content_type).merge(:text => data)
+ render options.slice(:status, :content_type).merge(body: data)
end
private
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index d920668184..e31d65aac2 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -55,10 +55,10 @@ module ActionController
# You can pass any of the following options to affect the before_action callback
# * <tt>only</tt> - The callback should be run only for this action
# * <tt>except</tt> - The callback should be run for all actions except this action
- # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a true value.
- # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a false value.
+ # * <tt>if</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a true value.
+ # * <tt>unless</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a false value.
def force_ssl(options = {})
action_options = options.slice(*ACTION_OPTIONS)
redirect_options = options.except(*ACTION_OPTIONS)
@@ -71,8 +71,8 @@ module ActionController
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
- # * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options
- # available to the <tt>force_ssl</tt> method.
+ # * <tt>host_or_options</tt> - Either a host name or any of the url &
+ # redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
options = {
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index b4da381d26..fcaf3e6425 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -73,7 +73,7 @@ module ActionController
# Provides a proxy to access helpers methods from outside the view.
def helpers
- @helper_proxy ||= begin
+ @helper_proxy ||= begin
proxy = ActionView::Base.new
proxy.config = config.inheritable_copy
proxy.extend(_helpers)
@@ -100,7 +100,7 @@ module ActionController
def all_helpers_from_path(path)
helpers = Array(path).flat_map do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) }
names.sort!
end
helpers.uniq!
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 39bed955a4..032275ac64 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -502,7 +502,7 @@ module ActionController
def authentication_request(controller, realm, message = nil)
message ||= "HTTP Token: Access denied.\n"
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
- controller.__send__ :render, :text => message, :status => :unauthorized
+ controller.__send__ :render, plain: message, status: :unauthorized
end
end
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index fab1be3459..1db68db20f 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -156,16 +156,16 @@ module ActionController #:nodoc:
# It works for both inline:
#
# respond_to do |format|
- # format.html.any { render text: "any" }
- # format.html.phone { render text: "phone" }
+ # format.html.any { render html: "any" }
+ # format.html.phone { render html: "phone" }
# end
#
# and block syntax:
#
# respond_to do |format|
# format.html do |variant|
- # variant.any(:tablet, :phablet){ render text: "any" }
- # variant.phone { render text: "phone" }
+ # variant.any(:tablet, :phablet){ render html: "any" }
+ # variant.phone { render html: "phone" }
# end
# end
#
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index cdfc523bd4..e680432127 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -4,8 +4,8 @@ require 'active_support/core_ext/module/anonymous'
require 'action_dispatch/http/mime_type'
module ActionController
- # Wraps the parameters hash into a nested hash. This will allow clients to submit
- # POST requests without having to specify any root elements.
+ # Wraps the parameters hash into a nested hash. This will allow clients to
+ # submit requests without having to specify any root elements.
#
# This functionality is enabled in +config/initializers/wrap_parameters.rb+
# and can be customized.
@@ -14,7 +14,7 @@ module ActionController
# a non-empty array:
#
# class UsersController < ApplicationController
- # wrap_parameters format: [:json, :xml]
+ # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
# end
#
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 45d3962494..cb74c4f0d4 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -94,7 +94,7 @@ module ActionController
# This method is the opposite of add method.
#
- # Usage:
+ # To remove a csv renderer:
#
# ActionController::Renderers.remove(:csv)
def self.remove(key)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index b9ae8dd5ea..a3b0645dc0 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -1,3 +1,6 @@
+require 'active_support/deprecation'
+require 'active_support/core_ext/string/filters'
+
module ActionController
module Rendering
extend ActiveSupport::Concern
@@ -74,6 +77,17 @@ module ActionController
def _normalize_options(options) #:nodoc:
_normalize_text(options)
+ if options[:text]
+ ActiveSupport::Deprecation.warn <<-WARNING.squish
+ `render :text` is deprecated because it does not actually render a
+ `text/plain` response. Switch to `render plain: 'plain text'` to
+ render as `text/plain`, `render html: '<strong>HTML</strong>'` to
+ render as `text/html`, or `render body: 'raw'` to match the deprecated
+ behavior and render with the default Content-Type, which is
+ `text/plain`.
+ WARNING
+ end
+
if options[:html]
options[:html] = ERB::Util.html_escape(options[:html])
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 4cb634477e..9e09242872 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -20,7 +20,7 @@ module ActionController #:nodoc:
# Since HTML and JavaScript requests are typically made from the browser, we
# need to ensure to verify request authenticity for the web browser. We can
# use session-oriented authentication for these types of requests, by using
- # the `protect_form_forgery` method in our controllers.
+ # the `protect_from_forgery` method in our controllers.
#
# GET requests are not protected since they don't have side effects like writing
# to the database and don't leak sensitive information. JavaScript requests are
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 09867e2407..e78f1f0d7e 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -104,10 +104,12 @@ module ActionController
# params = ActionController::Parameters.new(key: 'value')
# params[:key] # => "value"
# params["key"] # => "value"
- class Parameters < ActiveSupport::HashWithIndifferentAccess
+ class Parameters
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
+ delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters
+
# By default, never raise an UnpermittedParameters exception if these
# params are present. The default includes both 'controller' and 'action'
# because they are added by Rails and should be of no concern. One way
@@ -144,11 +146,22 @@ module ActionController
# params = ActionController::Parameters.new(name: 'Francesco')
# params.permitted? # => true
# Person.new(params) # => #<Person id: nil, name: "Francesco">
- def initialize(attributes = nil)
- super(attributes)
+ def initialize(parameters = {})
+ @parameters = parameters.with_indifferent_access
@permitted = self.class.permit_all_parameters
end
+ # Returns true if another +Parameters+ object contains the same content and
+ # permitted flag, or other Hash-like object contains the same content. This
+ # override is in place so you can perform a comparison with `Hash`.
+ def ==(other_hash)
+ if other_hash.respond_to?(:permitted?)
+ super
+ else
+ @parameters == other_hash
+ end
+ end
+
# Returns a safe +Hash+ representation of this parameter with all
# unpermitted keys removed.
#
@@ -162,7 +175,7 @@ module ActionController
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
def to_h
if permitted?
- to_hash
+ @parameters.to_h
else
slice(*self.class.always_permitted_parameters).permit!.to_h
end
@@ -170,20 +183,17 @@ module ActionController
# Returns an unsafe, unfiltered +Hash+ representation of this parameter.
def to_unsafe_h
- to_hash
+ @parameters.to_h
end
alias_method :to_unsafe_hash, :to_unsafe_h
# Convert all hashes in values into parameters, then yield each pair like
# the same way as <tt>Hash#each_pair</tt>
def each_pair(&block)
- super do |key, value|
- convert_hashes_to_parameters(key, value)
+ @parameters.each_pair do |key, value|
+ yield key, convert_hashes_to_parameters(key, value)
end
-
- super
end
-
alias_method :each, :each_pair
# Attribute that keeps track of converted arrays, if any, to avoid double
@@ -347,7 +357,13 @@ module ActionController
# params[:person] # => {"name"=>"Francesco"}
# params[:none] # => nil
def [](key)
- convert_hashes_to_parameters(key, super)
+ convert_hashes_to_parameters(key, @parameters[key])
+ end
+
+ # Assigns a value to a given +key+. The given key may still get filtered out
+ # when +permit+ is called.
+ def []=(key, value)
+ @parameters[key] = value
end
# Returns a parameter for the given +key+. If the +key+
@@ -361,10 +377,16 @@ module ActionController
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
- def fetch(key, *args)
- convert_hashes_to_parameters(key, super, false)
- rescue KeyError
- raise ActionController::ParameterMissing.new(key)
+ def fetch(key, *args, &block)
+ convert_value_to_parameters(
+ @parameters.fetch(key) {
+ if block_given?
+ yield
+ else
+ args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
+ end
+ }
+ )
end
# Returns a new <tt>ActionController::Parameters</tt> instance that
@@ -375,7 +397,24 @@ module ActionController
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {}
def slice(*keys)
- new_instance_with_inherited_permitted_status(super)
+ new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
+ end
+
+ # Returns current <tt>ActionController::Parameters</tt> instance which
+ # contains only the given +keys+.
+ def slice!(*keys)
+ @parameters.slice!(*keys)
+ self
+ end
+
+ # Returns a new <tt>ActionController::Parameters</tt> instance that
+ # filters out the given +keys+.
+ #
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
+ # params.except(:a, :b) # => {"c"=>3}
+ # params.except(:d) # => {"a"=>1,"b"=>2,"c"=>3}
+ def except(*keys)
+ new_instance_with_inherited_permitted_status(@parameters.except(*keys))
end
# Removes and returns the key/value pairs matching the given keys.
@@ -384,7 +423,7 @@ module ActionController
# params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
# params # => {"c"=>3}
def extract!(*keys)
- new_instance_with_inherited_permitted_status(super)
+ new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
end
# Returns a new <tt>ActionController::Parameters</tt> with the results of
@@ -393,36 +432,80 @@ module ActionController
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.transform_values { |x| x * 2 }
# # => {"a"=>2, "b"=>4, "c"=>6}
- def transform_values
- if block_given?
- new_instance_with_inherited_permitted_status(super)
+ def transform_values(&block)
+ if block
+ new_instance_with_inherited_permitted_status(
+ @parameters.transform_values(&block)
+ )
else
- super
+ @parameters.transform_values
end
end
- # This method is here only to make sure that the returned object has the
- # correct +permitted+ status. It should not matter since the parent of
- # this object is +HashWithIndifferentAccess+
- def transform_keys # :nodoc:
- if block_given?
- new_instance_with_inherited_permitted_status(super)
+ # Performs values transformation and returns the altered
+ # <tt>ActionController::Parameters</tt> instance.
+ def transform_values!(&block)
+ @parameters.transform_values!(&block)
+ self
+ end
+
+ # Returns a new <tt>ActionController::Parameters</tt> instance with the
+ # results of running +block+ once for every key. The values are unchanged.
+ def transform_keys(&block)
+ if block
+ new_instance_with_inherited_permitted_status(
+ @parameters.transform_keys(&block)
+ )
else
- super
+ @parameters.transform_keys
end
end
+ # Performs keys transfomration and returns the altered
+ # <tt>ActionController::Parameters</tt> instance.
+ def transform_keys!(&block)
+ @parameters.transform_keys!(&block)
+ self
+ end
+
# Deletes and returns a key-value pair from +Parameters+ whose key is equal
# to key. If the key is not found, returns the default value. If the
# optional code block is given and the key is not found, pass in the key
# and return the result of block.
def delete(key, &block)
- convert_hashes_to_parameters(key, super, false)
+ convert_value_to_parameters(@parameters.delete(key))
+ end
+
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with only
+ # items that the block evaluates to true.
+ def select(&block)
+ new_instance_with_inherited_permitted_status(@parameters.select(&block))
end
# Equivalent to Hash#keep_if, but returns nil if no changes were made.
def select!(&block)
- convert_value_to_parameters(super)
+ @parameters.select!(&block)
+ self
+ end
+ alias_method :keep_if, :select!
+
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with items
+ # that the block evaluates to true removed.
+ def reject(&block)
+ new_instance_with_inherited_permitted_status(@parameters.reject(&block))
+ end
+
+ # Removes items that the block evaluates to true and returns self.
+ def reject!(&block)
+ @parameters.reject!(&block)
+ self
+ end
+ alias_method :delete_if, :reject!
+
+ # Return values that were assigned to the given +keys+. Note that all the
+ # +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
+ def values_at(*keys)
+ convert_value_to_parameters(@parameters.values_at(*keys))
end
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
@@ -439,11 +522,30 @@ module ActionController
end
end
+ # Returns a new <tt>ActionController::Parameters</tt> with all keys from
+ # +other_hash+ merges into current hash.
+ def merge(other_hash)
+ new_instance_with_inherited_permitted_status(
+ @parameters.merge(other_hash)
+ )
+ end
+
+ # This is required by ActiveModel attribute assignment, so that user can
+ # pass +Parameters+ to a mass assignment methods in a model. It should not
+ # matter as we are using +HashWithIndifferentAccess+ internally.
+ def stringify_keys # :nodoc:
+ dup
+ end
+
protected
def permitted=(new_permitted)
@permitted = new_permitted
end
+ def fields_for_style?
+ @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
+ end
+
private
def new_instance_with_inherited_permitted_status(hash)
self.class.new(hash).tap do |new_instance|
@@ -451,40 +553,41 @@ module ActionController
end
end
- def convert_hashes_to_parameters(key, value, assign_if_converted=true)
+ def convert_hashes_to_parameters(key, value)
converted = convert_value_to_parameters(value)
- self[key] = converted if assign_if_converted && !converted.equal?(value)
+ @parameters[key] = converted unless converted.equal?(value)
converted
end
def convert_value_to_parameters(value)
- if value.is_a?(Array) && !converted_arrays.member?(value)
+ case value
+ when Array
+ return value if converted_arrays.member?(value)
converted = value.map { |_| convert_value_to_parameters(_) }
converted_arrays << converted
converted
- elsif value.is_a?(Parameters) || !value.is_a?(Hash)
- value
- else
+ when Hash
self.class.new(value)
+ else
+ value
end
end
def each_element(object)
- if object.is_a?(Array)
- object.map { |el| yield el }.compact
- elsif fields_for_style?(object)
- hash = object.class.new
- object.each { |k,v| hash[k] = yield v }
- hash
- else
- yield object
+ case object
+ when Array
+ object.grep(Parameters).map { |el| yield el }.compact
+ when Parameters
+ if object.fields_for_style?
+ hash = object.class.new
+ object.each { |k,v| hash[k] = yield v }
+ hash
+ else
+ yield object
+ end
end
end
- def fields_for_style?(object)
- object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
- end
-
def unpermitted_parameters!(params)
unpermitted_keys = unpermitted_keys(params)
if unpermitted_keys.any?
@@ -546,14 +649,8 @@ module ActionController
end
def array_of_permitted_scalars?(value)
- if value.is_a?(Array)
- value.all? {|element| permitted_scalar?(element)}
- end
- end
-
- def array_of_permitted_scalars_filter(params, key)
- if has_key?(key) && array_of_permitted_scalars?(self[key])
- params[key] = self[key]
+ if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)}
+ yield value
end
end
@@ -564,17 +661,17 @@ module ActionController
# Slicing filters out non-declared keys.
slice(*filter.keys).each do |key, value|
next unless value
+ next unless has_key? key
if filter[key] == EMPTY_ARRAY
# Declaration { comment_ids: [] }.
- array_of_permitted_scalars_filter(params, key)
+ array_of_permitted_scalars?(self[key]) do |val|
+ params[key] = val
+ end
else
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
- if element.is_a?(Hash)
- element = self.class.new(element) unless element.respond_to?(:permit)
- element.permit(*Array.wrap(filter[key]))
- end
+ element.permit(*Array.wrap(filter[key]))
end
end
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 5a0e5c62e4..dbf7241a14 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -41,7 +41,11 @@ module ActionController
if original_script_name
options[:original_script_name] = original_script_name
else
- options[:script_name] = same_origin ? request.script_name.dup : script_name
+ if same_origin
+ options[:script_name] = request.script_name.empty? ? "".freeze : request.script_name.dup
+ else
+ options[:script_name] = script_name
+ end
end
options.freeze
else
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 96f161fb09..f922e134f7 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -10,27 +10,45 @@ module ActionController
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
DEFAULT_ENV.delete 'PATH_INFO'
- def initialize(env = {})
- super
+ def self.new_session
+ TestSession.new
+ end
+
+ # Create a new test request with default `env` values
+ def self.create
+ env = {}
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
+ env["rack.request.cookie_hash"] = {}.with_indifferent_access
+ new(default_env.merge(env), new_session)
+ end
+
+ def self.default_env
+ DEFAULT_ENV
+ end
+ private_class_method :default_env
+
+ def initialize(env, session)
+ super(env)
- self.session = TestSession.new
+ self.session = session
self.session_options = TestSession::DEFAULT_OPTIONS
end
+ def query_string=(string)
+ @env[Rack::QUERY_STRING] = string
+ end
+
+ def request_parameters=(params)
+ @env["action_dispatch.request.request_parameters"] = params
+ end
+
def assign_parameters(routes, controller_path, action, parameters = {})
parameters = parameters.symbolize_keys
- extra_keys = routes.extra_keys(parameters.merge(:controller => controller_path, :action => action))
- non_path_parameters = get? ? query_parameters : request_parameters
+ generated_path, extra_keys = routes.generate_extras(parameters.merge(:controller => controller_path, :action => action))
+ non_path_parameters = {}
+ path_parameters = {}
parameters.each do |key, value|
- if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
- value = value.map{ |v| v.duplicable? ? v.dup : v }
- elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
- value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
- elsif value.frozen? && value.duplicable?
- value = value.dup
- end
-
if extra_keys.include?(key) || key == :action || key == :controller
non_path_parameters[key] = value
else
@@ -44,69 +62,83 @@ module ActionController
end
end
- path_parameters[:controller] = controller_path
- path_parameters[:action] = action
+ if get?
+ if self.query_string.blank?
+ self.query_string = non_path_parameters.to_query
+ end
+ else
+ if ENCODER.should_multipart?(non_path_parameters)
+ @env['CONTENT_TYPE'] = ENCODER.content_type
+ data = ENCODER.build_multipart non_path_parameters
+ else
+ @env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded'
+
+ # FIXME: setting `request_parametes` is normally handled by the
+ # params parser middleware, and we should remove this roundtripping
+ # when we switch to caling `call` on the controller
+
+ case content_mime_type.ref
+ when :json
+ data = ActiveSupport::JSON.encode(non_path_parameters)
+ params = ActiveSupport::JSON.decode(data).with_indifferent_access
+ self.request_parameters = params
+ when :xml
+ data = non_path_parameters.to_xml
+ params = Hash.from_xml(data)['hash']
+ self.request_parameters = params
+ when :url_encoded_form
+ data = non_path_parameters.to_query
+ else
+ raise "Unknown Content-Type: #{content_type}"
+ end
+ end
- # Clear the combined params hash in case it was already referenced.
- @env.delete("action_dispatch.request.parameters")
+ @env['CONTENT_LENGTH'] = data.length.to_s
+ @env['rack.input'] = StringIO.new(data)
+ end
- # Clear the filter cache variables so they're not stale
- @filtered_parameters = @filtered_env = @filtered_path = nil
+ @env["PATH_INFO"] ||= generated_path
+ path_parameters[:controller] = controller_path
+ path_parameters[:action] = action
- data = request_parameters.to_query
- @env['CONTENT_LENGTH'] = data.length.to_s
- @env['rack.input'] = StringIO.new(data)
+ self.path_parameters = path_parameters
end
- def recycle!
- @formats = nil
- @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
- @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
- @method = @request_method = nil
- @fullpath = @ip = @remote_ip = @protocol = nil
- @env['action_dispatch.request.query_parameters'] = {}
- @set_cookies ||= {}
- @set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
- deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies")
- @set_cookies.reject!{ |k,v| deleted_cookies.include?(k) }
- cookie_jar.update(rack_cookies)
- cookie_jar.update(cookies)
- cookie_jar.update(@set_cookies)
- cookie_jar.recycle!
- end
+ ENCODER = Class.new do
+ include Rack::Test::Utils
+
+ def should_multipart?(params)
+ # FIXME: lifted from Rack-Test. We should push this separation upstream
+ multipart = false
+ query = lambda { |value|
+ case value
+ when Array
+ value.each(&query)
+ when Hash
+ value.values.each(&query)
+ when Rack::Test::UploadedFile
+ multipart = true
+ end
+ }
+ params.values.each(&query)
+ multipart
+ end
- private
+ public :build_multipart
- def default_env
- DEFAULT_ENV
- end
- end
-
- class TestResponse < ActionDispatch::TestResponse
- def recycle!
- initialize
- end
+ def content_type
+ "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
+ end
+ end.new
end
class LiveTestResponse < Live::Response
- def recycle!
- @body = nil
- initialize
- end
-
- def body
- @body ||= super
- end
-
# Was the response successful?
alias_method :success?, :successful?
# Was the URL not found?
alias_method :missing?, :not_found?
- # Were we redirected?
- alias_method :redirect?, :redirection?
-
# Was there a server-side error?
alias_method :error?, :server_error?
end
@@ -195,7 +227,7 @@ module ActionController
# request. You can modify this object before sending the HTTP request. For example,
# you might want to set some session properties before sending a GET request.
# <b>@response</b>::
- # An ActionController::TestResponse object, representing the response
+ # An ActionDispatch::TestResponse object, representing the response
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
@@ -317,7 +349,9 @@ module ActionController
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
def get(action, *args)
- process_with_kwargs("GET", action, *args)
+ res = process_with_kwargs("GET", action, *args)
+ cookies.update res.cookies
+ res
end
# Simulate a POST request with the given parameters and set/volley the response.
@@ -365,19 +399,6 @@ module ActionController
end
alias xhr :xml_http_request
- def paramify_values(hash_or_array_or_value)
- case hash_or_array_or_value
- when Hash
- Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
- when Array
- hash_or_array_or_value.map {|i| paramify_values(i)}
- when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile
- hash_or_array_or_value
- else
- hash_or_array_or_value.to_param
- end
- end
-
# Simulate a HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
#
@@ -437,10 +458,6 @@ module ActionController
parameters ||= {}
- # Ensure that numbers and symbols passed as params are converted to
- # proper params, as is the case when engaging rack.
- parameters = paramify_values(parameters) if html_format?(parameters)
-
if format.present?
parameters[:format] = format
end
@@ -451,8 +468,13 @@ module ActionController
@controller.extend(Testing::Functional)
end
- @request.recycle!
- @response.recycle!
+ self.cookies.update @request.cookies
+ @request.env['HTTP_COOKIE'] = cookies.to_header
+ @request.env['action_dispatch.cookies'] = nil
+
+ @request = TestRequest.new scrub_env!(@request.env), @request.session
+ @response = build_response @response_klass
+ @response.request = @request
@controller.recycle!
@request.env['REQUEST_METHOD'] = http_method
@@ -474,14 +496,17 @@ module ActionController
@controller.request = @request
@controller.response = @response
- build_request_uri(controller_class_name, action, parameters)
+ @request.env["SCRIPT_NAME"] ||= @controller.config.relative_url_root
@controller.recycle!
@controller.process(action)
+ @request.env.delete 'HTTP_COOKIE'
+
if cookies = @request.env['action_dispatch.cookies']
unless @response.committed?
cookies.write(@response)
+ self.cookies.update(cookies.instance_variable_get(:@cookies))
end
end
@response.prepare!
@@ -496,6 +521,7 @@ module ActionController
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
end
+ @request.query_string = ''
@response
end
@@ -503,11 +529,11 @@ module ActionController
def setup_controller_request_and_response
@controller = nil unless defined? @controller
- response_klass = TestResponse
+ @response_klass = ActionDispatch::TestResponse
if klass = self.class.controller_class
if klass < ActionController::Live
- response_klass = LiveTestResponse
+ @response_klass = LiveTestResponse
end
unless @controller
begin
@@ -518,8 +544,8 @@ module ActionController
end
end
- @request = build_request
- @response = build_response response_klass
+ @request = TestRequest.create
+ @response = build_response @response_klass
@response.request = @request
if @controller
@@ -528,10 +554,6 @@ module ActionController
end
end
- def build_request
- TestRequest.new
- end
-
def build_response(klass)
klass.new
end
@@ -545,6 +567,14 @@ module ActionController
private
+ def scrub_env!(env)
+ env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
+ env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
+ env.delete 'action_dispatch.request.query_parameters'
+ env.delete 'action_dispatch.request.request_parameters'
+ env
+ end
+
def process_with_kwargs(http_method, action, *args)
if kwarg_request?(args)
args.first.merge!(method: http_method)
@@ -591,23 +621,6 @@ module ActionController
end
end
- def build_request_uri(controller_class_name, action, parameters)
- unless @request.env["PATH_INFO"]
- options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
- options.update(
- :controller => controller_class_name,
- :action => action,
- :relative_url_root => nil,
- :_recall => @request.path_parameters)
-
- url, query_string = @routes.path_for(options).split("?", 2)
-
- @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
- @request.env["PATH_INFO"] = url
- @request.env["QUERY_STRING"] = query_string || ""
- end
- end
-
def html_format?(parameters)
return true unless parameters.key?(:format)
Mime.fetch(parameters[:format]) { Mime['html'] }.html?
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index dcd3ee0644..f6336c8c7a 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -52,6 +52,7 @@ module ActionDispatch
autoload :DebugExceptions
autoload :ExceptionWrapper
autoload :Flash
+ autoload :LoadInterlock
autoload :ParamsParser
autoload :PublicExceptions
autoload :Reloader
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 7e585aa244..a639f8a8f8 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -45,7 +45,7 @@ module Mime
#
# respond_to do |format|
# format.html
- # format.ics { render text: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
+ # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
# format.xml { render xml: @post }
# end
# end
@@ -211,7 +211,7 @@ module Mime
# This method is opposite of register method.
#
- # Usage:
+ # To unregister a MIME type:
#
# Mime::Type.unregister(:mobile)
def unregister(symbol)
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 6e058b829e..e826551f4b 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -34,11 +34,11 @@ module ActionDispatch
end
end
- deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.") }
- deep_strings, strings = strings.partition { |s| s.include?("\\.") }
+ deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) }
+ deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) }
- regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
- deep_regexps << Regexp.new(deep_strings.join('|'), true) unless deep_strings.empty?
+ regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty?
+ deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty?
new regexps, deep_regexps, blocks
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index c2f05ecc86..4defb7f858 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -37,22 +37,7 @@ module ActionDispatch
# Convert nested Hash to HashWithIndifferentAccess.
#
def normalize_encode_params(params)
- case params
- when Hash
- if params.has_key?(:tempfile)
- UploadedFile.new(params)
- else
- params.each_with_object({}) do |(key, val), new_hash|
- new_hash[key] = if val.is_a?(Array)
- val.map! { |el| normalize_encode_params(el) }
- else
- normalize_encode_params(val)
- end
- end.with_indifferent_access
- end
- else
- params
- end
+ ActionDispatch::Request::Utils.normalize_encode_params params
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 3c62c055e5..6985cec5f5 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -290,7 +290,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
+ @env["action_dispatch.request.query_parameters"] ||= normalize_encode_params(super || {})
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:query, e)
end
@@ -298,7 +298,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
+ @env["action_dispatch.request.request_parameters"] ||= normalize_encode_params(super || {})
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new(:request, e)
end
@@ -318,11 +318,6 @@ module ActionDispatch
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
- protected
- def parse_query(*)
- Utils.deep_munge(super)
- end
-
private
def check_method(name)
HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index c5939adb9f..fd92e89231 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -80,11 +80,21 @@ module ActionDispatch # :nodoc:
@response = response
@buf = buf
@closed = false
+ @str_body = nil
+ end
+
+ def body
+ @str_body ||= begin
+ buf = ''
+ each { |chunk| buf << chunk }
+ buf
+ end
end
def write(string)
raise IOError, "closed stream" if closed?
+ @str_body = nil
@response.commit!
@buf.push string
end
@@ -187,13 +197,13 @@ module ActionDispatch # :nodoc:
@content_type = content_type.to_s
end
- # Sets the HTTP character set.
+ # Sets the HTTP character set. In case of nil parameter
+ # it sets the charset to utf-8.
+ #
+ # response.charset = 'utf-16' # => 'utf-16'
+ # response.charset = nil # => 'utf-8'
def charset=(charset)
- if nil == charset
- @charset = self.class.default_charset
- else
- @charset = charset
- end
+ @charset = charset.nil? ? self.class.default_charset : charset
end
# The response code of the request.
@@ -222,9 +232,7 @@ module ActionDispatch # :nodoc:
# Returns the content of the response as a string. This contains the contents
# of any calls to <tt>render</tt>.
def body
- strings = []
- each { |part| strings << part.to_s }
- strings.join
+ @stream.body
end
EMPTY = " "
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index f5b709ccd6..6fcf49030b 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -245,7 +245,7 @@ module ActionDispatch
# req = Request.new 'HTTP_HOST' => 'example.com:8080'
# req.host # => "example.com"
def host
- raw_host_with_port.sub(/:\d+$/, '')
+ raw_host_with_port.sub(/:\d+$/, ''.freeze)
end
# Returns a \host:\port string for this request, such as "example.com" or
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index c0566c6fc9..d8bb10ffab 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -14,7 +14,7 @@ module ActionDispatch
def generate(name, options, path_parameters, parameterize = nil)
constraints = path_parameters.merge(options)
- missing_keys = []
+ missing_keys = nil # need for variable scope
match_route(name, constraints) do |route|
parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
@@ -25,22 +25,22 @@ module ActionDispatch
next unless name || route.dispatcher?
missing_keys = missing_keys(route, parameterized_parts)
- next unless missing_keys.empty?
+ next if missing_keys && !missing_keys.empty?
params = options.dup.delete_if do |key, _|
parameterized_parts.key?(key) || route.defaults.key?(key)
end
defaults = route.defaults
required_parts = route.required_parts
- parameterized_parts.delete_if do |key, value|
- value.to_s == defaults[key].to_s && !required_parts.include?(key)
+ parameterized_parts.keep_if do |key, value|
+ defaults[key].nil? || value.to_s != defaults[key].to_s || required_parts.include?(key)
end
return [route.format(parameterized_parts), params]
end
message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}"
- message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty?
+ message << " missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
raise ActionController::UrlGenerationError, message
end
@@ -54,12 +54,12 @@ module ActionDispatch
def extract_parameterized_parts(route, options, recall, parameterize = nil)
parameterized_parts = recall.merge(options)
- keys_to_keep = route.parts.reverse.drop_while { |part|
+ keys_to_keep = route.parts.reverse_each.drop_while { |part|
!options.key?(part) || (options[part] || recall[part]).nil?
} | route.required_parts
- (parameterized_parts.keys - keys_to_keep).each do |bad_key|
- parameterized_parts.delete(bad_key)
+ parameterized_parts.delete_if do |bad_key, _|
+ !keys_to_keep.include?(bad_key)
end
if parameterize
@@ -110,15 +110,36 @@ module ActionDispatch
routes
end
+ module RegexCaseComparator
+ DEFAULT_INPUT = /[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/
+ DEFAULT_REGEX = /\A#{DEFAULT_INPUT}\Z/
+
+ def self.===(regex)
+ DEFAULT_INPUT == regex
+ end
+ end
+
# Returns an array populated with missing keys if any are present.
def missing_keys(route, parts)
- missing_keys = []
+ missing_keys = nil
tests = route.path.requirements
route.required_parts.each { |key|
- if tests.key?(key)
- missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
+ case tests[key]
+ when nil
+ unless parts[key]
+ missing_keys ||= []
+ missing_keys << key
+ end
+ when RegexCaseComparator
+ unless RegexCaseComparator::DEFAULT_REGEX === parts[key]
+ missing_keys ||= []
+ missing_keys << key
+ end
else
- missing_keys << key unless parts[key]
+ unless /\A#{tests[key]}\Z/ === parts[key]
+ missing_keys ||= []
+ missing_keys << key
+ end
end
}
missing_keys
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index bb01c087bc..cf6542b370 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -30,7 +30,7 @@ module ActionDispatch
end
def name
- left.tr '*:', ''
+ left.tr '*:'.freeze, ''.freeze
end
def type
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index d02ed96d0d..9793ca1c7a 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -14,10 +14,10 @@ module ActionDispatch
# normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
path = "/#{path}"
- path.squeeze!('/')
- path.sub!(%r{/+\Z}, '')
+ path.squeeze!('/'.freeze)
+ path.sub!(%r{/+\Z}, ''.freeze)
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
- path = '/' if path == ''
+ path = '/' if path == ''.freeze
path
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 9a1e2bd45c..07d97bd6bd 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -279,6 +279,10 @@ module ActionDispatch
self
end
+ def to_header
+ @cookies.map { |k,v| "#{k}=#{v}" }.join ';'
+ end
+
def handle_options(options) #:nodoc:
options[:path] ||= "/"
diff --git a/actionpack/lib/action_dispatch/middleware/load_interlock.rb b/actionpack/lib/action_dispatch/middleware/load_interlock.rb
new file mode 100644
index 0000000000..07f498319c
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/load_interlock.rb
@@ -0,0 +1,21 @@
+require 'active_support/dependencies'
+require 'rack/body_proxy'
+
+module ActionDispatch
+ class LoadInterlock
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ interlock = ActiveSupport::Dependencies.interlock
+ interlock.start_running
+ response = @app.call(env)
+ body = Rack::BodyProxy.new(response[2]) { interlock.done_running }
+ response[2] = body
+ response
+ ensure
+ interlock.done_running unless body
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index 29d43faeed..2617956c74 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -13,40 +13,35 @@ module ActionDispatch
end
end
- DEFAULT_PARSERS = { Mime::JSON => :json }
+ DEFAULT_PARSERS = {
+ Mime::JSON => lambda { |raw_post|
+ data = ActiveSupport::JSON.decode(raw_post)
+ data = {:_json => data} unless data.is_a?(Hash)
+ Request::Utils.normalize_encode_params(data)
+ }
+ }
def initialize(app, parsers = {})
@app, @parsers = app, DEFAULT_PARSERS.merge(parsers)
end
def call(env)
- if params = parse_formatted_parameters(env)
- env["action_dispatch.request.request_parameters"] = params
- end
+ default = env["action_dispatch.request.request_parameters"]
+ env["action_dispatch.request.request_parameters"] = parse_formatted_parameters(env, @parsers, default)
@app.call(env)
end
private
- def parse_formatted_parameters(env)
+ def parse_formatted_parameters(env, parsers, default)
request = Request.new(env)
- return false if request.content_length.zero?
+ return default if request.content_length.zero?
- strategy = @parsers[request.content_mime_type]
+ strategy = parsers.fetch(request.content_mime_type) { return default }
- return false unless strategy
+ strategy.call(request.raw_post)
- case strategy
- when Proc
- strategy.call(request.raw_post)
- when :json
- data = ActiveSupport::JSON.decode(request.raw_post)
- data = {:_json => data} unless data.is_a?(Hash)
- Request::Utils.deep_munge(data).with_indifferent_access
- else
- false
- end
rescue => e # JSON or Ruby code block errors
logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index b098ea389f..f38da4fdf6 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -35,7 +35,7 @@ module ActionDispatch
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p|
- path = File.join(@root, p.force_encoding('UTF-8'))
+ path = File.join(@root, p.force_encoding('UTF-8'.freeze))
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
@@ -76,7 +76,7 @@ module ActionDispatch
end
def content_type(path)
- ::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
+ ::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze)
end
def gzip_encoding_accepted?(env)
@@ -112,7 +112,7 @@ module ActionDispatch
def call(env)
case env['REQUEST_METHOD']
when 'GET', 'HEAD'
- path = env['PATH_INFO'].chomp('/')
+ path = env['PATH_INFO'].chomp('/'.freeze)
if match = @file_handler.match?(path)
env['PATH_INFO'] = match
return @file_handler.call(env)
diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb
index 1c9371d89c..3973ea6346 100644
--- a/actionpack/lib/action_dispatch/request/utils.rb
+++ b/actionpack/lib/action_dispatch/request/utils.rb
@@ -5,24 +5,45 @@ module ActionDispatch
mattr_accessor :perform_deep_munge
self.perform_deep_munge = true
- class << self
- # Remove nils from the params hash
- def deep_munge(hash, keys = [])
- return hash unless perform_deep_munge
+ def self.normalize_encode_params(params)
+ if perform_deep_munge
+ NoNilParamEncoder.normalize_encode_params params
+ else
+ ParamEncoder.normalize_encode_params params
+ end
+ end
- hash.each do |k, v|
- keys << k
- case v
- when Array
- v.grep(Hash) { |x| deep_munge(x, keys) }
- v.compact!
- when Hash
- deep_munge(v, keys)
+ class ParamEncoder # :nodoc:
+ # Convert nested Hash to HashWithIndifferentAccess.
+ #
+ def self.normalize_encode_params(params)
+ case params
+ when Array
+ handle_array params
+ when Hash
+ if params.has_key?(:tempfile)
+ ActionDispatch::Http::UploadedFile.new(params)
+ else
+ params.each_with_object({}) do |(key, val), new_hash|
+ new_hash[key] = normalize_encode_params(val)
+ end.with_indifferent_access
end
- keys.pop
+ else
+ params
end
+ end
+
+ def self.handle_array(params)
+ params.map! { |el| normalize_encode_params(el) }
+ end
+ end
- hash
+ # Remove nils from the params hash
+ class NoNilParamEncoder < ParamEncoder # :nodoc:
+ def self.handle_array(params)
+ list = super
+ list.compact!
+ list
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 7cfe4693c1..64352d5742 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -443,6 +443,21 @@ module ActionDispatch
# dynamic segment used to generate the routes).
# You can access that segment from your controller using
# <tt>params[<:param>]</tt>.
+ # In your router:
+ #
+ # resources :user, param: :name
+ #
+ # You can override <tt>ActiveRecord::Base#to_param</tt> of a related
+ # model to constructe an URL.
+ #
+ # class User < ActiveRecord::Base
+ # def to_param # overridden
+ # name
+ # end
+ # end
+ #
+ # user = User.find_by(name: 'Phusion')
+ # user_path(user) # => "/users/Phusion"
#
# [:path]
# The path prefix for the routes.
@@ -1538,7 +1553,7 @@ module ActionDispatch
path = path_for_action(action, options.delete(:path))
raise ArgumentError, "path is required" if path.blank?
- action = action.to_s.dup
+ action = action.to_s
if action =~ /^[\w\-\/]+$/
options[:action] ||= action.tr('-', '_') unless action.include?("/")
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 454593b59f..c968381c26 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -267,9 +267,13 @@ module ActionDispatch
path_params -= controller_options.keys
path_params -= result.keys
end
- path_params -= inner_options.keys
- path_params.take(args.size).each do |param|
- result[param] = args.shift
+ inner_options.each do |key, _|
+ path_params.delete(key)
+ end
+
+ args.each_with_index do |arg, index|
+ param = path_params[index]
+ result[param] = arg if param
end
end
@@ -594,8 +598,8 @@ module ActionDispatch
def initialize(named_route, options, recall, set)
@named_route = named_route
- @options = options.dup
- @recall = recall.dup
+ @options = options
+ @recall = recall
@set = set
normalize_recall!
@@ -617,7 +621,7 @@ module ActionDispatch
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
if !named_route_exists? || segment_keys.include?(key)
- @options[key] = @recall.delete(key)
+ @options[key] = @recall[key]
end
end
end
@@ -671,12 +675,18 @@ module ActionDispatch
# Remove leading slashes from controllers
def normalize_controller!
- @options[:controller] = controller.sub(%r{^/}, '') if controller
+ if controller
+ if controller.start_with?("/".freeze)
+ @options[:controller] = controller[1..-1]
+ else
+ @options[:controller] = controller
+ end
+ end
end
# Move 'index' action from options to recall
def normalize_action!
- if @options[:action] == 'index'
+ if @options[:action] == 'index'.freeze
@recall[:action] = @options.delete(:action)
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 8379d089df..967bbd62f8 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -171,6 +171,10 @@ module ActionDispatch
route_name = options.delete :use_route
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
route_name)
+ when ActionController::Parameters
+ route_name = options.delete :use_route
+ _routes.url_for(options.to_unsafe_h.symbolize_keys.
+ reverse_merge!(url_options), route_name)
when String
options
when Symbol
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 13a72220b3..b6e21b0d28 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -3,6 +3,13 @@ module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
module ResponseAssertions
+ RESPONSE_PREDICATES = { # :nodoc:
+ success: :successful?,
+ missing: :not_found?,
+ redirect: :redirection?,
+ error: :server_error?,
+ }
+
# Asserts that the response is one of the following types:
#
# * <tt>:success</tt> - Status code was in the 200-299 range
@@ -20,11 +27,9 @@ module ActionDispatch
# # assert that the response code was status code 401 (unauthorized)
# assert_response 401
def assert_response(type, message = nil)
- message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
-
if Symbol === type
if [:success, :missing, :redirect, :error].include?(type)
- assert @response.send("#{type}?"), message
+ assert_predicate @response, RESPONSE_PREDICATES[type], message
else
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
if code.nil?
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index c94eea9134..d0e3ea818e 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -165,7 +165,7 @@ module ActionDispatch
# ROUTES TODO: These assertions should really work in an integration context
def method_missing(selector, *args, &block)
- if defined?(@controller) && @controller && @routes && @routes.named_routes.route_defined?(selector)
+ if defined?(@controller) && @controller && defined?(@routes) && @routes && @routes.named_routes.route_defined?(selector)
@controller.send(selector, *args, &block)
else
super
@@ -183,7 +183,7 @@ module ActionDispatch
end
# Assume given controller
- request = ActionController::TestRequest.new
+ request = ActionController::TestRequest.create
if path =~ %r{://}
fail_on(URI::InvalidURIError, msg) do
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index dc664d5540..0298962409 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -391,7 +391,7 @@ module ActionDispatch
attr_reader :app
- def before_setup
+ def before_setup # :nodoc:
@app = nil
@integration_session = nil
super
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 415ef80cd2..494644cd46 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -19,7 +19,7 @@ module ActionDispatch
end
def cookies
- @request.cookie_jar
+ @cookie_jar ||= Cookies::CookieJar.build(@request.env, @request.host, @request.ssl?, @request.cookies)
end
def redirect_to_url
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index 4b9a088265..ad1a7f7109 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -4,19 +4,22 @@ require 'rack/utils'
module ActionDispatch
class TestRequest < Request
DEFAULT_ENV = Rack::MockRequest.env_for('/',
- 'HTTP_HOST' => 'test.host',
- 'REMOTE_ADDR' => '0.0.0.0',
- 'HTTP_USER_AGENT' => 'Rails Testing'
+ 'HTTP_HOST' => 'test.host',
+ 'REMOTE_ADDR' => '0.0.0.0',
+ 'HTTP_USER_AGENT' => 'Rails Testing',
)
- def self.new(env = {})
- super
+ # Create a new test request with default `env` values
+ def self.create(env = {})
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
+ env["rack.request.cookie_hash"] ||= {}.with_indifferent_access
+ new(default_env.merge(env))
end
- def initialize(env = {})
- env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
- super(default_env.merge(env))
+ def self.default_env
+ DEFAULT_ENV
end
+ private_class_method :default_env
def request_method=(method)
@env['REQUEST_METHOD'] = method.to_s.upcase
@@ -62,17 +65,5 @@ module ActionDispatch
@env.delete('action_dispatch.request.accepts')
@env['HTTP_ACCEPT'] = Array(mime_types).collect(&:to_s).join(",")
end
-
- alias :rack_cookies :cookies
-
- def cookies
- @cookies ||= {}.with_indifferent_access
- end
-
- private
-
- def default_env
- DEFAULT_ENV
- end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index a9b88ac5fd..6a31d6243f 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -16,9 +16,6 @@ module ActionDispatch
# Was the URL not found?
alias_method :missing?, :not_found?
- # Were we redirected?
- alias_method :redirect?, :redirection?
-
# Was there a server-side error?
alias_method :error?, :server_error?
end
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
index 5e64cae7e2..82c747680d 100644
--- a/actionpack/test/assertions/response_assertions_test.rb
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -7,7 +7,7 @@ module ActionDispatch
include ResponseAssertions
FakeResponse = Struct.new(:response_code) do
- [:success, :missing, :redirect, :error].each do |sym|
+ [:successful, :not_found, :redirection, :server_error].each do |sym|
define_method("#{sym}?") do
sym == response_code
end
@@ -16,7 +16,7 @@ module ActionDispatch
def test_assert_response_predicate_methods
[:success, :missing, :redirect, :error].each do |sym|
- @response = FakeResponse.new sym
+ @response = FakeResponse.new RESPONSE_PREDICATES[sym].to_s.sub(/\?/, '').to_sym
assert_response sym
assert_raises(Minitest::Assertion) {
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 5b85e83045..beeafc2e53 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -43,12 +43,12 @@ class ActionPackAssertionsController < ActionController::Base
def flash_me
flash['hello'] = 'my name is inigo montoya...'
- render :text => "Inconceivable!"
+ render plain: "Inconceivable!"
end
def flash_me_naked
flash.clear
- render :text => "wow!"
+ render plain: "wow!"
end
def assign_this
@@ -57,30 +57,30 @@ class ActionPackAssertionsController < ActionController::Base
end
def render_based_on_parameters
- render :text => "Mr. #{params[:name]}"
+ render plain: "Mr. #{params[:name]}"
end
def render_url
- render :text => "<div>#{url_for(:action => 'flash_me', :only_path => true)}</div>"
+ render html: "<div>#{url_for(action: 'flash_me', only_path: true)}</div>"
end
def render_text_with_custom_content_type
- render :text => "Hello!", :content_type => Mime::RSS
+ render body: "Hello!", content_type: Mime::RSS
end
def session_stuffing
session['xmas'] = 'turkey'
- render :text => "ho ho ho"
+ render plain: "ho ho ho"
end
def raise_exception_on_get
raise "get" if request.get?
- render :text => "request method: #{request.env['REQUEST_METHOD']}"
+ render plain: "request method: #{request.env['REQUEST_METHOD']}"
end
def raise_exception_on_post
raise "post" if request.post?
- render :text => "request method: #{request.env['REQUEST_METHOD']}"
+ render plain: "request method: #{request.env['REQUEST_METHOD']}"
end
def render_file_absolute_path
@@ -101,7 +101,7 @@ class AssertResponseWithUnexpectedErrorController < ActionController::Base
end
def show
- render :text => "Boom", :status => 500
+ render plain: "Boom", status: 500
end
end
@@ -318,7 +318,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_missing_response_code
process :response404
- assert @response.missing?
+ assert @response.not_found?
end
def test_client_error_response_code
@@ -346,12 +346,12 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_successful_response_code
process :nothing
- assert @response.success?
+ assert @response.successful?
end
def test_response_object
process :nothing
- assert_kind_of ActionController::TestResponse, @response
+ assert_kind_of ActionDispatch::TestResponse, @response
end
def test_render_based_on_parameters
diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb
index d1eb27bf24..b4f1673be0 100644
--- a/actionpack/test/controller/api/conditional_get_test.rb
+++ b/actionpack/test/controller/api/conditional_get_test.rb
@@ -7,12 +7,12 @@ class ConditionalGetApiController < ActionController::API
def one
if stale?(last_modified: Time.now.utc.beginning_of_day, etag: [:foo, 123])
- render text: "Hi!"
+ render plain: "Hi!"
end
end
def two
- render text: "Hi!"
+ render plain: "Hi!"
end
private
diff --git a/actionpack/test/controller/api/params_wrapper_test.rb b/actionpack/test/controller/api/params_wrapper_test.rb
index e40a39d829..53b3a0c3cc 100644
--- a/actionpack/test/controller/api/params_wrapper_test.rb
+++ b/actionpack/test/controller/api/params_wrapper_test.rb
@@ -7,7 +7,7 @@ class ParamsWrapperForApiTest < ActionController::TestCase
wrap_parameters :person, format: [:json]
def test
- self.last_parameters = params.except(:controller, :action)
+ self.last_parameters = params.except(:controller, :action).to_unsafe_h
head :ok
end
end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 3240185414..d9374ce9c3 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -53,7 +53,7 @@ end
class ActionMissingController < ActionController::Base
def action_missing(action)
- render :text => "Response for #{action}"
+ render plain: "Response for #{action}"
end
end
@@ -127,8 +127,6 @@ class PerformActionTest < ActionController::TestCase
# a more accurate simulation of what happens in "real life".
@controller.logger = ActiveSupport::Logger.new(nil)
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@request.host = "www.nextangle.com"
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 4f5af85fea..5698159eba 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -22,8 +22,6 @@ class FragmentCachingMetalTest < ActionController::TestCase
@controller.perform_caching = true
@controller.cache_store = @store
@params = { controller: 'posts', action: 'index' }
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@controller.params = @params
@controller.request = @request
@controller.response = @response
@@ -52,8 +50,6 @@ class FragmentCachingTest < ActionController::TestCase
@controller.perform_caching = true
@controller.cache_store = @store
@params = {:controller => 'posts', :action => 'index'}
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@controller.params = @params
@controller.request = @request
@controller.response = @response
@@ -166,6 +162,8 @@ class FunctionalCachingController < CachingController
end
def formatted_fragment_cached_with_variant
+ request.variant = :phone if params[:v] == "phone"
+
respond_to do |format|
format.html.phone
format.html
@@ -183,8 +181,6 @@ class FunctionalFragmentCachingTest < ActionController::TestCase
@controller = FunctionalCachingController.new
@controller.perform_caching = true
@controller.cache_store = @store
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
end
def test_fragment_caching
@@ -268,9 +264,7 @@ CACHED
def test_fragment_caching_with_variant
- @request.variant = :phone
-
- get :formatted_fragment_cached_with_variant, format: "html"
+ get :formatted_fragment_cached_with_variant, format: "html", params: { v: :phone }
assert_response :success
expected_body = "<body>\n<p>PHONE</p>\n</body>\n"
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index 89667df3a4..c5bbc479c9 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -4,29 +4,29 @@ class OldContentTypeController < ActionController::Base
# :ported:
def render_content_type_from_body
response.content_type = Mime::RSS
- render :text => "hello world!"
+ render body: "hello world!"
end
# :ported:
def render_defaults
- render :text => "hello world!"
+ render body: "hello world!"
end
# :ported:
def render_content_type_from_render
- render :text => "hello world!", :content_type => Mime::RSS
+ render body: "hello world!", :content_type => Mime::RSS
end
# :ported:
def render_charset_from_body
response.charset = "utf-16"
- render :text => "hello world!"
+ render body: "hello world!"
end
# :ported:
def render_nil_charset_from_body
response.charset = nil
- render :text => "hello world!"
+ render body: "hello world!"
end
def render_default_for_erb
@@ -42,10 +42,10 @@ class OldContentTypeController < ActionController::Base
def render_default_content_types_for_respond_to
respond_to do |format|
- format.html { render :text => "hello world!" }
- format.xml { render :action => "render_default_content_types_for_respond_to" }
- format.js { render :text => "hello world!" }
- format.rss { render :text => "hello world!", :content_type => Mime::XML }
+ format.html { render body: "hello world!" }
+ format.xml { render action: "render_default_content_types_for_respond_to" }
+ format.js { render body: "hello world!" }
+ format.rss { render body: "hello world!", content_type: Mime::XML }
end
end
end
@@ -64,14 +64,14 @@ class ContentTypeTest < ActionController::TestCase
def test_render_defaults
get :render_defaults
assert_equal "utf-8", @response.charset
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::TEXT, @response.content_type
end
def test_render_changed_charset_default
with_default_charset "utf-16" do
get :render_defaults
assert_equal "utf-16", @response.charset
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::TEXT, @response.content_type
end
end
@@ -92,14 +92,14 @@ class ContentTypeTest < ActionController::TestCase
# :ported:
def test_charset_from_body
get :render_charset_from_body
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::TEXT, @response.content_type
assert_equal "utf-16", @response.charset
end
# :ported:
def test_nil_charset_from_body
get :render_nil_charset_from_body
- assert_equal Mime::HTML, @response.content_type
+ assert_equal Mime::TEXT, @response.content_type
assert_equal "utf-8", @response.charset, @response.headers.inspect
end
diff --git a/actionpack/test/controller/default_url_options_with_before_action_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb
index 230f40d7ad..12fbe0424e 100644
--- a/actionpack/test/controller/default_url_options_with_before_action_test.rb
+++ b/actionpack/test/controller/default_url_options_with_before_action_test.rb
@@ -6,7 +6,7 @@ class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base
after_action { I18n.locale = "en" }
def target
- render :text => "final response"
+ render plain: "final response"
end
def redirect
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 9b0487841f..08271012e9 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -40,7 +40,7 @@ class FilterTest < ActionController::TestCase
before_action :ensure_login, :except => [:go_wild]
def go_wild
- render :text => "gobble"
+ render plain: "gobble"
end
end
@@ -51,7 +51,7 @@ class FilterTest < ActionController::TestCase
(1..3).each do |i|
define_method "fail_#{i}" do
- render :text => i.to_s
+ render plain: i.to_s
end
end
@@ -222,7 +222,7 @@ class FilterTest < ActionController::TestCase
skip_before_action :clean_up_tmp, only: :login, if: -> { true }
def login
- render text: 'ok'
+ render plain: 'ok'
end
end
@@ -234,7 +234,7 @@ class FilterTest < ActionController::TestCase
skip_before_action :clean_up_tmp, if: -> { true }, except: :login
def login
- render text: 'ok'
+ render plain: 'ok'
end
end
@@ -258,11 +258,11 @@ class FilterTest < ActionController::TestCase
before_action :ensure_login, :only => :index
def index
- render :text => 'ok'
+ render plain: 'ok'
end
def public
- render :text => 'ok'
+ render plain: 'ok'
end
end
@@ -272,7 +272,7 @@ class FilterTest < ActionController::TestCase
before_action :ensure_login
def index
- render :text => 'ok'
+ render plain: 'ok'
end
private
@@ -383,7 +383,7 @@ class FilterTest < ActionController::TestCase
before_action(AuditFilter)
def show
- render :text => "hello"
+ render plain: "hello"
end
end
@@ -421,11 +421,11 @@ class FilterTest < ActionController::TestCase
before_action :second, :only => :foo
def foo
- render :text => 'foo'
+ render plain: 'foo'
end
def bar
- render :text => 'bar'
+ render plain: 'bar'
end
protected
@@ -442,7 +442,7 @@ class FilterTest < ActionController::TestCase
before_action :choose
%w(foo bar baz).each do |action|
- define_method(action) { render :text => action }
+ define_method(action) { render plain: action }
end
private
@@ -471,7 +471,7 @@ class FilterTest < ActionController::TestCase
@ran_filter << 'between_before_all_and_after_all'
end
def show
- render :text => 'hello'
+ render plain: 'hello'
end
end
@@ -481,7 +481,7 @@ class FilterTest < ActionController::TestCase
def around(controller)
yield
rescue ErrorToRescue => ex
- controller.__send__ :render, :text => "I rescued this: #{ex.inspect}"
+ controller.__send__ :render, plain: "I rescued this: #{ex.inspect}"
end
end
@@ -757,9 +757,8 @@ class FilterTest < ActionController::TestCase
def test_dynamic_dispatch
%w(foo bar baz).each do |action|
- request = ActionController::TestRequest.new
- request.query_parameters[:choose] = action
- response = DynamicDispatchController.action(action).call(request.env).last
+ @request.query_parameters[:choose] = action
+ response = DynamicDispatchController.action(action).call(@request.env).last
assert_equal action, response.body
end
end
@@ -820,7 +819,7 @@ class FilterTest < ActionController::TestCase
response = test_process(RescuedController)
end
- assert response.success?
+ assert response.successful?
assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
end
@@ -839,8 +838,6 @@ class FilterTest < ActionController::TestCase
private
def test_process(controller, action = "show")
@controller = controller.is_a?(Class) ? controller.new : controller
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
process(action)
end
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 72ae30eb39..22f1cc7c22 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -2,11 +2,11 @@ require 'abstract_unit'
class ForceSSLController < ActionController::Base
def banana
- render :text => "monkey"
+ render plain: "monkey"
end
def cheeseburger
- render :text => "sikachu"
+ render plain: "sikachu"
end
end
@@ -26,7 +26,7 @@ class ForceSSLCustomOptions < ForceSSLController
force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice
def force_ssl_action
- render :text => action_name
+ render plain: action_name
end
alias_method :redirect_host, :force_ssl_action
@@ -40,15 +40,15 @@ class ForceSSLCustomOptions < ForceSSLController
alias_method :redirect_notice, :force_ssl_action
def use_flash
- render :text => flash[:message]
+ render plain: flash[:message]
end
def use_alert
- render :text => flash[:alert]
+ render plain: flash[:alert]
end
def use_notice
- render :text => flash[:notice]
+ render plain: flash[:notice]
end
end
@@ -85,10 +85,10 @@ end
class RedirectToSSL < ForceSSLController
def banana
- force_ssl_redirect || render(:text => 'monkey')
+ force_ssl_redirect || render(plain: 'monkey')
end
def cheeseburger
- force_ssl_redirect('secure.cheeseburger.host') || render(:text => 'ihaz')
+ force_ssl_redirect('secure.cheeseburger.host') || render(plain: 'ihaz')
end
end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index e263ed341f..3ecfedefd1 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -73,14 +73,8 @@ module LocalAbcHelper
end
class HelperPathsTest < ActiveSupport::TestCase
- def setup
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
- end
-
def test_helpers_paths_priority
- request = ActionController::TestRequest.new
- responses = HelpersPathsController.action(:index).call(request.env)
+ responses = HelpersPathsController.action(:index).call(ActionController::TestRequest::DEFAULT_ENV.dup)
# helpers1_pack was given as a second path, so pack1_helper should be
# included as the second one
@@ -141,8 +135,7 @@ class HelperTest < ActiveSupport::TestCase
end
def call_controller(klass, action)
- request = ActionController::TestRequest.new
- klass.action(action).call(request.env)
+ klass.action(action).call(ActionController::TestRequest::DEFAULT_ENV.dup)
end
def test_helper_for_nested_controller
@@ -158,7 +151,7 @@ class HelperTest < ActiveSupport::TestCase
assert_equal "test: baz", call_controller(Fun::PdfController, "test").last.body
#
# request = ActionController::TestRequest.new
- # response = ActionController::TestResponse.new
+ # response = ActionDispatch::TestResponse.new
# request.action = 'test'
#
# assert_equal 'test: baz', Fun::PdfController.process(request, response).body
@@ -249,7 +242,7 @@ class HelperTest < ActiveSupport::TestCase
end
-class IsolatedHelpersTest < ActiveSupport::TestCase
+class IsolatedHelpersTest < ActionController::TestCase
class A < ActionController::Base
def index
render :inline => '<%= shout %>'
@@ -273,13 +266,11 @@ class IsolatedHelpersTest < ActiveSupport::TestCase
end
def call_controller(klass, action)
- request = ActionController::TestRequest.new
- klass.action(action).call(request.env)
+ klass.action(action).call(@request.env)
end
def setup
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+ super
@request.action = 'index'
end
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index adaf19c2dc..ed3632007d 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -9,19 +9,19 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
def index
- render :text => "Hello Secret"
+ render plain: "Hello Secret"
end
def display
- render :text => 'Definitely Maybe' if @logged_in
+ render plain: 'Definitely Maybe' if @logged_in
end
def show
- render :text => 'Only for loooooong credentials'
+ render plain: 'Only for loooooong credentials'
end
def search
- render :text => 'All inline'
+ render plain: 'All inline'
end
private
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 57964769a7..f06912bd5a 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -10,11 +10,11 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))}
def index
- render :text => "Hello Secret"
+ render plain: "Hello Secret"
end
def display
- render :text => 'Definitely Maybe' if @logged_in
+ render plain: 'Definitely Maybe' if @logged_in
end
private
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index 802c17b6bf..9c5a01c318 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -7,15 +7,15 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
before_action :authenticate_long_credentials, only: :show
def index
- render :text => "Hello Secret"
+ render plain: "Hello Secret"
end
def display
- render :text => 'Definitely Maybe'
+ render plain: 'Definitely Maybe'
end
def show
- render :text => 'Only for loooooong credentials'
+ render plain: 'Only for loooooong credentials'
end
private
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index a6460168cb..0b9be8d671 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -359,28 +359,28 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
class IntegrationController < ActionController::Base
def get
respond_to do |format|
- format.html { render :text => "OK", :status => 200 }
- format.js { render :text => "JS OK", :status => 200 }
+ format.html { render plain: "OK", status: 200 }
+ format.js { render plain: "JS OK", status: 200 }
format.xml { render :xml => "<root></root>", :status => 200 }
end
end
def get_with_params
- render :text => "foo: #{params[:foo]}", :status => 200
+ render plain: "foo: #{params[:foo]}", status: 200
end
def post
- render :text => "Created", :status => 201
+ render plain: "Created", status: 201
end
def method
- render :text => "method: #{request.method.downcase}"
+ render plain: "method: #{request.method.downcase}"
end
def cookie_monster
cookies["cookie_1"] = nil
cookies["cookie_3"] = "chocolate"
- render :text => "Gone", :status => 410
+ render plain: "Gone", status: 410
end
def set_cookie
@@ -389,7 +389,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
def get_cookie
- render :text => cookies["foo"]
+ render plain: cookies["foo"]
end
def redirect
@@ -760,7 +760,7 @@ end
class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
def index
- render :text => "index"
+ render plain: "index"
end
end
@@ -847,7 +847,7 @@ end
class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
def post
- render :text => "Created", :status => 201
+ render plain: "Created", status: 201
end
end
@@ -880,15 +880,15 @@ end
class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
class FooController < ActionController::Base
def index
- render :text => "foo#index"
+ render plain: "foo#index"
end
def show
- render :text => "foo#show"
+ render plain: "foo#show"
end
def edit
- render :text => "foo#show"
+ render plain: "foo#show"
end
end
@@ -898,7 +898,7 @@ class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
end
def index
- render :text => "foo#index"
+ render plain: "foo#index"
end
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 0c65270ec1..6ba361f2f7 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'active_support/concurrency/latch'
+require 'concurrent/atomics'
Thread.abort_on_exception = true
module ActionController
@@ -125,7 +125,7 @@ module ActionController
end
def render_text
- render :text => 'zomg'
+ render plain: 'zomg'
end
def default_header
@@ -145,7 +145,7 @@ module ActionController
response.headers['Content-Type'] = 'text/event-stream'
%w{ hello world }.each do |word|
response.stream.write word
- latch.await
+ latch.wait
end
response.stream.close
end
@@ -162,7 +162,7 @@ module ActionController
end
def with_stale
- render text: 'stale' if stale?(etag: "123", template: false)
+ render plain: 'stale' if stale?(etag: "123", template: false)
end
def exception_in_view
@@ -212,7 +212,7 @@ module ActionController
# .. plus one more, because the #each frees up a slot:
response.stream.write '.'
- latch.release
+ latch.count_down
# This write will block, and eventually raise
response.stream.write 'x'
@@ -233,7 +233,7 @@ module ActionController
end
logger.info 'Work complete'
- latch.release
+ latch.count_down
end
end
@@ -278,7 +278,7 @@ module ActionController
def test_async_stream
rubinius_skip "https://github.com/rubinius/rubinius/issues/2934"
- @controller.latch = ActiveSupport::Concurrency::Latch.new
+ @controller.latch = Concurrent::CountDownLatch.new
parts = ['hello', 'world']
@controller.request = @request
@@ -289,8 +289,8 @@ module ActionController
resp.stream.each do |part|
assert_equal parts.shift, part
ol = @controller.latch
- @controller.latch = ActiveSupport::Concurrency::Latch.new
- ol.release
+ @controller.latch = Concurrent::CountDownLatch.new
+ ol.count_down
end
}
@@ -300,23 +300,23 @@ module ActionController
end
def test_abort_with_full_buffer
- @controller.latch = ActiveSupport::Concurrency::Latch.new
+ @controller.latch = Concurrent::CountDownLatch.new
@request.parameters[:format] = 'plain'
@controller.request = @request
@controller.response = @response
- got_error = ActiveSupport::Concurrency::Latch.new
+ got_error = Concurrent::CountDownLatch.new
@response.stream.on_error do
ActionController::Base.logger.warn 'Error while streaming'
- got_error.release
+ got_error.count_down
end
t = Thread.new(@response) { |resp|
resp.await_commit
_, _, body = resp.to_a
body.each do |part|
- @controller.latch.await
+ @controller.latch.wait
body.close
break
end
@@ -325,13 +325,13 @@ module ActionController
capture_log_output do |output|
@controller.process :overfill_buffer_and_die
t.join
- got_error.await
+ got_error.wait
assert_match 'Error while streaming', output.rewind && output.read
end
end
def test_ignore_client_disconnect
- @controller.latch = ActiveSupport::Concurrency::Latch.new
+ @controller.latch = Concurrent::CountDownLatch.new
@controller.request = @request
@controller.response = @response
@@ -349,7 +349,7 @@ module ActionController
@controller.process :ignore_client_disconnect
t.join
Timeout.timeout(3) do
- @controller.latch.await
+ @controller.latch.wait
end
assert_match 'Work complete', output.rewind && output.read
end
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 8591bdb4d9..64eb33f78f 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -4,43 +4,50 @@ require "active_support/log_subscriber/test_helper"
class RespondToController < ActionController::Base
layout :set_layout
+ before_action {
+ case params[:v]
+ when String then request.variant = params[:v].to_sym
+ when Array then request.variant = params[:v].map(&:to_sym)
+ end
+ }
+
def html_xml_or_rss
respond_to do |type|
- type.html { render :text => "HTML" }
- type.xml { render :text => "XML" }
- type.rss { render :text => "RSS" }
- type.all { render :text => "Nothing" }
+ type.html { render body: "HTML" }
+ type.xml { render body: "XML" }
+ type.rss { render body: "RSS" }
+ type.all { render body: "Nothing" }
end
end
def js_or_html
respond_to do |type|
- type.html { render :text => "HTML" }
- type.js { render :text => "JS" }
- type.all { render :text => "Nothing" }
+ type.html { render body: "HTML" }
+ type.js { render body: "JS" }
+ type.all { render body: "Nothing" }
end
end
def json_or_yaml
respond_to do |type|
- type.json { render :text => "JSON" }
- type.yaml { render :text => "YAML" }
+ type.json { render body: "JSON" }
+ type.yaml { render body: "YAML" }
end
end
def html_or_xml
respond_to do |type|
- type.html { render :text => "HTML" }
- type.xml { render :text => "XML" }
- type.all { render :text => "Nothing" }
+ type.html { render body: "HTML" }
+ type.xml { render body: "XML" }
+ type.all { render body: "Nothing" }
end
end
def json_xml_or_html
respond_to do |type|
- type.json { render :text => 'JSON' }
+ type.json { render body: 'JSON' }
type.xml { render :xml => 'XML' }
- type.html { render :text => 'HTML' }
+ type.html { render body: 'HTML' }
end
end
@@ -49,14 +56,14 @@ class RespondToController < ActionController::Base
request.format = :xml
respond_to do |type|
- type.html { render :text => "HTML" }
- type.xml { render :text => "XML" }
+ type.html { render body: "HTML" }
+ type.xml { render body: "XML" }
end
end
def just_xml
respond_to do |type|
- type.xml { render :text => "XML" }
+ type.xml { render body: "XML" }
end
end
@@ -74,52 +81,52 @@ class RespondToController < ActionController::Base
def using_defaults_with_all
respond_to do |type|
type.html
- type.all{ render text: "ALL" }
+ type.all{ render body: "ALL" }
end
end
def made_for_content_type
respond_to do |type|
- type.rss { render :text => "RSS" }
- type.atom { render :text => "ATOM" }
- type.all { render :text => "Nothing" }
+ type.rss { render body: "RSS" }
+ type.atom { render body: "ATOM" }
+ type.all { render body: "Nothing" }
end
end
def custom_type_handling
respond_to do |type|
- type.html { render :text => "HTML" }
- type.custom("application/crazy-xml") { render :text => "Crazy XML" }
- type.all { render :text => "Nothing" }
+ type.html { render body: "HTML" }
+ type.custom("application/crazy-xml") { render body: "Crazy XML" }
+ type.all { render body: "Nothing" }
end
end
def custom_constant_handling
respond_to do |type|
- type.html { render :text => "HTML" }
- type.mobile { render :text => "Mobile" }
+ type.html { render body: "HTML" }
+ type.mobile { render body: "Mobile" }
end
end
def custom_constant_handling_without_block
respond_to do |type|
- type.html { render :text => "HTML" }
+ type.html { render body: "HTML" }
type.mobile
end
end
def handle_any
respond_to do |type|
- type.html { render :text => "HTML" }
- type.any(:js, :xml) { render :text => "Either JS or XML" }
+ type.html { render body: "HTML" }
+ type.any(:js, :xml) { render body: "Either JS or XML" }
end
end
def handle_any_any
respond_to do |type|
- type.html { render :text => 'HTML' }
- type.any { render :text => 'Whatever you ask for, I got it' }
+ type.html { render body: 'HTML' }
+ type.any { render body: 'Whatever you ask for, I got it' }
end
end
@@ -160,15 +167,15 @@ class RespondToController < ActionController::Base
request.variant = :mobile
respond_to do |type|
- type.html { render text: "mobile" }
+ type.html { render body: "mobile" }
end
end
def multiple_variants_for_format
respond_to do |type|
type.html do |html|
- html.tablet { render text: "tablet" }
- html.phone { render text: "phone" }
+ html.tablet { render body: "tablet" }
+ html.phone { render body: "phone" }
end
end
end
@@ -176,7 +183,7 @@ class RespondToController < ActionController::Base
def variant_plus_none_for_format
respond_to do |format|
format.html do |variant|
- variant.phone { render text: "phone" }
+ variant.phone { render body: "phone" }
variant.none
end
end
@@ -184,9 +191,9 @@ class RespondToController < ActionController::Base
def variant_inline_syntax
respond_to do |format|
- format.js { render text: "js" }
- format.html.none { render text: "none" }
- format.html.phone { render text: "phone" }
+ format.js { render body: "js" }
+ format.html.none { render body: "none" }
+ format.html.phone { render body: "phone" }
end
end
@@ -201,8 +208,8 @@ class RespondToController < ActionController::Base
def variant_any
respond_to do |format|
format.html do |variant|
- variant.any(:tablet, :phablet){ render text: "any" }
- variant.phone { render text: "phone" }
+ variant.any(:tablet, :phablet){ render body: "any" }
+ variant.phone { render body: "phone" }
end
end
end
@@ -210,23 +217,23 @@ class RespondToController < ActionController::Base
def variant_any_any
respond_to do |format|
format.html do |variant|
- variant.any { render text: "any" }
- variant.phone { render text: "phone" }
+ variant.any { render body: "any" }
+ variant.phone { render body: "phone" }
end
end
end
def variant_inline_any
respond_to do |format|
- format.html.any(:tablet, :phablet){ render text: "any" }
- format.html.phone { render text: "phone" }
+ format.html.any(:tablet, :phablet){ render body: "any" }
+ format.html.phone { render body: "phone" }
end
end
def variant_inline_any_any
respond_to do |format|
- format.html.phone { render text: "phone" }
- format.html.any { render text: "any" }
+ format.html.phone { render body: "phone" }
+ format.html.any { render body: "any" }
end
end
@@ -239,16 +246,16 @@ class RespondToController < ActionController::Base
def variant_any_with_none
respond_to do |format|
- format.html.any(:none, :phone){ render text: "none or phone" }
+ format.html.any(:none, :phone){ render body: "none or phone" }
end
end
def format_any_variant_any
respond_to do |format|
- format.html { render text: "HTML" }
+ format.html { render body: "HTML" }
format.any(:js, :xml) do |variant|
- variant.phone{ render text: "phone" }
- variant.any(:tablet, :phablet){ render text: "tablet" }
+ variant.phone{ render body: "phone" }
+ variant.any(:tablet, :phablet){ render body: "tablet" }
end
end
end
@@ -612,8 +619,7 @@ class RespondToControllerTest < ActionController::TestCase
logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
old_logger, ActionController::Base.logger = ActionController::Base.logger, logger
- @request.variant = :invalid
- get :variant_with_implicit_rendering
+ get :variant_with_implicit_rendering, params: { v: :invalid }
assert_response :no_content
assert_equal 1, logger.logged(:info).select{ |s| s =~ /No template found/ }.size, "Implicit head :no_content not logged"
ensure
@@ -626,28 +632,24 @@ class RespondToControllerTest < ActionController::TestCase
end
def test_variant_with_implicit_rendering
- @request.variant = :implicit
- get :variant_with_implicit_rendering
+ get :variant_with_implicit_rendering, params: { v: :implicit }
assert_response :no_content
end
def test_variant_with_implicit_template_rendering
- @request.variant = :mobile
- get :variant_with_implicit_rendering
+ get :variant_with_implicit_rendering, params: { v: :mobile }
assert_equal "text/html", @response.content_type
assert_equal "mobile", @response.body
end
def test_variant_with_format_and_custom_render
- @request.variant = :phone
- get :variant_with_format_and_custom_render
+ get :variant_with_format_and_custom_render, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "mobile", @response.body
end
def test_multiple_variants_for_format
- @request.variant = :tablet
- get :multiple_variants_for_format
+ get :multiple_variants_for_format, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "tablet", @response.body
end
@@ -667,32 +669,27 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal "none", @response.body
- @request.variant = :phone
- get :variant_inline_syntax
+ get :variant_inline_syntax, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
def test_variant_inline_syntax_without_block
- @request.variant = :phone
- get :variant_inline_syntax_without_block
+ get :variant_inline_syntax_without_block, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
def test_variant_any
- @request.variant = :phone
- get :variant_any
+ get :variant_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
- @request.variant = :tablet
- get :variant_any
+ get :variant_any, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
- @request.variant = :phablet
- get :variant_any
+ get :variant_any, params: { v: :phablet }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
end
@@ -702,54 +699,45 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
- @request.variant = :phone
- get :variant_any_any
+ get :variant_any_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
- @request.variant = :yolo
- get :variant_any_any
+ get :variant_any_any, params: { v: :yolo }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
end
def test_variant_inline_any
- @request.variant = :phone
- get :variant_any
+ get :variant_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
- @request.variant = :tablet
- get :variant_inline_any
+ get :variant_inline_any, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
- @request.variant = :phablet
- get :variant_inline_any
+ get :variant_inline_any, params: { v: :phablet }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
end
def test_variant_inline_any_any
- @request.variant = :phone
- get :variant_inline_any_any
+ get :variant_inline_any_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
- @request.variant = :yolo
- get :variant_inline_any_any
+ get :variant_inline_any_any, params: { v: :yolo }
assert_equal "text/html", @response.content_type
assert_equal "any", @response.body
end
def test_variant_any_implicit_render
- @request.variant = :tablet
- get :variant_any_implicit_render
+ get :variant_any_implicit_render, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "tablet", @response.body
- @request.variant = :phablet
- get :variant_any_implicit_render
+ get :variant_any_implicit_render, params: { v: :phablet }
assert_equal "text/html", @response.content_type
assert_equal "phablet", @response.body
end
@@ -759,36 +747,31 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
assert_equal "none or phone", @response.body
- @request.variant = :phone
- get :variant_any_with_none
+ get :variant_any_with_none, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "none or phone", @response.body
end
def test_format_any_variant_any
- @request.variant = :tablet
- get :format_any_variant_any, format: :js
+ get :format_any_variant_any, format: :js, params: { v: :tablet }
assert_equal "text/javascript", @response.content_type
assert_equal "tablet", @response.body
end
def test_variant_negotiation_inline_syntax
- @request.variant = [:tablet, :phone]
- get :variant_inline_syntax_without_block
+ get :variant_inline_syntax_without_block, params: { v: [:tablet, :phone] }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
def test_variant_negotiation_block_syntax
- @request.variant = [:tablet, :phone]
- get :variant_plus_none_for_format
+ get :variant_plus_none_for_format, params: { v: [:tablet, :phone] }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
def test_variant_negotiation_without_block
- @request.variant = [:tablet, :phone]
- get :variant_inline_syntax_without_block
+ get :variant_inline_syntax_without_block, params: { v: [:tablet, :phone] }
assert_equal "text/html", @response.content_type
assert_equal "phone", @response.body
end
@@ -797,7 +780,7 @@ end
class RespondToWithBlockOnDefaultRenderController < ActionController::Base
def show
default_render do
- render text: 'default_render yielded'
+ render body: 'default_render yielded'
end
end
end
@@ -811,6 +794,6 @@ class RespondToWithBlockOnDefaultRenderControllerTest < ActionController::TestCa
def test_default_render_uses_block_when_no_template_exists
get :show
assert_equal "default_render yielded", @response.body
- assert_equal "text/html", @response.content_type
+ assert_equal "text/plain", @response.content_type
end
end
diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb
index 964f22eb03..0755dafe93 100644
--- a/actionpack/test/controller/new_base/base_test.rb
+++ b/actionpack/test/controller/new_base/base_test.rb
@@ -6,7 +6,7 @@ module Dispatching
before_action :authenticate
def index
- render :text => "success"
+ render body: "success"
end
def modify_response_body
@@ -22,7 +22,7 @@ module Dispatching
end
def show_actions
- render :text => "actions: #{action_methods.to_a.sort.join(', ')}"
+ render body: "actions: #{action_methods.to_a.sort.join(', ')}"
end
protected
@@ -51,7 +51,7 @@ module Dispatching
assert_body "success"
assert_status 200
- assert_content_type "text/html; charset=utf-8"
+ assert_content_type "text/plain; charset=utf-8"
end
# :api: plugin
diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb
index c8166280fc..c0e92b3b05 100644
--- a/actionpack/test/controller/new_base/content_negotiation_test.rb
+++ b/actionpack/test/controller/new_base/content_negotiation_test.rb
@@ -9,7 +9,7 @@ module ContentNegotiation
)]
def all
- render :text => self.formats.inspect
+ render plain: self.formats.inspect
end
end
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index 88453988dd..0445a837ca 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -3,16 +3,16 @@ require 'abstract_unit'
module ContentType
class BaseController < ActionController::Base
def index
- render :text => "Hello world!"
+ render body: "Hello world!"
end
def set_on_response_obj
response.content_type = Mime::RSS
- render :text => "Hello world!"
+ render body: "Hello world!"
end
def set_on_render
- render :text => "Hello world!", :content_type => Mime::RSS
+ render body: "Hello world!", content_type: Mime::RSS
end
end
@@ -30,17 +30,17 @@ module ContentType
class CharsetController < ActionController::Base
def set_on_response_obj
response.charset = "utf-16"
- render :text => "Hello world!"
+ render body: "Hello world!"
end
def set_as_nil_on_response_obj
response.charset = nil
- render :text => "Hello world!"
+ render body: "Hello world!"
end
end
class ExplicitContentTypeTest < Rack::TestCase
- test "default response is HTML and UTF8" do
+ test "default response is text/plain and UTF8" do
with_routing do |set|
set.draw do
get ':controller', :action => 'index'
@@ -49,7 +49,7 @@ module ContentType
get "/content_type/base"
assert_body "Hello world!"
- assert_header "Content-Type", "text/html; charset=utf-8"
+ assert_header "Content-Type", "text/plain; charset=utf-8"
end
end
@@ -99,14 +99,14 @@ module ContentType
get "/content_type/charset/set_on_response_obj"
assert_body "Hello world!"
- assert_header "Content-Type", "text/html; charset=utf-16"
+ assert_header "Content-Type", "text/plain; charset=utf-16"
end
test "setting the charset of the response as nil directly on the response object" do
get "/content_type/charset/set_as_nil_on_response_obj"
assert_body "Hello world!"
- assert_header "Content-Type", "text/html; charset=utf-8"
+ assert_header "Content-Type", "text/plain; charset=utf-8"
end
end
end
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index 11a19ab783..963f2c2f5c 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -37,14 +37,14 @@ module Render
private
def secretz
- render :text => "FAIL WHALE!"
+ render plain: "FAIL WHALE!"
end
end
class DoubleRenderController < ActionController::Base
def index
- render :text => "hello"
- render :text => "world"
+ render plain: "hello"
+ render plain: "world"
end
end
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index 10bad57cd6..435bb18dce 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/deprecation'
module RenderText
class MinimalController < ActionController::Metal
@@ -73,7 +74,10 @@ module RenderText
class RenderTextTest < Rack::TestCase
test "rendering text from a minimal controller" do
- get "/render_text/minimal/index"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/minimal/index"
+ end
+
assert_body "Hello World!"
assert_status 200
end
@@ -82,7 +86,10 @@ module RenderText
with_routing do |set|
set.draw { get ':controller', action: 'index' }
- get "/render_text/simple"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/simple"
+ end
+
assert_body "hello david"
assert_status 200
end
@@ -92,7 +99,9 @@ module RenderText
with_routing do |set|
set.draw { get ':controller', action: 'index' }
- get "/render_text/with_layout"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout"
+ end
assert_body "hello david"
assert_status 200
@@ -100,59 +109,81 @@ module RenderText
end
test "rendering text, while also providing a custom status code" do
- get "/render_text/with_layout/custom_code"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout/custom_code"
+ end
assert_body "hello world"
assert_status 404
end
test "rendering text with nil returns an empty body" do
- get "/render_text/with_layout/with_nil"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout/with_nil"
+ end
assert_body ""
assert_status 200
end
test "Rendering text with nil and custom status code returns an empty body and the status" do
- get "/render_text/with_layout/with_nil_and_status"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout/with_nil_and_status"
+ end
assert_body ""
assert_status 403
end
test "rendering text with false returns the string 'false'" do
- get "/render_text/with_layout/with_false"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout/with_false"
+ end
assert_body "false"
assert_status 200
end
test "rendering text with layout: true" do
- get "/render_text/with_layout/with_layout_true"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout/with_layout_true"
+ end
assert_body "hello world, I'm here!"
assert_status 200
end
test "rendering text with layout: 'greetings'" do
- get "/render_text/with_layout/with_custom_layout"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout/with_custom_layout"
+ end
assert_body "hello world, I wish thee well."
assert_status 200
end
test "rendering text with layout: false" do
- get "/render_text/with_layout/with_layout_false"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout/with_layout_false"
+ end
assert_body "hello world"
assert_status 200
end
test "rendering text with layout: nil" do
- get "/render_text/with_layout/with_layout_nil"
+ ActiveSupport::Deprecation.silence do
+ get "/render_text/with_layout/with_layout_nil"
+ end
assert_body "hello world"
assert_status 200
end
+
+ test "rendering text displays deprecation warning" do
+ assert_deprecated do
+ get "/render_text/with_layout/with_layout_nil"
+ end
+ end
end
end
diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb
index 744d8664be..6c57c4caeb 100644
--- a/actionpack/test/controller/parameters/mutators_test.rb
+++ b/actionpack/test/controller/parameters/mutators_test.rb
@@ -62,11 +62,15 @@ class ParametersMutatorsTest < ActiveSupport::TestCase
end
test "select! retains permitted status" do
+ jruby_skip "https://github.com/jruby/jruby/issues/3137"
+
@params.permit!
assert @params.select! { |k| k != "person" }.permitted?
end
test "select! retains unpermitted status" do
+ jruby_skip "https://github.com/jruby/jruby/issues/3137"
+
assert_not @params.select! { |k| k != "person" }.permitted?
end
diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb
index 3b1257e8d5..7151a8567c 100644
--- a/actionpack/test/controller/parameters/nested_parameters_test.rb
+++ b/actionpack/test/controller/parameters/nested_parameters_test.rb
@@ -136,7 +136,7 @@ class NestedParametersTest < ActiveSupport::TestCase
authors_attributes: {
:'0' => { name: 'William Shakespeare', age_of_death: '52' },
:'1' => { name: 'Unattributed Assistant' },
- :'2' => { name: %w(injected names)}
+ :'2' => { name: %w(injected names) }
}
}
})
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 2ed486516d..9f7d14e85d 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -194,6 +194,19 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal "monkey", @params.fetch(:foo) { "monkey" }
end
+ test "fetch doesnt raise ParameterMissing exception if there is a default that is nil" do
+ assert_equal nil, @params.fetch(:foo, nil)
+ assert_equal nil, @params.fetch(:foo) { nil }
+ end
+
+ test 'KeyError in fetch block should not be covered up' do
+ params = ActionController::Parameters.new
+ e = assert_raises(KeyError) do
+ params.fetch(:missing_key) { {}.fetch(:also_missing) }
+ end
+ assert_match(/:also_missing$/, e.message)
+ end
+
test "not permitted is sticky beyond merges" do
assert !@params.merge(a: "b").permitted?
end
@@ -253,7 +266,6 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert @params.to_h.is_a? Hash
assert_not @params.to_h.is_a? ActionController::Parameters
- assert_equal @params.to_hash, @params.to_h
end
test "to_h returns converted hash when .permit_all_parameters is set" do
@@ -284,6 +296,5 @@ class ParametersPermitTest < ActiveSupport::TestCase
test "to_unsafe_h returns unfiltered params" do
assert @params.to_h.is_a? Hash
assert_not @params.to_h.is_a? ActionController::Parameters
- assert_equal @params.to_hash, @params.to_unsafe_h
end
end
diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb
index 7136fafae5..7c753a45a5 100644
--- a/actionpack/test/controller/permitted_params_test.rb
+++ b/actionpack/test/controller/permitted_params_test.rb
@@ -2,11 +2,11 @@ require 'abstract_unit'
class PeopleController < ActionController::Base
def create
- render text: params[:person].permitted? ? "permitted" : "forbidden"
+ render plain: params[:person].permitted? ? "permitted" : "forbidden"
end
def create_with_permit
- render text: params[:person].permit(:name).permitted? ? "permitted" : "forbidden"
+ render plain: params[:person].permit(:name).permitted? ? "permitted" : "forbidden"
end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 4f5ca46b04..91b30ede6a 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -3,8 +3,8 @@ require 'abstract_unit'
class RedirectController < ActionController::Base
# empty method not used anywhere to ensure methods like
# `status` and `location` aren't called on `redirect_to` calls
- def status; render :text => 'called status'; end
- def location; render :text => 'called location'; end
+ def status; render plain: 'called status'; end
+ def location; render plain: 'called location'; end
def simple_redirect
redirect_to :action => "hello_world"
diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb
index b1ad16bc55..3773900cc4 100644
--- a/actionpack/test/controller/render_json_test.rb
+++ b/actionpack/test/controller/render_json_test.rb
@@ -28,7 +28,7 @@ class RenderJsonTest < ActionController::TestCase
end
def render_json_render_to_string
- render :text => render_to_string(:json => '[]')
+ render plain: render_to_string(json: '[]')
end
def render_json_hello_world
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 43e992d432..9acdc29aeb 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -10,16 +10,16 @@ class TestControllerWithExtraEtags < ActionController::Base
etag { nil }
def fresh
- render text: "stale" if stale?(etag: '123', template: false)
+ render plain: "stale" if stale?(etag: '123', template: false)
end
def array
- render text: "stale" if stale?(etag: %w(1 2 3), template: false)
+ render plain: "stale" if stale?(etag: %w(1 2 3), template: false)
end
def with_template
if stale? template: 'test/hello_world'
- render text: 'stale'
+ render plain: 'stale'
end
end
end
@@ -622,7 +622,7 @@ class HttpCacheForeverTest < ActionController::TestCase
class HttpCacheForeverController < ActionController::Base
def cache_me_forever
http_cache_forever(public: params[:public], version: params[:version] || 'v1') do
- render text: 'hello'
+ render plain: 'hello'
end
end
end
diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb
index 77a2f68b1c..e5d698d5c2 100644
--- a/actionpack/test/controller/request/test_request_test.rb
+++ b/actionpack/test/controller/request/test_request_test.rb
@@ -1,11 +1,7 @@
require 'abstract_unit'
require 'stringio'
-class ActionController::TestRequestTest < ActiveSupport::TestCase
-
- def setup
- @request = ActionController::TestRequest.new
- end
+class ActionController::TestRequestTest < ActionController::TestCase
def test_test_request_has_session_options_initialized
assert @request.session_options
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 82c808754c..868520a219 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -13,7 +13,7 @@ module RequestForgeryProtectionActions
end
def unsafe
- render :text => 'pwn'
+ render plain: 'pwn'
end
def meta
@@ -484,11 +484,10 @@ end
class FreeCookieControllerTest < ActionController::TestCase
def setup
@controller = FreeCookieController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@token = "cf50faa3fe97702ca1ae"
SecureRandom.stubs(:base64).returns(@token)
+ super
end
def test_should_not_render_form_with_token_tag
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 2cfd0b8023..e767323773 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -43,8 +43,8 @@ class RescueController < ActionController::Base
rescue_from NotAllowed, :with => proc { head :forbidden }
rescue_from 'RescueController::NotAllowedToRescueAsString', :with => proc { head :forbidden }
- rescue_from InvalidRequest, :with => proc { |exception| render :text => exception.message }
- rescue_from 'InvalidRequestToRescueAsString', :with => proc { |exception| render :text => exception.message }
+ rescue_from InvalidRequest, with: proc { |exception| render plain: exception.message }
+ rescue_from 'InvalidRequestToRescueAsString', with: proc { |exception| render plain: exception.message }
rescue_from BadGateway do
head 502
@@ -54,18 +54,18 @@ class RescueController < ActionController::Base
end
rescue_from ResourceUnavailable do |exception|
- render :text => exception.message
+ render plain: exception.message
end
rescue_from 'ResourceUnavailableToRescueAsString' do |exception|
- render :text => exception.message
+ render plain: exception.message
end
rescue_from ActionView::TemplateError do
- render :text => 'action_view templater error'
+ render plain: 'action_view templater error'
end
rescue_from IOError do
- render :text => 'io error'
+ render plain: 'io error'
end
before_action(only: :before_action_raises) { raise 'umm nice' }
@@ -74,7 +74,7 @@ class RescueController < ActionController::Base
end
def raises
- render :text => 'already rendered'
+ render plain: 'already rendered'
raise "don't panic!"
end
@@ -302,7 +302,7 @@ class RescueTest < ActionDispatch::IntegrationTest
rescue_from RecordInvalid, :with => :show_errors
def foo
- render :text => "foo"
+ render plain: "foo"
end
def invalid
@@ -315,7 +315,7 @@ class RescueTest < ActionDispatch::IntegrationTest
protected
def show_errors(exception)
- render :text => exception.message
+ render plain: exception.message
end
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index f3da2df3ef..5a279639cc 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -1207,8 +1207,6 @@ class ResourcesTest < ActionController::TestCase
@controller = "#{options[:options][:controller].camelize}Controller".constantize.new
@controller.singleton_class.include(@routes.url_helpers)
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
get :index, params: options[:options]
options[:options].delete :action
@@ -1277,8 +1275,6 @@ class ResourcesTest < ActionController::TestCase
(options[:options] ||= {})[:controller] ||= singleton_name.to_s.pluralize
@controller = "#{options[:options][:controller].camelize}Controller".constantize.new
@controller.singleton_class.include(@routes.url_helpers)
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
get :show, params: options[:options]
options[:options].delete :action
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 9bbfb74e72..a333290ade 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -335,9 +335,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_route_with_colon_first
rs.draw do
- get '/:controller/:action/:id', :action => 'index', :id => nil
- get ':url', :controller => 'tiny_url', :action => 'translate'
+ get '/:controller/:action/:id', action: 'index', id: nil
+ get ':url', controller: 'content', action: 'translate'
end
+
+ assert_equal({controller: 'content', action: 'translate', url: 'example'}, rs.recognize_path('/example'))
end
def test_route_with_regexp_for_controller
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 36c57ec9b2..c0ddcf7f50 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -34,8 +34,6 @@ class SendFileTest < ActionController::TestCase
def setup
@controller = SendFileController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
end
def test_file_nostream
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 86ffb898ad..1c5de983d8 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -6,78 +6,78 @@ require 'rails/engine'
class TestCaseTest < ActionController::TestCase
class TestController < ActionController::Base
def no_op
- render text: 'dummy'
+ render plain: 'dummy'
end
def set_flash
flash["test"] = ">#{flash["test"]}<"
- render text: 'ignore me'
+ render plain: 'ignore me'
end
def delete_flash
flash.delete("test")
- render :text => 'ignore me'
+ render plain: 'ignore me'
end
def set_flash_now
flash.now["test_now"] = ">#{flash["test_now"]}<"
- render text: 'ignore me'
+ render plain: 'ignore me'
end
def set_session
session['string'] = 'A wonder'
session[:symbol] = 'it works'
- render text: 'Success'
+ render plain: 'Success'
end
def reset_the_session
reset_session
- render text: 'ignore me'
+ render plain: 'ignore me'
end
def render_raw_post
raise ActiveSupport::TestCase::Assertion, "#raw_post is blank" if request.raw_post.blank?
- render text: request.raw_post
+ render plain: request.raw_post
end
def render_body
- render text: request.body.read
+ render plain: request.body.read
end
def test_params
- render text: params.inspect
+ render plain: ::JSON.dump(params.to_unsafe_h)
end
def test_query_parameters
- render text: request.query_parameters.inspect
+ render plain: ::JSON.dump(request.query_parameters)
end
def test_request_parameters
- render text: request.request_parameters.inspect
+ render plain: request.request_parameters.inspect
end
def test_uri
- render text: request.fullpath
+ render plain: request.fullpath
end
def test_format
- render text: request.format
+ render plain: request.format
end
def test_query_string
- render text: request.query_string
+ render plain: request.query_string
end
def test_protocol
- render text: request.protocol
+ render plain: request.protocol
end
def test_headers
- render text: request.headers.env.to_json
+ render plain: request.headers.env.to_json
end
def test_html_output
- render text: <<HTML
+ render plain: <<HTML
<html>
<body>
<a href="/"><img src="/images/button.png" /></a>
@@ -99,7 +99,7 @@ HTML
def test_xml_output
response.content_type = "application/xml"
- render text: <<XML
+ render plain: <<XML
<?xml version="1.0" encoding="UTF-8"?>
<root>
<area>area is an empty tag in HTML, raising an error if not in xml mode</area>
@@ -108,15 +108,15 @@ XML
end
def test_only_one_param
- render text: (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
+ render plain: (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
end
def test_remote_addr
- render text: (request.remote_addr || "not specified")
+ render plain: (request.remote_addr || "not specified")
end
def test_file_upload
- render text: params[:file].size
+ render plain: params[:file].size
end
def test_send_file
@@ -158,8 +158,6 @@ XML
def setup
super
@controller = TestController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@request.env['PATH_INFO'] = nil
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
@@ -172,7 +170,7 @@ XML
before_action { @dynamic_opt = 'opt' }
def test_url_options_reset
- render text: url_for(params)
+ render plain: url_for(params)
end
def default_url_options
@@ -229,7 +227,7 @@ XML
def test_document_body_and_params_with_post
post :test_params, params: { id: 1 }
- assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @response.body)
+ assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body))
end
def test_document_body_with_post
@@ -485,7 +483,7 @@ XML
assert_deprecated {
get :test_params, page: { name: "Page name", month: '4', year: '2004', day: '6' }
}
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{
'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -504,7 +502,7 @@ XML
day: '6'
}
}
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{
'controller' => 'test_case_test/test', 'action' => 'test_params',
@@ -516,8 +514,8 @@ XML
def test_query_param_named_action
get :test_query_parameters, params: {action: 'foobar'}
- parsed_params = eval(@response.body)
- assert_equal({action: 'foobar'}, parsed_params)
+ parsed_params = JSON.parse(@response.body)
+ assert_equal({'action' => 'foobar'}, parsed_params)
end
def test_request_param_named_action
@@ -536,7 +534,7 @@ XML
}
}, session: { 'foo' => 'bar' }, flash: { notice: 'created' }
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
@@ -551,7 +549,7 @@ XML
get :test_params, params: {
page: { name: "Page name", month: 4, year: 2004, day: 6 }
}
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
@@ -561,17 +559,17 @@ XML
def test_params_passing_with_fixnums_when_not_html_request
get :test_params, params: { format: 'json', count: 999 }
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
- 'format' => 'json', 'count' => 999 },
+ 'format' => 'json', 'count' => '999' },
parsed_params
)
end
def test_params_passing_path_parameter_is_string_when_not_html_request
get :test_params, params: { format: 'json', id: 1 }
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'format' => 'json', 'id' => '1' },
@@ -581,7 +579,7 @@ XML
def test_deprecated_params_passing_path_parameter_is_string_when_not_html_request
assert_deprecated { get :test_params, format: 'json', id: 1 }
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'format' => 'json', 'id' => '1' },
@@ -595,7 +593,7 @@ XML
frozen: 'icy'.freeze, frozens: ['icy'.freeze].freeze, deepfreeze: { frozen: 'icy'.freeze }.freeze
}
end
- parsed_params = eval(@response.body)
+ parsed_params = ::JSON.parse(@response.body)
assert_equal(
{'controller' => 'test_case_test/test', 'action' => 'test_params',
'frozen' => 'icy', 'frozens' => ['icy'], 'deepfreeze' => { 'frozen' => 'icy' }},
@@ -693,13 +691,13 @@ XML
def test_deprecated_xhr_with_params
assert_deprecated { xhr :get, :test_params, params: { id: 1 } }
- assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @response.body)
+ assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body))
end
def test_xhr_with_params
get :test_params, params: { id: 1 }, xhr: true
- assert_equal(%({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}), @response.body)
+ assert_equal({"id"=>"1", "controller"=>"test_case_test/test", "action"=>"test_params"}, ::JSON.parse(@response.body))
end
def test_xhr_with_session
@@ -720,12 +718,6 @@ XML
assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access"
end
- def test_header_properly_reset_after_get_request
- get :test_params
- @request.recycle!
- assert_nil @request.instance_variable_get("@request_method")
- end
-
def test_deprecated_params_reset_between_post_requests
assert_deprecated { post :no_op, foo: "bar" }
assert_equal "bar", @request.params[:foo]
@@ -916,7 +908,7 @@ XML
filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}"
assert_deprecated {
- post :test_file_upload, file: ActionDispatch::Http::UploadedFile.new(filename: path, type: "image/jpg", tempfile: File.open(path))
+ post :test_file_upload, file: Rack::Test::UploadedFile.new(path, "image/jpg", true)
}
assert_equal '159528', @response.body
end
@@ -925,7 +917,7 @@ XML
filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}"
post :test_file_upload, params: {
- file: ActionDispatch::Http::UploadedFile.new(filename: path, type: "image/jpg", tempfile: File.open(path))
+ file: Rack::Test::UploadedFile.new(path, "image/jpg", true)
}
assert_equal '159528', @response.body
end
@@ -957,10 +949,11 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
end
end
- setup do
+ def before_setup
@original = ActionDispatch::Response.default_headers
@defaults = { 'A' => '1', 'B' => '2' }
ActionDispatch::Response.default_headers = @defaults
+ super
end
teardown do
@@ -970,8 +963,6 @@ class ResponseDefaultHeadersTest < ActionController::TestCase
def setup
super
@controller = TestController.new
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
@request.env['PATH_INFO'] = nil
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
@@ -1006,7 +997,7 @@ module EngineControllerTests
class BarController < ActionController::Base
def index
- render text: 'bar'
+ render plain: 'bar'
end
end
@@ -1092,7 +1083,7 @@ class AnonymousControllerTest < ActionController::TestCase
def setup
@controller = Class.new(ActionController::Base) do
def index
- render text: params[:controller]
+ render plain: params[:controller]
end
end.new
@@ -1113,11 +1104,11 @@ class RoutingDefaultsTest < ActionController::TestCase
def setup
@controller = Class.new(ActionController::Base) do
def post
- render text: request.fullpath
+ render plain: request.fullpath
end
def project
- render text: request.fullpath
+ render plain: request.fullpath
end
end.new
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index d9a1ae7d4f..5f2abc9606 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'controller/fake_controllers'
-class UrlRewriterTests < ActiveSupport::TestCase
+class UrlRewriterTests < ActionController::TestCase
class Rewriter
def initialize(request)
@options = {
@@ -16,7 +16,6 @@ class UrlRewriterTests < ActiveSupport::TestCase
end
def setup
- @request = ActionController::TestRequest.new
@params = {}
@rewriter = Rewriter.new(@request) #.new(@request, @params)
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index 21fa670bb6..b26f037c36 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -5,16 +5,22 @@ class WebServiceTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
def assign_parameters
if params[:full]
- render :text => dump_params_keys
+ render plain: dump_params_keys
else
- render :text => (params.keys - ['controller', 'action']).sort.join(", ")
+ render plain: (params.keys - ['controller', 'action']).sort.join(", ")
end
end
def dump_params_keys(hash = params)
hash.keys.sort.inject("") do |s, k|
value = hash[k]
- value = Hash === value ? "(#{dump_params_keys(value)})" : ""
+
+ if value.is_a?(Hash) || value.is_a?(ActionController::Parameters)
+ value = "(#{dump_params_keys(value)})"
+ else
+ value = ""
+ end
+
s << ", " unless s.empty?
s << "#{k}#{value}"
end
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index 512f3a8a7a..5cfa5f7b3b 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'active_support/concurrency/latch'
+require 'concurrent/atomics'
module ActionController
module Live
@@ -27,18 +27,18 @@ module ActionController
end
def test_parallel
- latch = ActiveSupport::Concurrency::Latch.new
+ latch = Concurrent::CountDownLatch.new
t = Thread.new {
@response.stream.write 'foo'
- latch.await
+ latch.wait
@response.stream.close
}
@response.await_commit
@response.each do |part|
assert_equal 'foo', part
- latch.release
+ latch.count_down
end
assert t.join
end
@@ -62,15 +62,15 @@ module ActionController
def test_headers_cannot_be_written_after_webserver_reads
@response.stream.write 'omg'
- latch = ActiveSupport::Concurrency::Latch.new
+ latch = Concurrent::CountDownLatch.new
t = Thread.new {
@response.stream.each do |chunk|
- latch.release
+ latch.count_down
end
}
- latch.await
+ latch.wait
assert @response.headers.frozen?
e = assert_raises(ActionDispatch::IllegalStateError) do
@response.headers['Content-Length'] = "zomg"
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index f90d5499d7..d75e31db62 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -73,26 +73,26 @@ module TestGenerationPrefix
include RailsApplication.routes.mounted_helpers
def index
- render :text => posts_path
+ render plain: posts_path
end
def show
- render :text => post_path(:id => params[:id])
+ render plain: post_path(id: params[:id])
end
def url_to_application
path = main_app.url_for(:controller => "outside_engine_generating",
:action => "index",
:only_path => true)
- render :text => path
+ render plain: path
end
def polymorphic_path_for_engine
- render :text => polymorphic_path(Post.new)
+ render plain: polymorphic_path(Post.new)
end
def conflicting
- render :text => "engine"
+ render plain: "engine"
end
end
@@ -101,28 +101,28 @@ module TestGenerationPrefix
include RailsApplication.routes.url_helpers
def index
- render :text => blog_engine.post_path(:id => 1)
+ render plain: blog_engine.post_path(id: 1)
end
def polymorphic_path_for_engine
- render :text => blog_engine.polymorphic_path(Post.new)
+ render plain: blog_engine.polymorphic_path(Post.new)
end
def polymorphic_path_for_app
- render :text => polymorphic_path(Post.new)
+ render plain: polymorphic_path(Post.new)
end
def polymorphic_with_url_for
- render :text => blog_engine.url_for(Post.new)
+ render plain: blog_engine.url_for(Post.new)
end
def conflicting
- render :text => "application"
+ render plain: "application"
end
def ivar_usage
@blog_engine = "Not the engine route helper"
- render :text => blog_engine.post_path(:id => 1)
+ render plain: blog_engine.post_path(id: 1)
end
end
@@ -378,7 +378,7 @@ module TestGenerationPrefix
include RailsApplication.routes.mounted_helpers
def show
- render :text => post_path(:id => params[:id])
+ render plain: post_path(id: params[:id])
end
end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index d77341bc64..c2300a0142 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -113,7 +113,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
def parse
self.class.last_request_parameters = request.request_parameters
- self.class.last_parameters = params
+ self.class.last_parameters = params.to_unsafe_h
head :ok
end
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 50f69c53cb..939a771c65 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -17,7 +17,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
def read
- render :text => "File: #{params[:uploaded_data].read}"
+ render plain: "File: #{params[:uploaded_data].read}"
end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 7aca251066..780e7dc3e2 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -42,6 +42,13 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal Encoding::UTF_8, response.body.encoding
end
+ def test_response_charset_writer
+ @response.charset = 'utf-16'
+ assert_equal 'utf-16', @response.charset
+ @response.charset = nil
+ assert_equal 'utf-8', @response.charset
+ end
+
test "simple output" do
@response.body = "Hello, World!"
@@ -171,6 +178,8 @@ class ResponseTest < ActiveSupport::TestCase
end
test "read content type without charset" do
+ jruby_skip "https://github.com/jruby/jruby/issues/3138"
+
original = ActionDispatch::Response.default_charset
begin
ActionDispatch::Response.default_charset = 'utf-16'
diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb
index 7ef513b0c8..361ceca677 100644
--- a/actionpack/test/dispatch/routing/concerns_test.rb
+++ b/actionpack/test/dispatch/routing/concerns_test.rb
@@ -109,6 +109,8 @@ class RoutingConcernsTest < ActionDispatch::IntegrationTest
end
def test_concerns_executes_block_in_context_of_current_mapper
+ jruby_skip "https://github.com/jruby/jruby/issues/3143"
+
mapper = ActionDispatch::Routing::Mapper.new(ActionDispatch::Routing::RouteSet.new)
mapper.concern :test_concern do
resources :things
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 26b8636c2f..b18a9ab647 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3729,7 +3729,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
module ::Admin
class StorageFilesController < ActionController::Base
def index
- render :text => "admin/storage_files#index"
+ render plain: "admin/storage_files#index"
end
end
end
@@ -3824,7 +3824,7 @@ class TestDefaultScope < ActionDispatch::IntegrationTest
module ::Blog
class PostsController < ActionController::Base
def index
- render :text => "blog/posts#index"
+ render plain: "blog/posts#index"
end
end
end
@@ -4164,13 +4164,13 @@ end
class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
class CategoriesController < ActionController::Base
def show
- render :text => "categories#show"
+ render plain: "categories#show"
end
end
class ProductsController < ActionController::Base
def show
- render :text => "products#show"
+ render plain: "products#show"
end
end
@@ -4265,7 +4265,7 @@ end
class TestInvalidUrls < ActionDispatch::IntegrationTest
class FooController < ActionController::Base
def show
- render :text => "foo#show"
+ render plain: "foo#show"
end
end
@@ -4568,7 +4568,7 @@ end
class TestDefaultUrlOptions < ActionDispatch::IntegrationTest
class PostsController < ActionController::Base
def archive
- render :text => "posts#archive"
+ render plain: "posts#archive"
end
end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index 22a46b0930..e6a70358c8 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -18,11 +18,11 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
end
def get_session_value
- render :text => "foo: #{session[:foo].inspect}"
+ render plain: "foo: #{session[:foo].inspect}"
end
def get_session_id
- render :text => "#{request.session.id}"
+ render plain: "#{request.session.id}"
end
def call_reset_session
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index e7f4235de8..715eb90566 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -16,25 +16,25 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
end
def persistent_session_id
- render :text => session[:session_id]
+ render plain: session[:session_id]
end
def set_session_value
session[:foo] = "bar"
- render :text => Rack::Utils.escape(Verifier.generate(session.to_hash))
+ render plain: Rack::Utils.escape(Verifier.generate(session.to_hash))
end
def get_session_value
- render :text => "foo: #{session[:foo].inspect}"
+ render plain: "foo: #{session[:foo].inspect}"
end
def get_session_id
- render :text => "id: #{request.session.id}"
+ render plain: "id: #{request.session.id}"
end
def get_class_after_reset_session
reset_session
- render :text => "class: #{session.class}"
+ render plain: "class: #{session.class}"
end
def call_session_clear
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 9a5d5131c0..6e9107ecbf 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -19,11 +19,11 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
end
def get_session_value
- render :text => "foo: #{session[:foo].inspect}"
+ render plain: "foo: #{session[:foo].inspect}"
end
def get_session_id
- render :text => "#{request.session.id}"
+ render plain: "#{request.session.id}"
end
def call_reset_session
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
index cc35d4594e..ede1cec4e6 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
class TestRequestTest < ActiveSupport::TestCase
test "sane defaults" do
- env = ActionDispatch::TestRequest.new.env
+ env = ActionDispatch::TestRequest.create.env
assert_equal "GET", env.delete("REQUEST_METHOD")
assert_equal "off", env.delete("HTTPS")
@@ -24,12 +24,10 @@ class TestRequestTest < ActiveSupport::TestCase
assert_equal true, env.delete("rack.multithread")
assert_equal true, env.delete("rack.multiprocess")
assert_equal false, env.delete("rack.run_once")
-
- assert env.empty?, env.inspect
end
test "cookie jar" do
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal({}, req.cookies)
assert_equal nil, req.env["HTTP_COOKIE"]
@@ -57,38 +55,38 @@ class TestRequestTest < ActiveSupport::TestCase
test "does not complain when Rails.application is nil" do
Rails.stubs(:application).returns(nil)
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal false, req.env.empty?
end
test "default remote address is 0.0.0.0" do
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal '0.0.0.0', req.remote_addr
end
test "allows remote address to be overridden" do
- req = ActionDispatch::TestRequest.new('REMOTE_ADDR' => '127.0.0.1')
+ req = ActionDispatch::TestRequest.create('REMOTE_ADDR' => '127.0.0.1')
assert_equal '127.0.0.1', req.remote_addr
end
test "default host is test.host" do
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal 'test.host', req.host
end
test "allows host to be overridden" do
- req = ActionDispatch::TestRequest.new('HTTP_HOST' => 'www.example.com')
+ req = ActionDispatch::TestRequest.create('HTTP_HOST' => 'www.example.com')
assert_equal 'www.example.com', req.host
end
test "default user agent is 'Rails Testing'" do
- req = ActionDispatch::TestRequest.new
+ req = ActionDispatch::TestRequest.create({})
assert_equal 'Rails Testing', req.user_agent
end
test "allows user agent to be overridden" do
- req = ActionDispatch::TestRequest.new('HTTP_USER_AGENT' => 'GoogleBot')
+ req = ActionDispatch::TestRequest.create('HTTP_USER_AGENT' => 'GoogleBot')
assert_equal 'GoogleBot', req.user_agent
end
diff --git a/actionpack/test/dispatch/test_response_test.rb b/actionpack/test/dispatch/test_response_test.rb
index dc17668def..a4f9d56a6a 100644
--- a/actionpack/test/dispatch/test_response_test.rb
+++ b/actionpack/test/dispatch/test_response_test.rb
@@ -11,10 +11,9 @@ class TestResponseTest < ActiveSupport::TestCase
end
test "helpers" do
- assert_response_code_range 200..299, :success?
- assert_response_code_range [404], :missing?
- assert_response_code_range 300..399, :redirect?
- assert_response_code_range 500..599, :error?
+ assert_response_code_range 200..299, :successful?
+ assert_response_code_range [404], :not_found?
+ assert_response_code_range 300..399, :redirection?
assert_response_code_range 500..599, :server_error?
assert_response_code_range 400..499, :client_error?
end
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index ce1e1d0a6a..fd4ede4d1b 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -8,7 +8,7 @@ module TestUrlGeneration
class ::MyRouteGeneratingController < ActionController::Base
include Routes.url_helpers
def index
- render :text => foo_path
+ render plain: foo_path
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 069d181674..55a2e155d1 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,26 @@
+* Add wildcard matching to explicit dependencies.
+
+ Turns:
+
+ ```erb
+ <% # Template Dependency: recordings/threads/events/subscribers_changed %>
+ <% # Template Dependency: recordings/threads/events/completed %>
+ <% # Template Dependency: recordings/threads/events/uncompleted %>
+ ```
+
+ Into:
+
+ ```erb
+ <% # Template Dependency: recordings/threads/events/* %>
+ ```
+
+ *Kasper Timm Hansen*
+
+* Allow defining explicit collection caching using a `# Template Collection: ...`
+ directive inside templates.
+
+ *Dov Murik*
+
* Asset helpers raise `ArgumentError` when `nil` is passed as a source.
*Anton Kolomiychuk*
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 7a7e116dbb..6e8c7f8203 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -1,16 +1,18 @@
require 'thread_safe'
+require 'action_view/path_set'
module ActionView
class DependencyTracker # :nodoc:
@trackers = ThreadSafe::Cache.new
- def self.find_dependencies(name, template)
+ def self.find_dependencies(name, template, view_paths = nil)
tracker = @trackers[template.handler]
+ return [] unless tracker.present?
- if tracker.present?
- tracker.call(name, template)
+ if tracker.respond_to?(:supports_view_paths?) && tracker.supports_view_paths?
+ tracker.call(name, template, view_paths)
else
- []
+ tracker.call(name, template)
end
end
@@ -82,12 +84,16 @@ module ActionView
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
/xm
- def self.call(name, template)
- new(name, template).dependencies
+ def self.supports_view_paths? # :nodoc:
+ true
+ end
+
+ def self.call(name, template, view_paths = nil)
+ new(name, template, view_paths).dependencies
end
- def initialize(name, template)
- @name, @template = name, template
+ def initialize(name, template, view_paths = nil)
+ @name, @template, @view_paths = name, template, view_paths
end
def dependencies
@@ -142,8 +148,22 @@ module ActionView
end
end
+ def resolve_directories(wildcard_dependencies)
+ return [] unless @view_paths
+
+ wildcard_dependencies.each_with_object([]) do |query, templates|
+ @view_paths.find_all_with_query(query).each do |template|
+ templates << "#{File.dirname(query)}/#{File.basename(template).split('.').first}"
+ end
+ end
+ end
+
def explicit_dependencies
- source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
+ dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
+
+ wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == '*' }
+
+ (explicits + resolve_directories(wildcards)).uniq
end
end
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index df8059d04e..9a8a4feb2e 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -8,6 +8,13 @@ module ActionView
@@cache = ThreadSafe::Cache.new
@@digest_monitor = Monitor.new
+ class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc:
+ def call(env)
+ ActionView::Digestor.cache.clear
+ app.call(env)
+ end
+ end
+
class << self
# Supported options:
#
@@ -41,10 +48,7 @@ module ActionView
Digestor
end
- digest = klass.new(options).digest
- # Store the actual digest if config.cache_template_loading is true
- @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching?
- digest
+ @@cache[cache_key] = stored_digest = klass.new(options).digest
ensure
# something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
@@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
@@ -68,7 +72,7 @@ module ActionView
end
def dependencies
- DependencyTracker.find_dependencies(name, template)
+ DependencyTracker.find_dependencies(name, template, finder.view_paths)
rescue ActionView::MissingTemplate
logger.try :error, " '#{name}' file doesn't exist, so no dependencies"
[]
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index e32f8e219e..e506c782d6 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -60,7 +60,7 @@ module ActionView
tag_options = {
"src" => path_to_javascript(source, path_options)
}.merge!(options)
- content_tag(:script, "", tag_options)
+ content_tag("script".freeze, "", tag_options)
}.join("\n").html_safe
end
@@ -237,7 +237,7 @@ module ActionView
# image_alt('underscored_file_name.png')
# # => Underscored file name
def image_alt(src)
- File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize
+ File.basename(src, '.*'.freeze).sub(/-[[:xdigit:]]{32}\z/, ''.freeze).tr('-_'.freeze, ' '.freeze).capitalize
end
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index b19dc25025..3beb7fefb1 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -127,7 +127,7 @@ module ActionView
return "" unless source.present?
return source if source =~ URI_REGEXP
- tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '')
+ tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, ''.freeze)
if extname = compute_asset_extname(source, options)
source = "#{source}#{extname}"
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 8945575860..636d7d4cc3 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -98,7 +98,19 @@ module ActionView
# <%# Template Dependency: todolists/todolist %>
# <%= render_sortable_todolists @project.todolists %>
#
- # The pattern used to match these is <tt>/# Template Dependency: (\S+)/</tt>,
+ # In some cases, like a single table inheritance setup, you might have
+ # a bunch of explicit dependencies. Instead of writing every template out,
+ # you can use a wildcard to match any template in a directory:
+ #
+ # <%# Template Dependency: events/* %>
+ # <%= render_categorizable_events @person.events %>
+ #
+ # This marks every template in the directory as a dependency. To find those
+ # templates, the wildcard path must be absolutely defined from app/views or paths
+ # otherwise added with +prepend_view_path+ or +append_view_path+.
+ # This way the wildcard for `app/views/recordings/events` would be `recordings/events/*` etc.
+ #
+ # The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>,
# so it's important that you type it out just so.
# You can only declare one template dependency per line.
#
@@ -137,6 +149,21 @@ module ActionView
# The automatic cache multi read can be turned off like so:
#
# <%= render @notifications, cache: false %>
+ #
+ # === Explicit Collection Caching
+ #
+ # If the partial template doesn't start with a clean cache call as
+ # mentioned above, you can still benefit from collection caching by
+ # adding a special comment format anywhere in the template, like:
+ #
+ # <%# Template Collection: notification %>
+ # <% my_helper_that_calls_cache(some_arg, notification) do %>
+ # <%= notification.name %>
+ # <% end %>
+ #
+ # The pattern used to match these is <tt>/# Template Collection: (\S+)/</tt>,
+ # so it's important that you type it out just so.
+ # You can only declare one collection in a partial template file.
def cache(name = {}, options = {}, &block)
if controller.respond_to?(:perform_caching) && controller.perform_caching
safe_concat(fragment_for(cache_fragment_name(name, options), options, &block))
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index a67ba580f1..93c7cba395 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -115,7 +115,7 @@ module ActionView
# <li><%= link_to 'Home', action: 'index' %></li>
# <% end %>
#
- # And in other place:
+ # And in another place:
#
# <% content_for :navigation do %>
# <li><%= link_to 'Login', action: 'login' %></li>
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 9c8edc69a9..94fbaa2dca 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -70,7 +70,7 @@ module ActionView
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
#
# With the <tt>scope</tt> option, you can define a custom scope for Rails
- # to lookup the translation.
+ # to look up the translation.
#
# For example you can define the following in your locale (e.g. en.yml).
#
@@ -681,7 +681,7 @@ module ActionView
content = args.first || I18n.l(date_or_time, :format => format)
datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
- content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
+ content_tag("time".freeze, content, options.reverse_merge(:datetime => datetime), &block)
end
end
@@ -809,7 +809,7 @@ module ActionView
1.upto(12) do |month_number|
options = { :value => month_number }
options[:selected] = "selected" if month == month_number
- month_options << content_tag(:option, month_name(month_number), options) + "\n"
+ month_options << content_tag("option".freeze, month_name(month_number), options) + "\n"
end
build_select(:month, month_options.join)
end
@@ -971,7 +971,7 @@ module ActionView
tag_options[:selected] = "selected" if selected == i
text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
text = options[:ampm] ? AMPM_TRANSLATION[i] : text
- select_options << content_tag(:option, text, tag_options)
+ select_options << content_tag("option".freeze, text, tag_options)
end
(select_options.join("\n") + "\n").html_safe
@@ -991,11 +991,11 @@ module ActionView
select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes]
select_html = "\n"
- select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
+ select_html << content_tag("option".freeze, '', :value => '') + "\n" if @options[:include_blank]
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
select_html << select_options_as_html
- (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe
+ (content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe
end
# Builds a prompt option tag with supplied options or from default options.
@@ -1012,7 +1012,7 @@ module ActionView
I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale])
end
- prompt ? content_tag(:option, prompt, :value => '') : ''
+ prompt ? content_tag("option".freeze, prompt, :value => '') : ''
end
# Builds hidden input tag for date part and value.
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 8e729b3c39..728e633bbf 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -456,7 +456,7 @@ module ActionView
option_tags = options_from_collection_for_select(
group.send(group_method), option_key_method, option_value_method, selected_key)
- content_tag(:optgroup, option_tags, label: group.send(group_label_method))
+ content_tag("optgroup".freeze, option_tags, label: group.send(group_label_method))
end.join.html_safe
end
@@ -528,7 +528,7 @@ module ActionView
body = "".html_safe
if prompt
- body.safe_concat content_tag(:option, prompt_text(prompt), value: "")
+ body.safe_concat content_tag("option".freeze, prompt_text(prompt), value: "")
end
grouped_options.each do |container|
@@ -541,7 +541,7 @@ module ActionView
end
html_attributes = { label: label }.merge!(html_attributes)
- body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), html_attributes)
+ body.safe_concat content_tag("optgroup".freeze, options_for_select(container, selected_key), html_attributes)
end
body
@@ -577,7 +577,7 @@ module ActionView
end
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
- zone_options.safe_concat content_tag(:option, '-------------', value: '', disabled: true)
+ zone_options.safe_concat content_tag("option".freeze, '-------------', value: '', disabled: true)
zone_options.safe_concat "\n"
zones = zones - priority_zones
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 896020bc15..d36701955a 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -140,15 +140,15 @@ module ActionView
end
if include_blank
- option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags)
+ option_tags = content_tag("option".freeze, include_blank, value: '').safe_concat(option_tags)
end
end
if prompt = options.delete(:prompt)
- option_tags = content_tag(:option, prompt, value: '').safe_concat(option_tags)
+ option_tags = content_tag("option".freeze, prompt, value: '').safe_concat(option_tags)
end
- content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
+ content_tag "select".freeze, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
end
# Creates a standard text field; use these text fields to input smaller chunks of text like a username
@@ -568,7 +568,7 @@ module ActionView
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
def field_set_tag(legend = nil, options = nil, &block)
output = tag(:fieldset, options, true)
- output.safe_concat(content_tag(:legend, legend)) unless legend.blank?
+ output.safe_concat(content_tag("legend".freeze, legend)) unless legend.blank?
output.concat(capture(&block)) if block_given?
output.safe_concat("</fieldset>")
end
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index e237a32cb7..ed7e882c94 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -47,8 +47,8 @@ module ActionView
# tag.
#
# javascript_tag "alert('All is good')", defer: 'defer'
- #
- # Returns:
+ #
+ # Returns:
# <script defer="defer">
# //<![CDATA[
# alert('All is good')
@@ -70,7 +70,7 @@ module ActionView
content_or_options_with_block
end
- content_tag(:script, javascript_cdata_section(content), html_options)
+ content_tag("script".freeze, javascript_cdata_section(content), html_options)
end
def javascript_cdata_section(content) #:nodoc:
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index 827932d8e2..c98f2d74a8 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -18,7 +18,7 @@ module ActionView
# performs HTML escape on the string first. Setting the content type as
# <tt>text/html</tt>.
# * <tt>:body</tt> - Renders the text passed in, and inherits the content
- # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
+ # type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
# object.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index a87c223a71..2562504896 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -22,9 +22,10 @@ module ActionView
TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set
- PRE_CONTENT_STRINGS = {
- :textarea => "\n"
- }
+ PRE_CONTENT_STRINGS = Hash.new { "".freeze }
+ PRE_CONTENT_STRINGS[:textarea] = "\n"
+ PRE_CONTENT_STRINGS["textarea"] = "\n"
+
# Returns an empty HTML tag of type +name+ which by default is XHTML
# compliant. Set +open+ to true to create an open tag compatible
@@ -143,24 +144,30 @@ module ActionView
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
content = ERB::Util.unwrapped_html_escape(content) if escape
- "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
+ "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
end
def tag_options(options, escape = true)
return if options.blank?
- attrs = []
+ output = ""
+ sep = " ".freeze
options.each_pair do |key, value|
if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
value.each_pair do |k, v|
- attrs << prefix_tag_option(key, k, v, escape)
+ output << sep
+ output << prefix_tag_option(key, k, v, escape)
end
elsif BOOLEAN_ATTRIBUTES.include?(key)
- attrs << boolean_tag_option(key) if value
+ if value
+ output << sep
+ output << boolean_tag_option(key)
+ end
elsif !value.nil?
- attrs << tag_option(key, value, escape)
+ output << sep
+ output << tag_option(key, value, escape)
end
end
- " #{attrs * ' '}" unless attrs.empty?
+ output unless output.empty?
end
def prefix_tag_option(prefix, key, value, escape)
@@ -177,7 +184,7 @@ module ActionView
def tag_option(key, value, escape)
if value.is_a?(Array)
- value = escape ? safe_join(value, " ") : value.join(" ")
+ value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
else
value = escape ? ERB::Util.unwrapped_html_escape(value) : value
end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index c216d4401f..6a3d01667d 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -206,6 +206,11 @@ module ActionView
# +plural+ is supplied, it will use that when count is > 1, otherwise
# it will use the Inflector to determine the plural form.
#
+ # If passed an optional +locale:+ parameter, the word will be pluralized
+ # using rules defined for that language (you must define your own
+ # inflection rules for languages other than English). See
+ # ActiveSupport::Inflector.pluralize
+ #
# pluralize(1, 'person')
# # => 1 person
#
@@ -217,11 +222,14 @@ module ActionView
#
# pluralize(0, 'person')
# # => 0 people
- def pluralize(count, singular, plural = nil)
+ #
+ # pluralize(2, 'Person', locale: :de)
+ # # => 2 Personen
+ def pluralize(count, singular, plural = nil, locale: nil)
word = if (count == 1 || count =~ /^1(\.0+)?$/)
singular
else
- plural || singular.pluralize
+ plural || singular.pluralize(locale)
end
"#{count || 0} #{word}"
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index afb1265ad9..d676a0a931 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -184,9 +184,9 @@ module ActionView
html_options = convert_options_to_data_attributes(options, html_options)
url = url_for(options)
- html_options['href'] ||= url
+ html_options["href".freeze] ||= url
- content_tag(:a, name || url, html_options, &block)
+ content_tag("a".freeze, name || url, html_options, &block)
end
# Generates a form containing a single button that submits to the URL created
@@ -468,9 +468,10 @@ module ActionView
}.compact
extras = extras.empty? ? '' : '?' + extras.join('&')
- html_options["href"] = "mailto:#{email_address}#{extras}"
+ encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
+ html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
- content_tag(:a, name || email_address, html_options, &block)
+ content_tag("a".freeze, name || email_address, html_options, &block)
end
# True if the current request URI was generated by the given +options+.
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 817b30dd2f..f4a10aa393 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -6,10 +6,11 @@ require 'action_view/template/resolver'
module ActionView
# = Action View Lookup Context
#
- # <tt>LookupContext</tt> is the object responsible to hold all information required to lookup
- # templates, i.e. view paths and details. The LookupContext is also responsible to
- # generate a key, given to view paths, used in the resolver cache lookup. Since
- # this key is generated just once during the request, it speeds up all cache accesses.
+ # <tt>LookupContext</tt> is the object responsible for holding all information
+ # required for looking up templates, i.e. view paths and details.
+ # <tt>LookupContext</tt> is also responsible for generating a key, given to
+ # view paths, used in the resolver cache lookup. Since this key is generated
+ # only once during the request, it speeds up all cache accesses.
class LookupContext #:nodoc:
attr_accessor :prefixes, :rendered_format
@@ -172,13 +173,13 @@ module ActionView
# name instead of the prefix.
def normalize_name(name, prefixes) #:nodoc:
prefixes = prefixes.presence
- parts = name.to_s.split('/')
+ parts = name.to_s.split('/'.freeze)
parts.shift if parts.first.empty?
name = parts.pop
return name, prefixes || [""] if parts.empty?
- parts = parts.join('/')
+ parts = parts.join('/'.freeze)
prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
return name, prefixes
@@ -203,7 +204,7 @@ module ActionView
# add :html as fallback to :js.
def formats=(values)
if values
- values.concat(default_formats) if values.delete "*/*"
+ values.concat(default_formats) if values.delete "*/*".freeze
if values == [:js]
values << :html
@html_fallback_for_js = true
diff --git a/actionview/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb
index 91ee2ea8f5..7a88f6bc50 100644
--- a/actionview/lib/action_view/path_set.rb
+++ b/actionview/lib/action_view/path_set.rb
@@ -61,6 +61,15 @@ module ActionView #:nodoc:
find_all(path, prefixes, *args).any?
end
+ def find_all_with_query(query) # :nodoc:
+ paths.each do |resolver|
+ templates = resolver.find_all_with_query(query)
+ return templates unless templates.empty?
+ end
+
+ []
+ end
+
private
def typecast(paths)
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 5dc7950d6b..e829d86c99 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -42,6 +42,14 @@ module ActionView
end
end
+ initializer "action_view.per_request_digest_cache" do |app|
+ ActiveSupport.on_load(:action_view) do
+ if app.config.consider_all_requests_local
+ app.middleware.use ActionView::Digestor::PerRequestDigestCacheExpiry
+ end
+ end
+ end
+
initializer "action_view.setup_action_pack" do |app|
ActiveSupport.on_load(:action_controller) do
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 780fdabbd1..1f9e960488 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -520,7 +520,7 @@ module ActionView
def retrieve_variable(path, as)
variable = as || begin
- base = path[-1] == "/" ? "" : File.basename(path)
+ base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/
$1.to_sym
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 91b7e845d6..86a80a5421 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -103,7 +103,7 @@ module ActionView
view_renderer.render(context, options)
end
- # Assign the rendered format to lookup context.
+ # Assign the rendered format to look up context.
def _process_format(format, options = {}) #:nodoc:
super
lookup_context.formats = [format.to_sym]
diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb
index 0371db07dc..20d6b9a64c 100644
--- a/actionview/lib/action_view/routing_url_for.rb
+++ b/actionview/lib/action_view/routing_url_for.rb
@@ -92,6 +92,16 @@ module ActionView
end
super(options)
+ when ActionController::Parameters
+ unless options.key?(:only_path)
+ if options[:host].nil?
+ options[:only_path] = _generate_paths_by_default
+ else
+ options[:only_path] = false
+ end
+ end
+
+ super(options)
when :back
_back_url
when Array
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 377ceb534a..e232808dcb 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -130,7 +130,7 @@ module ActionView
@source = source
@identifier = identifier
@handler = handler
- @cache_name = extract_resource_cache_call_name
+ @cache_name = extract_resource_cache_name
@compiled = false
@original_encoding = nil
@locals = details[:locals] || []
@@ -154,7 +154,7 @@ module ActionView
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
def render(view, locals, buffer=nil, &block)
- instrument("!render_template") do
+ instrument("!render_template".freeze) do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
@@ -190,7 +190,7 @@ module ActionView
end
def inspect
- @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
+ @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", ''.freeze) : identifier
end
# This method is responsible for properly setting the encoding of the
@@ -337,27 +337,41 @@ module ActionView
def method_name #:nodoc:
@method_name ||= begin
m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
- m.tr!('-', '_')
+ m.tr!('-'.freeze, '_'.freeze)
m
end
end
def identifier_method_name #:nodoc:
- inspect.tr('^a-z_', '_')
+ inspect.tr('^a-z_'.freeze, '_'.freeze)
end
def instrument(action, &block)
payload = { virtual_path: @virtual_path, identifier: @identifier }
- ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block)
+ case action
+ when "!render_template".freeze
+ ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, payload, &block)
+ else
+ ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, payload, &block)
+ end
end
- def extract_resource_cache_call_name
- $1 if @handler.respond_to?(:resource_cache_call_pattern) &&
- @source =~ @handler.resource_cache_call_pattern
+ EXPLICIT_COLLECTION = /# Template Collection: (?<resource_name>\w+)/
+
+ def extract_resource_cache_name
+ if match = @source.match(EXPLICIT_COLLECTION) || resource_cache_call_match
+ match[:resource_name]
+ end
+ end
+
+ def resource_cache_call_match
+ if @handler.respond_to?(:resource_cache_call_pattern)
+ @source.match(@handler.resource_cache_call_pattern)
+ end
end
def inferred_cache_name
- @inferred_cache_name ||= @virtual_path.split('/').last.sub('_', '')
+ @inferred_cache_name ||= @virtual_path.split('/'.freeze).last.sub('_'.freeze, ''.freeze)
end
end
end
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index da96347e4d..1f8459c24b 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -125,7 +125,7 @@ module ActionView
# Returns Regexp to extract a cached resource's name from a cache call at the
# first line of a template.
- # The extracted cache name is expected in $1.
+ # The extracted cache name is captured as :resource_name.
#
# <% cache notification do %> # => notification
#
@@ -138,7 +138,14 @@ module ActionView
#
# <% cache notification.event do %> # => nil
def resource_cache_call_pattern
- /\A(?:<%#.*%>)*\s*<%\s*cache\(?\s*(\w+)[\s\)]/m
+ /\A
+ (?:<%\#.*%>)* # optional initial comment
+ \s* # followed by optional spaces or newlines
+ <%\s*cache[\(\s] # followed by an ERB call to cache
+ \s* # followed by optional spaces or newlines
+ (?<resource_name>\w+) # capture the cache call argument as :resource_name
+ [\s\)] # followed by a space or close paren
+ /xm
end
private
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 2b00b0303d..28967f40a6 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -52,6 +52,7 @@ module ActionView
def initialize
@data = SmallCache.new(&KEY_BLOCK)
+ @query_cache = SmallCache.new
end
# Cache the templates returned by the block
@@ -70,8 +71,17 @@ module ActionView
end
end
+ def cache_query(query) # :nodoc:
+ if Resolver.caching?
+ @query_cache[query] ||= canonical_no_templates(yield)
+ else
+ yield
+ end
+ end
+
def clear
@data.clear
+ @query_cache.clear
end
private
@@ -116,6 +126,10 @@ module ActionView
end
end
+ def find_all_with_query(query) # :nodoc:
+ @cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
+ end
+
private
delegate :caching?, to: :class
@@ -222,7 +236,7 @@ module ActionView
end
def escape_entry(entry)
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze)
end
# Returns the file mtime from the filesystem.
@@ -234,7 +248,7 @@ module ActionView
# from the path, or the handler, we should return the array of formats given
# to the resolver.
def extract_handler_and_format_and_variant(path, default_formats)
- pieces = File.basename(path).split('.')
+ pieces = File.basename(path).split('.'.freeze)
pieces.shift
extension = pieces.pop
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 06810ad14d..c4bc26ca8a 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -24,8 +24,8 @@ module ActionView
def initialize
super
self.class.controller_path = ""
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+ @request = ActionController::TestRequest.create
+ @response = ActionDispatch::TestResponse.new
@request.env.delete('PATH_INFO')
@params = {}
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index ebca598337..37722013ce 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -36,9 +36,9 @@ module ActionView
self.class._prefixes
end
- # <tt>LookupContext</tt> is the object responsible to hold all information required to lookup
- # templates, i.e. view paths and details. Check <tt>ActionView::LookupContext</tt> for more
- # information.
+ # <tt>LookupContext</tt> is the object responsible for holding all
+ # information required for looking up templates, i.e. view paths and
+ # details. Check <tt>ActionView::LookupContext</tt> for more information.
def lookup_context
@_lookup_context ||=
ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
diff --git a/actionview/test/actionpack/abstract/layouts_test.rb b/actionview/test/actionpack/abstract/layouts_test.rb
index a6786d9b6b..80bc665b0a 100644
--- a/actionview/test/actionpack/abstract/layouts_test.rb
+++ b/actionview/test/actionpack/abstract/layouts_test.rb
@@ -52,7 +52,7 @@ module AbstractControllerTests
end
def overwrite_skip
- render :text => "Hello text!"
+ render plain: "Hello text!"
end
end
@@ -371,7 +371,7 @@ module AbstractControllerTests
test "layout for anonymous controller" do
klass = Class.new(WithString) do
def index
- render :text => 'index', :layout => true
+ render plain: 'index', layout: true
end
end
diff --git a/actionview/test/actionpack/abstract/render_test.rb b/actionview/test/actionpack/abstract/render_test.rb
index d09f91c1e2..e5721d6416 100644
--- a/actionview/test/actionpack/abstract/render_test.rb
+++ b/actionview/test/actionpack/abstract/render_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/deprecation'
module AbstractController
module Testing
@@ -33,7 +34,7 @@ module AbstractController
end
def text
- render :text => "With Text"
+ render plain: "With Text"
end
def default
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index ebaf39a12d..8d048ddbcb 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -121,7 +121,7 @@ class TestController < ApplicationController
# :ported:
def render_hello_world_from_variable
@person = "david"
- render :text => "hello #{@person}"
+ render plain: "hello #{@person}"
end
# :ported:
@@ -143,13 +143,13 @@ class TestController < ApplicationController
# :ported:
def render_text_hello_world
- render :text => "hello world"
+ render plain: "hello world"
end
# :ported:
def render_text_hello_world_with_layout
@variable_for_layout = ", I am here!"
- render :text => "hello world", :layout => true
+ render plain: "hello world", :layout => true
end
def hello_world_with_layout_false
@@ -212,26 +212,26 @@ class TestController < ApplicationController
# :ported:
def render_custom_code
- render :text => "hello world", :status => 404
+ render plain: "hello world", :status => 404
end
# :ported:
def render_text_with_nil
- render :text => nil
+ render plain: nil
end
# :ported:
def render_text_with_false
- render :text => false
+ render plain: false
end
def render_text_with_resource
- render :text => Customer.new("David")
+ render plain: Customer.new("David")
end
# :ported:
def render_nothing_with_appendix
- render :text => "appended"
+ render plain: "appended"
end
# This test is testing 3 things:
@@ -262,7 +262,7 @@ class TestController < ApplicationController
# :ported:
def blank_response
- render :text => ' '
+ render plain: ' '
end
# :ported:
@@ -294,7 +294,7 @@ class TestController < ApplicationController
def hello_in_a_string
@customers = [ Customer.new("david"), Customer.new("mary") ]
- render :text => "How's there? " + render_to_string(:template => "test/list")
+ render plain: "How's there? " + render_to_string(:template => "test/list")
end
def accessing_params_in_template
@@ -362,7 +362,7 @@ class TestController < ApplicationController
def render_to_string_with_assigns
@before = "i'm before the render"
- render_to_string :text => "foo"
+ render_to_string plain: "foo"
@after = "i'm after the render"
render :template => "test/hello_world"
end
@@ -409,8 +409,8 @@ class TestController < ApplicationController
# :ported:
def double_render
- render :text => "hello"
- render :text => "world"
+ render plain: "hello"
+ render plain: "world"
end
def double_redirect
@@ -419,13 +419,13 @@ class TestController < ApplicationController
end
def render_and_redirect
- render :text => "hello"
+ render plain: "hello"
redirect_to :action => "double_render"
end
def render_to_string_and_render
- @stuff = render_to_string :text => "here is some cached stuff"
- render :text => "Hi web users! #{@stuff}"
+ @stuff = render_to_string plain: "here is some cached stuff"
+ render plain: "Hi web users! #{@stuff}"
end
def render_to_string_with_inline_and_render
@@ -454,7 +454,7 @@ class TestController < ApplicationController
# :addressed:
def render_text_with_assigns
@hello = "world"
- render :text => "foo"
+ render plain: "foo"
end
def render_with_assigns_option
@@ -467,7 +467,7 @@ class TestController < ApplicationController
def render_content_type_from_body
response.content_type = Mime::RSS
- render :text => "hello world!"
+ render body: "hello world!"
end
def render_using_layout_around_block
@@ -770,7 +770,7 @@ class RenderTest < ActionController::TestCase
# :ported:
def test_do_with_render_text_and_layout
get :render_text_hello_world_with_layout
- assert_equal "<html>hello world, I am here!</html>", @response.body
+ assert_equal "{{hello world, I am here!}}\n", @response.body
end
# :ported:
diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb
index 7fba9ff8ff..e99659c802 100644
--- a/actionview/test/actionpack/controller/view_paths_test.rb
+++ b/actionview/test/actionpack/controller/view_paths_test.rb
@@ -23,8 +23,8 @@ class ViewLoadPathsTest < ActionController::TestCase
end
def setup
- @request = ActionController::TestRequest.new
- @response = ActionController::TestResponse.new
+ @request = ActionController::TestRequest.create
+ @response = ActionDispatch::TestResponse.new
@controller = TestController.new
@paths = TestController.view_paths
end
diff --git a/actionview/test/fixtures/actionpack/layouts/standard.text.erb b/actionview/test/fixtures/actionpack/layouts/standard.text.erb
new file mode 100644
index 0000000000..a58afb1aa2
--- /dev/null
+++ b/actionview/test/fixtures/actionpack/layouts/standard.text.erb
@@ -0,0 +1 @@
+{{<%= yield %><%= @variable_for_layout %>}}
diff --git a/actionview/test/fixtures/digestor/events/_completed.html.erb b/actionview/test/fixtures/digestor/events/_completed.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionview/test/fixtures/digestor/events/_completed.html.erb
diff --git a/actionview/test/fixtures/digestor/events/index.html.erb b/actionview/test/fixtures/digestor/events/index.html.erb
new file mode 100644
index 0000000000..bc45e41bcb
--- /dev/null
+++ b/actionview/test/fixtures/digestor/events/index.html.erb
@@ -0,0 +1 @@
+<% # Template Dependency: events/* %> \ No newline at end of file
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index f0afcdb5ae..dde757b5a2 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'fileutils'
+require 'action_view/dependency_tracker'
class FixtureTemplate
attr_reader :source, :handler
@@ -15,12 +16,13 @@ end
class FixtureFinder
FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor"
- attr_reader :details
+ attr_reader :details, :view_paths
attr_accessor :formats
attr_accessor :variants
def initialize
@details = {}
+ @view_paths = ActionView::PathSet.new(['digestor'])
@formats = []
@variants = []
end
@@ -75,6 +77,34 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
+ def test_explicit_dependency_wildcard
+ assert_digest_difference("events/index") do
+ change_template("events/_completed")
+ end
+ end
+
+ def test_explicit_dependency_wildcard_picks_up_added_file
+ old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false
+
+ assert_digest_difference("events/index") do
+ add_template("events/_uncompleted")
+ end
+ ensure
+ remove_template("events/_uncompleted")
+ ActionView::Resolver.caching = old_caching
+ end
+
+ def test_explicit_dependency_wildcard_picks_up_removed_file
+ old_caching, ActionView::Resolver.caching = ActionView::Resolver.caching, false
+ add_template("events/_subscribers_changed")
+
+ assert_digest_difference("events/index") do
+ remove_template("events/_subscribers_changed")
+ end
+ ensure
+ ActionView::Resolver.caching = old_caching
+ end
+
def test_second_level_dependency
assert_digest_difference("messages/show") do
change_template("comments/_comments")
@@ -219,7 +249,7 @@ class TemplateDigestorTest < ActionView::TestCase
end
def test_variants
- assert_digest_difference("messages/new", false, variants: [:iphone]) do
+ assert_digest_difference("messages/new", variants: [:iphone]) do
change_template("messages/new", :iphone)
change_template("messages/_header", :iphone)
end
@@ -239,16 +269,6 @@ class TemplateDigestorTest < ActionView::TestCase
assert_not_equal digest_phone, digest_fridge_phone
end
- def test_cache_template_loading
- resolver_before = ActionView::Resolver.caching
- ActionView::Resolver.caching = false
- assert_digest_difference("messages/edit", true) do
- change_template("comments/_comment")
- end
- ensure
- ActionView::Resolver.caching = resolver_before
- end
-
def test_digest_cache_cleanup_with_recursion
first_digest = digest("level/_recursion")
second_digest = digest("level/_recursion")
@@ -291,9 +311,9 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
- def assert_digest_difference(template_name, persistent = false, options = {})
+ def assert_digest_difference(template_name, options = {})
previous_digest = digest(template_name, options)
- ActionView::Digestor.cache.clear unless persistent
+ ActionView::Digestor.cache.clear
yield
@@ -329,4 +349,9 @@ class TemplateDigestorTest < ActionView::TestCase
f.write "\nTHIS WAS CHANGED!"
end
end
+ alias_method :add_template, :change_template
+
+ def remove_template(template_name)
+ File.delete("digestor/#{template_name}.html.erb")
+ end
end
diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb
index d17034e88f..d3b51cd629 100644
--- a/actionview/test/template/template_test.rb
+++ b/actionview/test/template/template_test.rb
@@ -194,14 +194,15 @@ class TestERBTemplate < ActiveSupport::TestCase
[
"<%= 'Hello' %>",
"<% cache_customer = 42 %>",
- "<% cache customer.name do %><% end %>"
+ "<% cache customer.name do %><% end %>",
+ "<% my_cache customer do %><% end %>"
].each do |body|
template = new_template(body, virtual_path: "test/foo/_customer")
assert_not template.eligible_for_collection_caching?, "Template #{body.inspect} should not be eligible for collection caching"
end
end
- def test_eligible_for_collection_caching_with_cache_call
+ def test_eligible_for_collection_caching_with_cache_call_or_explicit
[
"<% cache customer do %><% end %>",
"<% cache(customer) do %><% end %>",
@@ -213,7 +214,8 @@ class TestERBTemplate < ActiveSupport::TestCase
"<%# comment %><% cache customer do %><% end %>",
"<%# comment %>\n<% cache customer do %><% end %>",
"<%# comment\n line 2\n line 3 %>\n<% cache customer do %><% end %>",
- "<%# comment 1 %>\n<%# comment 2 %>\n<% cache customer do %><% end %>"
+ "<%# comment 1 %>\n<%# comment 2 %>\n<% cache customer do %><% end %>",
+ "<%# comment 1 %>\n<%# Template Collection: customer %>\n<% my_cache customer do %><% end %>"
].each do |body|
template = new_template(body, virtual_path: "test/foo/_customer")
assert template.eligible_for_collection_caching?, "Template #{body.inspect} should be eligible for collection caching"
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index f1b84c4786..5791f33069 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -383,6 +383,18 @@ class TextHelperTest < ActionView::TestCase
assert_equal("12 berries", pluralize(12, "berry"))
end
+ def test_pluralization_with_locale
+ ActiveSupport::Inflector.inflections(:de) do |inflect|
+ inflect.plural(/(person)$/i, '\1en')
+ inflect.singular(/(person)en$/i, '\1')
+ end
+
+ assert_equal("2 People", pluralize(2, "Person", locale: :en))
+ assert_equal("2 Personen", pluralize(2, "Person", locale: :de))
+
+ ActiveSupport::Inflector.inflections(:de).clear
+ end
+
def test_cycle_class
value = Cycle.new("one", 2, "3")
assert_equal("one", value.to_s)
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 0e35c67516..416d30938a 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -500,6 +500,13 @@ class UrlHelperTest < ActiveSupport::TestCase
mail_to("david@loudthinking.com", "David Heinemeier Hansson", class: "admin")
end
+ def test_mail_to_with_special_characters
+ assert_dom_equal(
+ %{<a href="mailto:%23%21%24%25%26%27%2A%2B-%2F%3D%3F%5E_%60%7B%7D%7C%7E@example.org">#!$%&amp;&#39;*+-/=?^_`{}|~@example.org</a>},
+ mail_to("#!$%&'*+-/=?^_`{}|~@example.org")
+ )
+ end
+
def test_mail_with_options
assert_dom_equal(
%{<a href="mailto:me@example.com?cc=ccaddress%40example.com&amp;bcc=bccaddress%40example.com&amp;body=This%20is%20the%20body%20of%20the%20message.&amp;subject=This%20is%20an%20example%20email&amp;reply-to=foo%40bar.com">My email</a>},
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 9b307e8dc8..e71a3b4f85 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -7,7 +7,7 @@ module ActiveJob
extend ActiveSupport::Concern
included do
- def before_setup
+ def before_setup # :nodoc:
test_adapter = ActiveJob::QueueAdapters::TestAdapter.new
@old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass|
@@ -24,7 +24,7 @@ module ActiveJob
super
end
- def after_teardown
+ def after_teardown # :nodoc:
super
@old_queue_adapters.each do |(klass, adapter)|
klass.queue_adapter = adapter
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 5c36b1277e..d954467387 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -154,7 +154,7 @@ behavior out of the box:
* Making objects serializable
- ActiveModel::Serialization provides a standard interface for your object
+ <tt>ActiveModel::Serialization</tt> provides a standard interface for your object
to provide +to_json+ or +to_xml+ serialization.
class SerialPerson
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 286cd6c206..9033ec0dca 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -372,7 +372,7 @@ module ActiveModel
"define_method(:'#{name}') do |*args|"
end
- extra = (extra.map!(&:inspect) << "*args").join(", ")
+ extra = (extra.map!(&:inspect) << "*args").join(", ".freeze)
target = if send =~ CALL_COMPILABLE_REGEXP
"#{"self." unless include_private}#{send}(#{extra})"
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 1f1749af4e..213f2d5b6a 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -192,7 +192,7 @@ module ActiveModel
private
def _singularize(string)
- ActiveSupport::Inflector.underscore(string).tr('/', '_')
+ ActiveSupport::Inflector.underscore(string).tr('/'.freeze, '_'.freeze)
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index c1019169e1..5f1dde4aa3 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -87,8 +87,7 @@ module ActiveModel
validates_with BlockValidator, _merge_attributes(attr_names), &block
end
- # :nodoc:
- VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze
+ VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldy and
@@ -130,6 +129,9 @@ module ActiveModel
# end
# end
#
+ # Note that the return value of validation methods is not relevant.
+ # It's not possible to halt the validate callback chain.
+ #
# Options:
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
# Runs in all validation contexts by default (nil). You can pass a symbol
@@ -402,7 +404,7 @@ module ActiveModel
protected
def run_validations! #:nodoc:
- run_callbacks :validate
+ _run_validate_callbacks
errors.empty?
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index b4301c23e4..4b58ef66e3 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -109,7 +109,7 @@ module ActiveModel
# Overwrite run validations to include callbacks.
def run_validations! #:nodoc:
- run_callbacks(:validation) { super }
+ _run_validation_callbacks { super }
end
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index bda436d8d0..1da4df21e7 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -115,7 +115,7 @@ module ActiveModel
key = "#{key.to_s.camelize}Validator"
begin
- validator = key.include?('::') ? key.constantize : const_get(key)
+ validator = key.include?('::'.freeze) ? key.constantize : const_get(key)
rescue NameError
raise ArgumentError, "Unknown validator: '#{key}'"
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 3fa63917d0..f6d171bec6 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -27,14 +27,6 @@ class ErrorsTest < ActiveModel::TestCase
end
end
- def setup
- @mock_generator = MiniTest::Mock.new
- end
-
- def teardown
- @mock_generator.verify
- end
-
def test_delete
errors = ActiveModel::Errors.new(self)
errors[:foo] << 'omg'
@@ -307,8 +299,7 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_empty generates message" do
person = Person.new
- @mock_generator.expect(:call, nil, [:name, :empty, {}])
- person.errors.stub(:generate_message, @mock_generator) do
+ assert_called_with(person.errors, :generate_message, [:name, :empty, {}]) do
assert_deprecated do
person.errors.add_on_empty :name
end
@@ -317,9 +308,8 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_empty generates message for multiple attributes" do
person = Person.new
- @mock_generator.expect(:call, nil, [:name, :empty, {}])
- @mock_generator.expect(:call, nil, [:age, :empty, {}])
- person.errors.stub(:generate_message, @mock_generator) do
+ expected_calls = [ [:name, :empty, {}], [:age, :empty, {}] ]
+ assert_called_with(person.errors, :generate_message, expected_calls) do
assert_deprecated do
person.errors.add_on_empty [:name, :age]
end
@@ -328,8 +318,7 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_empty generates message with custom default message" do
person = Person.new
- @mock_generator.expect(:call, nil, [:name, :empty, { message: 'custom' }])
- person.errors.stub(:generate_message, @mock_generator) do
+ assert_called_with(person.errors, :generate_message, [:name, :empty, { message: 'custom' }]) do
assert_deprecated do
person.errors.add_on_empty :name, message: 'custom'
end
@@ -339,8 +328,7 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_empty generates message with empty string value" do
person = Person.new
person.name = ''
- @mock_generator.expect(:call, nil, [:name, :empty, {}])
- person.errors.stub(:generate_message, @mock_generator) do
+ assert_called_with(person.errors, :generate_message, [:name, :empty, {}]) do
assert_deprecated do
person.errors.add_on_empty :name
end
@@ -349,8 +337,7 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_blank generates message" do
person = Person.new
- @mock_generator.expect(:call, nil, [:name, :blank, {}])
- person.errors.stub(:generate_message, @mock_generator) do
+ assert_called_with(person.errors, :generate_message, [:name, :blank, {}]) do
assert_deprecated do
person.errors.add_on_blank :name
end
@@ -359,9 +346,8 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_blank generates message for multiple attributes" do
person = Person.new
- @mock_generator.expect(:call, nil, [:name, :blank, {}])
- @mock_generator.expect(:call, nil, [:age, :blank, {}])
- person.errors.stub(:generate_message, @mock_generator) do
+ expected_calls = [ [:name, :blank, {}], [:age, :blank, {}] ]
+ assert_called_with(person.errors, :generate_message, expected_calls) do
assert_deprecated do
person.errors.add_on_blank [:name, :age]
end
@@ -370,8 +356,7 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_blank generates message with custom default message" do
person = Person.new
- @mock_generator.expect(:call, nil, [:name, :blank, { message: 'custom' }])
- person.errors.stub(:generate_message, @mock_generator) do
+ assert_called_with(person.errors, :generate_message, [:name, :blank, { message: 'custom' }]) do
assert_deprecated do
person.errors.add_on_blank :name, message: 'custom'
end
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 0d179ea9ad..c100646837 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -11,6 +11,7 @@ ActiveSupport::Deprecation.debug = true
I18n.enforce_available_locales = false
require 'active_support/testing/autorun'
+require 'active_support/testing/method_call_assertions'
require 'minitest/mock'
@@ -22,3 +23,7 @@ end
def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
+
+class ActiveModel::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 70b93a202b..09d7226b5a 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -11,7 +11,6 @@ class I18nValidationTest < ActiveModel::TestCase
I18n.load_path.clear
I18n.backend = I18n::Backend::Simple.new
I18n.backend.store_translations('en', errors: { messages: { custom: nil } })
- @mock_generator = MiniTest::Mock.new
end
def teardown
@@ -19,7 +18,6 @@ class I18nValidationTest < ActiveModel::TestCase
I18n.load_path.replace @old_load_path
I18n.backend = @old_backend
I18n.backend.reload!
- @mock_generator.verify
end
def test_full_message_encoding
@@ -32,8 +30,7 @@ class I18nValidationTest < ActiveModel::TestCase
def test_errors_full_messages_translates_human_attribute_name_for_model_attributes
@person.errors.add(:name, 'not found')
- @mock_generator.expect(:call, "Person's name", [:name, default: 'Name'])
- Person.stub(:human_attribute_name, @mock_generator) do
+ assert_called_with(Person, :human_attribute_name, [:name, default: 'Name'], returns: "Person's name") do
assert_equal ["Person's name not found"], @person.errors.full_messages
end
end
@@ -58,206 +55,175 @@ class I18nValidationTest < ActiveModel::TestCase
[ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }]
]
- # validates_confirmation_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_confirmation_of on generated message #{name}" do
Person.validates_confirmation_of :title, validation_options
@person.title_confirmation = 'foo'
- @mock_generator.expect(:call, nil, [:title_confirmation, :confirmation, generate_message_options.merge(attribute: 'Title')])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title_confirmation, :confirmation, generate_message_options.merge(attribute: 'Title')]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_acceptance_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_acceptance_of on generated message #{name}" do
Person.validates_acceptance_of :title, validation_options.merge(allow_nil: false)
- @mock_generator.expect(:call, nil, [:title, :accepted, generate_message_options])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :accepted, generate_message_options]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_presence_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_presence_of on generated message #{name}" do
Person.validates_presence_of :title, validation_options
- @mock_generator.expect(:call, nil, [:title, :blank, generate_message_options])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :blank, generate_message_options]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_length_of :within too short w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_length_of for :within on generated message when too short #{name}" do
Person.validates_length_of :title, validation_options.merge(within: 3..5)
- @mock_generator.expect(:call, nil, [:title, :too_short, generate_message_options.merge(count: 3)])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :too_short, generate_message_options.merge(count: 3)]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_length_of :within too long w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_length_of for :too_long generated message #{name}" do
Person.validates_length_of :title, validation_options.merge(within: 3..5)
@person.title = 'this title is too long'
- @mock_generator.expect(:call, nil, [:title, :too_long, generate_message_options.merge(count: 5)])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :too_long, generate_message_options.merge(count: 5)]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_length_of :is w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_length_of for :is on generated message #{name}" do
Person.validates_length_of :title, validation_options.merge(is: 5)
- @mock_generator.expect(:call, nil, [:title, :wrong_length, generate_message_options.merge(count: 5)])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :wrong_length, generate_message_options.merge(count: 5)]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_format_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_format_of on generated message #{name}" do
Person.validates_format_of :title, validation_options.merge(with: /\A[1-9][0-9]*\z/)
@person.title = '72x'
- @mock_generator.expect(:call, nil, [:title, :invalid, generate_message_options.merge(value: '72x')])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :invalid, generate_message_options.merge(value: '72x')]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_inclusion_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_inclusion_of on generated message #{name}" do
Person.validates_inclusion_of :title, validation_options.merge(in: %w(a b c))
@person.title = 'z'
- @mock_generator.expect(:call, nil, [:title, :inclusion, generate_message_options.merge(value: 'z')])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :inclusion, generate_message_options.merge(value: 'z')]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_inclusion_of using :within w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_inclusion_of using :within on generated message #{name}" do
Person.validates_inclusion_of :title, validation_options.merge(within: %w(a b c))
@person.title = 'z'
- @mock_generator.expect(:call, nil, [:title, :inclusion, generate_message_options.merge(value: 'z')])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :inclusion, generate_message_options.merge(value: 'z')]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_exclusion_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_exclusion_of generated message #{name}" do
Person.validates_exclusion_of :title, validation_options.merge(in: %w(a b c))
@person.title = 'a'
- @mock_generator.expect(:call, nil, [:title, :exclusion, generate_message_options.merge(value: 'a')])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :exclusion, generate_message_options.merge(value: 'a')]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_exclusion_of using :within w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_exclusion_of using :within generated message #{name}" do
Person.validates_exclusion_of :title, validation_options.merge(within: %w(a b c))
@person.title = 'a'
- @mock_generator.expect(:call, nil, [:title, :exclusion, generate_message_options.merge(value: 'a')])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :exclusion, generate_message_options.merge(value: 'a')]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_numericality_of without :only_integer w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of generated message #{name}" do
Person.validates_numericality_of :title, validation_options
@person.title = 'a'
- @mock_generator.expect(:call, nil, [:title, :not_a_number, generate_message_options.merge(value: 'a')])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :not_a_number, generate_message_options.merge(value: 'a')]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_numericality_of with :only_integer w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of for :only_integer on generated message #{name}" do
Person.validates_numericality_of :title, validation_options.merge(only_integer: true)
@person.title = '0.0'
- @mock_generator.expect(:call, nil, [:title, :not_an_integer, generate_message_options.merge(value: '0.0')])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :not_an_integer, generate_message_options.merge(value: '0.0')]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_numericality_of :odd w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of for :odd on generated message #{name}" do
Person.validates_numericality_of :title, validation_options.merge(only_integer: true, odd: true)
@person.title = 0
- @mock_generator.expect(:call, nil, [:title, :odd, generate_message_options.merge(value: 0)])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :odd, generate_message_options.merge(value: 0)]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
- # validates_numericality_of :less_than w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_numericality_of for :less_than on generated message #{name}" do
Person.validates_numericality_of :title, validation_options.merge(only_integer: true, less_than: 0)
@person.title = 1
- @mock_generator.expect(:call, nil, [:title, :less_than, generate_message_options.merge(value: 1, count: 0)])
- @person.errors.stub(:generate_message, @mock_generator) do
+ call = [:title, :less_than, generate_message_options.merge(value: 1, count: 0)]
+ assert_called_with(@person.errors, :generate_message, call) do
@person.valid?
end
end
end
-
- # To make things DRY this macro is defined to define 3 tests for every validation case.
+ # To make things DRY this macro is created to define 3 tests for every validation case.
def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation)
if error_type == :confirmation
attribute = :title_confirmation
else
attribute = :title
end
- # test "validates_confirmation_of finds custom model key translation when blank"
+
test "#{validation} finds custom model key translation when #{error_type}" do
I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => 'custom message' } } } } } }
I18n.backend.store_translations 'en', errors: { messages: { error_type => 'global message'}}
@@ -267,7 +233,6 @@ class I18nValidationTest < ActiveModel::TestCase
assert_equal ['custom message'], @person.errors[attribute]
end
- # test "validates_confirmation_of finds custom model key translation with interpolation when blank"
test "#{validation} finds custom model key translation with interpolation when #{error_type}" do
I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => 'custom message with %{extra}' } } } } } }
I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} }
@@ -277,7 +242,6 @@ class I18nValidationTest < ActiveModel::TestCase
assert_equal ['custom message with extra information'], @person.errors[attribute]
end
- # test "validates_confirmation_of finds global default key translation when blank"
test "#{validation} finds global default key translation when #{error_type}" do
I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} }
@@ -287,27 +251,19 @@ class I18nValidationTest < ActiveModel::TestCase
end
end
- # validates_confirmation_of w/o mocha
-
set_expectations_for_validation "validates_confirmation_of", :confirmation do |person, options_to_merge|
Person.validates_confirmation_of :title, options_to_merge
person.title_confirmation = 'foo'
end
- # validates_acceptance_of w/o mocha
-
set_expectations_for_validation "validates_acceptance_of", :accepted do |person, options_to_merge|
Person.validates_acceptance_of :title, options_to_merge.merge(allow_nil: false)
end
- # validates_presence_of w/o mocha
-
set_expectations_for_validation "validates_presence_of", :blank do |person, options_to_merge|
Person.validates_presence_of :title, options_to_merge
end
- # validates_length_of :within w/o mocha
-
set_expectations_for_validation "validates_length_of", :too_short do |person, options_to_merge|
Person.validates_length_of :title, options_to_merge.merge(within: 3..5)
end
@@ -317,61 +273,43 @@ class I18nValidationTest < ActiveModel::TestCase
person.title = "too long"
end
- # validates_length_of :is w/o mocha
-
set_expectations_for_validation "validates_length_of", :wrong_length do |person, options_to_merge|
Person.validates_length_of :title, options_to_merge.merge(is: 5)
end
- # validates_format_of w/o mocha
-
set_expectations_for_validation "validates_format_of", :invalid do |person, options_to_merge|
Person.validates_format_of :title, options_to_merge.merge(with: /\A[1-9][0-9]*\z/)
end
- # validates_inclusion_of w/o mocha
-
set_expectations_for_validation "validates_inclusion_of", :inclusion do |person, options_to_merge|
Person.validates_inclusion_of :title, options_to_merge.merge(in: %w(a b c))
end
- # validates_exclusion_of w/o mocha
-
set_expectations_for_validation "validates_exclusion_of", :exclusion do |person, options_to_merge|
Person.validates_exclusion_of :title, options_to_merge.merge(in: %w(a b c))
person.title = 'a'
end
- # validates_numericality_of without :only_integer w/o mocha
-
set_expectations_for_validation "validates_numericality_of", :not_a_number do |person, options_to_merge|
Person.validates_numericality_of :title, options_to_merge
person.title = 'a'
end
- # validates_numericality_of with :only_integer w/o mocha
-
set_expectations_for_validation "validates_numericality_of", :not_an_integer do |person, options_to_merge|
Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true)
person.title = '1.0'
end
- # validates_numericality_of :odd w/o mocha
-
set_expectations_for_validation "validates_numericality_of", :odd do |person, options_to_merge|
Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, odd: true)
person.title = 0
end
- # validates_numericality_of :less_than w/o mocha
-
set_expectations_for_validation "validates_numericality_of", :less_than do |person, options_to_merge|
Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, less_than: 0)
person.title = 1
end
- # test with validates_with
-
def test_validations_with_message_symbol_must_translate
I18n.backend.store_translations 'en', errors: { messages: { custom_error: "I am a custom error" } }
Person.validates_presence_of :title, message: :custom_error
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 9ee8b79da9..03c7943308 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -97,7 +97,7 @@ class ValidatesWithTest < ActiveModel::TestCase
test "passes all configuration options to the validator class" do
topic = Topic.new
- validator = MiniTest::Mock.new
+ validator = Minitest::Mock.new
validator.expect(:new, validator, [{foo: :bar, if: "1 == 1", class: Topic}])
validator.expect(:validate, nil, [topic])
validator.expect(:is_a?, false, [Symbol])
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 6054b08f33..5f273d53f2 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,124 @@
+* Add `cache_key` to ActiveRecord::Relation.
+
+ Example:
+
+ @users = User.where("name like ?", "%Alberto%")
+ @users.cache_key
+ => "/users/query-5942b155a43b139f2471b872ac54251f-3-20150714212107656125000"
+
+ *Alberto Fernández-Capel*
+
+* Properly allow uniqueness validations on primary keys.
+
+ Fixes #20966.
+
+ *Sean Griffin*, *presskey*
+
+* Don't raise an error if an association failed to destroy when `destroy` was
+ called on the parent (as opposed to `destroy!`).
+
+ Fixes #20991.
+
+ *Sean Griffin*
+
+* `ActiveRecord::RecordNotFound` modified to store model name, primary_key and
+ id of the caller model. It allows the catcher of this exception to make
+ a better decision to what to do with it.
+
+ Example:
+
+ class SomeAbstractController < ActionController::Base
+ rescue_from ActiveRecord::RecordNotFound, with: :redirect_to_404
+
+ private def redirect_to_404(e)
+ return redirect_to(posts_url) if e.model == 'Post'
+ raise
+ end
+ end
+
+ *Sameer Rahmani*
+
+* Deprecate the keys for association `restrict_dependent_destroy` errors in favor
+ of new key names.
+
+ Previously `has_one` and `has_many` associations were using the
+ `one` and `many` keys respectively. Both of these keys have special
+ meaning in I18n (they are considered to be pluralizations) so by
+ renaming them to `has_one` and `has_many` we make the messages more explicit
+ and most importantly they don't clash with linguistical systems that need to
+ validate translation keys (and their pluralizations).
+
+ The `:'restrict_dependent_destroy.one'` key should be replaced with
+ `:'restrict_dependent_destroy.has_one'`, and `:'restrict_dependent_destroy.many'`
+ with `:'restrict_dependent_destroy.has_many'`.
+
+ *Roque Pinel*, *Christopher Dell*
+
+* Fix state being carried over from previous transaction.
+
+ Considering the following example where `name` is a required attribute.
+ Before we had `new_record?` returning `true` for a persisted record:
+
+ author = Author.create! name: 'foo'
+ author.name = nil
+ author.save # => false
+ author.new_record? # => true
+
+ Fixes #20824.
+
+ *Roque Pinel*
+
+* Correctly ignore `mark_for_destruction` when `autosave` isn't set to `true`
+ when validating associations.
+
+ Fixes #20882.
+
+ *Sean Griffin*
+
+* Fix a bug where counter_cache doesn't always work with polymorphic
+ relations.
+
+ Fixes #16407.
+
+ *Stefan Kanev*, *Sean Griffin*
+
+* Ensure that cyclic associations with autosave don't cause duplicate errors
+ to be added to the parent record.
+
+ Fixes #20874.
+
+ *Sean Griffin*
+
+* Ensure that `ActionController::Parameters` can still be passed to nested
+ attributes.
+
+ Fixes #20922.
+
+ *Sean Griffin*
+
+* Deprecate force association reload by passing a truthy argument to
+ association method.
+
+ For collection association, you can call `#reload` on association proxy to
+ force a reload:
+
+ @user.posts.reload # Instead of @user.posts(true)
+
+ For singular association, you can call `#reload` on the parent object to
+ clear its association cache then call the association method:
+
+ @user.reload.profile # Instead of @user.profile(true)
+
+ Passing a truthy argument to force association to reload will be removed in
+ Rails 5.1.
+
+ *Prem Sichanugrist*
+
+* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
+ from the concurrent-ruby gem.
+
+ *Jerry D'Antonio*
+
* Fix through associations using scopes having the scope merged multiple
times.
@@ -21,7 +142,7 @@
Example:
change_column_default :posts, :status, from: nil, to: "draft"
- change_column_default :users, authorized, from: true, to: false
+ change_column_default :users, :authorized, from: true, to: false
*Prem Sichanugrist*
@@ -73,7 +194,7 @@
*Aster Ryan*
-* Add `:enum_prefix`/`:enum_suffix` option to `enum` definition.
+* Add `:_prefix` and `:_suffix` options to `enum` definition.
Fixes #17511, #17415.
@@ -99,14 +220,12 @@
* Do not set `sql_mode` if `strict: :default` is specified.
- ```
- # database.yml
+ # config/database.yml
production:
adapter: mysql2
database: foo_prod
user: foo
strict: :default
- ```
*Ryuta Kamizono*
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index a619204e6f..8ea22fd901 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -83,15 +83,6 @@ end
task "isolated_test_#{adapter}" => ["#{adapter}:env", "test:isolated:#{adapter}"]
end
-rule '.sqlite3' do |t|
- sh %Q{sqlite3 "#{t.name}" "create table a (a integer); drop table a;"}
-end
-
-task :test_sqlite3 => [
- 'test/fixtures/fixture_database.sqlite3',
- 'test/fixtures/fixture_database_2.sqlite3'
-]
-
namespace :db do
namespace :mysql do
desc 'Build the MySQL test databases'
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index f5cf92db64..264f869c68 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -53,6 +53,7 @@ module ActiveRecord
autoload :Persistence
autoload :QueryCache
autoload :Querying
+ autoload :CollectionCacheKey
autoload :ReadonlyAttributes
autoload :RecordInvalid, 'active_record/validations'
autoload :Reflection
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 82cb3fed59..a830b0e0e4 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -61,12 +61,18 @@ module ActiveRecord
end
end
- class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
+ class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
end
end
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
+ end
+
+ class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:
+ end
+
class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
@@ -79,12 +85,18 @@ module ActiveRecord
end
end
- class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
+ class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
end
end
+ class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
+ end
+
+ class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
+ end
+
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
def initialize(reflection)
super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 97eb007f62..6e4a53f7fb 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -33,16 +33,24 @@ module ActiveRecord::Associations::Builder
if (@_after_create_counter_called ||= false)
@_after_create_counter_called = false
- elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
- model = reflection.klass
+ elsif attribute_changed?(foreign_key) && !new_record?
+ if reflection.polymorphic?
+ model = attribute(reflection.foreign_type).try(:constantize)
+ model_was = attribute_was(reflection.foreign_type).try(:constantize)
+ else
+ model = reflection.klass
+ model_was = reflection.klass
+ end
+
foreign_key_was = attribute_was foreign_key
foreign_key = attribute foreign_key
if foreign_key && model.respond_to?(:increment_counter)
model.increment_counter(cache_column, foreign_key)
end
- if foreign_key_was && model.respond_to?(:decrement_counter)
- model.decrement_counter(cache_column, foreign_key_was)
+
+ if foreign_key_was && model_was.respond_to?(:decrement_counter)
+ model_was.decrement_counter(cache_column, foreign_key_was)
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 6caadb4ce8..87576abd92 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -1,3 +1,5 @@
+require "active_support/deprecation"
+
module ActiveRecord
module Associations
# = Active Record Association Collection
@@ -28,6 +30,12 @@ module ActiveRecord
# Implements the reader method, e.g. foo.items for Foo.has_many :items
def reader(force_reload = false)
if force_reload
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing an argument to force an association to reload is now
+ deprecated and will be removed in Rails 5.1. Please call `reload`
+ on the result collection proxy instead.
+ MSG
+
klass.uncached { reload }
elsif stale_target?
reload
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index ddeafb40ea..b5a8c81fe4 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -781,7 +781,7 @@ module ActiveRecord
# person.pets.any? # => false
#
# person.pets << Pet.new(name: 'Snoop')
- # person.pets.count # => 0
+ # person.pets.count # => 1
# person.pets.any? # => true
#
# You can also pass a +block+ to define criteria. The behavior
@@ -971,7 +971,7 @@ module ActiveRecord
alias_method :append, :<<
def prepend(*args)
- raise NoMethodError, "prepend on association is not defined. Please use << or append"
+ raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
end
# Equivalent to +delete_all+. The difference is that returns +self+, instead
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index ca27c9fdde..9f6c832c1b 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -16,7 +16,14 @@ module ActiveRecord
when :restrict_with_error
unless empty?
record = klass.human_attribute_name(reflection.name).downcase
- owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
+ message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.many', record: record, raise: true) rescue nil
+ if message
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ The error key `:'restrict_dependent_destroy.many'` has been deprecated and will be removed in Rails 5.1.
+ Please use `:'restrict_dependent_destroy.has_many'` instead.
+ MESSAGE
+ end
+ owner.errors.add(:base, message || :'restrict_dependent_destroy.has_many', record: record)
throw(:abort)
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index cd79266952..1aa6a2ca74 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -133,7 +133,7 @@ module ActiveRecord
if scope.klass.primary_key
count = scope.destroy_all.length
else
- scope.each { |record| record.run_callbacks :destroy }
+ scope.each(&:_run_destroy_callbacks)
arel = scope.arel
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 41a75b820e..5a92bc5e8a 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -12,7 +12,14 @@ module ActiveRecord
when :restrict_with_error
if load_target
record = klass.human_attribute_name(reflection.name).downcase
- owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
+ message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.one', record: record, raise: true) rescue nil
+ if message
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ The error key `:'restrict_dependent_destroy.one'` has been deprecated and will be removed in Rails 5.1.
+ Please use `:'restrict_dependent_destroy.has_one'` instead.
+ MESSAGE
+ end
+ owner.errors.add(:base, message || :'restrict_dependent_destroy.has_one', record: record)
throw(:abort)
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index bec9505bd2..30c5d72482 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -1,9 +1,17 @@
+require "active_support/deprecation"
+
module ActiveRecord
module Associations
class SingularAssociation < Association #:nodoc:
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
def reader(force_reload = false)
if force_reload && klass
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing an argument to force an association to reload is now
+ deprecated and will be removed in Rails 5.1. Please call `reload`
+ on the parent object instead.
+ MSG
+
klass.uncached { reload }
elsif !loaded? || stale_target?
reload
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 55ee9f04e0..d0ec3e8015 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -76,13 +76,21 @@ module ActiveRecord
def ensure_mutable
unless source_reflection.belongs_to?
- raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
+ if reflection.has_one?
+ raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
+ else
+ raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
+ end
end
end
def ensure_not_nested
if reflection.nested?
- raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
+ if reflection.has_one?
+ raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection)
+ else
+ raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
+ end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 9d58a19304..7fb899c242 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -42,7 +42,7 @@ module ActiveRecord
def [](name)
@method_cache.compute_if_absent(name) do
- safe_name = name.unpack('h*').first
+ safe_name = name.unpack('h*'.freeze).first
temp_method = "__temp__#{safe_name}"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
@module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
@@ -230,7 +230,15 @@ module ActiveRecord
# person.respond_to(:nothing) # => false
def respond_to?(name, include_private = false)
return false unless super
- name = name.to_s
+
+ case name
+ when :to_partial_path
+ name = "to_partial_path".freeze
+ when :to_model
+ name = "to_model".freeze
+ else
+ name = name.to_s
+ end
# If the result is true then check for the select case.
# For queries selecting a subset of columns, return false for unselected columns.
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 0d989c2eca..2363cf7608 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -37,7 +37,7 @@ module ActiveRecord
protected
def define_method_attribute(name)
- safe_name = name.unpack('h*').first
+ safe_name = name.unpack('h*'.freeze).first
temp_method = "__temp__#{safe_name}"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index ab017c7b54..07d5e7d38e 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -24,7 +24,7 @@ module ActiveRecord
protected
def define_method_attribute=(name)
- safe_name = name.unpack('h*').first
+ safe_name = name.unpack('h*'.freeze).first
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 0792d19c3e..dbb0e2fab2 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -222,6 +222,7 @@ module ActiveRecord
true
end
validate validation_method
+ after_validation :_ensure_no_duplicate_errors
end
end
end
@@ -324,7 +325,7 @@ module ActiveRecord
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
# enabled records if they're marked_for_destruction? or destroyed.
def association_valid?(reflection, record)
- return true if record.destroyed? || record.marked_for_destruction?
+ return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
unless valid = record.valid?(validation_context)
@@ -456,5 +457,11 @@ module ActiveRecord
end
end
end
+
+ def _ensure_no_duplicate_errors
+ errors.messages.each_key do |attribute|
+ errors[attribute].uniq!
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c918e88590..55a7e053bc 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -280,6 +280,7 @@ module ActiveRecord #:nodoc:
extend Explain
extend Enum
extend Delegation::DelegateCache
+ extend CollectionCacheKey
include Core
include Persistence
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 3027ce928e..c7c769b283 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -289,24 +289,28 @@ module ActiveRecord
end
def destroy #:nodoc:
- run_callbacks(:destroy) { super }
+ _run_destroy_callbacks { super }
+ rescue RecordNotDestroyed => e
+ @_association_destroy_exception = e
+ false
end
def touch(*) #:nodoc:
- run_callbacks(:touch) { super }
+ _run_touch_callbacks { super }
end
private
+
def create_or_update(*) #:nodoc:
- run_callbacks(:save) { super }
+ _run_save_callbacks { super }
end
def _create_record #:nodoc:
- run_callbacks(:create) { super }
+ _run_create_callbacks { super }
end
def _update_record(*) #:nodoc:
- run_callbacks(:update) { super }
+ _run_update_callbacks { super }
end
end
end
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
new file mode 100644
index 0000000000..3c4ca3d116
--- /dev/null
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -0,0 +1,31 @@
+module ActiveRecord
+ module CollectionCacheKey
+
+ def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
+ query_signature = Digest::MD5.hexdigest(collection.to_sql)
+ key = "#{collection.model_name.cache_key}/query-#{query_signature}"
+
+ if collection.loaded?
+ size = collection.size
+ timestamp = collection.max_by(&timestamp_column).public_send(timestamp_column)
+ else
+ column_type = type_for_attribute(timestamp_column.to_s)
+ column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
+
+ query = collection
+ .select("COUNT(*) AS size", "MAX(#{column}) AS timestamp")
+ .unscope(:order)
+ result = connection.select_one(query)
+
+ size = result["size"]
+ timestamp = column_type.deserialize(result["timestamp"])
+ end
+
+ if timestamp
+ "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
+ else
+ "#{key}-#{size}"
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 6535121075..282af220fb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -508,7 +508,7 @@ module ActiveRecord
synchronize do
remove_connection_from_thread_cache conn
- conn.run_callbacks :checkin do
+ conn._run_checkin_callbacks do
conn.expire
end
@@ -764,7 +764,7 @@ module ActiveRecord
end
def checkout_and_verify(c)
- c.run_callbacks :checkout do
+ c._run_checkout_callbacks do
c.verify!
end
c
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 e3115abe66..a30945d0ee 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -761,9 +761,9 @@ module ActiveRecord
# [<tt>:name</tt>]
# The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
# [<tt>:on_delete</tt>]
- # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
# [<tt>:on_update</tt>]
- # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
def add_foreign_key(from_table, to_table, options = {})
return unless supports_foreign_keys?
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 6d3a21a3dc..56227ddd80 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -107,6 +107,18 @@ module ActiveRecord
@prepared_statements = false
end
+ class Version
+ include Comparable
+
+ def initialize(version_string)
+ @version = version_string.split('.').map(&:to_i)
+ end
+
+ def <=>(version_string)
+ @version <=> version_string.split('.').map(&:to_i)
+ end
+ end
+
class BindCollector < Arel::Collectors::Bind
def compile(bvs, conn)
casted_binds = conn.prepare_binds_for_database(bvs)
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 2027492f29..af156c9c78 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -307,7 +307,7 @@ module ActiveRecord
#
# http://bugs.mysql.com/bug.php?id=39170
def supports_transaction_isolation?
- version[0] >= 5
+ version >= '5.0.0'
end
def supports_indexes_in_create?
@@ -319,11 +319,11 @@ module ActiveRecord
end
def supports_views?
- version[0] >= 5
+ version >= '5.0.0'
end
def supports_datetime_with_precision?
- (version[0] == 5 && version[1] >= 6) || version[0] >= 6
+ version >= '5.6.4'
end
def native_database_types
@@ -386,6 +386,14 @@ module ActiveRecord
0
end
+ def quoted_date(value)
+ if supports_datetime_with_precision?
+ super
+ else
+ super.sub(/\.\d{6}\z/, '')
+ end
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
@@ -938,7 +946,7 @@ module ActiveRecord
end
def version
- @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i)
+ @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
def mariadb?
@@ -946,7 +954,7 @@ module ActiveRecord
end
def supports_rename_index?
- mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
+ mariadb? ? false : version >= '5.7.6'
end
def configure_connection
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
index 58715978f7..b2c49989a4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb
@@ -12,7 +12,7 @@ module ActiveRecord
end
def sql_type
- super.gsub(/\[\]$/, "")
+ super.gsub(/\[\]$/, "".freeze)
end
def ==(other)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7c809b088c..358039723f 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -65,18 +65,6 @@ module ActiveRecord
boolean: { name: "boolean" }
}
- class Version
- include Comparable
-
- def initialize(version_string)
- @version = version_string.split('.').map(&:to_i)
- end
-
- def <=>(version_string)
- @version <=> version_string.split('.').map(&:to_i)
- end
- end
-
class StatementPool < ConnectionAdapters::StatementPool
private
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 8a014e682e..ffce2173ec 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -162,11 +162,13 @@ module ActiveRecord
}
record = statement.execute([id], self, connection).first
unless record
- raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
+ raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
+ name, primary_key, id)
end
record
rescue RangeError
- raise RecordNotFound, "Couldn't find #{name} with an out of range value for '#{primary_key}'"
+ raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'",
+ name, primary_key)
end
def find_by(*args) # :nodoc:
@@ -199,7 +201,7 @@ module ActiveRecord
end
def find_by!(*args) # :nodoc:
- find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}")
+ find_by(*args) or raise RecordNotFound.new("Couldn't find #{name}", name)
end
def initialize_generated_modules # :nodoc:
@@ -303,7 +305,7 @@ module ActiveRecord
assign_attributes(attributes) if attributes
yield self if block_given?
- run_callbacks :initialize
+ _run_initialize_callbacks
end
# Initialize an empty model object from +coder+. +coder+ should be
@@ -330,8 +332,8 @@ module ActiveRecord
self.class.define_attribute_methods
- run_callbacks :find
- run_callbacks :initialize
+ _run_find_callbacks
+ _run_initialize_callbacks
self
end
@@ -367,7 +369,7 @@ module ActiveRecord
@attributes = @attributes.dup
@attributes.reset(self.class.primary_key)
- run_callbacks(:initialize)
+ _run_initialize_callbacks
@new_record = true
@destroyed = false
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index c0d9d9c1c8..91a13cb0cd 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -75,22 +75,24 @@ module ActiveRecord
#
# Conversation.where("status <> ?", Conversation.statuses[:archived])
#
- # You can use the +:enum_prefix+ or +:enum_suffix+ options when you need
- # to define multiple enums with same values. If the passed value is +true+,
- # the methods are prefixed/suffixed with the name of the enum.
+ # You can use the +:_prefix+ or +:_suffix+ options when you need to define
+ # multiple enums with same values. If the passed value is +true+, the methods
+ # are prefixed/suffixed with the name of the enum. It is also possible to
+ # supply a custom value:
#
- # class Invoice < ActiveRecord::Base
- # enum verification: [:done, :fail], enum_prefix: true
+ # class Conversation < ActiveRecord::Base
+ # enum status: [:active, :archived], _suffix: true
+ # enum comments_status: [:active, :inactive], _prefix: :comments
# end
#
- # It is also possible to supply a custom prefix.
+ # With the above example, the bang and predicate methods along with the
+ # associated scopes are now prefixed and/or suffixed accordingly:
#
- # class Invoice < ActiveRecord::Base
- # enum verification: [:done, :fail], enum_prefix: :verification_status
- # end
+ # conversation.active_status!
+ # conversation.archived_status? # => false
#
- # Note that <tt>:enum_prefix</tt>/<tt>:enum_suffix</tt> are reserved keywords
- # and can not be used as an enum name.
+ # conversation.comments_inactive!
+ # conversation.comments_active? # => false
module Enum
def self.extended(base) # :nodoc:
@@ -137,8 +139,8 @@ module ActiveRecord
def enum(definitions)
klass = self
- enum_prefix = definitions.delete(:enum_prefix)
- enum_suffix = definitions.delete(:enum_suffix)
+ enum_prefix = definitions.delete(:_prefix)
+ enum_suffix = definitions.delete(:_suffix)
definitions.each do |name, values|
# statuses = { }
enum_values = ActiveSupport::HashWithIndifferentAccess.new
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 0f1759abaa..d589620f8a 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -47,6 +47,15 @@ module ActiveRecord
# Raised when Active Record cannot find record by given id or set of ids.
class RecordNotFound < ActiveRecordError
+ attr_reader :model, :primary_key, :id
+
+ def initialize(message = nil, model = nil, primary_key = nil, id = nil)
+ @primary_key = primary_key
+ @model = model
+ @id = id
+
+ super(message)
+ end
end
# Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index d062dd9e34..f1dc56df63 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -827,12 +827,12 @@ module ActiveRecord
module TestFixtures
extend ActiveSupport::Concern
- def before_setup
+ def before_setup # :nodoc:
setup_fixtures
super
end
- def after_teardown
+ def after_teardown # :nodoc:
super
teardown_fixtures
end
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 8a3c27e6da..0b35027b2b 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -16,8 +16,8 @@ en:
messages:
record_invalid: "Validation failed: %{errors}"
restrict_dependent_destroy:
- one: "Cannot delete record because a dependent %{record} exists"
- many: "Cannot delete record because dependent %{record} exist"
+ has_one: "Cannot delete record because a dependent %{record} exists"
+ has_many: "Cannot delete record because dependent %{record} exist"
# Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index c942d0e265..c5a1488588 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -386,6 +386,9 @@ module ActiveRecord
# then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
options = self.nested_attributes_options[association_name]
+ if attributes.respond_to?(:permitted?)
+ attributes = attributes.to_h
+ end
attributes = attributes.with_indifferent_access
existing_record = send(association_name)
@@ -442,6 +445,9 @@ module ActiveRecord
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
options = self.nested_attributes_options[association_name]
+ if attributes_collection.respond_to?(:permitted?)
+ attributes_collection = attributes_collection.to_h
+ end
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
@@ -468,6 +474,9 @@ module ActiveRecord
end
attributes_collection.each do |attributes|
+ if attributes.respond_to?(:permitted?)
+ attributes = attributes.to_h
+ end
attributes = attributes.with_indifferent_access
if attributes['id'].blank?
@@ -552,7 +561,9 @@ module ActiveRecord
end
def raise_nested_attributes_record_not_found!(association_name, record_id)
- raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
+ model = self.class._reflect_on_association(association_name).klass.name
+ raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
+ model, 'id', record_id)
end
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 0a6e4ac0bd..09c36d7b4d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -193,7 +193,7 @@ module ActiveRecord
# and #destroy! raises ActiveRecord::RecordNotDestroyed.
# See ActiveRecord::Callbacks for further details.
def destroy!
- destroy || raise(RecordNotDestroyed.new("Failed to destroy the record", self))
+ destroy || _raise_record_not_destroyed
end
# Returns an instance of the specified +klass+ with the attributes of the
@@ -548,5 +548,12 @@ module ActiveRecord
def verify_readonly_attribute(name)
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
end
+
+ def _raise_record_not_destroyed
+ @_association_destroy_exception ||= nil
+ raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy the record", self)
+ ensure
+ @_association_destroy_exception = nil
+ end
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index e4df122af6..3ed04dee3b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -298,6 +298,32 @@ module ActiveRecord
limit_value ? to_a.many? : size > 1
end
+ # Returns a cache key that can be used to identify the records fetched by
+ # this query. The cache key is built with a fingerprint of the sql query,
+ # the number of records matched by the query and a timestamp of the last
+ # updated record. When a new record comes to match the query, or any of
+ # the existing records is updated or deleted, the cache key changes.
+ #
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
+ # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
+ #
+ # If the collection is loaded, the method will iterate through the records
+ # to generate the timestamp, otherwise it will trigger one SQL query like:
+ #
+ # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
+ #
+ # You can also pass a custom timestamp column to fetch the timestamp of the
+ # last updated record.
+ #
+ # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
+ #
+ # You can customize the strategy to generate the key on a per model basis
+ # overriding ActiveRecord::Base#collection_cache_key.
+ def cache_key(timestamp_column = :updated_at)
+ @cache_keys ||= {}
+ @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column)
+ end
+
# Scope all queries to the current scope.
#
# Comment.where(post_id: 1).scoping do
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 86f2c30168..d75ec72b1a 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -18,7 +18,7 @@ module ActiveRecord
delegate = Class.new(klass) {
include ClassSpecificRelation
}
- const_set klass.name.gsub('::', '_'), delegate
+ const_set klass.name.gsub('::'.freeze, '_'.freeze), delegate
cache[klass] = delegate
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 9fef55adea..009b2bad57 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -85,7 +85,8 @@ module ActiveRecord
def find_by!(arg, *args)
where(arg, *args).take!
rescue RangeError
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range value"
+ raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
+ @klass.name)
end
# Gives a record (or N records if a parameter is supplied) without any implied
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index dd8f0aa298..0b38666ce9 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -87,8 +87,8 @@ module ActiveRecord
return if other.preload_values.empty? && other.includes_values.empty?
if other.klass == relation.klass
- relation.preload! other.preload_values unless other.preload_values.empty?
- relation.includes! other.includes_values unless other.includes_values.empty?
+ relation.preload!(*other.preload_values) unless other.preload_values.empty?
+ relation.includes!(other.includes_values) unless other.includes_values.empty?
else
reflection = relation.klass.reflect_on_all_associations.find do |r|
r.class_name == other.klass.name
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 43e9afe853..d26db7d4cf 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -52,7 +52,7 @@ module ActiveRecord
key
else
key = key.to_s
- key.split('.').first if key.include?('.')
+ key.split('.'.freeze).first if key.include?('.'.freeze)
end
end.compact
end
@@ -123,10 +123,10 @@ module ActiveRecord
end
def convert_dot_notation_to_hash(attributes)
- dot_notation = attributes.keys.select { |s| s.include?(".") }
+ dot_notation = attributes.keys.select { |s| s.include?(".".freeze) }
dot_notation.each do |key|
- table_name, column_name = key.split(".")
+ table_name, column_name = key.split(".".freeze)
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 6f2def0df1..887d7a5903 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -204,9 +204,8 @@ module ActiveRecord
#
# Note that "TRUNCATE" is also a MySQL DDL statement!
module ClassMethods
- # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
+ # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
def transaction(options = {}, &block)
- # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
connection.transaction(options, &block)
end
@@ -319,8 +318,8 @@ module ActiveRecord
end
def before_committed! # :nodoc:
- run_callbacks :before_commit_without_transaction_enrollment
- run_callbacks :before_commit
+ _run_before_commit_without_transaction_enrollment_callbacks
+ _run_before_commit_callbacks
end
# Call the +after_commit+ callbacks.
@@ -329,8 +328,8 @@ module ActiveRecord
# but call it after the commit of a destroyed object.
def committed!(should_run_callbacks: true) #:nodoc:
if should_run_callbacks && destroyed? || persisted?
- run_callbacks :commit_without_transaction_enrollment
- run_callbacks :commit
+ _run_commit_without_transaction_enrollment_callbacks
+ _run_commit_callbacks
end
ensure
force_clear_transaction_record_state
@@ -340,8 +339,8 @@ module ActiveRecord
# state should be rolled back to the beginning or just to the last savepoint.
def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc:
if should_run_callbacks
- run_callbacks :rollback
- run_callbacks :rollback_without_transaction_enrollment
+ _run_rollback_callbacks
+ _run_rollback_without_transaction_enrollment_callbacks
end
ensure
restore_transaction_record_state(force_restore_state)
@@ -380,6 +379,10 @@ module ActiveRecord
raise ActiveRecord::Rollback unless status
end
status
+ ensure
+ if @transaction_state && @transaction_state.committed?
+ clear_transaction_record_state
+ end
end
protected
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 5106f4e127..32d17a1392 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -17,7 +17,9 @@ module ActiveRecord
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
- relation = relation.where.not(finder_class.primary_key => record.id) if record.persisted?
+ if record.persisted? && finder_class.primary_key.to_s != attribute.to_s
+ relation = relation.where.not(finder_class.primary_key => record.id)
+ end
relation = scope_relation(record, table, relation)
relation = relation.merge(options[:conditions]) if options[:conditions]
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
index a296cf9d31..426b088e2f 100644
--- a/activerecord/test/cases/adapters/mysql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -12,4 +12,18 @@ class MysqlQuotingTest < ActiveRecord::MysqlTestCase
def test_type_cast_false
assert_equal 0, @conn.type_cast(false)
end
+
+ def test_quoted_date_precision_for_gte_564
+ @conn.stubs(:full_version).returns('5.6.4')
+ @conn.remove_instance_variable(:@version) if @conn.instance_variable_defined?(:@version)
+ t = Time.now.change(usec: 1)
+ assert_match(/\.000001\z/, @conn.quoted_date(t))
+ end
+
+ def test_quoted_date_precision_for_lt_564
+ @conn.stubs(:full_version).returns('5.6.3')
+ @conn.remove_instance_variable(:@version) if @conn.instance_variable_defined?(:@version)
+ t = Time.now.change(usec: 1)
+ refute_match(/\.000001\z/, @conn.quoted_date(t))
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
new file mode 100644
index 0000000000..92221e61bf
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
@@ -0,0 +1,21 @@
+require "cases/helper"
+
+class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ test 'quoted date precision for gte 5.6.4' do
+ @connection.stubs(:full_version).returns('5.6.4')
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ t = Time.now.change(usec: 1)
+ assert_match(/\.000001\z/, @connection.quoted_date(t))
+ end
+
+ test 'quoted date precision for lt 5.6.4' do
+ @connection.stubs(:full_version).returns('5.6.3')
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ t = Time.now.change(usec: 1)
+ refute_match(/\.000001\z/, @connection.quoted_date(t))
+ end
+end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index ebe08c618f..73cde05504 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -293,7 +293,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
client = Client.find(3)
client.firm = nil
client.save
- assert_nil client.firm(true)
+ client.association(:firm).reload
+ assert_nil client.firm
assert_nil client.client_of
end
@@ -301,7 +302,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
client = Client.create(:name => "Primary key client", :firm_name => companies(:first_firm).name)
client.firm_with_primary_key = nil
client.save
- assert_nil client.firm_with_primary_key(true)
+ client.association(:firm_with_primary_key).reload
+ assert_nil client.firm_with_primary_key
assert_nil client.client_of
end
@@ -318,11 +320,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_polymorphic_association_class
sponsor = Sponsor.new
assert_nil sponsor.association(:sponsorable).send(:klass)
- assert_nil sponsor.sponsorable(force_reload: true)
+ sponsor.association(:sponsorable).reload
+ assert_nil sponsor.sponsorable
sponsor.sponsorable_type = '' # the column doesn't have to be declared NOT NULL
assert_nil sponsor.association(:sponsorable).send(:klass)
- assert_nil sponsor.sponsorable(force_reload: true)
+ sponsor.association(:sponsorable).reload
+ assert_nil sponsor.sponsorable
sponsor.sponsorable = Member.new :name => "Bert"
assert_equal Member, sponsor.association(:sponsorable).send(:klass)
@@ -557,7 +561,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert final_cut.persisted?
assert firm.persisted?
assert_equal firm, final_cut.firm
- assert_equal firm, final_cut.firm(true)
+ final_cut.association(:firm).reload
+ assert_equal firm, final_cut.firm
end
def test_assignment_before_child_saved_with_primary_key
@@ -569,7 +574,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert final_cut.persisted?
assert firm.persisted?
assert_equal firm, final_cut.firm_with_primary_key
- assert_equal firm, final_cut.firm_with_primary_key(true)
+ final_cut.association(:firm_with_primary_key).reload
+ assert_equal firm, final_cut.firm_with_primary_key
end
def test_new_record_with_foreign_key_but_no_object
@@ -1064,6 +1070,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Column.create! record: record
assert_equal 1, Column.count
end
+
+ def test_association_force_reload_with_only_true_is_deprecated
+ client = Client.find(3)
+
+ assert_deprecated { client.firm(true) }
+ end
end
class BelongsToWithForeignKeyTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index e584c94ad8..abbe37ab38 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -157,8 +157,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
jamis.projects << action_controller
assert_equal 2, jamis.projects.size
- assert_equal 2, jamis.projects(true).size
- assert_equal 2, action_controller.developers(true).size
+ assert_equal 2, jamis.projects.reload.size
+ assert_equal 2, action_controller.developers.reload.size
end
def test_adding_type_mismatch
@@ -176,9 +176,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
action_controller.developers << jamis
- assert_equal 2, jamis.projects(true).size
+ assert_equal 2, jamis.projects.reload.size
assert_equal 2, action_controller.developers.size
- assert_equal 2, action_controller.developers(true).size
+ assert_equal 2, action_controller.developers.reload.size
end
def test_adding_from_the_project_fixed_timestamp
@@ -192,9 +192,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
action_controller.developers << jamis
assert_equal updated_at, jamis.updated_at
- assert_equal 2, jamis.projects(true).size
+ assert_equal 2, jamis.projects.reload.size
assert_equal 2, action_controller.developers.size
- assert_equal 2, action_controller.developers(true).size
+ assert_equal 2, action_controller.developers.reload.size
end
def test_adding_multiple
@@ -203,7 +203,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
aredridel.projects.reload
aredridel.projects.push(Project.find(1), Project.find(2))
assert_equal 2, aredridel.projects.size
- assert_equal 2, aredridel.projects(true).size
+ assert_equal 2, aredridel.projects.reload.size
end
def test_adding_a_collection
@@ -212,7 +212,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
aredridel.projects.reload
aredridel.projects.concat([Project.find(1), Project.find(2)])
assert_equal 2, aredridel.projects.size
- assert_equal 2, aredridel.projects(true).size
+ assert_equal 2, aredridel.projects.reload.size
end
def test_habtm_adding_before_save
@@ -227,7 +227,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal no_of_devels+1, Developer.count
assert_equal no_of_projects+1, Project.count
assert_equal 2, aredridel.projects.size
- assert_equal 2, aredridel.projects(true).size
+ assert_equal 2, aredridel.projects.reload.size
end
def test_habtm_saving_multiple_relationships
@@ -391,8 +391,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
david.projects.delete(active_record)
assert_equal 1, david.projects.size
- assert_equal 1, david.projects(true).size
- assert_equal 2, active_record.developers(true).size
+ assert_equal 1, david.projects.reload.size
+ assert_equal 2, active_record.developers.reload.size
end
def test_deleting_array
@@ -400,7 +400,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
david.projects.reload
david.projects.delete(Project.all.to_a)
assert_equal 0, david.projects.size
- assert_equal 0, david.projects(true).size
+ assert_equal 0, david.projects.reload.size
end
def test_deleting_all
@@ -408,7 +408,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
david.projects.reload
david.projects.clear
assert_equal 0, david.projects.size
- assert_equal 0, david.projects(true).size
+ assert_equal 0, david.projects.reload.size
end
def test_removing_associations_on_destroy
@@ -434,7 +434,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert join_records.empty?
assert_equal 1, david.reload.projects.size
- assert_equal 1, david.projects(true).size
+ assert_equal 1, david.projects.reload.size
end
def test_destroying_many
@@ -450,7 +450,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert join_records.empty?
assert_equal 0, david.reload.projects.size
- assert_equal 0, david.projects(true).size
+ assert_equal 0, david.projects.reload.size
end
def test_destroy_all
@@ -466,7 +466,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert join_records.empty?
assert david.projects.empty?
- assert david.projects(true).empty?
+ assert david.projects.reload.empty?
end
def test_destroy_associations_destroys_multiple_associations
@@ -482,11 +482,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}")
assert join_records.empty?
- assert george.pirates(true).empty?
+ assert george.pirates.reload.empty?
join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}")
assert join_records.empty?
- assert george.treasures(true).empty?
+ assert george.treasures.reload.empty?
end
def test_associations_with_conditions
@@ -654,7 +654,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_habtm_respects_select
- categories(:technology).select_testing_posts(true).each do |o|
+ categories(:technology).select_testing_posts.reload.each do |o|
assert_respond_to o, :correctness_marker
end
assert_respond_to categories(:technology).select_testing_posts.first, :correctness_marker
@@ -726,7 +726,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
developer = developers(:david)
- developer.projects(true)
+ developer.projects.reload
assert_queries(0) do
developer.project_ids
developer.project_ids
@@ -917,4 +917,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
DeveloperWithSymbolClassName.new
end
end
+
+ def test_association_force_reload_with_only_true_is_deprecated
+ developer = Developer.find(1)
+
+ assert_deprecated { developer.projects(true) }
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 2c4e2a875c..21e2ee3b11 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -704,7 +704,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
natural = Client.new("name" => "Natural Company")
companies(:first_firm).clients_of_firm << natural
assert_equal 3, companies(:first_firm).clients_of_firm.size # checking via the collection
- assert_equal 3, companies(:first_firm).clients_of_firm(true).size # checking using the db
+ assert_equal 3, companies(:first_firm).clients_of_firm.reload.size # checking using the db
assert_equal natural, companies(:first_firm).clients_of_firm.last
end
@@ -759,7 +759,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
assert_equal 4, companies(:first_firm).clients_of_firm.size
- assert_equal 4, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 4, companies(:first_firm).clients_of_firm.reload.size
end
def test_transactions_when_adding_to_persisted
@@ -771,7 +771,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
rescue Client::RaisedOnSave
end
- assert !companies(:first_firm).clients_of_firm(true).include?(good)
+ assert !companies(:first_firm).clients_of_firm.reload.include?(good)
end
def test_transactions_when_adding_to_new_record
@@ -903,12 +903,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
assert new_client.persisted?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
- assert_equal new_client, companies(:first_firm).clients_of_firm(true).last
+ assert_equal new_client, companies(:first_firm).clients_of_firm.reload.last
end
def test_create_many
companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}])
- assert_equal 4, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 4, companies(:first_firm).clients_of_firm.reload.size
end
def test_create_followed_by_save_does_not_load_target
@@ -921,7 +921,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
assert_equal 1, companies(:first_firm).clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_deleting_before_save
@@ -1058,7 +1058,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, companies(:first_firm).clients_of_firm.size
companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1], companies(:first_firm).clients_of_firm[2]])
assert_equal 0, companies(:first_firm).clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 0, companies(:first_firm).clients_of_firm.reload.size
end
def test_delete_all
@@ -1079,7 +1079,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
companies(:first_firm).clients_of_firm.reset
companies(:first_firm).clients_of_firm.delete_all
assert_equal 0, companies(:first_firm).clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 0, companies(:first_firm).clients_of_firm.reload.size
end
def test_transaction_when_deleting_persisted
@@ -1093,7 +1093,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
rescue Client::RaisedOnDestroy
end
- assert_equal [good, bad], companies(:first_firm).clients_of_firm(true)
+ assert_equal [good, bad], companies(:first_firm).clients_of_firm.reload
end
def test_transaction_when_deleting_new_record
@@ -1113,7 +1113,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients_of_firm.clear
assert_equal 0, firm.clients_of_firm.size
- assert_equal 0, firm.clients_of_firm(true).size
+ assert_equal 0, firm.clients_of_firm.reload.size
assert_equal [], Client.destroyed_client_ids[firm.id]
# Should not be destroyed since the association is not dependent.
@@ -1149,7 +1149,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.dependent_clients_of_firm.clear
assert_equal 0, firm.dependent_clients_of_firm.size
- assert_equal 0, firm.dependent_clients_of_firm(true).size
+ assert_equal 0, firm.dependent_clients_of_firm.reload.size
assert_equal [], Client.destroyed_client_ids[firm.id]
# Should be destroyed since the association is dependent.
@@ -1182,7 +1182,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.exclusively_dependent_clients_of_firm.clear
assert_equal 0, firm.exclusively_dependent_clients_of_firm.size
- assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size
+ assert_equal 0, firm.exclusively_dependent_clients_of_firm.reload.size
# no destroy-filters should have been called
assert_equal [], Client.destroyed_client_ids[firm.id]
@@ -1231,7 +1231,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# break the vanilla firm_id foreign key
assert_equal 3, firm.clients.count
firm.clients.first.update_columns(firm_id: nil)
- assert_equal 2, firm.clients(true).count
+ assert_equal 2, firm.clients.reload.count
assert_equal 2, firm.clients_using_primary_key_with_delete_all.count
old_record = firm.clients_using_primary_key_with_delete_all.first
firm = Firm.first
@@ -1257,7 +1257,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients_of_firm.clear
assert_equal 0, firm.clients_of_firm.size
- assert_equal 0, firm.clients_of_firm(true).size
+ assert_equal 0, firm.clients_of_firm.reload.size
end
def test_deleting_a_item_which_is_not_in_the_collection
@@ -1265,7 +1265,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
summit = Client.find_by_name('Summit')
companies(:first_firm).clients_of_firm.delete(summit)
assert_equal 2, companies(:first_firm).clients_of_firm.size
- assert_equal 2, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 2, companies(:first_firm).clients_of_firm.reload.size
assert_equal 2, summit.client_of
end
@@ -1303,7 +1303,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_destroying_by_fixnum_id
@@ -1314,7 +1314,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_destroying_by_string_id
@@ -1325,7 +1325,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_destroying_a_collection
@@ -1338,7 +1338,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal 1, companies(:first_firm).reload.clients_of_firm.size
- assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
def test_destroy_all
@@ -1349,7 +1349,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id)
assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
- assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
+ assert companies(:first_firm).clients_of_firm.reload.empty?, "37signals has no clients after destroy all and refresh"
end
def test_dependence
@@ -1426,6 +1426,26 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert firm.companies.exists?(:name => 'child')
end
+ def test_restrict_with_error_is_deprecated_using_key_many
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { many: 'message for deprecated key' } } } }
+
+ firm = RestrictedWithErrorFirm.create!(name: 'restrict')
+ firm.companies.create(name: 'child')
+
+ assert !firm.companies.empty?
+
+ assert_deprecated { firm.destroy }
+
+ assert !firm.errors.empty?
+
+ assert_equal 'message for deprecated key', firm.errors[:base].first
+ assert RestrictedWithErrorFirm.exists?(name: 'restrict')
+ assert firm.companies.exists?(name: 'child')
+ ensure
+ I18n.backend.reload!
+ end
+
def test_restrict_with_error
firm = RestrictedWithErrorFirm.create!(:name => 'restrict')
firm.companies.create(:name => 'child')
@@ -1518,7 +1538,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
rescue Client::RaisedOnSave
end
- assert_equal [good], companies(:first_firm).clients_of_firm(true)
+ assert_equal [good], companies(:first_firm).clients_of_firm.reload
end
def test_transactions_when_replacing_on_new_record
@@ -1534,7 +1554,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
company = companies(:first_firm)
- company.clients(true)
+ company.clients.reload
assert_queries(0) do
company.client_ids
company.client_ids
@@ -1588,7 +1608,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, '']
firm.save!
- assert_equal 2, firm.clients(true).size
+ assert_equal 2, firm.clients.reload.size
assert_equal true, firm.clients.include?(companies(:second_client))
end
@@ -2252,4 +2272,40 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [first_bulb, second_bulb], car.bulbs
end
+
+ def test_association_force_reload_with_only_true_is_deprecated
+ company = Company.find(1)
+
+ assert_deprecated { company.clients_of_firm(true) }
+ end
+
+ class AuthorWithErrorDestroyingAssociation < ActiveRecord::Base
+ self.table_name = "authors"
+ has_many :posts_with_error_destroying,
+ class_name: "PostWithErrorDestroying",
+ foreign_key: :author_id,
+ dependent: :destroy
+ end
+
+ class PostWithErrorDestroying < ActiveRecord::Base
+ self.table_name = "posts"
+ self.inheritance_column = nil
+ before_destroy -> { throw :abort }
+ end
+
+ def test_destroy_does_not_raise_when_association_errors_on_destroy
+ assert_no_difference "AuthorWithErrorDestroyingAssociation.count" do
+ author = AuthorWithErrorDestroyingAssociation.first
+
+ assert_not author.destroy
+ end
+ end
+
+ def test_destroy_with_bang_bubbles_errors_from_associations
+ error = assert_raises ActiveRecord::RecordNotDestroyed do
+ AuthorWithErrorDestroyingAssociation.first.destroy!
+ end
+
+ assert_instance_of PostWithErrorDestroying, error.record
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index c34387f06d..ce2557339e 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -188,7 +188,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert post.people.include?(person)
end
- assert post.reload.people(true).include?(person)
+ assert post.reload.people.reload.include?(person)
end
def test_delete_all_for_with_dependent_option_destroy
@@ -229,7 +229,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post = posts(:thinking)
post.people.concat [person]
assert_equal 1, post.people.size
- assert_equal 1, post.people(true).size
+ assert_equal 1, post.people.reload.size
end
def test_associate_existing_record_twice_should_add_to_target_twice
@@ -285,7 +285,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:thinking).people.include?(new_person)
end
- assert posts(:thinking).reload.people(true).include?(new_person)
+ assert posts(:thinking).reload.people.reload.include?(new_person)
end
def test_associate_new_by_building
@@ -310,8 +310,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
posts(:thinking).save
end
- assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Bob")
- assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Ted")
+ assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Bob")
+ assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Ted")
end
def test_build_then_save_with_has_many_inverse
@@ -356,7 +356,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:welcome).people.empty?
end
- assert posts(:welcome).reload.people(true).empty?
+ assert posts(:welcome).reload.people.reload.empty?
end
def test_destroy_association
@@ -367,7 +367,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert posts(:welcome).reload.people.empty?
- assert posts(:welcome).people(true).empty?
+ assert posts(:welcome).people.reload.empty?
end
def test_destroy_all
@@ -378,7 +378,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert posts(:welcome).reload.people.empty?
- assert posts(:welcome).people(true).empty?
+ assert posts(:welcome).people.reload.empty?
end
def test_should_raise_exception_for_destroying_mismatching_records
@@ -539,7 +539,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_replace_association
- assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)}
+ assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload}
# 1 query to delete the existing reader (michael)
# 1 query to associate the new reader (david)
@@ -552,8 +552,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert !posts(:welcome).people.include?(people(:michael))
}
- assert posts(:welcome).reload.people(true).include?(people(:david))
- assert !posts(:welcome).reload.people(true).include?(people(:michael))
+ assert posts(:welcome).reload.people.reload.include?(people(:david))
+ assert !posts(:welcome).reload.people.reload.include?(people(:michael))
end
def test_replace_order_is_preserved
@@ -592,7 +592,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:thinking).people.collect(&:first_name).include?("Jeb")
end
- assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Jeb")
+ assert posts(:thinking).reload.people.reload.collect(&:first_name).include?("Jeb")
end
def test_through_record_is_built_when_created_with_where
@@ -668,7 +668,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_clear_associations
- assert_queries(2) { posts(:welcome);posts(:welcome).people(true) }
+ assert_queries(2) { posts(:welcome);posts(:welcome).people.reload }
assert_queries(1) do
posts(:welcome).people.clear
@@ -678,7 +678,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert posts(:welcome).people.empty?
end
- assert posts(:welcome).reload.people(true).empty?
+ assert posts(:welcome).reload.people.reload.empty?
end
def test_association_callback_ordering
@@ -750,7 +750,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
person = people(:michael)
- person.posts(true)
+ person.posts.reload
assert_queries(0) do
person.post_ids
person.post_ids
@@ -828,14 +828,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
category = author.named_categories.build(:name => "Primary")
author.save
assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name)
- assert author.named_categories(true).include?(category)
+ assert author.named_categories.reload.include?(category)
end
def test_collection_create_with_nonstandard_primary_key_on_belongs_to
author = authors(:mary)
category = author.named_categories.create(:name => "Primary")
assert Categorization.exists?(:author_id => author.id, :named_category_name => category.name)
- assert author.named_categories(true).include?(category)
+ assert author.named_categories.reload.include?(category)
end
def test_collection_exists
@@ -850,7 +850,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
category = author.named_categories.create(:name => "Primary")
author.named_categories.delete(category)
assert !Categorization.exists?(:author_id => author.id, :named_category_name => category.name)
- assert author.named_categories(true).empty?
+ assert author.named_categories.reload.empty?
end
def test_collection_singular_ids_getter_with_string_primary_keys
@@ -871,10 +871,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised do
book = books(:awdr)
book.subscriber_ids = [subscribers(:second).nick]
- assert_equal [subscribers(:second)], book.subscribers(true)
+ assert_equal [subscribers(:second)], book.subscribers.reload
book.subscriber_ids = []
- assert_equal [], book.subscribers(true)
+ assert_equal [], book.subscribers.reload
end
end
@@ -1165,4 +1165,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_nil Club.new.special_favourites.distinct_value
end
+
+ def test_association_force_reload_with_only_true_is_deprecated
+ post = Post.find(1)
+
+ assert_deprecated { post.people(true) }
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 5c2e5e7b43..5a8afaf4d2 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -178,6 +178,25 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert firm.account.present?
end
+ def test_restrict_with_error_is_deprecated_using_key_one
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { one: 'message for deprecated key' } } } }
+
+ firm = RestrictedWithErrorFirm.create!(name: 'restrict')
+ firm.create_account(credit_limit: 10)
+
+ assert_not_nil firm.account
+
+ assert_deprecated { firm.destroy }
+
+ assert !firm.errors.empty?
+ assert_equal 'message for deprecated key', firm.errors[:base].first
+ assert RestrictedWithErrorFirm.exists?(name: 'restrict')
+ assert firm.account.present?
+ ensure
+ I18n.backend.reload!
+ end
+
def test_restrict_with_error
firm = RestrictedWithErrorFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
@@ -332,7 +351,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert a.persisted?
assert_equal a, firm.account
assert_equal a, firm.account
- assert_equal a, firm.account(true)
+ firm.association(:account).reload
+ assert_equal a, firm.account
end
def test_save_still_works_after_accessing_nil_has_one
@@ -607,4 +627,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
end
end
+
+ def test_association_force_reload_with_only_true_is_deprecated
+ firm = Firm.find(1)
+
+ assert_deprecated { firm.account(true) }
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index f8772547a2..2c8feb9152 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -245,12 +245,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_not_nil @member_detail.member_type
@member_detail.destroy
assert_queries(1) do
- assert_not_nil @member_detail.member_type(true)
+ @member_detail.association(:member_type).reload
+ assert_not_nil @member_detail.member_type
end
@member_detail.member.destroy
assert_queries(1) do
- assert_nil @member_detail.member_type(true)
+ @member_detail.association(:member_type).reload
+ assert_nil @member_detail.member_type
end
end
@@ -344,4 +346,17 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
end
end
+
+ def test_association_force_reload_with_only_true_is_deprecated
+ member = Member.find(1)
+
+ assert_deprecated { member.club(true) }
+ end
+
+ def test_has_one_through_associations_are_mutable_unless_through_belongs_to
+ member_detail = MemberDetail.new(member: @member)
+ assert_raise(ActiveRecord::HasOneThroughCantAssociateThroughHasOneOrManyReflection) do
+ member_detail.membership = Membership.new
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 5575419c35..f6dddaf5b4 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -213,7 +213,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
old_count = Tagging.count
post.destroy
assert_equal old_count-1, Tagging.count
- assert_nil posts(:welcome).tagging(true)
+ posts(:welcome).association(:tagging).reload
+ assert_nil posts(:welcome).tagging
end
def test_delete_polymorphic_has_one_with_nullify
@@ -224,7 +225,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
old_count = Tagging.count
post.destroy
assert_equal old_count, Tagging.count
- assert_nil posts(:welcome).tagging(true)
+ posts(:welcome).association(:tagging).reload
+ assert_nil posts(:welcome).tagging
end
def test_has_many_with_piggyback
@@ -461,7 +463,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert saved_post.tags.include?(new_tag)
assert new_tag.persisted?
- assert saved_post.reload.tags(true).include?(new_tag)
+ assert saved_post.reload.tags.reload.include?(new_tag)
new_post = Post.new(:title => "Association replacement works!", :body => "You best believe it.")
@@ -474,7 +476,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
new_post.save!
assert new_post.persisted?
- assert new_post.reload.tags(true).include?(saved_tag)
+ assert new_post.reload.tags.reload.include?(saved_tag)
assert !posts(:thinking).tags.build.persisted?
assert !posts(:thinking).tags.new.persisted?
@@ -490,7 +492,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
assert_equal(count + 1, post_thinking.reload.tags.size)
- assert_equal(count + 1, post_thinking.tags(true).size)
+ assert_equal(count + 1, post_thinking.tags.reload.size)
assert_kind_of Tag, post_thinking.tags.create!(:name => 'foo')
assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
@@ -498,7 +500,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
assert_equal(count + 2, post_thinking.reload.tags.size)
- assert_equal(count + 2, post_thinking.tags(true).size)
+ assert_equal(count + 2, post_thinking.tags.reload.size)
assert_nothing_raised { post_thinking.tags.concat(Tag.create!(:name => 'abc'), Tag.create!(:name => 'def')) }
assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
@@ -506,7 +508,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
assert_equal(count + 4, post_thinking.reload.tags.size)
- assert_equal(count + 4, post_thinking.tags(true).size)
+ assert_equal(count + 4, post_thinking.tags.reload.size)
# Raises if the wrong reflection name is used to set the Edge belongs_to
assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) }
@@ -544,11 +546,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
book = Book.create!(:name => 'Getting Real')
book_awdr = books(:awdr)
book_awdr.references << book
- assert_equal(count + 1, book_awdr.references(true).size)
+ assert_equal(count + 1, book_awdr.references.reload.size)
assert_nothing_raised { book_awdr.references.delete(book) }
assert_equal(count, book_awdr.references.size)
- assert_equal(count, book_awdr.references(true).size)
+ assert_equal(count, book_awdr.references.reload.size)
assert_equal(references_before.sort, book_awdr.references.sort)
end
@@ -558,14 +560,14 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
tag = Tag.create!(:name => 'doomed')
post_thinking = posts(:thinking)
post_thinking.tags << tag
- assert_equal(count + 1, post_thinking.taggings(true).size)
- assert_equal(count + 1, post_thinking.reload.tags(true).size)
+ assert_equal(count + 1, post_thinking.taggings.reload.size)
+ assert_equal(count + 1, post_thinking.reload.tags.reload.size)
assert_not_equal(tags_before, post_thinking.tags.sort)
assert_nothing_raised { post_thinking.tags.delete(tag) }
assert_equal(count, post_thinking.tags.size)
- assert_equal(count, post_thinking.tags(true).size)
- assert_equal(count, post_thinking.taggings(true).size)
+ assert_equal(count, post_thinking.tags.reload.size)
+ assert_equal(count, post_thinking.taggings.reload.size)
assert_equal(tags_before, post_thinking.tags.sort)
end
@@ -577,11 +579,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
quaked = Tag.create!(:name => 'quaked')
post_thinking = posts(:thinking)
post_thinking.tags << doomed << doomed2
- assert_equal(count + 2, post_thinking.reload.tags(true).size)
+ assert_equal(count + 2, post_thinking.reload.tags.reload.size)
assert_nothing_raised { post_thinking.tags.delete(doomed, doomed2, quaked) }
assert_equal(count, post_thinking.tags.size)
- assert_equal(count, post_thinking.tags(true).size)
+ assert_equal(count, post_thinking.tags.reload.size)
assert_equal(tags_before, post_thinking.tags.sort)
end
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index 31b68c940e..b040485d99 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -495,7 +495,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
groucho = members(:groucho)
founding = member_types(:founding)
- assert_raises(ActiveRecord::HasManyThroughNestedAssociationsAreReadonly) do
+ assert_raises(ActiveRecord::HasOneThroughNestedAssociationsAreReadonly) do
groucho.nested_member_type = founding
end
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 3d202a5527..08f790e21b 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -93,8 +93,10 @@ class AssociationsTest < ActiveRecord::TestCase
assert firm.clients.empty?, "New firm should have cached no client objects"
assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count"
- assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
- assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count"
+ ActiveSupport::Deprecation.silence do
+ assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
+ assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count"
+ end
end
def test_using_limitable_reflections_helper
@@ -110,10 +112,13 @@ class AssociationsTest < ActiveRecord::TestCase
def test_force_reload_is_uncached
firm = Firm.create!("name" => "A New Firm, Inc")
Client.create!("name" => "TheClient.com", :firm => firm)
- ActiveRecord::Base.cache do
- firm.clients.each {}
- assert_queries(0) { assert_not_nil firm.clients.each {} }
- assert_queries(1) { assert_not_nil firm.clients(true).each {} }
+
+ ActiveSupport::Deprecation.silence do
+ ActiveRecord::Base.cache do
+ firm.clients.each {}
+ assert_queries(0) { assert_not_nil firm.clients.each {} }
+ assert_queries(1) { assert_not_nil firm.clients(true).each {} }
+ end
end
end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index eeda9335ad..bd74821d87 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -142,7 +142,7 @@ module ActiveRecord
end
if current_adapter?(:PostgreSQLAdapter)
- test "arrays types can be specified" do
+ test "array types can be specified" do
klass = Class.new(OverloadedType) do
attribute :my_array, :string, limit: 50, array: true
attribute :my_int_array, :integer, array: true
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 80b5a0004d..37ec3f1106 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -67,6 +67,14 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
assert_no_difference_when_adding_callbacks_twice_for Pirate, :parrots
end
+ def test_cyclic_autosaves_do_not_add_multiple_validations
+ ship = ShipWithoutNestedAttributes.new
+ ship.prisoners.build
+
+ assert_not ship.valid?
+ assert_equal 1, ship.errors[:name].length
+ end
+
private
def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
@@ -149,7 +157,8 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
assert_equal a, firm.account
assert firm.save
assert_equal a, firm.account
- assert_equal a, firm.account(true)
+ firm.association(:account).reload
+ assert_equal a, firm.account
end
def test_assignment_before_either_saved
@@ -162,7 +171,8 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
assert firm.persisted?
assert a.persisted?
assert_equal a, firm.account
- assert_equal a, firm.account(true)
+ firm.association(:account).reload
+ assert_equal a, firm.account
end
def test_not_resaved_when_unchanged
@@ -248,7 +258,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert apple.save
assert apple.persisted?
assert_equal apple, client.firm
- assert_equal apple, client.firm(true)
+ client.association(:firm).reload
+ assert_equal apple, client.firm
end
def test_assignment_before_either_saved
@@ -261,7 +272,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert final_cut.persisted?
assert apple.persisted?
assert_equal apple, final_cut.firm
- assert_equal apple, final_cut.firm(true)
+ final_cut.association(:firm).reload
+ assert_equal apple, final_cut.firm
end
def test_store_two_association_with_one_save
@@ -456,7 +468,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
assert_equal new_client, companies(:first_firm).clients_of_firm.last
assert !companies(:first_firm).save
assert !new_client.persisted?
- assert_equal 2, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 2, companies(:first_firm).clients_of_firm.reload.size
end
def test_adding_before_save
@@ -481,7 +493,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
assert_equal no_of_clients + 2, Client.count # Clients were saved to database.
assert_equal 2, new_firm.clients_of_firm.size
- assert_equal 2, new_firm.clients_of_firm(true).size
+ assert_equal 2, new_firm.clients_of_firm.reload.size
end
def test_assign_ids
@@ -510,7 +522,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(2) { assert company.save }
assert new_client.persisted?
- assert_equal 3, company.clients_of_firm(true).size
+ assert_equal 3, company.clients_of_firm.reload.size
end
def test_build_many_before_save
@@ -519,7 +531,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(3) { assert company.save }
- assert_equal 4, company.clients_of_firm(true).size
+ assert_equal 4, company.clients_of_firm.reload.size
end
def test_build_via_block_before_save
@@ -530,7 +542,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(2) { assert company.save }
assert new_client.persisted?
- assert_equal 3, company.clients_of_firm(true).size
+ assert_equal 3, company.clients_of_firm.reload.size
end
def test_build_many_via_block_before_save
@@ -543,7 +555,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
company.name += '-changed'
assert_queries(3) { assert company.save }
- assert_equal 4, company.clients_of_firm(true).size
+ assert_equal 4, company.clients_of_firm.reload.size
end
def test_replace_on_new_object
@@ -1142,6 +1154,13 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_not_load_the_associated_model
assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
end
+
+ def test_mark_for_destruction_is_ignored_without_autosave_true
+ ship = ShipWithoutNestedAttributes.new(name: "The Black Flag")
+ ship.parts.build.mark_for_destruction
+
+ assert_not ship.valid?
+ end
end
class TestAutosaveAssociationOnAHasOneThroughAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 31c31e4329..382adbbdc7 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
require "cases/helper"
-require 'active_support/concurrency/latch'
require 'models/post'
require 'models/author'
require 'models/topic'
@@ -29,6 +28,7 @@ require 'models/bird'
require 'models/car'
require 'models/bulb'
require 'rexml/document'
+require 'concurrent/atomics'
class FirstAbstractClass < ActiveRecord::Base
self.abstract_class = true
@@ -1506,20 +1506,20 @@ class BasicsTest < ActiveRecord::TestCase
orig_handler = klass.connection_handler
new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
after_handler = nil
- latch1 = ActiveSupport::Concurrency::Latch.new
- latch2 = ActiveSupport::Concurrency::Latch.new
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
t = Thread.new do
klass.connection_handler = new_handler
- latch1.release
- latch2.await
+ latch1.count_down
+ latch2.wait
after_handler = klass.connection_handler
end
- latch1.await
+ latch1.wait
klass.connection_handler = orig_handler
- latch2.release
+ latch2.count_down
t.join
assert_equal after_handler, new_handler
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
new file mode 100644
index 0000000000..724234d7f4
--- /dev/null
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -0,0 +1,70 @@
+require "cases/helper"
+require "models/computer"
+require "models/developer"
+require "models/project"
+require "models/topic"
+require "models/post"
+require "models/comment"
+
+module ActiveRecord
+ class CollectionCacheKeyTest < ActiveRecord::TestCase
+ fixtures :developers, :projects, :developers_projects, :topics, :comments, :posts
+
+ test "collection_cache_key on model" do
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, Developer.collection_cache_key)
+ end
+
+ test "cache_key for relation" do
+ developers = Developer.where(name: "David")
+ last_developer_timestamp = developers.order(updated_at: :desc).first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/ =~ developers.cache_key
+
+ assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
+ test "it triggers at most one query" do
+ developers = Developer.where(name: "David")
+
+ assert_queries(1) { developers.cache_key }
+ assert_queries(0) { developers.cache_key }
+ end
+
+ test "it doesn't trigger any query if the relation is already loaded" do
+ developers = Developer.where(name: "David").load
+ assert_queries(0) { developers.cache_key }
+ end
+
+ test "relation cache_key changes when the sql query changes" do
+ developers = Developer.where(name: "David")
+ other_relation = Developer.where(name: "David").where("1 = 1")
+
+ assert_not_equal developers.cache_key, other_relation.cache_key
+ end
+
+ test "cache_key for empty relation" do
+ developers = Developer.where(name: "Non Existent Developer")
+ assert_match(/\Adevelopers\/query-(\h+)-0\Z/, developers.cache_key)
+ end
+
+ test "cache_key with custom timestamp column" do
+ topics = Topic.where("title like ?", "%Topic%")
+ last_topic_timestamp = topics(:fifth).written_on.utc.to_s(:nsec)
+ assert_match(last_topic_timestamp, topics.cache_key(:written_on))
+ end
+
+ test "cache_key with unknown timestamp column" do
+ topics = Topic.where("title like ?", "%Topic%")
+ assert_raises(ActiveRecord::StatementInvalid) { topics.cache_key(:published_at) }
+ end
+
+ test "collection proxy provides a cache_key" do
+ developers = projects(:active_record).developers
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\Z/, developers.cache_key)
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index c905772193..7ef5c93a48 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -1,5 +1,5 @@
require "cases/helper"
-require 'active_support/concurrency/latch'
+require 'concurrent/atomics'
module ActiveRecord
module ConnectionAdapters
@@ -133,15 +133,15 @@ module ActiveRecord
end
def test_reap_inactive
- ready = ActiveSupport::Concurrency::Latch.new
+ ready = Concurrent::CountDownLatch.new
@pool.checkout
child = Thread.new do
@pool.checkout
@pool.checkout
- ready.release
+ ready.count_down
Thread.stop
end
- ready.await
+ ready.wait
assert_equal 3, active_connections(@pool).size
@@ -360,13 +360,13 @@ module ActiveRecord
def test_concurrent_connection_establishment
assert_operator @pool.connections.size, :<=, 1
- all_threads_in_new_connection = ActiveSupport::Concurrency::Latch.new(@pool.size - @pool.connections.size)
- all_go = ActiveSupport::Concurrency::Latch.new
+ all_threads_in_new_connection = Concurrent::CountDownLatch.new(@pool.size - @pool.connections.size)
+ all_go = Concurrent::CountDownLatch.new
@pool.singleton_class.class_eval do
define_method(:new_connection) do
- all_threads_in_new_connection.release
- all_go.await
+ all_threads_in_new_connection.count_down
+ all_go.wait
super()
end
end
@@ -381,14 +381,14 @@ module ActiveRecord
# the kernel of the whole test is here, everything else is just scaffolding,
# this latch will not be released unless conn. pool allows for concurrent
# connection creation
- all_threads_in_new_connection.await
+ all_threads_in_new_connection.wait
end
rescue Timeout::Error
flunk 'pool unable to establish connections concurrently or implementation has ' <<
'changed, this test then needs to patch a different :new_connection method'
ensure
# clean up the threads
- all_go.release
+ all_go.count_down
connecting_threads.map(&:join)
end
end
@@ -441,11 +441,11 @@ module ActiveRecord
with_single_connection_pool do |pool|
[:disconnect, :disconnect!, :clear_reloadable_connections, :clear_reloadable_connections!].each do |group_action_method|
conn = pool.connection # drain the only available connection
- second_thread_done = ActiveSupport::Concurrency::Latch.new
+ second_thread_done = Concurrent::CountDownLatch.new
# create a first_thread and let it get into the FIFO queue first
first_thread = Thread.new do
- pool.with_connection { second_thread_done.await }
+ pool.with_connection { second_thread_done.wait }
end
# wait for first_thread to get in queue
@@ -456,7 +456,7 @@ module ActiveRecord
# first_thread when a connection is made available
second_thread = Thread.new do
pool.send(group_action_method)
- second_thread_done.release
+ second_thread_done.count_down
end
# wait for second_thread to get in queue
@@ -471,7 +471,7 @@ module ActiveRecord
failed = true unless second_thread.join(2)
#--- post test clean up start
- second_thread_done.release if failed
+ second_thread_done.count_down if failed
# after `pool.disconnect()` the first thread will be left stuck in queue, no need to wait for
# it to timeout with ConnectionTimeoutError
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 1f5055b2a2..922cb59280 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -1,6 +1,7 @@
require 'cases/helper'
require 'models/topic'
require 'models/car'
+require 'models/aircraft'
require 'models/wheel'
require 'models/engine'
require 'models/reply'
@@ -198,4 +199,16 @@ class CounterCacheTest < ActiveRecord::TestCase
assert_equal 2, car.engines_count
assert_equal 2, car.reload.engines_count
end
+
+ test "update counters in a polymorphic relationship" do
+ aircraft = Aircraft.create!
+
+ assert_difference 'aircraft.reload.wheels_count' do
+ aircraft.wheels << Wheel.create!
+ end
+
+ assert_difference 'aircraft.reload.wheels_count', -1 do
+ aircraft.wheels.first.destroy
+ end
+ end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 12c793c408..d9d0f929db 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -47,7 +47,8 @@ end
def mysql_56?
current_adapter?(:MysqlAdapter, :Mysql2Adapter) &&
- ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0"
+ ActiveRecord::Base.connection.send(:version) >= '5.6.0' &&
+ ActiveRecord::Base.connection.send(:version) < '5.7.0'
end
def mysql_enforcing_gtid_consistency?
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index dbdcc84b7d..2e1363334d 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -270,7 +270,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
car.wheels << Wheel.create!
end
assert_difference 'car.wheels.count', -1 do
- car.destroy
+ car.reload.destroy
end
assert car.destroyed?
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 6b4addd52f..b8a0401fe3 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -658,6 +658,16 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
end
+ def test_should_raise_RecordNotFound_if_an_id_belonging_to_a_different_record_is_given
+ other_pirate = Pirate.create! catchphrase: 'Ahoy!'
+ other_child = other_pirate.send(@association_name).create! name: 'Buccaneers Servant'
+
+ exception = assert_raise ActiveRecord::RecordNotFound do
+ @pirate.attributes = { association_getter => [{ id: other_child.id }] }
+ end
+ assert_equal "Couldn't find #{@child_1.class.name} with ID=#{other_child.id} for Pirate with ID=#{@pirate.id}", exception.message
+ end
+
def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing
@pirate.send(@association_name).destroy_all
@pirate.reload.attributes = {
@@ -1054,4 +1064,39 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
assert_not part.valid?
assert_equal ["Ship name can't be blank"], part.errors.full_messages
end
+
+ class ProtectedParameters
+ def initialize(hash)
+ @hash = hash
+ end
+
+ def permitted?
+ true
+ end
+
+ def [](key)
+ @hash[key]
+ end
+
+ def to_h
+ @hash
+ end
+ end
+
+ test "strong params style objects can be assigned for singular associations" do
+ params = { name: "Stern", ship_attributes:
+ ProtectedParameters.new(name: "The Black Rock") }
+ part = ShipPart.new(params)
+
+ assert_equal "Stern", part.name
+ assert_equal "The Black Rock", part.ship.name
+ end
+
+ test "strong params style objects can be assigned for collection associations" do
+ params = { trinkets_attributes: ProtectedParameters.new("0" => ProtectedParameters.new(name: "Necklace"), "1" => ProtectedParameters.new(name: "Spoon")) }
+ part = ShipPart.new(params)
+
+ assert_equal "Necklace", part.trinkets[0].name
+ assert_equal "Spoon", part.trinkets[1].name
+ end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index acbf85d398..5f48c2b40f 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -647,6 +647,25 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_preloading_with_associations_default_scopes_and_merges
+ post = Post.create! title: 'Uhuu', body: 'body'
+ reader = Reader.create! post_id: post.id, person_id: 1
+
+ post_rel = PostWithPreloadDefaultScope.preload(:readers).joins(:readers).where(title: 'Uhuu')
+ result_post = PostWithPreloadDefaultScope.all.merge(post_rel).to_a.first
+
+ assert_no_queries do
+ assert_equal [reader], result_post.readers.to_a
+ end
+
+ post_rel = PostWithIncludesDefaultScope.includes(:readers).where(title: 'Uhuu')
+ result_post = PostWithIncludesDefaultScope.all.merge(post_rel).to_a.first
+
+ assert_no_queries do
+ assert_equal [reader], result_post.readers.to_a
+ end
+ end
+
def test_loading_with_one_association
posts = Post.preload(:comments)
post = posts.find { |p| p.id == 1 }
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 2468a91969..29a6ec7522 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -175,13 +175,20 @@ class TransactionTest < ActiveRecord::TestCase
assert topic.new_record?, "#{topic.inspect} should be new record"
end
+ def test_transaction_state_is_cleared_when_record_is_persisted
+ author = Author.create! name: 'foo'
+ author.name = nil
+ assert_not author.save
+ assert_not author.new_record?
+ end
+
def test_update_should_rollback_on_failure
author = Author.find(1)
posts_count = author.posts.size
assert posts_count > 0
status = author.update(name: nil, post_ids: [])
assert !status
- assert_equal posts_count, author.posts(true).size
+ assert_equal posts_count, author.posts.reload.size
end
def test_update_should_rollback_on_failure!
@@ -191,7 +198,7 @@ class TransactionTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordInvalid) do
author.update!(name: nil, post_ids: [])
end
- assert_equal posts_count, author.posts(true).size
+ assert_equal posts_count, author.posts.reload.size
end
def test_cancellation_from_returning_false_in_before_filter
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 2608c84be2..ceca2c8366 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -427,4 +427,23 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert reply.valid?
assert topic.valid?
end
+
+ def test_validate_uniqueness_of_custom_primary_key
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "keyboards"
+ self.primary_key = :key_number
+
+ validates_uniqueness_of :key_number
+
+ def self.name
+ "Keyboard"
+ end
+ end
+
+ klass.create!(key_number: 10)
+ key2 = klass.create!(key_number: 11)
+
+ key2.key_number = 10
+ assert_not key2.valid?
+ end
end
diff --git a/activerecord/test/models/aircraft.rb b/activerecord/test/models/aircraft.rb
index 1f35ef45da..c4404a8094 100644
--- a/activerecord/test/models/aircraft.rb
+++ b/activerecord/test/models/aircraft.rb
@@ -1,4 +1,5 @@
class Aircraft < ActiveRecord::Base
self.pluralize_table_names = false
has_many :engines, :foreign_key => "car_id"
+ has_many :wheels, as: :wheelable
end
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index 24bfe47bbf..1927191393 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -10,10 +10,10 @@ class Book < ActiveRecord::Base
enum status: [:proposed, :written, :published]
enum read_status: {unread: 0, reading: 2, read: 3}
enum nullable_status: [:single, :married]
- enum language: [:english, :spanish, :french], enum_prefix: :in
- enum author_visibility: [:visible, :invisible], enum_prefix: true
- enum illustrator_visibility: [:visible, :invisible], enum_prefix: true
- enum font_size: [:small, :medium, :large], enum_prefix: :with, enum_suffix: true
+ enum language: [:english, :spanish, :french], _prefix: :in
+ enum author_visibility: [:visible, :invisible], _prefix: true
+ enum illustrator_visibility: [:visible, :invisible], _prefix: true
+ enum font_size: [:small, :medium, :large], _prefix: :with, _suffix: true
def published!
super
diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb
index 9d253aa126..157130986c 100644
--- a/activerecord/test/models/member_detail.rb
+++ b/activerecord/test/models/member_detail.rb
@@ -1,7 +1,8 @@
class MemberDetail < ActiveRecord::Base
- belongs_to :member, :inverse_of => false
+ belongs_to :member, inverse_of: false
belongs_to :organization
- has_one :member_type, :through => :member
+ has_one :member_type, through: :member
+ has_one :membership, through: :member
- has_many :organization_member_details, :through => :organization, :source => :member_details
+ has_many :organization_member_details, through: :organization, source: :member_details
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 052b1c9690..10f13b67da 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -208,6 +208,22 @@ class PostWithDefaultScope < ActiveRecord::Base
default_scope { order(:title) }
end
+class PostWithPreloadDefaultScope < ActiveRecord::Base
+ self.table_name = 'posts'
+
+ has_many :readers, foreign_key: 'post_id'
+
+ default_scope { preload(:readers) }
+end
+
+class PostWithIncludesDefaultScope < ActiveRecord::Base
+ self.table_name = 'posts'
+
+ has_many :readers, foreign_key: 'post_id'
+
+ default_scope { includes(:readers) }
+end
+
class SpecialPostWithDefaultScope < ActiveRecord::Base
self.table_name = 'posts'
default_scope { where(:id => [1, 5,6]) }
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index 312caef604..95172e4d3e 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -19,6 +19,18 @@ class Ship < ActiveRecord::Base
end
end
+class ShipWithoutNestedAttributes < ActiveRecord::Base
+ self.table_name = "ships"
+ has_many :prisoners, inverse_of: :ship, foreign_key: :ship_id
+ has_many :parts, class_name: "ShipPart", foreign_key: :ship_id
+
+ validates :name, presence: true
+end
+
+class Prisoner < ActiveRecord::Base
+ belongs_to :ship, autosave: true, class_name: "ShipWithoutNestedAttributes", inverse_of: :prisoners
+end
+
class FamousShip < ActiveRecord::Base
self.table_name = 'ships'
belongs_to :famous_pirate
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index f81ffe1d90..d17270021a 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -32,7 +32,7 @@ class Topic < ActiveRecord::Base
end
end
- has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
+ has_many :replies, dependent: :destroy, foreign_key: "parent_id", autosave: true
has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count'
has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 6872b49ad9..7bab675b2a 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -37,6 +37,7 @@ ActiveRecord::Schema.define do
create_table :aircraft, force: true do |t|
t.string :name
+ t.integer :wheels_count, default: 0, null: false
end
create_table :articles, force: true do |t|
@@ -678,6 +679,10 @@ ActiveRecord::Schema.define do
t.datetime :updated_at
end
+ create_table :prisoners, force: true do |t|
+ t.belongs_to :ship
+ end
+
create_table :speedometers, force: true, id: false do |t|
t.string :speedometer_id
t.string :name
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 6ebbdbc3db..7148f289bb 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,32 @@
+* Fix `TimeWithZone#eql?` to properly handle `TimeWithZone` created from `DateTime`:
+ twz = DateTime.now.in_time_zone
+ twz.eql?(twz.dup) => true
+
+ Fixes #14178.
+
+ *Roque Pinel*
+
+* ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return
+ enumerator if called without block.
+
+ Fixes #20095
+
+ *Bernard Potocki*
+
+* Removed `ActiveSupport::Concurrency::Latch`, superseded by `Concurrent::CountDownLatch`
+ from the concurrent-ruby gem.
+
+ *Jerry D'Antonio*
+
+* Fix not calling `#default` on `HashWithIndifferentAccess#to_hash` when only
+ `default_proc` is set, which could raise.
+
+ *Simon Eskildsen*
+
+* Fix setting `default_proc` on `HashWithIndifferentAccess#dup`
+
+ *Simon Eskildsen*
+
* Fix a range of values for parameters of the Time#change
*Nikolay Kondratyev*
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 12c6a4d449..c18c1c87c9 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -25,4 +25,5 @@ Gem::Specification.new do |s|
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4'
+ s.add_dependency 'concurrent-ruby', '~> 0.9.0'
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index e8ab3a7db5..f35e1f5098 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -80,8 +80,12 @@ module ActiveSupport
# save
# end
def run_callbacks(kind, &block)
- callbacks = send("_#{kind}_callbacks")
+ send "_run_#{kind}_callbacks", &block
+ end
+
+ private
+ def __run_callbacks__(callbacks, &block)
if callbacks.empty?
yield if block_given?
else
@@ -91,8 +95,6 @@ module ActiveSupport
end
end
- private
-
# A hook invoked every time a before callback is halted.
# This can be overridden in AS::Callback implementors in order
# to provide better debugging/logging.
@@ -806,6 +808,12 @@ module ActiveSupport
names.each do |name|
class_attribute "_#{name}_callbacks"
set_callbacks name, CallbackChain.new(name, options)
+
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def _run_#{name}_callbacks(&block)
+ __run_callbacks__(_#{name}_callbacks, &block)
+ end
+ RUBY
end
end
diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb
index 1507de433e..7b8df0df04 100644
--- a/activesupport/lib/active_support/concurrency/latch.rb
+++ b/activesupport/lib/active_support/concurrency/latch.rb
@@ -1,26 +1,18 @@
-require 'thread'
-require 'monitor'
+require 'concurrent/atomics'
module ActiveSupport
module Concurrency
- class Latch
- def initialize(count = 1)
- @count = count
- @lock = Monitor.new
- @cv = @lock.new_cond
- end
+ class Latch < Concurrent::CountDownLatch
- def release
- @lock.synchronize do
- @count -= 1 if @count > 0
- @cv.broadcast if @count.zero?
- end
+ def initialize(count = 1)
+ ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.")
+ super(count)
end
+
+ alias_method :release, :count_down
def await
- @lock.synchronize do
- @cv.wait_while { @count > 0 }
- end
+ wait(nil)
end
end
end
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
new file mode 100644
index 0000000000..ca48164c54
--- /dev/null
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -0,0 +1,142 @@
+require 'thread'
+require 'monitor'
+
+module ActiveSupport
+ module Concurrency
+ # A share/exclusive lock, otherwise known as a read/write lock.
+ #
+ # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
+ #--
+ # Note that a pending Exclusive lock attempt does not block incoming
+ # Share requests (i.e., we are "read-preferring"). That seems
+ # consistent with the behavior of "loose" upgrades, but may be the
+ # wrong choice otherwise: it nominally reduces the possibility of
+ # deadlock by risking starvation instead.
+ class ShareLock
+ include MonitorMixin
+
+ # We track Thread objects, instead of just using counters, because
+ # we need exclusive locks to be reentrant, and we need to be able
+ # to upgrade share locks to exclusive.
+
+
+ def initialize
+ super()
+
+ @cv = new_cond
+
+ @sharing = Hash.new(0)
+ @waiting = {}
+ @exclusive_thread = nil
+ @exclusive_depth = 0
+ end
+
+ # Returns false if +no_wait+ is set and the lock is not
+ # immediately available. Otherwise, returns true after the lock
+ # has been acquired.
+ #
+ # +purpose+ and +compatible+ work together; while this thread is
+ # waiting for the exclusive lock, it will yield its share (if any)
+ # to any other attempt whose +purpose+ appears in this attempt's
+ # +compatible+ list. This allows a "loose" upgrade, which, being
+ # less strict, prevents some classes of deadlocks.
+ #
+ # For many resources, loose upgrades are sufficient: if a thread
+ # is awaiting a lock, it is not running any other code. With
+ # +purpose+ matching, it is possible to yield only to other
+ # threads whose activity will not interfere.
+ def start_exclusive(purpose: nil, compatible: [], no_wait: false)
+ synchronize do
+ unless @exclusive_thread == Thread.current
+ if busy?(purpose)
+ return false if no_wait
+
+ loose_shares = @sharing.delete(Thread.current)
+ @waiting[Thread.current] = compatible if loose_shares
+
+ begin
+ @cv.wait_while { busy?(purpose) }
+ ensure
+ @waiting.delete Thread.current
+ @sharing[Thread.current] = loose_shares if loose_shares
+ end
+ end
+ @exclusive_thread = Thread.current
+ end
+ @exclusive_depth += 1
+
+ true
+ end
+ end
+
+ # Relinquish the exclusive lock. Must only be called by the thread
+ # that called start_exclusive (and currently holds the lock).
+ def stop_exclusive
+ synchronize do
+ raise "invalid unlock" if @exclusive_thread != Thread.current
+
+ @exclusive_depth -= 1
+ if @exclusive_depth == 0
+ @exclusive_thread = nil
+ @cv.broadcast
+ end
+ end
+ end
+
+ def start_sharing
+ synchronize do
+ if @exclusive_thread && @exclusive_thread != Thread.current
+ @cv.wait_while { @exclusive_thread }
+ end
+ @sharing[Thread.current] += 1
+ end
+ end
+
+ def stop_sharing
+ synchronize do
+ if @sharing[Thread.current] > 1
+ @sharing[Thread.current] -= 1
+ else
+ @sharing.delete Thread.current
+ @cv.broadcast
+ end
+ end
+ end
+
+ # Execute the supplied block while holding the Exclusive lock. If
+ # +no_wait+ is set and the lock is not immediately available,
+ # returns +nil+ without yielding. Otherwise, returns the result of
+ # the block.
+ #
+ # See +start_exclusive+ for other options.
+ def exclusive(purpose: nil, compatible: [], no_wait: false)
+ if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
+ begin
+ yield
+ ensure
+ stop_exclusive
+ end
+ end
+ end
+
+ # Execute the supplied block while holding the Share lock.
+ def sharing
+ start_sharing
+ begin
+ yield
+ ensure
+ stop_sharing
+ end
+ end
+
+ private
+
+ # Must be called within synchronize
+ def busy?(purpose)
+ (@exclusive_thread && @exclusive_thread != Thread.current) ||
+ @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } ||
+ @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 01153606c9..40811dafc0 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -92,15 +92,28 @@ module DateAndTime
end
# Returns a new date/time at the start of the month.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Thu, 18 Jun 2015
+ # today.beginning_of_month # => Mon, 01 Jun 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000
+ # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000
def beginning_of_month
first_hour(change(:day => 1))
end
alias :at_beginning_of_month :beginning_of_month
# Returns a new date/time at the start of the quarter.
- # Example: 1st January, 1st July, 1st October.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_quarter # => Wed, 01 Jul 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000
def beginning_of_quarter
first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
beginning_of_month.change(:month => first_quarter_month)
@@ -108,8 +121,14 @@ module DateAndTime
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new date/time at the end of the quarter.
- # Example: 31st March, 30th June, 30th September.
- # DateTime objects will have a time set to 23:59:59.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.end_of_quarter # => Wed, 30 Sep 2015
+ #
+ # +DateTime+ objects will have a time set to 23:59:59.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000
def end_of_quarter
last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
beginning_of_month.change(:month => last_quarter_month).end_of_month
@@ -117,8 +136,14 @@ module DateAndTime
alias :at_end_of_quarter :end_of_quarter
# Return a new date/time at the beginning of the year.
- # Example: 1st January.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_year # => Thu, 01 Jan 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000
def beginning_of_year
change(:month => 1).beginning_of_month
end
@@ -138,8 +163,8 @@ module DateAndTime
#
# +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
#
- # now = Time.current # => Thu, 07 May 2015 13:31:16 UTC +00:00
- # now.next_week # => Mon, 11 May 2015 00:00:00 UTC +00:00
+ # now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000
+ # now.next_week # => Mon, 11 May 2015 00:00:00 +0000
def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false)
result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
same_time ? copy_time_to(result) : result
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 6e397abf51..2f6d38c1f6 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,8 +1,9 @@
class Hash
- # Returns a hash that includes everything but the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false, c: nil}
+ # Returns a hash that includes everything except given keys.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except(:c) # => { a: true, b: false }
+ # hash.except(:a, :b) # => { c: nil }
+ # hash # => { a: true, b: false, c: nil }
#
# This is useful for limiting a set of parameters to everything but a few known toggles:
# @person.update(params[:person].except(:admin))
@@ -10,10 +11,10 @@ class Hash
dup.except!(*keys)
end
- # Replaces the hash without the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except!(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false }
+ # Removes the given keys from hash and returns it.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except!(:c) # => { a: true, b: false }
+ # hash # => { a: true, b: false }
def except!(*keys)
keys.each { |key| delete(key) }
self
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index d9fb392752..60732eb41a 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -23,7 +23,7 @@ class LoadError
# Returns true if the given path name (except perhaps for the ".rb"
# extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
- location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
+ location.sub(/\.rb$/, ''.freeze) == path.sub(/\.rb$/, ''.freeze)
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index e26b594fc4..65b88b9bbd 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -99,7 +99,7 @@ class Module
# end
#
# Todo.ancestors
- # # => Todo, Todo::EventTracking, Object
+ # # => [Todo, Todo::EventTracking, Object]
#
# This small step has some wonderful ripple effects. We can
# * grok the behavior of our class in one glance,
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 9b7a429db9..9dc0dee1bd 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -167,7 +167,7 @@ class Module
''
end
- file, line = caller(1, 1).first.split(':', 2)
+ file, line = caller(1, 1).first.split(':'.freeze, 2)
line = line.to_i
to = to.to_s
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 620f7b6561..befa5aee21 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbol, number objects;
+ # False for +nil+, +false+, +true+, symbol, number, method objects;
# true otherwise.
def duplicable?
true
@@ -78,6 +78,10 @@ end
require 'bigdecimal'
class BigDecimal
+ # BigDecimals are duplicable:
+ #
+ # BigDecimal.new("1.2").duplicable? # => true
+ # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 97f9720b2b..0d5e02cd77 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -164,7 +164,7 @@ class String
#
# <%= link_to(@person.name, person_path) %>
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
- def parameterize(sep = '-')
+ def parameterize(sep = '-'.freeze)
ActiveSupport::Inflector.parameterize(self, sep)
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index c554501893..96156deebb 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -108,6 +108,12 @@ class Time
# takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>,
# <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>,
# <tt>:seconds</tt>.
+ #
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index d683e7c777..133d3938eb 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -65,7 +65,8 @@ class Time
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
time_zone
else
- # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
+ # Look up the timezone based on the identifier (unless we've been
+ # passed a TZInfo::Timezone)
unless time_zone.respond_to?(:period_for_local)
time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 664cc15a29..f76ef04f49 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -12,12 +12,40 @@ require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/core_ext/string/starts_ends_with'
+require "active_support/dependencies/interlock"
require 'active_support/inflector'
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
+ mattr_accessor :interlock
+ self.interlock = Interlock.new
+
+ # :doc:
+
+ # Execute the supplied block without interference from any
+ # concurrent loads
+ def self.run_interlock
+ Dependencies.interlock.running { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time
+ def self.load_interlock
+ Dependencies.interlock.loading { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time
+ def self.unload_interlock
+ Dependencies.interlock.unloading { yield }
+ end
+
+ # :nodoc:
+
# Should we turn on Ruby warnings on the first load of dependent files?
mattr_accessor :warnings_on_first_load
self.warnings_on_first_load = false
@@ -128,7 +156,7 @@ module ActiveSupport #:nodoc:
# Normalize the list of new constants, and add them to the list we will return
new_constants.each do |suffix|
- constants << ([namespace, suffix] - ["Object"]).join("::")
+ constants << ([namespace, suffix] - ["Object"]).join("::".freeze)
end
end
constants
@@ -325,9 +353,11 @@ module ActiveSupport #:nodoc:
def clear
log_call
- loaded.clear
- loading.clear
- remove_unloadable_constants!
+ Dependencies.unload_interlock do
+ loaded.clear
+ loading.clear
+ remove_unloadable_constants!
+ end
end
def require_or_load(file_name, const_path = nil)
@@ -336,39 +366,44 @@ module ActiveSupport #:nodoc:
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
- # Record that we've seen this file *before* loading it to avoid an
- # infinite loop with mutual dependencies.
- loaded << expanded
- loading << expanded
+ Dependencies.load_interlock do
+ # Maybe it got loaded while we were waiting for our lock:
+ return if loaded.include?(expanded)
- begin
- if load?
- log "loading #{file_name}"
+ # Record that we've seen this file *before* loading it to avoid an
+ # infinite loop with mutual dependencies.
+ loaded << expanded
+ loading << expanded
- # Enable warnings if this file has not been loaded before and
- # warnings_on_first_load is set.
- load_args = ["#{file_name}.rb"]
- load_args << const_path unless const_path.nil?
-
- if !warnings_on_first_load or history.include?(expanded)
- result = load_file(*load_args)
+ begin
+ if load?
+ log "loading #{file_name}"
+
+ # Enable warnings if this file has not been loaded before and
+ # warnings_on_first_load is set.
+ load_args = ["#{file_name}.rb"]
+ load_args << const_path unless const_path.nil?
+
+ if !warnings_on_first_load or history.include?(expanded)
+ result = load_file(*load_args)
+ else
+ enable_warnings { result = load_file(*load_args) }
+ end
else
- enable_warnings { result = load_file(*load_args) }
+ log "requiring #{file_name}"
+ result = require file_name
end
- else
- log "requiring #{file_name}"
- result = require file_name
+ rescue Exception
+ loaded.delete expanded
+ raise
+ ensure
+ loading.pop
end
- rescue Exception
- loaded.delete expanded
- raise
- ensure
- loading.pop
- end
- # Record history *after* loading so first load gets warnings.
- history << expanded
- result
+ # Record history *after* loading so first load gets warnings.
+ history << expanded
+ result
+ end
end
# Is the provided constant path defined?
@@ -401,7 +436,7 @@ module ActiveSupport #:nodoc:
# Search for a file in autoload_paths matching the provided suffix.
def search_for_file(path_suffix)
- path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb")
+ path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze)
autoload_paths.each do |root|
path = File.join(root, path_suffix)
@@ -486,7 +521,7 @@ module ActiveSupport #:nodoc:
if file_path
expanded = File.expand_path(file_path)
- expanded.sub!(/\.rb\z/, '')
+ expanded.sub!(/\.rb\z/, ''.freeze)
if loading.include?(expanded)
raise "Circular dependency detected while autoloading constant #{qualified_name}"
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
new file mode 100644
index 0000000000..fbeb904684
--- /dev/null
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -0,0 +1,47 @@
+require 'active_support/concurrency/share_lock'
+
+module ActiveSupport #:nodoc:
+ module Dependencies #:nodoc:
+ class Interlock
+ def initialize # :nodoc:
+ @lock = ActiveSupport::Concurrency::ShareLock.new
+ end
+
+ def loading
+ @lock.exclusive(purpose: :load, compatible: [:load]) do
+ yield
+ end
+ end
+
+ def unloading
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do
+ yield
+ end
+ end
+
+ # Attempt to obtain an "unloading" (exclusive) lock. If possible,
+ # execute the supplied block while holding the lock. If there is
+ # concurrent activity, return immediately (without executing the
+ # block) instead of waiting.
+ def attempt_unloading
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do
+ yield
+ end
+ end
+
+ def start_running
+ @lock.start_sharing
+ end
+
+ def done_running
+ @lock.stop_sharing
+ end
+
+ def running
+ @lock.sharing do
+ yield
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 4f71f13971..c5d35b84f0 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -188,7 +188,7 @@ module ActiveSupport
# dup[:a][:c] # => "c"
def dup
self.class.new(self).tap do |new_hash|
- new_hash.default = default
+ set_defaults(new_hash)
end
end
@@ -238,16 +238,20 @@ module ActiveSupport
def to_options!; self end
def select(*args, &block)
+ return to_enum(:select) unless block_given?
dup.tap { |hash| hash.select!(*args, &block) }
end
def reject(*args, &block)
+ return to_enum(:reject) unless block_given?
dup.tap { |hash| hash.reject!(*args, &block) }
end
# Convert to a regular hash with string keys.
def to_hash
- _new_hash = Hash.new(default)
+ _new_hash = Hash.new
+ set_defaults(_new_hash)
+
each do |key, value|
_new_hash[key] = convert_value(value, for: :to_hash)
end
@@ -275,6 +279,14 @@ module ActiveSupport
value
end
end
+
+ def set_defaults(target)
+ if default_proc
+ target.default_proc = default_proc.dup
+ else
+ target.default = default
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 486838bd15..8ac1820776 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -27,6 +27,37 @@ module ActiveSupport
class Inflections
@__instance__ = ThreadSafe::Cache.new
+ class Uncountables < Array
+ def initialize
+ @regex_array = []
+ super
+ end
+
+ def delete(entry)
+ super entry
+ @regex_array.delete(to_regex(entry))
+ end
+
+ def <<(*word)
+ add(word)
+ end
+
+ def add(words)
+ self.concat(words.flatten.map(&:downcase))
+ @regex_array += self.map {|word| to_regex(word) }
+ self
+ end
+
+ def uncountable?(str)
+ @regex_array.detect {|regex| regex.match(str) }
+ end
+
+ private
+ def to_regex(string)
+ /\b#{::Regexp.escape(string)}\Z/i
+ end
+ end
+
def self.instance(locale = :en)
@__instance__[locale] ||= new
end
@@ -34,7 +65,7 @@ module ActiveSupport
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
def initialize
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
+ @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
end
# Private, for the test suite.
@@ -160,7 +191,7 @@ module ActiveSupport
# uncountable 'money', 'information'
# uncountable %w( money information rice )
def uncountable(*words)
- @uncountables += words.flatten.map(&:downcase)
+ @uncountables.add(words)
end
# Specifies a humanized form of a string by a regular expression rule or
@@ -185,7 +216,7 @@ module ActiveSupport
def clear(scope = :all)
case scope
when :all
- @plurals, @singulars, @uncountables, @humans = [], [], [], []
+ @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
else
instance_variable_set "@#{scope}", []
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index be369d21c6..899ba70af6 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -91,10 +91,10 @@ module ActiveSupport
def underscore(camel_cased_word)
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
- word.tr!("-", "_")
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
+ word.tr!("-".freeze, "_".freeze)
word.downcase!
word
end
@@ -127,9 +127,9 @@ module ActiveSupport
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
- result.sub!(/\A_+/, '')
- result.sub!(/_id\z/, '')
- result.tr!('_', ' ')
+ result.sub!(/\A_+/, ''.freeze)
+ result.sub!(/_id\z/, ''.freeze)
+ result.tr!('_'.freeze, ' '.freeze)
result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
@@ -178,14 +178,14 @@ module ActiveSupport
# classify('calculus') # => "Calculu"
def classify(table_name)
# strip out any leading schema name
- camelize(singularize(table_name.to_s.sub(/.*\./, '')))
+ camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
end
# Replaces underscores with dashes in the string.
#
# dasherize('puni_puni') # => "puni-puni"
def dasherize(underscored_word)
- underscored_word.tr('_', '-')
+ underscored_word.tr('_'.freeze, '-'.freeze)
end
# Removes the module part from the expression in the string.
@@ -248,7 +248,7 @@ module ActiveSupport
# NameError is raised when the name is not in CamelCase or the constant is
# unknown.
def constantize(camel_cased_word)
- names = camel_cased_word.split('::')
+ names = camel_cased_word.split('::'.freeze)
# Trigger a built-in NameError exception including the ill-formed constant in the message.
Object.const_get(camel_cased_word) if names.empty?
@@ -354,7 +354,7 @@ module ActiveSupport
# const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
# const_regexp("::") # => "::"
def const_regexp(camel_cased_word) #:nodoc:
- parts = camel_cased_word.split("::")
+ parts = camel_cased_word.split("::".freeze)
return Regexp.escape(camel_cased_word) if parts.blank?
@@ -372,7 +372,7 @@ module ActiveSupport
def apply_inflections(word, rules)
result = word.to_s.dup
- if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
+ if word.empty? || inflections.uncountables.uncountable?(result)
result
else
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index edea142e82..7b28eeb6e2 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -58,7 +58,7 @@ module ActiveSupport
# I18n.locale = :de
# transliterate('Jürgen')
# # => "Juergen"
- def transliterate(string, replacement = "?")
+ def transliterate(string, replacement = "?".freeze)
I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
:replacement => replacement)
@@ -75,13 +75,21 @@ module ActiveSupport
# Turn unwanted chars into the separator
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep)
unless sep.nil? || sep.empty?
- re_sep = Regexp.escape(sep)
+ if sep == "-".freeze
+ re_duplicate_seperator = /-{2,}/
+ re_leading_trailing_separator = /^-|-$/i
+ else
+ re_sep = Regexp.escape(sep)
+ re_duplicate_seperator = /#{re_sep}{2,}/
+ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
+ end
# No more than one of the separator in a row.
- parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
+ parameterized_string.gsub!(re_duplicate_seperator, sep)
# Remove leading/trailing separator.
- parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
+ parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze)
end
- parameterized_string.downcase
+ parameterized_string.downcase!
+ parameterized_string
end
end
end
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 75f353f62c..70ac4a4d5c 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -33,7 +33,7 @@ module ActiveSupport
# you can collect them doing @logger.logged(level), where level is the level
# used in logging, like info, debug, warn and so on.
module TestHelper
- def setup
+ def setup # :nodoc:
@logger = MockLogger.new
@notifier = ActiveSupport::Notifications::Fanout.new
@@ -44,7 +44,7 @@ module ActiveSupport
ActiveSupport::Notifications.notifier = @notifier
end
- def teardown
+ def teardown # :nodoc:
set_logger(nil)
ActiveSupport::Notifications.notifier = @old_notifier
end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 92ab6fe648..c82a13511e 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -82,7 +82,7 @@ module ActiveSupport
def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
+ encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)}
cipher.decrypt
cipher.key = @secret
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index b2a4404968..64c5232cf4 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -46,7 +46,7 @@ module ActiveSupport
def valid_message?(signed_message)
return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
- data, digest = signed_message.split("--")
+ data, digest = signed_message.split("--".freeze)
data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
end
@@ -74,7 +74,7 @@ module ActiveSupport
def verified(signed_message)
if valid_message?(signed_message)
begin
- data = signed_message.split("--")[0]
+ data = signed_message.split("--".freeze)[0]
@serializer.load(decode(data))
rescue ArgumentError => argument_error
return if argument_error.message =~ %r{invalid base64}
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 35efebc65f..4de7fa4e4b 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -58,7 +58,7 @@ module ActiveSupport
# Returns a regular expression pattern that matches the passed Unicode
# codepoints.
def self.codepoints_to_pattern(array_of_codepoints) #:nodoc:
- array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|')
+ array_of_codepoints.collect{ |e| [e].pack 'U*'.freeze }.join('|'.freeze)
end
TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u
LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u
@@ -273,7 +273,7 @@ module ActiveSupport
compose(reorder_characters(decompose(:compatibility, codepoints)))
else
raise ArgumentError, "#{form} is not a valid normalization variant", caller
- end.pack('U*')
+ end.pack('U*'.freeze)
end
def downcase(string)
diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb
new file mode 100644
index 0000000000..e3cbe40308
--- /dev/null
+++ b/activesupport/lib/active_support/testing/method_call_assertions.rb
@@ -0,0 +1,35 @@
+module ActiveSupport
+ module Testing
+ module MethodCallAssertions # :nodoc:
+ private
+ def assert_called(object, method_name, message = nil, times: 1)
+ times_called = 0
+
+ object.stub(method_name, -> { times_called += 1 }) { yield }
+
+ error = "Expected #{method_name} to be called #{times} times, " \
+ "but was called #{times_called} times"
+ error = "#{message}.\n#{error}" if message
+ assert_equal times, times_called, error
+ end
+
+ def assert_called_with(object, method_name, args = [], returns: nil)
+ mock = Minitest::Mock.new
+
+ if args.all? { |arg| arg.is_a?(Array) }
+ args.each { |arg| mock.expect(:call, returns, arg) }
+ else
+ mock.expect(:call, returns, args)
+ end
+
+ object.stub(method_name, mock) { yield }
+
+ mock.verify
+ end
+
+ def assert_not_called(object, method_name, message = nil, &block)
+ assert_called(object, method_name, message, times: 0, &block)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 412c72d27c..f8f1b9ac2c 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -247,7 +247,7 @@ module ActiveSupport
end
def eql?(other)
- utc.eql?(other)
+ other.eql?(utc)
end
def hash
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 9b88a2dcc1..65a8edbabb 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -15,6 +15,7 @@ silence_warnings do
end
require 'active_support/testing/autorun'
+require 'active_support/testing/method_call_assertions'
ENV['NO_RELOAD'] = '1'
require 'active_support'
@@ -38,4 +39,7 @@ def jruby_skip(message = '')
end
require 'minitest/mock'
-require 'mocha/setup' # FIXME: stop using mocha
+
+class ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 69aea6213f..74ceff44f9 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -133,37 +133,42 @@ class CacheStoreSettingTest < ActiveSupport::TestCase
end
def test_mem_cache_fragment_cache_store
- Dalli::Client.expects(:new).with(%w[localhost], {})
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_called_with(Dalli::Client, :new, [%w[localhost], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
end
def test_mem_cache_fragment_cache_store_with_given_mem_cache
mem_cache = Dalli::Client.new
- Dalli::Client.expects(:new).never
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_not_called(Dalli::Client, :new) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
end
def test_mem_cache_fragment_cache_store_with_not_dalli_client
- Dalli::Client.expects(:new).never
- memcache = Object.new
- assert_raises(ArgumentError) do
- ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ assert_not_called(Dalli::Client, :new) do
+ memcache = Object.new
+ assert_raises(ArgumentError) do
+ ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ end
end
end
def test_mem_cache_fragment_cache_store_with_multiple_servers
- Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], {})
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1'
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1'
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
end
def test_mem_cache_fragment_cache_store_with_options
- Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], { :timeout => 10 })
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- assert_equal 'foo', store.options[:namespace]
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { :timeout => 10 }]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_equal 'foo', store.options[:namespace]
+ end
end
def test_object_assigned_fragment_cache_store
@@ -224,13 +229,15 @@ module CacheStoreBehavior
def test_fetch_without_cache_miss
@cache.write('foo', 'bar')
- @cache.expects(:write).never
- assert_equal 'bar', @cache.fetch('foo') { 'baz' }
+ assert_not_called(@cache, :write) do
+ assert_equal 'bar', @cache.fetch('foo') { 'baz' }
+ end
end
def test_fetch_with_cache_miss
- @cache.expects(:write).with('foo', 'baz', @cache.options)
- assert_equal 'baz', @cache.fetch('foo') { 'baz' }
+ assert_called_with(@cache, :write, ['foo', 'baz', @cache.options]) do
+ assert_equal 'baz', @cache.fetch('foo') { 'baz' }
+ end
end
def test_fetch_with_cache_miss_passes_key_to_block
@@ -245,15 +252,18 @@ module CacheStoreBehavior
def test_fetch_with_forced_cache_miss
@cache.write('foo', 'bar')
- @cache.expects(:read).never
- @cache.expects(:write).with('foo', 'bar', @cache.options.merge(:force => true))
- @cache.fetch('foo', :force => true) { 'bar' }
+ assert_not_called(@cache, :read) do
+ assert_called_with(@cache, :write, ['foo', 'bar', @cache.options.merge(:force => true)]) do
+ @cache.fetch('foo', :force => true) { 'bar' }
+ end
+ end
end
def test_fetch_with_cached_nil
@cache.write('foo', nil)
- @cache.expects(:write).never
- assert_nil @cache.fetch('foo') { 'baz' }
+ assert_not_called(@cache, :write) do
+ assert_nil @cache.fetch('foo') { 'baz' }
+ end
end
def test_should_read_and_write_hash
@@ -304,8 +314,9 @@ module CacheStoreBehavior
end
def test_multi_with_objects
- foo = stub(:title => 'FOO!', :cache_key => 'foo')
- bar = stub(:cache_key => 'bar')
+ cache_struct = Struct.new(:cache_key, :title)
+ foo = cache_struct.new('foo', 'FOO!')
+ bar = cache_struct.new('bar')
@cache.write('bar', 'BAM!')
@@ -774,9 +785,10 @@ class FileStoreTest < ActiveSupport::TestCase
end
def test_log_exception_when_cache_read_fails
- File.expects(:exist?).raises(StandardError, "failed")
- @cache.send(:read_entry, "winston", {})
- assert @buffer.string.present?
+ File.stub(:exist?, -> { raise StandardError.new("failed") }) do
+ @cache.send(:read_entry, "winston", {})
+ assert @buffer.string.present?
+ end
end
def test_cleanup_removes_all_expired_entries
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index e10bee5e00..4624338df1 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -524,6 +524,10 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_indifferent_reverse_merging
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.reverse_merge! key: :new_value
+ assert_equal :old_value, hash[:key]
+
hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value')
hash.reverse_merge!(:some => 'noclobber', :another => 'clobber')
assert_equal 'value', hash[:some]
@@ -547,6 +551,11 @@ class HashExtTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
end
+ def test_indifferent_select_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
+ assert_instance_of Enumerator, enum
+ end
+
def test_indifferent_select_returns_a_hash_when_unchanged
hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true}
@@ -568,6 +577,11 @@ class HashExtTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
end
+ def test_indifferent_reject_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
+ assert_instance_of Enumerator, enum
+ end
+
def test_indifferent_reject_bang
indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
indifferent_strings.reject! {|k,v| v != 1}
@@ -961,10 +975,11 @@ class HashExtTest < ActiveSupport::TestCase
assert_raise(RuntimeError) { original.except!(:a) }
end
- def test_except_with_mocha_expectation_on_original
+ def test_except_does_not_delete_values_in_original
original = { :a => 'x', :b => 'y' }
- original.expects(:delete).never
- original.except(:a)
+ assert_not_called(original, :delete) do
+ original.except(:a)
+ end
end
def test_compact
@@ -998,6 +1013,37 @@ class HashExtTest < ActiveSupport::TestCase
assert hash.key?('a')
assert_equal 1, hash[:a]
end
+
+ def test_dup_with_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, v| raise "walrus" }
+ assert_nothing_raised { hash.dup }
+ end
+
+ def test_dup_with_default_proc_sets_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| k + 1 }
+ new_hash = hash.dup
+
+ assert_equal 3, new_hash[2]
+
+ new_hash.default = 2
+ assert_equal 2, new_hash[:non_existant]
+ end
+
+ def test_to_hash_with_raising_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| raise "walrus" }
+
+ assert_nothing_raised { hash.to_hash }
+ end
+
+ def test_new_from_hash_copying_default_should_not_raise_when_default_proc_does
+ hash = Hash.new
+ hash.default_proc = proc { |h, k| raise "walrus" }
+
+ assert_nothing_raised { HashWithIndifferentAccess.new_from_hash_copying_default(hash) }
+ end
end
class IWriteMyOwnXML
diff --git a/activesupport/test/core_ext/object/json_gem_encoding_test.rb b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
new file mode 100644
index 0000000000..02ab17fb64
--- /dev/null
+++ b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
@@ -0,0 +1,66 @@
+require 'abstract_unit'
+require 'json'
+require 'json/encoding_test_cases'
+
+# These test cases were added to test that we do not interfere with json gem's
+# output when the AS encoder is loaded, primarily for problems reported in
+# #20775. They need to be executed in isolation to reproduce the scenario
+# correctly, because other test cases might have already loaded additional
+# dependencies.
+
+# The AS::JSON encoder requires the BigDecimal core_ext, which, unfortunately,
+# changes the BigDecimal#to_s output, and consequently the JSON gem output. So
+# we need to require this unfront to ensure we don't get a false failure, but
+# ideally we should just fix the BigDecimal core_ext to not change to_s without
+# arguments.
+require 'active_support/core_ext/big_decimal'
+
+class JsonGemEncodingTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ JSONTest::EncodingTestCases.constants.each_with_index do |name|
+ JSONTest::EncodingTestCases.const_get(name).each_with_index do |(subject, _), i|
+ test("#{name[0..-6].underscore} #{i}") do
+ assert_same_with_or_without_active_support(subject)
+ end
+ end
+ end
+
+ class CustomToJson
+ def to_json(*)
+ '"custom"'
+ end
+ end
+
+ test "custom to_json" do
+ assert_same_with_or_without_active_support(CustomToJson.new)
+ end
+
+ private
+ def require_or_skip(file)
+ require(file) || skip("'#{file}' was already loaded")
+ end
+
+ def assert_same_with_or_without_active_support(subject)
+ begin
+ expected = JSON.generate(subject, quirks_mode: true)
+ rescue JSON::GeneratorError => e
+ exception = e
+ end
+
+ require_or_skip 'active_support/core_ext/object/json'
+
+ if exception
+ assert_raises_with_message JSON::GeneratorError, e.message do
+ JSON.generate(subject, quirks_mode: true)
+ end
+ else
+ assert_equal expected, JSON.generate(subject, quirks_mode: true)
+ end
+ end
+
+ def assert_raises_with_message(exception_class, message, &block)
+ err = assert_raises(exception_class) { block.call }
+ assert_match message, err.message
+ end
+end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 5b48bf328c..ccb7f02331 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -2,6 +2,7 @@ require 'abstract_unit'
require 'active_support/time'
require 'time_zone_test_helpers'
require 'active_support/core_ext/string/strip'
+require 'yaml'
class TimeWithZoneTest < ActiveSupport::TestCase
include TimeZoneTestHelpers
@@ -252,10 +253,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_eql?
+ assert_equal true, @twz.eql?(@twz.dup)
assert_equal true, @twz.eql?(Time.utc(2000))
assert_equal true, @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
assert_equal false, @twz.eql?( Time.utc(2000, 1, 1, 0, 0, 1) )
assert_equal false, @twz.eql?( DateTime.civil(1999, 12, 31, 23, 59, 59) )
+
+ other_twz = ActiveSupport::TimeWithZone.new(DateTime.now.utc, @time_zone)
+ assert_equal true, other_twz.eql?(other_twz.dup)
end
def test_hash
@@ -482,22 +487,24 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_method_missing_with_non_time_return_value
- @twz.time.expects(:foo).returns('bar')
+ time = @twz.time
+ def time.foo; 'bar'; end
assert_equal 'bar', @twz.foo
end
def test_date_part_value_methods
twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone)
- twz.expects(:method_missing).never
- assert_equal 1999, twz.year
- assert_equal 12, twz.month
- assert_equal 31, twz.day
- assert_equal 14, twz.hour
- assert_equal 18, twz.min
- assert_equal 17, twz.sec
- assert_equal 500, twz.usec
- assert_equal 5, twz.wday
- assert_equal 365, twz.yday
+ assert_not_called(twz, :method_missing) do
+ assert_equal 1999, twz.year
+ assert_equal 12, twz.month
+ assert_equal 31, twz.day
+ assert_equal 14, twz.hour
+ assert_equal 18, twz.min
+ assert_equal 17, twz.sec
+ assert_equal 500, twz.usec
+ assert_equal 5, twz.wday
+ assert_equal 365, twz.yday
+ end
end
def test_usec_returns_0_when_datetime_is_wrapped
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 6dce7560dd..757e600646 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -721,12 +721,13 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_unloadable_constants_should_receive_callback
- Object.const_set :C, Class.new
+ Object.const_set :C, Class.new { def self.before_remove_const; end }
C.unloadable
- C.expects(:before_remove_const).once
- assert C.respond_to?(:before_remove_const)
- ActiveSupport::Dependencies.clear
- assert !defined?(C)
+ assert_called(C, :before_remove_const, times: 1) do
+ assert C.respond_to?(:before_remove_const)
+ ActiveSupport::Dependencies.clear
+ assert !defined?(C)
+ end
ensure
remove_constants(:C)
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 20bd8ee5dd..7e8844b301 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -256,17 +256,17 @@ class DeprecationTest < ActiveSupport::TestCase
end
def test_deprecate_with_custom_deprecator
- custom_deprecator = mock('Deprecator') do
- expects(:deprecation_warning)
- end
+ custom_deprecator = Struct.new(:deprecation_warning).new
- klass = Class.new do
- def method
+ assert_called_with(custom_deprecator, :deprecation_warning, [:method, nil]) do
+ klass = Class.new do
+ def method
+ end
+ deprecate :method, deprecator: custom_deprecator
end
- deprecate :method, deprecator: custom_deprecator
- end
- klass.new.method
+ klass.new.method
+ end
end
def test_deprecated_constant_with_deprecator_given
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
deleted file mode 100644
index 1facd691fa..0000000000
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require 'abstract_unit'
-require 'active_support/hash_with_indifferent_access'
-
-class HashWithIndifferentAccessTest < ActiveSupport::TestCase
- def test_reverse_merge
- hash = HashWithIndifferentAccess.new key: :old_value
- hash.reverse_merge! key: :new_value
- assert_equal :old_value, hash[:key]
- end
-
-end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index ee47b97a8a..9f4b62fd8b 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -4,122 +4,24 @@ require 'active_support/core_ext/string/inflections'
require 'active_support/json'
require 'active_support/time'
require 'time_zone_test_helpers'
+require 'json/encoding_test_cases'
class TestJSONEncoding < ActiveSupport::TestCase
include TimeZoneTestHelpers
- class Foo
- def initialize(a, b)
- @a, @b = a, b
- end
- end
-
- class Hashlike
- def to_hash
- { :foo => "hello", :bar => "world" }
- end
- end
-
- class Custom
- def initialize(serialized)
- @serialized = serialized
- end
-
- def as_json(options = nil)
- @serialized
- end
- end
-
- class CustomWithOptions
- attr_accessor :foo, :bar
-
- def as_json(options={})
- options[:only] = %w(foo bar)
- super(options)
- end
- end
-
- class OptionsTest
- def as_json(options = :default)
- options
- end
- end
-
- class HashWithAsJson < Hash
- attr_accessor :as_json_called
-
- def initialize(*)
- super
- end
-
- def as_json(options={})
- @as_json_called = true
- super
- end
- end
-
- TrueTests = [[ true, %(true) ]]
- FalseTests = [[ false, %(false) ]]
- NilTests = [[ nil, %(null) ]]
- NumericTests = [[ 1, %(1) ],
- [ 2.5, %(2.5) ],
- [ 0.0/0.0, %(null) ],
- [ 1.0/0.0, %(null) ],
- [ -1.0/0.0, %(null) ],
- [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
- [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
-
- StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
- [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
- [ 'http://test.host/posts/1', %("http://test.host/posts/1")],
- [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
- %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
-
- ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
- [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
-
- RangeTests = [[ 1..2, %("1..2")],
- [ 1...2, %("1...2")],
- [ 1.5..2.5, %("1.5..2.5")]]
-
- SymbolTests = [[ :a, %("a") ],
- [ :this, %("this") ],
- [ :"a b", %("a b") ]]
-
- ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
- HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
- CustomTests = [[ Custom.new("custom"), '"custom"' ],
- [ Custom.new(nil), 'null' ],
- [ Custom.new(:a), '"a"' ],
- [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
- [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
- [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
- [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
-
- RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
-
- DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
- TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
- DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
-
- StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
- StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
- StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
- StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
-
def sorted_json(json)
return json unless json =~ /^\{.*\}$/
'{' + json[1..-2].split(',').sort.join(',') + '}'
end
- constants.grep(/Tests$/).each do |class_tests|
+ JSONTest::EncodingTestCases.constants.each do |class_tests|
define_method("test_#{class_tests[0..-6].underscore}") do
begin
prev = ActiveSupport.use_standard_json_time_format
ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
- self.class.const_get(class_tests).each do |pair|
+ JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
end
ensure
@@ -224,7 +126,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_hash_like_with_options
- h = Hashlike.new
+ h = JSONTest::Hashlike.new
json = h.to_json :only => [:foo]
assert_equal({"foo"=>"hello"}, JSON.parse(json))
@@ -345,6 +247,15 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
+ class CustomWithOptions
+ attr_accessor :foo, :bar
+
+ def as_json(options={})
+ options[:only] = %w(foo bar)
+ super(options)
+ end
+ end
+
def test_hash_to_json_should_not_keep_options_around
f = CustomWithOptions.new
f.foo = "hello"
@@ -365,6 +276,12 @@ class TestJSONEncoding < ActiveSupport::TestCase
{"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
end
+ class OptionsTest
+ def as_json(options = :default)
+ options
+ end
+ end
+
def test_hash_as_json_without_options
json = { foo: OptionsTest.new }.as_json
assert_equal({"foo" => :default}, json)
@@ -412,6 +329,19 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal false, false.as_json
end
+ class HashWithAsJson < Hash
+ attr_accessor :as_json_called
+
+ def initialize(*)
+ super
+ end
+
+ def as_json(options={})
+ @as_json_called = true
+ super
+ end
+ end
+
def test_json_gem_dump_by_passing_active_support_encoder
h = HashWithAsJson.new
h[:foo] = "hello"
diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb
new file mode 100644
index 0000000000..0159ba8606
--- /dev/null
+++ b/activesupport/test/json/encoding_test_cases.rb
@@ -0,0 +1,88 @@
+require 'bigdecimal'
+
+module JSONTest
+ class Foo
+ def initialize(a, b)
+ @a, @b = a, b
+ end
+ end
+
+ class Hashlike
+ def to_hash
+ { :foo => "hello", :bar => "world" }
+ end
+ end
+
+ class Custom
+ def initialize(serialized)
+ @serialized = serialized
+ end
+
+ def as_json(options = nil)
+ @serialized
+ end
+ end
+
+ class MyStruct < Struct.new(:name, :value)
+ def initialize(*)
+ @unused = "unused instance variable"
+ super
+ end
+ end
+
+ module EncodingTestCases
+ TrueTests = [[ true, %(true) ]]
+ FalseTests = [[ false, %(false) ]]
+ NilTests = [[ nil, %(null) ]]
+ NumericTests = [[ 1, %(1) ],
+ [ 2.5, %(2.5) ],
+ [ 0.0/0.0, %(null) ],
+ [ 1.0/0.0, %(null) ],
+ [ -1.0/0.0, %(null) ],
+ [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
+ [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
+
+ StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
+ [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
+ [ 'http://test.host/posts/1', %("http://test.host/posts/1")],
+ [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
+ %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
+
+ ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
+ [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
+
+ HashTests = [[ {foo: "bar"}, %({\"foo\":\"bar\"}) ],
+ [ {1 => 1, 2 => 'a', 3 => :b, 4 => nil, 5 => false}, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
+
+ RangeTests = [[ 1..2, %("1..2")],
+ [ 1...2, %("1...2")],
+ [ 1.5..2.5, %("1.5..2.5")]]
+
+ SymbolTests = [[ :a, %("a") ],
+ [ :this, %("this") ],
+ [ :"a b", %("a b") ]]
+
+ ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
+ HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
+ StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
+ [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
+ CustomTests = [[ Custom.new("custom"), '"custom"' ],
+ [ Custom.new(nil), 'null' ],
+ [ Custom.new(:a), '"a"' ],
+ [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
+ [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
+
+ RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
+
+ DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
+ TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
+ DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
+
+ StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
+ StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
+ StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
+ StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
+ end
+end
diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb
index 2a0e8d20ed..998a6887c5 100644
--- a/activesupport/test/log_subscriber_test.rb
+++ b/activesupport/test/log_subscriber_test.rb
@@ -82,10 +82,11 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase
def test_does_not_send_the_event_if_logger_is_nil
ActiveSupport::LogSubscriber.logger = nil
- @log_subscriber.expects(:some_event).never
- ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
- instrument "some_event.my_log_subscriber"
- wait
+ assert_not_called(@log_subscriber, :some_event) do
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
+ instrument "some_event.my_log_subscriber"
+ wait
+ end
end
def test_does_not_fail_with_non_namespaced_events
diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb
index 52ae266f01..dd33641ec2 100644
--- a/activesupport/test/multibyte_unicode_database_test.rb
+++ b/activesupport/test/multibyte_unicode_database_test.rb
@@ -11,8 +11,9 @@ class MultibyteUnicodeDatabaseTest < ActiveSupport::TestCase
UnicodeDatabase::ATTRIBUTES.each do |attribute|
define_method "test_lazy_loading_on_attribute_access_of_#{attribute}" do
- @ucd.expects(:load)
- @ucd.send(attribute)
+ assert_called(@ucd, :load) do
+ @ucd.send(attribute)
+ end
end
end
diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb
new file mode 100644
index 0000000000..ad41db608b
--- /dev/null
+++ b/activesupport/test/share_lock_test.rb
@@ -0,0 +1,333 @@
+require 'abstract_unit'
+require 'concurrent/atomics'
+require 'active_support/concurrency/share_lock'
+
+class ShareLockTest < ActiveSupport::TestCase
+ def setup
+ @lock = ActiveSupport::Concurrency::ShareLock.new
+ end
+
+ def test_reentrancy
+ thread = Thread.new do
+ @lock.sharing { @lock.sharing {} }
+ @lock.exclusive { @lock.exclusive {} }
+ end
+ assert_threads_not_stuck thread
+ end
+
+ def test_sharing_doesnt_block
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_latch|
+ assert_threads_not_stuck(Thread.new {@lock.sharing {} })
+ end
+ end
+
+ def test_sharing_blocks_exclusive
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ @lock.exclusive(no_wait: true) { flunk } # polling should fail
+ exclusive_thread = Thread.new { @lock.exclusive {} }
+ assert_threads_stuck_but_releasable_by_latch exclusive_thread, sharing_thread_release_latch
+ end
+ end
+
+ def test_exclusive_blocks_sharing
+ with_thread_waiting_in_lock_section(:exclusive) do |exclusive_thread_release_latch|
+ sharing_thread = Thread.new { @lock.sharing {} }
+ assert_threads_stuck_but_releasable_by_latch sharing_thread, exclusive_thread_release_latch
+ end
+ end
+
+ def test_multiple_exlusives_are_able_to_progress
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ exclusive_threads = (1..2).map do
+ Thread.new do
+ @lock.exclusive {}
+ end
+ end
+
+ assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch
+ end
+ end
+
+ def test_sharing_is_upgradeable_to_exclusive
+ upgrading_thread = Thread.new do
+ @lock.sharing do
+ @lock.exclusive {}
+ end
+ end
+ assert_threads_not_stuck upgrading_thread
+ end
+
+ def test_exclusive_upgrade_waits_for_other_sharers_to_leave
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ in_sharing = Concurrent::CountDownLatch.new
+
+ upgrading_thread = Thread.new do
+ @lock.sharing do
+ in_sharing.count_down
+ @lock.exclusive {}
+ end
+ end
+
+ in_sharing.wait
+ assert_threads_stuck_but_releasable_by_latch upgrading_thread, sharing_thread_release_latch
+ end
+ end
+
+ def test_exclusive_matching_purpose
+ [true, false].each do |use_upgrading|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ exclusive_threads = (1..2).map do
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: :load, compatible: [:load, :unload]) {}
+ end
+ end
+ end
+
+ assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch
+ end
+ end
+ end
+
+ def test_killed_thread_loses_lock
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ thread = Thread.new do
+ @lock.sharing do
+ @lock.exclusive {}
+ end
+ end
+
+ assert_threads_stuck thread
+ thread.kill
+
+ sharing_thread_release_latch.count_down
+
+ thread = Thread.new do
+ @lock.exclusive {}
+ end
+
+ assert_threads_not_stuck thread
+ end
+ end
+
+ def test_exclusive_conflicting_purpose
+ [true, false].each do |use_upgrading|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ begin
+ conflicting_exclusive_threads = [
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: :red, compatible: [:green, :purple]) {}
+ end
+ end,
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: :blue, compatible: [:green]) {}
+ end
+ end
+ ]
+
+ assert_threads_stuck conflicting_exclusive_threads # wait for threads to get into their respective `exclusive {}` blocks
+
+ # This thread will be stuck as long as any other thread is in
+ # a sharing block. While it's blocked, it holds no lock, so it
+ # doesn't interfere with any other attempts.
+ no_purpose_thread = Thread.new do
+ @lock.exclusive {}
+ end
+ assert_threads_stuck no_purpose_thread
+
+ # This thread is compatible with both of the "primary"
+ # attempts above. It's initially stuck on the outer share
+ # lock, but as soon as that's released, it can run --
+ # regardless of whether those threads hold share locks.
+ compatible_thread = Thread.new do
+ @lock.exclusive(purpose: :green, compatible: []) {}
+ end
+ assert_threads_stuck compatible_thread
+
+ assert_threads_stuck conflicting_exclusive_threads
+
+ sharing_thread_release_latch.count_down
+
+ assert_threads_not_stuck compatible_thread # compatible thread is now able to squeak through
+
+ if use_upgrading
+ # The "primary" threads both each hold a share lock, and are
+ # mutually incompatible; they're still stuck.
+ assert_threads_stuck conflicting_exclusive_threads
+
+ # The thread without a specified purpose is also stuck; it's
+ # not compatible with anything.
+ assert_threads_stuck no_purpose_thread
+ else
+ # As the primaries didn't hold a share lock, as soon as the
+ # outer one was released, all the exclusive locks are free
+ # to be acquired in turn.
+
+ assert_threads_not_stuck conflicting_exclusive_threads
+ assert_threads_not_stuck no_purpose_thread
+ end
+ ensure
+ conflicting_exclusive_threads.each(&:kill)
+ no_purpose_thread.kill
+ end
+ end
+ end
+ end
+
+ def test_exclusive_ordering
+ scratch_pad = []
+ scratch_pad_mutex = Mutex.new
+
+ load_params = [:load, [:load]]
+ unload_params = [:unload, [:unload, :load]]
+
+ [load_params, load_params, unload_params, unload_params].permutation do |thread_params|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ threads = thread_params.map do |purpose, compatible|
+ Thread.new do
+ @lock.sharing do
+ @lock.exclusive(purpose: purpose, compatible: compatible) do
+ scratch_pad_mutex.synchronize { scratch_pad << purpose }
+ end
+ end
+ end
+ end
+
+ sleep(0.01)
+ scratch_pad_mutex.synchronize { assert_empty scratch_pad }
+
+ sharing_thread_release_latch.count_down
+
+ assert_threads_not_stuck threads
+ scratch_pad_mutex.synchronize do
+ assert_equal [:load, :load, :unload, :unload], scratch_pad
+ scratch_pad.clear
+ end
+ end
+ end
+ end
+
+ def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads
+ scratch_pad = []
+ scratch_pad_mutex = Mutex.new
+
+ upgrading_load_params = [:load, [:load], true]
+ non_upgrading_unload_params = [:unload, [:load, :unload], false]
+
+ [upgrading_load_params, non_upgrading_unload_params].permutation do |thread_params|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ threads = thread_params.map do |purpose, compatible, use_upgrading|
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: purpose, compatible: compatible) do
+ scratch_pad_mutex.synchronize { scratch_pad << purpose }
+ end
+ end
+ end
+ end
+
+ assert_threads_stuck threads
+ scratch_pad_mutex.synchronize { assert_empty scratch_pad }
+
+ sharing_thread_release_latch.count_down
+
+ assert_threads_not_stuck threads
+ scratch_pad_mutex.synchronize do
+ assert_equal [:load, :unload], scratch_pad
+ scratch_pad.clear
+ end
+ end
+ end
+ end
+
+ private
+
+ module CustomAssertions
+ SUFFICIENT_TIMEOUT = 0.2
+
+ private
+
+ def assert_threads_stuck_but_releasable_by_latch(threads, latch)
+ assert_threads_stuck threads
+ latch.count_down
+ assert_threads_not_stuck threads
+ end
+
+ def assert_threads_stuck(threads)
+ sleep(SUFFICIENT_TIMEOUT) # give threads time to do their business
+ assert(Array(threads).all? { |t| t.join(0.001).nil? })
+ end
+
+ def assert_threads_not_stuck(threads)
+ assert(Array(threads).all? { |t| t.join(SUFFICIENT_TIMEOUT) })
+ end
+ end
+
+ class CustomAssertionsTest < ActiveSupport::TestCase
+ include CustomAssertions
+
+ def setup
+ @latch = Concurrent::CountDownLatch.new
+ @thread = Thread.new { @latch.wait }
+ end
+
+ def teardown
+ @latch.count_down
+ @thread.join
+ end
+
+ def test_happy_path
+ assert_threads_stuck_but_releasable_by_latch @thread, @latch
+ end
+
+ def test_detects_stuck_thread
+ assert_raises(Minitest::Assertion) do
+ assert_threads_not_stuck @thread
+ end
+ end
+
+ def test_detects_free_thread
+ @latch.count_down
+ assert_raises(Minitest::Assertion) do
+ assert_threads_stuck @thread
+ end
+ end
+
+ def test_detects_already_released
+ @latch.count_down
+ assert_raises(Minitest::Assertion) do
+ assert_threads_stuck_but_releasable_by_latch @thread, @latch
+ end
+ end
+
+ def test_detects_remains_latched
+ another_latch = Concurrent::CountDownLatch.new
+ assert_raises(Minitest::Assertion) do
+ assert_threads_stuck_but_releasable_by_latch @thread, another_latch
+ end
+ end
+ end
+
+ include CustomAssertions
+
+ def with_thread_waiting_in_lock_section(lock_section)
+ in_section = Concurrent::CountDownLatch.new
+ section_release = Concurrent::CountDownLatch.new
+
+ stuck_thread = Thread.new do
+ @lock.send(lock_section) do
+ in_section.count_down
+ section_release.wait
+ end
+ end
+
+ in_section.wait
+
+ yield section_release
+ ensure
+ section_release.count_down
+ stuck_thread.join # clean up
+ end
+end
diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb
new file mode 100644
index 0000000000..a9908aea0d
--- /dev/null
+++ b/activesupport/test/testing/method_call_assertions_test.rb
@@ -0,0 +1,98 @@
+require 'abstract_unit'
+require 'active_support/testing/method_call_assertions'
+
+class MethodCallAssertionsTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+
+ class Level
+ def increment; 1; end
+ def decrement; end
+ def <<(arg); end
+ end
+
+ setup do
+ @object = Level.new
+ end
+
+ def test_assert_called_with_defaults_to_expect_once
+ assert_called @object, :increment do
+ @object.increment
+ end
+ end
+
+ def test_assert_called_more_than_once
+ assert_called(@object, :increment, times: 2) do
+ @object.increment
+ @object.increment
+ end
+ end
+
+ def test_assert_called_failure
+ error = assert_raises(Minitest::Assertion) do
+ assert_called(@object, :increment) do
+ # Call nothing...
+ end
+ end
+
+ assert_equal "Expected increment to be called 1 times, but was called 0 times.\nExpected: 1\n Actual: 0", error.message
+ end
+
+ def test_assert_called_with_message
+ error = assert_raises(Minitest::Assertion) do
+ assert_called(@object, :increment, 'dang it') do
+ # Call nothing...
+ end
+ end
+
+ assert_match(/dang it.\nExpected increment/, error.message)
+ end
+
+ def test_assert_called_with
+ assert_called_with(@object, :increment) do
+ @object.increment
+ end
+ end
+
+ def test_assert_called_with_arguments
+ assert_called_with(@object, :<<, [ 2 ]) do
+ @object << 2
+ end
+ end
+
+ def test_assert_called_with_failure
+ assert_raises(MockExpectationError) do
+ assert_called_with(@object, :<<, [ 4567 ]) do
+ @object << 2
+ end
+ end
+ end
+
+ def test_assert_called_with_returns
+ assert_called_with(@object, :increment, returns: 1) do
+ @object.increment
+ end
+ end
+
+ def test_assert_called_with_multiple_expected_arguments
+ assert_called_with(@object, :<<, [ [ 1 ], [ 2 ] ]) do
+ @object << 1
+ @object << 2
+ end
+ end
+
+ def test_assert_not_called
+ assert_not_called(@object, :decrement) do
+ @object.increment
+ end
+ end
+
+ def test_assert_not_called_failure
+ error = assert_raises(Minitest::Assertion) do
+ assert_not_called(@object, :increment) do
+ @object.increment
+ end
+ end
+
+ assert_equal "Expected increment to be called 0 times, but was called 1 times.\nExpected: 0\n Actual: 1", error.message
+ end
+end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 34bb0e2995..00d40c4497 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
require 'time_zone_test_helpers'
+require 'yaml'
class TimeZoneTest < ActiveSupport::TestCase
include TimeZoneTestHelpers
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index 0e4e7427d2..55e8181b54 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -3,6 +3,7 @@ require 'active_support/xml_mini'
require 'active_support/builder'
require 'active_support/core_ext/hash'
require 'active_support/core_ext/big_decimal'
+require 'yaml'
module XmlMiniTest
class RenameKeyTest < ActiveSupport::TestCase
diff --git a/guides/Rakefile b/guides/Rakefile
index 3c2099ac02..00577377d7 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -7,7 +7,7 @@ namespace :guides do
desc "Generate HTML guides"
task :html do
- ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this
+ ENV["WARNINGS"] = "1" # authors can't disable this
ruby "rails_guides.rb"
end
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 11561c55f9..58ba708a39 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -32,7 +32,7 @@ class TestController < ActionController::Base
include Rails.application.routes.url_helpers
def index
- render text: 'Home'
+ render plain: 'Home'
end
end
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 66887398b9..1a4b736348 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -31,7 +31,7 @@ class TestController < ActionController::Base
include Rails.application.routes.url_helpers
def index
- render text: 'Home'
+ render plain: 'Home'
end
end
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 98c6cbd540..db7eeed19a 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -15,7 +15,7 @@ After reading this guide, you will know:
What is Action View?
--------------------
-Action View and Action Controller are the two major components of Action Pack. In Rails, web requests are handled by Action Pack, which splits the work into a controller part (performing the logic) and a view part (rendering a template). Typically, Action Controller will be concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response.
+In Rails, web requests are handled by [Action Controller](action_controller_overview.html) and Action View. Typically, Action Controller will be concerned with communicating with the database and performing CRUD actions where necessary. Action View is then responsible for compiling the response.
Action View templates are written using embedded Ruby in tags mingled with HTML. To avoid cluttering the templates with boilerplate code, a number of helper classes provide common behavior for forms, dates, and strings. It's also easy to add new helpers to your application as it evolves.
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 1715fc327a..cb25a4c5f3 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -343,6 +343,13 @@ class GuestsCleanupJob < ActiveJob::Base
end
```
+### Deserialization
+
+GlobalID allows serializing full Active Record objects passed to `#perform`.
+
+If a passed record is deleted after the job is enqueued but before the `#perform`
+method is called Active Job will raise an `ActiveJob::DeserializationError`
+exception.
Job Testing
--------------
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index ce605c912e..980dfe6953 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -357,8 +357,8 @@ will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table
### Creating a Join Table
-Migration method `create_join_table` creates an HABTM join table. A typical use
-would be:
+The migration method `create_join_table` creates an HABTM (has and belongs to
+many) join table. A typical use would be:
```ruby
create_join_table :products, :categories
@@ -367,23 +367,21 @@ create_join_table :products, :categories
which creates a `categories_products` table with two columns called
`category_id` and `product_id`. These columns have the option `:null` set to
`false` by default. This can be overridden by specifying the `:column_options`
-option.
+option:
```ruby
-create_join_table :products, :categories, column_options: {null: true}
+create_join_table :products, :categories, column_options: { null: true }
```
-will create the `product_id` and `category_id` with the `:null` option as
-`true`.
-
-You can pass the option `:table_name` when you want to customize the table
-name. For example:
+By default, the name of the join table comes from the union of the first two
+arguments provided to create_join_table, in alphabetical order.
+To customize the name of the table, provide a `:table_name` option:
```ruby
create_join_table :products, :categories, table_name: :categorization
```
-will create a `categorization` table.
+creates a `categorization` table.
`create_join_table` also accepts a block, which you can use to add indices
(which are not created by default) or additional columns:
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index e3cfabb327..4b4d70d3ce 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1266,6 +1266,18 @@ class Client < ActiveRecord::Base
end
```
+NOTE: The `default_scope` is also applied while creating/building a record.
+It is not applied while updating a record. E.g.:
+
+```ruby
+class Client < ActiveRecord::Base
+ default_scope { where(active: true) }
+end
+
+Client.new # => #<Client id: nil, active: true>
+Client.unscoped.new # => #<Client id: nil, active: nil>
+```
+
### Merging of scopes
Just like `where` clauses scopes are merged using `AND` conditions.
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 7932853c11..71ca7a0f66 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -965,8 +965,9 @@ own custom validators.
You can also create methods that verify the state of your models and add
messages to the `errors` collection when they are invalid. You must then
-register these methods by using the `validate` class method, passing in the
-symbols for the validation methods' names.
+register these methods by using the `validate`
+([API](http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validate))
+class method, passing in the symbols for the validation methods' names.
You can pass more than one symbol for each class method and the respective
validations will be run in the same order as they were registered.
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index c0fa3cfd04..1191f5edfe 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -622,6 +622,19 @@ end
We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled model IDs, or exceptions about conflicting IDs, chances are you forgot that bit.
+You can also use the method `create_join_table`
+
+```ruby
+class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
+ def change
+ create_join_table :assemblies, :parts do |t|
+ t.index :assembly_id
+ t.index :part_id
+ end
+ end
+end
+```
+
### Controlling Association Scope
By default, associations look for objects only within the current module's scope. This can be important when you declare Active Record models within a module. For example:
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index b0103c9af4..9a56233e4a 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -3,12 +3,24 @@
Caching with Rails: An Overview
===============================
-This guide is an introduction to speeding up your Rails app with caching.
+This guide is an introduction to speeding up your Rails application with caching.
+
+Caching means to store content generated during the request-response cycle and
+to reuse it when responding to similar requests.
+
+Caching is often the most effective way to boost an application's performance.
+Through caching, web sites running on a single server with a single database
+can sustain a load of thousands of concurrent users.
+
+Rails provides a set of caching features out of the box. This guide will teach
+you the scope and purpose of each one of them. Master these techniques and your
+Rails applications can serve millions of views without exorbitant response times
+or server bills.
After reading this guide, you will know:
-* Page and action caching.
* Fragment and Russian doll caching.
+* How to manage the caching dependencies.
* Alternative cache stores.
* Conditional GET support.
@@ -31,6 +43,11 @@ the relevant `config/environments/*.rb` file:
config.action_controller.perform_caching = true
```
+NOTE: Changing the value of `config.action_controller.perform_caching` will
+only have an effect on the caching provided by the Action Controller component.
+For instance, it will not impact low-level caching, that we address
+[below](#low-level-caching).
+
### Page Caching
Page caching is a Rails mechanism which allows the request for a generated page
@@ -98,6 +115,30 @@ If you want to cache a fragment under certain conditions, you can use
<% end %>
```
+#### Collection caching
+
+The `render` helper can also cache individual templates rendered for a collection.
+It can even one up the previous example with `each` by reading all cache
+templates at once instead of one by one. This is done automatically if the template
+rendered by the collection includes a `cache` call. Take a collection that renders
+a `products/_product.html.erb` partial for each element:
+
+```ruby
+render products
+```
+
+If `products/_product.html.erb` starts with a `cache` call like so:
+
+```html+erb
+<% cache product do %>
+ <%= product.name %>
+<% end %>
+```
+
+All the cached templates from previous renders will be fetched at once with much
+greater speed. There's more info on how to make your templates [eligible for
+collection caching](http://api.rubyonrails.org/classes/ActionView/Template/Handlers/ERB.html#method-i-resource_cache_call_pattern).
+
### Russian Doll Caching
You may want to nest cached fragments inside other cached fragments. This is
@@ -122,7 +163,7 @@ For example, take the following view:
Which in turn renders this view:
```erb
-<% cache game %>
+<% cache game do %>
<%= render game %>
<% end %>
```
@@ -147,6 +188,93 @@ With `touch` set to true, any action which changes `updated_at` for a game
record will also change it for the associated product, thereby expiring the
cache.
+### Managing dependencies
+
+In order to correctly invalidate the cache, you need to properly define the
+caching dependencies. Rails is clever enough to handle common cases so you don't
+have to specify anything. However, sometimes, when you're dealing with custom
+helpers for instance, you need to explicitly define them.
+
+#### Implicit dependencies
+
+Most template dependencies can be derived from calls to `render` in the template
+itself. Here are some examples of render calls that `ActionView::Digestor` knows
+how to decode:
+
+```ruby
+render partial: "comments/comment", collection: commentable.comments
+render "comments/comments"
+render 'comments/comments'
+render('comments/comments')
+
+render "header" => render("comments/header")
+
+render(@topic) => render("topics/topic")
+render(topics) => render("topics/topic")
+render(message.topics) => render("topics/topic")
+```
+
+On the other hand, some calls need to be changed to make caching work properly.
+For instance, if you're passing a custom collection, you'll need to change:
+
+```ruby
+render @project.documents.where(published: true)
+```
+
+to:
+
+```ruby
+render partial: "documents/document", collection: @project.documents.where(published: true)
+```
+
+#### Explicit dependencies
+
+Sometimes you'll have template dependencies that can't be derived at all. This
+is typically the case when rendering happens in helpers. Here's an example:
+
+```html+erb
+<%= render_sortable_todolists @project.todolists %>
+```
+
+You'll need to use a special comment format to call those out:
+
+```html+erb
+<%# Template Dependency: todolists/todolist %>
+<%= render_sortable_todolists @project.todolists %>
+```
+
+In some cases, like a single table inheritance setup, you might have a bunch of
+explicit dependencies. Instead of writing every template out, you can use a
+wildcard to match any template in a directory:
+
+```html+erb
+<%# Template Dependency: events/* %>
+<%= render_categorizable_events @person.events %>
+```
+
+As for collection caching, if the partial template doesn't start with a clean
+cache call, you can still benefit from collection caching by adding a special
+comment format anywhere in the template, like:
+
+```html+erb
+<%# Template Collection: notification %>
+<% my_helper_that_calls_cache(some_arg, notification) do %>
+ <%= notification.name %>
+<% end %>
+```
+
+#### External dependencies
+
+If you use a helper method, for example, inside a cached block and you then update
+that helper, you'll have to bump the cache as well. It doesn't really matter how
+you do it, but the md5 of the template file must change. One recommendation is to
+simply be explicit in a comment, like:
+
+```html+erb
+<%# Helper Dependency Updated: Jul 28, 2015 at 7pm %>
+<%= some_helper_method(person) %>
+```
+
### Low-Level Caching
Sometimes you need to cache a particular value or query result instead of caching view fragments. Rails' caching mechanism works great for storing __any__ kind of information.
@@ -202,16 +330,17 @@ persistent fashion, you can with low level caching.
Cache Stores
------------
-Rails provides different stores for the cached data created by **action** and **fragment** caches.
-
-TIP: Page caches are always stored on disk.
+Rails provides different stores for the cached data (apart from SQL and page
+caching).
### Configuration
-You can set up your application's default cache store by calling `config.cache_store=` in the Application definition inside your `config/application.rb` file or in an Application.configure block in an environment specific configuration file (i.e. `config/environments/*.rb`). The first argument will be the cache store to use and the rest of the argument will be passed as arguments to the cache store constructor.
+You can set up your application's default cache store by setting the
+`config.cache_store` configuration option. Other parameters can be passed as
+arguments to the cache store's constructor:
```ruby
-config.cache_store = :memory_store
+config.cache_store = :memory_store, { size: 64.megabytes }
```
NOTE: Alternatively, you can call `ActionController::Base.cache_store` outside of a configuration block.
@@ -236,6 +365,19 @@ There are some common options used by all cache implementations. These can be pa
* `:race_condition_ttl` - This option is used in conjunction with the `:expires_in` option. It will prevent race conditions when cache entries expire by preventing multiple processes from simultaneously regenerating the same entry (also known as the dog pile effect). This option sets the number of seconds that an expired entry can be reused while a new value is being regenerated. It's a good practice to set this value if you use the `:expires_in` option.
+#### Custom Cache Stores
+
+You can create your own custom cache store by simply extending
+`ActiveSupport::Cache::Store` and implementing the appropriate methods. This way,
+you can swap in any number of caching technologies into your Rails application.
+
+To use a custom cache store, simply set the cache store to a new instance of your
+custom class.
+
+```ruby
+config.cache_store = MyCacheStore.new
+```
+
### ActiveSupport::Cache::MemoryStore
This cache store keeps entries in memory in the same Ruby process. The cache
@@ -287,36 +429,6 @@ The `write` and `fetch` methods on this cache accept two additional options that
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
```
-### ActiveSupport::Cache::EhcacheStore
-
-If you are using JRuby you can use Terracotta's Ehcache as the cache store for your application. Ehcache is an open source Java cache that also offers an enterprise version with increased scalability, management, and commercial support. You must first install the jruby-ehcache-rails3 gem (version 1.1.0 or later) to use this cache store.
-
-```ruby
-config.cache_store = :ehcache_store
-```
-
-When initializing the cache, you may use the `:ehcache_config` option to specify the Ehcache config file to use (where the default is "ehcache.xml" in your Rails config directory), and the :cache_name option to provide a custom name for your cache (the default is rails_cache).
-
-In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including:
-
-| Property | Argument Type | Description |
-| --------------------------- | ------------------- | ----------------------------------------------------------- |
-| elementEvictionData | ElementEvictionData | Sets this element's eviction data instance. |
-| eternal | boolean | Sets whether the element is eternal. |
-| timeToIdle, tti | int | Sets time to idle |
-| timeToLive, ttl, expires_in | int | Sets time to Live |
-| version | long | Sets the version attribute of the ElementAttributes object. |
-
-These options are passed to the `write` method as Hash options using either camelCase or underscore notation, as in the following examples:
-
-```ruby
-Rails.cache.write('key', 'value', time_to_idle: 60.seconds, timeToLive: 600.seconds)
-caches_action :index, expires_in: 60.seconds, unless_exist: true
-```
-
-For more information about Ehcache, see [http://ehcache.org/](http://ehcache.org/) .
-For more information about Ehcache for JRuby and Rails, see [http://ehcache.org/documentation/jruby.html](http://ehcache.org/documentation/jruby.html)
-
### ActiveSupport::Cache::NullStore
This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with `Rails.cache` but caching may interfere with being able to see the results of code changes. With this cache store, all `fetch` and `read` operations will result in a miss.
@@ -325,19 +437,13 @@ This cache store implementation is meant to be used only in development or test
config.cache_store = :null_store
```
-### Custom Cache Stores
-
-You can create your own custom cache store by simply extending `ActiveSupport::Cache::Store` and implementing the appropriate methods. In this way, you can swap in any number of caching technologies into your Rails application.
-
-To use a custom cache store, simply set the cache store to a new instance of the class.
-
-```ruby
-config.cache_store = MyCacheStore.new
-```
-
-### Cache Keys
+Cache Keys
+----------
-The keys used in a cache can be any object that responds to either `:cache_key` or `:to_param`. You can implement the `:cache_key` method on your classes if you need to generate custom keys. Active Record will generate keys based on the class name and record id.
+The keys used in a cache can be any object that responds to either `cache_key` or
+`to_param`. You can implement the `cache_key` method on your classes if you need
+to generate custom keys. Active Record will generate keys based on the class name
+and record id.
You can use Hashes and Arrays of values as cache keys.
@@ -346,7 +452,12 @@ You can use Hashes and Arrays of values as cache keys.
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
```
-The keys you use on `Rails.cache` will not be the same as those actually used with the storage engine. They may be modified with a namespace or altered to fit technology backend constraints. This means, for instance, that you can't save values with `Rails.cache` and then try to pull them out with the `memcache-client` gem. However, you also don't need to worry about exceeding the memcached size limit or violating syntax rules.
+The keys you use on `Rails.cache` will not be the same as those actually used with
+the storage engine. They may be modified with a namespace or altered to fit
+technology backend constraints. This means, for instance, that you can't save
+values with `Rails.cache` and then try to pull them out with the `dalli` gem.
+However, you also don't need to worry about exceeding the memcached size limit or
+violating syntax rules.
Conditional GET support
-----------------------
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 79a80de3cc..47c275609e 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -326,7 +326,7 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets rather than the application server itself.
-* `config.action_controller.perform_caching` configures whether the application should perform caching or not. Set to false in development mode, true in production.
+* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to false in development mode, true in production.
* `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`.
@@ -414,7 +414,7 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
`config.action_view` includes a small number of configuration settings:
-* `config.action_view.field_error_proc` provides an HTML generator for displaying errors that come from Active Record. The default is
+* `config.action_view.field_error_proc` provides an HTML generator for displaying errors that come from Active Model. The default is
```ruby
Proc.new do |html_tag, instance|
@@ -1084,22 +1084,22 @@ development:
timeout: 5000
```
-Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit.
+Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. The database connection pool is initially empty. As demand for connections increases it will create them until it reaches the connection pool limit.
-Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue.
+Any one request will check out a connection the first time it requires access to the database. At the end of the request it will check the connection back in. This means that the additional connection slot will be available again for the next request in the queue.
If you try to use more connections than are available, Active Record will block
-and wait for a connection from the pool. When it cannot get connection, a timeout
-error similar to given below will be thrown.
+you and wait for a connection from the pool. If it cannot get a connection, a
+timeout error similar to that given below will be thrown.
```ruby
ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it:
```
-If you get the above error, you might want to increase the size of connection
-pool by incrementing the `pool` option in `database.yml`
+If you get the above error, you might want to increase the size of the
+connection pool by incrementing the `pool` option in `database.yml`
-NOTE. If you are running in a multi-threaded environment, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
+NOTE. If you are running in a multi-threaded environment, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited number of connections.
Custom configuration
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index c19813c8a5..3b944f1274 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -61,7 +61,7 @@ can expect it to be marked "invalid" as soon as it's reviewed.
Sometimes, the line between 'bug' and 'feature' is a hard one to draw.
Generally, a feature is anything that adds new behavior, while a bug is
-anything that fixes already existing behavior that is misbehaving. Sometimes,
+anything that causes incorrect behavior. Sometimes,
the core team will have to make a judgement call. That said, the distinction
generally just affects which release your patch will get in to; we love feature
submissions! They just won't get backported to maintenance branches.
@@ -295,7 +295,7 @@ You can run a single test through ruby. For instance:
```bash
$ cd actionmailer
-$ ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout
+$ bundle exec ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout
```
The `-n` option allows you to run a single method instead of the whole
@@ -334,7 +334,7 @@ will now run the four of them in turn.
You can also run any single test separately:
```bash
-$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
+$ ARCONN=sqlite3 bundle exec ruby -Itest test/cases/associations/has_many_associations_test.rb
```
To run a single test against all adapters, use:
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index 61ea0b44ef..1d995581fa 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -28,7 +28,7 @@ Ruby on Rails Guides: Credits
<h3 class="section">Rails Guides Authors</h3>
<%= author('Ryan Bigg', 'radar', 'radar.png') do %>
- Ryan Bigg works as the Community Manager at <a href="http://spreecommerce.com">Spree Commerce</a> and has been working with Rails since 2006. He's the author of <a href="https://leanpub.com/multi-tenancy-rails">Multi Tenancy With Rails</a> and co-author of <a href="http://manning.com/bigg2">Rails 4 in Action</a>. He's written many gems which can be seen on <a href="https://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>.
+ Ryan Bigg works as a Rails developer at <a href="http://marketplacer.com">Marketplacer</a> and has been working with Rails since 2006. He's the author of <a href="https://leanpub.com/multi-tenancy-rails">Multi Tenancy With Rails</a> and co-author of <a href="http://manning.com/bigg2">Rails 4 in Action</a>. He's written many gems which can be seen on <a href="https://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>.
<% end %>
<%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %>
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 9145aee009..4473eba478 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -122,6 +122,10 @@
url: autoloading_and_reloading_constants.html
description: This guide documents how autoloading and reloading constants work.
-
+ name: "Caching with Rails: An Overview"
+ url: caching_with_rails.html
+ description: This guide is an introduction to speeding up your Rails application with caching.
+ -
name: Active Support Instrumentation
work_in_progress: true
url: active_support_instrumentation.html
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 8f7d97844e..84a8d695cb 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -211,9 +211,8 @@ IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local,
month, week, URL, email, number and range inputs are HTML5 controls.
If you require your app to have a consistent experience in older browsers,
you will need an HTML5 polyfill (provided by CSS and/or JavaScript).
-There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a couple of popular tools at the moment are
-[Modernizr](http://www.modernizr.com/) and [yepnope](http://yepnopejs.com/),
-which provide a simple way to add functionality based on the presence of
+There is definitely [no shortage of solutions for this](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills), although a popular tool at the moment is
+[Modernizr](http://www.modernizr.com/), which provides a simple way to add functionality based on the presence of
detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the [Security Guide](security.html#logging).
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 11051f71c2..dbbedc49ab 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -23,10 +23,12 @@ application from scratch. It does not assume that you have any prior experience
with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
-* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 2.2.2 or newer.
-* Right version of [Development Kit](http://rubyinstaller.org/downloads/), if you are using Windows
-* The [RubyGems](https://rubygems.org) packaging system, which is installed with Ruby
- versions 1.9 and later. To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org).
+* The [Ruby](https://www.ruby-lang.org/en/downloads) language version 2.2.2 or newer.
+* Right version of [Development Kit](http://rubyinstaller.org/downloads/), if you
+ are using Windows.
+* The [RubyGems](https://rubygems.org) packaging system, which is installed with
+ Ruby by default. To learn more about RubyGems, please read the
+ [RubyGems Guides](http://guides.rubygems.org).
* A working installation of the [SQLite3 Database](https://www.sqlite.org).
Rails is a web application framework running on the Ruby programming language.
@@ -621,7 +623,7 @@ def create
end
```
-The `render` method here is taking a very simple hash with a key of `plain` and
+The `render` method here is taking a very simple hash with a key of `:plain` and
value of `params[:article].inspect`. The `params` method is the object which
represents the parameters (or fields) coming in from the form. The `params`
method returns an `ActiveSupport::HashWithIndifferentAccess` object, which
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 52f11f92bd..cf828462ce 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -615,6 +615,8 @@ get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }
Rails would match `photos/12` to the `show` action of `PhotosController`, and set `params[:format]` to `"jpg"`.
+NOTE: You cannot override defaults via query parameters - this is for security reasons. The only defaults that can be overridden are dynamic segments via substitution in the URL path.
+
### Naming Routes
You can specify a name for any route using the `:as` option:
@@ -1087,6 +1089,20 @@ edit_videos GET /videos/:identifier/edit(.:format) videos#edit
Video.find_by(identifier: params[:identifier])
```
+You can override `ActiveRecord::Base#to_param` of a related
+model to constructe an URL.
+
+```ruby
+class Video < ActiveRecord::Base
+ def to_param # overridden
+ identifier
+ end
+end
+
+video = Video.find_by(identifier: "Roman-Holiday")
+edit_videos_path(video) # => "/videos/Roman-Holiday"
+```
+
Inspecting and Testing Routes
-----------------------------
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 2fd54a48fc..f40e765242 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -178,6 +178,14 @@ create test/fixtures/articles.yml
...
```
+You can also generate the test stub for a model using the following command:
+
+```bash
+$ bin/rails generate test_unit:model article title:string body:text
+create test/models/article_test.rb
+create test/fixtures/articles.yml
+```
+
The default test stub in `test/models/article_test.rb` looks like this:
```ruby
@@ -509,6 +517,14 @@ You should test for things such as:
Now that we have used Rails scaffold generator for our `Article` resource, it has already created the controller code and tests. You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory.
+The following command will generate a controller test case with a filled up
+test for each of the seven default actions.
+
+```bash
+$ bin/rails generate test_unit:scaffold article
+create test/controllers/articles_controller_test.rb
+```
+
Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`.
```ruby
@@ -1065,7 +1081,7 @@ Finally we can assert that our response was successful, template was rendered, a
#### Taking it further
-We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editting comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
+We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editing comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications.
Testing Your Mailers
--------------------
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index f3d3a83afc..4c996dd2d0 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -258,7 +258,7 @@ this generates
```html
<form action="/articles/1" class="button_to" data-remote="true" method="post">
- <div><input type="submit" value="An article"></div>
+ <input type="submit" value="An article" />
</form>
```
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 573b1f8f69..30449aa670 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Add a `--api` option in order to generate plugins that can be added
+ inside an API application.
+
+ *Robin Dupret*
+
* Fix `NoMethodError` when generating a scaffold inside a full engine.
*Yuji Yaginuma*
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index 6f9ccec137..88eade5c5a 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -26,7 +26,27 @@ module Rails
middleware.use ::Rack::Cache, rack_cache
end
- middleware.use ::Rack::Lock unless allow_concurrency?
+ if config.allow_concurrency == false
+ # User has explicitly opted out of concurrent request
+ # handling: presumably their code is not threadsafe
+
+ middleware.use ::Rack::Lock
+
+ elsif config.allow_concurrency == :unsafe
+ # Do nothing, even if we know this is dangerous. This is the
+ # historical behaviour for true.
+
+ else
+ # Default concurrency setting: enabled, but safe
+
+ unless config.cache_classes && config.eager_load
+ # Without cache_classes + eager_load, the load interlock
+ # is required for proper operation
+
+ middleware.use ::ActionDispatch::LoadInterlock
+ end
+ end
+
middleware.use ::Rack::Runtime
middleware.use ::Rack::MethodOverride unless config.api_only
middleware.use ::ActionDispatch::RequestId
@@ -65,14 +85,6 @@ module Rails
config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any?
end
- def allow_concurrency?
- if config.allow_concurrency.nil?
- config.cache_classes && config.eager_load
- else
- config.allow_concurrency
- end
- end
-
def load_rack_cache
rack_cache = config.action_dispatch.rack_cache
return unless rack_cache
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 0599e988d9..404e3c3e23 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -86,8 +86,10 @@ module Rails
# added in the hook are taken into account.
initializer :set_clear_dependencies_hook, group: :all do
callback = lambda do
- ActiveSupport::DescendantsTracker.clear
- ActiveSupport::Dependencies.clear
+ ActiveSupport::Dependencies.interlock.attempt_unloading do
+ ActiveSupport::DescendantsTracker.clear
+ ActiveSupport::Dependencies.clear
+ end
end
if config.reload_classes_only_on_change
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 6289d4cc46..d1e445ac70 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -77,17 +77,7 @@ module Rails
end
def middleware
- middlewares = []
-
- # FIXME: add Rack::Lock in the case people are using webrick.
- # This is to remain backwards compatible for those who are
- # running webrick in production. We should consider removing this
- # in development.
- if server.name == 'Rack::Handler::WEBrick'
- middlewares << [::Rack::Lock]
- end
-
- Hash.new(middlewares)
+ Hash.new([])
end
def default_options
diff --git a/railties/lib/rails/commands/test.rb b/railties/lib/rails/commands/test.rb
index fe5307788a..dd069f081f 100644
--- a/railties/lib/rails/commands/test.rb
+++ b/railties/lib/rails/commands/test.rb
@@ -1,5 +1,9 @@
require "rails/test_unit/minitest_plugin"
-$: << File.expand_path("../../test", APP_PATH)
+if defined?(ENGINE_ROOT)
+ $: << File.expand_path('test', ENGINE_ROOT)
+else
+ $: << File.expand_path('../../test', APP_PATH)
+end
exit Minitest.run(ARGV)
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index f39f926109..a6d87b78e4 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -2,7 +2,8 @@ ARGV << '--help' if ARGV.empty?
aliases = {
"g" => "generate",
- "d" => "destroy"
+ "d" => "destroy",
+ "t" => "test"
}
command = ARGV.shift
@@ -12,7 +13,7 @@ require ENGINE_PATH
engine = ::Rails::Engine.find(ENGINE_ROOT)
case command
-when 'generate', 'destroy'
+when 'generate', 'destroy', 'test'
require 'rails/generators'
Rails::Generators.namespace = engine.railtie_namespace
engine.load_generators
@@ -30,6 +31,7 @@ Usage: rails COMMAND [ARGS]
The common Rails commands available for engines are:
generate Generate new code (short-cut alias: "g")
destroy Undo code generated with "generate" (short-cut alias: "d")
+ test Run tests (short-cut alias: "t")
All commands can be run with -h for more information.
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 560a553789..b4356f71e0 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -92,11 +92,11 @@ module Rails
# file in <tt>config/environments</tt>.
#
# environment do
- # "config.autoload_paths += %W(#{config.root}/extras)"
+ # "config.action_controller.asset_host = 'cdn.provider.com'"
# end
#
# environment(nil, env: "development") do
- # "config.autoload_paths += %W(#{config.root}/extras)"
+ # "config.action_controller.asset_host = 'localhost:3000'"
# end
def environment(data=nil, options={})
sentinel = /class [a-z_:]+ < Rails::Application/i
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 813b8b629e..6fa413f8b0 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -103,12 +103,12 @@ module Rails
# hook_for :test_framework, as: :controller
# end
#
- # And now it will lookup at:
+ # And now it will look up at:
#
# "test_unit:controller", "test_unit"
#
- # Similarly, if you want it to also lookup in the rails namespace, you just
- # need to provide the :in value:
+ # Similarly, if you want it to also look up in the rails namespace, you
+ # just need to provide the :in value:
#
# class AwesomeGenerator < Rails::Generators::Base
# hook_for :test_framework, in: :rails, as: :controller
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index c3b8ef1181..5f4904fee1 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -28,4 +28,4 @@
<br>
-<%%= link_to 'New <%= human_name %>', new_<%= singular_table_name %>_path %>
+<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_table_name %>_path %>
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index b083381255..6a1c2faaab 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -36,13 +36,15 @@ group :development, :test do
end
group :development do
+<%- unless options.api? -%>
# Access an IRB console on exception pages or by using <%%= console %> in views
<%- if options.dev? || options.edge? -%>
gem 'web-console', github: 'rails/web-console'
<%- else -%>
gem 'web-console', '~> 2.0'
<%- end -%>
-<%- if spring_install? %>
+<%- end -%>
+<% if spring_install? -%>
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index cb86978d4c..c88426ec06 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -5,7 +5,7 @@
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// compiled file.
+// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
index 0cdd2788d0..0ebd7fe829 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
@@ -7,7 +7,8 @@
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
- * files in this directory. It is generally better to create a new file per style scope.
+ * files in this directory. Styles in this file should be added after the last require_* statement.
+ * It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup
index 0d41f2fe4c..0c8b179827 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -5,13 +5,17 @@ include FileUtils
# path to your application root.
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
chdir APP_ROOT do
# This script is a starting point to setup your application.
# Add necessary setup steps to this file.
puts '== Installing dependencies =='
- system 'gem install bundler --conservative'
- system('bundle check') or system('bundle install')
+ system! 'gem install bundler --conservative'
+ system('bundle check') or system!('bundle install')
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
@@ -19,11 +23,11 @@ chdir APP_ROOT do
# end
puts "\n== Preparing database =="
- system 'ruby bin/rake db:setup'
+ system! 'ruby bin/rake db:setup'
puts "\n== Removing old logs and tempfiles =="
- system 'ruby bin/rake log:clear tmp:clear'
+ system! 'ruby bin/rake log:clear tmp:clear'
puts "\n== Restarting application server =="
- system 'ruby bin/rake restart'
+ system! 'ruby bin/rake restart'
end
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 66111004aa..0e1326ce4f 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -17,15 +17,22 @@ module Rails
def app
if mountable?
- directory 'app'
- empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
+ if api?
+ directory 'app', exclude_pattern: %r{app/(views|helpers)}
+ else
+ directory 'app'
+ empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
+ end
elsif full?
empty_directory_with_keep_file 'app/models'
empty_directory_with_keep_file 'app/controllers'
- empty_directory_with_keep_file 'app/views'
- empty_directory_with_keep_file 'app/helpers'
empty_directory_with_keep_file 'app/mailers'
- empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
+
+ unless api?
+ empty_directory_with_keep_file "app/assets/images/#{namespaced_name}"
+ empty_directory_with_keep_file 'app/helpers'
+ empty_directory_with_keep_file 'app/views'
+ end
end
end
@@ -82,6 +89,7 @@ task default: :test
opts = (options || {}).slice(*PASSTHROUGH_OPTIONS)
opts[:force] = force
opts[:skip_bundle] = true
+ opts[:api] = options.api?
invoke Rails::Generators::AppGenerator,
[ File.expand_path(dummy_path, destination_root) ], opts
@@ -176,6 +184,9 @@ task default: :test
desc: "If creating plugin in application's directory " +
"skip adding entry to Gemfile"
+ class_option :api, type: :boolean, default: false,
+ desc: "Generate a smaller stack for API application plugins"
+
def initialize(*args)
@dummy_path = nil
super
@@ -210,15 +221,15 @@ task default: :test
end
def create_public_stylesheets_files
- build(:stylesheets)
+ build(:stylesheets) unless api?
end
def create_javascript_files
- build(:javascripts)
+ build(:javascripts) unless api?
end
def create_images_directory
- build(:images)
+ build(:images) unless api?
end
def create_bin_files
@@ -305,6 +316,10 @@ task default: :test
options[:skip_test].blank? || options[:dummy_path] != 'test/dummy'
end
+ def api?
+ options[:api]
+ end
+
def self.banner
"rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]"
end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
index 7157e48c42..7fe4e5034d 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt
@@ -1,5 +1,5 @@
<%= wrap_in_modules <<-rb.strip_heredoc
- class ApplicationController < ActionController::Base
+ class ApplicationController < ActionController::#{api? ? "API" : "Base"}
end
rb
%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
index 17afd52177..8938770fc4 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
@@ -1,6 +1,7 @@
<%= wrap_in_modules <<-rb.strip_heredoc
class Engine < ::Rails::Engine
#{mountable? ? ' isolate_namespace ' + camelized_modules : ' '}
+ #{api? ? " config.generators.api_only = true" : ' '}
end
rb
%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
index 8913b40f69..e54c6461cc 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
@@ -5,7 +5,7 @@
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// compiled file.
+// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
index 0cdd2788d0..0ebd7fe829 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/stylesheets.css
@@ -7,7 +7,8 @@
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
- * files in this directory. It is generally better to create a new file per style scope.
+ * files in this directory. Styles in this file should be added after the last require_* statement.
+ * It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 828039dc43..5cc1b5b219 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -28,14 +28,14 @@ if defined?(ActiveRecord::Base)
end
class ActionController::TestCase
- def before_setup
+ def before_setup # :nodoc:
@routes = Rails.application.routes
super
end
end
class ActionDispatch::IntegrationTest
- def before_setup
+ def before_setup # :nodoc:
@routes = Rails.application.routes
super
end
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index ab71298509..5552779ef1 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -48,4 +48,5 @@ module Minitest
mattr_accessor(:run_with_rails_extension) { false }
end
+Minitest.load_plugins
Minitest.extensions << 'rails'
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 6952472ff5..ba2c27d8e8 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -60,6 +60,7 @@ module ApplicationTests
test "assets do not require compressors until it is used" do
app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();"
add_to_env_config "production", "config.assets.compile = true"
+ add_to_env_config "production", "config.assets.precompile = []"
ENV["RAILS_ENV"] = "production"
require "#{app_path}/config/environment"
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index ce92ebbf66..d298e8d632 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -26,7 +26,7 @@ module ApplicationTests
assert_equal [
"Rack::Sendfile",
"ActionDispatch::Static",
- "Rack::Lock",
+ "ActionDispatch::LoadInterlock",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
"Rack::MethodOverride",
@@ -58,7 +58,7 @@ module ApplicationTests
assert_equal [
"Rack::Sendfile",
"ActionDispatch::Static",
- "Rack::Lock",
+ "ActionDispatch::LoadInterlock",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
"ActionDispatch::RequestId",
@@ -121,23 +121,40 @@ module ApplicationTests
assert !middleware.include?("ActiveRecord::Migration::CheckPending")
end
- test "includes lock if cache_classes is set but eager_load is not" do
+ test "includes interlock if cache_classes is set but eager_load is not" do
add_to_config "config.cache_classes = true"
boot!
- assert middleware.include?("Rack::Lock")
+ assert_not_includes middleware, "Rack::Lock"
+ assert_includes middleware, "ActionDispatch::LoadInterlock"
+ end
+
+ test "includes interlock if cache_classes is off" do
+ add_to_config "config.cache_classes = false"
+ boot!
+ assert_not_includes middleware, "Rack::Lock"
+ assert_includes middleware, "ActionDispatch::LoadInterlock"
end
test "does not include lock if cache_classes is set and so is eager_load" do
add_to_config "config.cache_classes = true"
add_to_config "config.eager_load = true"
boot!
- assert !middleware.include?("Rack::Lock")
+ assert_not_includes middleware, "Rack::Lock"
+ assert_not_includes middleware, "ActionDispatch::LoadInterlock"
+ end
+
+ test "does not include lock if allow_concurrency is set to :unsafe" do
+ add_to_config "config.allow_concurrency = :unsafe"
+ boot!
+ assert_not_includes middleware, "Rack::Lock"
+ assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
- test "does not include lock if allow_concurrency is set" do
- add_to_config "config.allow_concurrency = true"
+ test "includes lock if allow_concurrency is disabled" do
+ add_to_config "config.allow_concurrency = false"
boot!
- assert !middleware.include?("Rack::Lock")
+ assert_includes middleware, "Rack::Lock"
+ assert_not_includes middleware, "ActionDispatch::LoadInterlock"
end
test "removes static asset server if serve_static_files is disabled" do
diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb
new file mode 100644
index 0000000000..3198e12662
--- /dev/null
+++ b/railties/test/application/per_request_digest_cache_test.rb
@@ -0,0 +1,63 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+require 'minitest/mock'
+
+require 'action_view'
+require 'active_support/testing/method_call_assertions'
+
+class PerRequestDigestCacheTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include ActiveSupport::Testing::MethodCallAssertions
+ include Rack::Test::Methods
+
+ setup do
+ build_app
+ add_to_config 'config.consider_all_requests_local = true'
+
+ app_file 'app/models/customer.rb', <<-RUBY
+ class Customer < Struct.new(:name, :id)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ Rails.application.routes.draw do
+ resources :customers, only: :index
+ end
+ RUBY
+
+ app_file 'app/controllers/customers_controller.rb', <<-RUBY
+ class CustomersController < ApplicationController
+ def index
+ render [ Customer.new('david', 1), Customer.new('dingus', 2) ]
+ end
+ end
+ RUBY
+
+ app_file 'app/views/customers/_customer.html.erb', <<-RUBY
+ <% cache customer do %>
+ <%= customer.name %>
+ <% end %>
+ RUBY
+
+ require "#{app_path}/config/environment"
+ end
+
+ teardown :teardown_app
+
+ test "digests are reused when rendering the same template twice" do
+ get '/customers'
+ assert_equal 200, last_response.status
+
+ assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], ActionView::Digestor.cache.values
+ assert_equal %w(david dingus), last_response.body.split.map(&:strip)
+ end
+
+ test "template digests are cleared before a request" do
+ assert_called(ActionView::Digestor.cache, :clear) do
+ get '/customers'
+ assert_equal 200, last_response.status
+ end
+ end
+end
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index d6f5fee5c3..c87515f00f 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -114,6 +114,7 @@ module ApplicationTests
end
test 'register a new extension' do
+ add_to_config "config.assets.precompile = []"
add_to_config %q{ config.annotations.register_extensions("scss", "sass") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } }
app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss"
app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass"
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index 9978ad0da1..998da3ef84 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -38,6 +38,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/gem 'jquery-rails'/, content)
assert_no_match(/gem 'sass-rails'/, content)
assert_no_match(/gem 'jbuilder'/, content)
+ assert_no_match(/gem 'web-console'/, content)
assert_match(/gem 'active_model_serializers'/, content)
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 73e68863f4..6f5a815173 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -544,6 +544,60 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_skipping_useless_folders_generation_for_api_engines
+ ['--full', '--mountable'].each do |option|
+ run_generator [destination_root, option, '--api']
+
+ assert_no_directory "app/assets"
+ assert_no_directory "app/helpers"
+ assert_no_directory "app/views"
+
+ FileUtils.rm_rf destination_root
+ end
+ end
+
+ def test_application_controller_parent_for_mountable_api_plugins
+ run_generator [destination_root, '--mountable', '--api']
+
+ assert_file "app/controllers/bukkits/application_controller.rb" do |content|
+ assert_match "ApplicationController < ActionController::API", content
+ end
+ end
+
+ def test_dummy_api_application_for_api_plugins
+ run_generator [destination_root, '--api']
+
+ assert_file "test/dummy/config/application.rb" do |content|
+ assert_match "config.api_only = true", content
+ end
+ end
+
+
+ def test_api_generators_configuration_for_api_engines
+ run_generator [destination_root, '--full', '--api']
+
+ assert_file "lib/bukkits/engine.rb" do |content|
+ assert_match "config.generators.api_only = true", content
+ end
+ end
+
+ def test_scaffold_generator_for_mountable_api_plugins
+ run_generator [destination_root, '--mountable', '--api']
+
+ capture(:stdout) do
+ `#{destination_root}/bin/rails g scaffold article`
+ end
+
+ assert_file "app/models/bukkits/article.rb"
+ assert_file "app/controllers/bukkits/articles_controller.rb" do |content|
+ assert_match "only: [:show, :update, :destroy]", content
+ end
+
+ assert_no_directory "app/assets"
+ assert_no_directory "app/helpers"
+ assert_no_directory "app/views"
+ end
+
protected
def action(*args, &block)
silence(:stdout){ generator.send(*args, &block) }
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 648b8d9325..95ef853a11 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -182,7 +182,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
Dir.chdir(engine_path) do
quietly { `bin/rails g controller dashboard foo` }
- assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`)
+ assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
end
@@ -193,7 +193,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
Dir.chdir(engine_path) do
quietly { `bin/rails g controller dashboard foo` }
- assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`)
+ assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index debf1140ab..a853bfbe21 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -488,7 +488,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
`bin/rails g scaffold User name:string age:integer;
bundle exec rake db:migrate`
end
- assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`)
+ assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
end
@@ -502,7 +502,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
`bin/rails g scaffold User name:string age:integer;
bundle exec rake db:migrate`
end
- assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`)
+ assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
end
end
end