aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml42
-rw-r--r--.travis.yml8
-rw-r--r--Gemfile10
-rw-r--r--Gemfile.lock39
-rw-r--r--Rakefile2
-rw-r--r--actioncable/README.md2
-rw-r--r--actioncable/Rakefile10
-rw-r--r--actioncable/actioncable.gemspec2
-rw-r--r--actioncable/lib/action_cable/remote_connections.rb2
-rw-r--r--actioncable/lib/rails/generators/channel/channel_generator.rb2
-rw-r--r--actioncable/test/test_helper.rb2
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/rails/generators/mailer/mailer_generator.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb4
-rw-r--r--actionmailer/test/caching_test.rb12
-rw-r--r--actionmailer/test/log_subscriber_test.rb2
-rw-r--r--actionpack/CHANGELOG.md25
-rw-r--r--actionpack/Rakefile2
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/base.rb8
-rw-r--r--actionpack/lib/abstract_controller/caching/fragments.rb35
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb20
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/simulator.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb51
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb3
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb2
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb7
-rw-r--r--actionpack/test/abstract_unit.rb10
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb2
-rw-r--r--actionpack/test/controller/api/data_streaming_test.rb2
-rw-r--r--actionpack/test/controller/caching_test.rb71
-rw-r--r--actionpack/test/controller/helper_test.rb8
-rw-r--r--actionpack/test/controller/integration_test.rb2
-rw-r--r--actionpack/test/controller/live_stream_test.rb2
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_file_test.rb10
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb12
-rw-r--r--actionpack/test/controller/send_file_test.rb2
-rw-r--r--actionpack/test/controller/test_case_test.rb6
-rw-r--r--actionpack/test/dispatch/cookies_test.rb206
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/session_test.rb18
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/system_testing/system_test_case_test.rb12
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb6
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb2
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder2
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb2
-rw-r--r--actionpack/test/fixtures/functional_caching/fragment_cached.html.erb2
-rw-r--r--actionpack/test/journey/gtg/transition_table_test.rb34
-rw-r--r--actionpack/test/lib/controller/fake_models.rb4
-rw-r--r--actionview/CHANGELOG.md4
-rw-r--r--actionview/Rakefile12
-rw-r--r--actionview/actionview.gemspec2
-rw-r--r--actionview/app/assets/javascripts/README.md12
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb26
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb2
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb2
-rw-r--r--actionview/test/abstract_unit.rb14
-rw-r--r--actionview/test/actionpack/abstract/abstract_controller_test.rb4
-rw-r--r--actionview/test/actionpack/abstract/helper_test.rb4
-rw-r--r--actionview/test/actionpack/controller/capture_test.rb2
-rw-r--r--actionview/test/actionpack/controller/layout_test.rb6
-rw-r--r--actionview/test/actionpack/controller/render_test.rb12
-rw-r--r--actionview/test/active_record_unit.rb6
-rw-r--r--actionview/test/activerecord/relation_cache_test.rb2
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb1
-rw-r--r--actionview/test/template/digestor_test.rb2
-rw-r--r--actionview/test/template/form_options_helper_test.rb17
-rw-r--r--actionview/test/template/log_subscriber_test.rb2
-rw-r--r--actionview/test/template/render_test.rb10
-rw-r--r--actionview/test/template/resolver_patterns_test.rb2
-rw-r--r--actionview/test/template/url_helper_test.rb4
-rw-r--r--actionview/test/ujs/config.ru2
-rw-r--r--activejob/Rakefile8
-rw-r--r--activejob/activejob.gemspec2
-rw-r--r--activejob/lib/active_job/callbacks.rb4
-rw-r--r--activejob/lib/active_job/queue_adapter.rb19
-rw-r--r--activejob/lib/rails/generators/job/job_generator.rb2
-rw-r--r--activejob/test/adapters/delayed_job.rb2
-rw-r--r--activejob/test/cases/queue_adapter_test.rb5
-rw-r--r--activejob/test/support/integration/dummy_app_template.rb2
-rw-r--r--activejob/test/support/integration/helper.rb2
-rw-r--r--activemodel/CHANGELOG.md5
-rw-r--r--activemodel/Rakefile8
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/callbacks.rb3
-rw-r--r--activemodel/lib/active_model/validations.rb5
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb4
-rw-r--r--activemodel/lib/active_model/validator.rb2
-rw-r--r--activerecord/CHANGELOG.md17
-rw-r--r--activerecord/Rakefile6
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/associations/association.rb8
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb29
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb1
-rw-r--r--activerecord/lib/active_record/attribute_set.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb6
-rw-r--r--activerecord/lib/active_record/enum.rb23
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb73
-rw-r--r--activerecord/lib/active_record/model_schema.rb28
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb9
-rw-r--r--activerecord/lib/active_record/relation/batches.rb24
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb18
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb8
-rw-r--r--activerecord/lib/active_record/scoping/named.rb12
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb6
-rw-r--r--activerecord/lib/rails/generators/active_record.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/json_test.rb176
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb182
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb11
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb37
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb14
-rw-r--r--activerecord/test/cases/cache_key_test.rb30
-rw-r--r--activerecord/test/cases/enum_test.rb4
-rw-r--r--activerecord/test/cases/finder_test.rb1
-rw-r--r--activerecord/test/cases/inheritance_test.rb2
-rw-r--r--activerecord/test/cases/integration_test.rb62
-rw-r--r--activerecord/test/cases/json_shared_test_cases.rb177
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb46
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb8
-rw-r--r--activerecord/test/cases/migration_test.rb27
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb2
-rw-r--r--activerecord/test/cases/relation/merging_test.rb2
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb2
-rw-r--r--activerecord/test/cases/reload_models_test.rb2
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb11
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb28
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb7
-rw-r--r--activerecord/test/cases/validations_test.rb14
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb4
-rw-r--r--activerecord/test/config.rb2
-rw-r--r--activerecord/test/fixtures/books.yml1
-rw-r--r--activerecord/test/models/book.rb2
-rw-r--r--activerecord/test/models/category.rb9
-rw-r--r--activerecord/test/models/topic.rb2
-rw-r--r--activesupport/CHANGELOG.md37
-rw-r--r--activesupport/activesupport.gemspec2
-rwxr-xr-xactivesupport/bin/generate_tables2
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/cache.rb81
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb8
-rw-r--r--activesupport/lib/active_support/core_ext.rb2
-rw-r--r--activesupport/lib/active_support/current_attributes.rb193
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb2
-rw-r--r--activesupport/lib/active_support/duration.rb41
-rw-r--r--activesupport/lib/active_support/i18n.rb2
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb15
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/railtie.rb6
-rw-r--r--activesupport/test/caching_test.rb90
-rw-r--r--activesupport/test/core_ext/duration_test.rb34
-rw-r--r--activesupport/test/current_attributes_test.rb96
-rw-r--r--activesupport/test/dependencies_test.rb20
-rw-r--r--activesupport/test/dependencies_test_helpers.rb2
-rw-r--r--activesupport/test/inflector_test.rb7
-rw-r--r--activesupport/test/test_case_test.rb3
-rw-r--r--activesupport/test/testing/file_fixtures_test.rb4
-rw-r--r--activesupport/test/xml_mini/jdom_engine_test.rb2
-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/rails_guides/helpers.rb2
-rw-r--r--guides/source/action_controller_overview.md3
-rw-r--r--guides/source/action_mailer_basics.md3
-rw-r--r--guides/source/action_view_overview.md2
-rw-r--r--guides/source/active_job_basics.md6
-rw-r--r--guides/source/active_record_querying.md21
-rw-r--r--guides/source/api_app.md20
-rw-r--r--guides/source/api_documentation_guidelines.md2
-rw-r--r--guides/source/association_basics.md6
-rw-r--r--guides/source/configuring.md8
-rw-r--r--guides/source/generators.md4
-rw-r--r--guides/source/getting_started.md25
-rw-r--r--guides/source/layouts_and_rendering.md2
-rw-r--r--guides/source/nested_model_forms.md230
-rw-r--r--guides/source/profiling.md16
-rw-r--r--guides/source/rails_application_templates.md2
-rw-r--r--guides/source/rails_on_rack.md17
-rw-r--r--guides/source/security.md27
-rw-r--r--guides/source/working_with_javascript_in_rails.md2
-rw-r--r--rails.gemspec2
-rw-r--r--railties/Rakefile10
-rwxr-xr-xrailties/exe/rails4
-rw-r--r--railties/lib/rails/application.rb1
-rw-r--r--railties/lib/rails/application/configuration.rb12
-rw-r--r--railties/lib/rails/application_controller.rb2
-rw-r--r--railties/lib/rails/command/actions.rb2
-rw-r--r--railties/lib/rails/commands/secrets/secrets_command.rb21
-rw-r--r--railties/lib/rails/engine.rb2
-rw-r--r--railties/lib/rails/generators.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb4
-rw-r--r--railties/lib/rails/generators/base.rb2
-rw-r--r--railties/lib/rails/generators/css/assets/assets_generator.rb2
-rw-r--r--railties/lib/rails/generators/js/assets/assets_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb16
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/bundle2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt16
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb30
-rw-r--r--railties/lib/rails/generators/rails/encrypted_secrets/templates/config/secrets.yml.enc3
-rw-r--r--railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt8
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/test.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb8
-rw-r--r--railties/lib/rails/generators/test_case.rb4
-rw-r--r--railties/lib/rails/generators/test_unit/system/system_generator.rb2
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb4
-rw-r--r--railties/lib/rails/secrets.rb34
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/railties.gemspec2
-rw-r--r--railties/test/abstract_unit.rb2
-rw-r--r--railties/test/application/current_attributes_integration_test.rb84
-rw-r--r--railties/test/application/middleware/session_test.rb93
-rw-r--r--railties/test/application/per_request_digest_cache_test.rb4
-rw-r--r--railties/test/application/test_runner_test.rb2
-rw-r--r--railties/test/code_statistics_calculator_test.rb2
-rw-r--r--railties/test/code_statistics_test.rb2
-rw-r--r--railties/test/commands/secrets_test.rb7
-rw-r--r--railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb2
-rw-r--r--railties/test/generators/api_app_generator_test.rb1
-rw-r--r--railties/test/generators/app_generator_test.rb14
-rw-r--r--railties/test/generators/generators_test_helper.rb4
-rw-r--r--railties/test/generators/plugin_generator_test.rb12
-rw-r--r--railties/test/generators/system_test_generator_test.rb5
-rw-r--r--railties/test/generators_test.rb2
-rw-r--r--railties/test/isolation/abstract_unit.rb2
-rw-r--r--railties/test/rails_info_test.rb2
-rw-r--r--tasks/release.rb2
-rw-r--r--tools/test.rb5
257 files changed, 2252 insertions, 1464 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 0d1d0c36ce..8a0c55d5d4 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -18,27 +18,27 @@ Style/BracesAroundHashParameters:
Enabled: true
# Align `when` with `case`.
-Style/CaseIndentation:
+Layout/CaseIndentation:
Enabled: true
# Align comments with method definitions.
-Style/CommentIndentation:
+Layout/CommentIndentation:
Enabled: true
# No extra empty lines.
-Style/EmptyLines:
+Layout/EmptyLines:
Enabled: true
# In a regular class definition, no empty lines around the body.
-Style/EmptyLinesAroundClassBody:
+Layout/EmptyLinesAroundClassBody:
Enabled: true
# In a regular method definition, no empty lines around the body.
-Style/EmptyLinesAroundMethodBody:
+Layout/EmptyLinesAroundMethodBody:
Enabled: true
# In a regular module definition, no empty lines around the body.
-Style/EmptyLinesAroundModuleBody:
+Layout/EmptyLinesAroundModuleBody:
Enabled: true
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
@@ -47,30 +47,30 @@ Style/HashSyntax:
# Method definitions after `private` or `protected` isolated calls need one
# extra level of indentation.
-Style/IndentationConsistency:
+Layout/IndentationConsistency:
Enabled: true
EnforcedStyle: rails
# Two spaces, no tabs (for indentation).
-Style/IndentationWidth:
+Layout/IndentationWidth:
Enabled: true
-Style/SpaceAfterColon:
+Layout/SpaceAfterColon:
Enabled: true
-Style/SpaceAfterComma:
+Layout/SpaceAfterComma:
Enabled: true
-Style/SpaceAroundEqualsInParameterDefault:
+Layout/SpaceAroundEqualsInParameterDefault:
Enabled: true
-Style/SpaceAroundKeyword:
+Layout/SpaceAroundKeyword:
Enabled: true
-Style/SpaceAroundOperators:
+Layout/SpaceAroundOperators:
Enabled: true
-Style/SpaceBeforeFirstArg:
+Layout/SpaceBeforeFirstArg:
Enabled: true
# Defining a method with parameters needs parentheses.
@@ -78,18 +78,18 @@ Style/MethodDefParentheses:
Enabled: true
# Use `foo {}` not `foo{}`.
-Style/SpaceBeforeBlockBraces:
+Layout/SpaceBeforeBlockBraces:
Enabled: true
# Use `foo { bar }` not `foo {bar}`.
-Style/SpaceInsideBlockBraces:
+Layout/SpaceInsideBlockBraces:
Enabled: true
# Use `{ a: 1 }` not `{a:1}`.
-Style/SpaceInsideHashLiteralBraces:
+Layout/SpaceInsideHashLiteralBraces:
Enabled: true
-Style/SpaceInsideParens:
+Layout/SpaceInsideParens:
Enabled: true
# Check quotes usage according to lint rule below.
@@ -98,15 +98,15 @@ Style/StringLiterals:
EnforcedStyle: double_quotes
# Detect hard tabs, no hard tabs.
-Style/Tab:
+Layout/Tab:
Enabled: true
# Blank lines should not have any spaces.
-Style/TrailingBlankLines:
+Layout/TrailingBlankLines:
Enabled: true
# No trailing whitespace.
-Style/TrailingWhitespace:
+Layout/TrailingWhitespace:
Enabled: true
# Use quotes for string literals when they are enough.
diff --git a/.travis.yml b/.travis.yml
index 59f4ed0249..de708c509c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,7 +19,7 @@ addons:
bundler_args: --without test --jobs 3 --retry 3
before_install:
- "rm ${BUNDLE_GEMFILE}.lock"
- - "gem update --system 2.6.11"
+ - "gem update --system"
- "gem update bundler"
- "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
- "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
@@ -98,17 +98,17 @@ matrix:
- "GEM=ar:postgresql POSTGRES=9.2"
addons:
postgresql: "9.2"
- - rvm: jruby-9.1.8.0
+ - rvm: jruby-9.1.10.0
jdk: oraclejdk8
env:
- "GEM=ap"
- - rvm: jruby-9.1.8.0
+ - rvm: jruby-9.1.10.0
jdk: oraclejdk8
env:
- "GEM=am,amo,aj"
allow_failures:
- rvm: ruby-head
- - rvm: jruby-9.1.8.0
+ - rvm: jruby-9.1.10.0
- env: "GEM=ac:integration"
fast_finish: true
diff --git a/Gemfile b/Gemfile
index df25abe65b..8260533cb5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -33,8 +33,8 @@ gem "bcrypt", "~> 3.1.11", require: false
# sprockets.
gem "uglifier", ">= 1.3.0", require: false
-# FIXME: Remove this fork after https://github.com/nex3/rb-inotify/pull/49 is fixed.
-gem "rb-inotify", github: "matthewd/rb-inotify", branch: "close-handling", require: false
+# FIXME: Pending rb-inotify 0.9.9 release
+gem "rb-inotify", github: "guard/rb-inotify", branch: "master", require: false
# Explicitly avoid 1.x that doesn't support Ruby 2.4+
gem "json", ">= 2.0.0"
@@ -90,7 +90,7 @@ group :cable do
end
# Add your own local bundler stuff.
-local_gemfile = File.dirname(__FILE__) + "/.Gemfile"
+local_gemfile = File.expand_path(".Gemfile", __dir__)
instance_eval File.read local_gemfile if File.exist? local_gemfile
group :test do
@@ -154,3 +154,7 @@ end
gem "ibm_db" if ENV["IBM_DB"]
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem "wdm", ">= 0.1.0", platforms: [:mingw, :mswin, :x64_mingw, :mswin64]
+
+platforms :ruby_25 do
+ gem "mathn"
+end
diff --git a/Gemfile.lock b/Gemfile.lock
index 26a7fa8b0e..15b7aeb639 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -7,12 +7,12 @@ GIT
pg (>= 0.17, < 0.20)
GIT
- remote: https://github.com/matthewd/rb-inotify.git
- revision: 90553518d1fb79aedc98a3036c59bd2b6731ac40
- branch: close-handling
+ remote: https://github.com/guard/rb-inotify.git
+ revision: 7e3c714a09ae2b38d2620835e794150d8857cd49
+ branch: master
specs:
- rb-inotify (0.9.7)
- ffi (>= 0.5.0)
+ rb-inotify (0.9.9)
+ ffi (~> 1.0)
GIT
remote: https://github.com/matthewd/websocket-client-simple.git
@@ -148,10 +148,10 @@ GEM
daemons (1.2.4)
dalli (2.7.6)
dante (0.2.0)
- delayed_job (4.1.2)
- activesupport (>= 3.0, < 5.1)
- delayed_job_active_record (4.1.1)
- activerecord (>= 3.0, < 5.1)
+ delayed_job (4.1.3)
+ activesupport (>= 3.0, < 5.2)
+ delayed_job_active_record (4.1.2)
+ activerecord (>= 3.0, < 5.2)
delayed_job (>= 3.0, < 5)
em-hiredis (0.3.1)
eventmachine (~> 1.0)
@@ -207,8 +207,9 @@ GEM
ruby_dep (~> 1.2)
loofah (2.0.3)
nokogiri (>= 1.5.9)
- mail (2.6.4)
+ mail (2.6.5)
mime-types (>= 1.16, < 4)
+ mathn (0.1.0)
metaclass (0.0.4)
method_source (0.8.2)
mime-types (3.1)
@@ -232,6 +233,7 @@ GEM
mini_portile2 (~> 2.1.0)
nokogiri (1.7.1-x86-mingw32)
mini_portile2 (~> 2.1.0)
+ parallel (1.11.2)
parser (2.4.0.0)
ast (~> 2.2)
pg (0.19.0)
@@ -252,7 +254,7 @@ GEM
rack (2.0.3)
rack-cache (1.6.1)
rack (>= 0.4)
- rack-protection (1.5.3)
+ rack-protection (2.0.0)
rack
rack-test (0.6.3)
rack (>= 1.0)
@@ -261,12 +263,13 @@ GEM
nokogiri (~> 1.6)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- rainbow (2.2.1)
+ rainbow (2.2.2)
+ rake
rake (12.0.0)
rb-fsevent (0.9.8)
rdoc (5.1.0)
redcarpet (3.2.3)
- redis (3.3.2)
+ redis (3.3.3)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
resque (1.27.0)
@@ -280,7 +283,8 @@ GEM
redis (~> 3.3)
resque (~> 1.26)
rufus-scheduler (~> 3.2)
- rubocop (0.48.1)
+ rubocop (0.49.0)
+ parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
@@ -307,11 +311,11 @@ GEM
sequel (4.42.1)
serverengine (1.5.11)
sigdump (~> 0.2.2)
- sidekiq (4.2.9)
+ sidekiq (5.0.0)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
- redis (~> 3.2, >= 3.2.1)
+ redis (~> 3.3, >= 3.3.3)
sigdump (0.2.4)
simple_uuid (0.4.0)
sinatra (1.0)
@@ -396,6 +400,7 @@ DEPENDENCIES
kindlerb (~> 1.2.0)
libxml-ruby
listen (>= 3.0.5, < 3.2)
+ mathn
minitest (< 5.3.4)
mocha (~> 0.14)
mysql2 (>= 0.4.4)
@@ -433,4 +438,4 @@ DEPENDENCIES
websocket-client-simple!
BUNDLED WITH
- 1.14.6
+ 1.15.0
diff --git a/Rakefile b/Rakefile
index 202eb5e6fc..ae269fbce7 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,6 +1,6 @@
require "net/http"
-$:.unshift File.expand_path("..", __FILE__)
+$:.unshift __dir__
require "tasks/release"
require "railties/lib/rails/api/task"
diff --git a/actioncable/README.md b/actioncable/README.md
index e044f54b45..d14f20d75b 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -409,7 +409,7 @@ application. The recommended basic setup is as follows:
```ruby
# cable/config.ru
-require ::File.expand_path('../../config/environment', __FILE__)
+require ::File.expand_path('../config/environment', __dir__)
Rails.application.eager_load!
run ActionCable.server
diff --git a/actioncable/Rakefile b/actioncable/Rakefile
index bda8c7b6c8..e21843bb44 100644
--- a/actioncable/Rakefile
+++ b/actioncable/Rakefile
@@ -3,15 +3,13 @@ require "pathname"
require "open3"
require "action_cable"
-dir = File.dirname(__FILE__)
-
task default: :test
task package: %w( assets:compile assets:verify )
Rake::TestTask.new do |t|
t.libs << "test"
- t.test_files = Dir.glob("#{dir}/test/**/*_test.rb")
+ t.test_files = Dir.glob("#{__dir__}/test/**/*_test.rb")
t.warning = true
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
@@ -46,7 +44,7 @@ namespace :assets do
desc "Verify compiled Action Cable assets"
task :verify do
file = "lib/assets/compiled/action_cable.js"
- pathname = Pathname.new("#{dir}/#{file}")
+ pathname = Pathname.new("#{__dir__}/#{file}")
print "[verify] #{file} exists "
if pathname.exist?
@@ -64,8 +62,8 @@ namespace :assets do
fail
end
- print "[verify] #{dir} can be required as a module "
- stdout, stderr, status = Open3.capture3("node", "--print", "window = {}; require('#{dir}');")
+ print "[verify] #{__dir__} can be required as a module "
+ _, stderr, status = Open3.capture3("node", "--print", "window = {}; require('#{__dir__}');")
if status.success?
puts "[OK]"
else
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
index 6d95f022fa..05ffd655e8 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb
index d2856bc6ae..e689fbf21b 100644
--- a/actioncable/lib/action_cable/remote_connections.rb
+++ b/actioncable/lib/action_cable/remote_connections.rb
@@ -45,7 +45,7 @@ module ActionCable
end
# Returns all the identifiers that were applied to this connection.
- def identifiers
+ redefine_method :identifiers do
server.connection_identifiers
end
diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb
index 984b78bc9c..80f512c94c 100644
--- a/actioncable/lib/rails/generators/channel/channel_generator.rb
+++ b/actioncable/lib/rails/generators/channel/channel_generator.rb
@@ -1,7 +1,7 @@
module Rails
module Generators
class ChannelGenerator < NamedBase
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
argument :actions, type: :array, default: [], banner: "method method"
diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb
index a47032753b..5d246c2b76 100644
--- a/actioncable/test/test_helper.rb
+++ b/actioncable/test/test_helper.rb
@@ -11,7 +11,7 @@ rescue LoadError
end
# Require all the stubs and models
-Dir[File.dirname(__FILE__) + "/stubs/*.rb"].each { |file| require file }
+Dir[File.expand_path("stubs/*.rb", __dir__)].each { |file| require file }
class ActionCable::TestCase < ActiveSupport::TestCase
def wait_for_async
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index e75dae6cf9..5eadd01407 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
index 99fe4544f1..bc21b07109 100644
--- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
+++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
@@ -1,7 +1,7 @@
module Rails
module Generators
class MailerGenerator < NamedBase
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
argument :actions, type: :array, default: [], banner: "method method"
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index a646cbd581..dbfdb07e6e 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -9,7 +9,7 @@ end
module Rails
def self.root
- File.expand_path("../", File.dirname(__FILE__))
+ File.expand_path("..", __dir__)
end
end
@@ -28,7 +28,7 @@ ActiveSupport::Deprecation.debug = true
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-FIXTURE_LOAD_PATH = File.expand_path("fixtures", File.dirname(__FILE__))
+FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__)
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
class ActiveSupport::TestCase
diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb
index cff49c8894..e76466439e 100644
--- a/actionmailer/test/caching_test.rb
+++ b/actionmailer/test/caching_test.rb
@@ -5,7 +5,7 @@ require "mailers/caching_mailer"
CACHE_DIR = "test_cache"
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
-FILE_STORE_PATH = File.join(File.dirname(__FILE__), "/../temp/", CACHE_DIR)
+FILE_STORE_PATH = File.join(__dir__, "/../temp/", CACHE_DIR)
class FragmentCachingMailer < ActionMailer::Base
abstract!
@@ -21,10 +21,6 @@ class BaseCachingTest < ActiveSupport::TestCase
@mailer.perform_caching = true
@mailer.cache_store = @store
end
-
- def test_fragment_cache_key
- assert_equal "views/what a key", @mailer.fragment_cache_key("what a key")
- end
end
class FragmentCachingTest < BaseCachingTest
@@ -126,7 +122,7 @@ class FunctionalFragmentCachingTest < BaseCachingTest
assert_match expected_body, email.body.encoded
assert_match expected_body,
- @store.read("views/caching/#{template_digest("caching_mailer/fragment_cache")}")
+ @store.read("views/caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache")}/caching")
end
def test_fragment_caching_in_partials
@@ -135,7 +131,7 @@ class FunctionalFragmentCachingTest < BaseCachingTest
assert_match(expected_body, email.body.encoded)
assert_match(expected_body,
- @store.read("views/caching/#{template_digest("caching_mailer/_partial")}"))
+ @store.read("views/caching_mailer/_partial:#{template_digest("caching_mailer/_partial")}/caching"))
end
def test_skip_fragment_cache_digesting
@@ -185,7 +181,7 @@ class FunctionalFragmentCachingTest < BaseCachingTest
end
assert_equal "caching_mailer", payload[:mailer]
- assert_equal "views/caching/#{template_digest("caching_mailer/fragment_cache")}", payload[:key]
+ assert_equal [ :views, "caching_mailer/fragment_cache:#{template_digest("caching_mailer/fragment_cache")}", :caching ], payload[:key]
ensure
@mailer.enable_fragment_cache_logging = true
end
diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb
index 7969782e07..799c6144d7 100644
--- a/actionmailer/test/log_subscriber_test.rb
+++ b/actionmailer/test/log_subscriber_test.rb
@@ -36,7 +36,7 @@ class AMLogSubscriberTest < ActionMailer::TestCase
end
def test_receive_is_notified
- fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
+ fixture = File.read(File.expand_path("fixtures/raw_email", __dir__))
TestMailer.receive(fixture)
wait
assert_equal(1, @logger.logged(:info).size)
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index cc908dcc43..54937617df 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,4 +1,27 @@
-* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load`
+* AEAD encrypted cookies and sessions with GCM
+
+ Encrypted cookies now use AES-GCM which couples authentication and
+ encryption in one faster step and produces shorter ciphertexts. Cookies
+ encrypted using AES in CBC HMAC mode will be seamlessly upgraded when
+ this new mode is enabled via the
+ `action_dispatch.use_authenticated_cookie_encryption` configuration value.
+
+ *Michael J Coyne*
+
+* Change the cache key format for fragments to make it easier to debug key churn. The new format is:
+
+ views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123
+ ^template path ^template tree digest ^class ^id
+
+ *DHH*
+
+* Add support for recyclable cache keys with fragment caching. This uses the new versioned entries in the
+ `ActiveSupport::Cache` stores and relies on the fact that Active Record has split `#cache_key` and `#cache_version`
+ to support it.
+
+ *DHH*
+
+* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load`
`ActionController::Base` and `ActionController::API` have differing implementations. This means that
the one umbrella hook `action_controller` is not able to address certain situations where a method
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 31dd1865f9..69408c8aab 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -26,7 +26,7 @@ namespace :test do
end
task :lines do
- load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics"
+ load File.expand_path("..", __dir__) + "/tools/line_statistics"
files = FileList["lib/**/*.rb"]
CodeTools::LineStatistics.new(files).print_loc
end
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 2c24a54305..31803042dd 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index e7cb6347a2..dc79820a82 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -14,8 +14,16 @@ module AbstractController
# expected to provide their own +render+ method, since rendering means
# different things depending on the context.
class Base
+ ##
+ # Returns the body of the HTTP response sent by the controller.
attr_internal :response_body
+
+ ##
+ # Returns the name of the action this controller is processing.
attr_internal :action_name
+
+ ##
+ # Returns the formats that can be processed by the controller.
attr_internal :formats
include ActiveSupport::Configurable
diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb
index c85b4adba1..14e4a82523 100644
--- a/actionpack/lib/abstract_controller/caching/fragments.rb
+++ b/actionpack/lib/abstract_controller/caching/fragments.rb
@@ -25,7 +25,10 @@ module AbstractController
self.fragment_cache_keys = []
- helper_method :fragment_cache_key if respond_to?(:helper_method)
+ if respond_to?(:helper_method)
+ helper_method :fragment_cache_key
+ helper_method :combined_fragment_cache_key
+ end
end
module ClassMethods
@@ -62,17 +65,36 @@ module AbstractController
# with the specified +key+ value. The key is expanded using
# ActiveSupport::Cache.expand_cache_key.
def fragment_cache_key(key)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Calling fragment_cache_key directly is deprecated and will be removed in Rails 6.0.
+ All fragment accessors now use the combined_fragment_cache_key method that retains the key as an array,
+ such that the caching stores can interrogate the parts for cache versions used in
+ recyclable cache keys.
+ MSG
+
head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
ActiveSupport::Cache.expand_cache_key([*head, *tail], :views)
end
+ # Given a key (as described in +expire_fragment+), returns
+ # a key array suitable for use in reading, writing, or expiring a
+ # cached fragment. All keys begin with <tt>:views</tt>,
+ # followed by ENV["RAILS_CACHE_ID"] or ENV["RAILS_APP_VERSION"] if set,
+ # followed by any controller-wide key prefix values, ending
+ # with the specified +key+ value.
+ def combined_fragment_cache_key(key)
+ head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
+ tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
+ [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact
+ end
+
# Writes +content+ to the location signified by
# +key+ (see +expire_fragment+ for acceptable formats).
def write_fragment(key, content, options = nil)
return content unless cache_configured?
- key = fragment_cache_key(key)
+ key = combined_fragment_cache_key(key)
instrument_fragment_cache :write_fragment, key do
content = content.to_str
cache_store.write(key, content, options)
@@ -85,7 +107,7 @@ module AbstractController
def read_fragment(key, options = nil)
return unless cache_configured?
- key = fragment_cache_key(key)
+ key = combined_fragment_cache_key(key)
instrument_fragment_cache :read_fragment, key do
result = cache_store.read(key, options)
result.respond_to?(:html_safe) ? result.html_safe : result
@@ -96,7 +118,7 @@ module AbstractController
# +key+ exists (see +expire_fragment+ for acceptable formats).
def fragment_exist?(key, options = nil)
return unless cache_configured?
- key = fragment_cache_key(key)
+ key = combined_fragment_cache_key(key)
instrument_fragment_cache :exist_fragment?, key do
cache_store.exist?(key, options)
@@ -123,7 +145,7 @@ module AbstractController
# method (or <tt>delete_matched</tt>, for Regexp keys).
def expire_fragment(key, options = nil)
return unless cache_configured?
- key = fragment_cache_key(key) unless key.is_a?(Regexp)
+ key = combined_fragment_cache_key(key) unless key.is_a?(Regexp)
instrument_fragment_cache :expire_fragment, key do
if key.is_a?(Regexp)
@@ -135,8 +157,7 @@ module AbstractController
end
def instrument_fragment_cache(name, key) # :nodoc:
- payload = instrument_payload(key)
- ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", payload) { yield }
+ ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", instrument_payload(key)) { yield }
end
end
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index ce4ecf17cc..ba7dec6083 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -1,4 +1,24 @@
module AbstractController
+ # = Abstract Controller Callbacks
+ #
+ # Abstract Controller provides hooks during the life cycle of a controller action.
+ # Callbacks allow you to trigger logic during this cycle. Available callbacks are:
+ #
+ # * <tt>after_action</tt>
+ # * <tt>append_after_action</tt>
+ # * <tt>append_around_action</tt>
+ # * <tt>append_before_action</tt>
+ # * <tt>around_action</tt>
+ # * <tt>before_action</tt>
+ # * <tt>prepend_after_action</tt>
+ # * <tt>prepend_around_action</tt>
+ # * <tt>prepend_before_action</tt>
+ # * <tt>skip_after_action</tt>
+ # * <tt>skip_around_action</tt>
+ # * <tt>skip_before_action</tt>
+ #
+ # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
+ #
module Callbacks
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index d29a5fe68f..d00fcbcd13 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -60,9 +60,9 @@ module ActionController
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{method}(event)
return unless logger.info? && ActionController::Base.enable_fragment_cache_logging
- key_or_path = event.payload[:key] || event.payload[:path]
+ key = ActiveSupport::Cache.expand_cache_key(event.payload[:key] || event.payload[:path])
human_name = #{method.to_s.humanize.inspect}
- info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)")
+ info("\#{human_name} \#{key} (\#{event.duration.round(1)}ms)")
end
METHOD
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 61ba052e45..225272d66e 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -27,14 +27,18 @@ module ActionDispatch
@tempfile = hash[:tempfile]
raise(ArgumentError, ":tempfile is required") unless @tempfile
- @original_filename = hash[:filename]
- if @original_filename
+ if hash[:filename]
+ @original_filename = hash[:filename].dup
+
begin
@original_filename.encode!(Encoding::UTF_8)
rescue EncodingError
@original_filename.force_encoding(Encoding::UTF_8)
end
+ else
+ @original_filename = nil
end
+
@content_type = hash[:type]
@headers = hash[:head]
end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
index d692f6415c..62f052ced6 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
@@ -18,14 +18,6 @@ module ActionDispatch
@tt = transition_table
end
- def simulate(string)
- ms = memos(string) { return }
- MatchData.new(ms)
- end
-
- alias :=~ :simulate
- alias :match :simulate
-
def memos(string)
input = StringScanner.new(string)
state = [0]
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index e1ac2c873e..45aff287b1 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -82,7 +82,7 @@ module ActionDispatch
end
def visualizer(paths, title = "FSM")
- viz_dir = File.join File.dirname(__FILE__), "..", "visualizer"
+ viz_dir = File.join __dir__, "..", "visualizer"
fsm_js = File.read File.join(viz_dir, "fsm.js")
fsm_css = File.read File.join(viz_dir, "fsm.css")
erb = File.read File.join(viz_dir, "index.html.erb")
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index e565c03a8a..c0dda1bba5 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -43,6 +43,10 @@ module ActionDispatch
get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT
end
+ def authenticated_encrypted_cookie_salt
+ get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT
+ end
+
def secret_token
get_header Cookies::SECRET_TOKEN
end
@@ -149,6 +153,7 @@ module ActionDispatch
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
+ AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
@@ -207,6 +212,9 @@ module ActionDispatch
# If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
+ # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
+ # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
+ #
# This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
#
# Example:
@@ -219,6 +227,8 @@ module ActionDispatch
@encrypted ||=
if upgrade_legacy_signed_cookies?
UpgradeLegacyEncryptedCookieJar.new(self)
+ elsif upgrade_legacy_hmac_aes_cbc_cookies?
+ UpgradeLegacyHmacAesCbcCookieJar.new(self)
else
EncryptedCookieJar.new(self)
end
@@ -240,6 +250,13 @@ module ActionDispatch
def upgrade_legacy_signed_cookies?
request.secret_token.present? && request.secret_key_base.present?
end
+
+ def upgrade_legacy_hmac_aes_cbc_cookies?
+ request.secret_key_base.present? &&
+ request.authenticated_encrypted_cookie_salt.present? &&
+ request.encrypted_signed_cookie_salt.present? &&
+ request.encrypted_cookie_salt.present?
+ end
end
# Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
@@ -576,9 +593,11 @@ module ActionDispatch
"Read the upgrade documentation to learn more about this new config option."
end
- secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len]
- sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "")
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ cipher = "aes-256-gcm"
+ key_len = ActiveSupport::MessageEncryptor.key_len(cipher)
+ secret = key_generator.generate_key(request.authenticated_encrypted_cookie_salt || "")[0, key_len]
+
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
end
private
@@ -603,6 +622,32 @@ module ActionDispatch
include VerifyAndUpgradeLegacySignedMessage
end
+ # UpgradeLegacyHmacAesCbcCookieJar is used by ActionDispatch::Session::CookieStore
+ # to upgrade cookies encrypted with AES-256-CBC with HMAC to AES-256-GCM
+ class UpgradeLegacyHmacAesCbcCookieJar < EncryptedCookieJar
+ def initialize(parent_jar)
+ super
+
+ secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len]
+ sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "")
+
+ @legacy_encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ end
+
+ def decrypt_and_verify_legacy_encrypted_message(name, signed_message)
+ deserialize(name, @legacy_encryptor.decrypt_and_verify(signed_message)).tap do |value|
+ self[name] = { value: value }
+ end
+ rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
+ nil
+ end
+
+ private
+ def parse(name, signed_message)
+ super || decrypt_and_verify_legacy_encrypted_message(name, signed_message)
+ end
+ end
+
def initialize(app)
@app = app
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 1c720c5a8e..336a775880 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -10,7 +10,7 @@ module ActionDispatch
# This middleware is responsible for logging exceptions and
# showing a debugging page in case the request is local.
class DebugExceptions
- RESCUES_TEMPLATE_PATH = File.expand_path("../templates", __FILE__)
+ RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
class DebugView < ActionView::Base
def debug_params(params)
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 16a18a7f25..7662e164b8 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -16,6 +16,7 @@ module ActionDispatch
config.action_dispatch.signed_cookie_salt = "signed cookie"
config.action_dispatch.encrypted_cookie_salt = "encrypted cookie"
config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie"
+ config.action_dispatch.use_authenticated_cookie_encryption = false
config.action_dispatch.perform_deep_munge = true
config.action_dispatch.default_headers = {
@@ -36,6 +37,8 @@ module ActionDispatch
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
+ config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie" if config.action_dispatch.use_authenticated_cookie_encryption
+
config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 74ba6466cf..3547a8604f 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -101,11 +101,13 @@ module ActionDispatch
# Returns keys of the session as Array.
def keys
+ load_for_read!
@delegate.keys
end
# Returns values of the session as Array.
def values
+ load_for_read!
@delegate.values
end
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
index 187ba2cc5f..f03f0d4299 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
@@ -2,7 +2,12 @@ module ActionDispatch
module SystemTesting
module TestHelpers
module SetupAndTeardown # :nodoc:
- DEFAULT_HOST = "127.0.0.1"
+ DEFAULT_HOST = "http://127.0.0.1"
+
+ def host!(host)
+ super
+ Capybara.app_host = host
+ end
def before_setup
host! DEFAULT_HOST
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 4185ce1a1f..bd118b46be 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -1,6 +1,6 @@
-$:.unshift(File.dirname(__FILE__) + "/lib")
-$:.unshift(File.dirname(__FILE__) + "/fixtures/helpers")
-$:.unshift(File.dirname(__FILE__) + "/fixtures/alternate_helpers")
+$:.unshift File.expand_path("lib", __dir__)
+$:.unshift File.expand_path("fixtures/helpers", __dir__)
+$:.unshift File.expand_path("fixtures/alternate_helpers", __dir__)
require "active_support/core_ext/kernel/reporting"
@@ -56,7 +56,7 @@ ActiveSupport::Deprecation.debug = true
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), "fixtures")
+FIXTURE_LOAD_PATH = File.join(__dir__, "fixtures")
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
@@ -156,7 +156,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
end
def with_autoload_path(path)
- path = File.join(File.dirname(__FILE__), "fixtures", path)
+ path = File.join(__dir__, "fixtures", path)
if ActiveSupport::Dependencies.autoload_paths.include?(path)
yield
else
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 9ab152fc5c..73aab5848b 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -83,7 +83,7 @@ class ActionPackAssertionsController < ActionController::Base
end
def render_file_absolute_path
- render file: File.expand_path("../../../README.rdoc", __FILE__)
+ render file: File.expand_path("../../README.rdoc", __dir__)
end
def render_file_relative_path
diff --git a/actionpack/test/controller/api/data_streaming_test.rb b/actionpack/test/controller/api/data_streaming_test.rb
index f15b78d102..e6419b9adf 100644
--- a/actionpack/test/controller/api/data_streaming_test.rb
+++ b/actionpack/test/controller/api/data_streaming_test.rb
@@ -1,7 +1,7 @@
require "abstract_unit"
module TestApiFileUtils
- def file_path() File.expand_path(__FILE__) end
+ def file_path() __FILE__ end
def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index fa8d9dc09a..c86dcafee5 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -4,7 +4,7 @@ require "lib/controller/fake_models"
CACHE_DIR = "test_cache"
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
-FILE_STORE_PATH = File.join(File.dirname(__FILE__), "/../temp/", CACHE_DIR)
+FILE_STORE_PATH = File.join(__dir__, "../temp/", CACHE_DIR)
class FragmentCachingMetalTestController < ActionController::Metal
abstract!
@@ -26,10 +26,6 @@ class FragmentCachingMetalTest < ActionController::TestCase
@controller.request = @request
@controller.response = @response
end
-
- def test_fragment_cache_key
- assert_equal "views/what a key", @controller.fragment_cache_key("what a key")
- end
end
class CachingController < ActionController::Base
@@ -43,6 +39,8 @@ class FragmentCachingTestController < CachingController
end
class FragmentCachingTest < ActionController::TestCase
+ ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version)
+
def setup
super
@store = ActiveSupport::Cache::MemoryStore.new
@@ -53,12 +51,25 @@ class FragmentCachingTest < ActionController::TestCase
@controller.params = @params
@controller.request = @request
@controller.response = @response
+
+ @m1v1 = ModelWithKeyAndVersion.new("model/1", "1")
+ @m1v2 = ModelWithKeyAndVersion.new("model/1", "2")
+ @m2v1 = ModelWithKeyAndVersion.new("model/2", "1")
+ @m2v2 = ModelWithKeyAndVersion.new("model/2", "2")
end
def test_fragment_cache_key
- assert_equal "views/what a key", @controller.fragment_cache_key("what a key")
- assert_equal "views/test.host/fragment_caching_test/some_action",
- @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action")
+ assert_deprecated do
+ assert_equal "views/what a key", @controller.fragment_cache_key("what a key")
+ assert_equal "views/test.host/fragment_caching_test/some_action",
+ @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action")
+ end
+ end
+
+ def test_combined_fragment_cache_key
+ assert_equal [ :views, "what a key" ], @controller.combined_fragment_cache_key("what a key")
+ assert_equal [ :views, "test.host/fragment_caching_test/some_action" ],
+ @controller.combined_fragment_cache_key(controller: "fragment_caching_test", action: "some_action")
end
def test_read_fragment_with_caching_enabled
@@ -72,6 +83,12 @@ class FragmentCachingTest < ActionController::TestCase
assert_nil @controller.read_fragment("name")
end
+ def test_read_fragment_with_versioned_model
+ @controller.write_fragment([ "stuff", @m1v1 ], "hello")
+ assert_equal "hello", @controller.read_fragment([ "stuff", @m1v1 ])
+ assert_nil @controller.read_fragment([ "stuff", @m1v2 ])
+ end
+
def test_fragment_exist_with_caching_enabled
@store.write("views/name", "value")
assert @controller.fragment_exist?("name")
@@ -198,7 +215,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal "This bit's fragment cached",
- @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached")}")
+ @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached")}/fragment")
end
def test_fragment_caching_in_partials
@@ -207,7 +224,7 @@ CACHED
assert_match(/Old fragment caching in a partial/, @response.body)
assert_match("Old fragment caching in a partial",
- @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial")}"))
+ @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial")}/test.host/functional_caching/html_fragment_cached_with_partial"))
end
def test_skipping_fragment_cache_digesting
@@ -237,7 +254,7 @@ CACHED
assert_match(/Some inline content/, @response.body)
assert_match(/Some cached content/, @response.body)
assert_match("Some cached content",
- @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached")}"))
+ @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached")}/test.host/functional_caching/inline_fragment_cached"))
end
def test_fragment_cache_instrumentation
@@ -264,7 +281,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal "<p>ERB</p>",
- @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}")
+ @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment")
end
def test_xml_formatted_fragment_caching
@@ -275,7 +292,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal " <p>Builder</p>\n",
- @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}")
+ @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment")
end
def test_fragment_caching_with_variant
@@ -286,7 +303,7 @@ CACHED
assert_equal expected_body, @response.body
assert_equal "<p>PHONE</p>",
- @store.read("views/test.host/functional_caching/formatted_fragment_cached_with_variant/#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}")
+ @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}/fragment")
end
private
@@ -412,7 +429,7 @@ class CollectionCacheTest < ActionController::TestCase
def test_collection_fetches_cached_views
get :index
assert_equal 1, @controller.partial_rendered_times
- assert_customer_cached "david/1", "david, 1"
+ assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/david/1")
get :index
assert_equal 1, @controller.partial_rendered_times
@@ -444,14 +461,8 @@ class CollectionCacheTest < ActionController::TestCase
def test_caching_with_callable_cache_key
get :index_with_callable_cache_key
- assert_customer_cached "cached_david", "david, 1"
+ assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/cached_david")
end
-
- private
- def assert_customer_cached(key, content)
- assert_match content,
- ActionView::PartialRenderer.collection_cache.read("views/#{key}/7c228ab609f0baf0b1f2367469210937")
- end
end
class FragmentCacheKeyTestController < CachingController
@@ -470,11 +481,21 @@ class FragmentCacheKeyTest < ActionController::TestCase
@controller.cache_store = @store
end
- def test_fragment_cache_key
+ def test_combined_fragment_cache_key
@controller.account_id = "123"
- assert_equal "views/v1/123/what a key", @controller.fragment_cache_key("what a key")
+ assert_equal [ :views, "v1", "123", "what a key" ], @controller.combined_fragment_cache_key("what a key")
@controller.account_id = nil
- assert_equal "views/v1//what a key", @controller.fragment_cache_key("what a key")
+ assert_equal [ :views, "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key")
+ end
+
+ def test_combined_fragment_cache_key_with_envs
+ ENV["RAILS_APP_VERSION"] = "55"
+ assert_equal [ :views, "55", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key")
+
+ ENV["RAILS_CACHE_ID"] = "66"
+ assert_equal [ :views, "66", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key")
+ ensure
+ ENV["RAILS_CACHE_ID"] = ENV["RAILS_APP_VERSION"] = nil
end
end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 4c6a772062..03dbd63614 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -1,6 +1,6 @@
require "abstract_unit"
-ActionController::Base.helpers_path = File.expand_path("../../fixtures/helpers", __FILE__)
+ActionController::Base.helpers_path = File.expand_path("../fixtures/helpers", __dir__)
module Fun
class GamesController < ActionController::Base
@@ -48,7 +48,7 @@ end
class HelpersPathsController < ActionController::Base
paths = ["helpers2_pack", "helpers1_pack"].map do |path|
- File.join(File.expand_path("../../fixtures", __FILE__), path)
+ File.join(File.expand_path("../fixtures", __dir__), path)
end
$:.unshift(*paths)
@@ -61,7 +61,7 @@ class HelpersPathsController < ActionController::Base
end
class HelpersTypoController < ActionController::Base
- path = File.expand_path("../../fixtures/helpers_typo", __FILE__)
+ path = File.expand_path("../fixtures/helpers_typo", __dir__)
$:.unshift(path)
self.helpers_path = path
end
@@ -178,7 +178,7 @@ class HelperTest < ActiveSupport::TestCase
end
def test_all_helpers_with_alternate_helper_dir
- @controller_class.helpers_path = File.expand_path("../../fixtures/alternate_helpers", __FILE__)
+ @controller_class.helpers_path = File.expand_path("../fixtures/alternate_helpers", __dir__)
# Reload helpers
@controller_class._helpers = Module.new
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 57f58fd835..72163ccd5e 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -1091,7 +1091,7 @@ class IntegrationFileUploadTest < ActionDispatch::IntegrationTest
end
def self.fixture_path
- File.dirname(__FILE__) + "/../fixtures/multipart"
+ File.expand_path("../fixtures/multipart", __dir__)
end
routes.draw do
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 581081dd07..bfb47b90d5 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -152,7 +152,7 @@ module ActionController
end
def write_sleep_autoload
- path = File.join(File.dirname(__FILE__), "../fixtures")
+ path = File.expand_path("../fixtures", __dir__)
ActiveSupport::Dependencies.autoload_paths << path
response.headers["Content-Type"] = "text/event-stream"
diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb
index a22fa39051..d1c4dbfef7 100644
--- a/actionpack/test/controller/mime/accept_format_test.rb
+++ b/actionpack/test/controller/mime/accept_format_test.rb
@@ -29,7 +29,7 @@ class StarStarMimeControllerTest < ActionController::TestCase
end
class AbstractPostController < ActionController::Base
- self.view_paths = File.dirname(__FILE__) + "/../../fixtures/post_test/"
+ self.view_paths = File.expand_path("../../fixtures/post_test", __dir__)
end
# For testing layouts which are set automatically
diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb
index 6d651e0104..4491dd96ed 100644
--- a/actionpack/test/controller/new_base/render_file_test.rb
+++ b/actionpack/test/controller/new_base/render_file_test.rb
@@ -2,15 +2,15 @@ require "abstract_unit"
module RenderFile
class BasicController < ActionController::Base
- self.view_paths = File.dirname(__FILE__)
+ self.view_paths = __dir__
def index
- render file: File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world])
+ render file: File.expand_path("../../fixtures/test/hello_world", __dir__)
end
def with_instance_variables
@secret = "in the sauce"
- render file: File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar")
+ render file: File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__)
end
def relative_path
@@ -25,11 +25,11 @@ module RenderFile
def pathname
@secret = "in the sauce"
- render file: Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar])
+ render file: Pathname.new(__dir__).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar])
end
def with_locals
- path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals")
+ path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__)
render file: path, locals: { secret: "in the sauce" }
end
end
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 796283466a..c5fc8e15e1 100644
--- a/actionpack/test/controller/new_base/render_implicit_action_test.rb
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -6,7 +6,7 @@ module RenderImplicitAction
"render_implicit_action/simple/hello_world.html.erb" => "Hello world!",
"render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!",
"render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented"
- ), ActionView::FileSystemResolver.new(File.expand_path("../../../controller", __FILE__))]
+ ), ActionView::FileSystemResolver.new(File.expand_path("../../controller", __dir__))]
def hello_world() end
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 3a0a0a8bde..17d834d55f 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -257,7 +257,7 @@ end
module TemplateModificationHelper
private
def modify_template(name)
- path = File.expand_path("../../fixtures/#{name}.erb", __FILE__)
+ path = File.expand_path("../fixtures/#{name}.erb", __dir__)
original = File.read(path)
File.write(path, "#{original} Modified!")
ActionView::LookupContext::DetailsKey.clear
@@ -287,9 +287,9 @@ class ExpiresInRenderTest < ActionController::TestCase
def test_dynamic_render_with_file
# This is extremely bad, but should be possible to do.
- assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))
+ assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
response = get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' }
- assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")),
+ assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)),
response.body
end
@@ -306,16 +306,16 @@ class ExpiresInRenderTest < ActionController::TestCase
end
def test_dynamic_render
- assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))
+ assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
assert_raises ActionView::MissingTemplate do
get :dynamic_render, params: { id: '../\\../test/abstract_unit.rb' }
end
end
def test_permitted_dynamic_render_file_hash
- assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))
+ assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
response = get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } }
- assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")),
+ assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)),
response.body
end
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 9e6b975fe2..e265c6c49c 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -2,7 +2,7 @@ require "abstract_unit"
module TestFileUtils
def file_name() File.basename(__FILE__) end
- def file_path() File.expand_path(__FILE__) end
+ def file_path() __FILE__ end
def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 3a4307b64b..677e2ddded 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -122,7 +122,7 @@ XML
end
def test_send_file
- send_file(File.expand_path(__FILE__))
+ send_file(__FILE__)
end
def redirect_to_same_controller
@@ -780,7 +780,7 @@ XML
end
end
- FILES_DIR = File.dirname(__FILE__) + "/../fixtures/multipart"
+ FILES_DIR = File.expand_path("../fixtures/multipart", __dir__)
READ_BINARY = "rb:binary"
READ_PLAIN = "r:binary"
@@ -855,7 +855,7 @@ XML
end
def test_fixture_file_upload_ignores_fixture_path_given_full_path
- TestCaseTest.stub :fixture_path, File.dirname(__FILE__) do
+ TestCaseTest.stub :fixture_path, __dir__ do
uploaded_file = fixture_file_upload("#{FILES_DIR}/ruby_on_rails.jpg", "image/jpg")
assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 664faa31bb..e5646de82e 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -288,8 +288,7 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new(SALT, iterations: 2)
@request.env["action_dispatch.signed_cookie_salt"] =
- @request.env["action_dispatch.encrypted_cookie_salt"] =
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+ @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] = SALT
@request.host = "www.nextangle.com"
end
@@ -531,9 +530,7 @@ class CookiesTest < ActionController::TestCase
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "bar", cookies[:foo]
- assert_raise TypeError do
- cookies.signed[:foo]
- end
+ assert_nil cookies.signed[:foo]
assert_equal "bar", cookies.encrypted[:foo]
end
@@ -542,9 +539,7 @@ class CookiesTest < ActionController::TestCase
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "bar", cookies[:foo]
- assert_raises TypeError do
- cookies.signed[:foo]
- end
+ assert_nil cookies.signed[:foo]
assert_equal "bar", cookies.encrypted[:foo]
end
@@ -553,9 +548,7 @@ class CookiesTest < ActionController::TestCase
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "bar", cookies[:foo]
- assert_raises ::JSON::ParserError do
- cookies.signed[:foo]
- end
+ assert_nil cookies.signed[:foo]
assert_equal "bar", cookies.encrypted[:foo]
end
@@ -564,9 +557,7 @@ class CookiesTest < ActionController::TestCase
get :set_wrapped_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "wrapped: bar", cookies[:foo]
- assert_raises ::JSON::ParserError do
- cookies.signed[:foo]
- end
+ assert_nil cookies.signed[:foo]
assert_equal "wrapped: bar", cookies.encrypted[:foo]
end
@@ -577,38 +568,16 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar was dumped and loaded", cookies.encrypted[:foo]
end
- def test_encrypted_cookie_using_custom_digest
- @request.env["action_dispatch.cookies_digest"] = "SHA256"
- get :set_encrypted_cookie
- cookies = @controller.send :cookies
- assert_not_equal "bar", cookies[:foo]
- assert_equal "bar", cookies.encrypted[:foo]
-
- sign_secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
-
- sha1_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA1")
- sha256_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA256")
-
- assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do
- sha1_verifier.verify(cookies[:foo])
- end
-
- assert_nothing_raised do
- sha256_verifier.verify(cookies[:foo])
- end
- end
-
def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
@request.env["action_dispatch.cookies_serializer"] = :hybrid
- key_generator = @request.env["action_dispatch.key_generator"]
- encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
- encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
- secret = key_generator.generate_key(encrypted_cookie_salt)
- sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal)
- marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: Marshal).encrypt_and_sign("bar")
- @request.headers["Cookie"] = "foo=#{marshal_value}"
+ marshal_value = encryptor.encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape marshal_value}"
get :get_encrypted_cookie
@@ -616,40 +585,28 @@ class CookiesTest < ActionController::TestCase
assert_not_equal "bar", cookies[:foo]
assert_equal "bar", cookies.encrypted[:foo]
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
- assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
+ json_encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
+ assert_not_nil @response.cookies["foo"]
+ assert_equal "bar", json_encryptor.decrypt_and_verify(@response.cookies["foo"])
end
def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
@request.env["action_dispatch.cookies_serializer"] = :hybrid
- key_generator = @request.env["action_dispatch.key_generator"]
- encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
- encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
- secret = key_generator.generate_key(encrypted_cookie_salt)
- sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
- json_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON).encrypt_and_sign("bar")
- @request.headers["Cookie"] = "foo=#{json_value}"
-
- get :get_encrypted_cookie
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
- cookies = @controller.send :cookies
- assert_not_equal "bar", cookies[:foo]
- assert_equal "bar", cookies.encrypted[:foo]
-
- assert_nil @response.cookies["foo"]
- end
-
- def test_compat_encrypted_cookie_using_64_byte_key
- # Cookie generated with 64 bytes secret
- message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*")
- @request.headers["Cookie"] = "foo=#{message}"
+ json_value = encryptor.encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape json_value}"
get :get_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal "bar", cookies[:foo]
assert_equal "bar", cookies.encrypted[:foo]
+
assert_nil @response.cookies["foo"]
end
@@ -813,10 +770,10 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
- sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -842,8 +799,6 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.cookies_serializer"] = :json
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
@@ -852,10 +807,10 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
- sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -881,8 +836,6 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.cookies_serializer"] = :hybrid
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
@@ -891,10 +844,10 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
- sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -920,8 +873,6 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.cookies_serializer"] = :hybrid
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar")
@@ -930,10 +881,10 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- key_generator = @request.env["action_dispatch.key_generator"]
- secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
- sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON)
+ cipher = "aes-256-gcm"
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -959,6 +910,89 @@ class CookiesTest < ActionController::TestCase
assert_nil @response.cookies["foo"]
end
+ def test_legacy_hmac_aes_cbc_encrypted_marshal_cookie_is_upgraded_to_authenticated_encrypted_cookie
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.env["action_dispatch.encrypted_cookie_salt"] =
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: Marshal).encrypt_and_sign("bar")
+
+ @request.headers["Cookie"] = "foo=#{marshal_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ aead_cipher = "aes-256-gcm"
+ aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len(aead_cipher)]
+ aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: aead_cipher, serializer: Marshal)
+
+ assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_hmac_aes_cbc_encrypted_json_cookie_is_upgraded_to_authenticated_encrypted_cookie
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.env["action_dispatch.encrypted_cookie_salt"] =
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON).encrypt_and_sign("bar")
+
+ @request.headers["Cookie"] = "foo=#{marshal_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ aead_cipher = "aes-256-gcm"
+ aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len(aead_cipher)]
+ aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: aead_cipher, serializer: JSON)
+
+ assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_hmac_aes_cbc_encrypted_cookie_using_64_byte_key_is_upgraded_to_authenticated_encrypted_cookie
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.env["action_dispatch.encrypted_cookie_salt"] =
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+
+ # Cookie generated with 64 bytes secret
+ message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*")
+ @request.headers["Cookie"] = "foo=#{message}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+ cipher = "aes-256-gcm"
+
+ salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal)
+
+ assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
def test_cookie_with_all_domain_option
get :set_cookie_with_domain
assert_response :success
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 01c5ff1429..e7e8c82974 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -21,7 +21,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
end
- FIXTURE_PATH = File.dirname(__FILE__) + "/../../fixtures/multipart"
+ FIXTURE_PATH = File.expand_path("../../fixtures/multipart", __dir__)
def teardown
TestController.last_request_parameters = nil
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index 311b80ea0a..228135c547 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -54,6 +54,11 @@ module ActionDispatch
assert_equal %w[rails adequate], s.keys
end
+ def test_keys_with_deferred_loading
+ s = Session.create(store_with_data, req, {})
+ assert_equal %w[sample_key], s.keys
+ end
+
def test_values
s = Session.create(store, req, {})
s["rails"] = "ftw"
@@ -61,6 +66,11 @@ module ActionDispatch
assert_equal %w[ftw awesome], s.values
end
+ def test_values_with_deferred_loading
+ s = Session.create(store_with_data, req, {})
+ assert_equal %w[sample_value], s.values
+ end
+
def test_clear
s = Session.create(store, req, {})
s["rails"] = "ftw"
@@ -113,6 +123,14 @@ module ActionDispatch
def delete_session(env, id, options); 123; end
}.new
end
+
+ def store_with_data
+ Class.new {
+ def load_session(env); [1, { "sample_key" => "sample_value" }]; end
+ def session_exists?(env); true; end
+ def delete_session(env, id, options); 123; end
+ }.new
+ end
end
class SessionIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 1169bf0cdb..6721a388c1 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -107,7 +107,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
query = [
"customers[boston][first][name]=David",
"something_else=blah",
- "logo=#{File.expand_path(__FILE__)}"
+ "logo=#{__FILE__}"
].join("&")
expected = {
"customers" => {
@@ -118,7 +118,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
}
},
"something_else" => "blah",
- "logo" => File.expand_path(__FILE__),
+ "logo" => __FILE__,
}
assert_parses expected, query
end
diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
index 33d98f924f..8f90e45f5f 100644
--- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -19,3 +19,15 @@ class SetDriverToSeleniumTest < DrivenBySeleniumWithChrome
assert_equal :selenium, Capybara.current_driver
end
end
+
+class SetHostTest < DrivenByRackTest
+ test "sets default host" do
+ assert_equal "http://127.0.0.1", Capybara.app_host
+ end
+
+ test "overrides host" do
+ host! "http://example.com"
+
+ assert_equal "http://example.com", Capybara.app_host
+ end
+end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 51680216e4..0074d2a314 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -13,6 +13,12 @@ module ActionDispatch
assert_equal "foo", uf.original_filename
end
+ def test_filename_is_different_object
+ file_str = "foo"
+ uf = Http::UploadedFile.new(filename: file_str, tempfile: Object.new)
+ assert_not_equal file_str.object_id , uf.original_filename.object_id
+ end
+
def test_filename_should_be_in_utf_8
uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new)
assert_equal "UTF-8", uf.original_filename.encoding.to_s
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb
index 9b88fa1f5a..dfcd423978 100644
--- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb
@@ -1,3 +1,3 @@
<body>
-<%= cache do %><p>ERB</p><% end %>
+<%= cache("fragment") do %><p>ERB</p><% end %>
</body>
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder
index efdcc28e0f..6599579740 100644
--- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder
@@ -1,5 +1,5 @@
xml.body do
- cache do
+ cache("fragment") do
xml.p "Builder"
end
end
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
index e523b74ae3..abf7017ce6 100644
--- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
@@ -1,3 +1,3 @@
<body>
-<%= cache do %><p>PHONE</p><% end %>
+<%= cache("fragment") do %><p>PHONE</p><% end %>
</body>
diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
index fa5e6bd318..1148d83ad7 100644
--- a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
+++ b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
@@ -1,3 +1,3 @@
Hello
-<%= cache do %>This bit's fragment cached<% end %>
+<%= cache "fragment" do %>This bit's fragment cached<% end %>
<%= 'Ciao' %>
diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb
index c7315c0338..889640fdd7 100644
--- a/actionpack/test/journey/gtg/transition_table_test.rb
+++ b/actionpack/test/journey/gtg/transition_table_test.rb
@@ -35,25 +35,25 @@ module ActionDispatch
def test_simulate_gt
sim = simulator_for ["/foo", "/bar"]
- assert_match sim, "/foo"
+ assert_match_route sim, "/foo"
end
def test_simulate_gt_regexp
sim = simulator_for [":foo"]
- assert_match sim, "foo"
+ assert_match_route sim, "foo"
end
def test_simulate_gt_regexp_mix
sim = simulator_for ["/get", "/:method/foo"]
- assert_match sim, "/get"
- assert_match sim, "/get/foo"
+ assert_match_route sim, "/get"
+ assert_match_route sim, "/get/foo"
end
def test_simulate_optional
sim = simulator_for ["/foo(/bar)"]
- assert_match sim, "/foo"
- assert_match sim, "/foo/bar"
- assert_no_match sim, "/foo/"
+ assert_match_route sim, "/foo"
+ assert_match_route sim, "/foo/bar"
+ assert_no_match_route sim, "/foo/"
end
def test_match_data
@@ -65,11 +65,11 @@ module ActionDispatch
sim = GTG::Simulator.new tt
- match = sim.match "/get"
- assert_equal [paths.first], match.memos
+ memos = sim.memos "/get"
+ assert_equal [paths.first], memos
- match = sim.match "/get/foo"
- assert_equal [paths.last], match.memos
+ memos = sim.memos "/get/foo"
+ assert_equal [paths.last], memos
end
def test_match_data_ambiguous
@@ -86,8 +86,8 @@ module ActionDispatch
builder = GTG::Builder.new ast
sim = GTG::Simulator.new builder.transition_table
- match = sim.match "/articles/new"
- assert_equal [paths[1], paths[3]], match.memos
+ memos = sim.memos "/articles/new"
+ assert_equal [paths[1], paths[3]], memos
end
private
@@ -109,6 +109,14 @@ module ActionDispatch
def simulator_for(paths)
GTG::Simulator.new tt(paths)
end
+
+ def assert_match_route(simulator, path)
+ assert simulator.memos(path), "Simulator should match #{path}."
+ end
+
+ def assert_no_match_route(simulator, path)
+ assert_not simulator.memos(path) { nil }, "Simulator should not match #{path}."
+ end
end
end
end
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index b768553e7a..ff37d85ed8 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -26,6 +26,10 @@ Customer = Struct.new(:name, :id) do
def persisted?
id.present?
end
+
+ def cache_key
+ "#{name}/#{id}"
+ end
end
Post = Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) do
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index d478f4c437..122c42c5bd 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Add `:json` type to `auto_discovery_link_tag` to support [JSON Feeds](https://jsonfeed.org/version/1)
+
+ *Mike Gunderloy*
+
* Update `distance_of_time_in_words` helper to display better error messages
for bad input.
diff --git a/actionview/Rakefile b/actionview/Rakefile
index de588ad25c..0fc38e8db4 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -2,8 +2,6 @@ require "rake/testtask"
require "fileutils"
require "open3"
-dir = File.dirname(__FILE__)
-
desc "Default Task"
task default: :test
@@ -95,7 +93,7 @@ namespace :assets do
desc "Verify compiled Action View assets"
task :verify do
file = "lib/assets/compiled/rails-ujs.js"
- pathname = Pathname.new("#{dir}/#{file}")
+ pathname = Pathname.new("#{__dir__}/#{file}")
print "[verify] #{file} exists "
if pathname.exist?
@@ -113,13 +111,13 @@ namespace :assets do
fail
end
- print "[verify] #{dir} can be required as a module "
+ print "[verify] #{__dir__} can be required as a module "
js = <<-JS
window = { Event: class {} }
class Element {}
- require('#{dir}')
+ require('#{__dir__}')
JS
- stdout, stderr, status = Open3.capture3("node", "--print", js)
+ _, stderr, status = Open3.capture3("node", "--print", js)
if status.success?
puts "[OK]"
else
@@ -130,7 +128,7 @@ namespace :assets do
end
task :lines do
- load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics"
+ load File.join(File.expand_path("..", __dir__), "/tools/line_statistics")
files = FileList["lib/**/*.rb"]
CodeTools::LineStatistics.new(files).print_loc
end
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index cfaa5007a1..41221dd04e 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md
index 92f3e8a3b3..0819d5da5f 100644
--- a/actionview/app/assets/javascripts/README.md
+++ b/actionview/app/assets/javascripts/README.md
@@ -36,10 +36,20 @@ Require `rails-ujs` into your application.js manifest.
//= require rails-ujs
```
+Usage with yarn
+------------
+
+When using with Webpacker gem or your preferred JavaScript bundler. Just add the following to your main JS file and compile.
+
+```javascript
+import Rails from 'rails-ujs';
+Rails.start()
+```
+
How to run tests
------------
-Run `bundle exec rake ujs:server` first, and then run the web tests by visiting [[http://localhost:4567]] in your browser.
+Run `bundle exec rake ujs:server` first, and then run the web tests by visiting http://localhost:4567 in your browser.
## License
rails-ujs is released under the [MIT License](MIT-LICENSE).
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index ca586f1d52..99c5b831b5 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -92,5 +92,5 @@ end
require "active_support/core_ext/string/output_safety"
ActiveSupport.on_load(:i18n) do
- I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
+ I18n.load_path << File.expand_path("action_view/locale/en.yml", __dir__)
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 750f96f29e..c21fe782c6 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -122,9 +122,9 @@ module ActionView
end
# Returns a link tag that browsers and feed readers can use to auto-detect
- # an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
- # <tt>:atom</tt>. Control the link options in url_for format using the
- # +url_options+. You can modify the LINK tag itself in +tag_options+.
+ # an RSS, Atom, or JSON feed. The +type+ can be <tt>:rss</tt> (default),
+ # <tt>:atom</tt>, or <tt>:json</tt>. Control the link options in url_for format
+ # using the +url_options+. You can modify the LINK tag itself in +tag_options+.
#
# ==== Options
#
@@ -138,6 +138,8 @@ module ActionView
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
# auto_discovery_link_tag(:atom)
# # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:json)
+ # # => <link rel="alternate" type="application/json" title="JSON" href="http://www.currenthost.com/controller/action" />
# auto_discovery_link_tag(:rss, {action: "feed"})
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
# auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"})
@@ -147,8 +149,8 @@ module ActionView
# auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"})
# # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed.rss" />
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
- if !(type == :rss || type == :atom) && tag_options[:type].blank?
- raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss or :atom.")
+ if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank?
+ raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.")
end
tag(
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 15ab7e304f..c3aecadcd6 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -8,10 +8,9 @@ module ActionView
# fragments, and so on. This method takes a block that contains
# the content you wish to cache.
#
- # The best way to use this is by doing key-based cache expiration
- # on top of a cache store like Memcached that'll automatically
- # kick out old entries. For more on key-based expiration, see:
- # http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works
+ # The best way to use this is by doing recyclable key-based cache expiration
+ # on top of a cache store like Memcached or Redis that'll automatically
+ # kick out old entries.
#
# When using this method, you list the cache dependency as the name of the cache, like so:
#
@@ -23,10 +22,14 @@ module ActionView
# This approach will assume that when a new topic is added, you'll touch
# the project. The cache key generated from this call will be something like:
#
- # views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9
- # ^class ^id ^updated_at ^template tree digest
+ # views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123
+ # ^template path ^template tree digest ^class ^id
#
- # The cache is thus automatically bumped whenever the project updated_at is touched.
+ # This cache key is stable, but it's combined with a cache version derived from the project
+ # record. When the project updated_at is touched, the #cache_version changes, even
+ # if the key stays stable. This means that unlike a traditional key-based cache expiration
+ # approach, you won't be generating cache trash, unused keys, simply because the dependent
+ # record is updated.
#
# If your template cache depends on multiple sources (try to avoid this to keep things simple),
# you can name all these dependencies as part of an array:
@@ -217,10 +220,15 @@ module ActionView
def fragment_name_with_digest(name, virtual_path)
virtual_path ||= @virtual_path
+
if virtual_path
name = controller.url_for(name).split("://").last if name.is_a?(Hash)
- digest = Digestor.digest name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies
- [ name, digest ]
+
+ if digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies).presence
+ [ "#{virtual_path}:#{digest}", name ]
+ else
+ [ virtual_path, name ]
+ end
else
name
end
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 667c7e945a..9ff7e54e4f 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -33,7 +33,7 @@ module ActionView
# [nil, []]
# { nil => [] }
def grouped_choices?
- !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
+ !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
end
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
index 1fbe209200..847256ac78 100644
--- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
@@ -38,7 +38,7 @@ module ActionView
end
def expanded_cache_key(key)
- key = @view.fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
+ key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
end
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index dde66a7ba0..a7d706c5e1 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -1,8 +1,8 @@
-$:.unshift(File.dirname(__FILE__) + "/lib")
-$:.unshift(File.dirname(__FILE__) + "/fixtures/helpers")
-$:.unshift(File.dirname(__FILE__) + "/fixtures/alternate_helpers")
+$:.unshift File.expand_path("lib", __dir__)
+$:.unshift File.expand_path("fixtures/helpers", __dir__)
+$:.unshift File.expand_path("fixtures/alternate_helpers", __dir__)
-ENV["TMPDIR"] = File.join(File.dirname(__FILE__), "tmp")
+ENV["TMPDIR"] = File.expand_path("tmp", __dir__)
require "active_support/core_ext/kernel/reporting"
@@ -47,7 +47,7 @@ I18n.backend.store_translations "da", {}
I18n.backend.store_translations "pt-BR", {}
ORIGINAL_LOCALES = I18n.available_locales.map(&:to_s).sort
-FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), "fixtures")
+FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__)
module RenderERBUtils
def view
@@ -133,7 +133,7 @@ class BasicController
def config
@config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config|
# VIEW TODO: View tests should not require a controller
- public_dir = File.expand_path("../fixtures/public", __FILE__)
+ public_dir = File.expand_path("fixtures/public", __dir__)
config.assets_dir = public_dir
config.javascripts_dir = "#{public_dir}/javascripts"
config.stylesheets_dir = "#{public_dir}/stylesheets"
@@ -196,7 +196,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
end
def with_autoload_path(path)
- path = File.join(File.dirname(__FILE__), "fixtures", path)
+ path = File.join(File.expand_path("fixtures", __dir__), path)
if ActiveSupport::Dependencies.autoload_paths.include?(path)
yield
else
diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb
index a2cd3deb58..8f65a61493 100644
--- a/actionview/test/actionpack/abstract/abstract_controller_test.rb
+++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb
@@ -42,7 +42,7 @@ module AbstractController
super
end
- append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views"))
+ append_view_path File.expand_path("views", __dir__)
end
class Me2 < RenderingController
@@ -152,7 +152,7 @@ module AbstractController
class OverridingLocalPrefixes < AbstractController::Base
include AbstractController::Rendering
include ActionView::Rendering
- append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views"))
+ append_view_path File.expand_path("views", __dir__)
def index
render
diff --git a/actionview/test/actionpack/abstract/helper_test.rb b/actionview/test/actionpack/abstract/helper_test.rb
index 83237518d7..13922e4485 100644
--- a/actionview/test/actionpack/abstract/helper_test.rb
+++ b/actionview/test/actionpack/abstract/helper_test.rb
@@ -1,6 +1,6 @@
require "abstract_unit"
-ActionController::Base.helpers_path = File.expand_path("../../../fixtures/helpers", __FILE__)
+ActionController::Base.helpers_path = File.expand_path("../../fixtures/helpers", __dir__)
module AbstractController
module Testing
@@ -51,7 +51,7 @@ module AbstractController
class AbstractInvalidHelpers < AbstractHelpers
include ActionController::Helpers
- path = File.expand_path("../../../fixtures/helpers_missing", __FILE__)
+ path = File.expand_path("../../fixtures/helpers_missing", __dir__)
$:.unshift(path)
self.helpers_path = path
end
diff --git a/actionview/test/actionpack/controller/capture_test.rb b/actionview/test/actionpack/controller/capture_test.rb
index f0ae609845..cc3a23c60c 100644
--- a/actionview/test/actionpack/controller/capture_test.rb
+++ b/actionview/test/actionpack/controller/capture_test.rb
@@ -2,7 +2,7 @@ require "abstract_unit"
require "active_support/logger"
class CaptureController < ActionController::Base
- self.view_paths = [ File.dirname(__FILE__) + "/../../fixtures/actionpack" ]
+ self.view_paths = [ File.expand_path("../../fixtures/actionpack", __dir__) ]
def self.controller_name; "test"; end
def self.controller_path; "test"; end
diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index b79835ff34..b3e0329f57 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -5,7 +5,7 @@ require "active_support/core_ext/array/extract_options"
# method has access to the view_paths array when looking for a layout to automatically assign.
old_load_paths = ActionController::Base.view_paths
-ActionController::Base.view_paths = [ File.dirname(__FILE__) + "/../../fixtures/actionpack/layout_tests/" ]
+ActionController::Base.view_paths = [ File.expand_path("../../fixtures/actionpack/layout_tests", __dir__) ]
class LayoutTest < ActionController::Base
def self.controller_path; "views" end
@@ -96,7 +96,7 @@ class StreamingLayoutController < LayoutTest
end
class AbsolutePathLayoutController < LayoutTest
- layout File.expand_path(File.expand_path(__FILE__) + "/../../../fixtures/actionpack/layout_tests/layouts/layout_test")
+ layout File.expand_path("../../fixtures/actionpack/layout_tests/layouts/layout_test", __dir__)
end
class HasOwnLayoutController < LayoutTest
@@ -117,7 +117,7 @@ end
class PrependsViewPathController < LayoutTest
def hello
- prepend_view_path File.dirname(__FILE__) + "/../../fixtures/actionpack/layout_tests/alt/"
+ prepend_view_path File.expand_path("../../fixtures/actionpack/layout_tests/alt", __dir__)
render layout: "alt"
end
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 51ec8899b1..6528169312 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -56,7 +56,7 @@ class TestController < ApplicationController
end
def hello_world_file
- render file: File.expand_path("../../../fixtures/actionpack/hello", __FILE__), formats: [:html]
+ render file: File.expand_path("../../fixtures/actionpack/hello", __dir__), formats: [:html]
end
# :ported:
@@ -125,7 +125,7 @@ class TestController < ApplicationController
# :ported:
def render_file_with_instance_variables
@secret = "in the sauce"
- path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar")
+ path = File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__)
render file: path
end
@@ -142,21 +142,21 @@ class TestController < ApplicationController
def render_file_using_pathname
@secret = "in the sauce"
- render file: Pathname.new(File.dirname(__FILE__)).join("..", "..", "fixtures", "test", "dot.directory", "render_file_with_ivar")
+ render file: Pathname.new(__dir__).join("..", "..", "fixtures", "test", "dot.directory", "render_file_with_ivar")
end
def render_file_from_template
@secret = "in the sauce"
- @path = File.expand_path(File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar"))
+ @path = File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__)
end
def render_file_with_locals
- path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals")
+ path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__)
render file: path, locals: { secret: "in the sauce" }
end
def render_file_as_string_with_locals
- path = File.expand_path(File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals"))
+ path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__)
render file: path, locals: { secret: "in the sauce" }
end
diff --git a/actionview/test/active_record_unit.rb b/actionview/test/active_record_unit.rb
index 7f94b7ebb4..901c0e2b3e 100644
--- a/actionview/test/active_record_unit.rb
+++ b/actionview/test/active_record_unit.rb
@@ -13,7 +13,7 @@ end
# Try to grab AR
unless defined?(ActiveRecord) && defined?(FixtureSet)
begin
- PATH_TO_AR = "#{File.dirname(__FILE__)}/../../activerecord/lib"
+ PATH_TO_AR = File.expand_path("../../activerecord/lib", __dir__)
raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR)
$LOAD_PATH.unshift PATH_TO_AR
require "active_record"
@@ -58,13 +58,13 @@ class ActiveRecordTestConnector
# Load actionpack sqlite3 tables
def load_schema
- File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(";").each do |sql|
+ File.read(File.expand_path("fixtures/db_definitions/sqlite.sql", __dir__)).split(";").each do |sql|
ActiveRecord::Base.connection.execute(sql) unless sql.blank?
end
end
def require_fixture_models
- Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each { |f| require f }
+ Dir.glob(File.expand_path("fixtures/*.rb", __dir__)).each { |f| require f }
end
end
end
diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb
index 43f7242ee9..fbab512c41 100644
--- a/actionview/test/activerecord/relation_cache_test.rb
+++ b/actionview/test/activerecord/relation_cache_test.rb
@@ -10,7 +10,7 @@ class RelationCacheTest < ActionView::TestCase
def test_cache_relation_other
cache(Project.all) { concat("Hello World") }
- assert_equal "Hello World", controller.cache_store.read("views/projects-#{Project.count}/")
+ assert_equal "Hello World", controller.cache_store.read("views/path/projects-#{Project.count}")
end
def view_cache_dependencies; end
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index b7a993c5c9..6093a4e660 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -53,6 +53,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(auto_discovery_link_tag) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:rss)) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com" rel="alternate" title="ATOM" type="application/atom+xml" />),
+ %(auto_discovery_link_tag(:json)) => %(<link href="http://www.example.com" rel="alternate" title="JSON" type="application/json" />),
%(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(<link href="http://localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
%(auto_discovery_link_tag(:rss, "//localhost/feed")) => %(<link href="//localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index e225c3de09..de04f3f25d 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -14,7 +14,7 @@ class FixtureTemplate
end
class FixtureFinder < ActionView::LookupContext
- FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor"
+ FIXTURES_DIR = File.expand_path("../fixtures/digestor", __dir__)
def initialize(details = {})
super(ActionView::PathSet.new(["digestor", "digestor/api"]), details, [])
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index 258dcdb806..3247f20ba7 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -6,6 +6,15 @@ class Map < Hash
end
end
+class CustomEnumerable
+ include Enumerable
+
+ def each
+ yield "one"
+ yield "two"
+ end
+end
+
class FormOptionsHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormOptionsHelper
@@ -904,6 +913,14 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_enumerable
+ @post = Post.new
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"one\">one</option>\n<option value=\"two\">two</option></select>",
+ select("post", "category", CustomEnumerable.new)
+ )
+ end
+
def test_collection_select
@post = Post.new
@post.author_name = "Babe"
diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb
index 7f358add7e..584666d54b 100644
--- a/actionview/test/template/log_subscriber_test.rb
+++ b/actionview/test/template/log_subscriber_test.rb
@@ -39,7 +39,7 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def set_view_cache_dependencies
def @view.view_cache_dependencies; []; end
- def @view.fragment_cache_key(*); "ahoy `controller` dependency"; end
+ def @view.combined_fragment_cache_key(*); "ahoy `controller` dependency"; end
end
def test_render_file_template
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 3f66ab3ed3..9999607067 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -10,8 +10,8 @@ module RenderTestCases
@view = Class.new(ActionView::Base) do
def view_cache_dependencies; end
- def fragment_cache_key(key)
- ActiveSupport::Cache.expand_cache_key(key, :views)
+ def combined_fragment_cache_key(key)
+ [ :views, key ]
end
end.new(paths, @assigns)
@@ -138,7 +138,7 @@ module RenderTestCases
end
def test_render_file_with_full_path
- template_path = File.join(File.dirname(__FILE__), "../fixtures/test/hello_world")
+ template_path = File.expand_path("../fixtures/test/hello_world", __dir__)
assert_equal "Hello world!", @view.render(file: template_path)
end
@@ -160,7 +160,7 @@ module RenderTestCases
end
def test_render_outside_path
- assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))
+ assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__))
assert_raises ActionView::MissingTemplate do
@view.render(template: "../\\../test/abstract_unit.rb")
end
@@ -718,6 +718,6 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase
private
def cache_key(*names, virtual_path)
digest = ActionView::Digestor.digest name: virtual_path, finder: @view.lookup_context, dependencies: []
- @view.fragment_cache_key([ *names, digest ])
+ @view.combined_fragment_cache_key([ "#{virtual_path}:#{digest}", *names ])
end
end
diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb
index 43e3f21076..8e21f4b828 100644
--- a/actionview/test/template/resolver_patterns_test.rb
+++ b/actionview/test/template/resolver_patterns_test.rb
@@ -2,7 +2,7 @@ require "abstract_unit"
class ResolverPatternsTest < ActiveSupport::TestCase
def setup
- path = File.expand_path("../../fixtures/", __FILE__)
+ path = File.expand_path("../fixtures", __dir__)
pattern = ":prefix/{:formats/,}:action{.:formats,}{+:variants,}{.:handlers,}"
@resolver = ActionView::FileSystemResolver.new(path, pattern)
end
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 6adfa95dd1..58d903b1c8 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -615,8 +615,8 @@ class UrlHelperTest < ActiveSupport::TestCase
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")
+ %{<a href="mailto:%23%21%24%25%26%27%2A%2B-%2F%3D%3F%5E_%60%7B%7D%7C@example.org">#!$%&amp;&#39;*+-/=?^_`{}|@example.org</a>},
+ mail_to("#!$%&'*+-/=?^_`{}|@example.org")
)
end
diff --git a/actionview/test/ujs/config.ru b/actionview/test/ujs/config.ru
index 48b7a4b53a..213a41127a 100644
--- a/actionview/test/ujs/config.ru
+++ b/actionview/test/ujs/config.ru
@@ -1,4 +1,4 @@
-$LOAD_PATH.unshift File.expand_path("..", __FILE__)
+$LOAD_PATH.unshift __dir__
require "server"
run UJS::Server
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 41ff76135e..dd03ab0b8f 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -1,8 +1,7 @@
require "rake/testtask"
#TODO: add qu back to the list after it support Rails 5.1
-#TODO: add delayed_job back to the list after it support Rails 5.1
-ACTIVEJOB_ADAPTERS = %w(async inline que queue_classic resque sidekiq sneakers sucker_punch backburner test)
+ACTIVEJOB_ADAPTERS = %w(async inline delayed_job que queue_classic resque sidekiq sneakers sucker_punch backburner test)
ACTIVEJOB_ADAPTERS.delete("queue_classic") if defined?(JRUBY_VERSION)
task default: :test
@@ -44,9 +43,8 @@ namespace :test do
namespace :isolated do
task adapter => "test:env:#{adapter}" do
- dir = File.dirname(__FILE__)
- Dir.glob("#{dir}/test/cases/**/*_test.rb").all? do |file|
- sh(Gem.ruby, "-w", "-I#{dir}/lib", "-I#{dir}/test", file)
+ Dir.glob("#{__dir__}/test/cases/**/*_test.rb").all? do |file|
+ sh(Gem.ruby, "-w", "-I#{__dir__}/lib", "-I#{__dir__}/test", file)
end || raise("Failures")
end
end
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index 2547e91262..2f2b94a4c4 100644
--- a/activejob/activejob.gemspec
+++ b/activejob/activejob.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
index d5b17de8b5..9aebc880a5 100644
--- a/activejob/lib/active_job/callbacks.rb
+++ b/activejob/lib/active_job/callbacks.rb
@@ -4,7 +4,7 @@ module ActiveJob
# = Active Job Callbacks
#
# Active Job provides hooks during the life cycle of a job. Callbacks allow you
- # to trigger logic during the life cycle of a job. Available callbacks are:
+ # to trigger logic during this cycle. Available callbacks are:
#
# * <tt>before_enqueue</tt>
# * <tt>around_enqueue</tt>
@@ -13,6 +13,8 @@ module ActiveJob
# * <tt>around_perform</tt>
# * <tt>after_perform</tt>
#
+ # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
+ #
module Callbacks
extend ActiveSupport::Concern
include ActiveSupport::Callbacks
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index 9dae80ffc2..b22d8b8347 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -7,6 +7,7 @@ module ActiveJob
extend ActiveSupport::Concern
included do
+ class_attribute :_queue_adapter_name, instance_accessor: false, instance_predicate: false
class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
self.queue_adapter = :async
end
@@ -19,11 +20,15 @@ module ActiveJob
_queue_adapter
end
+ def queue_adapter_name
+ _queue_adapter_name
+ end
+
# Specify the backend queue provider. The default queue adapter
# is the +:async+ queue. See QueueAdapters for more
# information.
def queue_adapter=(name_or_adapter_or_class)
- self._queue_adapter = interpret_adapter(name_or_adapter_or_class)
+ interpret_adapter(name_or_adapter_or_class)
end
private
@@ -31,16 +36,24 @@ module ActiveJob
def interpret_adapter(name_or_adapter_or_class)
case name_or_adapter_or_class
when Symbol, String
- ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new
+ assign_adapter(name_or_adapter_or_class.to_s,
+ ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new)
else
if queue_adapter?(name_or_adapter_or_class)
- name_or_adapter_or_class
+ adapter_name = "#{name_or_adapter_or_class.class.name.demodulize.remove('Adapter').underscore}"
+ assign_adapter(adapter_name,
+ name_or_adapter_or_class)
else
raise ArgumentError
end
end
end
+ def assign_adapter(adapter_name, queue_adapter)
+ self._queue_adapter_name = adapter_name
+ self._queue_adapter = queue_adapter
+ end
+
QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze
def queue_adapter?(object)
diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb
index 50476a2e50..474f181f65 100644
--- a/activejob/lib/rails/generators/job/job_generator.rb
+++ b/activejob/lib/rails/generators/job/job_generator.rb
@@ -12,7 +12,7 @@ module Rails # :nodoc:
hook_for :test_framework
def self.default_generator_root
- File.dirname(__FILE__)
+ __dir__
end
def create_job_file
diff --git a/activejob/test/adapters/delayed_job.rb b/activejob/test/adapters/delayed_job.rb
index 5f0ee2418c..98e41c0c36 100644
--- a/activejob/test/adapters/delayed_job.rb
+++ b/activejob/test/adapters/delayed_job.rb
@@ -1,6 +1,6 @@
ActiveJob::Base.queue_adapter = :delayed_job
-$LOAD_PATH << File.dirname(__FILE__) + "/../support/delayed_job"
+$LOAD_PATH << File.expand_path("../support/delayed_job", __dir__)
Delayed::Worker.delay_jobs = false
Delayed::Worker.backend = :test
diff --git a/activejob/test/cases/queue_adapter_test.rb b/activejob/test/cases/queue_adapter_test.rb
index 9611b0909b..8368107bdf 100644
--- a/activejob/test/cases/queue_adapter_test.rb
+++ b/activejob/test/cases/queue_adapter_test.rb
@@ -25,14 +25,19 @@ class QueueAdapterTest < ActiveJob::TestCase
base_queue_adapter = ActiveJob::Base.queue_adapter
child_job_one = Class.new(ActiveJob::Base)
+ assert_equal child_job_one.queue_adapter_name, ActiveJob::Base.queue_adapter_name
+
child_job_one.queue_adapter = :stub_one
assert_not_equal ActiveJob::Base.queue_adapter, child_job_one.queue_adapter
+ assert_equal "stub_one", child_job_one.queue_adapter_name
assert_kind_of ActiveJob::QueueAdapters::StubOneAdapter, child_job_one.queue_adapter
child_job_two = Class.new(ActiveJob::Base)
child_job_two.queue_adapter = :stub_two
+ assert_equal "stub_two", child_job_two.queue_adapter_name
+
assert_kind_of ActiveJob::QueueAdapters::StubTwoAdapter, child_job_two.queue_adapter
assert_kind_of ActiveJob::QueueAdapters::StubOneAdapter, child_job_one.queue_adapter, "child_job_one's queue adapter should remain unchanged"
assert_equal base_queue_adapter, ActiveJob::Base.queue_adapter, "ActiveJob::Base's queue adapter should remain unchanged"
diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb
index 29a5691f30..14fe3c9adc 100644
--- a/activejob/test/support/integration/dummy_app_template.rb
+++ b/activejob/test/support/integration/dummy_app_template.rb
@@ -5,7 +5,7 @@ end
rails_command("db:migrate")
initializer "activejob.rb", <<-CODE
-require "#{File.expand_path("../jobs_manager.rb", __FILE__)}"
+require "#{File.expand_path("jobs_manager.rb", __dir__)}"
JobsManager.current_manager.setup
CODE
diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb
index 626b932cce..545b62752e 100644
--- a/activejob/test/support/integration/helper.rb
+++ b/activejob/test/support/integration/helper.rb
@@ -7,7 +7,7 @@ require "rails/generators/rails/app/app_generator"
require "tmpdir"
dummy_app_path = Dir.mktmpdir + "/dummy"
-dummy_app_template = File.expand_path("../dummy_app_template.rb", __FILE__)
+dummy_app_template = File.expand_path("dummy_app_template.rb", __dir__)
args = Rails::Generators::ARGVScrubber.new(["new", dummy_app_path, "--skip-gemfile", "--skip-bundle",
"--skip-git", "--skip-spring", "-d", "sqlite3", "--skip-javascript", "--force", "--quiet",
"--template", dummy_app_template]).prepare!
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index e707a65147..7483704212 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Fix regression in numericality validator when comparing Decimal and Float input
+ values with more scale than the schema.
+
+ *Bradley Priest*
+
* Fix methods `#keys`, `#values` in `ActiveModel::Errors`.
Change `#keys` to only return the keys that don't have empty messages.
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index c7f97a4258..d60f6d9997 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -1,14 +1,12 @@
require "rake/testtask"
-dir = File.dirname(__FILE__)
-
task default: :test
task :package
Rake::TestTask.new do |t|
t.libs << "test"
- t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb")
+ t.test_files = Dir.glob("#{__dir__}/test/cases/**/*_test.rb")
t.warning = true
t.verbose = true
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
@@ -16,8 +14,8 @@ end
namespace :test do
task :isolated do
- Dir.glob("#{dir}/test/**/*_test.rb").all? do |file|
- sh(Gem.ruby, "-w", "-I#{dir}/lib", "-I#{dir}/test", file)
+ Dir.glob("#{__dir__}/test/**/*_test.rb").all? do |file|
+ sh(Gem.ruby, "-w", "-I#{__dir__}/lib", "-I#{__dir__}/test", file)
end || raise("Failures")
end
end
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index fd715f6ba9..43f1e09c77 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 2389c858d5..a2892e9ea9 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -68,5 +68,5 @@ module ActiveModel
end
ActiveSupport.on_load(:i18n) do
- I18n.load_path << File.dirname(__FILE__) + "/active_model/locale/en.yml"
+ I18n.load_path << File.expand_path("active_model/locale/en.yml", __dir__)
end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index eac2761433..835e6f7716 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -56,6 +56,9 @@ module ActiveModel
#
# Would only create the +after_create+ and +before_create+ callback methods in
# your class.
+ #
+ # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
+ #
module Callbacks
def self.extended(base) #:nodoc:
base.class_eval do
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index d460068830..1f14a068d1 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -147,6 +147,9 @@ module ActiveModel
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a +true+ or +false+
# value.
+ #
+ # NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions.
+ #
def validate(*args, &block)
options = args.extract_options!
@@ -432,4 +435,4 @@ module ActiveModel
end
end
-Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
+Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file }
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index b82c85ddf4..fb053a4c4e 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -36,7 +36,9 @@ module ActiveModel
return
end
- unless raw_value.is_a?(Numeric)
+ if raw_value.is_a?(Numeric)
+ value = raw_value
+ else
value = parse_raw_value_as_a_number(raw_value)
end
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 1a4d13f2d0..6c11981e1d 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -82,7 +82,7 @@ module ActiveModel
# end
#
# It can be useful to access the class that is using that validator when there are prerequisites such
- # as an +attr_accessor+ being present. This class is accessible via +options[:class]+ in the constructor.
+ # as an +attr_accessor+ being present. This class is accessible via <tt>options[:class]</tt> in the constructor.
# To setup your validator override the constructor.
#
# class MyValidator < ActiveModel::Validator
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 4e264f5f2a..d17bbf80ca 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,4 +1,19 @@
-* Respect 'SchemaDumper.ignore_tables' in rake tasks for databases structure dump
+* Loading model schema from database is now thread-safe.
+
+ Fixes #28589.
+
+ *Vikrant Chaudhary*, *David Abdemoulaie*
+
+* Add `ActiveRecord::Base#cache_version` to support recyclable cache keys via the new versioned entries
+ in `ActiveSupport::Cache`. This also means that `ActiveRecord::Base#cache_key` will now return a stable key
+ that does not include a timestamp any more.
+
+ NOTE: This feature is turned off by default, and `#cache_key` will still return cache keys with timestamps
+ until you set `ActiveRecord::Base.cache_versioning = true`. That's the setting for all new apps on Rails 5.2+
+
+ *DHH*
+
+* Respect `SchemaDumper.ignore_tables` in rake tasks for databases structure dump
*Rusty Geldmacher*, *Guillermo Iguaran*
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 7be3d851f1..2d0d5bd657 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,7 +1,7 @@
require "rake/testtask"
-require File.expand_path(File.dirname(__FILE__)) + "/test/config"
-require File.expand_path(File.dirname(__FILE__)) + "/test/support/config"
+require File.expand_path("test/config", __dir__)
+require File.expand_path("test/support/config", __dir__)
def run_without_aborting(*tasks)
errors = []
@@ -134,7 +134,7 @@ task drop_postgresql_databases: "db:postgresql:drop"
task rebuild_postgresql_databases: "db:postgresql:rebuild"
task :lines do
- load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics"
+ load File.expand_path("../tools/line_statistics", __dir__)
files = FileList["lib/active_record/**/*.rb"]
CodeTools::LineStatistics.new(files).print_loc
end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 0b37e9076c..450ec0bba9 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 96b8545dfc..29f49c6195 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -177,5 +177,5 @@ ActiveSupport.on_load(:active_record) do
end
ActiveSupport.on_load(:i18n) do
- I18n.load_path << File.dirname(__FILE__) + "/active_record/locale/en.yml"
+ I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 1cb2b2d7c6..6b13e6936f 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -152,14 +152,6 @@ module ActiveRecord
reset
end
- def interpolate(sql, record = nil)
- if sql.respond_to?(:to_proc)
- owner.instance_exec(record, &sql)
- else
- sql
- end
- end
-
# We can't dump @reflection since it contains the scope proc
def marshal_dump
ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 8995b1e352..643226267c 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -106,12 +106,7 @@ module ActiveRecord
def join_constraints(outer_joins, join_type)
joins = join_root.children.flat_map { |child|
-
- if join_type == Arel::Nodes::OuterJoin
- make_left_outer_joins join_root, child
- else
- make_inner_joins join_root, child
- end
+ make_join_constraints(join_root, child, join_type)
}
joins.concat outer_joins.flat_map { |oj|
@@ -175,27 +170,15 @@ module ActiveRecord
end
def make_outer_joins(parent, child)
- tables = table_aliases_for(parent, child)
- join_type = Arel::Nodes::OuterJoin
- info = make_constraints parent, child, tables, join_type
-
- [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
- end
-
- def make_left_outer_joins(parent, child)
- tables = child.tables
join_type = Arel::Nodes::OuterJoin
- info = make_constraints parent, child, tables, join_type
-
- [info] + child.children.flat_map { |c| make_left_outer_joins(child, c) }
+ make_join_constraints(parent, child, join_type, true)
end
- def make_inner_joins(parent, child)
- tables = child.tables
- join_type = Arel::Nodes::InnerJoin
- info = make_constraints parent, child, tables, join_type
+ def make_join_constraints(parent, child, join_type, aliasing = false)
+ tables = aliasing ? table_aliases_for(parent, child) : child.tables
+ info = make_constraints(parent, child, tables, join_type)
- [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
+ [info] + child.children.flat_map { |c| make_join_constraints(child, c, join_type, aliasing) }
end
def table_aliases_for(parent, node)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 61cec5403a..80c9fde5d1 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -22,10 +22,6 @@ module ActiveRecord
@children = children
end
- def name
- reflection.name
- end
-
def match?(other)
self.class == other.class
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index ebe06566cc..83c61fad19 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -62,7 +62,6 @@ module ActiveRecord
super(attribute_names)
@attribute_methods_generated = true
end
- true
end
def undefine_attribute_methods # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 66b278219a..01f9d815d5 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -64,7 +64,7 @@ module ActiveRecord
end
def deep_dup
- dup.tap do |copy|
+ self.class.allocate.tap do |copy|
copy.instance_variable_set(:@attributes, attributes.deep_dup)
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 607c54e481..829a4f6e86 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -216,13 +216,7 @@ module ActiveRecord
method = :validate_single_association
end
- define_non_cyclic_method(validation_method) do
- send(method, reflection)
- # TODO: remove the following line as soon as the return value of
- # callbacks is ignored, that is, returning `false` does not
- # display a deprecation warning or halts the callback chain.
- true
- end
+ define_non_cyclic_method(validation_method) { send(method, reflection) }
validate validation_method
after_validation :_ensure_no_duplicate_errors
end
@@ -369,7 +363,6 @@ module ActiveRecord
# association whether or not the parent was a new record before saving.
def before_save_collection_association
@new_record_before_save = new_record?
- true
end
def after_save_collection_association
@@ -389,7 +382,7 @@ module ActiveRecord
autosave = reflection.options[:autosave]
# reconstruct the scope now that we know the owner's id
- association.reset_scope if association.respond_to?(:reset_scope)
+ association.reset_scope
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
if autosave
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 46d7f84efd..a30fbe0e05 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -146,7 +146,7 @@ module ActiveRecord
end
def polymorphic_options
- as_options(polymorphic)
+ as_options(polymorphic).merge(null: options[:null])
end
def index_options
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 13629dee7f..16a398f631 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1280,9 +1280,10 @@ module ActiveRecord
end
def foreign_key_name(table_name, options)
- identifier = "#{table_name}_#{options.fetch(:column)}_fk"
- hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
options.fetch(:name) do
+ identifier = "#{table_name}_#{options.fetch(:column)}_fk"
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
+
"fk_rails_#{hashed_identifier}"
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 5b483ad4ab..3eff9e2f83 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -186,17 +186,17 @@ module ActiveRecord
# Returns the current database encoding format.
def encoding
- select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA")
+ select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns the current database collation.
def collation
- select_value("SELECT datcollate FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA")
+ select_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns the current database ctype.
def ctype
- select_value("SELECT datctype FROM pg_database WHERE datname LIKE '#{current_database}'", "SCHEMA")
+ select_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns an array of schema names.
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 0ab03b2ab3..496abfc5d9 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -154,11 +154,12 @@ module ActiveRecord
definitions.each do |name, values|
# statuses = { }
enum_values = ActiveSupport::HashWithIndifferentAccess.new
- name = name.to_sym
+ name = name.to_s
# def self.statuses() statuses end
- detect_enum_conflict!(name, name.to_s.pluralize, true)
- klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
+ detect_enum_conflict!(name, name.pluralize, true)
+ singleton_class.send(:define_method, name.pluralize) { enum_values }
+ defined_enums[name] = enum_values
detect_enum_conflict!(name, name)
detect_enum_conflict!(name, "#{name}=")
@@ -170,7 +171,7 @@ module ActiveRecord
_enum_methods_module.module_eval do
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
- pairs.each do |value, i|
+ pairs.each do |label, value|
if enum_prefix == true
prefix = "#{name}_"
elsif enum_prefix
@@ -182,23 +183,23 @@ module ActiveRecord
suffix = "_#{enum_suffix}"
end
- value_method_name = "#{prefix}#{value}#{suffix}"
- enum_values[value] = i
+ value_method_name = "#{prefix}#{label}#{suffix}"
+ enum_values[label] = value
+ label = label.to_s
- # def active?() status == 0 end
+ # def active?() status == "active" end
klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
- define_method("#{value_method_name}?") { self[attr] == value.to_s }
+ define_method("#{value_method_name}?") { self[attr] == label }
- # def active!() update! status: :active end
+ # def active!() update!(status: 0) end
klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
define_method("#{value_method_name}!") { update!(attr => value) }
- # scope :active, -> { where status: 0 }
+ # scope :active, -> { where(status: 0) }
klass.send(:detect_enum_conflict!, name, value_method_name, true)
klass.scope value_method_name, -> { where(attr => value) }
end
end
- defined_enums[name.to_s] = enum_values
end
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index fbdaeaae51..236a65eba7 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -217,7 +217,7 @@ module ActiveRecord
def subclass_from_attributes(attrs)
attrs = attrs.to_h if attrs.respond_to?(:permitted?)
if attrs.is_a?(Hash)
- subclass_name = attrs.with_indifferent_access[inheritance_column]
+ subclass_name = attrs[inheritance_column] || attrs[inheritance_column.to_sym]
if subclass_name.present?
find_sti_class(subclass_name)
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 8e71b60b29..32362fa86f 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -7,12 +7,21 @@ module ActiveRecord
included do
##
# :singleton-method:
- # Indicates the format used to generate the timestamp in the cache key.
- # Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
+ # Indicates the format used to generate the timestamp in the cache key, if
+ # versioning is off. Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
#
# This is +:usec+, by default.
class_attribute :cache_timestamp_format, instance_writer: false
self.cache_timestamp_format = :usec
+
+ ##
+ # :singleton-method:
+ # Indicates whether to use a stable #cache_key method that is accompanied
+ # by a changing version in the #cache_version method.
+ #
+ # This is +false+, by default until Rails 6.0.
+ class_attribute :cache_versioning, instance_writer: false
+ self.cache_versioning = false
end
# Returns a +String+, which Action Pack uses for constructing a URL to this
@@ -42,35 +51,65 @@ module ActiveRecord
id && id.to_s # Be sure to stringify the id for routes
end
- # Returns a cache key that can be used to identify this record.
+ # Returns a stable cache key that can be used to identify this record.
#
# Product.new.cache_key # => "products/new"
- # Product.find(5).cache_key # => "products/5" (updated_at not available)
- # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
+ # Product.find(5).cache_key # => "products/5"
#
- # You can also pass a list of named timestamps, and the newest in the list will be
- # used to generate the key:
+ # If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
+ # the cache key will also include a version.
#
- # Person.find(5).cache_key(:updated_at, :last_reviewed_at)
+ # Product.cache_versioning = false
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
def cache_key(*timestamp_names)
if new_record?
"#{model_name.cache_key}/new"
else
- timestamp = if timestamp_names.any?
- max_updated_column_timestamp(timestamp_names)
+ if cache_version && timestamp_names.none?
+ "#{model_name.cache_key}/#{id}"
else
- max_updated_column_timestamp
- end
+ timestamp = if timestamp_names.any?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Specifying a timestamp name for #cache_key has been deprecated in favor of
+ the explicit #cache_version method that can be overwritten.
+ MSG
- if timestamp
- timestamp = timestamp.utc.to_s(cache_timestamp_format)
- "#{model_name.cache_key}/#{id}-#{timestamp}"
- else
- "#{model_name.cache_key}/#{id}"
+ max_updated_column_timestamp(timestamp_names)
+ else
+ max_updated_column_timestamp
+ end
+
+ if timestamp
+ timestamp = timestamp.utc.to_s(cache_timestamp_format)
+ "#{model_name.cache_key}/#{id}-#{timestamp}"
+ else
+ "#{model_name.cache_key}/#{id}"
+ end
end
end
end
+ # Returns a cache version that can be used together with the cache key to form
+ # a recyclable caching scheme. By default, the #updated_at column is used for the
+ # cache_version, but this method can be overwritten to return something else.
+ #
+ # Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
+ # +false+ (which it is by default until Rails 6.0).
+ def cache_version
+ if cache_versioning && timestamp = try(:updated_at)
+ timestamp.utc.to_s(:usec)
+ end
+ end
+
+ # Returns a cache key along with the version.
+ def cache_key_with_version
+ if version = cache_version
+ "#{cache_key}-#{version}"
+ else
+ cache_key
+ end
+ end
+
module ClassMethods
# Defines your model's +to_param+ method to generate "pretty" URLs
# using +method_name+, which can be any attribute or method that
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 54216caaaf..013562708c 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -1,3 +1,5 @@
+require "monitor"
+
module ActiveRecord
module ModelSchema
extend ActiveSupport::Concern
@@ -152,6 +154,8 @@ module ActiveRecord
self.inheritance_column = "type"
delegate :type_for_attribute, to: :class
+
+ initialize_load_schema_monitor
end
# Derives the join table name for +first_table+ and +second_table+. The
@@ -377,7 +381,7 @@ module ActiveRecord
# default values when instantiating the Active Record object for this table.
def column_defaults
load_schema
- _default_attributes.to_hash
+ @column_defaults ||= _default_attributes.to_hash
end
def _default_attributes # :nodoc:
@@ -435,15 +439,27 @@ module ActiveRecord
initialize_find_by_cache
end
+ protected
+
+ def initialize_load_schema_monitor
+ @load_schema_monitor = Monitor.new
+ end
+
private
+ def inherited(child_class)
+ super
+ child_class.initialize_load_schema_monitor
+ end
+
def schema_loaded?
- defined?(@columns_hash) && @columns_hash
+ defined?(@schema_loaded) && @schema_loaded
end
def load_schema
- unless schema_loaded?
- load_schema!
+ return if schema_loaded?
+ @load_schema_monitor.synchronize do
+ load_schema! unless defined?(@columns_hash) && @columns_hash
end
end
@@ -457,6 +473,8 @@ module ActiveRecord
user_provided_default: false
)
end
+
+ @schema_loaded = true
end
def reload_schema_from_cache
@@ -466,10 +484,12 @@ module ActiveRecord
@attribute_types = nil
@content_columns = nil
@default_attributes = nil
+ @column_defaults = nil
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
@attributes_builder = nil
@columns = nil
@columns_hash = nil
+ @schema_loaded = false
@attribute_names = nil
@yaml_encoder = nil
direct_descendants.each do |descendant|
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 01ecd79b8f..3f39fb84e8 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -458,7 +458,7 @@ module ActiveRecord
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})"
+ raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
end
check_record_limit!(options[:limit], attributes_collection)
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 1a9e0a4a40..65fdbc2fe4 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -199,7 +199,7 @@ module ActiveRecord
def klass_join_scope(table, predicate_builder) # :nodoc:
if klass.current_scope
klass.current_scope.clone.tap { |scope|
- scope.joins_values = []
+ scope.joins_values = scope.left_outer_joins_values = [].freeze
}
else
relation = ActiveRecord::Relation.create(
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 333ad16e11..7a8f9abb36 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -2,7 +2,7 @@ module ActiveRecord
# = Active Record \Relation
class Relation
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
- :order, :joins, :left_joins, :left_outer_joins, :references,
+ :order, :joins, :left_outer_joins, :references,
:extending, :unscope]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
@@ -18,6 +18,7 @@ module ActiveRecord
attr_reader :table, :klass, :loaded, :predicate_builder
alias :model :klass
alias :loaded? :loaded
+ alias :locked? :locked
def initialize(klass, table, predicate_builder, values = {})
@klass = klass
@@ -403,9 +404,9 @@ module ActiveRecord
#
# Note: Updating a large number of records will run an
# UPDATE query for each record, which may cause a performance
- # issue. So if it is not needed to run callbacks for each update, it is
- # preferred to use #update_all for updating all records using
- # a single query.
+ # issue. When running callbacks is not needed for each record update,
+ # it is preferred to use #update_all for updating all records
+ # in a single query.
def update(id = :all, attributes)
if id.is_a?(Array)
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 76031515fd..13a2c3f511 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -30,14 +30,14 @@ module ActiveRecord
# end
#
# ==== Options
- # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
- # an order is present in the relation.
+ # an order is present in the relation.
#
# Limits are honored, and if present there is no requirement for the batch
- # size, it can be less than, equal, or greater than the limit.
+ # size: it can be less than, equal to, or greater than the limit.
#
# The options +start+ and +finish+ are especially useful if you want
# multiple workers dealing with the same processing queue. You can make
@@ -89,14 +89,14 @@ module ActiveRecord
# To be yielded each record one by one, use #find_each instead.
#
# ==== Options
- # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
- # an order is present in the relation.
+ # an order is present in the relation.
#
# Limits are honored, and if present there is no requirement for the batch
- # size, it can be less than, equal, or greater than the limit.
+ # size: it can be less than, equal to, or greater than the limit.
#
# The options +start+ and +finish+ are especially useful if you want
# multiple workers dealing with the same processing queue. You can make
@@ -140,9 +140,9 @@ module ActiveRecord
# If you do not provide a block to #in_batches, it will return a
# BatchEnumerator which is enumerable.
#
- # Person.in_batches.with_index do |relation, batch_index|
+ # Person.in_batches.each_with_index do |relation, batch_index|
# puts "Processing relation ##{batch_index}"
- # relation.each { |relation| relation.delete_all }
+ # relation.delete_all
# end
#
# Examples of calling methods on the returned BatchEnumerator object:
@@ -152,12 +152,12 @@ module ActiveRecord
# Person.in_batches.each_record(&:party_all_night!)
#
# ==== Options
- # * <tt>:of</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
+ # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
- # an order is present in the relation.
+ # an order is present in the relation.
#
# Limits are honored, and if present there is no requirement for the batch
# size, it can be less than, equal, or greater than the limit.
@@ -186,7 +186,7 @@ module ActiveRecord
#
# NOTE: It's not possible to set the order. That is automatically set to
# ascending on the primary key ("id ASC") to make the batch ordering
- # consistent. Therefore the primary key must be orderable, e.g an integer
+ # consistent. Therefore the primary key must be orderable, e.g. an integer
# or a string.
#
# NOTE: By its nature, batch processing is subject to race conditions if
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 257ae04ff4..4f739a0415 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -46,6 +46,8 @@ module ActiveRecord
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, to: :klass
+ delegate :ast, :locked, to: :arel
+
module ClassSpecificRelation # :nodoc:
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 183fe91c05..a6309e0b5c 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,12 +1,3 @@
-require "active_record/relation/predicate_builder/array_handler"
-require "active_record/relation/predicate_builder/base_handler"
-require "active_record/relation/predicate_builder/basic_object_handler"
-require "active_record/relation/predicate_builder/range_handler"
-require "active_record/relation/predicate_builder/relation_handler"
-
-require "active_record/relation/predicate_builder/association_query_value"
-require "active_record/relation/predicate_builder/polymorphic_array_value"
-
module ActiveRecord
class PredicateBuilder # :nodoc:
delegate :resolve_column_aliases, to: :table
@@ -178,3 +169,12 @@ module ActiveRecord
end
end
end
+
+require "active_record/relation/predicate_builder/array_handler"
+require "active_record/relation/predicate_builder/base_handler"
+require "active_record/relation/predicate_builder/basic_object_handler"
+require "active_record/relation/predicate_builder/range_handler"
+require "active_record/relation/predicate_builder/relation_handler"
+
+require "active_record/relation/predicate_builder/association_query_value"
+require "active_record/relation/predicate_builder/polymorphic_array_value"
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 76e529f2de..79e65baae5 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1100,14 +1100,16 @@ module ActiveRecord
end
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
- "asc", "desc", "ASC", "DESC"] # :nodoc:
+ "asc", "desc", "ASC", "DESC"].to_set # :nodoc:
def validate_order_args(args)
args.each do |arg|
next unless arg.is_a?(Hash)
arg.each do |_key, value|
- raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
- "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
+ unless VALID_DIRECTIONS.include?(value)
+ raise ArgumentError,
+ "Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 27cdf8cb7e..029156189d 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -156,17 +156,17 @@ module ActiveRecord
if body.respond_to?(:to_proc)
singleton_class.send(:define_method, name) do |*args|
- scope = all.scoping { instance_exec(*args, &body) }
+ scope = all
+ scope = scope.scoping { instance_exec(*args, &body) || scope }
scope = scope.extending(extension) if extension
-
- scope || all
+ scope
end
else
singleton_class.send(:define_method, name) do |*args|
- scope = all.scoping { body.call(*args) }
+ scope = all
+ scope = scope.scoping { body.call(*args) || scope }
scope = scope.extending(extension) if extension
-
- scope || all
+ scope
end
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index c53dee43d7..ba686fc562 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -71,9 +71,9 @@ module ActiveRecord
@tasks[pattern] = task
end
- register_task(/mysql/, 'ActiveRecord::Tasks::MySQLDatabaseTasks')
- register_task(/postgresql/, 'ActiveRecord::Tasks::PostgreSQLDatabaseTasks')
- register_task(/sqlite/, 'ActiveRecord::Tasks::SQLiteDatabaseTasks')
+ register_task(/mysql/, "ActiveRecord::Tasks::MySQLDatabaseTasks")
+ register_task(/postgresql/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks")
+ register_task(/sqlite/, "ActiveRecord::Tasks::SQLiteDatabaseTasks")
def db_dir
@db_dir ||= Rails.application.config.paths["db"].first
diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb
index 68fca44e3b..a79b8eafea 100644
--- a/activerecord/lib/rails/generators/active_record.rb
+++ b/activerecord/lib/rails/generators/active_record.rb
@@ -10,7 +10,7 @@ module ActiveRecord
# Set the current directory as base for the inherited generators.
def self.base_root
- File.dirname(__FILE__)
+ __dir__
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb
index 6954006003..d311ffb703 100644
--- a/activerecord/test/cases/adapters/mysql2/json_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/json_test.rb
@@ -1,17 +1,11 @@
require "cases/helper"
-require "support/schema_dumping_helper"
+require "cases/json_shared_test_cases"
if ActiveRecord::Base.connection.supports_json?
class Mysql2JSONTest < ActiveRecord::Mysql2TestCase
- include SchemaDumpingHelper
+ include JSONSharedTestCases
self.use_transactional_tests = false
- class JsonDataType < ActiveRecord::Base
- self.table_name = "json_data_type"
-
- store_accessor :settings, :resolution
- end
-
def setup
@connection = ActiveRecord::Base.connection
begin
@@ -27,169 +21,9 @@ if ActiveRecord::Base.connection.supports_json?
JsonDataType.reset_column_information
end
- def test_column
- column = JsonDataType.columns_hash["payload"]
- assert_equal :json, column.type
- assert_equal "json", column.sql_type
-
- type = JsonDataType.type_for_attribute("payload")
- assert_not type.binary?
- end
-
- def test_change_table_supports_json
- @connection.change_table("json_data_type") do |t|
- t.json "users"
+ private
+ def column_type
+ :json
end
- JsonDataType.reset_column_information
- column = JsonDataType.columns_hash["users"]
- assert_equal :json, column.type
- end
-
- def test_schema_dumping
- output = dump_table_schema("json_data_type")
- assert_match(/t\.json\s+"settings"/, output)
- end
-
- def test_cast_value_on_write
- x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar }
- assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast)
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload)
- x.save
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
- end
-
- def test_type_cast_json
- type = JsonDataType.type_for_attribute("payload")
-
- data = "{\"a_key\":\"a_value\"}"
- hash = type.deserialize(data)
- assert_equal({ "a_key" => "a_value" }, hash)
- assert_equal({ "a_key" => "a_value" }, type.deserialize(data))
-
- assert_equal({}, type.deserialize("{}"))
- assert_equal({ "key" => nil }, type.deserialize('{"key": null}'))
- assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
- end
-
- def test_rewrite
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- x.payload = { '"a\'' => "b" }
- assert x.save!
- end
-
- def test_select
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- assert_equal({ "k" => "v" }, x.payload)
- end
-
- def test_select_multikey
- @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|
- x = JsonDataType.first
- assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
- end
-
- def test_null_json
- @connection.execute "insert into json_data_type (payload) VALUES(null)"
- x = JsonDataType.first
- assert_nil(x.payload)
- end
-
- def test_select_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- assert_equal(["v0", { "k1" => "v1" }], x.payload)
- end
-
- def test_select_nil_json_after_create
- json = JsonDataType.create(payload: nil)
- x = JsonDataType.where(payload: nil).first
- assert_equal(json, x)
- end
-
- def test_select_nil_json_after_update
- json = JsonDataType.create(payload: "foo")
- x = JsonDataType.where(payload: nil).first
- assert_nil(x)
-
- json.update_attributes payload: nil
- x = JsonDataType.where(payload: nil).first
- assert_equal(json.reload, x)
- end
-
- def test_rewrite_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- x.payload = ["v1", { "k2" => "v2" }, "v3"]
- assert x.save!
- end
-
- def test_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- x.save!
- x = JsonDataType.first
- assert_equal "320×480", x.resolution
-
- x.resolution = "640×1136"
- x.save!
-
- x = JsonDataType.first
- assert_equal "640×1136", x.resolution
- end
-
- def test_duplication_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = x.dup
- assert_equal "320×480", y.resolution
- end
-
- def test_yaml_round_trip_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = YAML.load(YAML.dump(x))
- assert_equal "320×480", y.resolution
- end
-
- def test_changes_in_place
- json = JsonDataType.new
- assert_not json.changed?
-
- json.payload = { "one" => "two" }
- assert json.changed?
- assert json.payload_changed?
-
- json.save!
- assert_not json.changed?
-
- json.payload["three"] = "four"
- assert json.payload_changed?
-
- json.save!
- json.reload
-
- assert_equal({ "one" => "two", "three" => "four" }, json.payload)
- assert_not json.changed?
- end
-
- def test_assigning_string_literal
- json = JsonDataType.create(payload: "foo")
- assert_equal "foo", json.payload
- end
-
- def test_assigning_number
- json = JsonDataType.create(payload: 1.234)
- assert_equal 1.234, json.payload
- end
-
- def test_assigning_boolean
- json = JsonDataType.create(payload: true)
- assert_equal true, json.payload
- end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 99175e8091..539c90f0bc 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -96,7 +96,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_write_binary
- data = File.read(File.join(File.dirname(__FILE__), "..", "..", "..", "assets", "example.log"))
+ data = File.read(File.join(__dir__, "..", "..", "..", "assets", "example.log"))
assert(data.size > 1)
record = ByteaDataType.create(payload: data)
assert_not record.new_record?
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 3deb007513..32afe331fa 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -31,15 +31,21 @@ module ActiveRecord
end
def test_encoding
- assert_not_nil @connection.encoding
+ assert_queries(1) do
+ assert_not_nil @connection.encoding
+ end
end
def test_collation
- assert_not_nil @connection.collation
+ assert_queries(1) do
+ assert_not_nil @connection.collation
+ end
end
def test_ctype
- assert_not_nil @connection.ctype
+ assert_queries(1) do
+ assert_not_nil @connection.ctype
+ end
end
def test_default_client_min_messages
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index d4e627001c..4eeb563781 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -1,14 +1,8 @@
require "cases/helper"
-require "support/schema_dumping_helper"
+require "cases/json_shared_test_cases"
module PostgresqlJSONSharedTestCases
- include SchemaDumpingHelper
-
- class JsonDataType < ActiveRecord::Base
- self.table_name = "json_data_type"
-
- store_accessor :settings, :resolution
- end
+ include JSONSharedTestCases
def setup
@connection = ActiveRecord::Base.connection
@@ -28,16 +22,6 @@ module PostgresqlJSONSharedTestCases
JsonDataType.reset_column_information
end
- def test_column
- column = JsonDataType.columns_hash["payload"]
- assert_equal column_type, column.type
- assert_equal column_type.to_s, column.sql_type
- assert_not column.array?
-
- type = JsonDataType.type_for_attribute("payload")
- assert_not type.binary?
- end
-
def test_default
@connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] }
JsonDataType.reset_column_information
@@ -48,34 +32,6 @@ module PostgresqlJSONSharedTestCases
JsonDataType.reset_column_information
end
- def test_change_table_supports_json
- @connection.transaction do
- @connection.change_table("json_data_type") do |t|
- t.public_send column_type, "users", default: "{}" # t.json 'users', default: '{}'
- end
- JsonDataType.reset_column_information
- column = JsonDataType.columns_hash["users"]
- assert_equal column_type, column.type
-
- raise ActiveRecord::Rollback # reset the schema change
- end
- ensure
- JsonDataType.reset_column_information
- end
-
- def test_schema_dumping
- output = dump_table_schema("json_data_type")
- assert_match(/t\.#{column_type.to_s}\s+"payload",\s+default: {}/, output)
- end
-
- def test_cast_value_on_write
- x = JsonDataType.new payload: { "string" => "foo", :symbol => :bar }
- assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast)
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload)
- x.save
- assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
- end
-
def test_deserialize_with_array
x = JsonDataType.new(objects: ["foo" => "bar"])
assert_equal ["foo" => "bar"], x.objects
@@ -84,140 +40,6 @@ module PostgresqlJSONSharedTestCases
x.reload
assert_equal ["foo" => "bar"], x.objects
end
-
- def test_type_cast_json
- type = JsonDataType.type_for_attribute("payload")
-
- data = "{\"a_key\":\"a_value\"}"
- hash = type.deserialize(data)
- assert_equal({ "a_key" => "a_value" }, hash)
- assert_equal({ "a_key" => "a_value" }, type.deserialize(data))
-
- assert_equal({}, type.deserialize("{}"))
- assert_equal({ "key" => nil }, type.deserialize('{"key": null}'))
- assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
- end
-
- def test_rewrite
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- x.payload = { '"a\'' => "b" }
- assert x.save!
- end
-
- def test_select
- @connection.execute "insert into json_data_type (payload) VALUES ('{\"k\":\"v\"}')"
- x = JsonDataType.first
- assert_equal({ "k" => "v" }, x.payload)
- end
-
- def test_select_multikey
- @connection.execute %q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|
- x = JsonDataType.first
- assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
- end
-
- def test_null_json
- @connection.execute "insert into json_data_type (payload) VALUES(null)"
- x = JsonDataType.first
- assert_nil(x.payload)
- end
-
- def test_select_nil_json_after_create
- json = JsonDataType.create(payload: nil)
- x = JsonDataType.where(payload: nil).first
- assert_equal(json, x)
- end
-
- def test_select_nil_json_after_update
- json = JsonDataType.create(payload: "foo")
- x = JsonDataType.where(payload: nil).first
- assert_nil(x)
-
- json.update_attributes payload: nil
- x = JsonDataType.where(payload: nil).first
- assert_equal(json.reload, x)
- end
-
- def test_select_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- assert_equal(["v0", { "k1" => "v1" }], x.payload)
- end
-
- def test_rewrite_array_json_value
- @connection.execute %q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|
- x = JsonDataType.first
- x.payload = ["v1", { "k2" => "v2" }, "v3"]
- assert x.save!
- end
-
- def test_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- x.save!
- x = JsonDataType.first
- assert_equal "320×480", x.resolution
-
- x.resolution = "640×1136"
- x.save!
-
- x = JsonDataType.first
- assert_equal "640×1136", x.resolution
- end
-
- def test_duplication_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = x.dup
- assert_equal "320×480", y.resolution
- end
-
- def test_yaml_round_trip_with_store_accessors
- x = JsonDataType.new(resolution: "320×480")
- assert_equal "320×480", x.resolution
-
- y = YAML.load(YAML.dump(x))
- assert_equal "320×480", y.resolution
- end
-
- def test_changes_in_place
- json = JsonDataType.new
- assert_not json.changed?
-
- json.payload = { "one" => "two" }
- assert json.changed?
- assert json.payload_changed?
-
- json.save!
- assert_not json.changed?
-
- json.payload["three"] = "four"
- assert json.payload_changed?
-
- json.save!
- json.reload
-
- assert_equal({ "one" => "two", "three" => "four" }, json.payload)
- assert_not json.changed?
- end
-
- def test_assigning_string_literal
- json = JsonDataType.create(payload: "foo")
- assert_equal "foo", json.payload
- end
-
- def test_assigning_number
- json = JsonDataType.create(payload: 1.234)
- assert_equal 1.234, json.payload
- end
-
- def test_assigning_boolean
- json = JsonDataType.create(payload: true)
- assert_equal true, json.payload
- end
end
class PostgresqlJSONTest < ActiveRecord::PostgreSQLTestCase
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index bf570176f4..f86a76e08a 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -75,17 +75,6 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase
end
end
- def test_schema_uniqueness
- assert_nothing_raised do
- set_session_auth
- USERS.each do |u|
- set_session_auth u
- assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1")
- set_session_auth
- end
- end
- end
-
def test_sequence_schema_caching
assert_nothing_raised do
USERS.each do |u|
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 7721bd5cd9..f9d1e44595 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -128,7 +128,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
assert ar.developers_log.empty?
alice = Developer.new(name: "alice")
ar.developers_with_callbacks << alice
- assert_equal"after_adding#{alice.id}", ar.developers_log.last
+ assert_equal "after_adding#{alice.id}", ar.developers_log.last
bob = ar.developers_with_callbacks.create(name: "bob")
assert_equal "after_adding#{bob.id}", ar.developers_log.last
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 8060790594..4bf1b5bcd5 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
@@ -954,7 +954,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_not_nil Developer._reflections["shared_computers"]
# Checking the fixture for named association is important here, because it's the only way
# we've been able to reproduce this bug
- assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers")
+ assert_not_nil File.read(File.expand_path("../../fixtures/developers.yml", __dir__)).index("shared_computers")
assert_equal developers(:david).shared_computers.first, computers(:laptop)
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 ea52fb5a67..9156f6d57a 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -64,10 +64,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
club1.members.sort_by(&:id)
end
- def make_model(name)
- Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
- end
-
def test_ordered_has_many_through
person_prime = Class.new(ActiveRecord::Base) do
def self.name; "Person"; end
@@ -152,20 +148,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert after_destroy_called, "after destroy should be called"
end
- def make_no_pk_hm_t
- lesson = make_model "Lesson"
- student = make_model "Student"
-
- lesson_student = make_model "LessonStudent"
- lesson_student.table_name = "lessons_students"
-
- lesson_student.belongs_to :lesson, anonymous_class: lesson
- lesson_student.belongs_to :student, anonymous_class: student
- lesson.has_many :lesson_students, anonymous_class: lesson_student
- lesson.has_many :students, through: :lesson_students, anonymous_class: student
- [lesson, lesson_student, student]
- end
-
def test_pk_is_not_required_for_join
post = Post.includes(:scategories).first
post2 = Post.includes(:categories).first
@@ -1252,4 +1234,23 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
)
end
end
+
+ private
+ def make_model(name)
+ Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
+ end
+
+ def make_no_pk_hm_t
+ lesson = make_model "Lesson"
+ student = make_model "Student"
+
+ lesson_student = make_model "LessonStudent"
+ lesson_student.table_name = "lessons_students"
+
+ lesson_student.belongs_to :lesson, anonymous_class: lesson
+ lesson_student.belongs_to :student, anonymous_class: student
+ lesson.has_many :lesson_students, anonymous_class: lesson_student
+ lesson.has_many :students, through: :lesson_students, anonymous_class: student
+ [lesson, lesson_student, student]
+ end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 287b3e9ebc..467cc73ecd 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -651,20 +651,6 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
end
- def test_child_instance_should_be_shared_with_replaced_via_method_parent
- face = faces(:confused)
- new_man = Man.new
-
- assert_not_nil face.polymorphic_man
- face.polymorphic_man = new_man
-
- assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance"
- face.description = "Bongo"
- assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance"
- new_man.polymorphic_face.description = "Mungo"
- assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
- end
-
def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed
new_man = Man.new
face = Face.new
diff --git a/activerecord/test/cases/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb
index 2c6a38ec35..7b8264e6e8 100644
--- a/activerecord/test/cases/cache_key_test.rb
+++ b/activerecord/test/cases/cache_key_test.rb
@@ -4,15 +4,23 @@ module ActiveRecord
class CacheKeyTest < ActiveRecord::TestCase
self.use_transactional_tests = false
- class CacheMe < ActiveRecord::Base; end
+ class CacheMe < ActiveRecord::Base
+ self.cache_versioning = false
+ end
+
+ class CacheMeWithVersion < ActiveRecord::Base
+ self.cache_versioning = true
+ end
setup do
@connection = ActiveRecord::Base.connection
- @connection.create_table(:cache_mes) { |t| t.timestamps }
+ @connection.create_table(:cache_mes, force: true) { |t| t.timestamps }
+ @connection.create_table(:cache_me_with_versions, force: true) { |t| t.timestamps }
end
teardown do
@connection.drop_table :cache_mes, if_exists: true
+ @connection.drop_table :cache_me_with_versions, if_exists: true
end
test "cache_key format is not too precise" do
@@ -21,5 +29,23 @@ module ActiveRecord
assert_equal key, record.reload.cache_key
end
+
+ test "cache_key has no version when versioning is on" do
+ record = CacheMeWithVersion.create
+ assert_equal "active_record/cache_key_test/cache_me_with_versions/#{record.id}", record.cache_key
+ end
+
+ test "cache_version is only there when versioning is on" do
+ assert CacheMeWithVersion.create.cache_version.present?
+ assert_not CacheMe.create.cache_version.present?
+ end
+
+ test "cache_key_with_version always has both key and version" do
+ r1 = CacheMeWithVersion.create
+ assert_equal "active_record/cache_key_test/cache_me_with_versions/#{r1.id}-#{r1.updated_at.to_s(:usec)}", r1.cache_key_with_version
+
+ r2 = CacheMe.create
+ assert_equal "active_record/cache_key_test/cache_mes/#{r2.id}-#{r2.updated_at.to_s(:usec)}", r2.cache_key_with_version
+ end
end
end
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 7f63bb2473..db3da53487 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -6,7 +6,6 @@ class EnumTest < ActiveRecord::TestCase
fixtures :books, :authors
setup do
- @author = authors(:david)
@book = books(:awdr)
end
@@ -39,6 +38,8 @@ class EnumTest < ActiveRecord::TestCase
assert_equal @book, Book.author_visibility_visible.first
assert_equal @book, Book.illustrator_visibility_visible.first
assert_equal @book, Book.medium_to_read.first
+ assert_equal books(:ddd), Book.forgotten.first
+ assert_equal books(:rfr), authors(:david).unpublished_books.first
end
test "find via where with values" do
@@ -57,7 +58,6 @@ class EnumTest < ActiveRecord::TestCase
assert_not_equal @book, Book.where(status: :written).first
assert_equal @book, Book.where(status: [:published]).first
assert_not_equal @book, Book.where(status: [:written]).first
- assert_not @author.unpublished_books.include?(@book)
assert_not_equal @book, Book.where.not(status: :published).first
assert_equal @book, Book.where.not(status: :written).first
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index a7b6333010..4837a169fa 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -743,7 +743,6 @@ class FinderTest < ActiveRecord::TestCase
assert Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "HHC", replies_count: 1, approved: false).find(1) }
- assert_raise(ActiveRecord::RecordNotFound) { Topic.where(author_name: "David", title: "The First Topic", replies_count: 1, approved: true).find(1) }
end
def test_condition_interpolation
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index d70572d6eb..b4bbdc6dad 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -316,7 +316,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_new_with_autoload_paths
- path = File.expand_path("../../models/autoloadable", __FILE__)
+ path = File.expand_path("../models/autoloadable", __dir__)
ActiveSupport::Dependencies.autoload_paths << path
firm = Company.new(type: "ExtraFirm")
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index 0678bb714f..9104976126 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -168,13 +168,65 @@ class IntegrationTest < ActiveRecord::TestCase
end
def test_named_timestamps_for_cache_key
- owner = owners(:blackbeard)
- assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)
+ assert_deprecated do
+ owner = owners(:blackbeard)
+ assert_equal "owners/#{owner.id}-#{owner.happy_at.utc.to_s(:usec)}", owner.cache_key(:updated_at, :happy_at)
+ end
end
def test_cache_key_when_named_timestamp_is_nil
- owner = owners(:blackbeard)
- owner.happy_at = nil
- assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at)
+ assert_deprecated do
+ owner = owners(:blackbeard)
+ owner.happy_at = nil
+ assert_equal "owners/#{owner.id}", owner.cache_key(:happy_at)
+ end
+ end
+
+ def test_cache_key_is_stable_with_versioning_on
+ Developer.cache_versioning = true
+
+ developer = Developer.first
+ first_key = developer.cache_key
+
+ developer.touch
+ second_key = developer.cache_key
+
+ assert_equal first_key, second_key
+ ensure
+ Developer.cache_versioning = false
+ end
+
+ def test_cache_version_changes_with_versioning_on
+ Developer.cache_versioning = true
+
+ developer = Developer.first
+ first_version = developer.cache_version
+
+ travel 10.seconds do
+ developer.touch
+ end
+
+ second_version = developer.cache_version
+
+ assert_not_equal first_version, second_version
+ ensure
+ Developer.cache_versioning = false
+ end
+
+ def test_cache_key_retains_version_when_custom_timestamp_is_used
+ Developer.cache_versioning = true
+
+ developer = Developer.first
+ first_key = developer.cache_key_with_version
+
+ travel 10.seconds do
+ developer.touch
+ end
+
+ second_key = developer.cache_key_with_version
+
+ assert_not_equal first_key, second_key
+ ensure
+ Developer.cache_versioning = false
end
end
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
new file mode 100644
index 0000000000..d190b027bf
--- /dev/null
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -0,0 +1,177 @@
+require "support/schema_dumping_helper"
+
+module JSONSharedTestCases
+ include SchemaDumpingHelper
+
+ class JsonDataType < ActiveRecord::Base
+ self.table_name = "json_data_type"
+
+ store_accessor :settings, :resolution
+ end
+
+ def test_column
+ column = JsonDataType.columns_hash["payload"]
+ assert_equal column_type, column.type
+ assert_equal column_type.to_s, column.sql_type
+
+ type = JsonDataType.type_for_attribute("payload")
+ assert_not type.binary?
+ end
+
+ def test_change_table_supports_json
+ @connection.change_table("json_data_type") do |t|
+ t.public_send column_type, "users"
+ end
+ JsonDataType.reset_column_information
+ column = JsonDataType.columns_hash["users"]
+ assert_equal column_type, column.type
+ assert_equal column_type.to_s, column.sql_type
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("json_data_type")
+ assert_match(/t\.#{column_type}\s+"settings"/, output)
+ end
+
+ def test_cast_value_on_write
+ x = JsonDataType.new(payload: { "string" => "foo", :symbol => :bar })
+ assert_equal({ "string" => "foo", :symbol => :bar }, x.payload_before_type_cast)
+ assert_equal({ "string" => "foo", "symbol" => "bar" }, x.payload)
+ x.save!
+ assert_equal({ "string" => "foo", "symbol" => "bar" }, x.reload.payload)
+ end
+
+ def test_type_cast_json
+ type = JsonDataType.type_for_attribute("payload")
+
+ data = '{"a_key":"a_value"}'
+ hash = type.deserialize(data)
+ assert_equal({ "a_key" => "a_value" }, hash)
+ assert_equal({ "a_key" => "a_value" }, type.deserialize(data))
+
+ assert_equal({}, type.deserialize("{}"))
+ assert_equal({ "key" => nil }, type.deserialize('{"key": null}'))
+ assert_equal({ "c" => "}", '"a"' => 'b "a b' }, type.deserialize(%q({"c":"}", "\"a\"":"b \"a b"})))
+ end
+
+ def test_rewrite
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|)
+ x = JsonDataType.first
+ x.payload = { '"a\'' => "b" }
+ assert x.save!
+ end
+
+ def test_select
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|)
+ x = JsonDataType.first
+ assert_equal({ "k" => "v" }, x.payload)
+ end
+
+ def test_select_multikey
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|)
+ x = JsonDataType.first
+ assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
+ end
+
+ def test_null_json
+ @connection.execute("insert into json_data_type (payload) VALUES(null)")
+ x = JsonDataType.first
+ assert_nil(x.payload)
+ end
+
+ def test_select_nil_json_after_create
+ json = JsonDataType.create!(payload: nil)
+ x = JsonDataType.where(payload: nil).first
+ assert_equal(json, x)
+ end
+
+ def test_select_nil_json_after_update
+ json = JsonDataType.create!(payload: "foo")
+ x = JsonDataType.where(payload: nil).first
+ assert_nil(x)
+
+ json.update_attributes(payload: nil)
+ x = JsonDataType.where(payload: nil).first
+ assert_equal(json.reload, x)
+ end
+
+ def test_select_array_json_value
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|)
+ x = JsonDataType.first
+ assert_equal(["v0", { "k1" => "v1" }], x.payload)
+ end
+
+ def test_rewrite_array_json_value
+ @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|)
+ x = JsonDataType.first
+ x.payload = ["v1", { "k2" => "v2" }, "v3"]
+ assert x.save!
+ end
+
+ def test_with_store_accessors
+ x = JsonDataType.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ x.save!
+ x = JsonDataType.first
+ assert_equal "320×480", x.resolution
+
+ x.resolution = "640×1136"
+ x.save!
+
+ x = JsonDataType.first
+ assert_equal "640×1136", x.resolution
+ end
+
+ def test_duplication_with_store_accessors
+ x = JsonDataType.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = x.dup
+ assert_equal "320×480", y.resolution
+ end
+
+ def test_yaml_round_trip_with_store_accessors
+ x = JsonDataType.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = YAML.load(YAML.dump(x))
+ assert_equal "320×480", y.resolution
+ end
+
+ def test_changes_in_place
+ json = JsonDataType.new
+ assert_not json.changed?
+
+ json.payload = { "one" => "two" }
+ assert json.changed?
+ assert json.payload_changed?
+
+ json.save!
+ assert_not json.changed?
+
+ json.payload["three"] = "four"
+ assert json.payload_changed?
+
+ json.save!
+ json.reload
+
+ assert_equal({ "one" => "two", "three" => "four" }, json.payload)
+ assert_not json.changed?
+ end
+
+ def test_assigning_string_literal
+ json = JsonDataType.create!(payload: "foo")
+ assert_equal "foo", json.payload
+ end
+
+ def test_assigning_number
+ json = JsonDataType.create!(payload: 1.234)
+ assert_equal 1.234, json.payload
+ end
+
+ def test_assigning_boolean
+ json = JsonDataType.create!(payload: true)
+ assert_equal true, json.payload
+ end
+end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index f426333aa5..b80257962c 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -59,19 +59,19 @@ class LogSubscriberTest < ActiveRecord::TestCase
logger = TestDebugLogSubscriber.new
assert_equal 0, logger.debugs.length
- logger.sql(Event.new(0, sql: "hi mom!"))
+ logger.sql(Event.new(0.9, sql: "hi mom!"))
assert_equal 1, logger.debugs.length
- logger.sql(Event.new(0, sql: "hi mom!", name: "foo"))
+ logger.sql(Event.new(0.9, sql: "hi mom!", name: "foo"))
assert_equal 2, logger.debugs.length
- logger.sql(Event.new(0, sql: "hi mom!", name: "SCHEMA"))
+ logger.sql(Event.new(0.9, sql: "hi mom!", name: "SCHEMA"))
assert_equal 2, logger.debugs.length
end
def test_sql_statements_are_not_squeezed
logger = TestDebugLogSubscriber.new
- logger.sql(Event.new(0, sql: "ruby rails"))
+ logger.sql(Event.new(0.9, sql: "ruby rails"))
assert_match(/ruby rails/, logger.debugs.first)
end
@@ -87,7 +87,7 @@ class LogSubscriberTest < ActiveRecord::TestCase
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.each do |verb, color_regex|
- logger.sql(Event.new(0, sql: verb.to_s))
+ logger.sql(Event.new(0.9, sql: verb.to_s))
assert_match(/#{REGEXP_BOLD}#{color_regex}#{verb}#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
@@ -96,11 +96,11 @@ class LogSubscriberTest < ActiveRecord::TestCase
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.each do |verb, _|
- logger.sql(Event.new(0, sql: verb.to_s))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
- logger.sql(Event.new(0, sql: verb.to_s, name: "SQL"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA}SQL \(0.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "SQL"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA}SQL \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
@@ -108,14 +108,14 @@ class LogSubscriberTest < ActiveRecord::TestCase
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.each do |verb, _|
- logger.sql(Event.new(0, sql: verb.to_s, name: "Model Load"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Load \(0\.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "Model Load"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Load \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
- logger.sql(Event.new(0, sql: verb.to_s, name: "Model Exists"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Exists \(0\.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "Model Exists"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}Model Exists \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
- logger.sql(Event.new(0, sql: verb.to_s, name: "ANY SPECIFIC NAME"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}ANY SPECIFIC NAME \(0\.0ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: verb.to_s, name: "ANY SPECIFIC NAME"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_CYAN}ANY SPECIFIC NAME \(0\.9ms\)#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
@@ -123,8 +123,8 @@ class LogSubscriberTest < ActiveRecord::TestCase
logger = TestDebugLogSubscriber.new
logger.colorize_logging = true
SQL_COLORINGS.slice(:SELECT, :INSERT, :UPDATE, :DELETE).each do |verb, color_regex|
- logger.sql(Event.new(0, sql: "#{verb} WHERE ID IN SELECT"))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}#{verb} WHERE ID IN SELECT#{REGEXP_CLEAR}/i, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: "#{verb} WHERE ID IN SELECT"))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}#{verb} WHERE ID IN SELECT#{REGEXP_CLEAR}/i, logger.debugs.last)
end
end
@@ -138,8 +138,8 @@ class LogSubscriberTest < ActiveRecord::TestCase
SELECT ID FROM THINGS
)
EOS
- logger.sql(Event.new(0, sql: sql))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}.*#{verb}.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: sql))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{color_regex}.*#{verb}.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
end
end
@@ -151,14 +151,14 @@ class LogSubscriberTest < ActiveRecord::TestCase
(SELECT * FROM mytable FOR UPDATE) ss
WHERE col1 = 5;
EOS
- logger.sql(Event.new(0, sql: sql))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*FOR UPDATE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: sql))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*FOR UPDATE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
sql = <<-EOS
LOCK TABLE films IN SHARE MODE;
EOS
- logger.sql(Event.new(0, sql: sql))
- assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.0ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*LOCK TABLE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
+ logger.sql(Event.new(0.9, sql: sql))
+ assert_match(/#{REGEXP_BOLD}#{REGEXP_MAGENTA} \(0\.9ms\)#{REGEXP_CLEAR} #{REGEXP_BOLD}#{SQL_COLORINGS[:LOCK]}.*LOCK TABLE.*#{REGEXP_CLEAR}/mi, logger.debugs.last)
end
def test_exists_query_logging
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index 06c44c8c52..e9eb9968cb 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -50,6 +50,14 @@ module ActiveRecord
assert column_exists?(table_name, :taggable_type, :string, default: "Photo")
end
+ def test_creates_reference_type_column_with_not_null
+ connection.create_table table_name, force: true do |t|
+ t.references :taggable, null: false, polymorphic: true
+ end
+ assert column_exists?(table_name, :taggable_id, :integer, null: false)
+ assert column_exists?(table_name, :taggable_type, :string, null: false)
+ end
+
def test_does_not_share_options_with_reference_type_column
add_reference table_name, :taggable, type: :integer, limit: 2, polymorphic: true
assert column_exists?(table_name, :taggable_id, :integer, limit: 2)
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index da7875187a..57f94950f9 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -402,33 +402,6 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migrator.up(migrations_path)
end
- def test_migration_sets_internal_metadata_even_when_fully_migrated
- current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- migrations_path = MIGRATIONS_ROOT + "/valid"
- old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
-
- ActiveRecord::Migrator.up(migrations_path)
- assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
-
- original_rails_env = ENV["RAILS_ENV"]
- original_rack_env = ENV["RACK_ENV"]
- ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo"
- new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
-
- refute_equal current_env, new_env
-
- sleep 1 # mysql by default does not store fractional seconds in the database
-
- ActiveRecord::Migrator.up(migrations_path)
- assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
- ensure
- ActiveRecord::Migrator.migrations_paths = old_path
- ENV["RAILS_ENV"] = original_rails_env
- ENV["RACK_ENV"] = original_rack_env
- ActiveRecord::Migrator.up(migrations_path)
- end
-
def test_internal_metadata_stores_environment_when_other_data_exists
ActiveRecord::InternalMetadata.delete_all
ActiveRecord::InternalMetadata[:foo] = "bar"
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index b87419d203..5a62cbd3a6 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -752,7 +752,7 @@ module NestedAttributesOnACollectionAssociationTests
exception = assert_raise ArgumentError do
@pirate.send(association_setter, "foo")
end
- assert_equal 'Hash or Array expected, got String ("foo")', exception.message
+ assert_equal %{Hash or Array expected for attribute `#{@association_name}`, got String ("foo")}, exception.message
end
def test_should_work_with_update_as_well
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 64866eaf2d..c3b39a9295 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -56,7 +56,7 @@ class RelationMergingTest < ActiveRecord::TestCase
def test_relation_merging_with_locks
devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2))
- assert devs.locked.present?
+ assert devs.locked?
end
def test_relation_merging_with_preload
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 11ef0d8743..dea787c07f 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -36,7 +36,7 @@ module ActiveRecord
@relation ||= Relation.new FakeKlass.new("posts"), Post.arel_table, Post.predicate_builder
end
- (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select, :left_joins]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal [:foo], relation.public_send("#{method}_values")
diff --git a/activerecord/test/cases/reload_models_test.rb b/activerecord/test/cases/reload_models_test.rb
index 5dc9d6d8b7..3f4c0c03e3 100644
--- a/activerecord/test/cases/reload_models_test.rb
+++ b/activerecord/test/cases/reload_models_test.rb
@@ -13,7 +13,7 @@ class ReloadModelsTest < ActiveRecord::TestCase
# development environment. Note that meanwhile the class Pet is not
# reloaded, simulating a class that is present in a plugin.
Object.class_eval { remove_const :Owner }
- Kernel.load(File.expand_path(File.join(File.dirname(__FILE__), "../models/owner.rb")))
+ Kernel.load(File.expand_path("../models/owner.rb", __dir__))
pet = Pet.find_by_name("parrot")
pet.owner = Owner.find_by_name("ashley")
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 3fbff7664b..8535be8402 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -229,12 +229,19 @@ class RelationScopingTest < ActiveRecord::TestCase
end
end
- def test_circular_joins_with_current_scope_does_not_crash
+ def test_circular_joins_with_scoping_does_not_crash
posts = Post.joins(comments: :post).scoping do
- Post.current_scope.first(10)
+ Post.first(10)
end
assert_equal posts, Post.joins(comments: :post).first(10)
end
+
+ def test_circular_left_joins_with_scoping_does_not_crash
+ posts = Post.left_joins(comments: :post).scoping do
+ Post.first(10)
+ end
+ assert_equal posts, Post.left_joins(comments: :post).first(10)
+ end
end
class NestedRelationScopingTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 673392b4c4..e1bdaab5cf 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -349,4 +349,32 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic.foo
refute topic.changed?
end
+
+ def test_serialized_attribute_works_under_concurrent_initial_access
+ model = Topic.dup
+
+ topic = model.last
+ topic.update group: "1"
+
+ model.serialize :group, JSON
+ model.reset_column_information
+
+ # This isn't strictly necessary for the test, but a little bit of
+ # knowledge of internals allows us to make failures far more likely.
+ model.define_singleton_method(:define_attribute) do |*args|
+ Thread.pass
+ super(*args)
+ end
+
+ threads = 4.times.map do
+ Thread.new do
+ topic.reload.group
+ end
+ end
+
+ # All the threads should retrieve the value knowing it is JSON, and
+ # thus decode it. If this fails, some threads will instead see the
+ # raw string ("1"), or raise an exception.
+ assert_equal [1] * threads.size, threads.map(&:value)
+ end
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 33da3d11fc..c22d974536 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -294,13 +294,6 @@ if current_adapter?(:Mysql2Adapter)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
end
- def test_structure_dump
- filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "test-db").returns(true)
-
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
- end
-
def test_structure_dump_with_extra_flags
filename = "awesome-file.sql"
expected_command = ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--noop", "test-db"]
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 5d9aa99497..a305aa295a 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -167,6 +167,20 @@ class ValidationsTest < ActiveRecord::TestCase
assert topic.valid?
end
+ def test_numericality_validation_checks_against_raw_value
+ klass = Class.new(Topic) do
+ def self.model_name
+ ActiveModel::Name.new(self, nil, "Topic")
+ end
+ attribute :wibble, :decimal, scale: 2, precision: 9
+ validates_numericality_of :wibble, greater_than_or_equal_to: BigDecimal.new("97.18")
+ end
+
+ assert_not klass.new(wibble: "97.179").valid?
+ assert_not klass.new(wibble: 97.179).valid?
+ assert_not klass.new(wibble: BigDecimal.new("97.179")).valid?
+ end
+
def test_acceptance_validator_doesnt_require_db_connection
klass = Class.new(ActiveRecord::Base) do
self.table_name = "posts"
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index ab0e67cd9d..bfc13d683d 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -123,8 +123,8 @@ class YamlSerializationTest < ActiveRecord::TestCase
def yaml_fixture(file_name)
path = File.expand_path(
- "../../support/yaml_compatibility_fixtures/#{file_name}.yml",
- __FILE__
+ "../support/yaml_compatibility_fixtures/#{file_name}.yml",
+ __dir__
)
File.read(path)
end
diff --git a/activerecord/test/config.rb b/activerecord/test/config.rb
index 6e2e8b2145..a65e6ff776 100644
--- a/activerecord/test/config.rb
+++ b/activerecord/test/config.rb
@@ -1,4 +1,4 @@
-TEST_ROOT = File.expand_path(File.dirname(__FILE__))
+TEST_ROOT = __dir__
ASSETS_ROOT = TEST_ROOT + "/assets"
FIXTURES_ROOT = TEST_ROOT + "/fixtures"
MIGRATIONS_ROOT = TEST_ROOT + "/migrations"
diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml
index b3625ee72e..699623a6f9 100644
--- a/activerecord/test/fixtures/books.yml
+++ b/activerecord/test/fixtures/books.yml
@@ -25,6 +25,7 @@ ddd:
name: "Domain-Driven Design"
format: "hardcover"
status: 2
+ read_status: "forgotten"
tlg:
author_id: 1
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index 5f8a8a96dd..6466e1b341 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -8,7 +8,7 @@ class Book < ActiveRecord::Base
has_many :subscribers, through: :subscriptions
enum status: [:proposed, :written, :published]
- enum read_status: { unread: 0, reading: 2, read: 3 }
+ enum read_status: { unread: 0, reading: 2, read: 3, forgotten: nil }
enum nullable_status: [:single, :married]
enum language: [:english, :spanish, :french], _prefix: :in
enum author_visibility: [:visible, :invisible], _prefix: true
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index e8654dca01..4b2840c653 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -29,6 +29,15 @@ class Category < ActiveRecord::Base
has_many :authors_with_select, -> { select "authors.*, categorizations.post_id" }, through: :categorizations, source: :author
scope :general, -> { where(name: "General") }
+
+ # Should be delegated `ast` and `locked` to `arel`.
+ def self.ast
+ raise
+ end
+
+ def self.locked
+ raise
+ end
end
class SpecialCategory < Category
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 0420e2d15c..d9381ac9cf 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -14,7 +14,7 @@ class Topic < ActiveRecord::Base
scope :replied, -> { where "replies_count > 0" }
scope "approved_as_string", -> { where(approved: true) }
- scope :anonymous_extension, -> { all } do
+ scope :anonymous_extension, -> {} do
def one
1
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 3ce4a0bbab..af70a81414 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,40 @@
+* `#singularize` and `#pluralize` now respect uncountables for the specified locale.
+
+ *Eilis Hamilton*
+
+* Add ActiveSupport::CurrentAttributes to provide a thread-isolated attributes singleton.
+ Primary use case is keeping all the per-request attributes easily available to the whole system.
+
+ *DHH*
+
+* Fix implicit coercion calculations with scalars and durations
+
+ Previously calculations where the scalar is first would be converted to a duration
+ of seconds but this causes issues with dates being converted to times, e.g:
+
+ Time.zone = "Beijing" # => Asia/Shanghai
+ date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
+ 2 * 1.day # => 172800 seconds
+ date + 2 * 1.day # => Mon, 22 May 2017 00:00:00 CST +08:00
+
+ Now the `ActiveSupport::Duration::Scalar` calculation methods will try to maintain
+ the part structure of the duration where possible, e.g:
+
+ Time.zone = "Beijing" # => Asia/Shanghai
+ date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
+ 2 * 1.day # => 2 days
+ date + 2 * 1.day # => Mon, 22 May 2017
+
+ Fixes #29160, #28970.
+
+ *Andrew White*
+
+* Add support for versioned cache entries. This enables the cache stores to recycle cache keys, greatly saving
+ on storage in cases with frequent churn. Works together with the separation of `#cache_key` and `#cache_version`
+ in Active Record and its use in Action Pack's fragment caching.
+
+ *DHH*
+
* Pass gem name and deprecation horizon to deprecation notifications.
*Willem van Bergen*
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 08370cba85..ed277c81ef 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index aa36a01b5b..6f62593f14 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -1,7 +1,7 @@
#!/usr/bin/env ruby
begin
- $:.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
+ $:.unshift(File.expand_path("../lib", __dir__))
require "active_support"
rescue IOError
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 03e3ce821a..a667fbb54a 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -32,6 +32,7 @@ module ActiveSupport
extend ActiveSupport::Autoload
autoload :Concern
+ autoload :CurrentAttributes
autoload :Dependencies
autoload :DescendantsTracker
autoload :ExecutionWrapper
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 4d8c2046e8..a1093a2e23 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -88,10 +88,11 @@ module ActiveSupport
private
def retrieve_cache_key(key)
case
- when key.respond_to?(:cache_key) then key.cache_key
- when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
- when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
- else key.to_param
+ when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
+ when key.respond_to?(:cache_key) then key.cache_key
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
+ else key.to_param
end.to_s
end
@@ -219,6 +220,10 @@ module ActiveSupport
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
#
+ # Setting <tt>:version</tt> verifies the cache stored under <tt>name</tt>
+ # is of the same version. nil is returned on mismatches despite contents.
+ # This feature is used to support recyclable cache keys.
+ #
# Setting <tt>:race_condition_ttl</tt> is very useful in situations where
# a cache entry is used very frequently and is under heavy load. If a
# cache expires and due to heavy load several different processes will try
@@ -287,6 +292,7 @@ module ActiveSupport
instrument(:read, name, options) do |payload|
cached_entry = read_entry(key, options) unless options[:force]
entry = handle_expired_entry(cached_entry, key, options)
+ entry = nil if entry && entry.mismatched?(normalize_version(name, options))
payload[:super_operation] = :fetch if payload
payload[:hit] = !!entry if payload
end
@@ -303,21 +309,30 @@ module ActiveSupport
end
end
- # Fetches data from the cache, using the given key. If there is data in
+ # Reads data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned. Otherwise,
# +nil+ is returned.
#
+ # Note, if data was written with the <tt>:expires_in<tt> or <tt>:version</tt> options,
+ # both of these conditions are applied before the data is returned.
+ #
# Options are passed to the underlying cache implementation.
def read(name, options = nil)
options = merged_options(options)
- key = normalize_key(name, options)
+ key = normalize_key(name, options)
+ version = normalize_version(name, options)
+
instrument(:read, name, options) do |payload|
entry = read_entry(key, options)
+
if entry
if entry.expired?
delete_entry(key, options)
payload[:hit] = false if payload
nil
+ elsif entry.mismatched?(version)
+ payload[:hit] = false if payload
+ nil
else
payload[:hit] = true if payload
entry.value
@@ -341,11 +356,15 @@ module ActiveSupport
results = {}
names.each do |name|
- key = normalize_key(name, options)
- entry = read_entry(key, options)
+ key = normalize_key(name, options)
+ version = normalize_version(name, options)
+ entry = read_entry(key, options)
+
if entry
if entry.expired?
delete_entry(key, options)
+ elsif entry.mismatched?(version)
+ # Skip mismatched versions
else
results[name] = entry.value
end
@@ -396,7 +415,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:write, name, options) do
- entry = Entry.new(value, options)
+ entry = Entry.new(value, options.merge(version: normalize_version(name, options)))
write_entry(normalize_key(name, options), entry, options)
end
end
@@ -420,7 +439,7 @@ module ActiveSupport
instrument(:exist?, name) do
entry = read_entry(normalize_key(name, options), options)
- (entry && !entry.expired?) || false
+ (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
end
end
@@ -517,6 +536,16 @@ module ActiveSupport
end
end
+ # Prefixes a key with the namespace. Namespace and key will be delimited
+ # with a colon.
+ def normalize_key(key, options)
+ key = expanded_key(key)
+ namespace = options[:namespace] if options
+ prefix = namespace.is_a?(Proc) ? namespace.call : namespace
+ key = "#{prefix}:#{key}" if prefix
+ key
+ end
+
# Expands key to be a consistent string value. Invokes +cache_key+ if
# object responds to +cache_key+. Otherwise, +to_param+ method will be
# called. If the key is a Hash, then keys will be sorted alphabetically.
@@ -537,14 +566,16 @@ module ActiveSupport
key.to_param
end
- # Prefixes a key with the namespace. Namespace and key will be delimited
- # with a colon.
- def normalize_key(key, options)
- key = expanded_key(key)
- namespace = options[:namespace] if options
- prefix = namespace.is_a?(Proc) ? namespace.call : namespace
- key = "#{prefix}:#{key}" if prefix
- key
+ def normalize_version(key, options = nil)
+ (options && options[:version].try(:to_param)) || expanded_version(key)
+ end
+
+ def expanded_version(key)
+ case
+ when key.respond_to?(:cache_version) then key.cache_version.to_param
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
+ when key.respond_to?(:to_a) then expanded_version(key.to_a)
+ end
end
def instrument(operation, key, options = nil)
@@ -591,13 +622,16 @@ module ActiveSupport
end
end
- # This class is used to represent cache entries. Cache entries have a value and an optional
- # expiration time. The expiration time is used to support the :race_condition_ttl option
- # on the cache.
+ # This class is used to represent cache entries. Cache entries have a value, an optional
+ # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
+ # on the cache. The version is used to support the :version option on the cache for rejecting
+ # mismatches.
#
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
# using short instance variable names that are lazily defined.
class Entry # :nodoc:
+ attr_reader :version
+
DEFAULT_COMPRESS_LIMIT = 16.kilobytes
# Creates a new cache entry for the specified value. Options supported are
@@ -610,6 +644,7 @@ module ActiveSupport
@value = value
end
+ @version = options[:version]
@created_at = Time.now.to_f
@expires_in = options[:expires_in]
@expires_in = @expires_in.to_f if @expires_in
@@ -619,6 +654,10 @@ module ActiveSupport
compressed? ? uncompress(@value) : @value
end
+ def mismatched?(version)
+ @version && version && @version != version
+ end
+
# Checks if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index e09cee3335..06fa9f67ad 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -97,12 +97,18 @@ module ActiveSupport
options = merged_options(options)
keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
+
raw_values = @data.get_multi(keys_to_names.keys)
values = {}
+
raw_values.each do |key, value|
entry = deserialize_entry(value)
- values[keys_to_names[key]] = entry.value unless entry.expired?
+
+ unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
+ values[keys_to_names[key]] = entry.value
+ end
end
+
values
end
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index f397f658f3..42e0acf66a 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,3 +1,3 @@
-(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"]).each do |path|
+Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).each do |path|
require path
end
diff --git a/activesupport/lib/active_support/current_attributes.rb b/activesupport/lib/active_support/current_attributes.rb
new file mode 100644
index 0000000000..872b0663c7
--- /dev/null
+++ b/activesupport/lib/active_support/current_attributes.rb
@@ -0,0 +1,193 @@
+module ActiveSupport
+ # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
+ # before and after each request. This allows you to keep all the per-request attributes easily
+ # available to the whole system.
+ #
+ # The following full app-like example demonstrates how to use a Current class to
+ # facilitate easy access to the global, per-request attributes without passing them deeply
+ # around everywhere:
+ #
+ # # app/models/current.rb
+ # class Current < ActiveSupport::CurrentAttributes
+ # attribute :account, :user
+ # attribute :request_id, :user_agent, :ip_address
+ #
+ # resets { Time.zone = nil }
+ #
+ # def user=(user)
+ # super
+ # self.account = user.account
+ # Time.zone = user.time_zone
+ # end
+ # end
+ #
+ # # app/controllers/concerns/authentication.rb
+ # module Authentication
+ # extend ActiveSupport::Concern
+ #
+ # included do
+ # before_action :authenticate
+ # end
+ #
+ # private
+ # def authenticate
+ # if authenticated_user = User.find_by(id: cookies.signed[:user_id])
+ # Current.user = authenticated_user
+ # else
+ # redirect_to new_session_url
+ # end
+ # end
+ # end
+ #
+ # # app/controllers/concerns/set_current_request_details.rb
+ # module SetCurrentRequestDetails
+ # extend ActiveSupport::Concern
+ #
+ # included do
+ # before_action do
+ # Current.request_id = request.uuid
+ # Current.user_agent = request.user_agent
+ # Current.ip_address = request.ip
+ # end
+ # end
+ # end
+ #
+ # class ApplicationController < ActionController::Base
+ # include Authentication
+ # include SetCurrentRequestDetails
+ # end
+ #
+ # class MessagesController < ApplicationController
+ # def create
+ # Current.account.messages.create(message_params)
+ # end
+ # end
+ #
+ # class Message < ApplicationRecord
+ # belongs_to :creator, default: -> { Current.user }
+ # after_create { |message| Event.create(record: message) }
+ # end
+ #
+ # class Event < ApplicationRecord
+ # before_create do
+ # self.request_id = Current.request_id
+ # self.user_agent = Current.user_agent
+ # self.ip_address = Current.ip_address
+ # end
+ # end
+ #
+ # A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result.
+ # Current should only be used for a few, top-level globals, like account, user, and request details.
+ # The attributes stuck in Current should be used by more or less all actions on all requests. If you start
+ # sticking controller-specific attributes in there, you're going to create a mess.
+ class CurrentAttributes
+ include ActiveSupport::Callbacks
+ define_callbacks :reset
+
+ class << self
+ # Returns singleton instance for this class in this thread. If none exists, one is created.
+ def instance
+ current_instances[name] ||= new
+ end
+
+ # Declares one or more attributes that will be given both class and instance accessor methods.
+ def attribute(*names)
+ generated_attribute_methods.module_eval do
+ names.each do |name|
+ define_method(name) do
+ attributes[name.to_sym]
+ end
+
+ define_method("#{name}=") do |attribute|
+ attributes[name.to_sym] = attribute
+ end
+ end
+ end
+
+ names.each do |name|
+ define_singleton_method(name) do
+ instance.public_send(name)
+ end
+
+ define_singleton_method("#{name}=") do |attribute|
+ instance.public_send("#{name}=", attribute)
+ end
+ end
+ end
+
+ # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
+ def resets(&block)
+ set_callback :reset, :after, &block
+ end
+
+ delegate :set, :reset, to: :instance
+
+ def reset_all # :nodoc:
+ current_instances.each_value(&:reset)
+ end
+
+ def clear_all # :nodoc:
+ reset_all
+ current_instances.clear
+ end
+
+ private
+ def generated_attribute_methods
+ @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
+ end
+
+ def current_instances
+ Thread.current[:current_attributes_instances] ||= {}
+ end
+
+ def method_missing(name, *args, &block)
+ # Caches the method definition as a singleton method of the receiver.
+ #
+ # By letting #delegate handle it, we avoid an enclosure that'll capture args.
+ singleton_class.delegate name, to: :instance
+
+ send(name, *args, &block)
+ end
+ end
+
+ attr_accessor :attributes
+
+ def initialize
+ @attributes = {}
+ end
+
+ # Expose one or more attributes within a block. Old values are returned after the block concludes.
+ # Example demonstrating the common use of needing to set Current attributes outside the request-cycle:
+ #
+ # class Chat::PublicationJob < ApplicationJob
+ # def perform(attributes, room_number, creator)
+ # Current.set(person: creator) do
+ # Chat::Publisher.publish(attributes: attributes, room_number: room_number)
+ # end
+ # end
+ # end
+ def set(set_attributes)
+ old_attributes = compute_attributes(set_attributes.keys)
+ assign_attributes(set_attributes)
+ yield
+ ensure
+ assign_attributes(old_attributes)
+ end
+
+ # Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
+ def reset
+ run_callbacks :reset do
+ self.attributes = {}
+ end
+ end
+
+ private
+ def assign_attributes(new_attributes)
+ new_attributes.each { |key, value| public_send("#{key}=", value) }
+ end
+
+ def compute_attributes(keys)
+ keys.collect { |key| [ key, public_send(key) ] }.to_h
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index 851d8eeda1..140bdccbb3 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -102,7 +102,7 @@ module ActiveSupport
end
end
- RAILS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/"
+ RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__)
def ignored_callstack(path)
path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"])
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index d4424ed792..39deb2313f 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -37,27 +37,56 @@ module ActiveSupport
end
def +(other)
- calculate(:+, other)
+ if Duration === other
+ seconds = value + other.parts[:seconds]
+ new_parts = other.parts.merge(seconds: seconds)
+ new_value = value + other.value
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:+, other)
+ end
end
def -(other)
- calculate(:-, other)
+ if Duration === other
+ seconds = value - other.parts[:seconds]
+ new_parts = other.parts.map { |part, other_value| [part, -other_value] }.to_h
+ new_parts = new_parts.merge(seconds: seconds)
+ new_value = value - other.value
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:-, other)
+ end
end
def *(other)
- calculate(:*, other)
+ if Duration === other
+ new_parts = other.parts.map { |part, other_value| [part, value * other_value] }.to_h
+ new_value = value * other.value
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:*, other)
+ end
end
def /(other)
- calculate(:/, other)
+ if Duration === other
+ new_parts = other.parts.map { |part, other_value| [part, value / other_value] }.to_h
+ new_value = new_parts.inject(0) { |total, (part, value)| total + value * Duration::PARTS_IN_SECONDS[part] }
+
+ Duration.new(new_value, new_parts)
+ else
+ calculate(:/, other)
+ end
end
private
def calculate(op, other)
if Scalar === other
Scalar.new(value.public_send(op, other.value))
- elsif Duration === other
- Duration.seconds(value).public_send(op, other)
elsif Numeric === other
Scalar.new(value.public_send(op, other))
else
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index f0408f429c..1a1f1a1257 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -10,4 +10,4 @@ end
require "active_support/lazy_load_hooks"
ActiveSupport.run_load_hooks(:i18n)
-I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
+I18n.load_path << File.expand_path("locale/en.yml", __dir__)
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index f05c707ccd..51fe6f3418 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -66,10 +66,6 @@ module I18n
app.reloaders << reloader
app.reloader.to_run do
reloader.execute_if_updated { require_unload_lock! }
- # TODO: remove the following line as soon as the return value of
- # callbacks is ignored, that is, returning `false` does not
- # display a deprecation warning or halts the callback chain.
- true
end
reloader.execute
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 1b089a7538..ff1a0cb8c7 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -28,7 +28,7 @@ module ActiveSupport
# pluralize('CamelOctopus') # => "CamelOctopi"
# pluralize('ley', :es) # => "leyes"
def pluralize(word, locale = :en)
- apply_inflections(word, inflections(locale).plurals)
+ apply_inflections(word, inflections(locale).plurals, locale)
end
# The reverse of #pluralize, returns the singular form of a word in a
@@ -45,7 +45,7 @@ module ActiveSupport
# singularize('CamelOctopi') # => "CamelOctopus"
# singularize('leyes', :es) # => "ley"
def singularize(word, locale = :en)
- apply_inflections(word, inflections(locale).singulars)
+ apply_inflections(word, inflections(locale).singulars, locale)
end
# Converts strings to UpperCamelCase.
@@ -387,12 +387,15 @@ module ActiveSupport
# Applies inflection rules for +singularize+ and +pluralize+.
#
- # apply_inflections('post', inflections.plurals) # => "posts"
- # apply_inflections('posts', inflections.singulars) # => "post"
- def apply_inflections(word, rules)
+ # If passed an optional +locale+ parameter, the uncountables will be
+ # found for that locale.
+ #
+ # apply_inflections('post', inflections.plurals, :en) # => "posts"
+ # apply_inflections('posts', inflections.singulars, :en) # => "post"
+ def apply_inflections(word, rules, locale = :en)
result = word.to_s.dup
- if word.empty? || inflections.uncountables.uncountable?(result)
+ if word.empty? || inflections(locale).uncountables.uncountable?(result)
result
else
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 0912912aba..8223e45e5a 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -357,7 +357,7 @@ module ActiveSupport
# Returns the directory in which the data files are stored.
def self.dirname
- File.dirname(__FILE__) + "/../values/"
+ File.expand_path("../values", __dir__)
end
# Returns the filename for the data file for this version.
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index b875875afe..1b4ecf4d72 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -7,6 +7,12 @@ module ActiveSupport
config.eager_load_namespaces << ActiveSupport
+ initializer "active_support.reset_all_current_attributes_instances" do |app|
+ app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all }
+ app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all }
+ app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all }
+ end
+
initializer "active_support.deprecation_behavior" do |app|
if deprecation = app.config.active_support.deprecation
ActiveSupport::Deprecation.behavior = deprecation
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index dbec684ce0..f53b98c73e 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -579,6 +579,93 @@ module CacheStoreBehavior
end
end
+module CacheStoreVersionBehavior
+ ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version)
+
+ def test_fetch_with_right_version_should_hit
+ @cache.fetch("foo", version: 1) { "bar" }
+ assert_equal "bar", @cache.read("foo", version: 1)
+ end
+
+ def test_fetch_with_wrong_version_should_miss
+ @cache.fetch("foo", version: 1) { "bar" }
+ assert_nil @cache.read("foo", version: 2)
+ end
+
+ def test_read_with_right_version_should_hit
+ @cache.write("foo", "bar", version: 1)
+ assert_equal "bar", @cache.read("foo", version: 1)
+ end
+
+ def test_read_with_wrong_version_should_miss
+ @cache.write("foo", "bar", version: 1)
+ assert_nil @cache.read("foo", version: 2)
+ end
+
+ def test_exist_with_right_version_should_be_true
+ @cache.write("foo", "bar", version: 1)
+ assert @cache.exist?("foo", version: 1)
+ end
+
+ def test_exist_with_wrong_version_should_be_false
+ @cache.write("foo", "bar", version: 1)
+ assert !@cache.exist?("foo", version: 2)
+ end
+
+ def test_reading_and_writing_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.write(m1v1, "bar")
+ assert_equal "bar", @cache.read(m1v1)
+ assert_nil @cache.read(m1v2)
+ end
+
+ def test_reading_and_writing_with_model_supporting_cache_version_using_nested_key
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.write([ "something", m1v1 ], "bar")
+ assert_equal "bar", @cache.read([ "something", m1v1 ])
+ assert_nil @cache.read([ "something", m1v2 ])
+ end
+
+ def test_fetching_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.fetch(m1v1) { "bar" }
+ assert_equal "bar", @cache.fetch(m1v1) { "bu" }
+ assert_equal "bu", @cache.fetch(m1v2) { "bu" }
+ end
+
+ def test_exist_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
+
+ @cache.write(m1v1, "bar")
+ assert @cache.exist?(m1v1)
+ assert_not @cache.fetch(m1v2)
+ end
+
+ def test_fetch_multi_with_model_supporting_cache_version
+ m1v1 = ModelWithKeyAndVersion.new("model/1", 1)
+ m2v1 = ModelWithKeyAndVersion.new("model/2", 1)
+ m2v2 = ModelWithKeyAndVersion.new("model/2", 2)
+
+ first_fetch_values = @cache.fetch_multi(m1v1, m2v1) { |m| m.cache_key }
+ second_fetch_values = @cache.fetch_multi(m1v1, m2v2) { |m| m.cache_key + " 2nd" }
+
+ assert_equal({ m1v1 => "model/1", m2v1 => "model/2" }, first_fetch_values)
+ assert_equal({ m1v1 => "model/1", m2v2 => "model/2 2nd" }, second_fetch_values)
+ end
+
+ def test_version_is_normalized
+ @cache.write("foo", "bar", version: 1)
+ assert_equal "bar", @cache.read("foo", version: "1")
+ end
+end
+
# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
# The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special
# characters like the umlaut in UTF-8.
@@ -822,6 +909,7 @@ class FileStoreTest < ActiveSupport::TestCase
end
include CacheStoreBehavior
+ include CacheStoreVersionBehavior
include LocalCacheBehavior
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
@@ -931,6 +1019,7 @@ class MemoryStoreTest < ActiveSupport::TestCase
end
include CacheStoreBehavior
+ include CacheStoreVersionBehavior
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
@@ -1052,6 +1141,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase
end
include CacheStoreBehavior
+ include CacheStoreVersionBehavior
include LocalCacheBehavior
include CacheIncrementDecrementBehavior
include EncodedKeyCacheBehavior
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 1648a9b270..3108f24f21 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -337,6 +337,13 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
end
+ def test_scalar_plus_parts
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal({ days: 1, seconds: 10 }, (scalar + 1.day).parts)
+ assert_equal({ days: -1, seconds: 10 }, (scalar + -1.day).parts)
+ end
+
def test_scalar_minus
scalar = ActiveSupport::Duration::Scalar.new(10)
@@ -349,6 +356,9 @@ class DurationTest < ActiveSupport::TestCase
assert_equal 5, scalar - 5.seconds
assert_instance_of ActiveSupport::Duration, scalar - 5.seconds
+ assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts)
+ assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts)
+
exception = assert_raises(TypeError) do
scalar - "foo"
end
@@ -356,6 +366,13 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
end
+ def test_scalar_minus_parts
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal({ days: -1, seconds: 10 }, (scalar - 1.day).parts)
+ assert_equal({ days: 1, seconds: 10 }, (scalar - -1.day).parts)
+ end
+
def test_scalar_multiply
scalar = ActiveSupport::Duration::Scalar.new(5)
@@ -375,6 +392,14 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
end
+ def test_scalar_multiply_parts
+ scalar = ActiveSupport::Duration::Scalar.new(1)
+ assert_equal({ days: 2 }, (scalar * 2.days).parts)
+ assert_equal(172800, (scalar * 2.days).value)
+ assert_equal({ days: -2 }, (scalar * -2.days).parts)
+ assert_equal(-172800, (scalar * -2.days).value)
+ end
+
def test_scalar_divide
scalar = ActiveSupport::Duration::Scalar.new(10)
@@ -394,6 +419,15 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
end
+ def test_scalar_divide_parts
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal({ days: 2 }, (scalar / 5.days).parts)
+ assert_equal(172800, (scalar / 5.days).value)
+ assert_equal({ days: -2 }, (scalar / -5.days).parts)
+ assert_equal(-172800, (scalar / -5.days).value)
+ end
+
def test_twelve_months_equals_one_year
assert_equal 12.months, 1.year
end
diff --git a/activesupport/test/current_attributes_test.rb b/activesupport/test/current_attributes_test.rb
new file mode 100644
index 0000000000..67ef6ef619
--- /dev/null
+++ b/activesupport/test/current_attributes_test.rb
@@ -0,0 +1,96 @@
+require "abstract_unit"
+
+class CurrentAttributesTest < ActiveSupport::TestCase
+ Person = Struct.new(:name, :time_zone)
+
+ class Current < ActiveSupport::CurrentAttributes
+ attribute :world, :account, :person, :request
+ delegate :time_zone, to: :person
+
+ resets { Time.zone = "UTC" }
+
+ def account=(account)
+ super
+ self.person = "#{account}'s person"
+ end
+
+ def person=(person)
+ super
+ Time.zone = person.try(:time_zone)
+ end
+
+ def request
+ "#{super} something"
+ end
+
+ def intro
+ "#{person.name}, in #{time_zone}"
+ end
+ end
+
+ setup { Current.reset }
+
+ test "read and write attribute" do
+ Current.world = "world/1"
+ assert_equal "world/1", Current.world
+ end
+
+ test "read overwritten attribute method" do
+ Current.request = "request/1"
+ assert_equal "request/1 something", Current.request
+ end
+
+ test "set attribute via overwritten method" do
+ Current.account = "account/1"
+ assert_equal "account/1", Current.account
+ assert_equal "account/1's person", Current.person
+ end
+
+ test "set auxiliary class via overwritten method" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "Central Time (US & Canada)", Time.zone.name
+ end
+
+ test "resets auxiliary class via callback" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "Central Time (US & Canada)", Time.zone.name
+
+ Current.reset
+ assert_equal "UTC", Time.zone.name
+ end
+
+ test "set attribute only via scope" do
+ Current.world = "world/1"
+
+ Current.set(world: "world/2") do
+ assert_equal "world/2", Current.world
+ end
+
+ assert_equal "world/1", Current.world
+ end
+
+ test "set multiple attributes" do
+ Current.world = "world/1"
+ Current.account = "account/1"
+
+ Current.set(world: "world/2", account: "account/2") do
+ assert_equal "world/2", Current.world
+ assert_equal "account/2", Current.account
+ end
+
+ assert_equal "world/1", Current.world
+ assert_equal "account/1", Current.account
+ end
+
+ test "delegation" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "Central Time (US & Canada)", Current.time_zone
+ assert_equal "Central Time (US & Canada)", Current.instance.time_zone
+ end
+
+ test "all methods forward to the instance" do
+ Current.person = Person.new("David", "Central Time (US & Canada)")
+ assert_equal "David, in Central Time (US & Canada)", Current.intro
+ assert_equal "David, in Central Time (US & Canada)", Current.instance.intro
+ end
+end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index e38d4e83e5..1ea36418ff 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -104,7 +104,7 @@ class DependenciesTest < ActiveSupport::TestCase
with_loading "dependencies" do
old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true
filename = "check_warnings"
- expanded = File.expand_path("#{File.dirname(__FILE__)}/dependencies/#{filename}")
+ expanded = File.expand_path("dependencies/#{filename}", __dir__)
$check_warnings_load_count = 0
assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
@@ -293,7 +293,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_doesnt_break_normal_require
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
with_autoloading_fixtures do
@@ -312,7 +312,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_doesnt_break_normal_require_nested
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -332,7 +332,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_require_returns_true_when_file_not_yet_required
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -345,7 +345,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_require_returns_true_when_file_not_yet_required_even_when_no_new_constants_added
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -359,7 +359,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_require_returns_false_when_file_already_required
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -379,7 +379,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_load_returns_true_when_file_found
- path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ path = File.expand_path("autoloading_fixtures/load_path", __dir__)
original_path = $:.dup
$:.push(path)
@@ -438,7 +438,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_loadable_constants_for_path_should_handle_relative_paths
fake_root = "dependencies"
- relative_root = File.dirname(__FILE__) + "/dependencies"
+ relative_root = File.expand_path("dependencies", __dir__)
["", "/"].each do |suffix|
with_loading fake_root + suffix do
assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(relative_root + "/a/b")
@@ -463,7 +463,7 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_loadable_constants_with_load_path_without_trailing_slash
- path = File.dirname(__FILE__) + "/autoloading_fixtures/class_folder/inline_class.rb"
+ path = File.expand_path("autoloading_fixtures/class_folder/inline_class.rb", __dir__)
with_loading "autoloading_fixtures/class/" do
assert_equal [], ActiveSupport::Dependencies.loadable_constants_for_path(path)
end
@@ -991,7 +991,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_remove_constant_does_not_trigger_loading_autoloads
constant = "ShouldNotBeAutoloaded"
Object.class_eval do
- autoload constant, File.expand_path("../autoloading_fixtures/should_not_be_required", __FILE__)
+ autoload constant, File.expand_path("autoloading_fixtures/should_not_be_required", __dir__)
end
assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant"
diff --git a/activesupport/test/dependencies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb
index 9bc63ed89e..451195a143 100644
--- a/activesupport/test/dependencies_test_helpers.rb
+++ b/activesupport/test/dependencies_test_helpers.rb
@@ -1,7 +1,7 @@
module DependenciesTestHelpers
def with_loading(*from)
old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
- this_dir = File.dirname(__FILE__)
+ this_dir = __dir__
parent_dir = File.dirname(this_dir)
path_copy = $LOAD_PATH.dup
$LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 14bc10513b..ef956eda90 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -420,6 +420,8 @@ class InflectorTest < ActiveSupport::TestCase
inflect.singular(/es$/, "")
inflect.irregular("el", "los")
+
+ inflect.uncountable("agua")
end
assert_equal("hijos", "hijo".pluralize(:es))
@@ -432,12 +434,17 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal("los", "el".pluralize(:es))
assert_equal("els", "el".pluralize)
+ assert_equal("agua", "agua".pluralize(:es))
+ assert_equal("aguas", "agua".pluralize)
+
ActiveSupport::Inflector.inflections(:es) { |inflect| inflect.clear }
assert ActiveSupport::Inflector.inflections(:es).plurals.empty?
assert ActiveSupport::Inflector.inflections(:es).singulars.empty?
+ assert ActiveSupport::Inflector.inflections(:es).uncountables.empty?
assert !ActiveSupport::Inflector.inflections.plurals.empty?
assert !ActiveSupport::Inflector.inflections.singulars.empty?
+ assert !ActiveSupport::Inflector.inflections.uncountables.empty?
end
def test_clear_all
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index af7fc44d66..40dfbe2542 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -237,9 +237,6 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
-class AlsoDoingNothingTest < ActiveSupport::TestCase
-end
-
# Setup and teardown callbacks.
class SetupAndTeardownTest < ActiveSupport::TestCase
setup :reset_callback_record, :foo
diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb
index faa81b5e75..9f28252c31 100644
--- a/activesupport/test/testing/file_fixtures_test.rb
+++ b/activesupport/test/testing/file_fixtures_test.rb
@@ -3,7 +3,7 @@ require "abstract_unit"
require "pathname"
class FileFixturesTest < ActiveSupport::TestCase
- self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__)
+ self.file_fixture_path = File.expand_path("../file_fixtures", __dir__)
test "#file_fixture returns Pathname to file fixture" do
path = file_fixture("sample.txt")
@@ -20,7 +20,7 @@ class FileFixturesTest < ActiveSupport::TestCase
end
class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase
- self.file_fixture_path = Pathname.new(File.expand_path("../../file_fixtures", __FILE__))
+ self.file_fixture_path = Pathname.new(File.expand_path("../file_fixtures", __dir__))
test "#file_fixture_path returns Pathname to file fixture" do
path = file_fixture("sample.txt")
diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb
index e783cea67c..fc35ac113b 100644
--- a/activesupport/test/xml_mini/jdom_engine_test.rb
+++ b/activesupport/test/xml_mini/jdom_engine_test.rb
@@ -2,7 +2,7 @@ require_relative "xml_mini_engine_test"
XMLMiniEngineTest.run_with_platform("java") do
class JDOMEngineTest < XMLMiniEngineTest
- FILES_DIR = File.dirname(__FILE__) + "/../fixtures/xml"
+ FILES_DIR = File.expand_path("../fixtures/xml", __dir__)
def test_not_allowed_to_expand_entities_to_files
attack_xml = <<-EOT
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 1d059cc2a5..8b7aa893fd 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -15,7 +15,7 @@ require "rack/test"
require "action_controller/railtie"
class TestApp < Rails::Application
- config.root = File.dirname(__FILE__)
+ config.root = __dir__
config.session_store :cookie_store, key: "cookie_store_key"
secrets.secret_token = "secret_token"
secrets.secret_key_base = "secret_key_base"
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 7644f6fe4a..3dd66c95ec 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -14,7 +14,7 @@ end
require "action_controller/railtie"
class TestApp < Rails::Application
- config.root = File.dirname(__FILE__)
+ config.root = __dir__
secrets.secret_token = "secret_token"
secrets.secret_key_base = "secret_key_base"
diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb
index 2a193ca6b5..520aa7f7cc 100644
--- a/guides/rails_guides/helpers.rb
+++ b/guides/rails_guides/helpers.rb
@@ -15,7 +15,7 @@ module RailsGuides
end
def documents_by_section
- @documents_by_section ||= YAML.load_file(File.expand_path("../../source/#{@language ? @language + '/' : ''}documents.yaml", __FILE__))
+ @documents_by_section ||= YAML.load_file(File.expand_path("../source/#{@language ? @language + '/' : ''}documents.yaml", __dir__))
end
def documents_flat
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 5d987264f5..22537f960c 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -715,6 +715,9 @@ end
Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to skip this filter only for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place.
+NOTE: Calling the same filter multiple times with different options will not work,
+since the last filter definition will overwrite the previous ones.
+
### After Filters and Around Filters
In addition to "before" filters, you can also run filters after an action has been executed, or both before and after.
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 65146ee7da..7751ac00df 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -781,7 +781,8 @@ config.action_mailer.smtp_settings = {
enable_starttls_auto: true }
```
Note: As of July 15, 2014, Google increased [its security measures](https://support.google.com/accounts/answer/6010255) and now blocks attempts from apps it deems less secure.
-You can change your gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts or
+You can change your Gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts. If your Gmail account has 2-factor authentication enabled,
+then you will need to set an [app password](https://myaccount.google.com/apppasswords) and use that instead of your regular password. Alternatively, you can
use another ESP to send email by replacing 'smtp.gmail.com' above with the address of your provider.
Mailer Testing
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index c835adeab6..10412128cc 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -419,7 +419,7 @@ image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png
#### auto_discovery_link_tag
-Returns a link tag that browsers and feed readers can use to auto-detect an RSS or Atom feed.
+Returns a link tag that browsers and feed readers can use to auto-detect an RSS, Atom, or JSON feed.
```ruby
auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", { title: "RSS Feed" }) # =>
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index b58ca61848..443be77934 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -310,6 +310,12 @@ UserMailer.welcome(@user).deliver_now
UserMailer.welcome(@user).deliver_later
```
+NOTE: Using the asynchronous queue from a Rake task (for example, to
+send an email using `.deliver_later`) will generally not work because Rake will
+likely end, causing the in-process thread pool to be deleted, before any/all
+of the `.deliver_later` emails are processed. To avoid this problem, use
+`.deliver_now` or run a persistent queue in development.
+
Internationalization
--------------------
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 26d01d4ede..aea7515974 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -118,7 +118,7 @@ You can also use this method to query for multiple objects. Call the `find` meth
```ruby
# Find the clients with primary keys 1 and 10.
-client = Client.find([1, 10]) # Or even Client.find(1, 10)
+clients = Client.find([1, 10]) # Or even Client.find(1, 10)
# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
```
@@ -150,7 +150,7 @@ The `take` method returns `nil` if no record is found and no exception will be r
You can pass in a numerical argument to the `take` method to return up to that number of results. For example
```ruby
-client = Client.take(2)
+clients = Client.take(2)
# => [
# #<Client id: 1, first_name: "Lifo">,
# #<Client id: 220, first_name: "Sara">
@@ -189,7 +189,7 @@ If your [default scope](active_record_querying.html#applying-a-default-scope) co
You can pass in a numerical argument to the `first` method to return up to that number of results. For example
```ruby
-client = Client.first(3)
+clients = Client.first(3)
# => [
# #<Client id: 1, first_name: "Lifo">,
# #<Client id: 2, first_name: "Fifo">,
@@ -240,7 +240,7 @@ If your [default scope](active_record_querying.html#applying-a-default-scope) co
You can pass in a numerical argument to the `last` method to return up to that number of results. For example
```ruby
-client = Client.last(3)
+clients = Client.last(3)
# => [
# #<Client id: 219, first_name: "James">,
# #<Client id: 220, first_name: "Sara">,
@@ -557,6 +557,19 @@ In other words, this query can be generated by calling `where` with no argument,
SELECT * FROM clients WHERE (clients.locked != 1)
```
+### OR Conditions
+
+`OR` condition between two relations can be build by calling `or` on the first relation
+and passing the second one as an argument.
+
+```ruby
+Client.where(locked: true).or(Client.where(orders_count: [1,3,5]))
+```
+
+```sql
+SELECT * FROM clients WHERE (clients.locked = 1 OR clients.orders_count IN (1,3,5))
+```
+
Ordering
--------
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index f373d313cc..64200ec242 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -206,16 +206,17 @@ An API application comes with the following middleware by default:
- `ActiveSupport::Cache::Strategy::LocalCache::Middleware`
- `Rack::Runtime`
- `ActionDispatch::RequestId`
+- `ActionDispatch::RemoteIp`
- `Rails::Rack::Logger`
- `ActionDispatch::ShowExceptions`
- `ActionDispatch::DebugExceptions`
-- `ActionDispatch::RemoteIp`
- `ActionDispatch::Reloader`
- `ActionDispatch::Callbacks`
- `ActiveRecord::Migration::CheckPending`
- `Rack::Head`
- `Rack::ConditionalGet`
- `Rack::ETag`
+- `MyApi::Application::Routes`
See the [internal middleware](rails_on_rack.html#internal-middleware-stack)
section of the Rack guide for further information on them.
@@ -360,7 +361,7 @@ middleware set, you can remove it with:
config.middleware.delete ::Rack::Sendfile
```
-Keep in mind that removing these middleware will remove support for certain
+Keep in mind that removing these middlewares will remove support for certain
features in Action Controller.
Choosing Controller Modules
@@ -385,8 +386,9 @@ controller modules by default:
hooks defined by Action Controller (see [the instrumentation
guide](active_support_instrumentation.html#action-controller) for
more information regarding this).
-- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash,
+- `ActionController::ParamsWrapper`: Wraps the parameters hash into a nested hash,
so that you don't have to specify root elements sending POST requests for instance.
+- `ActionController::Head`: Support for returning a response with no content, only headers
Other plugins may add additional modules. You can get a list of all modules
included into `ActionController::API` in the rails console:
@@ -394,12 +396,12 @@ included into `ActionController::API` in the rails console:
```bash
$ bin/rails c
>> ActionController::API.ancestors - ActionController::Metal.ancestors
-=> [ActionController::API,
- ActiveRecord::Railties::ControllerRuntime,
- ActionDispatch::Routing::RouteSet::MountedHelpers,
- ActionController::ParamsWrapper,
- ... ,
- AbstractController::Rendering,
+=> [ActionController::API,
+ ActiveRecord::Railties::ControllerRuntime,
+ ActionDispatch::Routing::RouteSet::MountedHelpers,
+ ActionController::ParamsWrapper,
+ ... ,
+ AbstractController::Rendering,
ActionView::ViewPaths]
```
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 3c61754982..c3c7367304 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -281,7 +281,7 @@ Methods created with `(module|class)_eval(STRING)` have a comment by their side
```ruby
for severity in Severity.constants
- class_eval <<-EOT, __FILE__, __LINE__
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
end # end
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 5794bfa666..5c7d1f5365 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -599,7 +599,7 @@ class CreateBooks < ActiveRecord::Migration[5.0]
t.string :book_number
t.integer :author_id
end
-
+
add_index :books, :author_id
add_foreign_key :books, :authors
end
@@ -1417,7 +1417,7 @@ If either of these saves fails due to validation errors, then the assignment sta
If the parent object (the one declaring the `has_one` association) is unsaved (that is, `new_record?` returns `true`) then the child objects are not saved. They will automatically when the parent object is saved.
-If you want to assign an object to a `has_one` association without saving the object, use the `association.build` method.
+If you want to assign an object to a `has_one` association without saving the object, use the `build_association` method.
### `has_many` Association Reference
@@ -1559,7 +1559,7 @@ The `collection.size` method returns the number of objects in the collection.
The `collection.find` method finds objects within the collection. It uses the same syntax and options as `ActiveRecord::Base.find`.
```ruby
-@available_books = @author.books.find(1)
+@available_book = @author.books.find(1)
```
##### `collection.where(...)`
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index bf9456a482..6a7eaf00e1 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -456,10 +456,14 @@ to `'http authentication'`.
Defaults to `'signed cookie'`.
* `config.action_dispatch.encrypted_cookie_salt` sets the encrypted cookies salt
-value. Defaults to `'encrypted cookie'`.
+ value. Defaults to `'encrypted cookie'`.
* `config.action_dispatch.encrypted_signed_cookie_salt` sets the signed
-encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
+
+* `config.action_dispatch.authenticated_encrypted_cookie_salt` sets the
+ authenticated encrypted cookie salt. Defaults to `'authenticated encrypted
+ cookie'`.
* `config.action_dispatch.perform_deep_munge` configures whether `deep_munge`
method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation)
diff --git a/guides/source/generators.md b/guides/source/generators.md
index a554e08204..d4ed2355d4 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -96,7 +96,7 @@ This is the generator just created:
```ruby
class InitializerGenerator < Rails::Generators::NamedBase
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
end
```
@@ -122,7 +122,7 @@ And now let's change the generator to copy this template when invoked:
```ruby
class InitializerGenerator < Rails::Generators::NamedBase
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
def copy_initializer_file
copy_file "initializer.rb", "config/initializers/#{file_name}.rb"
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index f3ae5a5b28..5553f08456 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -20,16 +20,7 @@ Guide Assumptions
This guide is designed for beginners who want to get started with a Rails
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 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).
+with Rails.
Rails is a web application framework running on the Ruby programming language.
If you have no prior experience with Ruby, you will find a very steep learning
@@ -86,6 +77,9 @@ your prompt will look something like `c:\source_code>`
### Installing Rails
+Before you install Rails, you should check to make sure that your system has the
+proper prerequisites installed. These include Ruby and SQLite3.
+
Open up a command line prompt. On macOS open Terminal.app, on Windows choose
"Run" from your Start menu and type 'cmd.exe'. Any commands prefaced with a
dollar sign `$` should be run in the command line. Verify that you have a
@@ -96,12 +90,19 @@ $ ruby -v
ruby 2.3.1p112
```
+Rails requires Ruby version 2.2.2 or later. If the version number returned is
+less than that number, you'll need to install a fresh copy of Ruby.
+
TIP: A number of tools exist to help you quickly install Ruby and Ruby
on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org),
while macOS users can use [Tokaido](https://github.com/tokaido/tokaidoapp).
For more installation methods for most Operating Systems take a look at
[ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/).
+If you are working on Windows, you should also install the
+[Ruby Installer Development Kit](http://rubyinstaller.org/downloads/).
+
+You will also need an installation of the SQLite3 database.
Many popular UNIX-like OSes ship with an acceptable version of SQLite3.
On Windows, if you installed Rails through Rails Installer, you
already have SQLite installed. Others can find installation instructions
@@ -127,7 +128,7 @@ run the following:
$ rails --version
```
-If it says something like "Rails 5.1.0", you are ready to continue.
+If it says something like "Rails 5.1.1", you are ready to continue.
### Creating the Blog Application
@@ -1195,7 +1196,7 @@ it look as follows:
This time we point the form to the `update` action, which is not defined yet
but will be very soon.
-Passing the article object to the method, will automagically create url for submitting the edited article form.
+Passing the article object to the method, will automagically create url for submitting the edited article form.
This option tells Rails that we want this form to be submitted
via the `PATCH` HTTP method which is the HTTP method you're expected to use to
**update** resources according to the REST protocol.
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 48bb3147f3..caa3d21d23 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -768,7 +768,7 @@ WARNING: The asset tag helpers do _not_ verify the existence of the assets at th
#### Linking to Feeds with the `auto_discovery_link_tag`
-The `auto_discovery_link_tag` helper builds HTML that most browsers and feed readers can use to detect the presence of RSS or Atom feeds. It takes the type of the link (`:rss` or `:atom`), a hash of options that are passed through to url_for, and a hash of options for the tag:
+The `auto_discovery_link_tag` helper builds HTML that most browsers and feed readers can use to detect the presence of RSS, Atom, or JSON feeds. It takes the type of the link (`:rss`, `:atom`, or `:json`), a hash of options that are passed through to url_for, and a hash of options for the tag:
```erb
<%= auto_discovery_link_tag(:rss, {action: "feed"},
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
deleted file mode 100644
index 71efa4b0d0..0000000000
--- a/guides/source/nested_model_forms.md
+++ /dev/null
@@ -1,230 +0,0 @@
-**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-
-Rails Nested Model Forms
-========================
-
-Creating a form for a model _and_ its associations can become quite tedious. Therefore Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations.
-
-After reading this guide, you will know:
-
-* do stuff.
-
---------------------------------------------------------------------------------
-
-NOTE: This guide assumes the user knows how to use the [Rails form helpers](form_helpers.html) in general. Also, it's **not** an API reference. For a complete reference please visit [the Rails API documentation](http://api.rubyonrails.org/).
-
-
-Model setup
------------
-
-To be able to use the nested model functionality in your forms, the model will need to support some basic operations.
-
-First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be built.
-
-If the associated object is an array, a form builder will be yielded for each object, else only a single form builder will be yielded.
-
-Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the `:address` attribute, the `fields_for` form helper will look for a method on the Person instance named `address_attributes=`.
-
-### ActiveRecord::Base model
-
-For an ActiveRecord::Base model and association this writer method is commonly defined with the `accepts_nested_attributes_for` class method:
-
-#### has_one
-
-```ruby
-class Person < ApplicationRecord
- has_one :address
- accepts_nested_attributes_for :address
-end
-```
-
-#### belongs_to
-
-```ruby
-class Person < ApplicationRecord
- belongs_to :firm
- accepts_nested_attributes_for :firm
-end
-```
-
-#### has_many / has_and_belongs_to_many
-
-```ruby
-class Person < ApplicationRecord
- has_many :projects
- accepts_nested_attributes_for :projects
-end
-```
-
-NOTE: For greater detail on associations see [Active Record Associations](association_basics.html).
-For a complete reference on associations please visit the API documentation for [ActiveRecord::Associations::ClassMethods](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html).
-
-### Custom model
-
-As you might have inflected from this explanation, you _don't_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behavior:
-
-#### Single associated object
-
-```ruby
-class Person
- def address
- Address.new
- end
-
- def address_attributes=(attributes)
- # ...
- end
-end
-```
-
-#### Association collection
-
-```ruby
-class Person
- def projects
- [Project.new, Project.new]
- end
-
- def projects_attributes=(attributes)
- # ...
- end
-end
-```
-
-NOTE: See (TODO) in the advanced section for more information on how to deal with the CRUD operations in your custom model.
-
-Views
------
-
-### Controller code
-
-A nested model form will _only_ be built if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first.
-
-Consider the following typical RESTful controller which will prepare a new Person instance and its `address` and `projects` associations before rendering the `new` template:
-
-```ruby
-class PeopleController < ApplicationController
- def new
- @person = Person.new
- @person.build_address
- 2.times { @person.projects.build }
- end
-
- def create
- @person = Person.new(params[:person])
- if @person.save
- # ...
- end
- end
-end
-```
-
-NOTE: Obviously the instantiation of the associated object(s) can become tedious and not DRY, so you might want to move that into the model itself. ActiveRecord::Base provides an `after_initialize` callback which is a good way to refactor this.
-
-### Form code
-
-Now that you have a model instance, with the appropriate methods and associated object(s), you can start building the nested model form.
-
-#### Standard form
-
-Start out with a regular RESTful form:
-
-```erb
-<%= form_for @person do |f| %>
- <%= f.text_field :name %>
-<% end %>
-```
-
-This will generate the following html:
-
-```html
-<form action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" type="text" />
-</form>
-```
-
-#### Nested form for a single associated object
-
-Now add a nested form for the `address` association:
-
-```erb
-<%= form_for @person do |f| %>
- <%= f.text_field :name %>
-
- <%= f.fields_for :address do |af| %>
- <%= af.text_field :street %>
- <% end %>
-<% end %>
-```
-
-This generates:
-
-```html
-<form action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" type="text" />
-
- <input id="person_address_attributes_street" name="person[address_attributes][street]" type="text" />
-</form>
-```
-
-Notice that `fields_for` recognized the `address` as an association for which a nested model form should be built by the way it has namespaced the `name` attribute.
-
-When this form is posted the Rails parameter parser will construct a hash like the following:
-
-```ruby
-{
- "person" => {
- "name" => "Eloy Duran",
- "address_attributes" => {
- "street" => "Nieuwe Prinsengracht"
- }
- }
-}
-```
-
-That's it. The controller will simply pass this hash on to the model from the `create` action. The model will then handle building the `address` association for you and automatically save it when the parent (`person`) is saved.
-
-#### Nested form for a collection of associated objects
-
-The form code for an association collection is pretty similar to that of a single associated object:
-
-```erb
-<%= form_for @person do |f| %>
- <%= f.text_field :name %>
-
- <%= f.fields_for :projects do |pf| %>
- <%= pf.text_field :name %>
- <% end %>
-<% end %>
-```
-
-Which generates:
-
-```html
-<form action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" type="text" />
-
- <input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" type="text" />
- <input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" type="text" />
-</form>
-```
-
-As you can see it has generated 2 `project name` inputs, one for each new `project` that was built in the controller's `new` action. Only this time the `name` attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as:
-
-```ruby
-{
- "person" => {
- "name" => "Eloy Duran",
- "projects_attributes" => {
- "0" => { "name" => "Project 1" },
- "1" => { "name" => "Project 2" }
- }
- }
-}
-```
-
-You can basically see the `projects_attributes` hash as an array of attribute hashes, one for each model instance.
-
-NOTE: The reason that `fields_for` constructed a hash instead of an array is that it won't work for any form nested deeper than one level deep.
-
-TIP: You _can_ however pass an array to the writer method generated by `accepts_nested_attributes_for` if you're using plain Ruby or some other API access. See (TODO) for more info and example.
diff --git a/guides/source/profiling.md b/guides/source/profiling.md
deleted file mode 100644
index ce093f78ba..0000000000
--- a/guides/source/profiling.md
+++ /dev/null
@@ -1,16 +0,0 @@
-*DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-
-A Guide to Profiling Rails Applications
-=======================================
-
-This guide covers built-in mechanisms in Rails for profiling your application.
-
-After reading this guide, you will know:
-
-* Rails profiling terminology.
-* How to write benchmark tests for your application.
-* Other benchmarking approaches and plugins.
-
---------------------------------------------------------------------------------
-
-
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 3e99ee7021..e087834a2f 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -277,6 +277,6 @@ relative paths to your template's location.
```ruby
def source_paths
- [File.expand_path(File.dirname(__FILE__))]
+ [__dir__]
end
```
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index f25b185fb5..cef8450ee4 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -110,11 +110,12 @@ use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
+use ActionDispatch::RemoteIp
+use Sprockets::Rails::QuietAssets
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use WebConsole::Middleware
use ActionDispatch::DebugExceptions
-use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
@@ -124,7 +125,7 @@ use ActionDispatch::Flash
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
-run Rails.application.routes
+run MyApp.application.routes
```
The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below.
@@ -238,6 +239,14 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Makes a unique `X-Request-Id` header available to the response and enables the `ActionDispatch::Request#request_id` method.
+**`ActionDispatch::RemoteIp`**
+
+* Checks for IP spoofing attacks.
+
+**`Sprockets::Rails::QuietAssets`**
+
+* Suppresses logger output for asset requests.
+
**`Rails::Rack::Logger`**
* Notifies the logs that the request has began. After request is complete, flushes all the logs.
@@ -250,10 +259,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Responsible for logging exceptions and showing a debugging page in case the request is local.
-**`ActionDispatch::RemoteIp`**
-
-* Checks for IP spoofing attacks.
-
**`ActionDispatch::Reloader`**
* Provides prepare and cleanup callbacks, intended to assist with code reloading during development.
diff --git a/guides/source/security.md b/guides/source/security.md
index c305350243..f69a0c72b0 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -95,16 +95,23 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves
* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret (`secrets.secret_token`) and inserted into the end of the cookie.
-However, since Rails 4, the default store is EncryptedCookieStore. With
-EncryptedCookieStore the session is encrypted before being stored in a cookie.
-This prevents the user from accessing and tampering the content of the cookie.
-Thus the session becomes a more secure place to store data. The encryption is
-done using a server-side secret key `secrets.secret_key_base` stored in
-`config/secrets.yml`.
+In Rails 4, encrypted cookies through AES in CBC mode with HMAC using SHA1 for
+verification was introduced. This prevents the user from accessing and tampering
+the content of the cookie. Thus the session becomes a more secure place to store
+data. The encryption is performed using a server-side `secrets.secret_key_base`.
+Two salts are used when deriving keys for encryption and verification. These
+salts are set via the `config.action_dispatch.encrypted_cookie_salt` and
+`config.action_dispatch.encrypted_signed_cookie_salt` configuration values.
-That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rails secret` instead_.
+Rails 5.2 uses AES-GCM for the encryption which couples authentication
+and encryption in one faster step and produces shorter ciphertexts.
-`secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.:
+Encrypted cookies are automatically upgraded if the
+`config.action_dispatch.use_authenticated_cookie_encryption` is enabled.
+
+_Do not use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters! Instead use `rails secret` to generate secret keys!_
+
+Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.:
development:
secret_key_base: a75d...
@@ -356,7 +363,7 @@ send_file('/var/www/uploads/' + params[:filename])
Simply pass a file name like "../../../etc/passwd" to download the server's login information. A simple solution against this, is to _check that the requested file is in the expected directory_:
```ruby
-basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files'))
+basename = File.expand_path('../../files', __dir__)
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename !=
File.expand_path(File.join(File.dirname(filename), '../../../'))
@@ -796,7 +803,7 @@ In December 2006, 34,000 actual user names and passwords were stolen in a [MySpa
INFO: _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._
-CSS Injection is explained best by the well-known [MySpace Samy worm](http://namb.la/popular/tech.html). This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, which created so much traffic that MySpace went offline. The following is a technical explanation of that worm.
+CSS Injection is explained best by the well-known [MySpace Samy worm](https://samy.pl/popular/tech.html). This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, which created so much traffic that MySpace went offline. The following is a technical explanation of that worm.
MySpace blocked many tags, but allowed CSS. So the worm's author put JavaScript into CSS like this:
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index cf08c5dd1d..290f2a509b 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -250,7 +250,7 @@ Since it's just a `<form>`, all of the information on `form_with` also applies.
### Customize remote elements
It is possible to customize the behavior of elements with a `data-remote`
-attribute without writing a line of JavaScript. Your can specify extra `data-`
+attribute without writing a line of JavaScript. You can specify extra `data-`
attributes to accomplish this.
#### `data-method`
diff --git a/rails.gemspec b/rails.gemspec
index 2d5be58c17..91316f089f 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/railties/Rakefile b/railties/Rakefile
index 680ed03f75..d6284b7dc5 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -16,10 +16,10 @@ namespace :test do
dash_i = [
"test",
"lib",
- "#{File.dirname(__FILE__)}/../activesupport/lib",
- "#{File.dirname(__FILE__)}/../actionpack/lib",
- "#{File.dirname(__FILE__)}/../actionview/lib",
- "#{File.dirname(__FILE__)}/../activemodel/lib"
+ "#{__dir__}/../activesupport/lib",
+ "#{__dir__}/../actionpack/lib",
+ "#{__dir__}/../actionview/lib",
+ "#{__dir__}/../activemodel/lib"
]
ruby "-w", "-I#{dash_i.join ':'}", file
end
@@ -27,7 +27,7 @@ namespace :test do
end
Rake::TestTask.new("test:regular") do |t|
- t.libs << "test" << "#{File.dirname(__FILE__)}/../activesupport/lib"
+ t.libs << "test" << "#{__dir__}/../activesupport/lib"
t.pattern = "test/**/*_test.rb"
t.warning = false
t.verbose = true
diff --git a/railties/exe/rails b/railties/exe/rails
index 7e791c1f99..a5635c2297 100755
--- a/railties/exe/rails
+++ b/railties/exe/rails
@@ -1,9 +1,9 @@
#!/usr/bin/env ruby
-git_path = File.expand_path("../../../.git", __FILE__)
+git_path = File.expand_path("../../.git", __dir__)
if File.exist?(git_path)
- railties_path = File.expand_path("../../lib", __FILE__)
+ railties_path = File.expand_path("../lib", __dir__)
$:.unshift(railties_path)
end
require "rails/cli"
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index f8a923141d..39ca2db8e1 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -260,6 +260,7 @@ module Rails
"action_dispatch.signed_cookie_salt" => config.action_dispatch.signed_cookie_salt,
"action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt,
"action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt,
+ "action_dispatch.authenticated_encrypted_cookie_salt" => config.action_dispatch.authenticated_encrypted_cookie_salt,
"action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer,
"action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest
)
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 27c1572357..4ffde6198a 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -77,9 +77,21 @@ module Rails
assets.unknown_asset_fallback = false
end
+ if respond_to?(:action_view)
+ action_view.form_with_generates_remote_forms = true
+ end
+
when "5.2"
load_defaults "5.1"
+ if respond_to?(:active_record)
+ active_record.cache_versioning = true
+ end
+
+ if respond_to?(:action_dispatch)
+ action_dispatch.use_authenticated_cookie_encryption = true
+ end
+
else
raise "Unknown version #{target_version.to_s.inspect}"
end
diff --git a/railties/lib/rails/application_controller.rb b/railties/lib/rails/application_controller.rb
index a98e51fd28..f7d112900a 100644
--- a/railties/lib/rails/application_controller.rb
+++ b/railties/lib/rails/application_controller.rb
@@ -1,5 +1,5 @@
class Rails::ApplicationController < ActionController::Base # :nodoc:
- self.view_paths = File.expand_path("../templates", __FILE__)
+ self.view_paths = File.expand_path("templates", __dir__)
layout "application"
private
diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb
index 8fda1c87c6..a00e58997c 100644
--- a/railties/lib/rails/command/actions.rb
+++ b/railties/lib/rails/command/actions.rb
@@ -5,7 +5,7 @@ module Rails
# This allows us to run `rails server` from other directories, but still get
# the main config.ru and properly set the tmp directory.
def set_application_directory!
- Dir.chdir(File.expand_path("../../", APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
+ Dir.chdir(File.expand_path("../..", APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
end
def require_application_and_environment!
diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb
index 03a640bd65..651411d444 100644
--- a/railties/lib/rails/commands/secrets/secrets_command.rb
+++ b/railties/lib/rails/commands/secrets/secrets_command.rb
@@ -13,10 +13,7 @@ module Rails
end
def setup
- require "rails/generators"
- require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
-
- Rails::Generators::EncryptedSecretsGenerator.start
+ generator.start
end
def edit
@@ -34,7 +31,6 @@ module Rails
require_application_and_environment!
Rails::Secrets.read_for_editing do |tmp_path|
- say "Waiting for secrets file to be saved. Abort with Ctrl-C."
system("\$EDITOR #{tmp_path}")
end
@@ -43,7 +39,22 @@ module Rails
say "Aborted changing encrypted secrets: nothing saved."
rescue Rails::Secrets::MissingKeyError => error
say error.message
+ rescue Errno::ENOENT => error
+ raise unless error.message =~ /secrets\.yml\.enc/
+
+ Rails::Secrets.read_template_for_editing do |tmp_path|
+ system("\$EDITOR #{tmp_path}")
+ generator.skip_secrets_file { setup }
+ end
end
+
+ private
+ def generator
+ require "rails/generators"
+ require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
+
+ Rails::Generators::EncryptedSecretsGenerator
+ end
end
end
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index dc0b158bd4..2732485c5a 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -40,7 +40,7 @@ module Rails
#
# class MyEngine < Rails::Engine
# # Add a load path for this specific Engine
- # config.autoload_paths << File.expand_path("../lib/some/path", __FILE__)
+ # config.autoload_paths << File.expand_path("lib/some/path", __dir__)
#
# initializer "my_engine.add_middleware" do |app|
# app.middleware.use MyEngine::Middleware
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 8ec805370b..8f15f3a594 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -1,4 +1,4 @@
-activesupport_path = File.expand_path("../../../../activesupport/lib", __FILE__)
+activesupport_path = File.expand_path("../../../activesupport/lib", __dir__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require "thor/group"
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index c715e5ac9f..e8b104a0b2 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -13,7 +13,6 @@ module Rails
DATABASES = %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver )
JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc )
DATABASES.concat(JDBC_DATABASES)
- WEBPACKS = %w( react vue angular )
attr_accessor :rails_template
add_shebang_option!
@@ -31,9 +30,6 @@ module Rails
class_option :database, type: :string, aliases: "-d", default: "sqlite3",
desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :webpack, type: :string, default: nil,
- desc: "Preconfigure for app-like JavaScript with Webpack (options: #{WEBPACKS.join('/')})"
-
class_option :skip_yarn, type: :boolean, default: false,
desc: "Don't use Yarn for managing JavaScript dependencies"
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index a650c52626..e7f51dba99 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -215,7 +215,7 @@ module Rails
# Returns the base root for a common set of generators. This is used to dynamically
# guess the default source root.
def self.base_root
- File.dirname(__FILE__)
+ __dir__
end
# Cache source root and add lib/generators/base/generator/templates to
diff --git a/railties/lib/rails/generators/css/assets/assets_generator.rb b/railties/lib/rails/generators/css/assets/assets_generator.rb
index 20baf31a34..af7b5cf609 100644
--- a/railties/lib/rails/generators/css/assets/assets_generator.rb
+++ b/railties/lib/rails/generators/css/assets/assets_generator.rb
@@ -3,7 +3,7 @@ require "rails/generators/named_base"
module Css # :nodoc:
module Generators # :nodoc:
class AssetsGenerator < Rails::Generators::NamedBase # :nodoc:
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
def copy_stylesheet
copy_file "stylesheet.css", File.join("app/assets/stylesheets", class_path, "#{file_name}.css")
diff --git a/railties/lib/rails/generators/js/assets/assets_generator.rb b/railties/lib/rails/generators/js/assets/assets_generator.rb
index 64d706ec91..52a71b58cd 100644
--- a/railties/lib/rails/generators/js/assets/assets_generator.rb
+++ b/railties/lib/rails/generators/js/assets/assets_generator.rb
@@ -3,7 +3,7 @@ require "rails/generators/named_base"
module Js # :nodoc:
module Generators # :nodoc:
class AssetsGenerator < Rails::Generators::NamedBase # :nodoc:
- source_root File.expand_path("../templates", __FILE__)
+ source_root File.expand_path("templates", __dir__)
def copy_javascript
copy_file "javascript.js", File.join("app/assets/javascripts", class_path, "#{file_name}.js")
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 669514b37e..20ee4b108d 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -121,7 +121,6 @@ module Rails
action_cable_config_exist = File.exist?("config/cable.yml")
rack_cors_config_exist = File.exist?("config/initializers/cors.rb")
assets_config_exist = File.exist?("config/initializers/assets.rb")
- new_framework_defaults_5_1_exist = File.exist?("config/initializers/new_framework_defaults_5_1.rb")
config
@@ -145,12 +144,6 @@ module Rails
unless assets_config_exist
remove_file "config/initializers/assets.rb"
end
-
- # Sprockets owns the only new default for 5.1:
- # In API-only Applications, we don't want the file.
- unless new_framework_defaults_5_1_exist
- remove_file "config/initializers/new_framework_defaults_5_1.rb"
- end
end
end
@@ -208,10 +201,12 @@ module Rails
module Generators
# We need to store the RAILS_DEV_PATH in a constant, otherwise the path
# can change in Ruby 1.8.7 when we FileUtils.cd.
- RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__))
+ RAILS_DEV_PATH = File.expand_path("../../../../../..", __dir__)
RESERVED_NAMES = %w[application destroy plugin runner test]
class AppGenerator < AppBase # :nodoc:
+ WEBPACKS = %w( react vue angular )
+
add_shared_options_for "application"
# Add bin/rails options
@@ -224,6 +219,9 @@ module Rails
class_option :skip_bundle, type: :boolean, aliases: "-B", default: false,
desc: "Don't run bundle install"
+ class_option :webpack, type: :string, default: nil,
+ desc: "Preconfigure for app-like JavaScript with Webpack (options: #{WEBPACKS.join('/')})"
+
def initialize(*args)
super
@@ -401,7 +399,7 @@ module Rails
def delete_new_framework_defaults
unless options[:update]
- remove_file "config/initializers/new_framework_defaults_5_1.rb"
+ remove_file "config/initializers/new_framework_defaults_5_2.rb"
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 1911fb7a7b..747d2e6253 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -1,9 +1,5 @@
source 'https://rubygems.org'
-
-git_source(:github) do |repo_name|
- repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
- "https://github.com/#{repo_name}.git"
-end
+git_source(:github) { |repo| "https://github.com/#{repo}.git" }
<% gemfile_entries.each do |gem| -%>
<% if gem.comment -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/bundle b/railties/lib/rails/generators/rails/app/templates/bin/bundle
index 1123dcf501..a84f0afe47 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/bundle
+++ b/railties/lib/rails/generators/rails/app/templates/bin/bundle
@@ -1,2 +1,2 @@
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
load Gem.bin_path('bundler', 'bundle')
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
index 52b3de5ee5..560cc64a3f 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
@@ -3,7 +3,7 @@ require 'fileutils'
include FileUtils
# path to your application root.
-APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+APP_ROOT = Pathname.new File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
index d385b363c6..0aedf0d6e2 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
@@ -3,7 +3,7 @@ require 'fileutils'
include FileUtils
# path to your application root.
-APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+APP_ROOT = Pathname.new File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt
deleted file mode 100644
index a0c7f44b60..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_1.rb.tt
+++ /dev/null
@@ -1,16 +0,0 @@
-# Be sure to restart your server when you modify this file.
-#
-# This file contains migration options to ease your Rails 5.1 upgrade.
-#
-# Once upgraded flip defaults one by one to migrate to the new default.
-#
-# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-
-# Make `form_with` generate non-remote forms.
-Rails.application.config.action_view.form_with_generates_remote_forms = false
-<%- unless options[:skip_sprockets] -%>
-
-# Unknown asset fallback will return the path passed in when the given
-# asset is not present in the asset pipeline.
-# Rails.application.config.assets.unknown_asset_fallback = false
-<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
new file mode 100644
index 0000000000..900baa607a
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
@@ -0,0 +1,15 @@
+# Be sure to restart your server when you modify this file.
+#
+# This file contains migration options to ease your Rails 5.2 upgrade.
+#
+# Once upgraded flip defaults one by one to migrate to the new default.
+#
+# Read the Guide for Upgrading Ruby on Rails for more info on each option.
+
+# Make Active Record use stable #cache_key alongside new #cache_version method.
+# This is needed for recyclable cache keys.
+# Rails.application.config.active_record.cache_versioning = true
+
+# Use AES 256 GCM authenticated encryption for encrypted cookies.
+# Existing cookies will be converted on read then written with the new scheme.
+# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index 2f92168eef..7568af5b5e 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
@@ -1,4 +1,4 @@
-require File.expand_path('../../config/environment', __FILE__)
+require File.expand_path('../config/environment', __dir__)
require 'rails/test_help'
class ActiveSupport::TestCase
diff --git a/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb b/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
index 8b29213610..1da2fbc1a5 100644
--- a/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
+++ b/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
@@ -36,25 +36,29 @@ module Rails
end
def add_encrypted_secrets_file
- unless File.exist?("config/secrets.yml.enc")
+ unless (defined?(@@skip_secrets_file) && @@skip_secrets_file) || File.exist?("config/secrets.yml.enc")
say "Adding config/secrets.yml.enc to store secrets that needs to be encrypted."
say ""
+ say "For now the file contains this but it's been encrypted with the generated key:"
+ say ""
+ say Secrets.template, :on_green
+ say ""
- template "config/secrets.yml.enc" do |prefill|
- say ""
- say "For now the file contains this but it's been encrypted with the generated key:"
- say ""
- say prefill, :on_green
- say ""
-
- Secrets.encrypt(prefill)
- end
+ Secrets.write(Secrets.template)
say "You can edit encrypted secrets with `bin/rails secrets:edit`."
-
- say "Add this to your config/environments/production.rb:"
- say "config.read_encrypted_secrets = true"
+ say ""
end
+
+ say "Add this to your config/environments/production.rb:"
+ say "config.read_encrypted_secrets = true"
+ end
+
+ def self.skip_secrets_file
+ @@skip_secrets_file = true
+ yield
+ ensure
+ @@skip_secrets_file = false
end
private
diff --git a/railties/lib/rails/generators/rails/encrypted_secrets/templates/config/secrets.yml.enc b/railties/lib/rails/generators/rails/encrypted_secrets/templates/config/secrets.yml.enc
deleted file mode 100644
index 70426a66a5..0000000000
--- a/railties/lib/rails/generators/rails/encrypted_secrets/templates/config/secrets.yml.enc
+++ /dev/null
@@ -1,3 +0,0 @@
-# See `secrets.yml` for tips on generating suitable keys.
-# production:
-# external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289…
diff --git a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt
index d0575772bc..178d5c3f9f 100644
--- a/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt
+++ b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt
@@ -1,3 +1,3 @@
class <%= class_name %>Generator < Rails::Generators::NamedBase
- source_root File.expand_path('../templates', __FILE__)
+ source_root File.expand_path('templates', __dir__)
end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
index d84d1aabdb..9a8c4bf098 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
@@ -1,4 +1,4 @@
-$:.push File.expand_path("../lib", __FILE__)
+$:.push File.expand_path("lib", __dir__)
# Maintain your gem's version:
require "<%= namespaced_name %>/version"
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
index 383d2fb2d1..3581dd401a 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
@@ -15,7 +15,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
end
<% if engine? && !options[:skip_active_record] && with_dummy_app? -%>
-APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
+APP_RAKEFILE = File.expand_path("<%= dummy_path -%>/Rakefile", __dir__)
load 'rails/tasks/engine.rake'
<% end %>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
index c03d9953d4..ffa277e334 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
@@ -1,12 +1,12 @@
# This command will automatically be run when you run "rails" with Rails gems
# installed from the root of your application.
-ENGINE_ROOT = File.expand_path('../..', __FILE__)
-ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__)
-APP_PATH = File.expand_path('../../<%= dummy_path -%>/config/application', __FILE__)
+ENGINE_ROOT = File.expand_path('..', __dir__)
+ENGINE_PATH = File.expand_path('../lib/<%= namespaced_name -%>/engine', __dir__)
+APP_PATH = File.expand_path('../<%= dummy_path -%>/config/application', __dir__)
# Set up gems listed in the Gemfile.
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
require 'rails/all'
diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
index 8385e6a8a2..8e7d321626 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
@@ -1,4 +1,4 @@
-$: << File.expand_path(File.expand_path("../../test", __FILE__))
+$: << File.expand_path("../test", __dir__)
require "bundler/setup"
require "rails/plugin/test"
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
index e84e403018..32e8202e1c 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
@@ -1,8 +1,8 @@
-require File.expand_path("../../<%= options[:dummy_path] -%>/config/environment.rb", __FILE__)
+require File.expand_path("../<%= options[:dummy_path] -%>/config/environment.rb", __dir__)
<% unless options[:skip_active_record] -%>
-ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../<%= options[:dummy_path] -%>/db/migrate", __FILE__)]
+ActiveRecord::Migrator.migrations_paths = [File.expand_path("../<%= options[:dummy_path] -%>/db/migrate", __dir__)]
<% if options[:mountable] -%>
-ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__)
+ActiveRecord::Migrator.migrations_paths << File.expand_path('../db/migrate', __dir__)
<% end -%>
<% end -%>
require "rails/test_help"
@@ -17,7 +17,7 @@ Rails::TestUnitReporter.executable = 'bin/test'
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
- ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
+ ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
ActiveSupport::TestCase.fixtures :all
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 3eec929aeb..575af80303 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -14,7 +14,7 @@ module Rails
#
# class AppGeneratorTest < Rails::Generators::TestCase
# tests AppGenerator
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # destination File.expand_path("../tmp", __dir__)
# end
#
# If you want to ensure your destination root is clean before running each test,
@@ -22,7 +22,7 @@ module Rails
#
# class AppGeneratorTest < Rails::Generators::TestCase
# tests AppGenerator
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # destination File.expand_path("../tmp", __dir__)
# setup :prepare_destination
# end
class TestCase < ActiveSupport::TestCase
diff --git a/railties/lib/rails/generators/test_unit/system/system_generator.rb b/railties/lib/rails/generators/test_unit/system/system_generator.rb
index aec415a4e5..0514957d9c 100644
--- a/railties/lib/rails/generators/test_unit/system/system_generator.rb
+++ b/railties/lib/rails/generators/test_unit/system/system_generator.rb
@@ -10,7 +10,7 @@ module TestUnit # :nodoc:
template "application_system_test_case.rb", File.join("test", "application_system_test_case.rb")
end
- template "system_test.rb", File.join("test/system", "#{file_name.pluralize}_test.rb")
+ template "system_test.rb", File.join("test/system", class_path, "#{file_name.pluralize}_test.rb")
end
end
end
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
index 64d641d096..7a954a791d 100644
--- a/railties/lib/rails/generators/testing/behaviour.rb
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -40,7 +40,7 @@ module Rails
# Sets the destination of generator files:
#
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # destination File.expand_path("../tmp", __dir__)
def destination(path)
self.destination_root = path
end
@@ -51,7 +51,7 @@ module Rails
#
# class AppGeneratorTest < Rails::Generators::TestCase
# tests AppGenerator
- # destination File.expand_path("../tmp", File.dirname(__FILE__))
+ # destination File.expand_path("../tmp", __dir__)
# setup :prepare_destination
#
# test "database.yml is not created when skipping Active Record" do
diff --git a/railties/lib/rails/secrets.rb b/railties/lib/rails/secrets.rb
index 8b644f212c..20c20cb9f1 100644
--- a/railties/lib/rails/secrets.rb
+++ b/railties/lib/rails/secrets.rb
@@ -1,5 +1,6 @@
require "yaml"
require "active_support/message_encryptor"
+require "active_support/core_ext/string/strip"
module Rails
# Greatly inspired by Ara T. Howard's magnificent sekrets gem. 😘
@@ -37,6 +38,15 @@ module Rails
ENV["RAILS_MASTER_KEY"] || read_key_file || handle_missing_key
end
+ def template
+ <<-end_of_template.strip_heredoc
+ # See `secrets.yml` for tips on generating suitable keys.
+ # production:
+ # external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289…
+
+ end_of_template
+ end
+
def encrypt(data)
encryptor.encrypt_and_sign(data)
end
@@ -54,15 +64,12 @@ module Rails
FileUtils.mv("#{path}.tmp", path)
end
- def read_for_editing
- tmp_path = File.join(Dir.tmpdir, File.basename(path))
- IO.binwrite(tmp_path, read)
-
- yield tmp_path
+ def read_for_editing(&block)
+ writing(read, &block)
+ end
- write(IO.binread(tmp_path))
- ensure
- FileUtils.rm(tmp_path) if File.exist?(tmp_path)
+ def read_template_for_editing(&block)
+ writing(template, &block)
end
private
@@ -92,6 +99,17 @@ module Rails
end
end
+ def writing(contents)
+ tmp_path = File.join(Dir.tmpdir, File.basename(path))
+ File.write(tmp_path, contents)
+
+ yield tmp_path
+
+ write(File.read(tmp_path))
+ ensure
+ FileUtils.rm(tmp_path) if File.exist?(tmp_path)
+ end
+
def encryptor
@encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: @cipher)
end
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 32a6b109bc..80720a42ff 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -16,7 +16,7 @@ namespace :app do
namespace :templates do
# desc "Copy all the templates from rails to the application directory for customization. Already existing local copies will be overwritten"
task :copy do
- generators_lib = File.expand_path("../../generators", __FILE__)
+ generators_lib = File.expand_path("../generators", __dir__)
project_templates = "#{Rails.root}/lib/templates"
default_templates = { "erb" => %w{controller mailer scaffold},
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 76de2b4639..2df303750c 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index e4b2d0457d..2d4c7a0f0b 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -12,7 +12,7 @@ require "rails/all"
module TestApp
class Application < Rails::Application
- config.root = File.dirname(__FILE__)
+ config.root = __dir__
secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33"
end
end
diff --git a/railties/test/application/current_attributes_integration_test.rb b/railties/test/application/current_attributes_integration_test.rb
new file mode 100644
index 0000000000..5653ec0be1
--- /dev/null
+++ b/railties/test/application/current_attributes_integration_test.rb
@@ -0,0 +1,84 @@
+require "isolation/abstract_unit"
+require "rack/test"
+
+class CurrentAttributesIntegrationTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ setup do
+ build_app
+
+ app_file "app/models/current.rb", <<-RUBY
+ class Current < ActiveSupport::CurrentAttributes
+ attribute :customer
+
+ resets { Time.zone = "UTC" }
+
+ def customer=(customer)
+ super
+ Time.zone = customer.try(:time_zone)
+ end
+ end
+ RUBY
+
+ app_file "app/models/customer.rb", <<-RUBY
+ class Customer < Struct.new(:name)
+ def time_zone
+ "Copenhagen"
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get "/customers/:action", controller: :customers
+ end
+ RUBY
+
+ app_file "app/controllers/customers_controller.rb", <<-RUBY
+ class CustomersController < ApplicationController
+ def set_current_customer
+ Current.customer = Customer.new("david")
+ render :index
+ end
+
+ def set_no_customer
+ render :index
+ end
+ end
+ RUBY
+
+ app_file "app/views/customers/index.html.erb", <<-RUBY
+ <%= Current.customer.try(:name) || 'noone' %>,<%= Time.zone.name %>
+ RUBY
+
+ require "#{app_path}/config/environment"
+ end
+
+ teardown :teardown_app
+
+ test "current customer is assigned and cleared" do
+ get "/customers/set_current_customer"
+ assert_equal 200, last_response.status
+ assert_match(/david,Copenhagen/, last_response.body)
+
+ get "/customers/set_no_customer"
+ assert_equal 200, last_response.status
+ assert_match(/noone,UTC/, last_response.body)
+ end
+
+ test "resets after execution" do
+ assert_nil Current.customer
+ assert_equal "UTC", Time.zone.name
+
+ Rails.application.executor.wrap do
+ Current.customer = Customer.new("david")
+
+ assert_equal "david", Current.customer.name
+ assert_equal "Copenhagen", Time.zone.name
+ end
+
+ assert_nil Current.customer
+ assert_equal "UTC", Time.zone.name
+ end
+end
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 959a629ede..a14ea589ed 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -162,6 +162,11 @@ module ApplicationTests
end
RUBY
+ add_to_config <<-RUBY
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
+ RUBY
+
require "#{app_path}/config/environment"
get "/foo/write_session"
@@ -171,9 +176,9 @@ module ApplicationTests
get "/foo/read_encrypted_cookie"
assert_equal "1", last_response.body
- secret = app.key_generator.generate_key("encrypted cookie")
- sign_secret = app.key_generator.generate_key("signed encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
get "/foo/read_raw_cookie"
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"]
@@ -209,6 +214,9 @@ module ApplicationTests
add_to_config <<-RUBY
secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
RUBY
require "#{app_path}/config/environment"
@@ -220,9 +228,9 @@ module ApplicationTests
get "/foo/read_encrypted_cookie"
assert_equal "1", last_response.body
- secret = app.key_generator.generate_key("encrypted cookie")
- sign_secret = app.key_generator.generate_key("signed encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
get "/foo/read_raw_cookie"
assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"]
@@ -264,6 +272,73 @@ module ApplicationTests
add_to_config <<-RUBY
secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ get "/foo/write_raw_session"
+ get "/foo/read_session"
+ assert_equal "1", last_response.body
+
+ get "/foo/write_session"
+ get "/foo/read_session"
+ assert_equal "2", last_response.body
+
+ get "/foo/read_encrypted_cookie"
+ assert_equal "2", last_response.body
+
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
+
+ get "/foo/read_raw_cookie"
+ assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"]
+ end
+
+ test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def write_raw_session
+ # AES-256-CBC with SHA1 HMAC
+ # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1}
+ cookies[:_myapp_session] = "TlgrdS85aUpDd1R2cDlPWlR6K0FJeGExckwySjZ2Z0pkR3d2QnRObGxZT25aalJWYWVvbFVLcHF4d0VQVDdSaFF2QjFPbG9MVjJzeWp3YjcyRUlKUUU2ZlR4bXlSNG9ZUkJPRUtld0E3dVU9LS0xNDZXbGpRZ3NjdW43N2haUEZJSUNRPT0=--3639b5ce54c09495cfeaae928cd5634e0c4b2e96"
+ head :ok
+ end
+
+ def write_session
+ session[:foo] = session[:foo] + 1
+ head :ok
+ end
+
+ def read_session
+ render plain: session[:foo]
+ end
+
+ def read_encrypted_cookie
+ render plain: cookies.encrypted[:_myapp_session]['foo']
+ end
+
+ def read_raw_cookie
+ render plain: cookies[:_myapp_session]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ # Use a static key
+ secrets.secret_key_base = "known key base"
+
+ # Enable AEAD cookies
+ config.action_dispatch.use_authenticated_cookie_encryption = true
RUBY
require "#{app_path}/config/environment"
@@ -279,9 +354,9 @@ module ApplicationTests
get "/foo/read_encrypted_cookie"
assert_equal "2", last_response.body
- secret = app.key_generator.generate_key("encrypted cookie")
- sign_secret = app.key_generator.generate_key("signed encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret)
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
get "/foo/read_raw_cookie"
assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"]
diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb
index 6c003e9bcc..6e6996a6ba 100644
--- a/railties/test/application/per_request_digest_cache_test.rb
+++ b/railties/test/application/per_request_digest_cache_test.rb
@@ -18,6 +18,10 @@ class PerRequestDigestCacheTest < ActiveSupport::TestCase
class Customer < Struct.new(:name, :id)
extend ActiveModel::Naming
include ActiveModel::Conversion
+
+ def cache_key
+ [ name, id ].join("/")
+ end
end
RUBY
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 23b259b503..8e0712fca2 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -469,7 +469,7 @@ module ApplicationTests
def test_run_app_without_rails_loaded
# Simulate a real Rails app boot.
app_file "config/boot.rb", <<-RUBY
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' # Set up gems listed in the Gemfile.
RUBY
diff --git a/railties/test/code_statistics_calculator_test.rb b/railties/test/code_statistics_calculator_test.rb
index 1bd4225f34..25a8a40d27 100644
--- a/railties/test/code_statistics_calculator_test.rb
+++ b/railties/test/code_statistics_calculator_test.rb
@@ -317,7 +317,7 @@ class Animal
private
def temp_file(name, content)
- dir = File.expand_path "../fixtures/tmp", __FILE__
+ dir = File.expand_path "fixtures/tmp", __dir__
path = "#{dir}/#{name}"
FileUtils.mkdir_p dir
diff --git a/railties/test/code_statistics_test.rb b/railties/test/code_statistics_test.rb
index 965b6eeb79..e6e3943117 100644
--- a/railties/test/code_statistics_test.rb
+++ b/railties/test/code_statistics_test.rb
@@ -3,7 +3,7 @@ require "rails/code_statistics"
class CodeStatisticsTest < ActiveSupport::TestCase
def setup
- @tmp_path = File.expand_path(File.join(File.dirname(__FILE__), "fixtures", "tmp"))
+ @tmp_path = File.expand_path("fixtures/tmp", __dir__)
@dir_js = File.join(@tmp_path, "lib.js")
FileUtils.mkdir_p(@dir_js)
end
diff --git a/railties/test/commands/secrets_test.rb b/railties/test/commands/secrets_test.rb
index 00b0343397..fb8fd2325e 100644
--- a/railties/test/commands/secrets_test.rb
+++ b/railties/test/commands/secrets_test.rb
@@ -18,7 +18,8 @@ class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
end
test "edit secrets" do
- run_setup_command
+ # Runs setup before first edit.
+ assert_match(/Adding config\/secrets\.yml\.key to store the encryption key/, run_edit_command)
# Run twice to ensure encrypted secrets can be reread after first edit pass.
2.times do
@@ -30,8 +31,4 @@ class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
def run_edit_command(editor: "cat")
Dir.chdir(app_path) { `EDITOR="#{editor}" bin/rails secrets:edit` }
end
-
- def run_setup_command
- Dir.chdir(app_path) { `bin/rails secrets:setup` }
- end
end
diff --git a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb
index 21b0ff6c28..701515440a 100644
--- a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb
+++ b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb
@@ -1,5 +1,5 @@
require "rails/generators"
class UsageTemplateGenerator < Rails::Generators::Base
- source_root File.expand_path("templates", File.dirname(__FILE__))
+ source_root File.expand_path("templates", __dir__)
end
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index 2edb39c8e8..a19e0f0dd8 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -70,7 +70,6 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
assert_no_file "config/initializers/cookies_serializer.rb"
assert_no_file "config/initializers/assets.rb"
- assert_no_file "config/initializers/new_framework_defaults_5_1.rb"
end
def test_app_update_does_not_generate_unnecessary_bin_files
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index ed8eea3243..31a5efb1b7 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -157,7 +157,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_new_application_doesnt_need_defaults
- assert_no_file "config/initializers/new_framework_defaults_5_1.rb"
+ assert_no_file "config/initializers/new_framework_defaults_5_2.rb"
end
def test_new_application_load_defaults
@@ -203,14 +203,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
- assert_no_file "#{app_root}/config/initializers/new_framework_defaults_5_1.rb"
+ assert_no_file "#{app_root}/config/initializers/new_framework_defaults_5_2.rb"
stub_rails_application(app_root) do
generator = Rails::Generators::AppGenerator.new ["rails"], { update: true }, destination_root: app_root, shell: @shell
generator.send(:app_const)
quietly { generator.send(:update_config_files) }
- assert_file "#{app_root}/config/initializers/new_framework_defaults_5_1.rb"
+ assert_file "#{app_root}/config/initializers/new_framework_defaults_5_2.rb"
end
end
@@ -412,13 +412,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_generator_if_skip_yarn_is_given
- run_generator [destination_root, "--skip-yarn"]
-
- assert_no_file "package.json"
- assert_no_file "bin/yarn"
- end
-
def test_generator_if_skip_action_cable_is_given
run_generator [destination_root, "--skip-action-cable"]
assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/
@@ -524,6 +517,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_for_yarn_skipped
run_generator([destination_root, "--skip-yarn"])
assert_no_file "package.json"
+ assert_no_file "bin/yarn"
assert_file "config/initializers/assets.rb" do |content|
assert_no_match(/node_modules/, content)
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 2cdddc8713..5fb331e197 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -9,7 +9,7 @@ module Rails
class << self
remove_possible_method :root
def root
- @root ||= Pathname.new(File.expand_path("../../fixtures", __FILE__))
+ @root ||= Pathname.new(File.expand_path("../fixtures", __dir__))
end
end
end
@@ -41,7 +41,7 @@ module GeneratorsTestHelper
end
def copy_routes
- routes = File.expand_path("../../../lib/rails/generators/rails/app/templates/config/routes.rb", __FILE__)
+ routes = File.expand_path("../../lib/rails/generators/rails/app/templates/config/routes.rb", __dir__)
destination = File.join(destination_root, "config")
FileUtils.mkdir_p(destination)
FileUtils.cp routes, destination
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index e1aea7fa8a..af16a2641a 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -294,7 +294,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "hyphenated-name/config/routes.rb", /Rails.application.routes.draw do/
assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/
assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/
- assert_file "hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/hyphenated\/name\/engine/
+ assert_file "hyphenated-name/bin/rails", /\.\.\/lib\/hyphenated\/name\/engine/
end
def test_creating_engine_with_hyphenated_and_underscored_name_in_full_mode
@@ -311,7 +311,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "my_hyphenated-name/config/routes.rb", /Rails\.application\.routes\.draw do/
assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n end\n end\nend/
assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/
- assert_file "my_hyphenated-name/bin/rails", /\.\.\/\.\.\/lib\/my_hyphenated\/name\/engine/
+ assert_file "my_hyphenated-name/bin/rails", /\.\.\/lib\/my_hyphenated\/name\/engine/
end
def test_being_quiet_while_creating_dummy_application
@@ -420,9 +420,9 @@ class PluginGeneratorTest < Rails::Generators::TestCase
def test_usage_of_engine_commands
run_generator [destination_root, "--full"]
- assert_file "bin/rails", /ENGINE_PATH = File\.expand_path\('\.\.\/\.\.\/lib\/bukkits\/engine', __FILE__\)/
- assert_file "bin/rails", /ENGINE_ROOT = File\.expand_path\('\.\.\/\.\.', __FILE__\)/
- assert_file "bin/rails", %r|APP_PATH = File\.expand_path\('\.\./\.\./test/dummy/config/application', __FILE__\)|
+ assert_file "bin/rails", /ENGINE_PATH = File\.expand_path\('\.\.\/lib\/bukkits\/engine', __dir__\)/
+ assert_file "bin/rails", /ENGINE_ROOT = File\.expand_path\('\.\.', __dir__\)/
+ assert_file "bin/rails", %r|APP_PATH = File\.expand_path\('\.\./test/dummy/config/application', __dir__\)|
assert_file "bin/rails", /require 'rails\/all'/
assert_file "bin/rails", /require 'rails\/engine\/commands'/
end
@@ -741,7 +741,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
quietly { Rails::Engine::Updater.run(:create_bin_files) }
assert_file "#{destination_root}/bin/rails" do |content|
- assert_match(%r|APP_PATH = File\.expand_path\('\.\./\.\./test/dummy/config/application', __FILE__\)|, content)
+ assert_match(%r|APP_PATH = File\.expand_path\('\.\./test/dummy/config/application', __dir__\)|, content)
end
ensure
Object.send(:remove_const, "ENGINE_ROOT")
diff --git a/railties/test/generators/system_test_generator_test.rb b/railties/test/generators/system_test_generator_test.rb
index e8e561ec49..4622360244 100644
--- a/railties/test/generators/system_test_generator_test.rb
+++ b/railties/test/generators/system_test_generator_test.rb
@@ -9,4 +9,9 @@ class SystemTestGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "test/system/users_test.rb", /class UsersTest < ApplicationSystemTestCase/
end
+
+ def test_namespaced_system_test_skeleton_is_created
+ run_generator %w(admin/user)
+ assert_file "test/system/admin/users_test.rb", /class Admin::UsersTest < ApplicationSystemTestCase/
+ end
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index c3c16b6f86..b784446535 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -233,7 +233,7 @@ class GeneratorsTest < Rails::Generators::TestCase
end
def test_usage_with_embedded_ruby
- require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__))
+ require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", __dir__)
output = capture(:stdout) { Rails::Generators.invoke :usage_template, ["--help"] }
assert_match(/:: 2 ::/, output)
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 2e742a005e..7496b5f84a 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -14,7 +14,7 @@ require "active_support/testing/autorun"
require "active_support/testing/stream"
require "active_support/test_case"
-RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
+RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__)
# These files do not require any others and are needed
# to run the tests
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 9f4c5bb025..383adcc55d 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -39,7 +39,7 @@ class InfoTest < ActiveSupport::TestCase
def test_rails_version
assert_property "Rails version",
- File.read(File.realpath("../../../RAILS_VERSION", __FILE__)).chomp
+ File.read(File.realpath("../../RAILS_VERSION", __dir__)).chomp
end
def test_html_includes_middleware
diff --git a/tasks/release.rb b/tasks/release.rb
index b021535245..038fdc584a 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -1,7 +1,7 @@
FRAMEWORKS = %w( activesupport activemodel activerecord actionview actionpack activejob actionmailer actioncable railties )
FRAMEWORK_NAMES = Hash.new { |h, k| k.split(/(?<=active|action)/).map(&:capitalize).join(" ") }
-root = File.expand_path("../../", __FILE__)
+root = File.expand_path("..", __dir__)
version = File.read("#{root}/RAILS_VERSION").strip
tag = "v#{version}"
gem_version = Gem::Version.new(version)
diff --git a/tools/test.rb b/tools/test.rb
index 71349a5974..1a1eca8f95 100644
--- a/tools/test.rb
+++ b/tools/test.rb
@@ -7,11 +7,12 @@ require "rails/test_unit/minitest_plugin"
require "rails/test_unit/line_filtering"
require "active_support/test_case"
-module Rails
+class << Rails
# Necessary to get rerun-snippts working.
- def self.root
+ def root
@root ||= Pathname.new(COMPONENT_ROOT)
end
+ alias __root root
end
ActiveSupport::TestCase.extend Rails::LineFiltering