aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.codeclimate.yml2
-rw-r--r--.github/issue_template.md9
-rw-r--r--.github/pull_request_template.md8
-rw-r--r--.rubocop.yml3
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock126
-rw-r--r--README.md2
-rw-r--r--actioncable/actioncable.gemspec3
-rw-r--r--actioncable/app/assets/javascripts/action_cable.js7
-rw-r--r--actioncable/app/javascript/action_cable/connection_monitor.js7
-rw-r--r--actionmailer/CHANGELOG.md5
-rw-r--r--actionmailer/actionmailer.gemspec3
-rw-r--r--actionmailer/lib/action_mailer/base.rb11
-rw-r--r--actionmailer/test/base_test.rb6
-rw-r--r--actionmailer/test/mailers/base_mailer.rb7
-rw-r--r--actionpack/CHANGELOG.md23
-rw-r--r--actionpack/actionpack.gemspec3
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb7
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb16
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb3
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb10
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb7
-rw-r--r--actionpack/test/controller/params_parse_test.rb34
-rw-r--r--actionpack/test/controller/rescue_test.rb26
-rw-r--r--actionpack/test/controller/resources_test.rb22
-rw-r--r--actionview/CHANGELOG.md20
-rw-r--r--actionview/actionview.gemspec3
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionview/test/template/text_helper_test.rb4
-rw-r--r--activejob/README.md10
-rw-r--r--activejob/activejob.gemspec5
-rw-r--r--activejob/lib/active_job/arguments.rb28
-rw-r--r--activejob/lib/active_job/base.rb2
-rw-r--r--activejob/lib/active_job/callbacks.rb2
-rw-r--r--activejob/lib/active_job/core.rb2
-rw-r--r--activejob/lib/active_job/enqueuing.rb10
-rw-r--r--activejob/lib/active_job/execution.rb2
-rw-r--r--activejob/lib/active_job/queue_adapters.rb6
-rw-r--r--activejob/test/cases/argument_serialization_test.rb10
-rw-r--r--activejob/test/support/stubs/strong_parameters.rb15
-rw-r--r--activemodel/activemodel.gemspec3
-rw-r--r--activemodel/lib/active_model/dirty.rb4
-rw-r--r--activemodel/lib/active_model/errors.rb6
-rw-r--r--activemodel/lib/active_model/type/decimal.rb4
-rw-r--r--activemodel/test/cases/errors_test.rb10
-rw-r--r--activerecord/CHANGELOG.md27
-rw-r--r--activerecord/activerecord.gemspec3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/enum.rb4
-rw-r--r--activerecord/lib/active_record/integration.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake17
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb4
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb10
-rw-r--r--activerecord/lib/active_record/transactions.rb4
-rw-r--r--activerecord/lib/arel/nodes/equality.rb7
-rw-r--r--activerecord/lib/arel/predications.rb8
-rw-r--r--activerecord/lib/arel/visitors/depth_first.rb2
-rw-r--r--activerecord/lib/arel/visitors/dot.rb2
-rw-r--r--activerecord/lib/arel/visitors/ibm_db.rb6
-rw-r--r--activerecord/lib/arel/visitors/mssql.rb25
-rw-r--r--activerecord/lib/arel/visitors/mysql.rb39
-rw-r--r--activerecord/lib/arel/visitors/oracle.rb6
-rw-r--r--activerecord/lib/arel/visitors/oracle12.rb6
-rw-r--r--activerecord/lib/arel/visitors/postgresql.rb12
-rw-r--r--activerecord/lib/arel/visitors/sqlite.rb12
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb56
-rw-r--r--activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb74
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb20
-rw-r--r--activerecord/test/cases/arel/visitors/ibm_db_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/informix_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/mssql_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/mysql_test.rb49
-rw-r--r--activerecord/test/cases/arel/visitors/oracle12_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/oracle_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/postgres_test.rb39
-rw-r--r--activerecord/test/cases/arel/visitors/sqlite_test.rb45
-rw-r--r--activerecord/test/cases/arel/visitors/to_sql_test.rb59
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb59
-rw-r--r--activerecord/test/cases/enum_test.rb18
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb30
-rw-r--r--activerecord/test/cases/numeric_data_test.rb34
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb4
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb11
-rw-r--r--activestorage/activestorage.gemspec3
-rw-r--r--activesupport/activesupport.gemspec3
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb15
-rw-r--r--activesupport/lib/active_support/testing/parallelization.rb4
-rw-r--r--activesupport/test/multibyte_chars_test.rb10
-rw-r--r--guides/assets/stylesheets/main.rtl.css762
-rw-r--r--guides/rails_guides.rb3
-rw-r--r--guides/rails_guides/generator.rb10
-rw-r--r--guides/source/action_cable_overview.md6
-rw-r--r--guides/source/active_job_basics.md2
-rw-r--r--guides/source/active_record_validations.md3
-rw-r--r--guides/source/association_basics.md2
-rw-r--r--guides/source/caching_with_rails.md6
-rw-r--r--guides/source/command_line.md50
-rw-r--r--guides/source/configuring.md10
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md23
-rw-r--r--guides/source/development_dependencies_install.md364
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md4
-rw-r--r--guides/source/security.md5
-rw-r--r--railties/CHANGELOG.md10
-rw-r--r--railties/lib/rails/command.rb19
-rw-r--r--railties/lib/rails/engine/configuration.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt2
-rw-r--r--railties/lib/rails/info.rb4
-rw-r--r--railties/lib/rails/info_controller.rb12
-rw-r--r--railties/lib/rails/paths.rb28
-rw-r--r--railties/railties.gemspec3
-rw-r--r--railties/test/application/bin_setup_test.rb2
-rw-r--r--railties/test/application/configuration_test.rb8
-rw-r--r--railties/test/application/rake/multi_dbs_test.rb41
-rw-r--r--railties/test/application/rake_test.rb6
-rw-r--r--railties/test/application/test_runner_test.rb63
-rw-r--r--railties/test/generators/actions_test.rb8
-rw-r--r--railties/test/generators/app_generator_test.rb11
-rw-r--r--railties/test/rails_info_controller_test.rb5
-rw-r--r--railties/test/rails_info_test.rb12
136 files changed, 2459 insertions, 564 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 7fd35d8241..139ee8013b 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -23,7 +23,7 @@ checks:
engines:
rubocop:
enabled: true
- channel: rubocop-0-58
+ channel: rubocop-0-60
ratings:
paths:
diff --git a/.github/issue_template.md b/.github/issue_template.md
index 2ff6a271db..87e666a7dd 100644
--- a/.github/issue_template.md
+++ b/.github/issue_template.md
@@ -1,13 +1,12 @@
### Steps to reproduce
-
-(Guidelines for creating a bug report are [available
-here](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#creating-a-bug-report))
+<!-- (Guidelines for creating a bug report are [available
+here](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#creating-a-bug-report)) -->
### Expected behavior
-Tell us what should happen
+<!-- Tell us what should happen -->
### Actual behavior
-Tell us what happens instead
+<!-- Tell us what happens instead -->
### System configuration
**Rails version**:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index a36687ec99..85c0e29ff0 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,13 +1,13 @@
### Summary
-Provide a general description of the code changes in your pull
+<!-- Provide a general description of the code changes in your pull
request... were there any bugs you had fixed? If so, mention them. If
these bugs have open GitHub issues, be sure to tag them here as well,
-to keep the conversation linked together.
+to keep the conversation linked together. -->
### Other Information
-If there's anything else that's important and relevant to your pull
+<!-- If there's anything else that's important and relevant to your pull
request, mention that information here. This could include
benchmarks, or other information.
@@ -18,4 +18,4 @@ Finally, if your pull request affects documentation or any non-code
changes, guidelines for those changes are [available
here](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)
-Thanks for contributing to Rails!
+Thanks for contributing to Rails! -->
diff --git a/.rubocop.yml b/.rubocop.yml
index 615a229432..033f7adf49 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -133,9 +133,6 @@ Style/FrozenStringLiteralComment:
Style/RedundantFreeze:
Enabled: true
- Exclude:
- - 'actionpack/lib/action_dispatch/journey/router/utils.rb'
- - 'activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb'
# Use `foo {}` not `foo{}`.
Layout/SpaceBeforeBlockBraces:
diff --git a/Gemfile b/Gemfile
index fb15bf127e..9df706f9c2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -104,8 +104,6 @@ group :test do
platforms :mri do
gem "stackprof"
gem "byebug"
- # FIXME: Remove this when thor 0.21 is release
- gem "thor", git: "https://github.com/erikhuda/thor.git", ref: "006832ea32480618791f89bb7d9e67b22fc814b9"
end
gem "benchmark-ips"
diff --git a/Gemfile.lock b/Gemfile.lock
index 1321436814..f4f3e430ba 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,11 +1,4 @@
GIT
- remote: https://github.com/erikhuda/thor.git
- revision: 006832ea32480618791f89bb7d9e67b22fc814b9
- ref: 006832ea32480618791f89bb7d9e67b22fc814b9
- specs:
- thor (0.20.0)
-
-GIT
remote: https://github.com/matthewd/rb-inotify.git
revision: 856730aad4b285969e8dd621e44808a7c5af4242
branch: close-handling
@@ -32,9 +25,9 @@ GIT
GIT
remote: https://github.com/rails/webpacker.git
- revision: 48d9fd52c3e9637dbb21e551b48c84c0f2dbb2ed
+ revision: bb132d591da35095e3246082cba3d693f847e0b5
specs:
- webpacker (4.0.0.pre.pre.2)
+ webpacker (4.0.0.pre.3)
activesupport (>= 4.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
@@ -105,16 +98,16 @@ PATH
GEM
remote: https://rubygems.org/
specs:
- activerecord-jdbc-adapter (52.0-java)
+ activerecord-jdbc-adapter (52.1-java)
activerecord (~> 5.2.0)
- activerecord-jdbcmysql-adapter (52.0-java)
- activerecord-jdbc-adapter (= 52.0)
+ activerecord-jdbcmysql-adapter (52.1-java)
+ activerecord-jdbc-adapter (= 52.1)
jdbc-mysql (~> 5.1.36)
- activerecord-jdbcpostgresql-adapter (52.0-java)
- activerecord-jdbc-adapter (= 52.0)
+ activerecord-jdbcpostgresql-adapter (52.1-java)
+ activerecord-jdbc-adapter (= 52.1)
jdbc-postgres (>= 9.4, < 43)
- activerecord-jdbcsqlite3-adapter (52.0-java)
- activerecord-jdbc-adapter (= 52.0)
+ activerecord-jdbcsqlite3-adapter (52.1-java)
+ activerecord-jdbc-adapter (= 52.1)
jdbc-sqlite3 (~> 3.8, < 3.30)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
@@ -123,17 +116,17 @@ GEM
io-like (~> 0.3.0)
ast (2.4.0)
aws-eventstream (1.0.1)
- aws-partitions (1.102.0)
- aws-sdk-core (3.25.0)
+ aws-partitions (1.111.0)
+ aws-sdk-core (3.37.0)
aws-eventstream (~> 1.0)
aws-partitions (~> 1.0)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
- aws-sdk-kms (1.7.0)
- aws-sdk-core (~> 3)
+ aws-sdk-kms (1.11.0)
+ aws-sdk-core (~> 3, >= 3.26.0)
aws-sigv4 (~> 1.0)
- aws-sdk-s3 (1.17.1)
- aws-sdk-core (~> 3, >= 3.21.2)
+ aws-sdk-s3 (1.23.1)
+ aws-sdk-core (~> 3, >= 3.26.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.0)
aws-sigv4 (1.0.3)
@@ -173,38 +166,38 @@ GEM
childprocess
faraday
selenium-webdriver
- bootsnap (1.3.1)
+ bootsnap (1.3.2)
msgpack (~> 1.0)
- bootsnap (1.3.1-java)
+ bootsnap (1.3.2-java)
msgpack (~> 1.0)
builder (3.2.3)
bunny (2.9.2)
amq-protocol (~> 2.3.0)
byebug (10.0.2)
- capybara (3.7.1)
+ capybara (3.10.1)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
- xpath (~> 3.1)
+ regexp_parser (~> 1.2)
+ xpath (~> 3.2)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
- chromedriver-helper (2.0.0)
+ chromedriver-helper (2.1.0)
archive-zip (~> 0.10)
nokogiri (~> 1.8)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
- concurrent-ruby (1.0.5)
- concurrent-ruby (1.0.5-java)
+ concurrent-ruby (1.1.3)
connection_pool (2.2.2)
cookiejar (0.3.3)
crass (1.0.4)
curses (1.0.2)
daemons (1.2.6)
- dalli (2.7.8)
+ dalli (2.7.9)
dante (0.2.0)
declarative (0.0.10)
declarative-option (0.1.0)
@@ -228,7 +221,7 @@ GEM
event_emitter (0.2.6)
eventmachine (1.2.7)
execjs (2.7.0)
- faraday (0.15.2)
+ faraday (0.15.3)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.12.2)
faraday (>= 0.7.4, < 1.0)
@@ -252,39 +245,39 @@ GEM
raabro (~> 1.1)
globalid (0.4.1)
activesupport (>= 4.2.0)
- google-api-client (0.23.8)
+ google-api-client (0.25.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- signet (~> 0.9)
- google-cloud-core (1.2.3)
+ signet (~> 0.10)
+ google-cloud-core (1.2.7)
google-cloud-env (~> 1.0)
- google-cloud-env (1.0.2)
+ google-cloud-env (1.0.5)
faraday (~> 0.11)
- google-cloud-storage (1.13.1)
+ google-cloud-storage (1.15.0)
digest-crc (~> 0.4)
google-api-client (~> 0.23)
google-cloud-core (~> 1.2)
googleauth (~> 0.6.2)
- googleauth (0.6.6)
+ googleauth (0.6.7)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
- memoist (~> 0.12)
+ memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.7)
- hiredis (0.6.1)
- hiredis (0.6.1-java)
+ hiredis (0.6.3)
+ hiredis (0.6.3-java)
http_parser.rb (0.6.0)
httpclient (2.8.3)
- i18n (1.1.0)
+ i18n (1.1.1)
concurrent-ruby (~> 1.0)
- image_processing (1.6.0)
+ image_processing (1.7.1)
mini_magick (~> 4.0)
- ruby-vips (>= 2.0.11, < 3)
+ ruby-vips (>= 2.0.13, < 3)
io-like (0.3.0)
jaro_winkler (1.5.1)
jaro_winkler (1.5.1-java)
@@ -303,7 +296,7 @@ GEM
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
- loofah (2.2.2)
+ loofah (2.2.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@@ -311,7 +304,7 @@ GEM
marcel (0.3.3)
mimemagic (~> 0.3.2)
memoist (0.16.0)
- method_source (0.9.0)
+ method_source (0.9.1)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2018.0812)
@@ -334,7 +327,7 @@ GEM
msgpack (1.2.4-x86-mingw32)
multi_json (1.13.1)
multipart-post (2.0.0)
- mustache (1.0.5)
+ mustache (1.1.0)
mustermann (1.0.3)
mysql2 (0.5.2)
mysql2 (0.5.2-x64-mingw32)
@@ -350,14 +343,14 @@ GEM
mini_portile2 (~> 2.3.0)
os (1.0.0)
parallel (1.12.1)
- parser (2.5.1.2)
+ parser (2.5.3.0)
ast (~> 2.4.0)
path_expander (1.0.3)
pg (1.1.3)
pg (1.1.3-x64-mingw32)
pg (1.1.3-x86-mingw32)
powerpack (0.1.2)
- psych (3.0.2)
+ psych (3.0.3)
public_suffix (3.0.3)
puma (3.12.0)
puma (3.12.0-java)
@@ -367,10 +360,10 @@ GEM
thor
raabro (1.1.6)
racc (1.4.14)
- rack (2.0.5)
+ rack (2.0.6)
rack-cache (1.8.0)
rack (>= 0.4)
- rack-protection (2.0.3)
+ rack-protection (2.0.4)
rack
rack-proxy (0.6.5)
rack
@@ -386,9 +379,10 @@ GEM
rb-fsevent (0.10.3)
rdoc (6.0.4)
redcarpet (3.2.3)
- redis (4.0.2)
+ redis (4.0.3)
redis-namespace (1.6.0)
redis (>= 3.0.4)
+ regexp_parser (1.2.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@@ -405,14 +399,14 @@ GEM
resque (~> 1.26)
rufus-scheduler (~> 3.2)
retriable (3.1.2)
- rubocop (0.58.2)
+ rubocop (0.60.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
powerpack (~> 0.1)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
- unicode-display_width (~> 1.0, >= 1.0.1)
+ unicode-display_width (~> 1.4.0)
ruby-progressbar (1.10.0)
ruby-vips (2.0.13)
ffi (~> 1.9)
@@ -420,7 +414,7 @@ GEM
rubyzip (1.2.2)
rufus-scheduler (3.5.2)
fugit (~> 1.1, >= 1.1.5)
- sass (3.5.7)
+ sass (3.7.2)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
@@ -433,26 +427,26 @@ GEM
tilt (>= 1.1, < 3)
sdoc (1.0.0)
rdoc (>= 5.0)
- selenium-webdriver (3.14.0)
+ selenium-webdriver (3.141.0)
childprocess (~> 0.5)
- rubyzip (~> 1.2)
- sequel (5.12.0)
+ rubyzip (~> 1.2, >= 1.2.2)
+ sequel (5.14.0)
serverengine (2.0.7)
sigdump (~> 0.2.2)
- sidekiq (5.2.1)
+ sidekiq (5.2.3)
connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
sigdump (0.2.4)
- signet (0.9.1)
+ signet (0.11.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
- sinatra (2.0.3)
+ sinatra (2.0.4)
mustermann (~> 1.0)
rack (~> 2.0)
- rack-protection (= 2.0.3)
+ rack-protection (= 2.0.4)
tilt (~> 2.0)
sneakers (2.7.0)
bunny (~> 2.9.2)
@@ -477,6 +471,7 @@ GEM
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
rack (>= 1, < 3)
+ thor (0.20.3)
thread_safe (0.3.6)
thread_safe (0.3.6-java)
tilt (2.0.8)
@@ -485,10 +480,10 @@ GEM
turbolinks-source (5.2.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
- tzinfo-data (1.2018.5)
+ tzinfo-data (1.2018.7)
tzinfo (>= 1.0.0)
uber (0.1.0)
- uglifier (4.1.18)
+ uglifier (4.1.19)
execjs (>= 0.3.0, < 3)
unicode-display_width (1.4.0)
useragent (0.16.10)
@@ -504,7 +499,7 @@ GEM
websocket-driver (0.7.0-java)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
- xpath (3.1.0)
+ xpath (3.2.0)
nokogiri (~> 1.8)
PLATFORMS
@@ -569,7 +564,6 @@ DEPENDENCIES
sqlite3 (~> 1.3.6)
stackprof
sucker_punch
- thor!
turbolinks (~> 5)
tzinfo-data
uglifier (>= 1.3.0)
@@ -579,4 +573,4 @@ DEPENDENCIES
websocket-client-simple!
BUNDLED WITH
- 1.16.6
+ 1.17.1
diff --git a/README.md b/README.md
index 4854d7de0e..461e8cea78 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ or to generate the body of an email. In Rails, View generation is handled by [Ac
[Active Record](activerecord/README.rdoc), [Active Model](activemodel/README.rdoc), [Action Pack](actionpack/README.rdoc), and [Action View](actionview/README.rdoc) can each be used independently outside Rails.
In addition to that, Rails also comes with [Action Mailer](actionmailer/README.rdoc), a library
to generate and send emails; [Active Job](activejob/README.md), a
-framework for declaring jobs and making them run on a variety of queueing
+framework for declaring jobs and making them run on a variety of queuing
backends; [Action Cable](actioncable/README.md), a framework to
integrate WebSockets with a Rails application; [Active Storage](activestorage/README.md), a library to attach cloud
and local files to Rails applications;
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
index 137fa64431..31c6fb41f2 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -25,6 +25,9 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actioncable/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "actionpack", version
s.add_dependency "nio4r", "~> 2.0"
diff --git a/actioncable/app/assets/javascripts/action_cable.js b/actioncable/app/assets/javascripts/action_cable.js
index df60b1fc24..07151b9d25 100644
--- a/actioncable/app/assets/javascripts/action_cable.js
+++ b/actioncable/app/assets/javascripts/action_cable.js
@@ -238,8 +238,8 @@
}, this.getPollInterval());
};
ConnectionMonitor.prototype.getPollInterval = function getPollInterval() {
- var _constructor$pollInte = this.constructor.pollInterval, min = _constructor$pollInte.min, max = _constructor$pollInte.max;
- var interval = 5 * Math.log(this.reconnectAttempts + 1);
+ var _constructor$pollInte = this.constructor.pollInterval, min = _constructor$pollInte.min, max = _constructor$pollInte.max, multiplier = _constructor$pollInte.multiplier;
+ var interval = multiplier * Math.log(this.reconnectAttempts + 1);
return Math.round(clamp(interval, min, max) * 1e3);
};
ConnectionMonitor.prototype.reconnectIfStale = function reconnectIfStale() {
@@ -275,7 +275,8 @@
}();
ConnectionMonitor.pollInterval = {
min: 3,
- max: 30
+ max: 30,
+ multiplier: 5
};
ConnectionMonitor.staleThreshold = 6;
var Consumer = function() {
diff --git a/actioncable/app/javascript/action_cable/connection_monitor.js b/actioncable/app/javascript/action_cable/connection_monitor.js
index 8d6ac1f682..cd1e4602d8 100644
--- a/actioncable/app/javascript/action_cable/connection_monitor.js
+++ b/actioncable/app/javascript/action_cable/connection_monitor.js
@@ -75,8 +75,8 @@ class ConnectionMonitor {
}
getPollInterval() {
- const {min, max} = this.constructor.pollInterval
- const interval = 5 * Math.log(this.reconnectAttempts + 1)
+ const {min, max, multiplier} = this.constructor.pollInterval
+ const interval = multiplier * Math.log(this.reconnectAttempts + 1)
return Math.round(clamp(interval, min, max) * 1000)
}
@@ -117,7 +117,8 @@ class ConnectionMonitor {
ConnectionMonitor.pollInterval = {
min: 3,
- max: 30
+ max: 30,
+ multiplier: 5
}
ConnectionMonitor.staleThreshold = 6 // Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index db6fc7ee9c..35574a2c73 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Mails with multipart `format` blocks with implicit render now also check for
+ a template name in options hash instead of only using the action name.
+
+ *Marcus Ilgner*
+
* Allow ActionMailer classes to configure the parameterized delivery job
Example:
```
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index f2fb160bdd..6f8d6d0699 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -26,6 +26,9 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actionmailer/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "actionpack", version
s.add_dependency "actionview", version
s.add_dependency "activejob", version
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 509d859ac3..2bb1078bb8 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -942,9 +942,7 @@ module ActionMailer
def collect_responses(headers)
if block_given?
- collector = ActionMailer::Collector.new(lookup_context) { render(action_name) }
- yield(collector)
- collector.responses
+ collect_responses_from_block(headers, &Proc.new)
elsif headers[:body]
collect_responses_from_text(headers)
else
@@ -952,6 +950,13 @@ module ActionMailer
end
end
+ def collect_responses_from_block(headers)
+ templates_name = headers[:template_name] || action_name
+ collector = ActionMailer::Collector.new(lookup_context) { render(templates_name) }
+ yield(collector)
+ collector.responses
+ end
+
def collect_responses_from_text(headers)
[{
body: headers.delete(:body),
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index cbbee9fae8..86c0172772 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -544,6 +544,12 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded)
end
+ test "you can specify a different template for multipart render" do
+ mail = BaseMailer.implicit_different_template_with_block("explicit_multipart_templates").deliver
+ assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
+ assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded)
+ end
+
test "should raise if missing template in implicit render" do
assert_raises ActionView::MissingTemplate do
BaseMailer.implicit_different_template("missing_template").deliver_now
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index a3101207dc..c1bb48cc96 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -111,6 +111,13 @@ class BaseMailer < ActionMailer::Base
mail(template_name: template_name)
end
+ def implicit_different_template_with_block(template_name = "")
+ mail(template_name: template_name) do |format|
+ format.text
+ format.html
+ end
+ end
+
def explicit_different_template(template_name = "")
mail do |format|
format.text { render template: "#{mailer_name}/#{template_name}" }
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 5554d4e6b8..90cf989100 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,11 +1,28 @@
+* Allow rescue from parameter parse errors:
+
+ ```
+ rescue_from ActionDispatch::Http::Parameters::ParseError do
+ head :unauthorized
+ end
+ ```
+
+ *Gannon McGibbon*, *Josh Cheek*
+
+* Reset Capybara sessions if failed system test screenshot raising an exception.
+
+ Reset Capybara sessions if `take_failed_screenshot` raise exception
+ in system test `after_teardown`.
+
+ *Maxim Perepelitsa*
+
* Use request object for context if there's no controller
There is no controller instance when using a redirect route or a
mounted rack application so pass the request object as the context
when resolving dynamic CSP sources in this scenario.
-
+
Fixes #34200.
-
+
*Andrew White*
* Apply mapping to symbols returned from dynamic CSP sources
@@ -14,7 +31,7 @@
would be converted to a string implicity, e.g:
policy.default_src -> { :self }
-
+
would generate the header:
Content-Security-Policy: default-src self
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 1dc8abf746..ec56de18f1 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -26,6 +26,9 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actionpack/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "activesupport", version
s.add_dependency "rack", "~> 2.0"
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index a678377d4f..7361946de5 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -253,7 +253,10 @@ module ActionController
# This will display the wrapped hash in the log file.
request.filtered_parameters.merge! wrapped_filtered_hash
end
- super
+ ensure
+ # NOTE: Rescues all exceptions so they
+ # may be caught in ActionController::Rescue.
+ return super
end
private
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 9f8f178402..cbb772175c 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -41,6 +41,8 @@ module ActionDispatch
# Returns a hash of parameters with all sensitive data replaced.
def filtered_parameters
@filtered_parameters ||= parameter_filter.filter(parameters)
+ rescue ActionDispatch::Http::Parameters::ParseError
+ @filtered_parameters = {}
end
# Returns a hash of request.env with all sensitive data replaced.
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index be129965d1..498b1e6695 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -7,6 +7,11 @@ module ActionDispatch
module MimeNegotiation
extend ActiveSupport::Concern
+ RESCUABLE_MIME_FORMAT_ERRORS = [
+ ActionController::BadRequest,
+ ActionDispatch::Http::Parameters::ParseError,
+ ]
+
included do
mattr_accessor :ignore_accept_header, default: false
end
@@ -59,7 +64,7 @@ module ActionDispatch
fetch_header("action_dispatch.request.formats") do |k|
params_readable = begin
parameters[:format]
- rescue ActionController::BadRequest
+ rescue *RESCUABLE_MIME_FORMAT_ERRORS
false
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 8d7431fd6b..13d0963a33 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -111,13 +111,23 @@ module ActionDispatch
begin
strategy.call(raw_post)
rescue # JSON or Ruby code block errors.
- my_logger = logger || ActiveSupport::Logger.new($stderr)
- my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}"
-
+ log_parse_error_once
raise ParseError
end
end
+ def log_parse_error_once
+ @parse_error_logged ||= begin
+ parse_logger = logger || ActiveSupport::Logger.new($stderr)
+ parse_logger.debug <<~MSG.chomp
+ Error occurred while parsing request parameters.
+ Contents:
+
+ #{raw_post}
+ MSG
+ end
+ end
+
def params_parsers
ActionDispatch::Request.parameter_parsers
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 7bc364d370..44f23940d3 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -383,9 +383,6 @@ module ActionDispatch
end
self.request_parameters = Request::Utils.normalize_encode_params(pr)
end
- rescue Http::Parameters::ParseError # one of the parse strategies blew up
- self.request_parameters = Request::Utils.normalize_encode_params(super || {})
- raise
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 30af3ff930..89a164f968 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -15,9 +15,6 @@ require "action_dispatch/journey/path/pattern"
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
- class RoutingError < ::StandardError # :nodoc:
- end
-
attr_accessor :routes
def initialize(routes)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 99f3b4c2cd..06ce165f76 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1171,10 +1171,16 @@ module ActionDispatch
end
def actions
+ if @except
+ available_actions - Array(@except).map(&:to_sym)
+ else
+ available_actions
+ end
+ end
+
+ def available_actions
if @only
Array(@only).map(&:to_sym)
- elsif @except
- default_actions - Array(@except).map(&:to_sym)
else
default_actions
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 e47d5020f4..600e9c733b 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
@@ -17,8 +17,11 @@ module ActionDispatch
end
def after_teardown
- take_failed_screenshot
- Capybara.reset_sessions!
+ begin
+ take_failed_screenshot
+ ensure
+ Capybara.reset_sessions!
+ end
ensure
super
end
diff --git a/actionpack/test/controller/params_parse_test.rb b/actionpack/test/controller/params_parse_test.rb
new file mode 100644
index 0000000000..440ab06fd7
--- /dev/null
+++ b/actionpack/test/controller/params_parse_test.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class ParamsParseTest < ActionController::TestCase
+ class UsersController < ActionController::Base
+ def create
+ head :ok
+ end
+ end
+
+ tests UsersController
+
+ def test_parse_error_logged_once
+ log_output = capture_log_output do
+ post :create, body: "{", as: :json
+ end
+ assert_equal <<~LOG, log_output
+ Error occurred while parsing request parameters.
+ Contents:
+
+ {
+ LOG
+ end
+
+ private
+
+ def capture_log_output
+ output = StringIO.new
+ request.set_header "action_dispatch.logger", ActiveSupport::Logger.new(output)
+ yield
+ output.string
+ end
+end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 4ed79073e5..3c39373e55 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -70,6 +70,10 @@ class RescueController < ActionController::Base
render plain: "io error"
end
+ rescue_from ActionDispatch::Http::Parameters::ParseError do
+ render plain: "parse error", status: :bad_request
+ end
+
before_action(only: :before_action_raises) { raise "umm nice" }
def before_action_raises
@@ -130,6 +134,11 @@ class RescueController < ActionController::Base
raise ResourceUnavailableToRescueAsString
end
+ def arbitrary_action
+ params
+ render plain: "arbitrary action"
+ end
+
def missing_template
end
@@ -306,6 +315,23 @@ class RescueControllerTest < ActionController::TestCase
get :exception_with_no_handler_for_wrapper
assert_response :unprocessable_entity
end
+
+ test "can rescue a ParseError" do
+ capture_log_output do
+ post :arbitrary_action, body: "{", as: :json
+ end
+ assert_response :bad_request
+ assert_equal "parse error", response.body
+ end
+
+ private
+
+ def capture_log_output
+ output = StringIO.new
+ request.set_header "action_dispatch.logger", ActiveSupport::Logger.new(output)
+ yield
+ output.string
+ end
end
class RescueTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index d336b96eff..d2146f12a5 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -853,6 +853,28 @@ class ResourcesTest < ActionController::TestCase
end
end
+ def test_resource_has_show_action_but_does_not_have_destroy_action
+ with_routing do |set|
+ set.draw do
+ resources :products, only: [:show, :destroy], except: :destroy
+ end
+
+ assert_resource_allowed_routes("products", {}, { id: "1" }, :show, [:index, :new, :create, :edit, :update, :destroy])
+ assert_resource_allowed_routes("products", { format: "xml" }, { id: "1" }, :show, [:index, :new, :create, :edit, :update, :destroy])
+ end
+ end
+
+ def test_singleton_resource_has_show_action_but_does_not_have_destroy_action
+ with_routing do |set|
+ set.draw do
+ resource :account, only: [:show, :destroy], except: :destroy
+ end
+
+ assert_singleton_resource_allowed_routes("accounts", {}, :show, [:new, :create, :edit, :update, :destroy])
+ assert_singleton_resource_allowed_routes("accounts", { format: "xml" }, :show, [:new, :create, :edit, :update, :destroy])
+ end
+ end
+
def test_resource_has_only_create_action_and_named_route
with_routing do |set|
set.draw do
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index fd8bae0cdf..237a25ba4f 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,21 @@
+* Prevent `ActionView::TextHelper#word_wrap` from unexpectedly stripping white space from the _left_ side of lines.
+
+ For example, given input like this:
+
+ ```
+ This is a paragraph with an initial indent,
+ followed by additional lines that are not indented,
+ and finally terminated with a blockquote:
+ "A pithy saying"
+ ```
+
+ Calling `word_wrap` should not trim the indents on the first and last lines.
+
+ Fixes #34487
+
+ *Lyle Mullican*
+
+
* Add allocations to template rendering instrumentation.
Adds the allocations for template and partial rendering to the server output on render.
@@ -11,7 +29,7 @@
*Eileen M. Uchitelle*, *Aaron Patterson*
* Respect the `only_path` option passed to `url_for` when the options are passed in as an array
-
+
Fixes #33237.
*Joel Ambass*
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index 49ee1a292b..5f1e746421 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -26,6 +26,9 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actionview/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "activesupport", version
s.add_dependency "builder", "~> 3.1"
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index a338d076e4..3d378dcb2f 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -259,7 +259,7 @@ module ActionView
# # => Once\r\nupon\r\na\r\ntime
def word_wrap(text, line_width: 80, break_sequence: "\n")
text.split("\n").collect! do |line|
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
end * break_sequence
end
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index 2925489f5d..e961a770e6 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -361,6 +361,10 @@ class TextHelperTest < ActionView::TestCase
assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", line_width: 15))
end
+ def test_word_wrap_with_leading_spaces
+ assert_equal(" This is a paragraph\nthat includes some\nindented lines:\n Like this sample\n blockquote", word_wrap(" This is a paragraph that includes some\nindented lines:\n Like this sample\n blockquote", line_width: 25))
+ end
+
def test_word_wrap_does_not_modify_the_options_hash
options = { line_width: 15 }
passed_options = options.dup
diff --git a/activejob/README.md b/activejob/README.md
index d49fcfe3c2..a2a5289ab7 100644
--- a/activejob/README.md
+++ b/activejob/README.md
@@ -1,7 +1,7 @@
# Active Job -- Make work happen later
Active Job is a framework for declaring jobs and making them run on a variety
-of queueing backends. These jobs can be everything from regularly scheduled
+of queuing backends. These jobs can be everything from regularly scheduled
clean-ups, to billing charges, to mailings. Anything that can be chopped up into
small units of work and run in parallel, really.
@@ -20,7 +20,7 @@ switch between them without having to rewrite your jobs.
## Usage
-To learn how to use your preferred queueing backend see its adapter
+To learn how to use your preferred queuing backend see its adapter
documentation at
[ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
@@ -39,7 +39,7 @@ end
Enqueue a job like so:
```ruby
-MyJob.perform_later record # Enqueue a job to be performed as soon as the queueing system is free.
+MyJob.perform_later record # Enqueue a job to be performed as soon as the queuing system is free.
```
```ruby
@@ -82,9 +82,9 @@ This works with any class that mixes in GlobalID::Identification, which
by default has been mixed into Active Record classes.
-## Supported queueing systems
+## Supported queuing systems
-Active Job has built-in adapters for multiple queueing backends (Sidekiq,
+Active Job has built-in adapters for multiple queuing backends (Sidekiq,
Resque, Delayed Job and others). To get an up-to-date list of the adapters
see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec
index be6292f737..20b9d4ccdd 100644
--- a/activejob/activejob.gemspec
+++ b/activejob/activejob.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.name = "activejob"
s.version = version
s.summary = "Job framework with pluggable queues."
- s.description = "Declare job classes that can be run by a variety of queueing backends."
+ s.description = "Declare job classes that can be run by a variety of queuing backends."
s.required_ruby_version = ">= 2.4.1"
@@ -25,6 +25,9 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activejob/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "activesupport", version
s.add_dependency "globalid", ">= 0.3.6"
end
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 31347194d2..fa58c50ed0 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -14,18 +14,18 @@ module ActiveJob
end
# Raised when an unsupported argument type is set as a job argument. We
- # currently support NilClass, Integer, Float, String, TrueClass, FalseClass,
- # BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record).
+ # currently support String, Integer, Float, NilClass, TrueClass, FalseClass,
+ # BigDecimal, Symbol, Date, Time, DateTime, ActiveSupport::TimeWithZone,
+ # ActiveSupport::Duration, Hash, ActiveSupport::HashWithIndifferentAccess,
+ # Array or GlobalID::Identification instances, although this can be extended
+ # by adding custom serializers.
# Raised if you set the key for a Hash something else than a string or
# a symbol. Also raised when trying to serialize an object which can't be
- # identified with a Global ID - such as an unpersisted Active Record model.
+ # identified with a GlobalID - such as an unpersisted Active Record model.
class SerializationError < ArgumentError; end
module Arguments
extend self
- # :nodoc:
- PERMITTED_TYPES = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ]
-
# Serializes a set of arguments. Intrinsic types that can safely be
# serialized without mutation are returned as-is. Arrays/Hashes are
# serialized element by element. All other types are serialized using
@@ -47,6 +47,8 @@ module ActiveJob
private
# :nodoc:
+ PERMITTED_TYPES = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ]
+ # :nodoc:
GLOBALID_KEY = "_aj_globalid"
# :nodoc:
SYMBOL_KEYS_KEY = "_aj_symbol_keys"
@@ -62,7 +64,7 @@ module ActiveJob
OBJECT_SERIALIZER_KEY, OBJECT_SERIALIZER_KEY.to_sym,
WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym,
]
- private_constant :RESERVED_KEYS
+ private_constant :PERMITTED_TYPES, :RESERVED_KEYS, :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY
def serialize_argument(argument)
case argument
@@ -73,14 +75,14 @@ module ActiveJob
when Array
argument.map { |arg| serialize_argument(arg) }
when ActiveSupport::HashWithIndifferentAccess
- result = serialize_hash(argument)
- result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true)
- result
+ serialize_indifferent_hash(argument)
when Hash
symbol_keys = argument.each_key.grep(Symbol).map(&:to_s)
result = serialize_hash(argument)
result[SYMBOL_KEYS_KEY] = symbol_keys
result
+ when -> (arg) { arg.respond_to?(:permitted?) }
+ serialize_indifferent_hash(argument.to_h)
else
Serializers.serialize(argument)
end
@@ -146,6 +148,12 @@ module ActiveJob
end
end
+ def serialize_indifferent_hash(indifferent_hash)
+ result = serialize_hash(indifferent_hash)
+ result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true)
+ result
+ end
+
def transform_symbol_keys(hash, symbol_keys)
# NOTE: HashWithIndifferentAccess#transform_keys always
# returns stringified keys with indifferent access
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index 95b1062701..ed41fac4b8 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -40,7 +40,7 @@ module ActiveJob #:nodoc:
# Records that are passed in are serialized/deserialized using Global
# ID. More information can be found in Arguments.
#
- # To enqueue a job to be performed as soon as the queueing system is free:
+ # To enqueue a job to be performed as soon as the queuing system is free:
#
# ProcessPhotoJob.perform_later(photo)
#
diff --git a/activejob/lib/active_job/callbacks.rb b/activejob/lib/active_job/callbacks.rb
index 334b24fb3b..61317c7cfc 100644
--- a/activejob/lib/active_job/callbacks.rb
+++ b/activejob/lib/active_job/callbacks.rb
@@ -130,7 +130,7 @@ module ActiveJob
set_callback(:enqueue, :after, *filters, &blk)
end
- # Defines a callback that will get called around the enqueueing
+ # Defines a callback that will get called around the enqueuing
# of the job.
#
# class VideoProcessJob < ActiveJob::Base
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index 62bb5861bb..698153636b 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -78,7 +78,7 @@ module ActiveJob
end
# Returns a hash with the job data that can safely be passed to the
- # queueing adapter.
+ # queuing adapter.
def serialize
{
"job_class" => self.class.name,
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index 53cb98fc71..b5b9f23c00 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -9,10 +9,12 @@ module ActiveJob
# Includes the +perform_later+ method for job initialization.
module ClassMethods
- # Push a job onto the queue. The arguments must be legal JSON types
- # (+string+, +int+, +float+, +nil+, +true+, +false+, +hash+ or +array+) or
- # GlobalID::Identification instances. Arbitrary Ruby objects
- # are not supported.
+ # Push a job onto the queue. By default the arguments must be either String,
+ # Integer, Float, NilClass, TrueClass, FalseClass, BigDecimal, Symbol, Date,
+ # Time, DateTime, ActiveSupport::TimeWithZone, ActiveSupport::Duration,
+ # Hash, ActiveSupport::HashWithIndifferentAccess, Array or
+ # GlobalID::Identification instances, although this can be extended by adding
+ # custom serializers.
#
# Returns an instance of the job class queued with arguments available in
# Job#arguments.
diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb
index f5a343311f..e96dbcd4c9 100644
--- a/activejob/lib/active_job/execution.rb
+++ b/activejob/lib/active_job/execution.rb
@@ -26,7 +26,7 @@ module ActiveJob
end
end
- # Performs the job immediately. The job is not sent to the queueing adapter
+ # Performs the job immediately. The job is not sent to the queuing adapter
# but directly executed by blocking the execution of others until it's finished.
#
# MyJob.new(*args).perform_now
diff --git a/activejob/lib/active_job/queue_adapters.rb b/activejob/lib/active_job/queue_adapters.rb
index 3e3a474fbb..525e79e302 100644
--- a/activejob/lib/active_job/queue_adapters.rb
+++ b/activejob/lib/active_job/queue_adapters.rb
@@ -3,7 +3,7 @@
module ActiveJob
# == Active Job adapters
#
- # Active Job has adapters for the following queueing backends:
+ # Active Job has adapters for the following queuing backends:
#
# * {Backburner}[https://github.com/nesquena/backburner]
# * {Delayed Job}[https://github.com/collectiveidea/delayed_job]
@@ -52,7 +52,7 @@ module ActiveJob
#
# No: The adapter will run jobs at the next opportunity and cannot use perform_later.
#
- # N/A: The adapter does not support queueing.
+ # N/A: The adapter does not support queuing.
#
# NOTE:
# queue_classic supports job scheduling since version 3.1.
@@ -74,7 +74,7 @@ module ActiveJob
#
# No: Does not allow the priority of jobs to be configured.
#
- # N/A: The adapter does not support queueing, and therefore sorting them.
+ # N/A: The adapter does not support queuing, and therefore sorting them.
#
# ==== Timeout
#
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index 8b2981926f..e4e14016d9 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -5,6 +5,7 @@ require "active_job/arguments"
require "models/person"
require "active_support/core_ext/hash/indifferent_access"
require "jobs/kwargs_job"
+require "support/stubs/strong_parameters"
class ArgumentSerializationTest < ActiveSupport::TestCase
setup do
@@ -49,6 +50,15 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
assert_arguments_roundtrip([a: 1, "b" => 2])
end
+ test "serialize a ActionController::Parameters" do
+ parameters = Parameters.new(a: 1)
+
+ assert_equal(
+ { "a" => 1, "_aj_hash_with_indifferent_access" => true },
+ ActiveJob::Arguments.serialize([parameters]).first
+ )
+ end
+
test "serialize a hash" do
symbol_key = { a: 1 }
string_key = { "a" => 1 }
diff --git a/activejob/test/support/stubs/strong_parameters.rb b/activejob/test/support/stubs/strong_parameters.rb
new file mode 100644
index 0000000000..acba3a4504
--- /dev/null
+++ b/activejob/test/support/stubs/strong_parameters.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class Parameters
+ def initialize(parameters = {})
+ @parameters = parameters.with_indifferent_access
+ end
+
+ def permitted?
+ true
+ end
+
+ def to_h
+ @parameters.to_h
+ end
+end
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 7be466dc4c..493fca698a 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -25,5 +25,8 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activemodel/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "activesupport", version
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index f0e1242554..0d9e761b1e 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -141,7 +141,9 @@ module ActiveModel
@mutations_from_database = nil
end
- def changes_applied # :nodoc:
+ # Clears dirty data and moves +changes+ to +previously_changed+ and
+ # +mutations_from_database+ to +mutations_before_last_save+ respectively.
+ def changes_applied
unless defined?(@attributes)
@previously_changed = changes
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index af94d52d45..9de6b609a3 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -327,11 +327,11 @@ module ActiveModel
# person.errors.added? :name, :too_long # => false
# person.errors.added? :name, "is too long" # => false
def added?(attribute, message = :invalid, options = {})
+ message = message.call if message.respond_to?(:call)
+
if message.is_a? Symbol
- self.details[attribute.to_sym].map { |e| e[:error] }.include? message
+ details[attribute.to_sym].include? normalize_detail(message, options)
else
- message = message.call if message.respond_to?(:call)
- message = normalize_message(attribute, message, options)
self[attribute].include? message
end
end
diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb
index e8ee18c00e..b37dad1c41 100644
--- a/activemodel/lib/active_model/type/decimal.rb
+++ b/activemodel/lib/active_model/type/decimal.rb
@@ -12,6 +12,10 @@ module ActiveModel
:decimal
end
+ def serialize(value)
+ cast(value)
+ end
+
def type_cast_for_schema(value)
value.to_s.inspect
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 41ff6443fe..185b5a24ae 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -228,6 +228,16 @@ class ErrorsTest < ActiveModel::TestCase
assert_not person.errors.added?(:name)
end
+ test "added? returns false when checking for an error with an incorrect or missing option" do
+ person = Person.new
+ person.errors.add :name, :too_long, count: 25
+
+ assert person.errors.added? :name, :too_long, count: 25
+ assert_not person.errors.added? :name, :too_long, count: 24
+ assert_not person.errors.added? :name, :too_long
+ assert_not person.errors.added? :name, "is too long"
+ end
+
test "added? returns false when checking for an error by symbol and a different error with same message is present" do
I18n.backend.store_translations("en", errors: { attributes: { name: { wrong: "is wrong", used: "is wrong" } } })
person = Person.new
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 9d80bbe51f..2a237f86cf 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,30 @@
+* Add an `:if_not_exists` option to `create_table`.
+
+ Example:
+
+ create_table :posts, if_not_exists: true do |t|
+ t.string :title
+ end
+
+ That would execute:
+
+ CREATE TABLE IF NOT EXISTS posts (
+ ...
+ )
+
+ If the table already exists, `if_not_exists: false` (the default) raises an
+ exception whereas `if_not_exists: true` does nothing.
+
+ *fatkodima*, *Stefan Kanev*
+
+* Defining an Enum as a Hash with blank key, or as an Array with a blank value, now raises an `ArgumentError`.
+
+ *Christophe Maximin*
+
+* Adds support for multiple databases to `rails db:schema:cache:dump` and `rails db:schema:cache:clear`.
+
+ *Gannon McGibbon*
+
* `update_columns` now correctly raises `ActiveModel::MissingAttributeError`
if the attribute does not exist.
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index a857d00c05..bcdd82052c 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -28,6 +28,9 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activerecord/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "activesupport", version
s.add_dependency "activemodel", version
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 9d9e8a4110..2cb0a2a4df 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -39,7 +39,9 @@ module ActiveRecord
end
def visit_TableDefinition(o)
- create_sql = +"CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} "
+ create_sql = +"CREATE#{table_modifier_in_create(o)} TABLE "
+ create_sql << "IF NOT EXISTS " if o.if_not_exists
+ create_sql << "#{quote_table_name(o.name)} "
statements = o.columns.map { |c| accept c }
statements << accept(o.primary_keys) if o.primary_keys
@@ -119,6 +121,11 @@ module ActiveRecord
sql
end
+ # Returns any SQL string to go between CREATE and TABLE. May be nil.
+ def table_modifier_in_create(o)
+ " TEMPORARY" if o.temporary
+ end
+
def foreign_key_in_create(from_table, to_table, options)
options = foreign_key_options(from_table, to_table, options)
accept ForeignKeyDefinition.new(from_table, to_table, options)
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 70607fda5a..db489143af 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/deprecation"
+
module ActiveRecord
module ConnectionAdapters #:nodoc:
# Abstract representation of an index definition on a table. Instances of
@@ -256,15 +258,25 @@ module ActiveRecord
class TableDefinition
include ColumnMethods
- attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment
+ attr_reader :name, :temporary, :if_not_exists, :options, :as, :comment, :indexes, :foreign_keys
+ attr_writer :indexes
+ deprecate :indexes=
- def initialize(name, temporary = false, options = nil, as = nil, comment: nil)
+ def initialize(
+ name,
+ temporary: false,
+ if_not_exists: false,
+ options: nil,
+ as: nil,
+ comment: nil,
+ **
+ )
@columns_hash = {}
@indexes = []
@foreign_keys = []
@primary_keys = nil
@temporary = temporary
+ @if_not_exists = if_not_exists
@options = options
@as = as
@name = name
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 8a7020a799..38cfc3a241 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -205,6 +205,9 @@ module ActiveRecord
# Set to true to drop the table before creating it.
# Set to +:cascade+ to drop dependent objects as well.
# Defaults to false.
+ # [<tt>:if_not_exists</tt>]
+ # Set to true to avoid raising an error when the table already exists.
+ # Defaults to false.
# [<tt>:as</tt>]
# SQL to use to generate the table. When this option is used, the block is
# ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
@@ -287,8 +290,8 @@ module ActiveRecord
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
#
# See also TableDefinition#column for details on how to create columns.
- def create_table(table_name, comment: nil, **options)
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
+ def create_table(table_name, **options)
+ td = create_table_definition(table_name, options)
if options[:id] != false && !options[:as]
pk = options.fetch(:primary_key) do
@@ -317,7 +320,9 @@ module ActiveRecord
end
if supports_comments? && !supports_comments_in_create?
- change_table_comment(table_name, comment) if comment.present?
+ if table_comment = options[:comment].presence
+ change_table_comment(table_name, table_comment)
+ end
td.columns.each do |column|
change_column_comment(table_name, column.name, column.comment) if column.comment.present?
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 564b226b39..0f2b1e85ff 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -137,7 +137,7 @@ module ActiveRecord
record.committed!
else
# if not running callbacks, only adds the record to the parent transaction
- record.add_to_transaction
+ connection.add_transaction_record(record)
end
end
ensure
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index e75202b0be..0895d06356 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -93,11 +93,11 @@ module ActiveRecord
elsif value.hex?
"X'#{value}'"
end
- when Float
- if value.infinite? || value.nan?
- "'#{value}'"
- else
+ when Numeric
+ if value.finite?
super
+ else
+ "'#{value}'"
end
when OID::Array::Data
_quote(encode_array(value))
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
index 8e381a92cf..ceb8b40bd9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
@@ -23,6 +23,17 @@ module ActiveRecord
end
super
end
+
+ # Returns any SQL string to go between CREATE and TABLE. May be nil.
+ def table_modifier_in_create(o)
+ # A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY
+ # tables are already UNLOGGED.
+ if o.temporary
+ " TEMPORARY"
+ elsif o.unlogged
+ " UNLOGGED"
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index 206b855a18..dc4a0bb26e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -175,6 +175,13 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+ attr_reader :unlogged
+
+ def initialize(*)
+ super
+ @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
+ end
+
private
def integer_like_primary_key_type(type, options)
if type == :bigint || options[:limit] == 8
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index a11a786ec7..d2ed699ee2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -85,6 +85,19 @@ module ActiveRecord
class PostgreSQLAdapter < AbstractAdapter
ADAPTER_NAME = "PostgreSQL"
+ ##
+ # :singleton-method:
+ # PostgreSQL allows the creation of "unlogged" tables, which do not record
+ # data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
+ # but significantly increases the risk of data loss if the database
+ # crashes. As a result, this should not be used in production
+ # environments. If you would like all created tables to be unlogged in
+ # the test environment you can add the following line to your test.rb
+ # file:
+ #
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
+ class_attribute :create_unlogged_tables, default: false
+
NATIVE_DATABASE_TYPES = {
primary_key: "bigserial primary key",
string: { name: "character varying" },
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index e0355a316b..3312c3de01 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -396,6 +396,12 @@ module ActiveRecord
end
private
+ # See https://www.sqlite.org/limits.html,
+ # the default value is 999 when not configured.
+ def bind_params_length
+ 999
+ end
+
def check_version
if sqlite_version < "3.8.0"
raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8."
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 777cb8a402..3ce9aad5fc 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -107,7 +107,7 @@ module ActiveRecord
# end
def connected_to(database: nil, role: nil, &blk)
if database && role
- raise ArgumentError, "connected_to can only accept a database or role argument, but not both arguments."
+ raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
elsif database
if database.is_a?(Hash)
role, database = database.first
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 3d97e4e513..3a600835e1 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -218,6 +218,10 @@ module ActiveRecord
MSG
raise ArgumentError, error_message
end
+
+ if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
+ raise ArgumentError, "Enum label name must not be blank."
+ end
end
ENUM_CONFLICT_MESSAGE = \
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 456689ec6d..33c4066b89 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -20,7 +20,7 @@ module ActiveRecord
# 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.
+ # This is +true+, by default on Rails 5.2 and above.
class_attribute :cache_versioning, instance_writer: false, default: false
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 1c7ceb4981..475baa7559 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -300,15 +300,22 @@ db_namespace = namespace :db do
namespace :cache do
desc "Creates a db/schema_cache.yml file."
task dump: :load_config do
- conn = ActiveRecord::Base.connection
- filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
- ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename)
+ ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ ActiveRecord::Base.establish_connection(db_config.config)
+ filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config.spec_name)
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(
+ ActiveRecord::Base.connection,
+ filename,
+ )
+ end
end
desc "Clears a db/schema_cache.yml file."
task clear: :load_config do
- filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
- rm_f filename, verbose: false
+ ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config.spec_name)
+ rm_f filename, verbose: false
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index a502713e56..e225628bae 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -125,6 +125,10 @@ module ActiveRecord
raise ArgumentError, "Invalid argument for .where.not(), got nil."
when Arel::Nodes::In
Arel::Nodes::NotIn.new(node.left, node.right)
+ when Arel::Nodes::IsNotDistinctFrom
+ Arel::Nodes::IsDistinctFrom.new(node.left, node.right)
+ when Arel::Nodes::IsDistinctFrom
+ Arel::Nodes::IsNotDistinctFrom.new(node.left, node.right)
when Arel::Nodes::Equality
Arel::Nodes::NotEqual.new(node.left, node.right)
when String
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 974d7a1c0a..27e401a756 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -313,6 +313,16 @@ module ActiveRecord
ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
end
+ def cache_dump_filename(namespace)
+ filename = if namespace == "primary"
+ "schema_cache.yml"
+ else
+ "#{namespace}_schema_cache.yml"
+ end
+
+ ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
+ end
+
def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
each_current_configuration(environment) { |configuration, spec_name, env|
load_schema(configuration, format, file, env, spec_name)
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index c5d5fca672..fe3842b905 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -375,10 +375,6 @@ module ActiveRecord
raise ActiveRecord::Rollback unless status
end
status
- ensure
- if @transaction_state && @transaction_state.committed?
- clear_transaction_record_state
- end
end
private
diff --git a/activerecord/lib/arel/nodes/equality.rb b/activerecord/lib/arel/nodes/equality.rb
index 2aa85a977e..551d56c2ff 100644
--- a/activerecord/lib/arel/nodes/equality.rb
+++ b/activerecord/lib/arel/nodes/equality.rb
@@ -7,5 +7,12 @@ module Arel # :nodoc: all
alias :operand1 :left
alias :operand2 :right
end
+
+ %w{
+ IsDistinctFrom
+ IsNotDistinctFrom
+ }.each do |name|
+ const_set name, Class.new(Equality)
+ end
end
end
diff --git a/activerecord/lib/arel/predications.rb b/activerecord/lib/arel/predications.rb
index e83a6f162f..77502dd199 100644
--- a/activerecord/lib/arel/predications.rb
+++ b/activerecord/lib/arel/predications.rb
@@ -18,6 +18,14 @@ module Arel # :nodoc: all
Nodes::Equality.new self, quoted_node(other)
end
+ def is_not_distinct_from(other)
+ Nodes::IsNotDistinctFrom.new self, quoted_node(other)
+ end
+
+ def is_distinct_from(other)
+ Nodes::IsDistinctFrom.new self, quoted_node(other)
+ end
+
def eq_any(others)
grouping_any :eq, others
end
diff --git a/activerecord/lib/arel/visitors/depth_first.rb b/activerecord/lib/arel/visitors/depth_first.rb
index 8f65d303ac..92d309453c 100644
--- a/activerecord/lib/arel/visitors/depth_first.rb
+++ b/activerecord/lib/arel/visitors/depth_first.rb
@@ -95,6 +95,8 @@ module Arel # :nodoc: all
alias :visit_Arel_Nodes_NotEqual :binary
alias :visit_Arel_Nodes_NotIn :binary
alias :visit_Arel_Nodes_NotRegexp :binary
+ alias :visit_Arel_Nodes_IsNotDistinctFrom :binary
+ alias :visit_Arel_Nodes_IsDistinctFrom :binary
alias :visit_Arel_Nodes_Or :binary
alias :visit_Arel_Nodes_OuterJoin :binary
alias :visit_Arel_Nodes_Regexp :binary
diff --git a/activerecord/lib/arel/visitors/dot.rb b/activerecord/lib/arel/visitors/dot.rb
index 9054f0159b..6389c875cb 100644
--- a/activerecord/lib/arel/visitors/dot.rb
+++ b/activerecord/lib/arel/visitors/dot.rb
@@ -195,6 +195,8 @@ module Arel # :nodoc: all
alias :visit_Arel_Nodes_JoinSource :binary
alias :visit_Arel_Nodes_LessThan :binary
alias :visit_Arel_Nodes_LessThanOrEqual :binary
+ alias :visit_Arel_Nodes_IsNotDistinctFrom :binary
+ alias :visit_Arel_Nodes_IsDistinctFrom :binary
alias :visit_Arel_Nodes_Matches :binary
alias :visit_Arel_Nodes_NotEqual :binary
alias :visit_Arel_Nodes_NotIn :binary
diff --git a/activerecord/lib/arel/visitors/ibm_db.rb b/activerecord/lib/arel/visitors/ibm_db.rb
index 0a06aef60b..73166054da 100644
--- a/activerecord/lib/arel/visitors/ibm_db.rb
+++ b/activerecord/lib/arel/visitors/ibm_db.rb
@@ -10,6 +10,12 @@ module Arel # :nodoc: all
collector = visit o.expr, collector
collector << " ROWS ONLY"
end
+
+ def is_distinct_from(o, collector)
+ collector << "DECODE("
+ collector = visit [o.left, o.right, 0, 1], collector
+ collector << ")"
+ end
end
end
end
diff --git a/activerecord/lib/arel/visitors/mssql.rb b/activerecord/lib/arel/visitors/mssql.rb
index d564e19089..fdd864b40d 100644
--- a/activerecord/lib/arel/visitors/mssql.rb
+++ b/activerecord/lib/arel/visitors/mssql.rb
@@ -12,6 +12,31 @@ module Arel # :nodoc: all
private
+ def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
+ right = o.right
+
+ if right.nil?
+ collector = visit o.left, collector
+ collector << " IS NULL"
+ else
+ collector << "EXISTS (VALUES ("
+ collector = visit o.left, collector
+ collector << ") INTERSECT VALUES ("
+ collector = visit right, collector
+ collector << "))"
+ end
+ end
+
+ def visit_Arel_Nodes_IsDistinctFrom(o, collector)
+ if o.right.nil?
+ collector = visit o.left, collector
+ collector << " IS NOT NULL"
+ else
+ collector << "NOT "
+ visit_Arel_Nodes_IsNotDistinctFrom o, collector
+ end
+ end
+
def visit_Arel_Visitors_MSSQL_RowNumber(o, collector)
collector << "ROW_NUMBER() OVER (ORDER BY "
inject_join(o.children, collector, ", ") << ") as _row_num"
diff --git a/activerecord/lib/arel/visitors/mysql.rb b/activerecord/lib/arel/visitors/mysql.rb
index 32f6705d04..dd77cfdf66 100644
--- a/activerecord/lib/arel/visitors/mysql.rb
+++ b/activerecord/lib/arel/visitors/mysql.rb
@@ -4,34 +4,6 @@ module Arel # :nodoc: all
module Visitors
class MySQL < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_Union(o, collector, suppress_parens = false)
- unless suppress_parens
- collector << "( "
- end
-
- case o.left
- when Arel::Nodes::Union
- visit_Arel_Nodes_Union o.left, collector, true
- else
- visit o.left, collector
- end
-
- collector << " UNION "
-
- case o.right
- when Arel::Nodes::Union
- visit_Arel_Nodes_Union o.right, collector, true
- else
- visit o.right, collector
- end
-
- if suppress_parens
- collector
- else
- collector << " )"
- end
- end
-
def visit_Arel_Nodes_Bin(o, collector)
collector << "BINARY "
visit o.expr, collector
@@ -65,6 +37,17 @@ module Arel # :nodoc: all
collector
end
+ def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
+ collector = visit o.left, collector
+ collector << " <=> "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_IsDistinctFrom(o, collector)
+ collector << "NOT "
+ visit_Arel_Nodes_IsNotDistinctFrom o, collector
+ end
+
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
# these, we must use a subquery.
diff --git a/activerecord/lib/arel/visitors/oracle.rb b/activerecord/lib/arel/visitors/oracle.rb
index 30a1529d46..f96bf65ee5 100644
--- a/activerecord/lib/arel/visitors/oracle.rb
+++ b/activerecord/lib/arel/visitors/oracle.rb
@@ -148,6 +148,12 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_BindParam(o, collector)
collector.add_bind(o.value) { |i| ":a#{i}" }
end
+
+ def is_distinct_from(o, collector)
+ collector << "DECODE("
+ collector = visit [o.left, o.right, 0, 1], collector
+ collector << ")"
+ end
end
end
end
diff --git a/activerecord/lib/arel/visitors/oracle12.rb b/activerecord/lib/arel/visitors/oracle12.rb
index 7061f06087..b092aa95e0 100644
--- a/activerecord/lib/arel/visitors/oracle12.rb
+++ b/activerecord/lib/arel/visitors/oracle12.rb
@@ -56,6 +56,12 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_BindParam(o, collector)
collector.add_bind(o.value) { |i| ":a#{i}" }
end
+
+ def is_distinct_from(o, collector)
+ collector << "DECODE("
+ collector = visit [o.left, o.right, 0, 1], collector
+ collector << ")"
+ end
end
end
end
diff --git a/activerecord/lib/arel/visitors/postgresql.rb b/activerecord/lib/arel/visitors/postgresql.rb
index c5110fa89c..920776b4dc 100644
--- a/activerecord/lib/arel/visitors/postgresql.rb
+++ b/activerecord/lib/arel/visitors/postgresql.rb
@@ -77,6 +77,18 @@ module Arel # :nodoc: all
grouping_parentheses o, collector
end
+ def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
+ collector = visit o.left, collector
+ collector << " IS NOT DISTINCT FROM "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_IsDistinctFrom(o, collector)
+ collector = visit o.left, collector
+ collector << " IS DISTINCT FROM "
+ visit o.right, collector
+ end
+
# Used by Lateral visitor to enclose select queries in parentheses
def grouping_parentheses(o, collector)
if o.expr.is_a? Nodes::SelectStatement
diff --git a/activerecord/lib/arel/visitors/sqlite.rb b/activerecord/lib/arel/visitors/sqlite.rb
index cb1d2424ad..af6f7e856a 100644
--- a/activerecord/lib/arel/visitors/sqlite.rb
+++ b/activerecord/lib/arel/visitors/sqlite.rb
@@ -22,6 +22,18 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_False(o, collector)
collector << "0"
end
+
+ def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
+ collector = visit o.left, collector
+ collector << " IS "
+ visit o.right, collector
+ end
+
+ def visit_Arel_Nodes_IsDistinctFrom(o, collector)
+ collector = visit o.left, collector
+ collector << " IS NOT "
+ visit o.right, collector
+ end
end
end
end
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index 8e56fb55a2..f9fe4404eb 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -268,13 +268,11 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_Union(o, collector)
- collector << "( "
- infix_value(o, collector, " UNION ") << " )"
+ infix_value_with_paren(o, collector, " UNION ")
end
def visit_Arel_Nodes_UnionAll(o, collector)
- collector << "( "
- infix_value(o, collector, " UNION ALL ") << " )"
+ infix_value_with_paren(o, collector, " UNION ALL ")
end
def visit_Arel_Nodes_Intersect(o, collector)
@@ -643,6 +641,26 @@ module Arel # :nodoc: all
end
end
+ def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
+ if o.right.nil?
+ collector = visit o.left, collector
+ collector << " IS NULL"
+ else
+ collector = is_distinct_from(o, collector)
+ collector << " = 0"
+ end
+ end
+
+ def visit_Arel_Nodes_IsDistinctFrom(o, collector)
+ if o.right.nil?
+ collector = visit o.left, collector
+ collector << " IS NOT NULL"
+ else
+ collector = is_distinct_from(o, collector)
+ collector << " = 1"
+ end
+ end
+
def visit_Arel_Nodes_NotEqual(o, collector)
right = o.right
@@ -845,6 +863,23 @@ module Arel # :nodoc: all
visit o.right, collector
end
+ def infix_value_with_paren(o, collector, value, suppress_parens = false)
+ collector << "( " unless suppress_parens
+ collector = if o.left.class == o.class
+ infix_value_with_paren(o.left, collector, value, true)
+ else
+ visit o.left, collector
+ end
+ collector << value
+ collector = if o.right.class == o.class
+ infix_value_with_paren(o.right, collector, value, true)
+ else
+ visit o.right, collector
+ end
+ collector << " )" unless suppress_parens
+ collector
+ end
+
def aggregate(name, o, collector)
collector << "#{name}("
if o.distinct
@@ -858,6 +893,19 @@ module Arel # :nodoc: all
collector
end
end
+
+ def is_distinct_from(o, collector)
+ collector << "CASE WHEN "
+ collector = visit o.left, collector
+ collector << " = "
+ collector = visit o.right, collector
+ collector << " OR ("
+ collector = visit o.left, collector
+ collector << " IS NULL AND "
+ collector = visit o.right, collector
+ collector << " IS NULL)"
+ collector << " THEN 0 ELSE 1 END"
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb b/activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb
new file mode 100644
index 0000000000..a02bae1453
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/create_unlogged_tables_test.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+class UnloggedTablesTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ TABLE_NAME = "things"
+ LOGGED_FIELD = "relpersistence"
+ LOGGED_QUERY = "SELECT #{LOGGED_FIELD} FROM pg_class WHERE relname = '#{TABLE_NAME}'"
+ LOGGED = "p"
+ UNLOGGED = "u"
+ TEMPORARY = "t"
+
+ class Thing < ActiveRecord::Base
+ self.table_name = TABLE_NAME
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = false
+ end
+
+ teardown do
+ @connection.drop_table TABLE_NAME, if_exists: true
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = false
+ end
+
+ def test_logged_by_default
+ @connection.create_table(TABLE_NAME) do |t|
+ end
+ assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], LOGGED
+ end
+
+ def test_unlogged_in_test_environment_when_unlogged_setting_enabled
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
+
+ @connection.create_table(TABLE_NAME) do |t|
+ end
+ assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], UNLOGGED
+ end
+
+ def test_not_included_in_schema_dump
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
+
+ @connection.create_table(TABLE_NAME) do |t|
+ end
+ assert_no_match(/unlogged/i, dump_table_schema(TABLE_NAME))
+ end
+
+ def test_not_changed_in_change_table
+ @connection.create_table(TABLE_NAME) do |t|
+ end
+
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
+
+ @connection.change_table(TABLE_NAME) do |t|
+ t.column :name, :string
+ end
+ assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], LOGGED
+ end
+
+ def test_gracefully_handles_temporary_tables
+ @connection.create_table(TABLE_NAME, temporary: true) do |t|
+ end
+
+ # Temporary tables are already unlogged, though this query results in a
+ # different result ("t" vs. "u"). This test is really just checking that we
+ # didn't try to run `CREATE TEMPORARY UNLOGGED TABLE`, which would result in
+ # a PostgreSQL error.
+ assert_equal @connection.execute(LOGGED_QUERY).first[LOGGED_FIELD], TEMPORARY
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 75e5aaed53..1aa0348879 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -37,7 +37,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
def test_default
assert_equal BigDecimal("150.55"), PostgresqlMoney.column_defaults["depth"]
assert_equal BigDecimal("150.55"), PostgresqlMoney.new.depth
- assert_equal "$150.55", PostgresqlMoney.new.depth_before_type_cast
+ assert_equal "150.55", PostgresqlMoney.new.depth_before_type_cast
end
def test_money_values
diff --git a/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb b/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb
new file mode 100644
index 0000000000..93a7dafebd
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/topic"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class SQLite3Adapter
+ class BindParameterTest < ActiveRecord::SQLite3TestCase
+ def test_too_many_binds
+ topics = Topic.where(id: (1..999).to_a << 2**63)
+ assert_equal Topic.count, topics.count
+
+ topics = Topic.where.not(id: (1..999).to_a << 2**63)
+ assert_equal 0, topics.count
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/arel/visitors/ibm_db_test.rb b/activerecord/test/cases/arel/visitors/ibm_db_test.rb
index 7163cb34d3..2ddbec3266 100644
--- a/activerecord/test/cases/arel/visitors/ibm_db_test.rb
+++ b/activerecord/test/cases/arel/visitors/ibm_db_test.rb
@@ -29,6 +29,45 @@ module Arel
sql = compile(stmt)
sql.must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT \"users\".\"id\" FROM \"users\" FETCH FIRST 1 ROWS ONLY)"
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ DECODE("users"."name", 'Aaron Patterson', 0, 1) = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 0
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 1
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/informix_test.rb b/activerecord/test/cases/arel/visitors/informix_test.rb
index b0b031cca3..b6c2dd6ae7 100644
--- a/activerecord/test/cases/arel/visitors/informix_test.rb
+++ b/activerecord/test/cases/arel/visitors/informix_test.rb
@@ -54,6 +54,45 @@ module Arel
sql = compile(stmt)
sql.must_be_like 'SELECT FROM "posts" INNER JOIN "comments"'
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ CASE WHEN "users"."name" = 'Aaron Patterson' OR ("users"."name" IS NULL AND 'Aaron Patterson' IS NULL) THEN 0 ELSE 1 END = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 0
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 1
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/mssql_test.rb b/activerecord/test/cases/arel/visitors/mssql_test.rb
index 340376c3d6..74f34b4dad 100644
--- a/activerecord/test/cases/arel/visitors/mssql_test.rb
+++ b/activerecord/test/cases/arel/visitors/mssql_test.rb
@@ -94,6 +94,45 @@ module Arel
sql = compile(stmt)
sql.must_be_like "SELECT COUNT(1) as count_id FROM (SELECT _t.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY ) as _row_num) as _t WHERE _row_num BETWEEN 1 AND 10) AS subquery"
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ EXISTS (VALUES ("users"."name") INTERSECT VALUES ('Aaron Patterson'))
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ EXISTS (VALUES ("users"."first_name") INTERSECT VALUES ("users"."last_name"))
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ NOT EXISTS (VALUES ("users"."first_name") INTERSECT VALUES ("users"."last_name"))
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/mysql_test.rb b/activerecord/test/cases/arel/visitors/mysql_test.rb
index 9d3bad8516..5f37587957 100644
--- a/activerecord/test/cases/arel/visitors/mysql_test.rb
+++ b/activerecord/test/cases/arel/visitors/mysql_test.rb
@@ -13,16 +13,6 @@ module Arel
@visitor.accept(node, Collectors::SQLString.new).value
end
- it "squashes parenthesis on multiple unions" do
- subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
- node = Nodes::Union.new subnode, Arel.sql("topright")
- assert_equal 1, compile(node).scan("(").length
-
- subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
- node = Nodes::Union.new Arel.sql("topleft"), subnode
- assert_equal 1, compile(node).scan("(").length
- end
-
###
# :'(
# http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
@@ -75,6 +65,45 @@ module Arel
}
end
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ "users"."name" <=> 'Aaron Patterson'
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" <=> "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" <=> NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ NOT "users"."first_name" <=> "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ NOT "users"."name" <=> NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/oracle12_test.rb b/activerecord/test/cases/arel/visitors/oracle12_test.rb
index 83a2ee36ca..4ce5cab4db 100644
--- a/activerecord/test/cases/arel/visitors/oracle12_test.rb
+++ b/activerecord/test/cases/arel/visitors/oracle12_test.rb
@@ -56,6 +56,45 @@ module Arel
}
end
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ DECODE("users"."name", 'Aaron Patterson', 0, 1) = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 0
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 1
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/oracle_test.rb b/activerecord/test/cases/arel/visitors/oracle_test.rb
index e1dfe40cf9..893edc7f74 100644
--- a/activerecord/test/cases/arel/visitors/oracle_test.rb
+++ b/activerecord/test/cases/arel/visitors/oracle_test.rb
@@ -192,6 +192,45 @@ module Arel
}
end
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ DECODE("users"."name", 'Aaron Patterson', 0, 1) = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 0
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ DECODE("users"."first_name", "users"."last_name", 0, 1) = 1
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/postgres_test.rb b/activerecord/test/cases/arel/visitors/postgres_test.rb
index f7f2c76b6f..0f9efb20b4 100644
--- a/activerecord/test/cases/arel/visitors/postgres_test.rb
+++ b/activerecord/test/cases/arel/visitors/postgres_test.rb
@@ -276,6 +276,45 @@ module Arel
}
end
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ "users"."name" IS NOT DISTINCT FROM 'Aaron Patterson'
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" IS NOT DISTINCT FROM "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT DISTINCT FROM NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" IS DISTINCT FROM "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS DISTINCT FROM NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/sqlite_test.rb b/activerecord/test/cases/arel/visitors/sqlite_test.rb
index 6650b6ff3a..ee4e07a675 100644
--- a/activerecord/test/cases/arel/visitors/sqlite_test.rb
+++ b/activerecord/test/cases/arel/visitors/sqlite_test.rb
@@ -6,7 +6,11 @@ module Arel
module Visitors
class SqliteTest < Arel::Spec
before do
- @visitor = SQLite.new Table.engine.connection_pool
+ @visitor = SQLite.new Table.engine.connection
+ end
+
+ def compile(node)
+ @visitor.accept(node, Collectors::SQLString.new).value
end
it "defaults limit to -1" do
@@ -27,6 +31,45 @@ module Arel
node = Nodes::False.new()
assert_equal "0", @visitor.accept(node, Collectors::SQLString.new).value
end
+
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ "users"."name" IS 'Aaron Patterson'
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" IS "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ "users"."first_name" IS NOT "users"."last_name"
+ }
+ end
+
+ it "should handle nil" do
+ @table = Table.new(:users)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/arel/visitors/to_sql_test.rb b/activerecord/test/cases/arel/visitors/to_sql_test.rb
index b6426a211e..4bfa799a96 100644
--- a/activerecord/test/cases/arel/visitors/to_sql_test.rb
+++ b/activerecord/test/cases/arel/visitors/to_sql_test.rb
@@ -155,6 +155,43 @@ module Arel
end
end
+ describe "Nodes::IsNotDistinctFrom" do
+ it "should construct a valid generic SQL statement" do
+ test = Table.new(:users)[:name].is_not_distinct_from "Aaron Patterson"
+ compile(test).must_be_like %{
+ CASE WHEN "users"."name" = 'Aaron Patterson' OR ("users"."name" IS NULL AND 'Aaron Patterson' IS NULL) THEN 0 ELSE 1 END = 0
+ }
+ end
+
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_not_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 0
+ }
+ end
+
+ it "should handle nil" do
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsNotDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NULL }
+ end
+ end
+
+ describe "Nodes::IsDistinctFrom" do
+ it "should handle column names on both sides" do
+ test = Table.new(:users)[:first_name].is_distinct_from Table.new(:users)[:last_name]
+ compile(test).must_be_like %{
+ CASE WHEN "users"."first_name" = "users"."last_name" OR ("users"."first_name" IS NULL AND "users"."last_name" IS NULL) THEN 0 ELSE 1 END = 1
+ }
+ end
+
+ it "should handle nil" do
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = compile Nodes::IsDistinctFrom.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" IS NOT NULL }
+ end
+ end
+
it "should visit string subclass" do
[
Class.new(String).new(":'("),
@@ -480,6 +517,28 @@ module Arel
end
end
+ describe "Nodes::Union" do
+ it "squashes parenthesis on multiple unions" do
+ subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::Union.new subnode, Arel.sql("topright")
+ assert_equal("( left UNION right UNION topright )", compile(node))
+ subnode = Nodes::Union.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::Union.new Arel.sql("topleft"), subnode
+ assert_equal("( topleft UNION left UNION right )", compile(node))
+ end
+ end
+
+ describe "Nodes::UnionAll" do
+ it "squashes parenthesis on multiple union alls" do
+ subnode = Nodes::UnionAll.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::UnionAll.new subnode, Arel.sql("topright")
+ assert_equal("( left UNION ALL right UNION ALL topright )", compile(node))
+ subnode = Nodes::UnionAll.new Arel.sql("left"), Arel.sql("right")
+ node = Nodes::UnionAll.new Arel.sql("topleft"), subnode
+ assert_equal("( topleft UNION ALL left UNION ALL right )", compile(node))
+ end
+ end
+
describe "Nodes::NotIn" do
it "should know how to visit" do
node = @attr.not_in [1, 2, 3]
diff --git a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
index a520fb1303..a394430dfe 100644
--- a/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
@@ -127,6 +127,9 @@ module ActiveRecord
ActiveRecord::Base.connected_to(database: { writing: "postgres://localhost/bar" }) do
handler = ActiveRecord::Base.connection_handler
assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
+
+ assert_not_nil pool = handler.retrieve_connection_pool("primary")
+ assert_equal({ adapter: "postgresql", database: "bar", host: "localhost" }, pool.spec.config)
end
ensure
ActiveRecord::Base.establish_connection(:arunit)
@@ -136,25 +139,54 @@ module ActiveRecord
def test_switching_connections_with_database_config_hash
previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
- config = { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
+ config = { adapter: "sqlite3", database: "db/readonly.sqlite3" }
ActiveRecord::Base.connected_to(database: { writing: config }) do
handler = ActiveRecord::Base.connection_handler
assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
+
+ assert_not_nil pool = handler.retrieve_connection_pool("primary")
+ assert_equal(config, pool.spec.config)
end
ensure
ActiveRecord::Base.establish_connection(:arunit)
ENV["RAILS_ENV"] = previous_env
end
+ def test_switching_connections_with_database_and_role_raises
+ error = assert_raises(ArgumentError) do
+ ActiveRecord::Base.connected_to(database: :readonly, role: :writing) { }
+ end
+ assert_equal "connected_to can only accept a `database` or a `role` argument, but not both arguments.", error.message
+ end
+
+ def test_switching_connections_without_database_and_role_raises
+ error = assert_raises(ArgumentError) do
+ ActiveRecord::Base.connected_to { }
+ end
+ assert_equal "must provide a `database` or a `role`.", error.message
+ end
+
def test_switching_connections_with_database_symbol
previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
- ActiveRecord::Base.connected_to(database: :arunit2) do
+ config = {
+ "default_env" => {
+ "readonly" => { adapter: "sqlite3", database: "db/readonly.sqlite3" },
+ "primary" => { adapter: "sqlite3", database: "db/primary.sqlite3" }
+ }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.connected_to(database: :readonly) do
handler = ActiveRecord::Base.connection_handler
- assert_equal handler, ActiveRecord::Base.connection_handlers[:arunit2]
+ assert_equal handler, ActiveRecord::Base.connection_handlers[:readonly]
+
+ assert_not_nil pool = handler.retrieve_connection_pool("primary")
+ assert_equal(config["default_env"]["readonly"], pool.spec.config)
end
ensure
+ ActiveRecord::Base.configurations = @prev_configs
ActiveRecord::Base.establish_connection(:arunit)
ENV["RAILS_ENV"] = previous_env
end
@@ -189,6 +221,27 @@ module ActiveRecord
ActiveRecord::Base.configurations = @prev_configs
ActiveRecord::Base.establish_connection(:arunit)
end
+
+ def test_connects_to_returns_array_of_established_connections
+ config = {
+ "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" },
+ "development_readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ result = ActiveRecord::Base.connects_to database: { writing: :development, reading: :development_readonly }
+
+ assert_equal(
+ [
+ ActiveRecord::Base.connection_handlers[:writing].retrieve_connection_pool("primary"),
+ ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("primary")
+ ],
+ result
+ )
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ end
end
def test_connection_pools
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index b4593ccdf2..867ce7082b 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -274,6 +274,24 @@ class EnumTest < ActiveRecord::TestCase
end
assert_match(/must be either a hash, an array of symbols, or an array of strings./, e.message)
+
+ e = assert_raises(ArgumentError) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: { "" => 1, "active" => 2 }
+ end
+ end
+
+ assert_match(/Enum label name must not be blank/, e.message)
+
+ e = assert_raises(ArgumentError) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: ["active", ""]
+ end
+ end
+
+ assert_match(/Enum label name must not be blank/, e.message)
end
test "reserved enum names" do
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index e7778af55b..7b388ebc5e 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -56,7 +56,7 @@ class HotCompatibilityTest < ActiveRecord::TestCase
assert_equal "bar", record.foo
end
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:PostgreSQLAdapter) && ActiveRecord::Base.connection.prepared_statements
test "cleans up after prepared statement failure in a transaction" do
with_two_connections do |original_connection, ddl_connection|
record = @klass.create! bar: "bar"
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 5d060c8899..661163b4a1 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -127,6 +127,36 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal 20131219224947, migrator.current_version
end
+ def test_create_table_raises_if_already_exists
+ connection = Person.connection
+ connection.create_table :testings, force: true do |t|
+ t.string :foo
+ end
+
+ assert_raise(ActiveRecord::StatementInvalid) do
+ connection.create_table :testings do |t|
+ t.string :foo
+ end
+ end
+ ensure
+ connection.drop_table :testings, if_exists: true
+ end
+
+ def test_create_table_with_if_not_exists_true
+ connection = Person.connection
+ connection.create_table :testings, force: true do |t|
+ t.string :foo
+ end
+
+ assert_nothing_raised do
+ connection.create_table :testings, if_not_exists: true do |t|
+ t.string :foo
+ end
+ end
+ ensure
+ connection.drop_table :testings, if_exists: true
+ end
+
def test_create_table_with_force_true_does_not_drop_nonexisting_table
# using a copy as we need the drop_table method to
# continue to work for the ensure block of the test
diff --git a/activerecord/test/cases/numeric_data_test.rb b/activerecord/test/cases/numeric_data_test.rb
index 14db63890e..079e664ee4 100644
--- a/activerecord/test/cases/numeric_data_test.rb
+++ b/activerecord/test/cases/numeric_data_test.rb
@@ -24,8 +24,10 @@ class NumericDataTest < ActiveRecord::TestCase
)
assert m.save
- m1 = NumericData.find(m.id)
- assert_not_nil m1
+ m1 = NumericData.find_by(
+ bank_balance: 1586.43,
+ big_bank_balance: BigDecimal("1000234000567.95")
+ )
assert_kind_of Integer, m1.world_population
assert_equal 2**62, m1.world_population
@@ -49,8 +51,10 @@ class NumericDataTest < ActiveRecord::TestCase
)
assert m.save
- m1 = NumericData.find(m.id)
- assert_not_nil m1
+ m1 = NumericData.find_by(
+ bank_balance: 1586.43122334,
+ big_bank_balance: BigDecimal("234000567.952344")
+ )
assert_kind_of Integer, m1.world_population
assert_equal 2**62, m1.world_population
@@ -64,4 +68,26 @@ class NumericDataTest < ActiveRecord::TestCase
assert_kind_of BigDecimal, m1.big_bank_balance
assert_equal BigDecimal("234000567.95"), m1.big_bank_balance
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_numeric_fields_with_nan
+ m = NumericData.new(
+ bank_balance: BigDecimal("NaN"),
+ big_bank_balance: BigDecimal("NaN"),
+ world_population: 2**62,
+ my_house_population: 3
+ )
+ assert_predicate m.bank_balance, :nan?
+ assert_predicate m.big_bank_balance, :nan?
+ assert m.save
+
+ m1 = NumericData.find_by(
+ bank_balance: BigDecimal("NaN"),
+ big_bank_balance: BigDecimal("NaN")
+ )
+
+ assert_predicate m1.bank_balance, :nan?
+ assert_predicate m1.big_bank_balance, :nan?
+ end
+ end
end
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
index 8703d238a0..0b06cec40b 100644
--- a/activerecord/test/cases/relation/where_clause_test.rb
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -92,12 +92,16 @@ class ActiveRecord::Relation
original = WhereClause.new([
table["id"].in([1, 2, 3]),
table["id"].eq(1),
+ table["id"].is_not_distinct_from(1),
+ table["id"].is_distinct_from(2),
"sql literal",
random_object
])
expected = WhereClause.new([
table["id"].not_in([1, 2, 3]),
table["id"].not_eq(1),
+ table["id"].is_distinct_from(1),
+ table["id"].is_not_distinct_from(2),
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new("sql literal")),
Arel::Nodes::Not.new(random_object)
])
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index c0be45eee7..aa6b7915a2 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -591,6 +591,17 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
assert_equal [:before_commit, :after_commit], @topic.history
end
+ def test_commit_run_transactions_callbacks_with_nested_transactions
+ @topic.transaction do
+ @topic.transaction(requires_new: true) do
+ @topic.content = "foo"
+ @topic.save!
+ @topic.class.connection.add_transaction_record(@topic)
+ end
+ end
+ assert_equal [:before_commit, :after_commit], @topic.history
+ end
+
def test_rollback_does_not_run_transactions_callbacks_without_enrollment
@topic.transaction do
@topic.content = "foo"
diff --git a/activestorage/activestorage.gemspec b/activestorage/activestorage.gemspec
index cb1bb00a25..2c8816df25 100644
--- a/activestorage/activestorage.gemspec
+++ b/activestorage/activestorage.gemspec
@@ -25,6 +25,9 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activestorage/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "actionpack", version
s.add_dependency "activerecord", version
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index aa695c98b2..448a2eeebb 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -27,6 +27,9 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/activesupport/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "i18n", ">= 0.7", "< 2"
s.add_dependency "tzinfo", "~> 1.1"
s.add_dependency "minitest", "~> 5.1"
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index f8344912bb..a721187d2d 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -52,8 +52,12 @@ module ActiveSupport
end
class Event
- attr_reader :name, :time, :transaction_id, :payload, :children
- attr_accessor :end
+ attr_reader :name, :time, :end, :transaction_id, :payload, :children
+
+ def self.clock_gettime_supported? # :nodoc:
+ defined?(Process::CLOCK_PROCESS_CPUTIME_ID) &&
+ !Gem.win_platform?
+ end
def initialize(name, start, ending, transaction_id, payload)
@name = name
@@ -83,6 +87,11 @@ module ActiveSupport
@allocation_count_finish = now_allocations
end
+ def end=(ending)
+ ActiveSupport::Deprecation.deprecation_warning(:end=, :finish!)
+ @end = ending
+ end
+
# Returns the CPU time (in milliseconds) passed since the call to
# +start!+ and the call to +finish!+
def cpu_time
@@ -130,7 +139,7 @@ module ActiveSupport
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
- if defined?(Process::CLOCK_PROCESS_CPUTIME_ID)
+ if clock_gettime_supported?
def now_cpu
Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
end
diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb
index 9c8dffa9d8..8de01eb19b 100644
--- a/activesupport/lib/active_support/testing/parallelization.rb
+++ b/activesupport/lib/active_support/testing/parallelization.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "drb"
-require "drb/unix"
+require "drb/unix" unless Gem.win_platform?
require "active_support/core_ext/module/attribute_accessors"
module ActiveSupport
@@ -15,6 +15,8 @@ module ActiveSupport
end
def record(reporter, result)
+ raise DRb::DRbConnError if result.is_a?(DRb::DRbUnknown)
+
reporter.synchronize do
reporter.record(result)
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index f87099566b..5f4e3f3fd3 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -772,6 +772,16 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
assert_deprecated { ActiveSupport::Multibyte::Unicode.swapcase("") }
end
+ def test_normalize_non_unicode_string
+ # Fullwidth Latin Capital Letter A in Windows 31J
+ str = "\u{ff21}".encode(Encoding::Windows_31J)
+ assert_raise Encoding::CompatibilityError do
+ ActiveSupport::Deprecation.silence do
+ ActiveSupport::Multibyte::Unicode.normalize(str)
+ end
+ end
+ end
+
private
def string_from_classes(classes)
diff --git a/guides/assets/stylesheets/main.rtl.css b/guides/assets/stylesheets/main.rtl.css
new file mode 100644
index 0000000000..ea31d6017c
--- /dev/null
+++ b/guides/assets/stylesheets/main.rtl.css
@@ -0,0 +1,762 @@
+/* Guides.rubyonrails.org */
+/* Main.css */
+/* Created January 30, 2009 */
+/* Modified February 8, 2009
+--------------------------------------- */
+
+/* General
+--------------------------------------- */
+
+.right {float: right; margin-left: 1em;}
+.left {float: left; margin-right: 1em;}
+@media screen and (max-width: 480px) {
+ .right, .left { float: none; }
+}
+.small {font-size: smaller;}
+.large {font-size: larger;}
+.hide {display: none;}
+
+ul, ol { margin: 0 1.5em 1.5em 1.5em; }
+
+ul { list-style-type: disc; }
+ol { list-style-type: decimal; }
+
+dl { margin: 0 0 1.5em 0; }
+dl dt { font-weight: bold; }
+dd { margin-right: 1.5em;}
+
+pre, code {
+ font-size: 1em;
+ font-family: "Anonymous Pro", "Inconsolata", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
+ line-height: 1.5;
+ margin: 1.5em 0;
+ overflow: auto;
+ color: #222;
+}
+
+p code {
+ background: #eee;
+ border-radius: 2px;
+ padding: 1px 3px;
+}
+
+pre, tt, code {
+ white-space: pre-wrap; /* css-3 */
+ white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
+ white-space: -pre-wrap; /* Opera 4-6 */
+ white-space: -o-pre-wrap; /* Opera 7 */
+ word-wrap: break-word; /* Internet Explorer 5.5+ */
+}
+
+abbr, acronym { border-bottom: 1px dotted #666; }
+address { margin: 0 0 1.5em; font-style: italic; }
+del { color:#666; }
+
+blockquote { margin: 1.5em; color: #666; font-style: italic; }
+strong { font-weight: bold; }
+em, dfn { font-style: italic; }
+dfn { font-weight: bold; }
+sup, sub { line-height: 0; }
+p {margin: 0 0 1.5em;}
+
+label { font-weight: bold; }
+fieldset { padding:1.4em; margin: 0 0 1.5em 0; border: 1px solid #ccc; }
+legend { font-weight: bold; font-size:1.2em; }
+
+input.text, input.title,
+textarea, select {
+ margin:0.5em 0;
+ border:1px solid #bbb;
+}
+
+table {
+ margin: 0 0 1.5em;
+ border: 2px solid #CCC;
+ background: #FFF;
+ border-collapse: collapse;
+}
+
+table th, table td {
+ padding: 9px 10px;
+ border: 1px solid #CCC;
+ border-collapse: collapse;
+}
+
+table th {
+ border-bottom: 2px solid #CCC;
+ background: #EEE;
+ font-weight: bold;
+}
+
+img {
+ max-width: 100%;
+}
+
+
+/* Structure and Layout
+--------------------------------------- */
+
+body {
+ text-align: center;
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: 87.5%;
+ line-height: 1.5em;
+ background: #fff;
+ color: #999;
+ direction: rtl;
+}
+
+.wrapper {
+ text-align: right;
+ margin: 0 auto;
+ max-width: 960px;
+ padding: 0 1em;
+}
+
+.red-button {
+ display: inline-block;
+ border-top: 1px solid rgba(255,255,255,.5);
+ background: #751913;
+ background: -webkit-gradient(linear, right top, right bottom, from(#c52f24), to(#751913));
+ background: -webkit-linear-gradient(top, #c52f24, #751913);
+ background: -moz-linear-gradient(top, #c52f24, #751913);
+ background: -ms-linear-gradient(top, #c52f24, #751913);
+ background: -o-linear-gradient(top, #c52f24, #751913);
+ padding: 9px 18px;
+ -webkit-border-radius: 11px;
+ -moz-border-radius: 11px;
+ border-radius: 11px;
+ -webkit-box-shadow: rgba(0,0,0,1) 0 1px 0;
+ -moz-box-shadow: rgba(0,0,0,1) 0 1px 0;
+ box-shadow: rgba(0,0,0,1) 0 1px 0;
+ text-shadow: rgba(0,0,0,.4) 0 1px 0;
+ color: white;
+ font-size: 15px;
+ font-family: Helvetica, Arial, Sans-Serif;
+ text-decoration: none;
+ vertical-align: middle;
+ cursor: pointer;
+}
+.red-button:active {
+ border-top: none;
+ padding-top: 10px;
+ background: -webkit-gradient(linear, right top, right bottom, from(#751913), to(#c52f24));
+ background: -webkit-linear-gradient(top, #751913, #c52f24);
+ background: -moz-linear-gradient(top, #751913, #c52f24);
+ background: -ms-linear-gradient(top, #751913, #c52f24);
+ background: -o-linear-gradient(top, #751913, #c52f24);
+}
+
+#topNav {
+ padding: 1em 0;
+ color: #565656;
+ background: #222;
+}
+
+.s-hidden {
+ display: none;
+}
+
+@media screen and (min-width: 1025px) {
+ .more-info-button {
+ display: none;
+ }
+ .more-info-links {
+ list-style: none;
+ display: inline;
+ margin: 0;
+ }
+
+ .more-info {
+ display: inline-block;
+ }
+ .more-info:after {
+ content: " |";
+ }
+
+ .more-info:last-child:after {
+ content: "";
+ }
+}
+
+@media screen and (max-width: 1024px) {
+ #topNav .wrapper { text-align: center; }
+ .more-info-button {
+ position: relative;
+ z-index: 25;
+ }
+
+ .more-info-label {
+ display: none;
+ }
+
+ .more-info-container {
+ position: absolute;
+ top: .5em;
+ z-index: 20;
+ margin: 0 auto;
+ right: 0;
+ left: 0;
+ width: 20em;
+ }
+
+ .more-info-links {
+ display: block;
+ list-style: none;
+ background-color: #c52f24;
+ border-radius: 5px;
+ padding-top: 5.25em;
+ border: 1px #980905 solid;
+ }
+ .more-info-links.s-hidden {
+ display: none;
+ }
+ .more-info {
+ padding: .75em;
+ border-top: 1px #980905 solid;
+ }
+ .more-info a, .more-info a:link, .more-info a:visited {
+ display: block;
+ color: white;
+ width: 100%;
+ height: 100%;
+ text-decoration: none;
+ text-transform: uppercase;
+ }
+}
+
+#header {
+ background: #c52f24 url(../images/header_tile.gif) repeat-x;
+ color: #FFF;
+ padding: 1.5em 0;
+ z-index: 99;
+}
+
+#feature {
+ background: #d5e9f6 url(../images/feature_tile.gif) repeat-x;
+ color: #333;
+ padding: 0.5em 0 1.5em;
+}
+
+#container {
+ color: #333;
+ padding: 0.5em 0 1.5em 0;
+}
+
+#mainCol {
+ max-width: 630px;
+ margin-right: 2em;
+}
+
+#subCol {
+ position: absolute;
+ z-index: 0;
+ top: 21px;
+ left: 0;
+ background: #FFF;
+ padding: 1em 1.5em 1em 1.25em;
+ width: 17em;
+ font-size: 0.9285em;
+ line-height: 1.3846em;
+ margin-left: 1em;
+}
+
+
+@media screen and (max-width: 800px) {
+ #subCol {
+ position: static;
+ width: inherit;
+ margin-right: -1em;
+ margin-left: 0;
+ padding-left: 1.25em;
+ }
+}
+
+#footer {
+ padding: 2em 0;
+ background: #222 url(../images/footer_tile.gif) repeat-x;
+}
+#footer .wrapper {
+ padding-right: 1em;
+ max-width: 960px;
+}
+
+#header .wrapper, #topNav .wrapper, #feature .wrapper {padding-right: 1em; max-width: 960px;}
+#feature .wrapper {max-width: 640px; padding-left: 23em; position: relative; z-index: 0;}
+
+@media screen and (max-width: 960px) {
+ #container .wrapper { padding-left: 23em; }
+}
+
+@media screen and (max-width: 800px) {
+ #feature .wrapper, #container .wrapper { padding-left: 0; }
+}
+
+/* Links
+--------------------------------------- */
+
+a, a:link, a:visited {
+ color: #ee3f3f;
+ text-decoration: underline;
+}
+
+#mainCol a, #subCol a, #feature a {color: #980905;}
+#mainCol a code, #subCol a code, #feature a code {color: #980905;}
+
+#mainCol a.anchorlink, #mainCol a.anchorlink code {color: #333;}
+#mainCol a.anchorlink { text-decoration: none; }
+#mainCol a.anchorlink:hover { text-decoration: underline; }
+
+/* Navigation
+--------------------------------------- */
+
+.nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ float: left;
+ margin-top: 1.5em;
+ font-size: 1.2857em;
+}
+
+.nav .nav-item {color: #FFF; text-decoration: none;}
+.nav .nav-item:hover {text-decoration: underline;}
+
+.guides-index-large, .guides-index-small .guides-index-item {
+ padding: 0.5em 1.5em;
+ border-radius: 1em;
+ -webkit-border-radius: 1em;
+ -moz-border-radius: 1em;
+ background: #980905;
+ position: relative;
+ color: white;
+}
+
+.guides-index .guides-index-item {
+ background: #980905 url(../images/nav_arrow.gif) no-repeat left top;
+ padding-left: 1em;
+ position: relative;
+ z-index: 15;
+ padding-bottom: 0.125em;
+}
+
+.guides-index:hover .guides-index-item, .guides-index .guides-index-item:hover {
+ background-position: left -81px;
+ text-decoration: underline !important;
+}
+
+@media screen and (min-width: 481px) {
+ .nav {
+ float: left;
+ margin-top: 1.5em;
+ font-size: 1.2857em;
+ }
+ .nav>li {
+ display: inline;
+ margin-right: 0.5em;
+ }
+ .guides-index.guides-index-small {
+ display: none;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ .nav {
+ float: none;
+ width: 100%;
+ text-align: center;
+ }
+ .nav .nav-item {
+ display: block;
+ margin: 0;
+ width: 100%;
+ background-color: #980905;
+ border: solid 1px #620c04;
+ border-top: 0;
+ padding: 15px 0;
+ text-align: center;
+ }
+ .nav .nav-item, .nav-item.guides-index-item {
+ text-transform: uppercase;
+ }
+ .nav .nav-item:first-child, .nav-item.guides-index-small {
+ border-top: solid 1px #620c04;
+ }
+ .guides-index.guides-index-small {
+ display: block;
+ margin-top: 1.5em;
+ }
+ .guides-index.guides-index-large {
+ display: none;
+ }
+ .guides-index-small .guides-index-item {
+ font: inherit;
+ padding-right: .75em;
+ font-size: .95em;
+ background-position: 96% 16px;
+ -webkit-appearance: none;
+ }
+ .guides-index-small .guides-index-item:hover{
+ background-position: 96% -65px;
+ }
+}
+
+#guides {
+ width: 37em;
+ display: block;
+ background: #980905;
+ border-radius: 1em;
+ color: #f1938c;
+ padding: 1.5em 2em;
+ position: absolute;
+ z-index: 10;
+ top: -0.25em;
+ left: 0;
+ padding-top: 2em;
+}
+
+#guides.visible {
+ display: block !important;
+}
+
+.guides-section dt, .guides-section dd {
+ font-weight: normal;
+ font-size: 0.722em;
+ margin: 0;
+ padding: 0;
+}
+.guides-section dt {
+ margin: 0.5em 0 0;
+ padding:0;
+}
+#guides a {
+ background: none !important;
+ color: #FFF;
+ text-decoration: none;
+}
+#guides a:hover {
+ text-decoration: underline;
+}
+.guides-section-container {
+ display: flex;
+ flex-direction: column;
+ flex-wrap: wrap;
+ width: 100%;
+ max-height: 35em;
+}
+
+.guides-section {
+ min-width: 5em;
+ margin: 0 2em 0.5em 0;
+ flex: auto;
+ max-width: 12em;
+}
+
+.guides-section dd {
+ line-height: 1.3;
+ margin-bottom: 0.5em;
+}
+
+#guides hr {
+ display: block;
+ border: none;
+ height: 1px;
+ color: #f1938c;
+ background: #f1938c;
+}
+
+/* Headings
+--------------------------------------- */
+
+h1 {
+ font-size: 2.5em;
+ line-height: 1em;
+ margin: 0.6em 0 .2em;
+ font-weight: bold;
+}
+
+h2 {
+ font-size: 2.1428em;
+ line-height: 1em;
+ margin: 0.7em 0 .2333em;
+ font-weight: bold;
+}
+
+@media screen and (max-width: 480px) {
+ h2 {
+ font-size: 1.45em;
+ }
+}
+
+h3 {
+ font-size: 1.7142em;
+ line-height: 1.286em;
+ margin: 0.875em 0 0.2916em;
+ font-weight: bold;
+}
+
+@media screen and (max-width: 480px) {
+ h3 {
+ font-size: 1.45em;
+ }
+}
+
+h4 {
+ font-size: 1.2857em;
+ line-height: 1.2em;
+ margin: 1.6667em 0 .3887em;
+ font-weight: bold;
+}
+
+h5 {
+ font-size: 1em;
+ line-height: 1.5em;
+ margin: 1em 0 .5em;
+ font-weight: bold;
+}
+
+h6 {
+ font-size: 1em;
+ line-height: 1.5em;
+ margin: 1em 0 .5em;
+ font-weight: normal;
+}
+
+.section {
+ padding-bottom: 0.25em;
+ border-bottom: 1px solid #999;
+}
+
+/* Content
+--------------------------------------- */
+
+.pic {
+ margin: 0 2em 2em 0;
+}
+
+#topNav strong {color: #999; margin-left: 0.5em;}
+#topNav strong a {color: #FFF;}
+
+#header h1 {
+ float: right;
+ background: url(../images/rails_guides_logo_1x.png) no-repeat;
+ width: 297px;
+ text-indent: -9999em;
+ margin: 0;
+ padding: 0;
+}
+
+@media
+only screen and (-webkit-min-device-pixel-ratio: 2),
+only screen and ( min--moz-device-pixel-ratio: 2),
+only screen and ( -o-min-device-pixel-ratio: 2/1),
+only screen and ( min-device-pixel-ratio: 2),
+only screen and ( min-resolution: 192dpi),
+only screen and ( min-resolution: 2dppx) {
+ #header h1 {
+ background: url(../images/rails_guides_logo_2x.png) no-repeat;
+ background-size: 160%;
+ }
+}
+
+@media screen and (max-width: 480px) {
+ #header h1 {
+ float: none;
+ }
+}
+
+#header h1 a {
+ text-decoration: none;
+ display: block;
+ height: 77px;
+}
+
+#feature p {
+ font-size: 1.2857em;
+ margin-bottom: 0.75em;
+}
+
+@media screen and (max-width: 480px) {
+ #feature p {
+ font-size: 1em;
+ }
+}
+
+#feature ul {margin-right: 0;}
+#feature ul li {
+ list-style: none;
+ background: url(../images/check_bullet.gif) no-repeat right 0.5em;
+ padding: 0.5em 1.75em 0.5em 1.75em;
+ font-size: 1.1428em;
+ font-weight: bold;
+}
+
+#mainCol dd, #subCol dd {
+ padding: 0.25em 0 1em;
+ border-bottom: 1px solid #CCC;
+ margin-bottom: 1em;
+ margin-right: 0;
+ /*padding-right: 28px;*/
+ padding-right: 0;
+}
+
+#mainCol dt, #subCol dt {
+ font-size: 1.2857em;
+ padding: 0.125em 0 0.25em 0;
+ margin-bottom: 0;
+}
+
+@media screen and (max-width: 480px) {
+ #mainCol dt, #subCol dt {
+ font-size: 1em;
+ }
+}
+
+#mainCol dd.work-in-progress, #subCol dd.work-in-progress {
+ background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1em 1.25em 48px;
+ margin-right: 0;
+ margin-top: 0.25em;
+}
+
+#mainCol dd.kindle, #subCol dd.kindle {
+ background: #d5e9f6 url(../images/tab_info.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1em 1.25em 48px;
+ margin-right: 0;
+ margin-top: 0.25em;
+}
+
+#mainCol div.warning, #subCol dd.warning {
+ background: #f9d9d8 url(../images/tab_red.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1.25em 0.25em 48px;
+ margin-right: 0;
+ margin-top: 0.25em;
+}
+
+#subCol .chapters {color: #980905;}
+#subCol .chapters a {font-weight: bold;}
+#subCol .chapters ul a {font-weight: normal;}
+#subCol .chapters li {margin-bottom: 0.75em;}
+#subCol h3.chapter {margin-top: 0.25em;}
+#subCol h3.chapter img {vertical-align: text-bottom;}
+#subCol .chapters ul {margin-right: 0; margin-top: 0.5em;}
+#subCol .chapters ul li {
+ list-style: none;
+ padding: 0 1em 0 0;
+ background: url(../images/bullet.gif) no-repeat right 0.45em;
+ margin-right: 0;
+ font-size: 1em;
+ font-weight: normal;
+}
+
+#subCol li ul, li ol { margin:0 1.5em; }
+
+div.code_container {
+ background: #EEE url(../images/tab_grey.gif) no-repeat right top;
+ padding: 0.25em 48px 0.5em 1em;
+}
+
+.note {
+ background: #fff9d8 url(../images/tab_note.gif) no-repeat right top;
+ border: none;
+ padding: 1em 48px 0.25em 1em;
+ margin: 0.25em 0 1.5em 0;
+}
+
+.info {
+ background: #d5e9f6 url(../images/tab_info.gif) no-repeat right top;
+ border: none;
+ padding: 1em 48px 0.25em 1em;
+ margin: 0.25em 0 1.5em 0;
+}
+
+#mainCol div.todo {
+ background: #fff9d8 url(../images/tab_yellow.gif) no-repeat right top;
+ border: none;
+ padding: 1em 48px 0.25em 1em;
+ margin: 0.25em 0 1.5em 0;
+}
+
+.note code, .info code, .todo code {
+ background: #fff;
+}
+
+#mainCol ul li {
+ list-style:none;
+ background: url(../images/grey_bullet.gif) no-repeat right 0.5em;
+ padding-right: 1em;
+ margin-right: 0;
+}
+
+#subCol .content {
+ font-size: 0.7857em;
+ line-height: 1.5em;
+}
+
+#subCol .content li {
+ font-weight: normal;
+ background: none;
+ padding: 0 0 1em;
+ font-size: 1.1667em;
+}
+
+/* Clearing
+--------------------------------------- */
+
+.clearfix:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+* html .clearfix {height: 1%;}
+.clearfix {display: block;}
+
+/* Same bottom margin for special boxes than for regular paragraphs, this way
+intermediate whitespace looks uniform. */
+div.code_container, div.important, div.caution, div.warning, div.note, div.info {
+ margin-bottom: 1.5em;
+}
+
+/* Remove bottom margin of paragraphs in special boxes, otherwise they get a
+spurious blank area below with the box background. */
+div.important p, div.caution p, div.warning p, div.note p, div.info p {
+ margin-bottom: 1em;
+}
+
+/* Edge Badge
+--------------------------------------- */
+
+#edge-badge {
+ position: fixed;
+ right: 0px;
+ top: 0px;
+ z-index: 100;
+ border: none;
+}
+
+/* Foundation v2.1.4 http://foundation.zurb.com */
+/* Artfully masterminded by ZURB */
+
+/* Mobile */
+@media only screen and (max-width: 767px) {
+ table.responsive { margin-bottom: 0; }
+
+ .pinned { position: absolute; right: 0; top: 0; background: #fff; width: 35%; overflow: hidden; overflow-x: scroll; border-left: 1px solid #ccc; border-right: 1px solid #ccc; }
+ .pinned table { border-left: none; border-right: none; width: 100%; }
+ .pinned table th, .pinned table td { white-space: nowrap; }
+ .pinned td:last-child { border-bottom: 0; }
+
+ div.table-wrapper { position: relative; margin-bottom: 20px; overflow: hidden; border-left: 1px solid #ccc; }
+ div.table-wrapper div.scrollable table { margin-right: 35%; }
+ div.table-wrapper div.scrollable { overflow: scroll; overflow-y: hidden; }
+
+ table.responsive td, table.responsive th { position: relative; white-space: nowrap; overflow: hidden; }
+ table.responsive th:first-child, table.responsive td:first-child, table.responsive td:first-child, table.responsive.pinned td { display: none; }
+
+}
diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb
index f2d4d6f647..b24c0205d5 100644
--- a/guides/rails_guides.rb
+++ b/guides/rails_guides.rb
@@ -25,5 +25,6 @@ RailsGuides::Generator.new(
all: env_flag["ALL"],
only: env_value["ONLY"],
kindle: env_flag["KINDLE"],
- language: env_value["GUIDES_LANGUAGE"]
+ language: env_value["GUIDES_LANGUAGE"],
+ rtl: env_value["RTL"]
).generate
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index c83538ad48..3d8f54ab34 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -17,13 +17,14 @@ module RailsGuides
class Generator
GUIDES_RE = /\.(?:erb|md)\z/
- def initialize(edge:, version:, all:, only:, kindle:, language:)
+ def initialize(edge:, version:, all:, only:, kindle:, language:, rtl: false)
@edge = edge
@version = version
@all = all
@only = only
@kindle = kindle
@language = language
+ @rtl = rtl
if @kindle
check_for_kindlegen
@@ -116,6 +117,11 @@ module RailsGuides
def copy_assets
FileUtils.cp_r(Dir.glob("#{@guides_dir}/assets/*"), @output_dir)
+
+ if @rtl
+ FileUtils.rm(Dir.glob("#{@output_dir}/stylesheets/main.css"))
+ FileUtils.mv("#{@output_dir}/stylesheets/main.rtl.css", "#{@output_dir}/stylesheets/main.css")
+ end
end
def output_file_for(guide)
@@ -198,7 +204,7 @@ module RailsGuides
def check_fragment_identifiers(html, anchors)
html.scan(/<a\s+href="#([^"]+)/).flatten.each do |fragment_identifier|
next if fragment_identifier == "mainCol" # in layout, jumps to some DIV
- unless anchors.member?(fragment_identifier)
+ unless anchors.member?(CGI.unescape(fragment_identifier))
guess = anchors.min { |a, b|
Levenshtein.distance(fragment_identifier, a) <=> Levenshtein.distance(fragment_identifier, b)
}
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index e6c0ae31a8..2f602c3e0a 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -242,9 +242,9 @@ WebNotificationsChannel.broadcast_to(
```
The `WebNotificationsChannel.broadcast_to` call places a message in the current
-subscription adapter (by default `redis` for production and `async` for development and
-test environments)'s pubsub queue under a separate broadcasting name for each user.
-For a user with an ID of 1, the broadcasting name would be `web_notifications:1`.
+subscription adapter's pubsub queue under a separate broadcasting name for each user.
+The default pubsub queue for Action Cable is `redis` in production and `async` in development and
+test environments. For a user with an ID of 1, the broadcasting name would be `web_notifications:1`.
The channel has been instructed to stream everything that arrives at
`web_notifications:1` directly to the client by invoking the `received`
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 4dc69ef911..39239852ca 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -289,7 +289,7 @@ style if the code inside your block is so short that it fits in a single line.
For example, you could send metrics for every job enqueued:
```ruby
-class ApplicationJob
+class ApplicationJob < ActiveJob::Base
before_enqueue { |job| $statsd.increment "#{job.class.name.underscore}.enqueue" }
end
```
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index c98f24d786..0fda7c5cfd 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -538,7 +538,8 @@ end
If you want to be sure that an association is present, you'll need to test
whether the associated object itself is present, and not the foreign key used
-to map the association.
+to map the association. This way, it is not only checked that the foreign key
+is not empty but also that the referenced object exists.
```ruby
class LineItem < ApplicationRecord
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index f75517c5ab..4f3e8b2cff 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1662,6 +1662,8 @@ Controls what happens to the associated objects when their owner is destroyed:
* `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there are any associated records
* `:restrict_with_error` causes an error to be added to the owner if there are any associated objects
+The `:destroy` and `:delete_all` options also affect the semantics of the `collection.delete` and `collection=` methods by causing them to destroy associated objects when they are removed from the collection.
+
##### `:foreign_key`
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 321eee637f..67b097f2ae 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -302,7 +302,7 @@ class Product < ApplicationRecord
end
```
-NOTE: Notice that in this example we used the `cache_key` method, so the resulting cache key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key` generates a string based on the model's `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key.
+NOTE: Notice that in this example we used the `cache_key_with_version` method, so the resulting cache key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key_with_version` generates a string based on the model's `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key.
### SQL Caching
@@ -563,7 +563,7 @@ class ProductsController < ApplicationController
# If the request is stale according to the given timestamp and etag value
# (i.e. it needs to be processed again) then execute this block
- if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key)
+ if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
respond_to do |wants|
# ... normal response processing
end
@@ -577,7 +577,7 @@ class ProductsController < ApplicationController
end
```
-Instead of an options hash, you can also simply pass in a model. Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`:
+Instead of an options hash, you can also simply pass in a model. Rails will use the `updated_at` and `cache_key_with_version` methods for setting `last_modified` and `etag`:
```ruby
class ProductsController < ApplicationController
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 5fd3ad17de..bbebf97c3f 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -36,35 +36,35 @@ $ rails --help
Usage: rails COMMAND [ARGS]
The most common rails commands are:
-generate Generate new code (short-cut alias: "g")
-console Start the Rails console (short-cut alias: "c")
-server Start the Rails server (short-cut alias: "s")
-...
+ generate Generate new code (short-cut alias: "g")
+ console Start the Rails console (short-cut alias: "c")
+ server Start the Rails server (short-cut alias: "s")
+ ...
All commands can be run with -h (or --help) for more information.
In addition to those commands, there are:
-about List versions of all Rails ...
-assets:clean[keep] Remove old compiled assets
-assets:clobber Remove compiled assets
-assets:environment Load asset compile environment
-assets:precompile Compile all the assets ...
-...
-db:fixtures:load Loads fixtures into the ...
-db:migrate Migrate the database ...
-db:migrate:status Display status of migrations
-db:rollback Rolls the schema back to ...
-db:schema:cache:clear Clears a db/schema_cache.yml file
-db:schema:cache:dump Creates a db/schema_cache.yml file
-db:schema:dump Creates a db/schema.rb file ...
-db:schema:load Loads a schema.rb file ...
-db:seed Loads the seed data ...
-db:structure:dump Dumps the database structure ...
-db:structure:load Recreates the databases ...
-db:version Retrieves the current schema ...
-...
-restart Restart app by touching ...
-tmp:create Creates tmp directories ...
+ about List versions of all Rails ...
+ assets:clean[keep] Remove old compiled assets
+ assets:clobber Remove compiled assets
+ assets:environment Load asset compile environment
+ assets:precompile Compile all the assets ...
+ ...
+ db:fixtures:load Loads fixtures into the ...
+ db:migrate Migrate the database ...
+ db:migrate:status Display status of migrations
+ db:rollback Rolls the schema back to ...
+ db:schema:cache:clear Clears a db/schema_cache.yml file
+ db:schema:cache:dump Creates a db/schema_cache.yml file
+ db:schema:dump Creates a db/schema.rb file ...
+ db:schema:load Loads a schema.rb file ...
+ db:seed Loads the seed data ...
+ db:structure:dump Dumps the database structure ...
+ db:structure:load Recreates the databases ...
+ db:version Retrieves the current schema ...
+ ...
+ restart Restart app by touching ...
+ tmp:create Creates tmp directories ...
```
Let's create a simple Rails application to step through each of these commands in context.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index d03943ba4a..de6766e12e 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -379,6 +379,14 @@ The MySQL adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`.
+The PostgreSQL adapter adds one additional configuration option:
+
+* `ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables`
+ controls whether database tables created should be "unlogged," which can speed
+ up performance but adds a risk of data loss if the database crashes. It is
+ highly recommended that you do not enable this in a production environment.
+ Defaults to `false` in all environments.
+
The SQLite3Adapter adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`
@@ -723,7 +731,7 @@ There are a few configuration options available in Active Support:
`config.active_job` provides the following configuration options:
-* `config.active_job.queue_adapter` sets the adapter for the queueing backend. The default adapter is `:async`. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
+* `config.active_job.queue_adapter` sets the adapter for the queuing backend. The default adapter is `:async`. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
```ruby
# Be sure to have the adapter's gem in your Gemfile
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index ed47a0de0f..709a5146e9 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -324,6 +324,26 @@ $ cd actionmailer
$ bundle exec rake test
```
+#### For a Specific Directory
+
+If you want to run the tests located in a specific directory use the `TEST_DIR`
+environment variable. For example, this will run the tests in the
+`railties/test/generators` directory only:
+
+```bash
+$ cd railties
+$ TEST_DIR=generators bundle exec rake test
+```
+
+#### For a Specific File
+
+You can run the tests for a particular file by using:
+
+```bash
+$ cd actionpack
+$ bundle exec ruby -w -Itest test/template/form_helper_test.rb
+```
+
#### Running a Single Test
You can run a single test through ruby. For instance:
@@ -333,8 +353,7 @@ $ cd actionmailer
$ bundle exec ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout
```
-The `-n` option allows you to run a single method instead of the whole
-file.
+The `-n` option allows you to run a single method instead of the whole file.
#### Running tests with a specific seed
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 07538a1cb7..b3baf726e3 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -8,8 +8,6 @@ This guide covers how to setup an environment for Ruby on Rails core development
After reading this guide, you will know:
* How to set up your machine for Rails development
-* How to run specific groups of unit tests from the Rails test suite
-* How the Active Record portion of the Rails test suite operates
--------------------------------------------------------------------------------
@@ -43,195 +41,131 @@ $ git clone https://github.com/rails/rails.git
$ cd rails
```
-### Set up and Run the Tests
+### Install Additional Tools and Services
-The test suite must pass with any submitted code. No matter whether you are writing a new patch, or evaluating someone else's, you need to be able to run the tests.
+Some Rails tests depend on additional tools that you need to install before running those specific tests.
-Install first SQLite3 and its development files for the `sqlite3` gem. On macOS
-users are done with:
+Here's the list of each gems' additional dependencies:
-```bash
-$ brew install sqlite3
-```
-
-In Ubuntu you're done with just:
-
-```bash
-$ sudo apt-get install sqlite3 libsqlite3-dev
-```
-
-If you are on Fedora or CentOS, you're done with
-
-```bash
-$ sudo yum install libsqlite3x libsqlite3x-devel
-```
-
-If you are on Arch Linux, you will need to run:
-
-```bash
-$ sudo pacman -S sqlite
-```
-
-For FreeBSD users, you're done with:
-
-```bash
-# pkg install sqlite3
-```
+* Action Cable depends on Redis
+* Active Record depends on SQLite3, MySQL and PostgreSQL
+* Active Storage depends on Yarn (additionally Yarn depends on
+ [Node.js](https://nodejs.org/)), ImageMagick, FFmpeg, muPDF, and on macOS
+ also XQuartz and Poppler.
+* Active Support depends on memcached and Redis
+* Railties depend on a JavaScript runtime environment, such as having
+ [Node.js](https://nodejs.org/) installed.
-Or compile the `databases/sqlite3` port.
+Install all the services you need to properly test the full gem you'll be
+making changes to.
-Get a recent version of [Bundler](https://bundler.io/)
+NOTE: Redis' documentation discourage installations with package managers as those are usually outdated. Installing from source and bringing the server up is straight forward and well documented on [Redis' documentation](https://redis.io/download#installation).
-```bash
-$ gem install bundler
-$ gem update bundler
-```
+NOTE: Active Record tests _must_ pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against single adapter.
-and run:
+Below you can find instructions on how to install all of the additional
+tools for different OSes.
-```bash
-$ bundle install --without db
-```
-
-This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon.
+#### macOS
-NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running.
+On macOS you can use [Homebrew](https://brew.sh/) to install all of the
+additional tools.
-You can use [Homebrew](https://brew.sh/) to install memcached on macOS:
+To install all run:
```bash
-$ brew install memcached
+$ brew bundle
```
-On Ubuntu you can install it with apt-get:
+You'll also need to start each of the installed services. To list all
+available services run:
```bash
-$ sudo apt-get install memcached
+$ brew services list
```
-Or use yum on Fedora or CentOS:
+You can then start each of the services one by one like this:
```bash
-$ sudo yum install memcached
+$ brew services start mysql
```
-If you are running on Arch Linux:
-
-```bash
-$ sudo pacman -S memcached
-```
-
-For FreeBSD users, you're done with:
-
-```bash
-# pkg install memcached
-```
+Replace `mysql` with the name of the service you want to start.
-Alternatively, you can compile the `databases/memcached` port.
+#### Ubuntu
-With the dependencies now installed, you can run the test suite with:
+To install all run:
```bash
-$ bundle exec rake test
-```
-
-You can also run tests for a specific component, like Action Pack, by going into its directory and executing the same command:
+$ sudo apt-get update
+$ sudo apt-get install sqlite3 libsqlite3-dev
+ mysql-server libmysqlclient-dev
+ postgresql postgresql-client postgresql-contrib libpq-dev
+ redis-server memcached imagemagick ffmpeg mupdf mupdf-tools
-```bash
-$ cd actionpack
-$ bundle exec rake test
+# Install Yarn
+$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+$ sudo apt-get install yarn
```
-If you want to run the tests located in a specific directory use the `TEST_DIR` environment variable. For example, this will run the tests in the `railties/test/generators` directory only:
-
-```bash
-$ cd railties
-$ TEST_DIR=generators bundle exec rake test
-```
+#### Fedora or CentOS
-You can run the tests for a particular file by using:
+To install all run:
```bash
-$ cd actionpack
-$ bundle exec ruby -Itest test/template/form_helper_test.rb
-```
-
-Or, you can run a single test in a particular file:
+$ sudo dnf install sqlite-devel sqlite-libs
+ mysql-server mysql-devel
+ postgresql-server postgresql-devel
+ redis memcached imagemagick ffmpeg mupdf
-```bash
-$ cd actionpack
-$ bundle exec ruby -Itest path/to/test.rb -n test_name
+# Install Yarn
+# Use this command if you do not have Node.js installed
+$ curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash -
+# If you have Node.js installed, use this command instead
+$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
+$ sudo dnf install yarn
```
-### Railties Setup
-
-Some Railties tests depend on a JavaScript runtime environment, such as having [Node.js](https://nodejs.org/) installed.
-
-### Active Record Setup
-
-Active Record's test suite runs three times: once for SQLite3, once for MySQL, and once for PostgreSQL. We are going to see now how to set up the environment for them.
-
-WARNING: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
-
-#### Database Configuration
-
-The Active Record test suite requires a custom config file: `activerecord/test/config.yml`. An example is provided in `activerecord/test/config.example.yml` which can be copied and used as needed for your environment.
+#### Arch Linux
-#### MySQL and PostgreSQL
-
-To be able to run the suite for MySQL and PostgreSQL we need their gems. Install
-first the servers, their client libraries, and their development files.
-
-On macOS, you can run:
-
-```bash
-$ brew install mysql
-$ brew install postgresql
-```
-
-Follow the instructions given by Homebrew to start these.
-
-On Ubuntu, just run:
-
-```bash
-$ sudo apt-get install mysql-server libmysqlclient-dev
-$ sudo apt-get install postgresql postgresql-client postgresql-contrib libpq-dev
-```
-
-On Fedora or CentOS, just run:
+To install all run:
```bash
-$ sudo yum install mysql-server mysql-devel
-$ sudo yum install postgresql-server postgresql-devel
+$ sudo pacman -S sqlite
+ mariadb libmariadbclient mariadb-clients
+ postgresql postgresql-libs
+ redis memcached imagemagick ffmpeg mupdf mupdf-tools poppler
+ yarn
+$ sudo systemctl start redis
```
-If you are running Arch Linux, MySQL isn't supported anymore so you will need to
-use MariaDB instead (see [this announcement](https://www.archlinux.org/news/mariadb-replaces-mysql-in-repositories/)):
+NOTE: If you are running Arch Linux, MySQL isn't supported anymore so you will need to
+use MariaDB instead (see [this announcement](https://www.archlinux.org/news/mariadb-replaces-mysql-in-repositories/)).
-```bash
-$ sudo pacman -S mariadb libmariadbclient mariadb-clients
-$ sudo pacman -S postgresql postgresql-libs
-```
+#### FreeBSD
-FreeBSD users will have to run the following:
+To install all run:
```bash
-# pkg install mysql56-client mysql56-server
-# pkg install postgresql94-client postgresql94-server
+# pkg install sqlite3
+ mysql80-client mysql80-server
+ postgresql11-client postgresql11-server
+ memcached imagemagick ffmpeg mupdf
+ yarn
+# portmaster databases/redis
```
-Or install them through ports (they are located under the `databases` folder).
-If you run into troubles during the installation of MySQL, please see
-[the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html).
+Or install everything through ports (these packages are located under the
+`databases` folder).
-After that, run:
+NOTE: If you run into troubles during the installation of MySQL, please see
+[the MySQL documentation](https://dev.mysql.com/doc/refman/8.0/en/freebsd-installation.html).
-```bash
-$ rm .bundle/config
-$ bundle install
-```
+### Database Configuration
-First, we need to delete `.bundle/config` because Bundler remembers in that file that we didn't want to install the "db" group (alternatively you can edit the file).
+There are couple of additional steps required to configure database engines
+required for running Active Record tests.
In order to be able to run the test suite against MySQL you need to create a user named `rails` with privileges on the test databases:
@@ -247,13 +181,6 @@ mysql> GRANT ALL PRIVILEGES ON inexistent_activerecord_unittest.*
to 'rails'@'localhost';
```
-and create the test databases:
-
-```bash
-$ cd activerecord
-$ bundle exec rake db:mysql:build
-```
-
PostgreSQL's authentication works differently. To setup the development environment
with your development account, on Linux or BSD, you just have to run:
@@ -267,21 +194,24 @@ and for macOS:
$ createuser --superuser $USER
```
-Then, you need to create the test databases with:
+Then, you need to create the test databases for both MySQL and PostgreSQL with:
```bash
$ cd activerecord
-$ bundle exec rake db:postgresql:build
+$ bundle exec rake db:create
```
-It is possible to build databases for both PostgreSQL and MySQL with:
+NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator".
+
+You can also create test databases for each database engine separately:
```bash
$ cd activerecord
-$ bundle exec rake db:create
+$ bundle exec rake db:mysql:build
+$ bundle exec rake db:postgresql:build
```
-You can cleanup the databases using:
+and you can drop the databases using:
```bash
$ cd activerecord
@@ -290,138 +220,40 @@ $ bundle exec rake db:drop
NOTE: Using the Rake task to create the test databases ensures they have the correct character set and collation.
-NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator".
-
If you're using another database, check the file `activerecord/test/config.yml` or `activerecord/test/config.example.yml` for default connection information. You can edit `activerecord/test/config.yml` to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails.
-### Action Cable Setup
-
-Action Cable uses Redis as its default subscriptions adapter ([read more](action_cable_overview.html#broadcasting)). Thus, in order to have Action Cable's tests passing you need to install and have Redis running.
-
-#### Install Redis From Source
+### Install JavaScript dependencies
-Redis' documentation discourage installations with package managers as those are usually outdated. Installing from source and bringing the server up is straight forward and well documented on [Redis' documentation](https://redis.io/download#installation).
-
-#### Install Redis From Package Manager
-
-On macOS, you can run:
-
-```bash
-$ brew install redis
-```
-
-Follow the instructions given by Homebrew to start these.
-
-On Ubuntu, just run:
-
-```bash
-$ sudo apt-get install redis-server
-```
-
-On Fedora or CentOS (requires EPEL enabled), just run:
-
-```bash
-$ sudo yum install redis
-```
-
-If you are running Arch Linux, just run:
-
-```bash
-$ sudo pacman -S redis
-$ sudo systemctl start redis
-```
-
-FreeBSD users will have to run the following:
-
-```bash
-# portmaster databases/redis
-```
-
-### Active Storage Setup
-
-When working on Active Storage, it is important to note that you need to
-install its JavaScript dependencies while working on that section of the
-codebase. In order to install these dependencies, it is necessary to
-have Yarn, a Node.js package manager, available on your system. A
-prerequisite for installing this package manager is that
-[Node.js](https://nodejs.org) is installed.
-
-
-On macOS, you can run:
-
-```bash
-$ brew install yarn
-```
-
-On Ubuntu, you can run:
-
-```bash
-$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
-$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
-
-$ sudo apt-get update && sudo apt-get install yarn
-```
-
-On Fedora or CentOS, just run:
-
-```bash
-$ sudo wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo
-
-$ sudo yum install yarn
-```
-
-Finally, after installing Yarn, you will need to run the following
-command inside of the `activestorage` directory to install the dependencies:
+If you installed Yarn, you will need to install the javascript dependencies:
```bash
+$ cd activestorage
$ yarn install
```
-Extracting previews, tested in Active Storage's test suite requires third-party
-applications, ImageMagick for images, FFmpeg for video and muPDF for PDFs, and on macOS also XQuartz
-and Poppler. Without these applications installed, Active Storage tests will
-raise errors.
+### Install Bundler gem
-On macOS you can run:
+Get a recent version of [Bundler](https://bundler.io/)
```bash
-$ brew install ffmpeg
-$ brew install imagemagick
-$ brew cask install xquartz
-$ brew install mupdf-tools
-$ brew install poppler
+$ gem install bundler
+$ gem update bundler
```
-On Ubuntu, you can run:
+and run:
```bash
-$ sudo apt-get update
-$ sudo apt-get install ffmpeg
-$ sudo apt-get install imagemagick
-$ sudo apt-get install mupdf mupdf-tools
+$ bundle install
```
-On Fedora or CentOS, just run:
+or:
```bash
-$ sudo yum install ffmpeg
-$ sudo yum install imagemagick
-$ sudo yum install mupdf
+$ bundle install --without db
```
-FreeBSD users can just run:
+if you don't need to run Active Record tests.
-```bash
-# pkg install imagemagick
-# pkg install ffmpeg
-# pkg install mupdf
-```
-
-On Arch Linux, you can run:
+### Contribute to Rails
-```bash
-$ sudo pacman -S ffmpeg
-$ sudo pacman -S imagemagick
-$ sudo pacman -S mupdf mupdf-tools
-$ sudo pacman -S poppler
-```
+After you've setup everything, read how you can start [contributing](contributing_to_ruby_on_rails.html#running-an-application-against-your-local-branch).
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index f5c0ba5b2d..4b56cf6296 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -107,8 +107,8 @@ HTML Guides
-----------
Before generating the guides, make sure that you have the latest version of
-Bundler installed on your system. As of this writing, you must install Bundler
-1.3.5 or later on your device.
+Bundler installed on your system. You can find the latest Bundler version
+[here](https://rubygems.org/gems/bundler). As of this writing, it's v1.17.1.
To install the latest version of Bundler, run `gem install bundler`.
diff --git a/guides/source/security.md b/guides/source/security.md
index bb996cc39c..dbec3cdd2d 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -1235,6 +1235,11 @@ version:
Rails.application.credentials.some_api_key! # => raises KeyError: :some_api_key is blank
```
+Dependency Management and CVEs
+------------------------------
+
+We don’t bump dependencies just to encourage use of new versions, including for security issues. This is because application owners need to manually update their gems regardless of our efforts. Use `bundle update --conservative gem_name` to safely update vulnerable dependencies.
+
Additional Resources
--------------------
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index f94b67a0ac..448fd48f10 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Remove `app/assets` and `app/javascript` from `eager_load_paths` and `autoload_paths`.
+
+ *Gannon McGibbon*
+
+* Add JSON support to rails properties route (`/rails/info/properties`).
+
+ Now, `Rails::Info` properties may be accessed in JSON format at `/rails/info/properties.json`.
+
+ *Yoshiyuki Hirano*
+
* Use Ids instead of memory addresses when displaying references in scaffold views.
Fixes #29200.
diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb
index 6d99ac9936..f09aa3ae0d 100644
--- a/railties/lib/rails/command.rb
+++ b/railties/lib/rails/command.rb
@@ -83,20 +83,21 @@ module Rails
end
def print_commands # :nodoc:
- sorted_groups.each { |b, n| print_list(b, n) }
+ commands.each { |command| puts(" #{command}") }
end
- def sorted_groups # :nodoc:
- lookup!
+ private
+ COMMANDS_IN_USAGE = %w(generate console server test test:system dbconsole new)
+ private_constant :COMMANDS_IN_USAGE
- groups = (subclasses - hidden_commands).group_by { |c| c.namespace.split(":").first }
- groups.transform_values! { |commands| commands.flat_map(&:printing_commands).sort }
+ def commands
+ lookup!
- rails = groups.delete("rails")
- [[ "rails", rails ]] + groups.sort.to_a
- end
+ visible_commands = (subclasses - hidden_commands).flat_map(&:printing_commands)
+
+ (visible_commands - COMMANDS_IN_USAGE).sort
+ end
- private
def command_type # :doc:
@command_type ||= "command"
end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 6bf0406b21..4143b3c881 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -38,7 +38,9 @@ module Rails
@paths ||= begin
paths = Rails::Paths::Root.new(@root)
- paths.add "app", eager_load: true, glob: "{*,*/concerns}"
+ paths.add "app", eager_load: true,
+ glob: "{*,*/concerns}",
+ exclude: %w(assets javascript)
paths.add "app/assets", glob: "*"
paths.add "app/controllers", eager_load: true
paths.add "app/channels", eager_load: true, glob: "**/*_channel.rb"
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index f56f79b8d4..33002790d4 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -258,8 +258,8 @@ 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 Webpack with a particular framework (options: #{WEBPACKS.join('/')})"
+ class_option :webpack, type: :string, aliases: "--webpacker", default: nil,
+ desc: "Preconfigure Webpack with a particular framework (options: #{WEBPACKS.join(", ")})"
class_option :skip_webpack_install, type: :boolean, default: false,
desc: "Don't run Webpack install"
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 a18e03e7db..3f73bae3da 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
@@ -23,7 +23,7 @@ FileUtils.chdir APP_ROOT do
# puts "\n== Copying sample files =="
# unless File.exist?('config/database.yml')
- # cp 'config/database.yml.sample', 'config/database.yml'
+ # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
# end
puts "\n== Preparing database =="
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt
index d3bcaa5ec8..c517b0f96b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt
@@ -11,6 +11,10 @@
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
+<%- unless options[:skip_javascript] -%>
+# # If you are using webpack-dev-server then specify webpack-dev-server host
+# policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
+<%- end -%>
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
index c918b57eca..c06cd525d7 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
@@ -4,7 +4,7 @@ require 'rails/test_help'
class ActiveSupport::TestCase
# Run tests in parallel with specified workers
-<% if defined?(JRUBY_VERSION) -%>
+<% if defined?(JRUBY_VERSION) || Gem.win_platform? -%>
parallelize(workers: 2, with: :threads)
<%- else -%>
parallelize(workers: 2)
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index 3df36efc4c..b8173c8d11 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -54,6 +54,10 @@ module Rails
table << "</table>"
end
end
+
+ def to_json
+ Hash[properties].to_json
+ end
end
# The Rails version.
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index b4f4a5922a..50fe176946 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -14,8 +14,16 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
end
def properties
- @info = Rails::Info.to_html
- @page_title = "Properties"
+ respond_to do |format|
+ format.html do
+ @info = Rails::Info.to_html
+ @page_title = "Properties"
+ end
+
+ format.json do
+ render json: Rails::Info.to_json
+ end
+ end
end
def routes
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 87222563fd..8367ac8980 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -113,10 +113,11 @@ module Rails
attr_accessor :glob
def initialize(root, current, paths, options = {})
- @paths = paths
- @current = current
- @root = root
- @glob = options[:glob]
+ @paths = paths
+ @current = current
+ @root = root
+ @glob = options[:glob]
+ @exclude = options[:exclude]
options[:autoload_once] ? autoload_once! : skip_autoload_once!
options[:eager_load] ? eager_load! : skip_eager_load!
@@ -189,13 +190,11 @@ module Rails
raise "You need to set a path root" unless @root.path
result = []
- each do |p|
- path = File.expand_path(p, @root.path)
+ each do |path|
+ path = File.expand_path(path, @root.path)
if @glob && File.directory?(path)
- Dir.chdir(path) do
- result.concat(Dir.glob(@glob).map { |file| File.join path, file }.sort)
- end
+ result.concat files_in(path)
else
result << path
end
@@ -222,6 +221,17 @@ module Rails
end
alias to_a expanded
+
+ private
+
+ def files_in(path)
+ Dir.chdir(path) do
+ files = Dir.glob(@glob)
+ files -= @exclude if @exclude
+ files.map! { |file| File.join(path, file) }
+ files.sort
+ end
+ end
end
end
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 6fdb4648c2..4e4a504c97 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -30,6 +30,9 @@ Gem::Specification.new do |s|
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/railties/CHANGELOG.md"
}
+ # NOTE: Please read our dependency guidelines before updating versions:
+ # https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves
+
s.add_dependency "activesupport", version
s.add_dependency "actionpack", version
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
index d02100d94c..a952d2466b 100644
--- a/railties/test/application/bin_setup_test.rb
+++ b/railties/test/application/bin_setup_test.rb
@@ -45,6 +45,8 @@ module ApplicationTests
output.sub!(/^Resolving dependencies\.\.\.\n/, "")
# Suppress Bundler platform warnings from output
output.gsub!(/^The dependency .* will be unused .*\.\n/, "")
+ # Ignore warnings such as `Psych.safe_load is deprecated`
+ output.gsub!(/^warning:\s.*\n/, "")
assert_equal(<<~OUTPUT, output)
== Installing dependencies ==
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index fa418f564b..b8a0434432 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -1663,6 +1663,14 @@ module ApplicationTests
assert_kind_of Hash, Rails.application.config.database_configuration
end
+ test "autoload paths do not include asset paths" do
+ app "development"
+ ActiveSupport::Dependencies.autoload_paths.each do |path|
+ assert_not_operator path, :ends_with?, "app/assets"
+ assert_not_operator path, :ends_with?, "app/javascript"
+ end
+ end
+
test "raises with proper error message if no database configuration found" do
FileUtils.rm("#{app_path}/config/database.yml")
err = assert_raises RuntimeError do
diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb
index 6478e06250..ef99365e75 100644
--- a/railties/test/application/rake/multi_dbs_test.rb
+++ b/railties/test/application/rake/multi_dbs_test.rb
@@ -65,6 +65,27 @@ module ApplicationTests
end
end
+ def db_migrate_and_schema_cache_dump
+ Dir.chdir(app_path) do
+ generate_models_for_animals
+ rails "db:migrate"
+ rails "db:schema:cache:dump"
+ assert File.exist?("db/schema_cache.yml")
+ assert File.exist?("db/animals_schema_cache.yml")
+ end
+ end
+
+ def db_migrate_and_schema_cache_dump_and_schema_cache_clear
+ Dir.chdir(app_path) do
+ generate_models_for_animals
+ rails "db:migrate"
+ rails "db:schema:cache:dump"
+ rails "db:schema:cache:clear"
+ assert_not File.exist?("db/schema_cache.yml")
+ assert_not File.exist?("db/animals_schema_cache.yml")
+ end
+ end
+
def db_migrate_and_schema_dump_and_load(format)
Dir.chdir(app_path) do
generate_models_for_animals
@@ -92,7 +113,7 @@ module ApplicationTests
end
end
- def db_migrate_namespaced(namespace, expected_database)
+ def db_migrate_namespaced(namespace)
Dir.chdir(app_path) do
generate_models_for_animals
output = rails("db:migrate:#{namespace}")
@@ -104,7 +125,7 @@ module ApplicationTests
end
end
- def db_migrate_status_namespaced(namespace, expected_database)
+ def db_migrate_status_namespaced(namespace)
Dir.chdir(app_path) do
generate_models_for_animals
output = rails("db:migrate:status:#{namespace}")
@@ -178,7 +199,7 @@ module ApplicationTests
test "db:migrate:namespace works" do
require "#{app_path}/config/environment"
ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
- db_migrate_namespaced db_config.spec_name, db_config.config["database"]
+ db_migrate_namespaced db_config.spec_name
end
end
@@ -190,10 +211,20 @@ module ApplicationTests
test "db:migrate:status:namespace works" do
require "#{app_path}/config/environment"
ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
- db_migrate_namespaced db_config.spec_name, db_config.config["database"]
- db_migrate_status_namespaced db_config.spec_name, db_config.config["database"]
+ db_migrate_namespaced db_config.spec_name
+ db_migrate_status_namespaced db_config.spec_name
end
end
+
+ test "db:schema:cache:dump works on all databases" do
+ require "#{app_path}/config/environment"
+ db_migrate_and_schema_cache_dump
+ end
+
+ test "db:schema:cache:clear works on all databases" do
+ require "#{app_path}/config/environment"
+ db_migrate_and_schema_cache_dump_and_schema_cache_clear
+ end
end
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index a0cdae898b..c38b91ef03 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -189,8 +189,10 @@ module ApplicationTests
rails "generate", "model", "Product"
rails "generate", "model", "Cart"
rails "generate", "scaffold", "LineItems", "product:references", "cart:belongs_to"
- with_rails_env("test") { rails("db:migrate") }
- rails("webpacker:compile")
+ with_rails_env("test") do
+ rails("db:migrate")
+ rails("webpacker:compile")
+ end
output = rails("test")
assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output)
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 5c34b205c9..140703e118 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -562,6 +562,44 @@ module ApplicationTests
assert_no_match "create_table(:users)", output
end
+ def test_run_in_parallel_with_unmarshable_exception
+ file = app_file "test/fail_test.rb", <<-RUBY
+ require "test_helper"
+ class FailTest < ActiveSupport::TestCase
+ class BadError < StandardError
+ def initialize
+ super
+ @proc = ->{ }
+ end
+ end
+
+ test "fail" do
+ raise BadError
+ assert true
+ end
+ end
+ RUBY
+
+ output = run_test_command(file)
+
+ assert_match "DRb::DRbRemoteError: FailTest::BadError", output
+ assert_match "1 runs, 0 assertions, 0 failures, 1 errors", output
+ end
+
+ def test_run_in_parallel_with_unknown_object
+ create_scaffold
+ app_file "config/environments/test.rb", <<-RUBY
+ Rails.application.configure do
+ config.action_controller.allow_forgery_protection = true
+ config.action_dispatch.show_exceptions = false
+ end
+ RUBY
+
+ output = run_test_command("-n test_should_create_user")
+
+ assert_match "ActionController::InvalidAuthenticityToken", output
+ end
+
def test_raise_error_when_specified_file_does_not_exist
error = capture(:stderr) { run_test_command("test/not_exists.rb", stderr: true) }
assert_match(%r{cannot load such file.+test/not_exists\.rb}, error)
@@ -703,6 +741,31 @@ module ApplicationTests
end
end
+ def test_reset_sessions_on_failed_system_test_screenshot
+ app_file "test/system/reset_sessions_on_failed_system_test_screenshot_test.rb", <<~RUBY
+ require "application_system_test_case"
+
+ class ResetSessionsOnFailedSystemTestScreenshotTest < ApplicationSystemTestCase
+ ActionDispatch::SystemTestCase.class_eval do
+ def take_failed_screenshot
+ raise Capybara::CapybaraError
+ end
+ end
+
+ Capybara.instance_eval do
+ def reset_sessions!
+ puts "Capybara.reset_sessions! called"
+ end
+ end
+
+ test "dummy" do
+ end
+ end
+ RUBY
+ output = run_test_command("test/system/reset_sessions_on_failed_system_test_screenshot_test.rb")
+ assert_match "Capybara.reset_sessions! called", output
+ end
+
def test_system_tests_are_not_run_with_the_default_test_command
app_file "test/system/dummy_test.rb", <<-RUBY
require "application_system_test_case"
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 6d53230eab..a2b35124c5 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -308,7 +308,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rails_should_run_rake_command_with_default_env
+ def test_rake_should_run_rake_command_with_default_env
assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rake, "log:clear"
@@ -316,13 +316,13 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- def test_rails_with_env_option_should_run_rake_command_in_env
+ def test_rake_with_env_option_should_run_rake_command_in_env
assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=production", verbose: false]) do
action :rake, "log:clear", env: "production"
end
end
- test "rails command with RAILS_ENV variable should run rake command in env" do
+ test "rake with RAILS_ENV variable should run rake command in env" do
assert_called_with(generator, :run, ["rake log:clear RAILS_ENV=production", verbose: false]) do
with_rails_env "production" do
action :rake, "log:clear"
@@ -338,7 +338,7 @@ class ActionsTest < Rails::Generators::TestCase
end
end
- test "rails command with sudo option should run rake command with sudo" do
+ test "rake with sudo option should run rake command with sudo" do
assert_called_with(generator, :run, ["sudo rake log:clear RAILS_ENV=development", verbose: false]) do
with_rails_env nil do
action :rake, "log:clear", sudo: true
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index bb3aaa9d14..32d00f3157 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -230,6 +230,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_equal "false\n", output
end
+ def test_csp_initializer_include_connect_src_example
+ run_generator
+
+ assert_file "config/initializers/content_security_policy.rb" do |content|
+ assert_match(/# policy\.connect_src/, content)
+ end
+ end
+
def test_app_update_keep_the_cookie_serializer_if_it_is_already_configured
app_root = File.join(destination_root, "myapp")
run_generator [app_root]
@@ -807,6 +815,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
assert_no_gem "webpacker"
+ assert_file "config/initializers/content_security_policy.rb" do |content|
+ assert_no_match(/policy\.connect_src/, content)
+ end
end
def test_webpack_option_with_js_framework
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index 878a238f8d..6ab68f8333 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -50,6 +50,11 @@ class InfoControllerTest < ActionController::TestCase
assert_select "table"
end
+ test "info controller renders json with properties" do
+ get :properties, format: :json
+ assert_equal Rails::Info.to_json, response.body
+ end
+
test "info controller renders with routes" do
get :routes
assert_response :success
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 50522c1be6..d167a86e56 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -43,6 +43,18 @@ class InfoTest < ActiveSupport::TestCase
end
end
+ def test_json_includes_middleware
+ Rails::Info.module_eval do
+ property "Middleware", ["Rack::Lock", "Rack::Static"]
+ end
+
+ hash = JSON.parse(Rails::Info.to_json)
+ assert_includes hash.keys, "Middleware"
+ properties.value_for("Middleware").each do |value|
+ assert_includes hash["Middleware"], value
+ end
+ end
+
private
def properties
Rails::Info.properties