aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/no-response.yml9
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml25
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock7
-rw-r--r--actioncable/.babelrc8
-rw-r--r--actioncable/.eslintrc19
-rw-r--r--actioncable/.gitignore1
-rw-r--r--actioncable/Rakefile43
-rw-r--r--actioncable/actioncable.gemspec2
-rw-r--r--actioncable/app/assets/javascripts/action_cable.coffee.erb38
-rw-r--r--actioncable/app/assets/javascripts/action_cable.js475
-rw-r--r--actioncable/app/assets/javascripts/action_cable/connection.coffee116
-rw-r--r--actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee95
-rw-r--r--actioncable/app/assets/javascripts/action_cable/consumer.coffee46
-rw-r--r--actioncable/app/assets/javascripts/action_cable/subscription.coffee72
-rw-r--r--actioncable/app/assets/javascripts/action_cable/subscriptions.coffee66
-rw-r--r--actioncable/app/javascript/action_cable/connection.js156
-rw-r--r--actioncable/app/javascript/action_cable/connection_monitor.js125
-rw-r--r--actioncable/app/javascript/action_cable/consumer.js53
-rw-r--r--actioncable/app/javascript/action_cable/index.js58
-rw-r--r--actioncable/app/javascript/action_cable/subscription.js89
-rw-r--r--actioncable/app/javascript/action_cable/subscriptions.js86
-rw-r--r--actioncable/blade.yml6
-rw-r--r--actioncable/lib/action_cable/connection/authorization.rb2
-rw-r--r--actioncable/lib/rails/generators/channel/templates/javascript/index.js.tt4
-rw-r--r--actioncable/package.json23
-rw-r--r--actioncable/rollup.config.js28
-rw-r--r--actioncable/yarn.lock1928
-rw-r--r--actionmailer/test/base_test.rb8
-rw-r--r--actionpack/CHANGELOG.md31
-rw-r--r--actionpack/lib/action_controller/api.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb7
-rw-r--r--actionpack/lib/action_controller/metal/live.rb48
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/content_security_policy.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb21
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb8
-rw-r--r--actionpack/test/dispatch/content_security_policy_test.rb14
-rw-r--r--actionview/CHANGELOG.md12
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb3
-rw-r--r--actionview/lib/action_view/log_subscriber.rb6
-rw-r--r--actionview/test/activerecord/controller_runtime_test.rb10
-rw-r--r--actionview/test/template/log_subscriber_test.rb4
-rw-r--r--activejob/CHANGELOG.md9
-rw-r--r--activejob/lib/active_job/arguments.rb5
-rw-r--r--activejob/lib/active_job/logging.rb12
-rw-r--r--activejob/lib/active_job/queue_adapters/async_adapter.rb2
-rw-r--r--activejob/lib/active_job/test_helper.rb12
-rw-r--r--activejob/test/cases/argument_serialization_test.rb5
-rw-r--r--activejob/test/cases/logging_test.rb10
-rw-r--r--activejob/test/cases/test_helper_test.rb24
-rw-r--r--activejob/test/support/integration/adapters/backburner.rb3
-rw-r--r--activejob/test/support/integration/adapters/que.rb3
-rw-r--r--activejob/test/support/integration/adapters/queue_classic.rb3
-rw-r--r--activejob/test/support/integration/adapters/sneakers.rb16
-rw-r--r--activemodel/lib/active_model/attribute.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb38
-rw-r--r--activemodel/lib/active_model/attributes.rb32
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/type/date_time.rb6
-rw-r--r--activemodel/test/cases/type/date_time_test.rb11
-rw-r--r--activerecord/CHANGELOG.md113
-rw-r--r--activerecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb43
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb50
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb29
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb45
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_handling.rb104
-rw-r--r--activerecord/lib/active_record/core.rb70
-rw-r--r--activerecord/lib/active_record/database_configurations.rb2
-rw-r--r--activerecord/lib/active_record/enum.rb11
-rw-r--r--activerecord/lib/active_record/fixture_set/model_metadata.rb11
-rw-r--r--activerecord/lib/active_record/fixture_set/table_row.rb76
-rw-r--r--activerecord/lib/active_record/fixture_set/table_rows.rb96
-rw-r--r--activerecord/lib/active_record/fixtures.rb11
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb6
-rw-r--r--activerecord/lib/active_record/model_schema.rb6
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb13
-rw-r--r--activerecord/lib/active_record/railties/databases.rake21
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb30
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb5
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb18
-rw-r--r--activerecord/lib/active_record/scoping/named.rb2
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb15
-rw-r--r--activerecord/lib/arel/nodes/bind_param.rb4
-rw-r--r--activerecord/lib/arel/visitors/mysql.rb29
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb63
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb24
-rw-r--r--activerecord/test/cases/associations/eager_test.rb28
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb43
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb23
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb6
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb6
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb14
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb232
-rw-r--r--activerecord/test/cases/defaults_test.rb7
-rw-r--r--activerecord/test/cases/enum_test.rb11
-rw-r--r--activerecord/test/cases/filter_attributes_test.rb56
-rw-r--r--activerecord/test/cases/finder_test.rb22
-rw-r--r--activerecord/test/cases/fixtures_test.rb22
-rw-r--r--activerecord/test/cases/helper.rb9
-rw-r--r--activerecord/test/cases/null_relation_test.rb1
-rw-r--r--activerecord/test/cases/relations_test.rb10
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb7
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb24
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb2
-rw-r--r--activerecord/test/config.example.yml1
-rw-r--r--activerecord/test/fixtures/citations.yml1
-rw-r--r--activerecord/test/models/account.rb13
-rw-r--r--activerecord/test/models/country.rb2
-rw-r--r--activerecord/test/models/parrot.rb6
-rw-r--r--activerecord/test/models/post.rb8
-rw-r--r--activerecord/test/models/treaty.rb2
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb3
-rw-r--r--activerecord/test/schema/schema.rb109
-rw-r--r--activestorage/app/models/active_storage/attachment.rb5
-rw-r--r--activesupport/CHANGELOG.md37
-rw-r--r--activesupport/lib/active_support/cache.rb13
-rw-r--r--activesupport/lib/active_support/callbacks.rb3
-rw-r--r--activesupport/lib/active_support/configurable.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb244
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb60
-rw-r--r--activesupport/lib/active_support/current_attributes.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb6
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb1
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb1
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb6
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb76
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb60
-rw-r--r--activesupport/lib/active_support/notifications.rb22
-rw-r--r--activesupport/lib/active_support/number_helper.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb2
-rw-r--r--activesupport/lib/active_support/parameter_filter.rb62
-rw-r--r--activesupport/lib/active_support/reloader.rb1
-rw-r--r--activesupport/lib/active_support/subscriber.rb8
-rw-r--r--activesupport/lib/active_support/testing/file_fixtures.rb2
-rw-r--r--activesupport/lib/active_support/testing/method_call_assertions.rb2
-rw-r--r--activesupport/lib/active_support/testing/parallelization.rb1
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb5
-rw-r--r--activesupport/test/autoloading_fixtures/module_folder/nested_with_require.rb8
-rw-r--r--activesupport/test/autoloading_fixtures/nested_with_require_parent.rb5
-rw-r--r--activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb2
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb4
-rw-r--r--activesupport/test/dependencies/module_folder/lib_class.rb8
-rw-r--r--activesupport/test/dependencies_test.rb26
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb7
-rw-r--r--activesupport/test/json/encoding_test.rb10
-rw-r--r--activesupport/test/multibyte_chars_test.rb111
-rw-r--r--activesupport/test/multibyte_conformance_test.rb98
-rw-r--r--activesupport/test/multibyte_grapheme_break_conformance_test.rb10
-rw-r--r--activesupport/test/multibyte_normalization_conformance_test.rb98
-rw-r--r--activesupport/test/parameter_filter_test.rb54
-rw-r--r--activesupport/test/testing/method_call_assertions_test.rb12
-rw-r--r--guides/assets/images/rails_guides_logo_1x.pngbin0 -> 2340 bytes
-rw-r--r--guides/assets/images/rails_guides_logo_2x.pngbin0 -> 3107 bytes
-rw-r--r--guides/assets/stylesheets/main.css60
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb10
-rw-r--r--guides/bug_report_templates/action_controller_master.rb7
-rw-r--r--guides/bug_report_templates/active_job_gem.rb10
-rw-r--r--guides/bug_report_templates/active_job_master.rb10
-rw-r--r--guides/bug_report_templates/active_record_gem.rb10
-rw-r--r--guides/bug_report_templates/active_record_master.rb7
-rw-r--r--guides/bug_report_templates/active_record_migrations_gem.rb10
-rw-r--r--guides/bug_report_templates/active_record_migrations_master.rb10
-rw-r--r--guides/bug_report_templates/benchmark.rb7
-rw-r--r--guides/bug_report_templates/generic_gem.rb10
-rw-r--r--guides/bug_report_templates/generic_master.rb7
-rw-r--r--guides/source/5_1_release_notes.md2
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailer_basics.md29
-rw-r--r--guides/source/active_record_basics.md2
-rw-r--r--guides/source/active_support_instrumentation.md5
-rw-r--r--guides/source/asset_pipeline.md62
-rw-r--r--guides/source/association_basics.md31
-rw-r--r--guides/source/configuring.md3
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md13
-rw-r--r--guides/source/debugging_rails_applications.md49
-rw-r--r--guides/source/documents.yaml32
-rw-r--r--guides/source/generators.md6
-rw-r--r--guides/source/i18n.md64
-rw-r--r--guides/source/layout.html.erb18
-rw-r--r--guides/source/layouts_and_rendering.md2
-rw-r--r--guides/source/testing.md2
-rw-r--r--railties/CHANGELOG.md43
-rw-r--r--railties/lib/rails/app_updater.rb2
-rw-r--r--railties/lib/rails/application/configuration.rb2
-rw-r--r--railties/lib/rails/generators.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb9
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/javascript/channels/index.js4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore.tt9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/package.json.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb3
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/gitignore.tt2
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb2
-rw-r--r--railties/test/application/configuration_test.rb2
-rw-r--r--railties/test/application/rake/multi_dbs_test.rb70
-rw-r--r--railties/test/generators/app_generator_test.rb47
-rw-r--r--railties/test/generators/scaffold_generator_test.rb16
-rw-r--r--railties/test/generators/shared_generator_tests.rb35
-rw-r--r--railties/test/isolation/abstract_unit.rb3
234 files changed, 5629 insertions, 1910 deletions
diff --git a/.github/no-response.yml b/.github/no-response.yml
index 7193eaa3b2..326fa84b7e 100644
--- a/.github/no-response.yml
+++ b/.github/no-response.yml
@@ -6,8 +6,7 @@ daysUntilClose: 14
responseRequiredLabel: more-information-needed
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
- This issue has been automatically closed because there has been no response
- to our request for more information from the original author. With only the
- information that is currently in the issue, we don't have enough information
- to take action. Please reach out if you have or find the answers we need so
- that we can investigate further.
+ This issue has been automatically closed because there has been no follow-up
+ response from the original author. We currently don't have enough
+ information in order to take action. Please reach out if you have any additional
+ information that will help us move this issue forward.
diff --git a/.gitignore b/.gitignore
index d44875ed9a..35440cb54f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,5 @@ node_modules/
package-lock.json
pkg/
/tmp/
-/yarn.lock \ No newline at end of file
+/yarn-error.log
+/yarn.lock
diff --git a/.travis.yml b/.travis.yml
index a4f5fa045d..3db4e9cd94 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,13 +31,11 @@ addons:
- postgresql-10
- postgresql-client-10
-bundler_args: --without test --jobs 3 --retry 3
+bundler_args: --jobs 3 --retry 3
before_install:
- "rm ${BUNDLE_GEMFILE}.lock"
- "travis_retry gem update --system"
- "travis_retry gem install bundler"
- - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
- - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
- "[[ -z $encrypted_0fb9444d0374_key && -z $encrypted_0fb9444d0374_iv ]] || openssl aes-256-cbc -K $encrypted_0fb9444d0374_key -iv $encrypted_0fb9444d0374_iv -in activestorage/test/service/configurations.yml.enc -out activestorage/test/service/configurations.yml -d"
- "[[ $GEM != 'av:ujs' ]] || nvm install node"
- "[[ $GEM != 'av:ujs' ]] || node --version"
@@ -101,23 +99,44 @@ matrix:
- rvm: 2.5.1
env: "GEM=av:ujs"
- rvm: 2.4.4
+ sudo: required
env: "GEM=aj:integration"
services:
- memcached
- redis-server
- rabbitmq
+ before_install:
+ - sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf
+ - "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
+ - "sudo service postgresql restart 10"
+ - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
+ - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
- rvm: 2.5.1
+ sudo: required
env: "GEM=aj:integration"
services:
- memcached
- redis-server
- rabbitmq
+ before_install:
+ - sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf
+ - "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
+ - "sudo service postgresql restart 10"
+ - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
+ - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
- rvm: ruby-head
+ sudo: required
env: "GEM=aj:integration"
services:
- memcached
- redis-server
- rabbitmq
+ before_install:
+ - sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf
+ - "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf"
+ - "sudo service postgresql restart 10"
+ - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
+ - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
- rvm: 2.4.4
env: "GEM=ar:mysql2"
sudo: required
diff --git a/Gemfile b/Gemfile
index c630f95482..fb15bf127e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -99,6 +99,7 @@ instance_eval File.read local_gemfile if File.exist? local_gemfile
group :test do
gem "minitest-bisect"
+ gem "minitest-retry"
platforms :mri do
gem "stackprof"
diff --git a/Gemfile.lock b/Gemfile.lock
index b4f46698bc..1321436814 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -306,7 +306,7 @@ GEM
loofah (2.2.2)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
- mail (2.7.0)
+ mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
@@ -323,6 +323,8 @@ GEM
minitest-bisect (1.4.0)
minitest-server (~> 1.0)
path_expander (~> 1.0)
+ minitest-retry (0.1.9)
+ minitest (>= 5.0)
minitest-server (1.0.5)
minitest (~> 5.0)
mono_logger (1.1.0)
@@ -538,6 +540,7 @@ DEPENDENCIES
libxml-ruby
listen (>= 3.0.5, < 3.2)
minitest-bisect
+ minitest-retry
mysql2 (>= 0.4.10)
nokogiri (>= 1.8.1)
pg (>= 0.18.0)
@@ -576,4 +579,4 @@ DEPENDENCIES
websocket-client-simple!
BUNDLED WITH
- 1.16.5
+ 1.16.6
diff --git a/actioncable/.babelrc b/actioncable/.babelrc
new file mode 100644
index 0000000000..4f0c469c60
--- /dev/null
+++ b/actioncable/.babelrc
@@ -0,0 +1,8 @@
+{
+ "presets": [
+ ["env", { "modules": false, "loose": true } ]
+ ],
+ "plugins": [
+ "external-helpers"
+ ]
+}
diff --git a/actioncable/.eslintrc b/actioncable/.eslintrc
new file mode 100644
index 0000000000..3d9ecd4bce
--- /dev/null
+++ b/actioncable/.eslintrc
@@ -0,0 +1,19 @@
+{
+ "extends": "eslint:recommended",
+ "rules": {
+ "semi": ["error", "never"],
+ "quotes": ["error", "double"],
+ "no-unused-vars": ["error", { "vars": "all", "args": "none" }]
+ },
+ "plugins": [
+ "import"
+ ],
+ "env": {
+ "browser": true,
+ "es6": true
+ },
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "module"
+ }
+}
diff --git a/actioncable/.gitignore b/actioncable/.gitignore
index f514e58c16..5d5c867f2d 100644
--- a/actioncable/.gitignore
+++ b/actioncable/.gitignore
@@ -1,2 +1,3 @@
+/app/javascript/action_cable/internal.js
/lib/assets/compiled/
/tmp/
diff --git a/actioncable/Rakefile b/actioncable/Rakefile
index fb75d14363..52c86eb5d7 100644
--- a/actioncable/Rakefile
+++ b/actioncable/Rakefile
@@ -7,7 +7,7 @@ require "action_cable"
task default: :test
-task package: %w( assets:compile assets:verify )
+task :package
Rake::TestTask.new do |t|
t.libs << "test"
@@ -35,42 +35,13 @@ namespace :test do
end
namespace :assets do
- desc "Compile Action Cable assets"
- task :compile do
- require "blade"
- require "sprockets"
- require "sprockets/export"
- Blade.build
- end
-
- desc "Verify compiled Action Cable assets"
- task :verify do
- file = "lib/assets/compiled/action_cable.js"
- pathname = Pathname.new("#{__dir__}/#{file}")
-
- print "[verify] #{file} exists "
- if pathname.exist?
- puts "[OK]"
- else
- $stderr.puts "[FAIL]"
- fail
- end
+ desc "Generate ActionCable::INTERNAL JS module"
+ task :codegen do
+ require "json"
+ require "action_cable"
- print "[verify] #{file} is a UMD module "
- if /module\.exports.*define\.amd/m.match?(pathname.read)
- puts "[OK]"
- else
- $stderr.puts "[FAIL]"
- fail
- end
-
- print "[verify] #{__dir__} can be required as a module "
- _, stderr, status = Open3.capture3("node", "--print", "window = {}; require('#{__dir__}');")
- if status.success?
- puts "[OK]"
- else
- $stderr.puts "[FAIL]\n#{stderr}"
- fail
+ File.open(File.join(__dir__, "app/javascript/action_cable/internal.js").to_s, "w+") do |file|
+ file.write("export default #{JSON.generate(ActionCable::INTERNAL)}")
end
end
end
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
index d946d0797f..137fa64431 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
s.email = ["pratiknaik@gmail.com", "david@loudthinking.com"]
s.homepage = "http://rubyonrails.org"
- s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*"]
+ s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*", "app/assets/javascripts/action_cable.js"]
s.require_path = "lib"
s.metadata = {
diff --git a/actioncable/app/assets/javascripts/action_cable.coffee.erb b/actioncable/app/assets/javascripts/action_cable.coffee.erb
deleted file mode 100644
index e0758dae72..0000000000
--- a/actioncable/app/assets/javascripts/action_cable.coffee.erb
+++ /dev/null
@@ -1,38 +0,0 @@
-#= export ActionCable
-#= require_self
-#= require ./action_cable/consumer
-
-@ActionCable =
- INTERNAL: <%= ActionCable::INTERNAL.to_json %>
- WebSocket: window.WebSocket
- logger: window.console
-
- createConsumer: (url) ->
- url ?= @getConfig("url") ? @INTERNAL.default_mount_path
- new ActionCable.Consumer @createWebSocketURL(url)
-
- getConfig: (name) ->
- element = document.head.querySelector("meta[name='action-cable-#{name}']")
- element?.getAttribute("content")
-
- createWebSocketURL: (url) ->
- if url and not /^wss?:/i.test(url)
- a = document.createElement("a")
- a.href = url
- # Fix populating Location properties in IE. Otherwise, protocol will be blank.
- a.href = a.href
- a.protocol = a.protocol.replace("http", "ws")
- a.href
- else
- url
-
- startDebugging: ->
- @debugging = true
-
- stopDebugging: ->
- @debugging = null
-
- log: (messages...) ->
- if @debugging
- messages.push(Date.now())
- @logger.log("[ActionCable]", messages...)
diff --git a/actioncable/app/assets/javascripts/action_cable.js b/actioncable/app/assets/javascripts/action_cable.js
new file mode 100644
index 0000000000..df60b1fc24
--- /dev/null
+++ b/actioncable/app/assets/javascripts/action_cable.js
@@ -0,0 +1,475 @@
+(function(global, factory) {
+ typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : global.ActionCable = factory();
+})(this, function() {
+ "use strict";
+ var INTERNAL = {
+ message_types: {
+ welcome: "welcome",
+ ping: "ping",
+ confirmation: "confirm_subscription",
+ rejection: "reject_subscription"
+ },
+ default_mount_path: "/cable",
+ protocols: [ "actioncable-v1-json", "actioncable-unsupported" ]
+ };
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function(obj) {
+ return typeof obj;
+ } : function(obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+ };
+ var classCallCheck = function(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ };
+ var message_types = INTERNAL.message_types, protocols = INTERNAL.protocols;
+ var supportedProtocols = protocols.slice(0, protocols.length - 1);
+ var indexOf = [].indexOf;
+ var Connection = function() {
+ function Connection(consumer) {
+ classCallCheck(this, Connection);
+ this.open = this.open.bind(this);
+ this.consumer = consumer;
+ this.subscriptions = this.consumer.subscriptions;
+ this.monitor = new ActionCable.ConnectionMonitor(this);
+ this.disconnected = true;
+ }
+ Connection.prototype.send = function send(data) {
+ if (this.isOpen()) {
+ this.webSocket.send(JSON.stringify(data));
+ return true;
+ } else {
+ return false;
+ }
+ };
+ Connection.prototype.open = function open() {
+ if (this.isActive()) {
+ ActionCable.log("Attempted to open WebSocket, but existing socket is " + this.getState());
+ return false;
+ } else {
+ ActionCable.log("Opening WebSocket, current state is " + this.getState() + ", subprotocols: " + protocols);
+ if (this.webSocket) {
+ this.uninstallEventHandlers();
+ }
+ this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols);
+ this.installEventHandlers();
+ this.monitor.start();
+ return true;
+ }
+ };
+ Connection.prototype.close = function close() {
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
+ allowReconnect: true
+ }, allowReconnect = _ref.allowReconnect;
+ if (!allowReconnect) {
+ this.monitor.stop();
+ }
+ if (this.isActive()) {
+ return this.webSocket ? this.webSocket.close() : undefined;
+ }
+ };
+ Connection.prototype.reopen = function reopen() {
+ ActionCable.log("Reopening WebSocket, current state is " + this.getState());
+ if (this.isActive()) {
+ try {
+ return this.close();
+ } catch (error) {
+ ActionCable.log("Failed to reopen WebSocket", error);
+ } finally {
+ ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
+ setTimeout(this.open, this.constructor.reopenDelay);
+ }
+ } else {
+ return this.open();
+ }
+ };
+ Connection.prototype.getProtocol = function getProtocol() {
+ return this.webSocket ? this.webSocket.protocol : undefined;
+ };
+ Connection.prototype.isOpen = function isOpen() {
+ return this.isState("open");
+ };
+ Connection.prototype.isActive = function isActive() {
+ return this.isState("open", "connecting");
+ };
+ Connection.prototype.isProtocolSupported = function isProtocolSupported() {
+ return indexOf.call(supportedProtocols, this.getProtocol()) >= 0;
+ };
+ Connection.prototype.isState = function isState() {
+ for (var _len = arguments.length, states = Array(_len), _key = 0; _key < _len; _key++) {
+ states[_key] = arguments[_key];
+ }
+ return indexOf.call(states, this.getState()) >= 0;
+ };
+ Connection.prototype.getState = function getState() {
+ if (this.webSocket) {
+ for (var state in WebSocket) {
+ if (WebSocket[state] === this.webSocket.readyState) {
+ return state.toLowerCase();
+ }
+ }
+ }
+ return null;
+ };
+ Connection.prototype.installEventHandlers = function installEventHandlers() {
+ for (var eventName in this.events) {
+ var handler = this.events[eventName].bind(this);
+ this.webSocket["on" + eventName] = handler;
+ }
+ };
+ Connection.prototype.uninstallEventHandlers = function uninstallEventHandlers() {
+ for (var eventName in this.events) {
+ this.webSocket["on" + eventName] = function() {};
+ }
+ };
+ return Connection;
+ }();
+ Connection.reopenDelay = 500;
+ Connection.prototype.events = {
+ message: function message(event) {
+ if (!this.isProtocolSupported()) {
+ return;
+ }
+ var _JSON$parse = JSON.parse(event.data), identifier = _JSON$parse.identifier, message = _JSON$parse.message, type = _JSON$parse.type;
+ switch (type) {
+ case message_types.welcome:
+ this.monitor.recordConnect();
+ return this.subscriptions.reload();
+
+ case message_types.ping:
+ return this.monitor.recordPing();
+
+ case message_types.confirmation:
+ return this.subscriptions.notify(identifier, "connected");
+
+ case message_types.rejection:
+ return this.subscriptions.reject(identifier);
+
+ default:
+ return this.subscriptions.notify(identifier, "received", message);
+ }
+ },
+ open: function open() {
+ ActionCable.log("WebSocket onopen event, using '" + this.getProtocol() + "' subprotocol");
+ this.disconnected = false;
+ if (!this.isProtocolSupported()) {
+ ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
+ return this.close({
+ allowReconnect: false
+ });
+ }
+ },
+ close: function close(event) {
+ ActionCable.log("WebSocket onclose event");
+ if (this.disconnected) {
+ return;
+ }
+ this.disconnected = true;
+ this.monitor.recordDisconnect();
+ return this.subscriptions.notifyAll("disconnected", {
+ willAttemptReconnect: this.monitor.isRunning()
+ });
+ },
+ error: function error() {
+ ActionCable.log("WebSocket onerror event");
+ }
+ };
+ var now = function now() {
+ return new Date().getTime();
+ };
+ var secondsSince = function secondsSince(time) {
+ return (now() - time) / 1e3;
+ };
+ var clamp = function clamp(number, min, max) {
+ return Math.max(min, Math.min(max, number));
+ };
+ var ConnectionMonitor = function() {
+ function ConnectionMonitor(connection) {
+ classCallCheck(this, ConnectionMonitor);
+ this.visibilityDidChange = this.visibilityDidChange.bind(this);
+ this.connection = connection;
+ this.reconnectAttempts = 0;
+ }
+ ConnectionMonitor.prototype.start = function start() {
+ if (!this.isRunning()) {
+ this.startedAt = now();
+ delete this.stoppedAt;
+ this.startPolling();
+ document.addEventListener("visibilitychange", this.visibilityDidChange);
+ ActionCable.log("ConnectionMonitor started. pollInterval = " + this.getPollInterval() + " ms");
+ }
+ };
+ ConnectionMonitor.prototype.stop = function stop() {
+ if (this.isRunning()) {
+ this.stoppedAt = now();
+ this.stopPolling();
+ document.removeEventListener("visibilitychange", this.visibilityDidChange);
+ ActionCable.log("ConnectionMonitor stopped");
+ }
+ };
+ ConnectionMonitor.prototype.isRunning = function isRunning() {
+ return this.startedAt && !this.stoppedAt;
+ };
+ ConnectionMonitor.prototype.recordPing = function recordPing() {
+ this.pingedAt = now();
+ };
+ ConnectionMonitor.prototype.recordConnect = function recordConnect() {
+ this.reconnectAttempts = 0;
+ this.recordPing();
+ delete this.disconnectedAt;
+ ActionCable.log("ConnectionMonitor recorded connect");
+ };
+ ConnectionMonitor.prototype.recordDisconnect = function recordDisconnect() {
+ this.disconnectedAt = now();
+ ActionCable.log("ConnectionMonitor recorded disconnect");
+ };
+ ConnectionMonitor.prototype.startPolling = function startPolling() {
+ this.stopPolling();
+ this.poll();
+ };
+ ConnectionMonitor.prototype.stopPolling = function stopPolling() {
+ clearTimeout(this.pollTimeout);
+ };
+ ConnectionMonitor.prototype.poll = function poll() {
+ var _this = this;
+ this.pollTimeout = setTimeout(function() {
+ _this.reconnectIfStale();
+ _this.poll();
+ }, 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);
+ return Math.round(clamp(interval, min, max) * 1e3);
+ };
+ ConnectionMonitor.prototype.reconnectIfStale = function reconnectIfStale() {
+ if (this.connectionIsStale()) {
+ ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + this.getPollInterval() + " ms, time disconnected = " + secondsSince(this.disconnectedAt) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
+ this.reconnectAttempts++;
+ if (this.disconnectedRecently()) {
+ ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
+ } else {
+ ActionCable.log("ConnectionMonitor reopening");
+ this.connection.reopen();
+ }
+ }
+ };
+ ConnectionMonitor.prototype.connectionIsStale = function connectionIsStale() {
+ return secondsSince(this.pingedAt ? this.pingedAt : this.startedAt) > this.constructor.staleThreshold;
+ };
+ ConnectionMonitor.prototype.disconnectedRecently = function disconnectedRecently() {
+ return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
+ };
+ ConnectionMonitor.prototype.visibilityDidChange = function visibilityDidChange() {
+ var _this2 = this;
+ if (document.visibilityState === "visible") {
+ setTimeout(function() {
+ if (_this2.connectionIsStale() || !_this2.connection.isOpen()) {
+ ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
+ _this2.connection.reopen();
+ }
+ }, 200);
+ }
+ };
+ return ConnectionMonitor;
+ }();
+ ConnectionMonitor.pollInterval = {
+ min: 3,
+ max: 30
+ };
+ ConnectionMonitor.staleThreshold = 6;
+ var Consumer = function() {
+ function Consumer(url) {
+ classCallCheck(this, Consumer);
+ this.url = url;
+ this.subscriptions = new ActionCable.Subscriptions(this);
+ this.connection = new ActionCable.Connection(this);
+ }
+ Consumer.prototype.send = function send(data) {
+ return this.connection.send(data);
+ };
+ Consumer.prototype.connect = function connect() {
+ return this.connection.open();
+ };
+ Consumer.prototype.disconnect = function disconnect() {
+ return this.connection.close({
+ allowReconnect: false
+ });
+ };
+ Consumer.prototype.ensureActiveConnection = function ensureActiveConnection() {
+ if (!this.connection.isActive()) {
+ return this.connection.open();
+ }
+ };
+ return Consumer;
+ }();
+ var extend = function extend(object, properties) {
+ if (properties != null) {
+ for (var key in properties) {
+ var value = properties[key];
+ object[key] = value;
+ }
+ }
+ return object;
+ };
+ var Subscription = function() {
+ function Subscription(consumer) {
+ var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+ var mixin = arguments[2];
+ classCallCheck(this, Subscription);
+ this.consumer = consumer;
+ this.identifier = JSON.stringify(params);
+ extend(this, mixin);
+ }
+ Subscription.prototype.perform = function perform(action) {
+ var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+ data.action = action;
+ return this.send(data);
+ };
+ Subscription.prototype.send = function send(data) {
+ return this.consumer.send({
+ command: "message",
+ identifier: this.identifier,
+ data: JSON.stringify(data)
+ });
+ };
+ Subscription.prototype.unsubscribe = function unsubscribe() {
+ return this.consumer.subscriptions.remove(this);
+ };
+ return Subscription;
+ }();
+ var Subscriptions = function() {
+ function Subscriptions(consumer) {
+ classCallCheck(this, Subscriptions);
+ this.consumer = consumer;
+ this.subscriptions = [];
+ }
+ Subscriptions.prototype.create = function create(channelName, mixin) {
+ var channel = channelName;
+ var params = (typeof channel === "undefined" ? "undefined" : _typeof(channel)) === "object" ? channel : {
+ channel: channel
+ };
+ var subscription = new ActionCable.Subscription(this.consumer, params, mixin);
+ return this.add(subscription);
+ };
+ Subscriptions.prototype.add = function add(subscription) {
+ this.subscriptions.push(subscription);
+ this.consumer.ensureActiveConnection();
+ this.notify(subscription, "initialized");
+ this.sendCommand(subscription, "subscribe");
+ return subscription;
+ };
+ Subscriptions.prototype.remove = function remove(subscription) {
+ this.forget(subscription);
+ if (!this.findAll(subscription.identifier).length) {
+ this.sendCommand(subscription, "unsubscribe");
+ }
+ return subscription;
+ };
+ Subscriptions.prototype.reject = function reject(identifier) {
+ var _this = this;
+ return this.findAll(identifier).map(function(subscription) {
+ _this.forget(subscription);
+ _this.notify(subscription, "rejected");
+ return subscription;
+ });
+ };
+ Subscriptions.prototype.forget = function forget(subscription) {
+ this.subscriptions = this.subscriptions.filter(function(s) {
+ return s !== subscription;
+ });
+ return subscription;
+ };
+ Subscriptions.prototype.findAll = function findAll(identifier) {
+ return this.subscriptions.filter(function(s) {
+ return s.identifier === identifier;
+ });
+ };
+ Subscriptions.prototype.reload = function reload() {
+ var _this2 = this;
+ return this.subscriptions.map(function(subscription) {
+ return _this2.sendCommand(subscription, "subscribe");
+ });
+ };
+ Subscriptions.prototype.notifyAll = function notifyAll(callbackName) {
+ var _this3 = this;
+ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ args[_key - 1] = arguments[_key];
+ }
+ return this.subscriptions.map(function(subscription) {
+ return _this3.notify.apply(_this3, [ subscription, callbackName ].concat(args));
+ });
+ };
+ Subscriptions.prototype.notify = function notify(subscription, callbackName) {
+ for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
+ args[_key2 - 2] = arguments[_key2];
+ }
+ var subscriptions = void 0;
+ if (typeof subscription === "string") {
+ subscriptions = this.findAll(subscription);
+ } else {
+ subscriptions = [ subscription ];
+ }
+ return subscriptions.map(function(subscription) {
+ return typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : undefined;
+ });
+ };
+ Subscriptions.prototype.sendCommand = function sendCommand(subscription, command) {
+ var identifier = subscription.identifier;
+ return this.consumer.send({
+ command: command,
+ identifier: identifier
+ });
+ };
+ return Subscriptions;
+ }();
+ var ActionCable = {
+ Connection: Connection,
+ ConnectionMonitor: ConnectionMonitor,
+ Consumer: Consumer,
+ INTERNAL: INTERNAL,
+ Subscription: Subscription,
+ Subscriptions: Subscriptions,
+ WebSocket: window.WebSocket,
+ logger: window.console,
+ createConsumer: function createConsumer(url) {
+ if (url == null) {
+ var urlConfig = this.getConfig("url");
+ url = urlConfig ? urlConfig : this.INTERNAL.default_mount_path;
+ }
+ return new Consumer(this.createWebSocketURL(url));
+ },
+ getConfig: function getConfig(name) {
+ var element = document.head.querySelector("meta[name='action-cable-" + name + "']");
+ return element ? element.getAttribute("content") : undefined;
+ },
+ createWebSocketURL: function createWebSocketURL(url) {
+ if (url && !/^wss?:/i.test(url)) {
+ var a = document.createElement("a");
+ a.href = url;
+ a.href = a.href;
+ a.protocol = a.protocol.replace("http", "ws");
+ return a.href;
+ } else {
+ return url;
+ }
+ },
+ startDebugging: function startDebugging() {
+ this.debugging = true;
+ },
+ stopDebugging: function stopDebugging() {
+ this.debugging = null;
+ },
+ log: function log() {
+ if (this.debugging) {
+ var _logger;
+ for (var _len = arguments.length, messages = Array(_len), _key = 0; _key < _len; _key++) {
+ messages[_key] = arguments[_key];
+ }
+ messages.push(Date.now());
+ (_logger = this.logger).log.apply(_logger, [ "[ActionCable]" ].concat(messages));
+ }
+ }
+ };
+ return ActionCable;
+});
diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee
deleted file mode 100644
index 7fd68cad2f..0000000000
--- a/actioncable/app/assets/javascripts/action_cable/connection.coffee
+++ /dev/null
@@ -1,116 +0,0 @@
-#= require ./connection_monitor
-
-# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.
-
-{message_types, protocols} = ActionCable.INTERNAL
-[supportedProtocols..., unsupportedProtocol] = protocols
-
-class ActionCable.Connection
- @reopenDelay: 500
-
- constructor: (@consumer) ->
- {@subscriptions} = @consumer
- @monitor = new ActionCable.ConnectionMonitor this
- @disconnected = true
-
- send: (data) ->
- if @isOpen()
- @webSocket.send(JSON.stringify(data))
- true
- else
- false
-
- open: =>
- if @isActive()
- ActionCable.log("Attempted to open WebSocket, but existing socket is #{@getState()}")
- false
- else
- ActionCable.log("Opening WebSocket, current state is #{@getState()}, subprotocols: #{protocols}")
- @uninstallEventHandlers() if @webSocket?
- @webSocket = new ActionCable.WebSocket(@consumer.url, protocols)
- @installEventHandlers()
- @monitor.start()
- true
-
- close: ({allowReconnect} = {allowReconnect: true}) ->
- @monitor.stop() unless allowReconnect
- @webSocket?.close() if @isActive()
-
- reopen: ->
- ActionCable.log("Reopening WebSocket, current state is #{@getState()}")
- if @isActive()
- try
- @close()
- catch error
- ActionCable.log("Failed to reopen WebSocket", error)
- finally
- ActionCable.log("Reopening WebSocket in #{@constructor.reopenDelay}ms")
- setTimeout(@open, @constructor.reopenDelay)
- else
- @open()
-
- getProtocol: ->
- @webSocket?.protocol
-
- isOpen: ->
- @isState("open")
-
- isActive: ->
- @isState("open", "connecting")
-
- # Private
-
- isProtocolSupported: ->
- @getProtocol() in supportedProtocols
-
- isState: (states...) ->
- @getState() in states
-
- getState: ->
- return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState
- null
-
- installEventHandlers: ->
- for eventName of @events
- handler = @events[eventName].bind(this)
- @webSocket["on#{eventName}"] = handler
- return
-
- uninstallEventHandlers: ->
- for eventName of @events
- @webSocket["on#{eventName}"] = ->
- return
-
- events:
- message: (event) ->
- return unless @isProtocolSupported()
- {identifier, message, type} = JSON.parse(event.data)
- switch type
- when message_types.welcome
- @monitor.recordConnect()
- @subscriptions.reload()
- when message_types.ping
- @monitor.recordPing()
- when message_types.confirmation
- @subscriptions.notify(identifier, "connected")
- when message_types.rejection
- @subscriptions.reject(identifier)
- else
- @subscriptions.notify(identifier, "received", message)
-
- open: ->
- ActionCable.log("WebSocket onopen event, using '#{@getProtocol()}' subprotocol")
- @disconnected = false
- if not @isProtocolSupported()
- ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.")
- @close(allowReconnect: false)
-
- close: (event) ->
- ActionCable.log("WebSocket onclose event")
- return if @disconnected
- @disconnected = true
- @monitor.recordDisconnect()
- @subscriptions.notifyAll("disconnected", {willAttemptReconnect: @monitor.isRunning()})
-
- error: ->
- ActionCable.log("WebSocket onerror event")
diff --git a/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee b/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee
deleted file mode 100644
index 0cc675fa94..0000000000
--- a/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee
+++ /dev/null
@@ -1,95 +0,0 @@
-# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting
-# revival reconnections if things go astray. Internal class, not intended for direct user manipulation.
-class ActionCable.ConnectionMonitor
- @pollInterval:
- min: 3
- max: 30
-
- @staleThreshold: 6 # Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
-
- constructor: (@connection) ->
- @reconnectAttempts = 0
-
- start: ->
- unless @isRunning()
- @startedAt = now()
- delete @stoppedAt
- @startPolling()
- document.addEventListener("visibilitychange", @visibilityDidChange)
- ActionCable.log("ConnectionMonitor started. pollInterval = #{@getPollInterval()} ms")
-
- stop: ->
- if @isRunning()
- @stoppedAt = now()
- @stopPolling()
- document.removeEventListener("visibilitychange", @visibilityDidChange)
- ActionCable.log("ConnectionMonitor stopped")
-
- isRunning: ->
- @startedAt? and not @stoppedAt?
-
- recordPing: ->
- @pingedAt = now()
-
- recordConnect: ->
- @reconnectAttempts = 0
- @recordPing()
- delete @disconnectedAt
- ActionCable.log("ConnectionMonitor recorded connect")
-
- recordDisconnect: ->
- @disconnectedAt = now()
- ActionCable.log("ConnectionMonitor recorded disconnect")
-
- # Private
-
- startPolling: ->
- @stopPolling()
- @poll()
-
- stopPolling: ->
- clearTimeout(@pollTimeout)
-
- poll: ->
- @pollTimeout = setTimeout =>
- @reconnectIfStale()
- @poll()
- , @getPollInterval()
-
- getPollInterval: ->
- {min, max} = @constructor.pollInterval
- interval = 5 * Math.log(@reconnectAttempts + 1)
- Math.round(clamp(interval, min, max) * 1000)
-
- reconnectIfStale: ->
- if @connectionIsStale()
- ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = #{@reconnectAttempts}, pollInterval = #{@getPollInterval()} ms, time disconnected = #{secondsSince(@disconnectedAt)} s, stale threshold = #{@constructor.staleThreshold} s")
- @reconnectAttempts++
- if @disconnectedRecently()
- ActionCable.log("ConnectionMonitor skipping reopening recent disconnect")
- else
- ActionCable.log("ConnectionMonitor reopening")
- @connection.reopen()
-
- connectionIsStale: ->
- secondsSince(@pingedAt ? @startedAt) > @constructor.staleThreshold
-
- disconnectedRecently: ->
- @disconnectedAt and secondsSince(@disconnectedAt) < @constructor.staleThreshold
-
- visibilityDidChange: =>
- if document.visibilityState is "visible"
- setTimeout =>
- if @connectionIsStale() or not @connection.isOpen()
- ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = #{document.visibilityState}")
- @connection.reopen()
- , 200
-
- now = ->
- new Date().getTime()
-
- secondsSince = (time) ->
- (now() - time) / 1000
-
- clamp = (number, min, max) ->
- Math.max(min, Math.min(max, number))
diff --git a/actioncable/app/assets/javascripts/action_cable/consumer.coffee b/actioncable/app/assets/javascripts/action_cable/consumer.coffee
deleted file mode 100644
index 3298be717f..0000000000
--- a/actioncable/app/assets/javascripts/action_cable/consumer.coffee
+++ /dev/null
@@ -1,46 +0,0 @@
-#= require ./connection
-#= require ./subscriptions
-#= require ./subscription
-
-# The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established,
-# the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates.
-# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription
-# method.
-#
-# The following example shows how this can be setup:
-#
-# @App = {}
-# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1"
-# App.appearance = App.cable.subscriptions.create "AppearanceChannel"
-#
-# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
-#
-# When a consumer is created, it automatically connects with the server.
-#
-# To disconnect from the server, call
-#
-# App.cable.disconnect()
-#
-# and to restart the connection:
-#
-# App.cable.connect()
-#
-# Any channel subscriptions which existed prior to disconnecting will
-# automatically resubscribe.
-class ActionCable.Consumer
- constructor: (@url) ->
- @subscriptions = new ActionCable.Subscriptions this
- @connection = new ActionCable.Connection this
-
- send: (data) ->
- @connection.send(data)
-
- connect: ->
- @connection.open()
-
- disconnect: ->
- @connection.close(allowReconnect: false)
-
- ensureActiveConnection: ->
- unless @connection.isActive()
- @connection.open()
diff --git a/actioncable/app/assets/javascripts/action_cable/subscription.coffee b/actioncable/app/assets/javascripts/action_cable/subscription.coffee
deleted file mode 100644
index 8e0805a174..0000000000
--- a/actioncable/app/assets/javascripts/action_cable/subscription.coffee
+++ /dev/null
@@ -1,72 +0,0 @@
-# A new subscription is created through the ActionCable.Subscriptions instance available on the consumer.
-# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding
-# Channel instance on the server side.
-#
-# An example demonstrates the basic functionality:
-#
-# App.appearance = App.cable.subscriptions.create "AppearanceChannel",
-# connected: ->
-# # Called once the subscription has been successfully completed
-#
-# disconnected: ({ willAttemptReconnect: boolean }) ->
-# # Called when the client has disconnected with the server.
-# # The object will have an `willAttemptReconnect` property which
-# # says whether the client has the intention of attempting
-# # to reconnect.
-#
-# appear: ->
-# @perform 'appear', appearing_on: @appearingOn()
-#
-# away: ->
-# @perform 'away'
-#
-# appearingOn: ->
-# $('main').data 'appearing-on'
-#
-# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server
-# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away).
-# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter.
-#
-# This is how the server component would look:
-#
-# class AppearanceChannel < ApplicationActionCable::Channel
-# def subscribed
-# current_user.appear
-# end
-#
-# def unsubscribed
-# current_user.disappear
-# end
-#
-# def appear(data)
-# current_user.appear on: data['appearing_on']
-# end
-#
-# def away
-# current_user.away
-# end
-# end
-#
-# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name.
-# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method.
-class ActionCable.Subscription
- constructor: (@consumer, params = {}, mixin) ->
- @identifier = JSON.stringify(params)
- extend(this, mixin)
-
- # Perform a channel action with the optional data passed as an attribute
- perform: (action, data = {}) ->
- data.action = action
- @send(data)
-
- send: (data) ->
- @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data))
-
- unsubscribe: ->
- @consumer.subscriptions.remove(this)
-
- extend = (object, properties) ->
- if properties?
- for key, value of properties
- object[key] = value
- object
diff --git a/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee b/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee
deleted file mode 100644
index aa052bf5d8..0000000000
--- a/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee
+++ /dev/null
@@ -1,66 +0,0 @@
-# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user
-# us ActionCable.Subscriptions#create, and it should be called through the consumer like so:
-#
-# @App = {}
-# App.cable = ActionCable.createConsumer "ws://example.com/accounts/1"
-# App.appearance = App.cable.subscriptions.create "AppearanceChannel"
-#
-# For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
-class ActionCable.Subscriptions
- constructor: (@consumer) ->
- @subscriptions = []
-
- create: (channelName, mixin) ->
- channel = channelName
- params = if typeof channel is "object" then channel else {channel}
- subscription = new ActionCable.Subscription @consumer, params, mixin
- @add(subscription)
-
- # Private
-
- add: (subscription) ->
- @subscriptions.push(subscription)
- @consumer.ensureActiveConnection()
- @notify(subscription, "initialized")
- @sendCommand(subscription, "subscribe")
- subscription
-
- remove: (subscription) ->
- @forget(subscription)
- unless @findAll(subscription.identifier).length
- @sendCommand(subscription, "unsubscribe")
- subscription
-
- reject: (identifier) ->
- for subscription in @findAll(identifier)
- @forget(subscription)
- @notify(subscription, "rejected")
- subscription
-
- forget: (subscription) ->
- @subscriptions = (s for s in @subscriptions when s isnt subscription)
- subscription
-
- findAll: (identifier) ->
- s for s in @subscriptions when s.identifier is identifier
-
- reload: ->
- for subscription in @subscriptions
- @sendCommand(subscription, "subscribe")
-
- notifyAll: (callbackName, args...) ->
- for subscription in @subscriptions
- @notify(subscription, callbackName, args...)
-
- notify: (subscription, callbackName, args...) ->
- if typeof subscription is "string"
- subscriptions = @findAll(subscription)
- else
- subscriptions = [subscription]
-
- for subscription in subscriptions
- subscription[callbackName]?(args...)
-
- sendCommand: (subscription, command) ->
- {identifier} = subscription
- @consumer.send({command, identifier})
diff --git a/actioncable/app/javascript/action_cable/connection.js b/actioncable/app/javascript/action_cable/connection.js
new file mode 100644
index 0000000000..4ad436f2c0
--- /dev/null
+++ b/actioncable/app/javascript/action_cable/connection.js
@@ -0,0 +1,156 @@
+import ActionCable from "./index"
+import INTERNAL from "./internal"
+
+// Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.
+
+const {message_types, protocols} = INTERNAL
+const supportedProtocols = protocols.slice(0, protocols.length - 1)
+
+const indexOf = [].indexOf
+
+class Connection {
+ constructor(consumer) {
+ this.open = this.open.bind(this)
+ this.consumer = consumer
+ this.subscriptions = this.consumer.subscriptions
+ this.monitor = new ActionCable.ConnectionMonitor(this)
+ this.disconnected = true
+ }
+
+ send(data) {
+ if (this.isOpen()) {
+ this.webSocket.send(JSON.stringify(data))
+ return true
+ } else {
+ return false
+ }
+ }
+
+ open() {
+ if (this.isActive()) {
+ ActionCable.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`)
+ return false
+ } else {
+ ActionCable.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`)
+ if (this.webSocket) { this.uninstallEventHandlers() }
+ this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols)
+ this.installEventHandlers()
+ this.monitor.start()
+ return true
+ }
+ }
+
+ close({allowReconnect} = {allowReconnect: true}) {
+ if (!allowReconnect) { this.monitor.stop() }
+ if (this.isActive()) { return (this.webSocket ? this.webSocket.close() : undefined) }
+ }
+
+ reopen() {
+ ActionCable.log(`Reopening WebSocket, current state is ${this.getState()}`)
+ if (this.isActive()) {
+ try {
+ return this.close()
+ } catch (error) {
+ ActionCable.log("Failed to reopen WebSocket", error)
+ }
+ finally {
+ ActionCable.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`)
+ setTimeout(this.open, this.constructor.reopenDelay)
+ }
+ } else {
+ return this.open()
+ }
+ }
+
+ getProtocol() {
+ return (this.webSocket ? this.webSocket.protocol : undefined)
+ }
+
+ isOpen() {
+ return this.isState("open")
+ }
+
+ isActive() {
+ return this.isState("open", "connecting")
+ }
+
+ // Private
+
+ isProtocolSupported() {
+ return indexOf.call(supportedProtocols, this.getProtocol()) >= 0
+ }
+
+ isState(...states) {
+ return indexOf.call(states, this.getState()) >= 0
+ }
+
+ getState() {
+ if (this.webSocket) {
+ for (let state in WebSocket) {
+ if (WebSocket[state] === this.webSocket.readyState) {
+ return state.toLowerCase()
+ }
+ }
+ }
+ return null
+ }
+
+ installEventHandlers() {
+ for (let eventName in this.events) {
+ const handler = this.events[eventName].bind(this)
+ this.webSocket[`on${eventName}`] = handler
+ }
+ }
+
+ uninstallEventHandlers() {
+ for (let eventName in this.events) {
+ this.webSocket[`on${eventName}`] = function() {}
+ }
+ }
+
+}
+
+Connection.reopenDelay = 500
+
+Connection.prototype.events = {
+ message(event) {
+ if (!this.isProtocolSupported()) { return }
+ const {identifier, message, type} = JSON.parse(event.data)
+ switch (type) {
+ case message_types.welcome:
+ this.monitor.recordConnect()
+ return this.subscriptions.reload()
+ case message_types.ping:
+ return this.monitor.recordPing()
+ case message_types.confirmation:
+ return this.subscriptions.notify(identifier, "connected")
+ case message_types.rejection:
+ return this.subscriptions.reject(identifier)
+ default:
+ return this.subscriptions.notify(identifier, "received", message)
+ }
+ },
+
+ open() {
+ ActionCable.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`)
+ this.disconnected = false
+ if (!this.isProtocolSupported()) {
+ ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.")
+ return this.close({allowReconnect: false})
+ }
+ },
+
+ close(event) {
+ ActionCable.log("WebSocket onclose event")
+ if (this.disconnected) { return }
+ this.disconnected = true
+ this.monitor.recordDisconnect()
+ return this.subscriptions.notifyAll("disconnected", {willAttemptReconnect: this.monitor.isRunning()})
+ },
+
+ error() {
+ ActionCable.log("WebSocket onerror event")
+ }
+}
+
+export default Connection
diff --git a/actioncable/app/javascript/action_cable/connection_monitor.js b/actioncable/app/javascript/action_cable/connection_monitor.js
new file mode 100644
index 0000000000..8d6ac1f682
--- /dev/null
+++ b/actioncable/app/javascript/action_cable/connection_monitor.js
@@ -0,0 +1,125 @@
+import ActionCable from "./index"
+
+// Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting
+// revival reconnections if things go astray. Internal class, not intended for direct user manipulation.
+
+const now = () => new Date().getTime()
+
+const secondsSince = time => (now() - time) / 1000
+
+const clamp = (number, min, max) => Math.max(min, Math.min(max, number))
+
+class ConnectionMonitor {
+ constructor(connection) {
+ this.visibilityDidChange = this.visibilityDidChange.bind(this)
+ this.connection = connection
+ this.reconnectAttempts = 0
+ }
+
+ start() {
+ if (!this.isRunning()) {
+ this.startedAt = now()
+ delete this.stoppedAt
+ this.startPolling()
+ document.addEventListener("visibilitychange", this.visibilityDidChange)
+ ActionCable.log(`ConnectionMonitor started. pollInterval = ${this.getPollInterval()} ms`)
+ }
+ }
+
+ stop() {
+ if (this.isRunning()) {
+ this.stoppedAt = now()
+ this.stopPolling()
+ document.removeEventListener("visibilitychange", this.visibilityDidChange)
+ ActionCable.log("ConnectionMonitor stopped")
+ }
+ }
+
+ isRunning() {
+ return this.startedAt && !this.stoppedAt
+ }
+
+ recordPing() {
+ this.pingedAt = now()
+ }
+
+ recordConnect() {
+ this.reconnectAttempts = 0
+ this.recordPing()
+ delete this.disconnectedAt
+ ActionCable.log("ConnectionMonitor recorded connect")
+ }
+
+ recordDisconnect() {
+ this.disconnectedAt = now()
+ ActionCable.log("ConnectionMonitor recorded disconnect")
+ }
+
+ // Private
+
+ startPolling() {
+ this.stopPolling()
+ this.poll()
+ }
+
+ stopPolling() {
+ clearTimeout(this.pollTimeout)
+ }
+
+ poll() {
+ this.pollTimeout = setTimeout(() => {
+ this.reconnectIfStale()
+ this.poll()
+ }
+ , this.getPollInterval())
+ }
+
+ getPollInterval() {
+ const {min, max} = this.constructor.pollInterval
+ const interval = 5 * Math.log(this.reconnectAttempts + 1)
+ return Math.round(clamp(interval, min, max) * 1000)
+ }
+
+ reconnectIfStale() {
+ if (this.connectionIsStale()) {
+ ActionCable.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, pollInterval = ${this.getPollInterval()} ms, time disconnected = ${secondsSince(this.disconnectedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`)
+ this.reconnectAttempts++
+ if (this.disconnectedRecently()) {
+ ActionCable.log("ConnectionMonitor skipping reopening recent disconnect")
+ } else {
+ ActionCable.log("ConnectionMonitor reopening")
+ this.connection.reopen()
+ }
+ }
+ }
+
+ connectionIsStale() {
+ return secondsSince(this.pingedAt ? this.pingedAt : this.startedAt) > this.constructor.staleThreshold
+ }
+
+ disconnectedRecently() {
+ return this.disconnectedAt && (secondsSince(this.disconnectedAt) < this.constructor.staleThreshold)
+ }
+
+ visibilityDidChange() {
+ if (document.visibilityState === "visible") {
+ setTimeout(() => {
+ if (this.connectionIsStale() || !this.connection.isOpen()) {
+ ActionCable.log(`ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = ${document.visibilityState}`)
+ this.connection.reopen()
+ }
+ }
+ , 200)
+ }
+ }
+
+}
+
+ConnectionMonitor.pollInterval = {
+ min: 3,
+ max: 30
+}
+
+ConnectionMonitor.staleThreshold = 6 // Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
+
+export default ConnectionMonitor
diff --git a/actioncable/app/javascript/action_cable/consumer.js b/actioncable/app/javascript/action_cable/consumer.js
new file mode 100644
index 0000000000..c484ceebbd
--- /dev/null
+++ b/actioncable/app/javascript/action_cable/consumer.js
@@ -0,0 +1,53 @@
+import ActionCable from "./index"
+
+// The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established,
+// the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates.
+// The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription
+// method.
+//
+// The following example shows how this can be setup:
+//
+// App = {}
+// App.cable = ActionCable.createConsumer("ws://example.com/accounts/1")
+// App.appearance = App.cable.subscriptions.create("AppearanceChannel")
+//
+// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
+//
+// When a consumer is created, it automatically connects with the server.
+//
+// To disconnect from the server, call
+//
+// App.cable.disconnect()
+//
+// and to restart the connection:
+//
+// App.cable.connect()
+//
+// Any channel subscriptions which existed prior to disconnecting will
+// automatically resubscribe.
+
+export default class Consumer {
+ constructor(url) {
+ this.url = url
+ this.subscriptions = new ActionCable.Subscriptions(this)
+ this.connection = new ActionCable.Connection(this)
+ }
+
+ send(data) {
+ return this.connection.send(data)
+ }
+
+ connect() {
+ return this.connection.open()
+ }
+
+ disconnect() {
+ return this.connection.close({allowReconnect: false})
+ }
+
+ ensureActiveConnection() {
+ if (!this.connection.isActive()) {
+ return this.connection.open()
+ }
+ }
+}
diff --git a/actioncable/app/javascript/action_cable/index.js b/actioncable/app/javascript/action_cable/index.js
new file mode 100644
index 0000000000..eb0c4844df
--- /dev/null
+++ b/actioncable/app/javascript/action_cable/index.js
@@ -0,0 +1,58 @@
+import Connection from "./connection"
+import ConnectionMonitor from "./connection_monitor"
+import Consumer from "./consumer"
+import INTERNAL from "./internal"
+import Subscription from "./subscription"
+import Subscriptions from "./subscriptions"
+
+export default {
+ Connection,
+ ConnectionMonitor,
+ Consumer,
+ INTERNAL,
+ Subscription,
+ Subscriptions,
+ WebSocket: window.WebSocket,
+ logger: window.console,
+
+ createConsumer(url) {
+ if (url == null) {
+ const urlConfig = this.getConfig("url")
+ url = (urlConfig ? urlConfig : this.INTERNAL.default_mount_path)
+ }
+ return new Consumer(this.createWebSocketURL(url))
+ },
+
+ getConfig(name) {
+ const element = document.head.querySelector(`meta[name='action-cable-${name}']`)
+ return (element ? element.getAttribute("content") : undefined)
+ },
+
+ createWebSocketURL(url) {
+ if (url && !/^wss?:/i.test(url)) {
+ const a = document.createElement("a")
+ a.href = url
+ // Fix populating Location properties in IE. Otherwise, protocol will be blank.
+ a.href = a.href
+ a.protocol = a.protocol.replace("http", "ws")
+ return a.href
+ } else {
+ return url
+ }
+ },
+
+ startDebugging() {
+ this.debugging = true
+ },
+
+ stopDebugging() {
+ this.debugging = null
+ },
+
+ log(...messages) {
+ if (this.debugging) {
+ messages.push(Date.now())
+ this.logger.log("[ActionCable]", ...messages)
+ }
+ }
+}
diff --git a/actioncable/app/javascript/action_cable/subscription.js b/actioncable/app/javascript/action_cable/subscription.js
new file mode 100644
index 0000000000..7de08f93b3
--- /dev/null
+++ b/actioncable/app/javascript/action_cable/subscription.js
@@ -0,0 +1,89 @@
+// A new subscription is created through the ActionCable.Subscriptions instance available on the consumer.
+// It provides a number of callbacks and a method for calling remote procedure calls on the corresponding
+// Channel instance on the server side.
+//
+// An example demonstrates the basic functionality:
+//
+// App.appearance = App.cable.subscriptions.create("AppearanceChannel", {
+// connected() {
+// // Called once the subscription has been successfully completed
+// },
+//
+// disconnected({ willAttemptReconnect: boolean }) {
+// // Called when the client has disconnected with the server.
+// // The object will have an `willAttemptReconnect` property which
+// // says whether the client has the intention of attempting
+// // to reconnect.
+// },
+//
+// appear() {
+// this.perform('appear', {appearing_on: this.appearingOn()})
+// },
+//
+// away() {
+// this.perform('away')
+// },
+//
+// appearingOn() {
+// $('main').data('appearing-on')
+// }
+// })
+//
+// The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server
+// by calling the `perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away).
+// The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter.
+//
+// This is how the server component would look:
+//
+// class AppearanceChannel < ApplicationActionCable::Channel
+// def subscribed
+// current_user.appear
+// end
+//
+// def unsubscribed
+// current_user.disappear
+// end
+//
+// def appear(data)
+// current_user.appear on: data['appearing_on']
+// end
+//
+// def away
+// current_user.away
+// end
+// end
+//
+// The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name.
+// The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the perform method.
+
+const extend = function(object, properties) {
+ if (properties != null) {
+ for (let key in properties) {
+ const value = properties[key]
+ object[key] = value
+ }
+ }
+ return object
+}
+
+export default class Subscription {
+ constructor(consumer, params = {}, mixin) {
+ this.consumer = consumer
+ this.identifier = JSON.stringify(params)
+ extend(this, mixin)
+ }
+
+ // Perform a channel action with the optional data passed as an attribute
+ perform(action, data = {}) {
+ data.action = action
+ return this.send(data)
+ }
+
+ send(data) {
+ return this.consumer.send({command: "message", identifier: this.identifier, data: JSON.stringify(data)})
+ }
+
+ unsubscribe() {
+ return this.consumer.subscriptions.remove(this)
+ }
+}
diff --git a/actioncable/app/javascript/action_cable/subscriptions.js b/actioncable/app/javascript/action_cable/subscriptions.js
new file mode 100644
index 0000000000..712ff50d28
--- /dev/null
+++ b/actioncable/app/javascript/action_cable/subscriptions.js
@@ -0,0 +1,86 @@
+import ActionCable from "./index"
+
+// Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user
+// us ActionCable.Subscriptions#create, and it should be called through the consumer like so:
+//
+// App = {}
+// App.cable = ActionCable.createConsumer("ws://example.com/accounts/1")
+// App.appearance = App.cable.subscriptions.create("AppearanceChannel")
+//
+// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
+
+export default class Subscriptions {
+ constructor(consumer) {
+ this.consumer = consumer
+ this.subscriptions = []
+ }
+
+ create(channelName, mixin) {
+ const channel = channelName
+ const params = typeof channel === "object" ? channel : {channel}
+ const subscription = new ActionCable.Subscription(this.consumer, params, mixin)
+ return this.add(subscription)
+ }
+
+ // Private
+
+ add(subscription) {
+ this.subscriptions.push(subscription)
+ this.consumer.ensureActiveConnection()
+ this.notify(subscription, "initialized")
+ this.sendCommand(subscription, "subscribe")
+ return subscription
+ }
+
+ remove(subscription) {
+ this.forget(subscription)
+ if (!this.findAll(subscription.identifier).length) {
+ this.sendCommand(subscription, "unsubscribe")
+ }
+ return subscription
+ }
+
+ reject(identifier) {
+ return this.findAll(identifier).map((subscription) => {
+ this.forget(subscription)
+ this.notify(subscription, "rejected")
+ return subscription
+ })
+ }
+
+ forget(subscription) {
+ this.subscriptions = (this.subscriptions.filter((s) => s !== subscription))
+ return subscription
+ }
+
+ findAll(identifier) {
+ return this.subscriptions.filter((s) => s.identifier === identifier)
+ }
+
+ reload() {
+ return this.subscriptions.map((subscription) =>
+ this.sendCommand(subscription, "subscribe"))
+ }
+
+ notifyAll(callbackName, ...args) {
+ return this.subscriptions.map((subscription) =>
+ this.notify(subscription, callbackName, ...args))
+ }
+
+ notify(subscription, callbackName, ...args) {
+ let subscriptions
+ if (typeof subscription === "string") {
+ subscriptions = this.findAll(subscription)
+ } else {
+ subscriptions = [subscription]
+ }
+
+ return subscriptions.map((subscription) =>
+ (typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined))
+ }
+
+ sendCommand(subscription, command) {
+ const {identifier} = subscription
+ return this.consumer.send({command, identifier})
+ }
+}
diff --git a/actioncable/blade.yml b/actioncable/blade.yml
index e38e9b2f1d..54d13a2ba2 100644
--- a/actioncable/blade.yml
+++ b/actioncable/blade.yml
@@ -6,12 +6,6 @@ load_paths:
logical_paths:
- test.js
-build:
- logical_paths:
- - action_cable.js
- path: lib/assets/compiled
- clean: true
-
plugins:
sauce_labs:
browsers:
diff --git a/actioncable/lib/action_cable/connection/authorization.rb b/actioncable/lib/action_cable/connection/authorization.rb
index a22179d988..aef3386f71 100644
--- a/actioncable/lib/action_cable/connection/authorization.rb
+++ b/actioncable/lib/action_cable/connection/authorization.rb
@@ -5,7 +5,7 @@ module ActionCable
module Authorization
class UnauthorizedError < StandardError; end
- # Closes the \WebSocket connection if it is open and returns a 404 "File not Found" response.
+ # Closes the WebSocket connection if it is open and returns a 404 "File not Found" response.
def reject_unauthorized_connection
logger.error "An unauthorized connection attempt was rejected"
raise UnauthorizedError
diff --git a/actioncable/lib/rails/generators/channel/templates/javascript/index.js.tt b/actioncable/lib/rails/generators/channel/templates/javascript/index.js.tt
index 5da1ce2dce..0cfcf74919 100644
--- a/actioncable/lib/rails/generators/channel/templates/javascript/index.js.tt
+++ b/actioncable/lib/rails/generators/channel/templates/javascript/index.js.tt
@@ -1,5 +1,5 @@
-// Load all the channels within this directory and all subdirectories.
+// Load all the channels within this directory and all subdirectories.
// Channel files must be named *_channel.js.
-const channels = require.context('.', true, /\_channel\.js$/)
+const channels = require.context('.', true, /_channel\.js$/)
channels.keys().forEach(channels)
diff --git a/actioncable/package.json b/actioncable/package.json
index d9df46043d..5d5105a6d5 100644
--- a/actioncable/package.json
+++ b/actioncable/package.json
@@ -2,9 +2,9 @@
"name": "actioncable",
"version": "6.0.0-alpha",
"description": "WebSocket framework for Ruby on Rails.",
- "main": "lib/assets/compiled/action_cable.js",
+ "main": "app/assets/javascripts/action_cable.js",
"files": [
- "lib/assets/compiled/*.js"
+ "app/assets/javascripts/*.js"
],
"repository": {
"type": "git",
@@ -20,5 +20,22 @@
"bugs": {
"url": "https://github.com/rails/rails/issues"
},
- "homepage": "http://rubyonrails.org/"
+ "homepage": "http://rubyonrails.org/",
+ "devDependencies": {
+ "babel-core": "^6.25.0",
+ "babel-plugin-external-helpers": "^6.22.0",
+ "babel-preset-env": "^1.6.0",
+ "eslint": "^4.3.0",
+ "eslint-plugin-import": "^2.7.0",
+ "rollup": "^0.58.2",
+ "rollup-plugin-babel": "^3.0.4",
+ "rollup-plugin-commonjs": "^9.1.0",
+ "rollup-plugin-node-resolve": "^3.3.0",
+ "rollup-plugin-uglify": "^3.0.0"
+ },
+ "scripts": {
+ "prebuild": "yarn lint && bundle exec rake assets:codegen",
+ "build": "rollup --config rollup.config.js",
+ "lint": "eslint app/javascript"
+ }
}
diff --git a/actioncable/rollup.config.js b/actioncable/rollup.config.js
new file mode 100644
index 0000000000..03046526aa
--- /dev/null
+++ b/actioncable/rollup.config.js
@@ -0,0 +1,28 @@
+import resolve from "rollup-plugin-node-resolve"
+import commonjs from "rollup-plugin-commonjs"
+import babel from "rollup-plugin-babel"
+import uglify from "rollup-plugin-uglify"
+
+const uglifyOptions = {
+ mangle: false,
+ compress: false,
+ output: {
+ beautify: true,
+ indent_level: 2
+ }
+}
+
+export default {
+ input: "app/javascript/action_cable/index.js",
+ output: {
+ file: "app/assets/javascripts/action_cable.js",
+ format: "umd",
+ name: "ActionCable"
+ },
+ plugins: [
+ resolve(),
+ commonjs(),
+ babel(),
+ uglify(uglifyOptions)
+ ]
+}
diff --git a/actioncable/yarn.lock b/actioncable/yarn.lock
new file mode 100644
index 0000000000..8b4fcaebd6
--- /dev/null
+++ b/actioncable/yarn.lock
@@ -0,0 +1,1928 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@types/estree@0.0.38":
+ version "0.0.38"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2"
+
+"@types/node@*":
+ version "10.11.6"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.6.tgz#ce5690df6cd917a9178439a1013e39a7e565c46e"
+
+acorn-jsx@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
+ dependencies:
+ acorn "^3.0.4"
+
+acorn@^3.0.4:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
+
+acorn@^5.2.1:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822"
+
+ajv-keywords@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
+
+ajv@^5.2.3, ajv@^5.3.0:
+ version "5.5.2"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
+ dependencies:
+ co "^4.6.0"
+ fast-deep-equal "^1.0.0"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.3.0"
+
+ansi-escapes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+
+ansi-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+
+ansi-styles@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+
+ansi-styles@^3.1.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
+ dependencies:
+ color-convert "^1.9.0"
+
+argparse@^1.0.7:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
+ dependencies:
+ sprintf-js "~1.0.2"
+
+arr-diff@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
+ dependencies:
+ arr-flatten "^1.0.1"
+
+arr-flatten@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
+
+array-union@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
+ dependencies:
+ array-uniq "^1.0.1"
+
+array-uniq@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
+
+array-unique@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
+
+arrify@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
+
+babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
+ dependencies:
+ chalk "^1.1.3"
+ esutils "^2.0.2"
+ js-tokens "^3.0.2"
+
+babel-core@^6.25.0, babel-core@^6.26.0:
+ version "6.26.3"
+ resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
+ dependencies:
+ babel-code-frame "^6.26.0"
+ babel-generator "^6.26.0"
+ babel-helpers "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-register "^6.26.0"
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ convert-source-map "^1.5.1"
+ debug "^2.6.9"
+ json5 "^0.5.1"
+ lodash "^4.17.4"
+ minimatch "^3.0.4"
+ path-is-absolute "^1.0.1"
+ private "^0.1.8"
+ slash "^1.0.0"
+ source-map "^0.5.7"
+
+babel-generator@^6.26.0:
+ version "6.26.1"
+ resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
+ dependencies:
+ babel-messages "^6.23.0"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ detect-indent "^4.0.0"
+ jsesc "^1.3.0"
+ lodash "^4.17.4"
+ source-map "^0.5.7"
+ trim-right "^1.0.1"
+
+babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
+ dependencies:
+ babel-helper-explode-assignable-expression "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-call-delegate@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
+ dependencies:
+ babel-helper-hoist-variables "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-define-map@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-helper-explode-assignable-expression@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-function-name@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
+ dependencies:
+ babel-helper-get-function-arity "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-get-function-arity@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-hoist-variables@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-optimise-call-expression@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-helper-regex@^6.24.1:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-helper-remap-async-to-generator@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helper-replace-supers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
+ dependencies:
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-helpers@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-messages@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-check-es2015-constants@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-external-helpers@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-external-helpers/-/babel-plugin-external-helpers-6.22.0.tgz#2285f48b02bd5dede85175caf8c62e86adccefa1"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-syntax-async-functions@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
+
+babel-plugin-syntax-exponentiation-operator@^6.8.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
+
+babel-plugin-syntax-trailing-function-commas@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
+
+babel-plugin-transform-async-to-generator@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
+ dependencies:
+ babel-helper-remap-async-to-generator "^6.24.1"
+ babel-plugin-syntax-async-functions "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-arrow-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-block-scoping@^6.23.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ lodash "^4.17.4"
+
+babel-plugin-transform-es2015-classes@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
+ dependencies:
+ babel-helper-define-map "^6.24.1"
+ babel-helper-function-name "^6.24.1"
+ babel-helper-optimise-call-expression "^6.24.1"
+ babel-helper-replace-supers "^6.24.1"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-computed-properties@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-destructuring@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-duplicate-keys@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-for-of@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-function-name@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
+ dependencies:
+ babel-helper-function-name "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
+ dependencies:
+ babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
+ version "6.26.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
+ dependencies:
+ babel-plugin-transform-strict-mode "^6.24.1"
+ babel-runtime "^6.26.0"
+ babel-template "^6.26.0"
+ babel-types "^6.26.0"
+
+babel-plugin-transform-es2015-modules-systemjs@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
+ dependencies:
+ babel-helper-hoist-variables "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-modules-umd@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
+ dependencies:
+ babel-plugin-transform-es2015-modules-amd "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+
+babel-plugin-transform-es2015-object-super@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
+ dependencies:
+ babel-helper-replace-supers "^6.24.1"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-parameters@^6.23.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
+ dependencies:
+ babel-helper-call-delegate "^6.24.1"
+ babel-helper-get-function-arity "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-template "^6.24.1"
+ babel-traverse "^6.24.1"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-shorthand-properties@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-spread@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-sticky-regex@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
+ dependencies:
+ babel-helper-regex "^6.24.1"
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-plugin-transform-es2015-template-literals@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
+ dependencies:
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-es2015-unicode-regex@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
+ dependencies:
+ babel-helper-regex "^6.24.1"
+ babel-runtime "^6.22.0"
+ regexpu-core "^2.0.0"
+
+babel-plugin-transform-exponentiation-operator@^6.22.0:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
+ dependencies:
+ babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
+ babel-plugin-syntax-exponentiation-operator "^6.8.0"
+ babel-runtime "^6.22.0"
+
+babel-plugin-transform-regenerator@^6.22.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
+ dependencies:
+ regenerator-transform "^0.10.0"
+
+babel-plugin-transform-strict-mode@^6.24.1:
+ version "6.24.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758"
+ dependencies:
+ babel-runtime "^6.22.0"
+ babel-types "^6.24.1"
+
+babel-preset-env@^1.6.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a"
+ dependencies:
+ babel-plugin-check-es2015-constants "^6.22.0"
+ babel-plugin-syntax-trailing-function-commas "^6.22.0"
+ babel-plugin-transform-async-to-generator "^6.22.0"
+ babel-plugin-transform-es2015-arrow-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
+ babel-plugin-transform-es2015-block-scoping "^6.23.0"
+ babel-plugin-transform-es2015-classes "^6.23.0"
+ babel-plugin-transform-es2015-computed-properties "^6.22.0"
+ babel-plugin-transform-es2015-destructuring "^6.23.0"
+ babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
+ babel-plugin-transform-es2015-for-of "^6.23.0"
+ babel-plugin-transform-es2015-function-name "^6.22.0"
+ babel-plugin-transform-es2015-literals "^6.22.0"
+ babel-plugin-transform-es2015-modules-amd "^6.22.0"
+ babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
+ babel-plugin-transform-es2015-modules-umd "^6.23.0"
+ babel-plugin-transform-es2015-object-super "^6.22.0"
+ babel-plugin-transform-es2015-parameters "^6.23.0"
+ babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
+ babel-plugin-transform-es2015-spread "^6.22.0"
+ babel-plugin-transform-es2015-sticky-regex "^6.22.0"
+ babel-plugin-transform-es2015-template-literals "^6.22.0"
+ babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
+ babel-plugin-transform-es2015-unicode-regex "^6.22.0"
+ babel-plugin-transform-exponentiation-operator "^6.22.0"
+ babel-plugin-transform-regenerator "^6.22.0"
+ browserslist "^3.2.6"
+ invariant "^2.2.2"
+ semver "^5.3.0"
+
+babel-register@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
+ dependencies:
+ babel-core "^6.26.0"
+ babel-runtime "^6.26.0"
+ core-js "^2.5.0"
+ home-or-tmp "^2.0.0"
+ lodash "^4.17.4"
+ mkdirp "^0.5.1"
+ source-map-support "^0.4.15"
+
+babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
+ dependencies:
+ core-js "^2.4.0"
+ regenerator-runtime "^0.11.0"
+
+babel-template@^6.24.1, babel-template@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
+ dependencies:
+ babel-runtime "^6.26.0"
+ babel-traverse "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ lodash "^4.17.4"
+
+babel-traverse@^6.24.1, babel-traverse@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
+ dependencies:
+ babel-code-frame "^6.26.0"
+ babel-messages "^6.23.0"
+ babel-runtime "^6.26.0"
+ babel-types "^6.26.0"
+ babylon "^6.18.0"
+ debug "^2.6.8"
+ globals "^9.18.0"
+ invariant "^2.2.2"
+ lodash "^4.17.4"
+
+babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
+ dependencies:
+ babel-runtime "^6.26.0"
+ esutils "^2.0.2"
+ lodash "^4.17.4"
+ to-fast-properties "^1.0.3"
+
+babylon@^6.18.0:
+ version "6.18.0"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+
+brace-expansion@^1.1.7:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+braces@^1.8.2:
+ version "1.8.5"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
+ dependencies:
+ expand-range "^1.8.1"
+ preserve "^0.2.0"
+ repeat-element "^1.1.2"
+
+browserslist@^3.2.6:
+ version "3.2.8"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6"
+ dependencies:
+ caniuse-lite "^1.0.30000844"
+ electron-to-chromium "^1.3.47"
+
+builtin-modules@^1.0.0, builtin-modules@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+
+builtin-modules@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-2.0.0.tgz#60b7ef5ae6546bd7deefa74b08b62a43a232648e"
+
+caller-path@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
+ dependencies:
+ callsites "^0.2.0"
+
+callsites@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
+
+caniuse-lite@^1.0.30000844:
+ version "1.0.30000890"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000890.tgz#86a18ffcc65d79ec6a437e985761b8bf1c4efeaf"
+
+chalk@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+ dependencies:
+ ansi-styles "^2.2.1"
+ escape-string-regexp "^1.0.2"
+ has-ansi "^2.0.0"
+ strip-ansi "^3.0.0"
+ supports-color "^2.0.0"
+
+chalk@^2.0.0, chalk@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
+ dependencies:
+ ansi-styles "^3.1.0"
+ escape-string-regexp "^1.0.5"
+ supports-color "^4.0.0"
+
+chardet@^0.4.0:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
+
+circular-json@^0.3.1:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
+
+cli-cursor@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
+ dependencies:
+ restore-cursor "^2.0.0"
+
+cli-width@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
+
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+
+color-convert@^1.9.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
+ dependencies:
+ color-name "^1.1.1"
+
+color-name@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+
+commander@~2.13.0:
+ version "2.13.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+
+concat-stream@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
+ dependencies:
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+contains-path@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
+
+convert-source-map@^1.5.1:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
+ dependencies:
+ safe-buffer "~5.1.1"
+
+core-js@^2.4.0, core-js@^2.5.0:
+ version "2.5.7"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+cross-spawn@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
+ dependencies:
+ lru-cache "^4.0.1"
+ shebang-command "^1.2.0"
+ which "^1.2.9"
+
+debug@^2.6.8, debug@^2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ dependencies:
+ ms "2.0.0"
+
+debug@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ dependencies:
+ ms "2.0.0"
+
+deep-is@~0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
+
+del@^2.0.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
+ dependencies:
+ globby "^5.0.0"
+ is-path-cwd "^1.0.0"
+ is-path-in-cwd "^1.0.0"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+ rimraf "^2.2.8"
+
+detect-indent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
+ dependencies:
+ repeating "^2.0.0"
+
+doctrine@1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
+ dependencies:
+ esutils "^2.0.2"
+ isarray "^1.0.0"
+
+doctrine@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
+ dependencies:
+ esutils "^2.0.2"
+
+electron-to-chromium@^1.3.47:
+ version "1.3.75"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.75.tgz#dd04551739e7371862b0ac7f4ddaa9f3f95b7e68"
+
+error-ex@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
+ dependencies:
+ is-arrayish "^0.2.1"
+
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+
+eslint-import-resolver-node@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a"
+ dependencies:
+ debug "^2.6.9"
+ resolve "^1.5.0"
+
+eslint-module-utils@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449"
+ dependencies:
+ debug "^2.6.8"
+ pkg-dir "^1.0.0"
+
+eslint-plugin-import@^2.7.0:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz#fa1b6ef31fcb3c501c09859c1b86f1fc5b986894"
+ dependencies:
+ builtin-modules "^1.1.1"
+ contains-path "^0.1.0"
+ debug "^2.6.8"
+ doctrine "1.5.0"
+ eslint-import-resolver-node "^0.3.1"
+ eslint-module-utils "^2.1.1"
+ has "^1.0.1"
+ lodash.cond "^4.3.0"
+ minimatch "^3.0.3"
+ read-pkg-up "^2.0.0"
+
+eslint-scope@^3.7.1:
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
+ dependencies:
+ esrecurse "^4.1.0"
+ estraverse "^4.1.1"
+
+eslint-visitor-keys@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
+
+eslint@^4.3.0:
+ version "4.16.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.16.0.tgz#934ada9e98715e1d7bbfd6f6f0519ed2fab35cc1"
+ dependencies:
+ ajv "^5.3.0"
+ babel-code-frame "^6.22.0"
+ chalk "^2.1.0"
+ concat-stream "^1.6.0"
+ cross-spawn "^5.1.0"
+ debug "^3.1.0"
+ doctrine "^2.1.0"
+ eslint-scope "^3.7.1"
+ eslint-visitor-keys "^1.0.0"
+ espree "^3.5.2"
+ esquery "^1.0.0"
+ esutils "^2.0.2"
+ file-entry-cache "^2.0.0"
+ functional-red-black-tree "^1.0.1"
+ glob "^7.1.2"
+ globals "^11.0.1"
+ ignore "^3.3.3"
+ imurmurhash "^0.1.4"
+ inquirer "^3.0.6"
+ is-resolvable "^1.0.0"
+ js-yaml "^3.9.1"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.3.0"
+ lodash "^4.17.4"
+ minimatch "^3.0.2"
+ mkdirp "^0.5.1"
+ natural-compare "^1.4.0"
+ optionator "^0.8.2"
+ path-is-inside "^1.0.2"
+ pluralize "^7.0.0"
+ progress "^2.0.0"
+ require-uncached "^1.0.3"
+ semver "^5.3.0"
+ strip-ansi "^4.0.0"
+ strip-json-comments "~2.0.1"
+ table "^4.0.1"
+ text-table "~0.2.0"
+
+espree@^3.5.2:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca"
+ dependencies:
+ acorn "^5.2.1"
+ acorn-jsx "^3.0.0"
+
+esprima@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
+
+esquery@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
+ dependencies:
+ estraverse "^4.0.0"
+
+esrecurse@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163"
+ dependencies:
+ estraverse "^4.1.0"
+ object-assign "^4.0.1"
+
+estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
+
+estree-walker@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.2.1.tgz#bdafe8095383d8414d5dc2ecf4c9173b6db9412e"
+
+estree-walker@^0.5.1, estree-walker@^0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39"
+
+esutils@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
+
+expand-brackets@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
+ dependencies:
+ is-posix-bracket "^0.1.0"
+
+expand-range@^1.8.1:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
+ dependencies:
+ fill-range "^2.1.0"
+
+external-editor@^2.0.4:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48"
+ dependencies:
+ chardet "^0.4.0"
+ iconv-lite "^0.4.17"
+ tmp "^0.0.33"
+
+extglob@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
+ dependencies:
+ is-extglob "^1.0.0"
+
+fast-deep-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
+
+fast-levenshtein@~2.0.4:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+
+figures@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
+ dependencies:
+ escape-string-regexp "^1.0.5"
+
+file-entry-cache@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
+ dependencies:
+ flat-cache "^1.2.1"
+ object-assign "^4.0.1"
+
+filename-regex@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
+
+fill-range@^2.1.0:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565"
+ dependencies:
+ is-number "^2.1.0"
+ isobject "^2.0.0"
+ randomatic "^3.0.0"
+ repeat-element "^1.1.2"
+ repeat-string "^1.5.2"
+
+find-up@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
+ dependencies:
+ path-exists "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+find-up@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+ dependencies:
+ locate-path "^2.0.0"
+
+flat-cache@^1.2.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481"
+ dependencies:
+ circular-json "^0.3.1"
+ del "^2.0.2"
+ graceful-fs "^4.1.2"
+ write "^0.2.1"
+
+for-in@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
+
+for-own@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
+ dependencies:
+ for-in "^1.0.1"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+
+function-bind@^1.0.2:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+
+functional-red-black-tree@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
+
+glob-base@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
+ dependencies:
+ glob-parent "^2.0.0"
+ is-glob "^2.0.0"
+
+glob-parent@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
+ dependencies:
+ is-glob "^2.0.0"
+
+glob@^7.0.3, glob@^7.0.5, glob@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globals@^11.0.1:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.1.0.tgz#632644457f5f0e3ae711807183700ebf2e4633e4"
+
+globals@^9.18.0:
+ version "9.18.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
+
+globby@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
+ dependencies:
+ array-union "^1.0.1"
+ arrify "^1.0.0"
+ glob "^7.0.3"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
+graceful-fs@^4.1.2:
+ version "4.1.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
+
+has-ansi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+has-flag@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
+
+has@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
+ dependencies:
+ function-bind "^1.0.2"
+
+home-or-tmp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.1"
+
+hosted-git-info@^2.1.4:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
+
+iconv-lite@^0.4.17:
+ version "0.4.19"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
+
+ignore@^3.3.3:
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@^2.0.3, inherits@~2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+inquirer@^3.0.6:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
+ dependencies:
+ ansi-escapes "^3.0.0"
+ chalk "^2.0.0"
+ cli-cursor "^2.1.0"
+ cli-width "^2.0.0"
+ external-editor "^2.0.4"
+ figures "^2.0.0"
+ lodash "^4.3.0"
+ mute-stream "0.0.7"
+ run-async "^2.2.0"
+ rx-lite "^4.0.8"
+ rx-lite-aggregates "^4.0.8"
+ string-width "^2.1.0"
+ strip-ansi "^4.0.0"
+ through "^2.3.6"
+
+invariant@^2.2.2:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
+ dependencies:
+ loose-envify "^1.0.0"
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+
+is-buffer@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
+
+is-builtin-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
+ dependencies:
+ builtin-modules "^1.0.0"
+
+is-dotfile@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
+
+is-equal-shallow@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
+ dependencies:
+ is-primitive "^2.0.0"
+
+is-extendable@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+
+is-extglob@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
+
+is-finite@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+
+is-glob@^2.0.0, is-glob@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
+ dependencies:
+ is-extglob "^1.0.0"
+
+is-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
+
+is-number@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
+ dependencies:
+ kind-of "^3.0.2"
+
+is-number@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
+
+is-path-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
+
+is-path-in-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc"
+ dependencies:
+ is-path-inside "^1.0.0"
+
+is-path-inside@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
+ dependencies:
+ path-is-inside "^1.0.1"
+
+is-posix-bracket@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
+
+is-primitive@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
+
+is-promise@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
+
+is-resolvable@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.1.tgz#acca1cd36dbe44b974b924321555a70ba03b1cf4"
+
+isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+
+isobject@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
+ dependencies:
+ isarray "1.0.0"
+
+"js-tokens@^3.0.0 || ^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+
+js-tokens@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+
+js-yaml@^3.9.1:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
+jsesc@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
+
+jsesc@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+
+json-schema-traverse@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
+
+json-stable-stringify-without-jsonify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+
+json5@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+
+kind-of@^3.0.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
+ dependencies:
+ is-buffer "^1.1.5"
+
+kind-of@^6.0.0:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
+
+levn@^0.3.0, levn@~0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
+ dependencies:
+ prelude-ls "~1.1.2"
+ type-check "~0.3.2"
+
+load-json-file@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ strip-bom "^3.0.0"
+
+locate-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+ dependencies:
+ p-locate "^2.0.0"
+ path-exists "^3.0.0"
+
+lodash.cond@^4.3.0:
+ version "4.5.2"
+ resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
+
+lodash@^4.17.4, lodash@^4.3.0:
+ version "4.17.4"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
+
+loose-envify@^1.0.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+lru-cache@^4.0.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
+ dependencies:
+ pseudomap "^1.0.2"
+ yallist "^2.1.2"
+
+magic-string@^0.22.4:
+ version "0.22.5"
+ resolved "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e"
+ dependencies:
+ vlq "^0.2.2"
+
+math-random@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
+
+micromatch@^2.3.11:
+ version "2.3.11"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
+ dependencies:
+ arr-diff "^2.0.0"
+ array-unique "^0.2.1"
+ braces "^1.8.2"
+ expand-brackets "^0.1.4"
+ extglob "^0.3.1"
+ filename-regex "^2.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.1"
+ kind-of "^3.0.2"
+ normalize-path "^2.0.1"
+ object.omit "^2.0.0"
+ parse-glob "^3.0.4"
+ regex-cache "^0.4.2"
+
+mimic-fn@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
+
+minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+
+mkdirp@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ dependencies:
+ minimist "0.0.8"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+
+mute-stream@0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
+
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+
+normalize-package-data@^2.3.2:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
+ dependencies:
+ hosted-git-info "^2.1.4"
+ is-builtin-module "^1.0.0"
+ semver "2 || 3 || 4 || 5"
+ validate-npm-package-license "^3.0.1"
+
+normalize-path@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
+ dependencies:
+ remove-trailing-separator "^1.0.1"
+
+number-is-nan@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+
+object-assign@^4.0.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+
+object.omit@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
+ dependencies:
+ for-own "^0.1.4"
+ is-extendable "^0.1.1"
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ dependencies:
+ wrappy "1"
+
+onetime@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
+ dependencies:
+ mimic-fn "^1.0.0"
+
+optionator@^0.8.2:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
+ dependencies:
+ deep-is "~0.1.3"
+ fast-levenshtein "~2.0.4"
+ levn "~0.3.0"
+ prelude-ls "~1.1.2"
+ type-check "~0.3.2"
+ wordwrap "~1.0.0"
+
+os-homedir@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+
+os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+
+p-limit@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
+ dependencies:
+ p-try "^1.0.0"
+
+p-locate@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+ dependencies:
+ p-limit "^1.1.0"
+
+p-try@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+
+parse-glob@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
+ dependencies:
+ glob-base "^0.3.0"
+ is-dotfile "^1.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.0"
+
+parse-json@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
+ dependencies:
+ error-ex "^1.2.0"
+
+path-exists@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
+ dependencies:
+ pinkie-promise "^2.0.0"
+
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+
+path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+
+path-is-inside@^1.0.1, path-is-inside@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+
+path-parse@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
+
+path-type@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
+ dependencies:
+ pify "^2.0.0"
+
+pify@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+
+pinkie-promise@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
+ dependencies:
+ pinkie "^2.0.0"
+
+pinkie@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+
+pkg-dir@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
+ dependencies:
+ find-up "^1.0.0"
+
+pluralize@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
+
+prelude-ls@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+
+preserve@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
+
+private@^0.1.6, private@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
+
+process-nextick-args@~1.0.6:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
+
+progress@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
+
+pseudomap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+
+randomatic@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.0.tgz#36f2ca708e9e567f5ed2ec01949026d50aa10116"
+ dependencies:
+ is-number "^4.0.0"
+ kind-of "^6.0.0"
+ math-random "^1.0.1"
+
+read-pkg-up@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
+ dependencies:
+ find-up "^2.0.0"
+ read-pkg "^2.0.0"
+
+read-pkg@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
+ dependencies:
+ load-json-file "^2.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^2.0.0"
+
+readable-stream@^2.2.2:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.0.3"
+ util-deprecate "~1.0.1"
+
+regenerate@^1.2.1:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
+
+regenerator-runtime@^0.11.0:
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
+
+regenerator-transform@^0.10.0:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
+ dependencies:
+ babel-runtime "^6.18.0"
+ babel-types "^6.19.0"
+ private "^0.1.6"
+
+regex-cache@^0.4.2:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
+ dependencies:
+ is-equal-shallow "^0.1.3"
+
+regexpu-core@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240"
+ dependencies:
+ regenerate "^1.2.1"
+ regjsgen "^0.2.0"
+ regjsparser "^0.1.4"
+
+regjsgen@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
+
+regjsparser@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
+ dependencies:
+ jsesc "~0.5.0"
+
+remove-trailing-separator@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
+
+repeat-element@^1.1.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
+
+repeat-string@^1.5.2:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+
+repeating@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
+ dependencies:
+ is-finite "^1.0.0"
+
+require-uncached@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
+ dependencies:
+ caller-path "^0.1.0"
+ resolve-from "^1.0.0"
+
+resolve-from@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
+
+resolve@^1.1.6:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
+ dependencies:
+ path-parse "^1.0.5"
+
+resolve@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
+ dependencies:
+ path-parse "^1.0.5"
+
+restore-cursor@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
+ dependencies:
+ onetime "^2.0.0"
+ signal-exit "^3.0.2"
+
+rimraf@^2.2.8:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
+ dependencies:
+ glob "^7.0.5"
+
+rollup-plugin-babel@^3.0.4:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-3.0.7.tgz#5b13611f1ab8922497e9d15197ae5d8a23fe3b1e"
+ dependencies:
+ rollup-pluginutils "^1.5.0"
+
+rollup-plugin-commonjs@^9.1.0:
+ version "9.1.8"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.1.8.tgz#4113ed94e6054b5f8a3501d8811f934cadde3246"
+ dependencies:
+ estree-walker "^0.5.1"
+ magic-string "^0.22.4"
+ resolve "^1.5.0"
+ rollup-pluginutils "^2.0.1"
+
+rollup-plugin-node-resolve@^3.3.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.4.0.tgz#908585eda12e393caac7498715a01e08606abc89"
+ dependencies:
+ builtin-modules "^2.0.0"
+ is-module "^1.0.0"
+ resolve "^1.1.6"
+
+rollup-plugin-uglify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-uglify/-/rollup-plugin-uglify-3.0.0.tgz#a34eca24617709c6bf1778e9653baafa06099b86"
+ dependencies:
+ uglify-es "^3.3.7"
+
+rollup-pluginutils@^1.5.0:
+ version "1.5.2"
+ resolved "http://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz#1e156e778f94b7255bfa1b3d0178be8f5c552408"
+ dependencies:
+ estree-walker "^0.2.1"
+ minimatch "^3.0.2"
+
+rollup-pluginutils@^2.0.1:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz#3aad9b1eb3e7fe8262820818840bf091e5ae6794"
+ dependencies:
+ estree-walker "^0.5.2"
+ micromatch "^2.3.11"
+
+rollup@^0.58.2:
+ version "0.58.2"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.58.2.tgz#2feddea8c0c022f3e74b35c48e3c21b3433803ce"
+ dependencies:
+ "@types/estree" "0.0.38"
+ "@types/node" "*"
+
+run-async@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
+ dependencies:
+ is-promise "^2.1.0"
+
+rx-lite-aggregates@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
+ dependencies:
+ rx-lite "*"
+
+rx-lite@*, rx-lite@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
+
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
+
+"semver@2 || 3 || 4 || 5", semver@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
+
+shebang-command@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+ dependencies:
+ shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+
+signal-exit@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+
+slash@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+
+slice-ansi@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d"
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+
+source-map-support@^0.4.15:
+ version "0.4.18"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
+ dependencies:
+ source-map "^0.5.6"
+
+source-map@^0.5.6, source-map@^0.5.7:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+
+source-map@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+
+spdx-correct@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
+ dependencies:
+ spdx-license-ids "^1.0.2"
+
+spdx-expression-parse@~1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
+
+spdx-license-ids@^1.0.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+
+string-width@^2.1.0, string-width@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^4.0.0"
+
+string_decoder@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
+ dependencies:
+ safe-buffer "~5.1.0"
+
+strip-ansi@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+strip-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+ dependencies:
+ ansi-regex "^3.0.0"
+
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+
+supports-color@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+
+supports-color@^4.0.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
+ dependencies:
+ has-flag "^2.0.0"
+
+table@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
+ dependencies:
+ ajv "^5.2.3"
+ ajv-keywords "^2.1.0"
+ chalk "^2.1.0"
+ lodash "^4.17.4"
+ slice-ansi "1.0.0"
+ string-width "^2.1.1"
+
+text-table@~0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+
+through@^2.3.6:
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+
+tmp@^0.0.33:
+ version "0.0.33"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+ dependencies:
+ os-tmpdir "~1.0.2"
+
+to-fast-properties@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
+
+trim-right@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
+
+type-check@~0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
+ dependencies:
+ prelude-ls "~1.1.2"
+
+typedarray@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+
+uglify-es@^3.3.7:
+ version "3.3.9"
+ resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"
+ dependencies:
+ commander "~2.13.0"
+ source-map "~0.6.1"
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+
+validate-npm-package-license@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
+ dependencies:
+ spdx-correct "~1.0.0"
+ spdx-expression-parse "~1.0.0"
+
+vlq@^0.2.2:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
+
+which@^1.2.9:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
+ dependencies:
+ isexe "^2.0.0"
+
+wordwrap@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+
+write@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
+ dependencies:
+ mkdirp "^0.5.1"
+
+yallist@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 7a1a505398..cbbee9fae8 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -152,9 +152,9 @@ class BaseTest < ActiveSupport::TestCase
assert_equal(2, email.parts.length)
assert_equal("multipart/mixed", email.mime_type)
assert_equal("text/html", email.parts[0].mime_type)
- assert_equal("Attachment with content", email.parts[0].body.encoded)
+ assert_equal("Attachment with content", email.parts[0].decoded)
assert_equal("application/pdf", email.parts[1].mime_type)
- assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded)
+ assert_equal("This is test File content", email.parts[1].decoded)
end
test "adds the given :body as part" do
@@ -162,9 +162,9 @@ class BaseTest < ActiveSupport::TestCase
assert_equal(2, email.parts.length)
assert_equal("multipart/mixed", email.mime_type)
assert_equal("text/plain", email.parts[0].mime_type)
- assert_equal("I'm the eggman", email.parts[0].body.encoded)
+ assert_equal("I'm the eggman", email.parts[0].decoded)
assert_equal("application/pdf", email.parts[1].mime_type)
- assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded)
+ assert_equal("This is test File content", email.parts[1].decoded)
end
test "can embed an inline attachment" do
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 8205d79dd2..5554d4e6b8 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,34 @@
+* 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
+
+ Previously if a dynamic source returned a symbol such as :self it
+ would be converted to a string implicity, e.g:
+
+ policy.default_src -> { :self }
+
+ would generate the header:
+
+ Content-Security-Policy: default-src self
+
+ and now it generates:
+
+ Content-Security-Policy: default-src 'self'
+
+ *Andrew White*
+
+* Add `ActionController::Parameters#each_value`.
+
+ *Lukáš Zapletal*
+
* Deprecate `ActionDispatch::Http::ParameterFilter` in favor of `ActiveSupport::ParameterFilter`.
*Yoshiyuki Kinjo*
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index 93ffff1bd6..c276ee57c0 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -12,7 +12,7 @@ module ActionController
#
# An API Controller is different from a normal controller in the sense that
# by default it doesn't include a number of features that are usually required
- # by browser access only: layouts and templates rendering, cookies, sessions,
+ # by browser access only: layouts and templates rendering,
# flash, assets, and so on. This makes the entire controller stack thinner,
# suitable for API applications. It doesn't mean you won't have such
# features if you need them: they're all available for you to include in
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 6de1fb2c19..afbd38e7fe 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -18,14 +18,17 @@ module ActionController
def process_action(event)
info do
- payload = event.payload
+ payload = event.payload
additions = ActionController::Base.log_process_action(payload)
-
status = payload[:status]
+
if status.nil? && payload[:exception].present?
exception_class_name = payload[:exception].first
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
+
+ additions << "Allocations: #{event.allocations}"
+
message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
message << " (#{additions.join(" | ")})" unless additions.empty?
message << "\n\n" if defined?(Rails.env) && Rails.env.development?
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 1482b2999a..083b762f5a 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -280,33 +280,35 @@ module ActionController
raise error if error
end
- # Spawn a new thread to serve up the controller in. This is to get
- # around the fact that Rack isn't based around IOs and we need to use
- # a thread to stream data from the response bodies. Nobody should call
- # this method except in Rails internals. Seriously!
- def new_controller_thread # :nodoc:
- Thread.new {
- t2 = Thread.current
- t2.abort_on_exception = true
- yield
- }
+ def response_body=(body)
+ super
+ response.close if response
end
- def log_error(exception)
- logger = ActionController::Base.logger
- return unless logger
+ private
- logger.fatal do
- message = +"\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << exception.backtrace.join("\n ")
- "#{message}\n\n"
+ # Spawn a new thread to serve up the controller in. This is to get
+ # around the fact that Rack isn't based around IOs and we need to use
+ # a thread to stream data from the response bodies. Nobody should call
+ # this method except in Rails internals. Seriously!
+ def new_controller_thread # :nodoc:
+ Thread.new {
+ t2 = Thread.current
+ t2.abort_on_exception = true
+ yield
+ }
end
- end
- def response_body=(body)
- super
- response.close if response
- end
+ def log_error(exception)
+ logger = ActionController::Base.logger
+ return unless logger
+
+ logger.fatal do
+ message = +"\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << exception.backtrace.join("\n ")
+ "#{message}\n\n"
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index c1272ce667..04922b0715 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -352,7 +352,7 @@ module ActionController
# the same way as <tt>Hash#each_value</tt>.
def each_value(&block)
@parameters.each_pair do |key, value|
- yield [convert_hashes_to_parameters(key, value)]
+ yield convert_hashes_to_parameters(key, value)
end
end
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
index 50953e32b5..b1e5a28be5 100644
--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -22,7 +22,8 @@ module ActionDispatch #:nodoc:
if policy = request.content_security_policy
nonce = request.content_security_policy_nonce
- headers[header_name(request)] = policy.build(request.controller_instance, nonce)
+ context = request.controller_instance || request
+ headers[header_name(request)] = policy.build(context, nonce)
end
response
@@ -257,7 +258,8 @@ module ActionDispatch #:nodoc:
if context.nil?
raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
else
- context.instance_exec(&source)
+ resolved = context.instance_exec(&source)
+ resolved.is_a?(Symbol) ? apply_mapping(resolved) : resolved
end
else
raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index dd74695229..c3e0ea3c89 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-# -*- frozen-string-literal: true -*-
-
require "singleton"
require "active_support/core_ext/string/starts_ends_with"
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index fedf622e90..2ae75b0da8 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -378,8 +378,6 @@ module ActionDispatch
@disable_clear_and_finalize = false
@finalized = false
@env_key = "ROUTES_#{object_id}_SCRIPT_NAME"
- @url_helpers = nil
- @deferred_classes = []
@set = Journey::Routes.new
@router = Journey::Router.new @set
@@ -435,25 +433,6 @@ module ActionDispatch
end
private :eval_block
- def include_helpers(klass, include_path_helpers)
- if @finalized
- include_helpers_now klass, include_path_helpers
- else
- @deferred_classes << [klass, include_path_helpers]
- end
- end
-
- def include_helpers_now(klass, include_path_helpers)
- namespace = klass.module_parents.detect { |m| m.respond_to?(:railtie_include_helpers) }
-
- if namespace && namespace.railtie_namespace.routes != self
- namespace.railtie_include_helpers(klass, include_path_helpers)
- else
- klass.include(url_helpers(include_path_helpers))
- end
- end
- private :include_helpers_now
-
def finalize!
return if @finalized
@append.each { |blk| eval_block(blk) }
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index 9f1fb3d042..7789e654d5 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -77,11 +77,15 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
test "each_value carries permitted status" do
@params.permit!
- @params["person"].each_value { |value| assert(value.permitted?) if value == 32 }
+ @params.each_value do |value|
+ assert_predicate(value, :permitted?)
+ end
end
test "each_value carries unpermitted status" do
- @params["person"].each_value { |value| assert_not(value.permitted?) if value == 32 }
+ @params.each_value do |value|
+ assert_not_predicate(value, :permitted?)
+ end
end
test "each_key converts to hash for permitted" do
diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb
index 13ad22b5c5..c8c885f35c 100644
--- a/actionpack/test/dispatch/content_security_policy_test.rb
+++ b/actionpack/test/dispatch/content_security_policy_test.rb
@@ -260,12 +260,13 @@ class DefaultContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationT
ROUTES.draw do
scope module: "default_content_security_policy_integration_test" do
get "/", to: "policy#index"
+ get "/redirect", to: redirect("/")
end
end
POLICY = ActionDispatch::ContentSecurityPolicy.new do |p|
- p.default_src :self
- p.script_src :https
+ p.default_src -> { :self }
+ p.script_src -> { :https }
end
class PolicyConfigMiddleware
@@ -295,14 +296,19 @@ class DefaultContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationT
def test_adds_nonce_to_script_src_content_security_policy_only_once
get "/"
get "/"
+ assert_response :success
+ assert_policy "default-src 'self'; script-src https: 'nonce-iyhD0Yc0W+c='"
+ end
+
+ def test_redirect_works_with_dynamic_sources
+ get "/redirect"
+ assert_response :redirect
assert_policy "default-src 'self'; script-src https: 'nonce-iyhD0Yc0W+c='"
end
private
def assert_policy(expected, report_only: false)
- assert_response :success
-
if report_only
expected_header = "Content-Security-Policy-Report-Only"
unexpected_header = "Content-Security-Policy"
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 252fce6472..fd8bae0cdf 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,15 @@
+* Add allocations to template rendering instrumentation.
+
+ Adds the allocations for template and partial rendering to the server output on render.
+
+ ```
+ Rendered posts/_form.html.erb (Duration: 7.1ms | Allocations: 6004)
+ Rendered posts/new.html.erb within layouts/application (Duration: 8.3ms | Allocations: 6654)
+ Completed 200 OK in 858ms (Views: 848.4ms | ActiveRecord: 0.4ms | Allocations: 1539564)
+ ```
+
+ *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.
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 6e769aa560..c2caa77afb 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1130,6 +1130,9 @@ module ActionView
# text_field(:post, :title, class: "create_input")
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
#
+ # text_field(:post, :title, maxlength: 30, class: "title_input")
+ # # => <input type="text" id="post_title" name="post[title]" maxlength="30" size="30" value="#{@post.title}" class="title_input" />
+ #
# text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
# # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
#
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index db07b9d7fb..227f025385 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -18,7 +18,7 @@ module ActionView
info do
message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
- message << " (#{event.duration.round(1)}ms)"
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
end
end
@@ -26,7 +26,7 @@ module ActionView
info do
message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
- message << " (#{event.duration.round(1)}ms)"
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
message
end
@@ -37,7 +37,7 @@ module ActionView
info do
" Rendered collection of #{from_rails_root(identifier)}" \
- " #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
+ " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
end
end
diff --git a/actionview/test/activerecord/controller_runtime_test.rb b/actionview/test/activerecord/controller_runtime_test.rb
index 7cbd3aaf89..563044f11e 100644
--- a/actionview/test/activerecord/controller_runtime_test.rb
+++ b/actionview/test/activerecord/controller_runtime_test.rb
@@ -68,7 +68,7 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase
wait
assert_equal 2, @logger.logged(:info).size
- assert_match(/\(Views: [\d.]+ms \| ActiveRecord: [\d.]+ms\)/, @logger.logged(:info)[1])
+ assert_match(/\(Views: [\d.]+ms \| ActiveRecord: [\d.]+ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[1])
end
def test_runtime_reset_before_requests
@@ -77,20 +77,20 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase
wait
assert_equal 2, @logger.logged(:info).size
- assert_match(/\(Views: [\d.]+ms \| ActiveRecord: 0\.0ms\)/, @logger.logged(:info)[1])
+ assert_match(/\(Views: [\d.]+ms \| ActiveRecord: [\d.]+ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[1])
end
def test_log_with_active_record_when_post
post :create
wait
- assert_match(/ActiveRecord: ([1-9][\d.]+)ms\)/, @logger.logged(:info)[2])
+ assert_match(/ActiveRecord: ([1-9][\d.]+)ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[2])
end
def test_log_with_active_record_when_redirecting
get :redirect
wait
assert_equal 3, @logger.logged(:info).size
- assert_match(/\(ActiveRecord: [\d.]+ms\)/, @logger.logged(:info)[2])
+ assert_match(/\(ActiveRecord: [\d.]+ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[2])
end
def test_include_time_query_time_after_rendering
@@ -98,6 +98,6 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase
wait
assert_equal 2, @logger.logged(:info).size
- assert_match(/\(Views: [\d.]+ms \| ActiveRecord: ([1-9][\d.]+)ms\)/, @logger.logged(:info)[1])
+ assert_match(/\(Views: [\d.]+ms \| ActiveRecord: ([1-9][\d.]+)ms \| Allocations: [\d.]+\)/, @logger.logged(:info)[1])
end
end
diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb
index 7f4fd25573..9fcf80bb24 100644
--- a/actionview/test/template/log_subscriber_test.rb
+++ b/actionview/test/template/log_subscriber_test.rb
@@ -129,14 +129,14 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
wait
*, cached_inner, uncached_outer = @logger.logged(:info)
assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache miss\]/, cached_inner)
- assert_match(/Rendered test\/_nested_cached_customer\.erb \(.*?ms\)$/, uncached_outer)
+ assert_match(/Rendered test\/_nested_cached_customer\.erb \(Duration: .*?ms \| Allocations: .*?\)$/, uncached_outer)
# Second render hits the cache for the _cached_customer partial. Outer template's log shouldn't be affected.
@view.render(partial: "test/nested_cached_customer", locals: { cached_customer: Customer.new("Stan") })
wait
*, cached_inner, uncached_outer = @logger.logged(:info)
assert_match(/Rendered test\/_cached_customer\.erb (.*) \[cache hit\]/, cached_inner)
- assert_match(/Rendered test\/_nested_cached_customer\.erb \(.*?ms\)$/, uncached_outer)
+ assert_match(/Rendered test\/_nested_cached_customer\.erb \(Duration: .*?ms \| Allocations: .*?\)$/, uncached_outer)
end
end
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index af5c197bac..8bbecd5a5a 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,12 @@
+* Restore HashWithIndifferentAccess support to ActiveJob::Arguments.deserialize.
+
+ *Gannon McGibbon*
+
+* Include deserialized arguments in job instances returned from
+ `assert_enqueued_with` and `assert_performed_with`
+
+ *Alan Wu*
+
* Allow `assert_enqueued_with`/`assert_performed_with` methods to accept
a proc for the `args` argument. This is useful to check if only a subset of arguments
matches your expectations.
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb
index 8dcf588f35..31347194d2 100644
--- a/activejob/lib/active_job/arguments.rb
+++ b/activejob/lib/active_job/arguments.rb
@@ -147,7 +147,10 @@ module ActiveJob
end
def transform_symbol_keys(hash, symbol_keys)
- hash.transform_keys do |key|
+ # NOTE: HashWithIndifferentAccess#transform_keys always
+ # returns stringified keys with indifferent access
+ # so we call #to_h here to ensure keys are symbolized.
+ hash.to_h.transform_keys do |key|
if symbol_keys.include?(key)
key.to_sym
else
diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb
index 0abee4ed98..416be83c24 100644
--- a/activejob/lib/active_job/logging.rb
+++ b/activejob/lib/active_job/logging.rb
@@ -93,8 +93,12 @@ module ActiveJob
ex = event.payload[:error]
wait = event.payload[:wait]
- error do
- "Retrying #{job.class} in #{wait.inspect} seconds, due to a #{ex&.class.inspect}. The original exception was #{ex&.cause.inspect}."
+ info do
+ if ex
+ "Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
+ else
+ "Retrying #{job.class} in #{wait.to_i} seconds."
+ end
end
end
@@ -103,7 +107,7 @@ module ActiveJob
ex = event.payload[:error]
error do
- "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts. The original exception was #{ex.cause.inspect}."
+ "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
end
end
@@ -112,7 +116,7 @@ module ActiveJob
ex = event.payload[:error]
error do
- "Discarded #{job.class} due to a #{ex.class}. The original exception was #{ex.cause.inspect}."
+ "Discarded #{job.class} due to a #{ex.class}."
end
end
diff --git a/activejob/lib/active_job/queue_adapters/async_adapter.rb b/activejob/lib/active_job/queue_adapters/async_adapter.rb
index ebf6f384e3..53a7e3d53e 100644
--- a/activejob/lib/active_job/queue_adapters/async_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/async_adapter.rb
@@ -31,7 +31,7 @@ module ActiveJob
# jobs. Since jobs share a single thread pool, long-running jobs will block
# short-lived jobs. Fine for dev/test; bad for production.
class AsyncAdapter
- # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
+ # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadPoolExecutor.html] for executor options.
def initialize(**executor_options)
@scheduler = Scheduler.new(**executor_options)
end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 261b68d4f8..0deb68d0d2 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -197,7 +197,7 @@ module ActiveJob
# assert_performed_jobs 2
# end
#
- # If a block is passed, that block should cause the specified number of
+ # If a block is passed, asserts that the block will cause the specified number of
# jobs to be performed.
#
# def test_jobs_again
@@ -279,7 +279,7 @@ module ActiveJob
# end
# end
#
- # If a block is passed, that block should not cause any job to be performed.
+ # If a block is passed, asserts that the block will not cause any job to be performed.
#
# def test_jobs_again
# assert_no_performed_jobs do
@@ -347,7 +347,7 @@ module ActiveJob
# end
#
#
- # If a block is passed, that block should cause the job to be
+ # If a block is passed, asserts that the block will cause the job to be
# enqueued with the given arguments.
#
# def test_assert_enqueued_with
@@ -594,8 +594,7 @@ module ActiveJob
def flush_enqueued_jobs(only: nil, except: nil, queue: nil)
enqueued_jobs_with(only: only, except: except, queue: queue) do |payload|
- args = ActiveJob::Arguments.deserialize(payload[:args])
- instantiate_job(payload.merge(args: args)).perform_now
+ instantiate_job(payload).perform_now
queue_adapter.performed_jobs << payload
end
end
@@ -613,7 +612,8 @@ module ActiveJob
end
def instantiate_job(payload)
- job = payload[:job].new(*payload[:args])
+ args = ActiveJob::Arguments.deserialize(payload[:args])
+ job = payload[:job].new(*args)
job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
job.queue_name = payload[:queue]
job
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index f07529d743..8b2981926f 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -73,6 +73,7 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
string_key = { "a" => 1, "_aj_symbol_keys" => [] }
another_string_key = { "a" => 1 }
indifferent_access = { "a" => 1, "_aj_hash_with_indifferent_access" => true }
+ indifferent_access_symbol_key = symbol_key.with_indifferent_access
assert_equal(
{ a: 1 },
@@ -90,6 +91,10 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
{ "a" => 1 },
ActiveJob::Arguments.deserialize([indifferent_access]).first
)
+ assert_equal(
+ { a: 1 },
+ ActiveJob::Arguments.deserialize([indifferent_access_symbol_key]).first
+ )
end
test "should maintain hash with indifferent access" do
diff --git a/activejob/test/cases/logging_test.rb b/activejob/test/cases/logging_test.rb
index b5bf40c83b..48ef39aaca 100644
--- a/activejob/test/cases/logging_test.rb
+++ b/activejob/test/cases/logging_test.rb
@@ -169,19 +169,19 @@ class LoggingTest < ActiveSupport::TestCase
def test_enqueue_retry_logging
perform_enqueued_jobs do
RetryJob.perform_later "DefaultsError", 2
- assert_match(/Retrying RetryJob in \d+ seconds, due to a DefaultsError\. The original exception was nil\./, @logger.messages)
+ assert_match(/Retrying RetryJob in 3 seconds, due to a DefaultsError\./, @logger.messages)
end
end
def test_enqueue_retry_logging_on_retry_job
perform_enqueued_jobs { RescueJob.perform_later "david" }
- assert_match(/Retrying RescueJob in nil seconds, due to a nil\. The original exception was nil\./, @logger.messages)
+ assert_match(/Retrying RescueJob in 0 seconds\./, @logger.messages)
end
def test_retry_stopped_logging
perform_enqueued_jobs do
RetryJob.perform_later "CustomCatchError", 6
- assert_match(/Stopped retrying RetryJob due to a CustomCatchError, which reoccurred on \d+ attempts\. The original exception was #<CustomCatchError: CustomCatchError>\./, @logger.messages)
+ assert_match(/Stopped retrying RetryJob due to a CustomCatchError, which reoccurred on \d+ attempts\./, @logger.messages)
end
end
@@ -190,7 +190,7 @@ class LoggingTest < ActiveSupport::TestCase
begin
RetryJob.perform_later "DefaultsError", 6
rescue DefaultsError
- assert_match(/Stopped retrying RetryJob due to a DefaultsError, which reoccurred on \d+ attempts\. The original exception was #<DefaultsError: DefaultsError>\./, @logger.messages)
+ assert_match(/Stopped retrying RetryJob due to a DefaultsError, which reoccurred on \d+ attempts\./, @logger.messages)
end
end
end
@@ -198,7 +198,7 @@ class LoggingTest < ActiveSupport::TestCase
def test_discard_logging
perform_enqueued_jobs do
RetryJob.perform_later "DiscardableError", 2
- assert_match(/Discarded RetryJob due to a DiscardableError\. The original exception was nil\./, @logger.messages)
+ assert_match(/Discarded RetryJob due to a DiscardableError\./, @logger.messages)
end
end
end
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 83c71ab1c4..8dc32037ff 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -476,23 +476,23 @@ class EnqueuedJobsTest < ActiveJob::TestCase
def test_assert_enqueued_with_returns
job = assert_enqueued_with(job: LoggingJob) do
- LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3)
+ LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3, keyword: true)
end
assert_instance_of LoggingJob, job
assert_in_delta 5.minutes.from_now, job.scheduled_at, 1
assert_equal "default", job.queue_name
- assert_equal [1, 2, 3], job.arguments
+ assert_equal [1, 2, 3, { keyword: true }], job.arguments
end
def test_assert_enqueued_with_with_no_block_returns
- LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3)
+ LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3, keyword: true)
job = assert_enqueued_with(job: LoggingJob)
assert_instance_of LoggingJob, job
assert_in_delta 5.minutes.from_now, job.scheduled_at, 1
assert_equal "default", job.queue_name
- assert_equal [1, 2, 3], job.arguments
+ assert_equal [1, 2, 3, { keyword: true }], job.arguments
end
def test_assert_enqueued_with_failure
@@ -1515,26 +1515,26 @@ class PerformedJobsTest < ActiveJob::TestCase
end
def test_assert_performed_with_returns
- job = assert_performed_with(job: NestedJob, queue: "default") do
- NestedJob.perform_later
+ job = assert_performed_with(job: LoggingJob, queue: "default") do
+ LoggingJob.perform_later(keyword: :sym)
end
- assert_instance_of NestedJob, job
+ assert_instance_of LoggingJob, job
assert_nil job.scheduled_at
- assert_equal [], job.arguments
+ assert_equal [{ keyword: :sym }], job.arguments
assert_equal "default", job.queue_name
end
def test_assert_performed_with_without_block_returns
- NestedJob.perform_later
+ LoggingJob.perform_later(keyword: :sym)
perform_enqueued_jobs
- job = assert_performed_with(job: NestedJob, queue: "default")
+ job = assert_performed_with(job: LoggingJob, queue: "default")
- assert_instance_of NestedJob, job
+ assert_instance_of LoggingJob, job
assert_nil job.scheduled_at
- assert_equal [], job.arguments
+ assert_equal [{ keyword: :sym }], job.arguments
assert_equal "default", job.queue_name
end
diff --git a/activejob/test/support/integration/adapters/backburner.rb b/activejob/test/support/integration/adapters/backburner.rb
index eb179011d9..1163ae8178 100644
--- a/activejob/test/support/integration/adapters/backburner.rb
+++ b/activejob/test/support/integration/adapters/backburner.rb
@@ -8,7 +8,8 @@ module BackburnerJobsManager
end
unless can_run?
puts "Cannot run integration tests for backburner. To be able to run integration tests for backburner you need to install and start beanstalkd.\n"
- exit
+ status = ENV["CI"] ? false : true
+ exit status
end
end
diff --git a/activejob/test/support/integration/adapters/que.rb b/activejob/test/support/integration/adapters/que.rb
index 2e7d327b37..f231e5e12d 100644
--- a/activejob/test/support/integration/adapters/que.rb
+++ b/activejob/test/support/integration/adapters/que.rb
@@ -32,7 +32,8 @@ module QueJobsManager
rescue Sequel::DatabaseConnectionError
puts "Cannot run integration tests for que. To be able to run integration tests for que you need to install and start postgresql.\n"
- exit
+ status = ENV["CI"] ? false : true
+ exit status
end
def stop_workers
diff --git a/activejob/test/support/integration/adapters/queue_classic.rb b/activejob/test/support/integration/adapters/queue_classic.rb
index dbbdc12b9d..2b5375461a 100644
--- a/activejob/test/support/integration/adapters/queue_classic.rb
+++ b/activejob/test/support/integration/adapters/queue_classic.rb
@@ -30,7 +30,8 @@ module QueueClassicJobsManager
rescue PG::ConnectionBad
puts "Cannot run integration tests for queue_classic. To be able to run integration tests for queue_classic you need to install and start postgresql.\n"
- exit
+ status = ENV["CI"] ? false : true
+ exit status
end
def stop_workers
diff --git a/activejob/test/support/integration/adapters/sneakers.rb b/activejob/test/support/integration/adapters/sneakers.rb
index 965e6e2e6c..eb8d4cc2d5 100644
--- a/activejob/test/support/integration/adapters/sneakers.rb
+++ b/activejob/test/support/integration/adapters/sneakers.rb
@@ -1,19 +1,8 @@
# frozen_string_literal: true
require "sneakers/runner"
-require "sneakers/publisher"
require "timeout"
-module Sneakers
- class Publisher
- def safe_ensure_connected
- @mutex.synchronize do
- ensure_connection! unless connected?
- end
- end
- end
-end
-
module SneakersJobsManager
def setup
ActiveJob::Base.queue_adapter = :sneakers
@@ -29,7 +18,8 @@ module SneakersJobsManager
log: Rails.root.join("log/sneakers.log").to_s
unless can_run?
puts "Cannot run integration tests for sneakers. To be able to run integration tests for sneakers you need to install and start rabbitmq.\n"
- exit
+ status = ENV["CI"] ? false : true
+ exit status
end
end
@@ -79,7 +69,7 @@ module SneakersJobsManager
def bunny_publisher
@bunny_publisher ||= begin
p = ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper.send(:publisher)
- p.safe_ensure_connected
+ p.ensure_connection!
p
end
end
diff --git a/activemodel/lib/active_model/attribute.rb b/activemodel/lib/active_model/attribute.rb
index 3f19cda07b..75f60d205e 100644
--- a/activemodel/lib/active_model/attribute.rb
+++ b/activemodel/lib/active_model/attribute.rb
@@ -206,6 +206,7 @@ module ActiveModel
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
end
alias_method :with_value_from_user, :with_value_from_database
+ alias_method :with_cast_value, :with_value_from_database
end
class Uninitialized < Attribute # :nodoc:
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 1ad9071cc2..d8352343e9 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -474,5 +474,43 @@ module ActiveModel
def _read_attribute(attr)
__send__(attr)
end
+
+ module AttrNames # :nodoc:
+ DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
+
+ # We want to generate the methods via module_eval rather than
+ # define_method, because define_method is slower on dispatch.
+ # Evaluating many similar methods may use more memory as the instruction
+ # sequences are duplicated and cached (in MRI). define_method may
+ # be slower on dispatch, but if you're careful about the closure
+ # created, then define_method will consume much less memory.
+ #
+ # But sometimes the database might return columns with
+ # characters that are not allowed in normal method names (like
+ # 'my_column(omg)'. So to work around this we first define with
+ # the __temp__ identifier, and then use alias method to rename
+ # it to what we want.
+ #
+ # We are also defining a constant to hold the frozen string of
+ # the attribute name. Using a constant means that we do not have
+ # to allocate an object on each call to the attribute method.
+ # Making it frozen means that it doesn't get duped when used to
+ # key the @attributes in read_attribute.
+ def self.define_attribute_accessor_method(mod, attr_name, writer: false)
+ method_name = "#{attr_name}#{'=' if writer}"
+ if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
+ yield method_name, "'#{attr_name}'.freeze"
+ else
+ safe_name = attr_name.unpack1("h*")
+ const_name = "ATTR_#{safe_name}"
+ const_set(const_name, attr_name) unless const_defined?(const_name)
+ temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
+ attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
+ yield temp_method_name, attr_name_expr
+ mod.send(:alias_method, method_name, temp_method_name)
+ mod.send(:undef_method, temp_method_name)
+ end
+ end
+ end
end
end
diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb
index 41fe5168f3..c3a446098c 100644
--- a/activemodel/lib/active_model/attributes.rb
+++ b/activemodel/lib/active_model/attributes.rb
@@ -29,17 +29,16 @@ module ActiveModel
private
def define_method_attribute=(name)
- safe_name = name.unpack1("h*")
- ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
-
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__#{safe_name}=(value)
- name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
- write_attribute(name, value)
- end
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
- undef_method :__temp__#{safe_name}=
- STR
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
+ generated_attribute_methods, name, writer: true,
+ ) do |temp_method_name, attr_name_expr|
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{temp_method_name}(value)
+ name = #{attr_name_expr}
+ write_attribute(name, value)
+ end
+ RUBY
+ end
end
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
@@ -97,15 +96,4 @@ module ActiveModel
write_attribute(attribute_name, value)
end
end
-
- module AttributeMethods #:nodoc:
- AttrNames = Module.new {
- def self.set_name_cache(name, value)
- const_name = "ATTR_#{name}"
- unless const_defined? const_name
- const_set const_name, -value
- end
- end
- }
- end
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 093052a70c..f0e1242554 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -151,7 +151,7 @@ module ActiveModel
@mutations_from_database = nil
end
- # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
+ # Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
#
# person.changed? # => false
# person.name = 'bob'
diff --git a/activemodel/lib/active_model/type/date_time.rb b/activemodel/lib/active_model/type/date_time.rb
index 9641bf45ee..d48598376e 100644
--- a/activemodel/lib/active_model/type/date_time.rb
+++ b/activemodel/lib/active_model/type/date_time.rb
@@ -39,9 +39,9 @@ module ActiveModel
end
def value_from_multiparameter_assignment(values_hash)
- missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
- if missing_parameter
- raise ArgumentError, missing_parameter
+ missing_parameters = (1..3).select { |key| !values_hash.key?(key) }
+ if missing_parameters.any?
+ raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
end
super
end
diff --git a/activemodel/test/cases/type/date_time_test.rb b/activemodel/test/cases/type/date_time_test.rb
index 60f62becc2..74b47d1b4d 100644
--- a/activemodel/test/cases/type/date_time_test.rb
+++ b/activemodel/test/cases/type/date_time_test.rb
@@ -25,6 +25,17 @@ module ActiveModel
end
end
+ def test_hash_to_time
+ type = Type::DateTime.new
+ assert_equal ::Time.utc(2018, 10, 15, 0, 0, 0), type.cast(1 => 2018, 2 => 10, 3 => 15)
+ end
+
+ def test_hash_with_wrong_keys
+ type = Type::DateTime.new
+ error = assert_raises(ArgumentError) { type.cast(a: 1) }
+ assert_equal "Provided hash {:a=>1} doesn't contain necessary keys: [1, 2, 3]", error.message
+ end
+
private
def with_timezone_config(default:)
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 96fd6a62c6..9d80bbe51f 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,110 @@
+* `update_columns` now correctly raises `ActiveModel::MissingAttributeError`
+ if the attribute does not exist.
+
+ *Sean Griffin*
+
+* Add support for hash and url configs in database hash of `ActiveRecord::Base.connected_to`.
+
+ ````
+ User.connected_to(database: { writing: "postgres://foo" }) do
+ User.create!(name: "Gannon")
+ end
+
+ config = { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
+ User.connected_to(database: { reading: config }) do
+ User.count
+ end
+ ````
+
+ *Gannon McGibbon*
+
+* Support default expression for MySQL.
+
+ MySQL 8.0.13 and higher supports default value to be a function or expression.
+
+ https://dev.mysql.com/doc/refman/8.0/en/create-table.html
+
+ *Ryuta Kamizono*
+
+* Support expression indexes for MySQL.
+
+ MySQL 8.0.13 and higher supports functional key parts that index
+ expression values rather than column or column prefix values.
+
+ https://dev.mysql.com/doc/refman/8.0/en/create-index.html
+
+ *Ryuta Kamizono*
+
+* Fix collection cache key with limit and custom select to avoid ambiguous timestamp column error.
+
+ Fixes #33056.
+
+ *Federico Martinez*
+
+* Add basic API for connection switching to support multiple databases.
+
+ 1) Adds a `connects_to` method for models to connect to multiple databases. Example:
+
+ ```
+ class AnimalsModel < ApplicationRecord
+ self.abstract_class = true
+
+ connects_to database: { writing: :animals_primary, reading: :animals_replica }
+ end
+
+ class Dog < AnimalsModel
+ # connected to both the animals_primary db for writing and the animals_replica for reading
+ end
+ ```
+
+ 2) Adds a `connected_to` block method for switching connection roles or connecting to
+ a database that the model didn't connect to. Connecting to the database in this block is
+ useful when you have another defined connection, for example `slow_replica` that you don't
+ want to connect to by default but need in the console, or a specific code block.
+
+ ```
+ ActiveRecord::Base.connected_to(role: :reading) do
+ Dog.first # finds dog from replica connected to AnimalsBase
+ Book.first # doesn't have a reading connection, will raise an error
+ end
+ ```
+
+ ```
+ ActiveRecord::Base.connected_to(database: :slow_replica) do
+ SlowReplicaModel.first # if the db config has a slow_replica configuration this will be used to do the lookup, otherwise this will throw an exception
+ end
+ ```
+
+ *Eileen M. Uchitelle*
+
+* Enum raises on invalid definition values
+
+ When defining a Hash enum it can be easy to use [] instead of {}. This
+ commit checks that only valid definition values are provided, those can
+ be a Hash, an array of Symbols or an array of Strings. Otherwise it
+ raises an ArgumentError.
+
+ Fixes #33961
+
+ *Alberto Almagro*
+
+* Reloading associations now clears the Query Cache like `Persistence#reload` does.
+
+ ```
+ class Post < ActiveRecord::Base
+ has_one :category
+ belongs_to :author
+ has_many :comments
+ end
+
+ # Each of the following will now clear the query cache.
+ post.reload_category
+ post.reload_author
+ post.comments.reload
+ ```
+
+ *Christophe Maximin*
+
* Added `index` option for `change_table` migration helpers.
With this change you can create indexes while adding new
columns into the existing tables.
@@ -74,13 +181,13 @@
specify sensitive attributes to specific model.
```
- Rails.application.config.filter_parameters += [:credit_card_number]
- Account.last.inspect # => #<Account id: 123, name: "DHH", credit_card_number: [FILTERED] ...>
+ Rails.application.config.filter_parameters += [:credit_card_number, /phone/]
+ Account.last.inspect # => #<Account id: 123, name: "DHH", credit_card_number: [FILTERED], telephone_number: [FILTERED] ...>
SecureAccount.filter_attributes += [:name]
SecureAccount.last.inspect # => #<SecureAccount id: 42, name: [FILTERED], credit_card_number: [FILTERED] ...>
```
- *Zhang Kang*
+ *Zhang Kang*, *Yoshiyuki Kinjo*
* Deprecate `column_name_length`, `table_name_length`, `columns_per_table`,
`indexes_per_table`, `columns_per_multicolumn_index`, `sql_query_length`,
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index ab1e7ad269..fb1df00dc8 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1294,7 +1294,7 @@ module ActiveRecord
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
# * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
+ # * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there are any associated records.
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
#
# If using with the <tt>:through</tt> option, the association on the join model must be
@@ -1437,7 +1437,7 @@ module ActiveRecord
# * <tt>:destroy</tt> causes the associated object to also be destroyed
# * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
# * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
+ # * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there is an associated record
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
#
# Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 4686b9f517..bf4942aac8 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -40,7 +40,9 @@ module ActiveRecord
end
# Reloads the \target and returns +self+ on success.
- def reload
+ # The QueryCache is cleared if +force+ is true.
+ def reload(force = false)
+ klass.connection.clear_query_cache if force && klass
reset
reset_scope
load_target
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 9a30198b95..811fbfd927 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -1088,7 +1088,7 @@ module ActiveRecord
# person.pets.reload # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reload
- proxy_association.reload
+ proxy_association.reload(true)
reset_scope
end
@@ -1125,7 +1125,7 @@ module ActiveRecord
SpawnMethods,
].flat_map { |klass|
klass.public_instance_methods(false)
- } - self.public_instance_methods(false) - [:select] + [:scoping]
+ } - self.public_instance_methods(false) - [:select] + [:scoping, :values]
delegate(*delegate_methods, to: :scope)
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index cfab16a745..8e50cce102 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -26,7 +26,7 @@ module ActiveRecord
# Implements the reload reader method, e.g. foo.reload_bar for
# Foo.has_one :bar
def force_reload_reader
- klass.uncached { reload }
+ reload(true)
target
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 221ebea8ea..1e92ee3b96 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -22,15 +22,6 @@ module ActiveRecord
delegate :column_for_attribute, to: :class
end
- AttrNames = Module.new {
- def self.set_name_cache(name, value)
- const_name = "ATTR_#{name}"
- unless const_defined? const_name
- const_set const_name, -value
- end
- end
- }
-
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
class GeneratedAttributeMethods < Module #:nodoc:
@@ -270,21 +261,14 @@ module ActiveRecord
def respond_to?(name, include_private = false)
return false unless super
- case name
- when :to_partial_path
- name = "to_partial_path"
- when :to_model
- name = "to_model"
- else
- name = name.to_s
- end
-
# If the result is true then check for the select case.
# For queries selecting a subset of columns, return false for unselected columns.
# We check defined?(@attributes) not to issue warnings if called on objects that
# have been allocated but not yet initialized.
- if defined?(@attributes) && self.class.column_names.include?(name)
- return has_attribute?(name)
+ if defined?(@attributes)
+ if name = self.class.symbol_column_to_string(name.to_sym)
+ return has_attribute?(name)
+ end
end
true
@@ -345,14 +329,7 @@ module ActiveRecord
# # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
def attribute_for_inspect(attr_name)
value = read_attribute(attr_name)
-
- if value.is_a?(String) && value.length > 50
- "#{value[0, 50]}...".inspect
- elsif value.is_a?(Date) || value.is_a?(Time)
- %("#{value.to_s(:db)}")
- else
- value.inspect
- end
+ format_for_inspect(value)
end
# Returns +true+ if the specified +attribute+ has been set by the user or by a
@@ -472,6 +449,16 @@ module ActiveRecord
end
end
+ def format_for_inspect(value)
+ if value.is_a?(String) && value.length > 50
+ "#{value[0, 50]}...".inspect
+ elsif value.is_a?(Date) || value.is_a?(Time)
+ %("#{value.to_s(:db)}")
+ else
+ value.inspect
+ end
+ end
+
def readonly_attribute?(name)
self.class.readonly_attributes.include?(name)
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 903fe86e04..6e1275e990 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -8,42 +8,19 @@ module ActiveRecord
module ClassMethods # :nodoc:
private
- # We want to generate the methods via module_eval rather than
- # define_method, because define_method is slower on dispatch.
- # Evaluating many similar methods may use more memory as the instruction
- # sequences are duplicated and cached (in MRI). define_method may
- # be slower on dispatch, but if you're careful about the closure
- # created, then define_method will consume much less memory.
- #
- # But sometimes the database might return columns with
- # characters that are not allowed in normal method names (like
- # 'my_column(omg)'. So to work around this we first define with
- # the __temp__ identifier, and then use alias method to rename
- # it to what we want.
- #
- # We are also defining a constant to hold the frozen string of
- # the attribute name. Using a constant means that we do not have
- # to allocate an object on each call to the attribute method.
- # Making it frozen means that it doesn't get duped when used to
- # key the @attributes in read_attribute.
def define_method_attribute(name)
- safe_name = name.unpack1("h*")
- temp_method = "__temp__#{safe_name}"
-
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def #{temp_method}
- #{sync_with_transaction_state}
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
- _read_attribute(name) { |n| missing_attribute(n, caller) }
- end
- STR
-
- generated_attribute_methods.module_eval do
- alias_method name, temp_method
- undef_method temp_method
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
+ generated_attribute_methods, name
+ ) do |temp_method_name, attr_name_expr|
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{temp_method_name}
+ #{sync_with_transaction_state}
+ name = #{attr_name_expr}
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
+ end
+ RUBY
end
end
end
@@ -52,10 +29,9 @@ module ActiveRecord
# it has been typecast (for example, "2004-12-12" in a date column is cast
# to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name, &block)
- name = if self.class.attribute_alias?(attr_name)
- self.class.attribute_alias(attr_name).to_s
- else
- attr_name.to_s
+ name = attr_name.to_s
+ if self.class.attribute_alias?(name)
+ name = self.class.attribute_alias(name)
end
primary_key = self.class.primary_key
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 62743bc9d8..455e67e19b 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -13,19 +13,19 @@ module ActiveRecord
private
def define_method_attribute=(name)
- safe_name = name.unpack1("h*")
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__#{safe_name}=(value)
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
- #{sync_with_transaction_state}
- _write_attribute(name, value)
- end
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
- undef_method :__temp__#{safe_name}=
- STR
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
+ generated_attribute_methods, name, writer: true,
+ ) do |temp_method_name, attr_name_expr|
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{temp_method_name}(value)
+ name = #{attr_name_expr}
+ #{sync_with_transaction_state}
+ _write_attribute(name, value)
+ end
+ RUBY
+ end
end
end
@@ -33,10 +33,9 @@ module ActiveRecord
# specified +value+. Empty strings for Integer and Float columns are
# turned into +nil+.
def write_attribute(attr_name, value)
- name = if self.class.attribute_alias?(attr_name)
- self.class.attribute_alias(attr_name).to_s
- else
- attr_name.to_s
+ name = attr_name.to_s
+ if self.class.attribute_alias?(name)
+ name = self.class.attribute_alias(name)
end
primary_key = self.class.primary_key
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 1bffe89875..5407af85ea 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -95,7 +95,7 @@ module ActiveRecord
#
# private
# def delete_parents
- # self.class.delete_all "parent_id = #{id}"
+ # self.class.where(parent_id: id).delete_all
# end
# end
#
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 61581b0451..4b6db8a96c 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -20,9 +20,9 @@ module ActiveRecord
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
if collection.has_limit_or_offset?
- query = collection.select(column)
+ query = collection.select("#{column} AS collection_cache_key_timestamp")
subquery_alias = "subquery_for_cache_key"
- subquery_column = "#{subquery_alias}.#{timestamp_column}"
+ subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
subquery = query.arel.as(subquery_alias)
arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column)
else
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 015204c056..70607fda5a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -348,8 +348,8 @@ module ActiveRecord
#
# create_table :taggings do |t|
# t.references :tag, index: { name: 'index_taggings_on_tag_id' }
- # t.references :tagger, polymorphic: true, index: true
- # t.references :taggable, polymorphic: { default: 'Photo' }
+ # t.references :tagger, polymorphic: true
+ # t.references :taggable, polymorphic: { default: 'Photo' }, index: false
# end
def column(name, type, options = {})
name = name.to_s
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 723d8c318d..8a7020a799 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -846,17 +846,17 @@ module ActiveRecord
# [<tt>:null</tt>]
# Whether the column allows nulls. Defaults to true.
#
- # ====== Create a user_id bigint column
+ # ====== Create a user_id bigint column without a index
#
- # add_reference(:products, :user)
+ # add_reference(:products, :user, index: false)
#
# ====== Create a user_id string column
#
# add_reference(:products, :user, type: :string)
#
- # ====== Create supplier_id, supplier_type columns and appropriate index
+ # ====== Create supplier_id, supplier_type columns
#
- # add_reference(:products, :supplier, polymorphic: true, index: true)
+ # add_reference(:products, :supplier, polymorphic: true)
#
# ====== Create a supplier_id column with a unique index
#
@@ -884,7 +884,7 @@ module ActiveRecord
#
# ====== Remove the reference
#
- # remove_reference(:products, :user, index: true)
+ # remove_reference(:products, :user, index: false)
#
# ====== Remove polymorphic reference
#
@@ -892,7 +892,7 @@ module ActiveRecord
#
# ====== Remove the reference with a foreign key
#
- # remove_reference(:products, :user, index: true, foreign_key: true)
+ # remove_reference(:products, :user, foreign_key: true)
#
def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
if foreign_key
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index fa10f18cb7..0fe868478c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -125,6 +125,8 @@ module ActiveRecord
@advisory_locks_enabled = self.class.type_cast_config_to_boolean(
config.fetch(:advisory_locks, true)
)
+
+ check_version
end
def replica?
@@ -502,6 +504,9 @@ module ActiveRecord
end
private
+ def check_version
+ end
+
def type_map
@type_map ||= Type::TypeMap.new.tap do |mapping|
initialize_type_map(mapping)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index d40f38fb77..13c799b64a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -54,10 +54,6 @@ module ActiveRecord
super(connection, logger, config)
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
-
- if version < "5.5.8"
- raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8."
- end
end
def version #:nodoc:
@@ -76,6 +72,10 @@ module ActiveRecord
!mariadb? && version >= "8.0.1"
end
+ def supports_expression_index?
+ !mariadb? && version >= "8.0.13"
+ end
+
def supports_transaction_isolation?
true
end
@@ -535,6 +535,12 @@ module ActiveRecord
end
private
+ def check_version
+ if version < "5.5.8"
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8."
+ end
+ end
+
def combine_multi_statements(total_sql)
total_sql.each_with_object([]) do |sql, total_sql_chunks|
previous_packet = total_sql_chunks.last
@@ -622,6 +628,8 @@ module ActiveRecord
# See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
ER_DUP_ENTRY = 1062
ER_NOT_NULL_VIOLATION = 1048
+ ER_NO_REFERENCED_ROW = 1216
+ ER_ROW_IS_REFERENCED = 1217
ER_DO_NOT_HAVE_DEFAULT = 1364
ER_ROW_IS_REFERENCED_2 = 1451
ER_NO_REFERENCED_ROW_2 = 1452
@@ -638,7 +646,7 @@ module ActiveRecord
case error_number(exception)
when ER_DUP_ENTRY
RecordNotUnique.new(message)
- when ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
+ when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
InvalidForeignKey.new(message)
when ER_CANNOT_ADD_FOREIGN
mismatched_foreign_key(message)
diff --git a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
index 3dcb916d99..883747b84b 100644
--- a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
+++ b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
@@ -10,7 +10,12 @@ module ActiveRecord
super
end
- def visit_Arel_Nodes_In(*)
+ def visit_Arel_Nodes_In(o, collector)
+ @preparable = false
+ super
+ end
+
+ def visit_Arel_Nodes_NotIn(o, collector)
@preparable = false
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index e167c01802..4894fd1c08 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -35,13 +35,39 @@ module ActiveRecord
]
end
- indexes.last[-2] << row[:Column_name]
- indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part]
- indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
+ if row[:Expression]
+ expression = row[:Expression]
+ expression = +"(#{expression})" unless expression.start_with?("(")
+ indexes.last[-2] << expression
+ indexes.last[-1][:expressions] ||= {}
+ indexes.last[-1][:expressions][expression] = expression
+ indexes.last[-1][:orders][expression] = :desc if row[:Collation] == "D"
+ else
+ indexes.last[-2] << row[:Column_name]
+ indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part]
+ indexes.last[-1][:orders][row[:Column_name]] = :desc if row[:Collation] == "D"
+ end
end
end
- indexes.map { |index| IndexDefinition.new(*index) }
+ indexes.map do |index|
+ options = index.last
+
+ if expressions = options.delete(:expressions)
+ orders = options.delete(:orders)
+ lengths = options.delete(:lengths)
+
+ columns = index[-2].map { |name|
+ [ name.to_sym, expressions[name] || +quote_column_name(name) ]
+ }.to_h
+
+ index[-2] = add_options_for_index_columns(
+ columns, order: orders, length: lengths
+ ).values.join(", ")
+ end
+
+ IndexDefinition.new(*index)
+ end
end
def remove_column(table_name, column_name, type = nil, options = {})
@@ -80,10 +106,13 @@ module ActiveRecord
def new_column_from_field(table_name, field)
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(field[:Default])
- default, default_function = nil, field[:Default]
- else
- default, default_function = field[:Default], nil
+ default, default_function = field[:Default], nil
+
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
+ default, default_function = nil, default
+ elsif type_metadata.extra == "DEFAULT_GENERATED"
+ default = +"(#{default})" unless default.start_with?("(")
+ default, default_function = nil, default
end
MySQL::Column.new(
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index bc6eb11572..a11a786ec7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -5,7 +5,7 @@ gem "pg", ">= 0.18", "< 2.0"
require "pg"
# Use async_exec instead of exec_params on pg versions before 1.1
-class ::PG::Connection
+class ::PG::Connection # :nodoc:
unless self.public_method_defined?(:async_exec_params)
remove_method :exec_params
alias exec_params async_exec
@@ -43,9 +43,14 @@ module ActiveRecord
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
conn_params.slice!(*valid_conn_param_keys)
- # The postgres drivers don't allow the creation of an unconnected PG::Connection object,
- # so just pass a nil connection object for the time being.
- ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
+ conn = PG.connect(conn_params)
+ ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
+ rescue ::PG::Error => error
+ if error.message.include?("does not exist")
+ raise ActiveRecord::NoDatabaseError
+ else
+ raise
+ end
end
end
@@ -220,15 +225,11 @@ module ActiveRecord
@local_tz = nil
@max_identifier_length = nil
- connect
+ configure_connection
add_pg_encoders
@statements = StatementPool.new @connection,
self.class.type_cast_config_to_integer(config[:statement_limit])
- if postgresql_version < 90100
- raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
- end
-
add_pg_decoders
@type_map = Type::HashLookupTypeMap.new
@@ -410,6 +411,12 @@ module ActiveRecord
end
private
+ def check_version
+ if postgresql_version < 90100
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
+ end
+ end
+
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
VALUE_LIMIT_VIOLATION = "22001"
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
@@ -699,12 +706,6 @@ module ActiveRecord
def connect
@connection = PG.connect(@connection_parameters)
configure_connection
- rescue ::PG::Error => error
- if error.message.include?("does not exist")
- raise ActiveRecord::NoDatabaseError
- else
- raise
- end
end
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 81882f6cc1..e0355a316b 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -105,11 +105,6 @@ module ActiveRecord
@active = true
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
-
- if sqlite_version < "3.8.0"
- raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8."
- end
-
configure_connection
end
@@ -401,6 +396,12 @@ module ActiveRecord
end
private
+ 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."
+ end
+ end
+
def initialize_type_map(m = type_map)
super
register_class_with_limit m, %r(int)i, SQLite3Integer
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 18114f9e1c..777cb8a402 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -47,6 +47,99 @@ module ActiveRecord
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
# may be returned on an error.
def establish_connection(config_or_env = nil)
+ config_hash = resolve_config_for_connection(config_or_env)
+ connection_handler.establish_connection(config_hash)
+ end
+
+ # Connects a model to the databases specified. The +database+ keyword
+ # takes a hash consisting of a +role+ and a +database_key+.
+ #
+ # This will create a connection handler for switching between connections,
+ # look up the config hash using the +database_key+ and finally
+ # establishes a connection to that config.
+ #
+ # class AnimalsModel < ApplicationRecord
+ # self.abstract_class = true
+ #
+ # connects_to database: { writing: :primary, reading: :primary_replica }
+ # end
+ #
+ # Returns an array of established connections.
+ def connects_to(database: {})
+ connections = []
+
+ database.each do |role, database_key|
+ config_hash = resolve_config_for_connection(database_key)
+ handler = lookup_connection_handler(role.to_sym)
+
+ connections << handler.establish_connection(config_hash)
+ end
+
+ connections
+ end
+
+ # Connects to a database or role (ex writing, reading, or another
+ # custom role) for the duration of the block.
+ #
+ # If a role is passed, Active Record will look up the connection
+ # based on the requested role:
+ #
+ # ActiveRecord::Base.connected_to(role: :writing) do
+ # Dog.create! # creates dog using dog connection
+ # end
+ #
+ # ActiveRecord::Base.connected_to(role: :reading) do
+ # Dog.create! # throws exception because we're on a replica
+ # end
+ #
+ # ActiveRecord::Base.connected_to(role: :unknown_ode) do
+ # # raises exception due to non-existent role
+ # end
+ #
+ # For cases where you may want to connect to a database outside of the model,
+ # you can use +connected_to+ with a +database+ argument. The +database+ argument
+ # expects a symbol that corresponds to the database key in your config.
+ #
+ # This will connect to a new database for the queries inside the block.
+ #
+ # ActiveRecord::Base.connected_to(database: :animals_slow_replica) do
+ # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
+ # 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."
+ elsif database
+ if database.is_a?(Hash)
+ role, database = database.first
+ role = role.to_sym
+ else
+ role = database.to_sym
+ end
+
+ config_hash = resolve_config_for_connection(database)
+ handler = lookup_connection_handler(role)
+
+ with_handler(role) do
+ handler.establish_connection(config_hash)
+ yield
+ end
+ elsif role
+ with_handler(role.to_sym, &blk)
+ else
+ raise ArgumentError, "must provide a `database` or a `role`."
+ end
+ end
+
+ def lookup_connection_handler(handler_key) # :nodoc:
+ connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
+ end
+
+ def with_handler(handler_key, &blk) # :nodoc:
+ handler = lookup_connection_handler(handler_key)
+ swap_connection_handler(handler, &blk)
+ end
+
+ def resolve_config_for_connection(config_or_env) # :nodoc:
raise "Anonymous class is not allowed." unless name
config_or_env ||= DEFAULT_ENV.call.to_sym
@@ -57,7 +150,7 @@ module ActiveRecord
config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
config_hash[:name] = pool_name
- connection_handler.establish_connection(config_hash)
+ config_hash
end
# Returns the connection currently associated with the class. This can
@@ -118,5 +211,14 @@ module ActiveRecord
delegate :clear_active_connections!, :clear_reloadable_connections!,
:clear_all_connections!, :flush_idle_connections!, to: :connection_handler
+
+ private
+
+ def swap_connection_handler(handler, &blk) # :nodoc:
+ old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
+ yield
+ ensure
+ ActiveRecord::Base.connection_handler = old_handler
+ end
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 392602bc0f..da3e2549a2 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -2,15 +2,13 @@
require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/string/filters"
+require "active_support/parameter_filter"
require "concurrent/map"
-require "set"
module ActiveRecord
module Core
extend ActiveSupport::Concern
- FILTERED = "[FILTERED]" # :nodoc:
-
included do
##
# :singleton-method:
@@ -124,6 +122,8 @@ module ActiveRecord
mattr_accessor :belongs_to_required_by_default, instance_accessor: false
+ mattr_accessor :connection_handlers, instance_accessor: false, default: {}
+
class_attribute :default_connection_handler, instance_writer: false
self.filter_attributes = []
@@ -137,6 +137,7 @@ module ActiveRecord
end
self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
+ self.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
end
module ClassMethods
@@ -236,9 +237,7 @@ module ActiveRecord
end
# Specifies columns which shouldn't be exposed while calling +#inspect+.
- def filter_attributes=(attributes_names)
- @filter_attributes = attributes_names.map(&:to_s).to_set
- end
+ attr_writer :filter_attributes
# Returns a string like 'Post(id:integer, title:string, body:text)'
def inspect # :nodoc:
@@ -341,34 +340,22 @@ module ActiveRecord
# post = Post.allocate
# post.init_with(coder)
# post.title # => 'hello world'
- def init_with(coder)
+ def init_with(coder, &block)
coder = LegacyYamlAdapter.convert(self.class, coder)
- @attributes = self.class.yaml_encoder.decode(coder)
-
- init_internals
-
- @new_record = coder["new_record"]
-
- self.class.define_attribute_methods
-
- yield self if block_given?
-
- _run_find_callbacks
- _run_initialize_callbacks
-
- self
+ attributes = self.class.yaml_encoder.decode(coder)
+ init_with_attributes(attributes, coder["new_record"], &block)
end
##
- # Initializer used for instantiating objects that have been read from the
- # database. +attributes+ should be an attributes object, and unlike the
+ # Initialize an empty model object from +attributes+.
+ # +attributes+ should be an attributes object, and unlike the
# `initialize` method, no assignment calls are made per attribute.
#
# :nodoc:
- def init_from_db(attributes)
+ def init_with_attributes(attributes, new_record = false)
init_internals
- @new_record = false
+ @new_record = new_record
@attributes = attributes
self.class.define_attribute_methods
@@ -511,11 +498,14 @@ module ActiveRecord
inspection = if defined?(@attributes) && @attributes
self.class.attribute_names.collect do |name|
if has_attribute?(name)
- if filter_attribute?(name)
- "#{name}: #{ActiveRecord::Core::FILTERED}"
+ attr = read_attribute(name)
+ value = if attr.nil?
+ attr.inspect
else
- "#{name}: #{attribute_for_inspect(name)}"
+ attr = format_for_inspect(attr)
+ inspection_filter.filter_param(name, attr)
end
+ "#{name}: #{value}"
end
end.compact.join(", ")
else
@@ -531,18 +521,16 @@ module ActiveRecord
return super if custom_inspect_method_defined?
pp.object_address_group(self) do
if defined?(@attributes) && @attributes
- column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
- pp.seplist(column_names, proc { pp.text "," }) do |column_name|
+ attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
+ pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
pp.breakable " "
pp.group(1) do
- pp.text column_name
+ pp.text attr_name
pp.text ":"
pp.breakable
- if filter_attribute?(column_name)
- pp.text ActiveRecord::Core::FILTERED
- else
- pp.pp read_attribute(column_name)
- end
+ value = read_attribute(attr_name)
+ value = inspection_filter.filter_param(attr_name, value) unless value.nil?
+ pp.pp value
end
end
else
@@ -594,8 +582,14 @@ module ActiveRecord
self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
end
- def filter_attribute?(attribute_name)
- self.class.filter_attributes.include?(attribute_name) && !read_attribute(attribute_name).nil?
+ def inspection_filter
+ @inspection_filter ||= begin
+ mask = DelegateClass(::String).new(ActiveSupport::ParameterFilter::FILTERED)
+ def mask.pretty_print(pp)
+ pp.text __getobj__
+ end
+ ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb
index fa1589511e..30cb0a27e7 100644
--- a/activerecord/lib/active_record/database_configurations.rb
+++ b/activerecord/lib/active_record/database_configurations.rb
@@ -29,7 +29,7 @@ module ActiveRecord
# configs for all environments.
# <tt>spec_name:</tt> The specification name (ie primary, animals, etc.). Defaults
# to +nil+.
- # <tt>include_replicas:</tt> Determines whether to include replicas in the
+ # <tt>include_replicas:</tt> Determines whether to include replicas in
# the returned list. Most of the time we're only iterating over the write
# connection (i.e. migrations don't need to run for the write and read connection).
# Defaults to +false+.
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 23ecb24542..3d97e4e513 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -150,6 +150,7 @@ module ActiveRecord
enum_prefix = definitions.delete(:_prefix)
enum_suffix = definitions.delete(:_suffix)
definitions.each do |name, values|
+ assert_valid_enum_definition_values(values)
# statuses = { }
enum_values = ActiveSupport::HashWithIndifferentAccess.new
name = name.to_s
@@ -210,10 +211,20 @@ module ActiveRecord
end
end
+ def assert_valid_enum_definition_values(values)
+ unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) }
+ error_message = <<~MSG
+ Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
+ MSG
+ raise ArgumentError, error_message
+ end
+ end
+
ENUM_CONFLICT_MESSAGE = \
"You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
"this will generate a %{type} method \"%{method}\", which is already defined " \
"by %{source}."
+ private_constant :ENUM_CONFLICT_MESSAGE
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
if klass_method && dangerous_class_method?(method_name)
diff --git a/activerecord/lib/active_record/fixture_set/model_metadata.rb b/activerecord/lib/active_record/fixture_set/model_metadata.rb
index edc03939c8..fb23df6f45 100644
--- a/activerecord/lib/active_record/fixture_set/model_metadata.rb
+++ b/activerecord/lib/active_record/fixture_set/model_metadata.rb
@@ -3,9 +3,8 @@
module ActiveRecord
class FixtureSet
class ModelMetadata # :nodoc:
- def initialize(model_class, table_name)
+ def initialize(model_class)
@model_class = model_class
- @table_name = table_name
end
def primary_key_name
@@ -23,18 +22,12 @@ module ActiveRecord
def timestamp_column_names
@timestamp_column_names ||=
- %w(created_at created_on updated_at updated_on) & column_names
+ %w(created_at created_on updated_at updated_on) & @model_class.column_names
end
def inheritance_column_name
@inheritance_column_name ||= @model_class && @model_class.inheritance_column
end
-
- private
-
- def column_names
- @column_names ||= @model_class.connection.columns(@table_name).collect(&:name)
- end
end
end
end
diff --git a/activerecord/lib/active_record/fixture_set/table_row.rb b/activerecord/lib/active_record/fixture_set/table_row.rb
index 5f72c1df38..cb4726f1ee 100644
--- a/activerecord/lib/active_record/fixture_set/table_row.rb
+++ b/activerecord/lib/active_record/fixture_set/table_row.rb
@@ -3,6 +3,38 @@
module ActiveRecord
class FixtureSet
class TableRow # :nodoc:
+ class ReflectionProxy # :nodoc:
+ def initialize(association)
+ @association = association
+ end
+
+ def join_table
+ @association.join_table
+ end
+
+ def name
+ @association.name
+ end
+
+ def primary_key_type
+ @association.klass.type_for_attribute(@association.klass.primary_key).type
+ end
+ end
+
+ class HasManyThroughProxy < ReflectionProxy # :nodoc:
+ def rhs_key
+ @association.foreign_key
+ end
+
+ def lhs_key
+ @association.through_reflection.foreign_key
+ end
+
+ def join_table
+ @association.through_reflection.table_name
+ end
+ end
+
def initialize(fixture, table_rows:, label:, now:)
@table_rows = table_rows
@label = label
@@ -31,7 +63,7 @@ module ActiveRecord
interpolate_label
generate_primary_key
resolve_enums
- @table_rows.resolve_sti_reflections(@row)
+ resolve_sti_reflections
end
def reflection_class
@@ -74,6 +106,48 @@ module ActiveRecord
end
end
end
+
+ def resolve_sti_reflections
+ # If STI is used, find the correct subclass for association reflection
+ reflection_class._reflections.each_value do |association|
+ case association.macro
+ when :belongs_to
+ # Do not replace association name with association foreign key if they are named the same
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+
+ if association.name.to_s != fk_name && value = @row.delete(association.name.to_s)
+ if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ # support polymorphic belongs_to as "label (Type)"
+ @row[association.foreign_type] = $1
+ end
+
+ fk_type = reflection_class.type_for_attribute(fk_name).type
+ @row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
+ end
+ when :has_many
+ if association.options[:through]
+ add_join_records(HasManyThroughProxy.new(association))
+ end
+ end
+ end
+ end
+
+ def add_join_records(association)
+ # This is the case when the join table has no fixtures file
+ if (targets = @row.delete(association.name.to_s))
+ table_name = association.join_table
+ column_type = association.primary_key_type
+ lhs_key = association.lhs_key
+ rhs_key = association.rhs_key
+
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
+ joins = targets.map do |target|
+ { lhs_key => @row[model_metadata.primary_key_name],
+ rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
+ end
+ @table_rows.tables[table_name].concat(joins)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/fixture_set/table_rows.rb b/activerecord/lib/active_record/fixture_set/table_rows.rb
index e8335a2e10..23814b6cb5 100644
--- a/activerecord/lib/active_record/fixture_set/table_rows.rb
+++ b/activerecord/lib/active_record/fixture_set/table_rows.rb
@@ -6,88 +6,31 @@ require "active_record/fixture_set/model_metadata"
module ActiveRecord
class FixtureSet
class TableRows # :nodoc:
- class ReflectionProxy # :nodoc:
- def initialize(association)
- @association = association
- end
-
- def join_table
- @association.join_table
- end
-
- def name
- @association.name
- end
-
- def primary_key_type
- @association.klass.type_for_attribute(@association.klass.primary_key).type
- end
- end
-
- class HasManyThroughProxy < ReflectionProxy # :nodoc:
- def rhs_key
- @association.foreign_key
- end
-
- def lhs_key
- @association.through_reflection.foreign_key
- end
-
- def join_table
- @association.through_reflection.table_name
- end
- end
-
def initialize(table_name, model_class:, fixtures:, config:)
- @table_name = table_name
@model_class = model_class
# track any join tables we need to insert later
@tables = Hash.new { |h, table| h[table] = [] }
- build_table_rows_from(fixtures, config)
+ # ensure this table is loaded before any HABTM associations
+ @tables[table_name] = nil
+
+ build_table_rows_from(table_name, fixtures, config)
end
- attr_reader :table_name, :model_class
+ attr_reader :tables, :model_class
def to_hash
@tables.transform_values { |rows| rows.map(&:to_hash) }
end
def model_metadata
- @model_metadata ||= ModelMetadata.new(model_class, table_name)
- end
-
- def resolve_sti_reflections(row)
- # If STI is used, find the correct subclass for association reflection
- reflection_class = reflection_class_for(row)
-
- reflection_class._reflections.each_value do |association|
- case association.macro
- when :belongs_to
- # Do not replace association name with association foreign key if they are named the same
- fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
-
- if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
- if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
- # support polymorphic belongs_to as "label (Type)"
- row[association.foreign_type] = $1
- end
-
- fk_type = reflection_class.type_for_attribute(fk_name).type
- row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
- end
- when :has_many
- if association.options[:through]
- add_join_records(row, HasManyThroughProxy.new(association))
- end
- end
- end
+ @model_metadata ||= ModelMetadata.new(model_class)
end
private
- def build_table_rows_from(fixtures, config)
+ def build_table_rows_from(table_name, fixtures, config)
now = config.default_timezone == :utc ? Time.now.utc : Time.now
@tables[table_name] = fixtures.map do |label, fixture|
@@ -99,31 +42,6 @@ module ActiveRecord
)
end
end
-
- def reflection_class_for(row)
- if row.include?(model_metadata.inheritance_column_name)
- row[model_metadata.inheritance_column_name].constantize rescue model_class
- else
- model_class
- end
- end
-
- def add_join_records(row, association)
- # This is the case when the join table has no fixtures file
- if (targets = row.delete(association.name.to_s))
- table_name = association.join_table
- column_type = association.primary_key_type
- lhs_key = association.lhs_key
- rhs_key = association.rhs_key
-
- targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
- joins = targets.map do |target|
- { lhs_key => row[model_metadata.primary_key_name],
- rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
- end
- @tables[table_name].concat(joins)
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index b090c76a38..1248ed00c5 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -577,9 +577,8 @@ module ActiveRecord
fixtures_map = {}
fixture_sets = fixture_files.map do |fixture_set_name|
klass = class_names[fixture_set_name]
- conn = klass&.connection || connection
fixtures_map[fixture_set_name] = new( # ActiveRecord::FixtureSet.new
- conn,
+ nil,
fixture_set_name,
klass,
::File.join(fixtures_directory, fixture_set_name)
@@ -621,7 +620,7 @@ module ActiveRecord
attr_reader :table_name, :name, :fixtures, :model_class, :config
- def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
+ def initialize(_, name, class_name, path, config = ActiveRecord::Base)
@name = name
@path = path
@config = config
@@ -630,11 +629,7 @@ module ActiveRecord
@fixtures = read_fixture_files(path)
- @connection = connection
-
- @table_name = (model_class.respond_to?(:table_name) ?
- model_class.table_name :
- self.class.default_fixture_table_name(name, config))
+ @table_name = model_class&.table_name || self.class.default_fixture_table_name(name, config)
end
def [](x)
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 5d1d15c94d..130ef8a330 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -14,9 +14,9 @@ module ActiveRecord
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
#
# Account.transaction do
- # # select * from accounts where name = 'shugo' limit 1 for update
- # shugo = Account.where("name = 'shugo'").lock(true).first
- # yuko = Account.where("name = 'yuko'").lock(true).first
+ # # select * from accounts where name = 'shugo' limit 1 for update nowait
+ # shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo")
+ # yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko")
# shugo.balance -= 100
# shugo.save!
# yuko.balance += 100
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 5a4a1fb969..c50a420432 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -388,6 +388,11 @@ module ActiveRecord
@column_names ||= columns.map(&:name)
end
+ def symbol_column_to_string(name_symbol) # :nodoc:
+ @symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym)
+ @symbol_column_to_string_name_hash[name_symbol]
+ end
+
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
# and columns used for single table inheritance have been removed.
def content_columns
@@ -477,6 +482,7 @@ module ActiveRecord
def reload_schema_from_cache
@arel_table = nil
@column_names = nil
+ @symbol_column_to_string_name_hash = nil
@attribute_types = nil
@content_columns = nil
@default_attributes = nil
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 50767ee93f..8b9098df6c 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -426,7 +426,7 @@ module ActiveRecord
existing_record.assign_attributes(assignable_attributes)
association(association_name).initialize_attributes(existing_record)
else
- method = "build_#{association_name}"
+ method = :"build_#{association_name}"
if respond_to?(method)
send(method, assignable_attributes)
else
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 6eb2bfb452..7bf8d568df 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -209,7 +209,7 @@ module ActiveRecord
# new instance of the class. Accepts only keys as strings.
def instantiate_instance_of(klass, attributes, column_types = {}, &block)
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
- klass.allocate.init_from_db(attributes, &block)
+ klass.allocate.init_with_attributes(attributes, &block)
end
# Called by +instantiate+ to decide which class to use for a new
@@ -479,15 +479,16 @@ module ActiveRecord
verify_readonly_attribute(key.to_s)
end
+ id_in_database = self.id_in_database
+ attributes.each do |k, v|
+ write_attribute_without_type_cast(k, v)
+ end
+
affected_rows = self.class._update_record(
attributes,
self.class.primary_key => id_in_database
)
- attributes.each do |k, v|
- write_attribute_without_type_cast(k, v)
- end
-
affected_rows == 1
end
@@ -758,6 +759,8 @@ module ActiveRecord
@_association_destroy_exception = nil
end
+ # The name of the method used to touch a +belongs_to+ association when the
+ # +:touch+ option is used.
def belongs_to_touch_method
:touch
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index be5ef350a9..1c7ceb4981 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -149,18 +149,21 @@ db_namespace = namespace :db do
desc "Display status of migrations"
task status: :load_config do
- unless ActiveRecord::SchemaMigration.table_exists?
- abort "Schema migrations table does not exist yet."
+ ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
+ ActiveRecord::Base.establish_connection(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.migrate_status
end
+ end
- # output
- puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
- puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
- puts "-" * 50
- ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
- puts "#{status.center(8)} #{version.ljust(14)} #{name}"
+ namespace :status do
+ ActiveRecord::Tasks::DatabaseTasks.for_each do |spec_name|
+ desc "Display status of migrations for #{spec_name} database"
+ task spec_name => :load_config do
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, spec_name: spec_name)
+ ActiveRecord::Base.establish_connection(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.migrate_status
+ end
end
- puts
end
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 8f657840f5..383dc1bf4b 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -17,6 +17,7 @@ module ActiveRecord
delegate = Class.new(klass) {
include ClassSpecificRelation
}
+ include_relation_methods(delegate)
mangled_name = klass.name.gsub("::", "_")
const_set mangled_name, delegate
private_constant mangled_name
@@ -29,6 +30,35 @@ module ActiveRecord
child_class.initialize_relation_delegate_cache
super
end
+
+ protected
+ def include_relation_methods(delegate)
+ superclass.include_relation_methods(delegate) unless base_class?
+ delegate.include generated_relation_methods
+ end
+
+ private
+ def generated_relation_methods
+ @generated_relation_methods ||= Module.new.tap do |mod|
+ mod_name = "GeneratedRelationMethods"
+ const_set mod_name, mod
+ private_constant mod_name
+ end
+ end
+
+ def generate_relation_method(method)
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
+ generated_relation_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(*args, &block)
+ scoping { klass.#{method}(*args, &block) }
+ end
+ RUBY
+ else
+ generated_relation_methods.send(:define_method, method) do |*args, &block|
+ scoping { klass.public_send(method, *args, &block) }
+ end
+ end
+ end
end
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 6f420fe6bb..afaa900442 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -363,7 +363,7 @@ module ActiveRecord
case conditions
when Array, Hash
- relation.where!(conditions)
+ relation.where!(conditions) unless conditions.empty?
else
relation.where!(primary_key => conditions) unless conditions == :none
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index fadb3c420d..ee2ece1560 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -22,9 +22,8 @@ module ActiveRecord
when 1 then predicate_builder.build(attribute, values.first)
else
values.map! do |v|
- bind = predicate_builder.build_bind_attribute(attribute.name, v)
- bind if bind.value.boundable?
- end.compact!
+ predicate_builder.build_bind_attribute(attribute.name, v)
+ end
values.empty? ? NullPredicate : attribute.in(values)
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 5170d38fa8..eb80aab701 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -909,21 +909,18 @@ module ActiveRecord
@arel ||= build_arel(aliases)
end
- # Returns a relation value with a given name
- def get_value(name) # :nodoc:
- @values.fetch(name, DEFAULT_VALUES[name])
- end
-
- protected
+ private
+ # Returns a relation value with a given name
+ def get_value(name)
+ @values.fetch(name, DEFAULT_VALUES[name])
+ end
# Sets the relation value with the given name
- def set_value(name, value) # :nodoc:
+ def set_value(name, value)
assert_mutability!
@values[name] = value
end
- private
-
def assert_mutability!
raise ImmutableRelation if @loaded
raise ImmutableRelation if defined?(@arel) && @arel
@@ -1188,8 +1185,9 @@ module ActiveRecord
STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
def structurally_incompatible_values_for_or(other)
+ values = other.values
STRUCTURAL_OR_METHODS.reject do |method|
- get_value(method) == other.get_value(method)
+ get_value(method) == values.fetch(method, DEFAULT_VALUES[method])
end
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 573d97b819..d5cc5db97e 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -191,6 +191,8 @@ module ActiveRecord
scope
end
end
+
+ generate_relation_method(name)
end
private
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 5e29085aff..974d7a1c0a 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -197,6 +197,21 @@ module ActiveRecord
Migration.verbose = verbose_was
end
+ def migrate_status
+ unless ActiveRecord::SchemaMigration.table_exists?
+ Kernel.abort "Schema migrations table does not exist yet."
+ end
+
+ # output
+ puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
+ puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
+ puts "-" * 50
+ ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
+ puts "#{status.center(8)} #{version.ljust(14)} #{name}"
+ end
+ puts
+ end
+
def check_target_version
if target_version && !(Migration::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(ENV["VERSION"]))
raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`"
diff --git a/activerecord/lib/arel/nodes/bind_param.rb b/activerecord/lib/arel/nodes/bind_param.rb
index 91e9b2b70f..ba8340558a 100644
--- a/activerecord/lib/arel/nodes/bind_param.rb
+++ b/activerecord/lib/arel/nodes/bind_param.rb
@@ -23,6 +23,10 @@ module Arel # :nodoc: all
def nil?
value.nil?
end
+
+ def boundable?
+ !value.respond_to?(:boundable?) || value.boundable?
+ end
end
end
end
diff --git a/activerecord/lib/arel/visitors/mysql.rb b/activerecord/lib/arel/visitors/mysql.rb
index 9d9294fecc..32f6705d04 100644
--- a/activerecord/lib/arel/visitors/mysql.rb
+++ b/activerecord/lib/arel/visitors/mysql.rb
@@ -9,20 +9,20 @@ module Arel # :nodoc: all
collector << "( "
end
- collector = case o.left
- when Arel::Nodes::Union
- visit_Arel_Nodes_Union o.left, collector, true
- else
- visit o.left, collector
+ case o.left
+ when Arel::Nodes::Union
+ visit_Arel_Nodes_Union o.left, collector, true
+ else
+ visit o.left, collector
end
collector << " UNION "
- collector = case o.right
- when Arel::Nodes::Union
- visit_Arel_Nodes_Union o.right, collector, true
- else
- visit o.right, collector
+ case o.right
+ when Arel::Nodes::Union
+ visit_Arel_Nodes_Union o.right, collector, true
+ else
+ visit o.right, collector
end
if suppress_parens
@@ -75,14 +75,7 @@ module Arel # :nodoc: all
o
end
end
-
- def prepare_delete_statement(o)
- if o.offset || has_join_sources?(o)
- super
- else
- o
- end
- end
+ alias :prepare_delete_statement :prepare_update_statement
# MySQL is too stupid to create a temporary table for use subquery, so we have
# to give it some prompting in the form of a subsubquery.
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index 7c0f6c2e97..8e56fb55a2 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -76,10 +76,18 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_DeleteStatement(o, collector)
o = prepare_delete_statement(o)
- collector << "DELETE FROM "
+ if has_join_sources?(o)
+ collector << "DELETE "
+ visit o.relation.left, collector
+ collector << " FROM "
+ else
+ collector << "DELETE FROM "
+ end
collector = visit o.relation, collector
- collect_where_for(o, collector)
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
+ collect_nodes_for o.orders, collector, " ORDER BY "
+ maybe_visit o.limit, collector
end
def visit_Arel_Nodes_UpdateStatement(o, collector)
@@ -87,12 +95,11 @@ module Arel # :nodoc: all
collector << "UPDATE "
collector = visit o.relation, collector
- unless o.values.empty?
- collector << " SET "
- collector = inject_join o.values, collector, ", "
- end
+ collect_nodes_for o.values, collector, " SET "
- collect_where_for(o, collector)
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
+ collect_nodes_for o.orders, collector, " ORDER BY "
+ maybe_visit o.limit, collector
end
def visit_Arel_Nodes_InsertStatement(o, collector)
@@ -225,10 +232,7 @@ module Arel # :nodoc: all
collect_nodes_for o.wheres, collector, WHERE, AND
collect_nodes_for o.groups, collector, GROUP_BY
- unless o.havings.empty?
- collector << " HAVING "
- inject_join o.havings, collector, AND
- end
+ collect_nodes_for o.havings, collector, " HAVING ", AND
collect_nodes_for o.windows, collector, WINDOW
collector
@@ -237,11 +241,7 @@ module Arel # :nodoc: all
def collect_nodes_for(nodes, collector, spacer, connector = COMMA)
unless nodes.empty?
collector << spacer
- len = nodes.length - 1
- nodes.each_with_index do |x, i|
- collector = visit(x, collector)
- collector << connector unless len == i
- end
+ inject_join nodes, collector, connector
end
end
@@ -296,10 +296,7 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_Window(o, collector)
collector << "("
- if o.partitions.any?
- collector << "PARTITION BY "
- collector = inject_join o.partitions, collector, ", "
- end
+ collect_nodes_for o.partitions, collector, "PARTITION BY "
if o.orders.any?
collector << SPACE if o.partitions.any?
@@ -582,6 +579,10 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_In(o, collector)
+ if Array === o.right && !o.right.empty?
+ o.right.keep_if { |value| boundable?(value) }
+ end
+
if Array === o.right && o.right.empty?
collector << "1=0"
else
@@ -592,6 +593,10 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_NotIn(o, collector)
+ if Array === o.right && !o.right.empty?
+ o.right.keep_if { |value| boundable?(value) }
+ end
+
if Array === o.right && o.right.empty?
collector << "1=1"
else
@@ -791,6 +796,10 @@ module Arel # :nodoc: all
}
end
+ def boundable?(value)
+ !value.respond_to?(:boundable?) || value.boundable?
+ end
+
def has_join_sources?(o)
o.relation.is_a?(Nodes::JoinSource) && !o.relation.right.empty?
end
@@ -830,20 +839,6 @@ module Arel # :nodoc: all
stmt
end
- def collect_where_for(o, collector)
- unless o.wheres.empty?
- collector << " WHERE "
- collector = inject_join o.wheres, collector, " AND "
- end
-
- unless o.orders.empty?
- collector << " ORDER BY "
- collector = inject_join o.orders, collector, ", "
- end
-
- maybe_visit o.limit, collector
- end
-
def infix_value(o, collector, value)
collector = visit o.left, collector
collector << value
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index d520919332..93dd427951 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -367,6 +367,30 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal "ODEGY", odegy_account.reload_firm.name
end
+ def test_reload_the_belonging_object_with_query_cache
+ odegy_account_id = accounts(:odegy_account).id
+
+ connection = ActiveRecord::Base.connection
+ connection.enable_query_cache!
+ connection.clear_query_cache
+
+ # Populate the cache with a query
+ odegy_account = Account.find(odegy_account_id)
+
+ # Populate the cache with a second query
+ odegy_account.firm
+
+ assert_equal 2, connection.query_cache.size
+
+ # Clear the cache and fetch the firm again, populating the cache with a query
+ assert_queries(1) { odegy_account.reload_firm }
+
+ # This query is not cached anymore, so it should make a real SQL query
+ assert_queries(1) { Account.find(odegy_account_id) }
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
def test_natural_assignment_to_nil
client = Client.find(3)
client.firm = nil
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 39034746c9..b37e59038e 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -34,7 +34,7 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
fixtures :citations
def test_preloading_too_many_ids
- assert_equal Citation.count, Citation.preload(:citations).to_a.size
+ assert_equal Citation.count, Citation.preload(:reference_of).to_a.size
end
def test_eager_loading_too_may_ids
@@ -1618,32 +1618,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- # Associations::Preloader#preloaders_on works with hash-like objects
- test "preloading works with an object that responds to :to_hash" do
- CustomHash = Class.new(Hash)
-
- assert_nothing_raised do
- Post.preload(CustomHash.new(comments: [{ author: :essays }])).first
- end
- end
-
- # Associations::Preloader#preloaders_on works with string-like objects
- test "preloading works with an object that responds to :to_str" do
- CustomString = Class.new(String)
-
- assert_nothing_raised do
- Post.preload(CustomString.new("comments")).first
- end
- end
-
- # Associations::Preloader#preloaders_on does not work with ranges
- test "preloading fails when Range is passed" do
- exception = assert_raises(ArgumentError) do
- Post.preload(1..10).first
- end
- assert_equal("1..10 was not recognized for preload", exception.message)
- end
-
private
def find_all_ordered(klass, include = nil)
klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 38b121d37b..515eb65d37 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -25,6 +25,8 @@ require "models/user"
require "models/member"
require "models/membership"
require "models/sponsor"
+require "models/lesson"
+require "models/student"
require "models/country"
require "models/treaty"
require "models/vertex"
@@ -275,7 +277,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_habtm_saving_multiple_relationships
new_project = Project.new("name" => "Grimetime")
amount_of_developers = 4
- developers = (0...amount_of_developers).collect { |i| Developer.create(name: "JME #{i}") }.reverse
+ developers = (0...amount_of_developers).reverse_each.map { |i| Developer.create(name: "JME #{i}") }
new_project.developer_ids = [developers[0].id, developers[1].id]
new_project.developers_with_callback_ids = [developers[2].id, developers[3].id]
@@ -780,6 +782,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort
end
+ def test_singular_ids_are_reloaded_after_collection_concat
+ student = Student.create(name: "Alberto Almagro")
+ student.lesson_ids
+
+ lesson = Lesson.create(name: "DSI")
+ student.lessons << lesson
+
+ assert_includes student.lesson_ids, lesson.id
+ end
+
def test_scoped_find_on_through_association_doesnt_return_read_only_records
tag = Post.find(1).tags.find_by_name("General")
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index eb65f9e74f..d13e1a86e9 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -829,6 +829,48 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_not_same original_object, collection.first, "Expected #first after #reload to return a new object"
end
+ def test_reload_with_query_cache
+ connection = ActiveRecord::Base.connection
+ connection.enable_query_cache!
+ connection.clear_query_cache
+
+ # Populate the cache with a query
+ firm = Firm.first
+ # Populate the cache with a second query
+ firm.clients.load
+
+ assert_equal 2, connection.query_cache.size
+
+ # Clear the cache and fetch the clients again, populating the cache with a query
+ assert_queries(1) { firm.clients.reload }
+ # This query is cached, so it shouldn't make a real SQL query
+ assert_queries(0) { firm.clients.load }
+
+ assert_equal 1, connection.query_cache.size
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
+ def test_reloading_unloaded_associations_with_query_cache
+ connection = ActiveRecord::Base.connection
+ connection.enable_query_cache!
+ connection.clear_query_cache
+
+ firm = Firm.create!(name: "firm name")
+ client = firm.clients.create!(name: "client name")
+ firm.clients.to_a # add request to cache
+
+ connection.uncached do
+ client.update!(name: "new client name")
+ end
+
+ firm = Firm.find(firm.id)
+
+ assert_equal [client.name], firm.clients.reload.map(&:name)
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
def test_find_all_with_include_and_conditions
assert_nothing_raised do
Developer.all.merge!(joins: :audit_logs, where: { "audit_logs.message" => nil, :name => "Smith" }).to_a
@@ -2391,6 +2433,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_collection_association_with_private_kernel_method
firm = companies(:first_firm)
assert_equal [accounts(:signals37)], firm.accounts.open
+ assert_equal [accounts(:signals37)], firm.accounts.available
end
def test_association_with_or_doesnt_set_inverse_instance_key
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 801720b214..adfb3ce072 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -333,6 +333,29 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal 80, odegy.reload_account.credit_limit
end
+ def test_reload_association_with_query_cache
+ odegy_id = companies(:odegy).id
+
+ connection = ActiveRecord::Base.connection
+ connection.enable_query_cache!
+ connection.clear_query_cache
+
+ # Populate the cache with a query
+ odegy = Company.find(odegy_id)
+ # Populate the cache with a second query
+ odegy.account
+
+ assert_equal 2, connection.query_cache.size
+
+ # Clear the cache and fetch the account again, populating the cache with a query
+ assert_queries(1) { odegy.reload_account }
+
+ # This query is not cached anymore, so it should make a real SQL query
+ assert_queries(1) { Company.find(odegy_id) }
+ ensure
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+
def test_build
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 0bfd46a522..6c2e256447 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -310,6 +310,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal "New topic", topic.title
end
+ test "write_attribute raises ActiveModel::MissingAttributeError when the attribute does not exist" do
+ topic = Topic.first
+ assert_raises(ActiveModel::MissingAttributeError) { topic.update_columns(no_column_exists: "Hello!") }
+ assert_raises(ActiveModel::UnknownAttributeError) { topic.update(no_column_exists: "Hello!") }
+ end
+
test "read_attribute" do
topic = Topic.new
topic.title = "Don't change the topic"
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 9c1f7aaef2..bd5f157ca1 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -36,8 +36,12 @@ if ActiveRecord::Base.connection.prepared_statements
def test_too_many_binds
bind_params_length = @connection.send(:bind_params_length)
- topics = Topic.where(id: (1 .. bind_params_length + 1).to_a)
+
+ topics = Topic.where(id: (1 .. bind_params_length).to_a << 2**63)
assert_equal Topic.count, topics.count
+
+ topics = Topic.where.not(id: (1 .. bind_params_length).to_a << 2**63)
+ assert_equal 0, topics.count
end
def test_bind_from_join_in_subquery
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index 844b2b2162..483383257b 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -42,6 +42,20 @@ module ActiveRecord
assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
end
+ test "cache_key for relation with custom select and limit" do
+ developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5)
+ developers_with_select = developers.select("developers.*")
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers_with_select.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers_with_select.cache_key
+
+ assert_equal ActiveSupport::Digest.hexdigest(developers_with_select.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
test "cache_key for loaded relation" do
developers = Developer.where(salary: 100000).order(updated_at: :desc).limit(5).load
last_developer_timestamp = developers.first.updated_at
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
new file mode 100644
index 0000000000..a520fb1303
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/connection_handlers_multi_db_test.rb
@@ -0,0 +1,232 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/person"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionHandlersMultiDbTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ fixtures :people
+
+ def setup
+ @handlers = { writing: ConnectionHandler.new, reading: ConnectionHandler.new }
+ @rw_handler = @handlers[:writing]
+ @ro_handler = @handlers[:reading]
+ @spec_name = "primary"
+ @rw_pool = @handlers[:writing].establish_connection(ActiveRecord::Base.configurations["arunit"])
+ @ro_pool = @handlers[:reading].establish_connection(ActiveRecord::Base.configurations["arunit"])
+ end
+
+ def teardown
+ ActiveRecord::Base.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
+ end
+
+ class MultiConnectionTestModel < ActiveRecord::Base
+ end
+
+ def test_multiple_connection_handlers_works_in_a_threaded_environment
+ tf_writing = Tempfile.open "test_writing"
+ tf_reading = Tempfile.open "test_reading"
+
+ MultiConnectionTestModel.connects_to database: { writing: { database: tf_writing.path, adapter: "sqlite3" }, reading: { database: tf_reading.path, adapter: "sqlite3" } }
+
+ MultiConnectionTestModel.connection.execute("CREATE TABLE `test_1` (connection_role VARCHAR (255))")
+ MultiConnectionTestModel.connection.execute("INSERT INTO test_1 VALUES ('writing')")
+
+ ActiveRecord::Base.connected_to(role: :reading) do
+ MultiConnectionTestModel.connection.execute("CREATE TABLE `test_1` (connection_role VARCHAR (255))")
+ MultiConnectionTestModel.connection.execute("INSERT INTO test_1 VALUES ('reading')")
+ end
+
+ read_latch = Concurrent::CountDownLatch.new
+ write_latch = Concurrent::CountDownLatch.new
+
+ MultiConnectionTestModel.connection
+
+ thread = Thread.new do
+ MultiConnectionTestModel.connection
+
+ write_latch.wait
+ assert_equal "writing", MultiConnectionTestModel.connection.select_value("SELECT connection_role from test_1")
+ read_latch.count_down
+ end
+
+ ActiveRecord::Base.connected_to(role: :reading) do
+ write_latch.count_down
+ assert_equal "reading", MultiConnectionTestModel.connection.select_value("SELECT connection_role from test_1")
+ read_latch.wait
+ end
+
+ thread.join
+ ensure
+ tf_reading.close
+ tf_reading.unlink
+ tf_writing.close
+ tf_writing.unlink
+ end
+
+ unless in_memory_db?
+ def test_establish_connection_using_3_levels_config
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+
+ 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.connects_to(database: { writing: :primary, reading: :readonly })
+
+ assert_not_nil pool = ActiveRecord::Base.connection_handlers[:writing].retrieve_connection_pool("primary")
+ assert_equal "db/primary.sqlite3", pool.spec.config[:database]
+
+ assert_not_nil pool = ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("primary")
+ assert_equal "db/readonly.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_switching_connections_via_handler
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+
+ 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.connects_to(database: { writing: :primary, reading: :readonly })
+
+ ActiveRecord::Base.connected_to(role: :reading) do
+ @ro_handler = ActiveRecord::Base.connection_handler
+ assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:reading]
+ end
+
+ ActiveRecord::Base.connected_to(role: :writing) do
+ assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:writing]
+ assert_not_equal @ro_handler, ActiveRecord::Base.connection_handler
+ end
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_switching_connections_with_database_url
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+ previous_url, ENV["DATABASE_URL"] = ENV["DATABASE_URL"], "postgres://localhost/foo"
+
+ ActiveRecord::Base.connected_to(database: { writing: "postgres://localhost/bar" }) do
+ handler = ActiveRecord::Base.connection_handler
+ assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
+ end
+ ensure
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ ENV["DATABASE_URL"] = previous_url
+ end
+
+ 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" }
+
+ ActiveRecord::Base.connected_to(database: { writing: config }) do
+ handler = ActiveRecord::Base.connection_handler
+ assert_equal handler, ActiveRecord::Base.connection_handlers[:writing]
+ end
+ ensure
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ 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
+ handler = ActiveRecord::Base.connection_handler
+ assert_equal handler, ActiveRecord::Base.connection_handlers[:arunit2]
+ end
+ ensure
+ ActiveRecord::Base.establish_connection(:arunit)
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_connects_to_with_single_configuration
+ config = {
+ "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" },
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ ActiveRecord::Base.connects_to database: { writing: :development }
+
+ assert_equal 1, ActiveRecord::Base.connection_handlers.size
+ assert_equal ActiveRecord::Base.connection_handler, ActiveRecord::Base.connection_handlers[:writing]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ end
+
+ def test_connects_to_using_top_level_key_in_two_level_config
+ 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
+
+ ActiveRecord::Base.connects_to database: { writing: :development, reading: :development_readonly }
+
+ assert_not_nil pool = ActiveRecord::Base.connection_handlers[:reading].retrieve_connection_pool("primary")
+ assert_equal "db/readonly.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ActiveRecord::Base.establish_connection(:arunit)
+ end
+ end
+
+ def test_connection_pools
+ assert_equal([@rw_pool], @handlers[:writing].connection_pools)
+ assert_equal([@ro_pool], @handlers[:reading].connection_pools)
+ end
+
+ def test_retrieve_connection
+ assert @rw_handler.retrieve_connection(@spec_name)
+ assert @ro_handler.retrieve_connection(@spec_name)
+ end
+
+ def test_active_connections?
+ assert_not_predicate @rw_handler, :active_connections?
+ assert_not_predicate @ro_handler, :active_connections?
+
+ assert @rw_handler.retrieve_connection(@spec_name)
+ assert @ro_handler.retrieve_connection(@spec_name)
+
+ assert_predicate @rw_handler, :active_connections?
+ assert_predicate @ro_handler, :active_connections?
+
+ @rw_handler.clear_active_connections!
+ assert_not_predicate @rw_handler, :active_connections?
+
+ @ro_handler.clear_active_connections!
+ assert_not_predicate @ro_handler, :active_connections?
+ end
+
+ def test_retrieve_connection_pool
+ assert_not_nil @rw_handler.retrieve_connection_pool(@spec_name)
+ assert_not_nil @ro_handler.retrieve_connection_pool(@spec_name)
+ end
+
+ def test_retrieve_connection_pool_with_invalid_id
+ assert_nil @rw_handler.retrieve_connection_pool("foo")
+ assert_nil @ro_handler.retrieve_connection_pool("foo")
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 0f957d41cf..5d02e59ef6 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -106,6 +106,13 @@ if current_adapter?(:Mysql2Adapter)
class MysqlDefaultExpressionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
+ if supports_default_expression?
+ test "schema dump includes default expression" do
+ output = dump_table_schema("defaults")
+ assert_match %r/t\.binary\s+"uuid",\s+limit: 36,\s+default: -> { "\(uuid\(\)\)" }/i, output
+ end
+ end
+
if subsecond_precision_supported?
test "schema dump datetime includes default expression" do
output = dump_table_schema("datetime_defaults")
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index d5a1d11e12..b4593ccdf2 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -265,6 +265,17 @@ class EnumTest < ActiveRecord::TestCase
assert_equal "published", @book.status
end
+ test "invalid definition values raise an ArgumentError" do
+ e = assert_raises(ArgumentError) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [proposed: 1, written: 2, published: 3]
+ end
+ end
+
+ assert_match(/must be either a hash, an array of symbols, or an array of strings./, e.message)
+ end
+
test "reserved enum names" do
klass = Class.new(ActiveRecord::Base) do
self.table_name = "books"
diff --git a/activerecord/test/cases/filter_attributes_test.rb b/activerecord/test/cases/filter_attributes_test.rb
index af5badd87d..47161a547a 100644
--- a/activerecord/test/cases/filter_attributes_test.rb
+++ b/activerecord/test/cases/filter_attributes_test.rb
@@ -4,6 +4,7 @@ require "cases/helper"
require "models/admin"
require "models/admin/user"
require "models/admin/account"
+require "models/user"
require "pp"
class FilterAttributesTest < ActiveRecord::TestCase
@@ -30,6 +31,32 @@ class FilterAttributesTest < ActiveRecord::TestCase
end
end
+ test "string filter_attributes perform pertial match" do
+ ActiveRecord::Base.filter_attributes = ["n"]
+ Admin::Account.all.each do |account|
+ assert_includes account.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+ end
+
+ test "regex filter_attributes are accepted" do
+ ActiveRecord::Base.filter_attributes = [/\An\z/]
+ account = Admin::Account.find_by(name: "37signals")
+ assert_includes account.inspect, 'name: "37signals"'
+ assert_equal 0, account.inspect.scan("[FILTERED]").length
+
+ ActiveRecord::Base.filter_attributes = [/\An/]
+ account = Admin::Account.find_by(name: "37signals")
+ assert_includes account.reload.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+
+ test "proc filter_attributes are accepted" do
+ ActiveRecord::Base.filter_attributes = [ lambda { |key, value| value.reverse! if key == "name" } ]
+ account = Admin::Account.find_by(name: "37signals")
+ assert_includes account.inspect, 'name: "slangis73"'
+ end
+
test "filter_attributes could be overwritten by models" do
Admin::Account.all.each do |account|
assert_includes account.inspect, "name: [FILTERED]"
@@ -37,7 +64,6 @@ class FilterAttributesTest < ActiveRecord::TestCase
end
begin
- previous_account_filter_attributes = Admin::Account.filter_attributes
Admin::Account.filter_attributes = []
# Above changes should not impact other models
@@ -51,7 +77,7 @@ class FilterAttributesTest < ActiveRecord::TestCase
assert_equal 0, account.inspect.scan("[FILTERED]").length
end
ensure
- Admin::Account.filter_attributes = previous_account_filter_attributes
+ Admin::Account.remove_instance_variable(:@filter_attributes)
end
end
@@ -63,6 +89,18 @@ class FilterAttributesTest < ActiveRecord::TestCase
assert_equal 0, account.inspect.scan("[FILTERED]").length
end
+ test "filter_attributes should handle [FILTERED] value properly" do
+ begin
+ User.filter_attributes = ["auth"]
+ user = User.new(token: "[FILTERED]", auth_token: "[FILTERED]")
+
+ assert_includes user.inspect, "auth_token: [FILTERED]"
+ assert_includes user.inspect, 'token: "[FILTERED]"'
+ ensure
+ User.remove_instance_variable(:@filter_attributes)
+ end
+ end
+
test "filter_attributes on pretty_print" do
user = admin_users(:david)
actual = "".dup
@@ -81,4 +119,18 @@ class FilterAttributesTest < ActiveRecord::TestCase
assert_not_includes actual, "name: [FILTERED]"
assert_equal 0, actual.scan("[FILTERED]").length
end
+
+ test "filter_attributes on pretty_print should handle [FILTERED] value properly" do
+ begin
+ User.filter_attributes = ["auth"]
+ user = User.new(token: "[FILTERED]", auth_token: "[FILTERED]")
+ actual = "".dup
+ PP.pp(user, StringIO.new(actual))
+
+ assert_includes actual, "auth_token: [FILTERED]"
+ assert_includes actual, 'token: "[FILTERED]"'
+ ensure
+ User.remove_instance_variable(:@filter_attributes)
+ end
+ end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 355fb4517f..52fd9291b2 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -20,6 +20,7 @@ require "models/matey"
require "models/dog"
require "models/car"
require "models/tyre"
+require "models/subscriber"
class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
@@ -167,6 +168,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.exists?(id: [1, 9999])
assert_equal false, Topic.exists?(45)
+ assert_equal false, Topic.exists?(9999999999999999999999999999999)
assert_equal false, Topic.exists?(Topic.new.id)
assert_raise(NoMethodError) { Topic.exists?([1, 2]) }
@@ -211,17 +213,23 @@ class FinderTest < ActiveRecord::TestCase
assert_equal false, relation.exists?(false)
end
+ def test_exists_with_string
+ assert_equal false, Subscriber.exists?("foo")
+ assert_equal false, Subscriber.exists?(" ")
+
+ Subscriber.create!(id: "foo")
+ Subscriber.create!(id: " ")
+
+ assert_equal true, Subscriber.exists?("foo")
+ assert_equal true, Subscriber.exists?(" ")
+ end
+
def test_exists_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.exists?(Topic.new)
end
end
- def test_exists_returns_false_when_parameter_has_invalid_type
- assert_equal false, Topic.exists?("foo")
- assert_equal false, Topic.exists?(("9" * 53).to_i) # number that's bigger than int
- end
-
def test_exists_does_not_select_columns_without_alias
assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
Topic.exists?
@@ -246,6 +254,10 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.first.replies.exists?
end
+ def test_exists_with_empty_hash_arg
+ assert_equal true, Topic.exists?({})
+ end
+
# Ensure +exists?+ runs without an error by excluding distinct value.
# See https://github.com/rails/rails/pull/26981.
def test_exists_with_order_and_distinct
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 82ca15b415..a5592fc86a 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -473,11 +473,11 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_empty_yaml_fixture
- assert_not_nil ActiveRecord::FixtureSet.new(Account.connection, "accounts", Account, FIXTURES_ROOT + "/naked/yml/accounts")
+ assert_not_nil ActiveRecord::FixtureSet.new(nil, "accounts", Account, FIXTURES_ROOT + "/naked/yml/accounts")
end
def test_empty_yaml_fixture_with_a_comment_in_it
- assert_not_nil ActiveRecord::FixtureSet.new(Account.connection, "companies", Company, FIXTURES_ROOT + "/naked/yml/companies")
+ assert_not_nil ActiveRecord::FixtureSet.new(nil, "companies", Company, FIXTURES_ROOT + "/naked/yml/companies")
end
def test_nonexistent_fixture_file
@@ -487,14 +487,14 @@ class FixturesTest < ActiveRecord::TestCase
assert_empty Dir[nonexistent_fixture_path + "*"]
assert_raise(Errno::ENOENT) do
- ActiveRecord::FixtureSet.new(Account.connection, "companies", Company, nonexistent_fixture_path)
+ ActiveRecord::FixtureSet.new(nil, "companies", Company, nonexistent_fixture_path)
end
end
def test_dirty_dirty_yaml_file
fixture_path = FIXTURES_ROOT + "/naked/yml/courses"
error = assert_raise(ActiveRecord::Fixture::FormatError) do
- ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path)
+ ActiveRecord::FixtureSet.new(nil, "courses", Course, fixture_path)
end
assert_equal "fixture is not a hash: #{fixture_path}.yml", error.to_s
end
@@ -502,7 +502,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_yaml_file_with_one_invalid_fixture
fixture_path = FIXTURES_ROOT + "/naked/yml/courses_with_invalid_key"
error = assert_raise(ActiveRecord::Fixture::FormatError) do
- ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path)
+ ActiveRecord::FixtureSet.new(nil, "courses", Course, fixture_path)
end
assert_equal "fixture key is not a hash: #{fixture_path}.yml, keys: [\"two\"]", error.to_s
end
@@ -525,7 +525,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_omap_fixtures
assert_nothing_raised do
- fixtures = ActiveRecord::FixtureSet.new(Account.connection, "categories", Category, FIXTURES_ROOT + "/categories_ordered")
+ fixtures = ActiveRecord::FixtureSet.new(nil, "categories", Category, FIXTURES_ROOT + "/categories_ordered")
fixtures.each.with_index do |(name, fixture), i|
assert_equal "fixture_no_#{i}", name
@@ -596,7 +596,7 @@ class HasManyThroughFixture < ActiveRecord::TestCase
parrots = File.join FIXTURES_ROOT, "parrots"
- fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots
+ fs = ActiveRecord::FixtureSet.new(nil, "parrots", parrot, parrots)
rows = fs.table_rows
assert_equal load_has_and_belongs_to_many["parrots_treasures"], rows["parrots_treasures"]
end
@@ -614,18 +614,22 @@ class HasManyThroughFixture < ActiveRecord::TestCase
parrots = File.join FIXTURES_ROOT, "parrots"
- fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots
+ fs = ActiveRecord::FixtureSet.new(nil, "parrots", parrot, parrots)
rows = fs.table_rows
assert_equal load_has_and_belongs_to_many["parrots_treasures"], rows["parrot_treasures"]
end
+ def test_has_and_belongs_to_many_order
+ assert_equal ["parrots", "parrots_treasures"], load_has_and_belongs_to_many.keys
+ end
+
def load_has_and_belongs_to_many
parrot = make_model "Parrot"
parrot.has_and_belongs_to_many :treasures
parrots = File.join FIXTURES_ROOT, "parrots"
- fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots
+ fs = ActiveRecord::FixtureSet.new(nil, "parrots", parrot, parrots)
fs.table_rows
end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 68be685e4b..730cd663a2 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -48,6 +48,15 @@ def mysql_enforcing_gtid_consistency?
current_adapter?(:Mysql2Adapter) && "ON" == ActiveRecord::Base.connection.show_variable("enforce_gtid_consistency")
end
+def supports_default_expression?
+ if current_adapter?(:PostgreSQLAdapter)
+ true
+ elsif current_adapter?(:Mysql2Adapter)
+ conn = ActiveRecord::Base.connection
+ !conn.mariadb? && conn.version >= "8.0.13"
+ end
+end
+
def supports_savepoints?
ActiveRecord::Base.connection.supports_savepoints?
end
diff --git a/activerecord/test/cases/null_relation_test.rb b/activerecord/test/cases/null_relation_test.rb
index c5d5ded5ab..ee96ea1af6 100644
--- a/activerecord/test/cases/null_relation_test.rb
+++ b/activerecord/test/cases/null_relation_test.rb
@@ -17,6 +17,7 @@ class NullRelationTest < ActiveRecord::TestCase
end
def test_none_chainable
+ Developer.send(:load_schema)
assert_no_queries do
assert_equal [], Developer.none.where(name: "David")
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 9914a61033..e471ee8039 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1812,6 +1812,16 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "Thank you for the welcome,Thank you again for the welcome", Post.first.comments.join(",")
end
+ def test_relation_with_private_kernel_method
+ accounts = Account.all
+ assert_equal [accounts(:signals37)], accounts.open
+ assert_equal [accounts(:signals37)], accounts.available
+
+ sub_accounts = SubAccount.all
+ assert_equal [accounts(:signals37)], sub_accounts.open
+ assert_equal [accounts(:signals37)], sub_accounts.available
+ end
+
test "#skip_query_cache!" do
Post.cache do
assert_queries(1) do
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index db13f20a39..dda3efa47c 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -229,11 +229,14 @@ class SchemaDumperTest < ActiveRecord::TestCase
if ActiveRecord::Base.connection.supports_expression_index?
def test_schema_dump_expression_indices
index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
+ index_definition.sub!(/, name: "company_expression_index"\z/, "")
if current_adapter?(:PostgreSQLAdapter)
- assert_match %r{CASE.+lower\(\(name\)::text\)}i, index_definition
+ assert_match %r{CASE.+lower\(\(name\)::text\).+END\) DESC"\z}i, index_definition
+ elsif current_adapter?(:Mysql2Adapter)
+ assert_match %r{CASE.+lower\(`name`\).+END\) DESC"\z}i, index_definition
elsif current_adapter?(:SQLite3Adapter)
- assert_match %r{CASE.+lower\(name\)}i, index_definition
+ assert_match %r{CASE.+lower\(name\).+END\) DESC"\z}i, index_definition
else
assert false
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index d674bd562f..3fd1813d64 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -731,7 +731,7 @@ module ActiveRecord
end
if current_adapter?(:SQLite3Adapter) && !in_memory_db?
- class DatabaseTasksMigrateTest < ActiveRecord::TestCase
+ class DatabaseTasksMigrationTestCase < ActiveRecord::TestCase
self.use_transactional_tests = false
# Use a memory db here to avoid having to rollback at the end
@@ -751,7 +751,9 @@ module ActiveRecord
@conn.release_connection if @conn
ActiveRecord::Base.establish_connection :arunit
end
+ end
+ class DatabaseTasksMigrateTest < DatabaseTasksMigrationTestCase
def test_migrate_set_and_unset_verbose_and_version_env_vars
verbose, version = ENV["VERBOSE"], ENV["VERSION"]
ENV["VERSION"] = "2"
@@ -812,6 +814,26 @@ module ActiveRecord
end
end
end
+
+ class DatabaseTasksMigrateStatusTest < DatabaseTasksMigrationTestCase
+ def test_migrate_status_table
+ ActiveRecord::SchemaMigration.create_table
+ output = capture_migration_status
+ assert_match(/database: :memory:/, output)
+ assert_match(/down 001 Valid people have last names/, output)
+ assert_match(/down 002 We need reminders/, output)
+ assert_match(/down 003 Innocent jointable/, output)
+ ActiveRecord::SchemaMigration.drop_table
+ end
+
+ private
+
+ def capture_migration_status
+ capture(:stdout) do
+ ActiveRecord::Tasks::DatabaseTasks.migrate_status
+ end
+ end
+ end
end
class DatabaseTasksMigrateErrorTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 4d6dff68f9..552e623fd4 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -272,7 +272,7 @@ if current_adapter?(:Mysql2Adapter)
def test_db_retrieves_collation
ActiveRecord::Base.stub(:connection, @connection) do
- assert_called_with(@connection, :collation) do
+ assert_called(@connection, :collation) do
ActiveRecord::Tasks::DatabaseTasks.collation @configuration
end
end
diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml
index be337ddcd8..18347cd07d 100644
--- a/activerecord/test/config.example.yml
+++ b/activerecord/test/config.example.yml
@@ -59,6 +59,7 @@ connections:
arunit2:
username: rails
encoding: utf8mb4
+ collation: utf8mb4_general_ci
oracle:
arunit:
diff --git a/activerecord/test/fixtures/citations.yml b/activerecord/test/fixtures/citations.yml
index d31cb8efa1..396099621c 100644
--- a/activerecord/test/fixtures/citations.yml
+++ b/activerecord/test/fixtures/citations.yml
@@ -1,4 +1,5 @@
<% 65536.times do |i| %>
fixture_no_<%= i %>:
id: <%= i %>
+ book2_id: <%= i*i %>
<% end %>
diff --git a/activerecord/test/models/account.rb b/activerecord/test/models/account.rb
index 0c3cd45a81..639e395743 100644
--- a/activerecord/test/models/account.rb
+++ b/activerecord/test/models/account.rb
@@ -11,9 +11,8 @@ class Account < ActiveRecord::Base
end
# Test private kernel method through collection proxy using has_many.
- def self.open
- where("firm_name = ?", "37signals")
- end
+ scope :open, -> { where("firm_name = ?", "37signals") }
+ scope :available, -> { open }
before_destroy do |account|
if account.firm
@@ -32,3 +31,11 @@ class Account < ActiveRecord::Base
"Sir, yes sir!"
end
end
+
+class SubAccount < Account
+ def self.instantiate_instance_of(klass, attributes, column_types = {}, &block)
+ klass = superclass
+ super
+ end
+ private_class_method :instantiate_instance_of
+end
diff --git a/activerecord/test/models/country.rb b/activerecord/test/models/country.rb
index 0c84a40de2..4b4a276a98 100644
--- a/activerecord/test/models/country.rb
+++ b/activerecord/test/models/country.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
class Country < ActiveRecord::Base
- self.primary_key = :country_id
-
has_and_belongs_to_many :treaties
end
diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb
index ba9ddb8c6a..3bb5316eca 100644
--- a/activerecord/test/models/parrot.rb
+++ b/activerecord/test/models/parrot.rb
@@ -20,6 +20,12 @@ class Parrot < ActiveRecord::Base
def increment_updated_count
self.updated_count += 1
end
+
+ def self.delete_all(*)
+ connection.delete("DELETE FROM parrots_pirates")
+ connection.delete("DELETE FROM parrots_treasures")
+ super
+ end
end
class LiveParrot < Parrot
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 528585fb75..710a75ad30 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -297,8 +297,6 @@ end
class FakeKlass
extend ActiveRecord::Delegation::DelegateCache
- inherited self
-
class << self
def connection
Post.connection
@@ -335,5 +333,11 @@ class FakeKlass
def predicate_builder
Post.predicate_builder
end
+
+ def base_class?
+ true
+ end
end
+
+ inherited self
end
diff --git a/activerecord/test/models/treaty.rb b/activerecord/test/models/treaty.rb
index 5c1d75aa09..b87a757d2a 100644
--- a/activerecord/test/models/treaty.rb
+++ b/activerecord/test/models/treaty.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
class Treaty < ActiveRecord::Base
- self.primary_key = :treaty_id
-
has_and_belongs_to_many :countries
end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 499280cb0c..ccca9a2d9b 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -19,6 +19,9 @@ ActiveRecord::Schema.define do
t.datetime :fixed_time, default: "2004-01-01 00:00:00"
t.column :char1, "char(1)", default: "Y"
t.string :char2, limit: 50, default: "a varchar field"
+ if supports_default_expression?
+ t.binary :uuid, limit: 36, default: -> { "(uuid())" }
+ end
end
create_table :binary_fields, force: true do |t|
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 2aaf393009..7034c773d2 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -93,7 +93,7 @@ ActiveRecord::Schema.define do
t.integer :pirate_id
end
- create_table :books, force: true do |t|
+ create_table :books, id: :integer, force: true do |t|
t.references :author
t.string :format
t.column :name, :string
@@ -158,8 +158,8 @@ ActiveRecord::Schema.define do
end
create_table :citations, force: true do |t|
- t.column :book1_id, :integer
- t.column :book2_id, :integer
+ t.references :book1
+ t.references :book2
t.references :citation
end
@@ -216,7 +216,7 @@ ActiveRecord::Schema.define do
t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc }
t.index [:firm_id, :type], name: "company_partial_index", where: "(rating > 10)"
t.index :name, name: "company_name_index", using: :btree
- t.index "(CASE WHEN rating > 0 THEN lower(name) END)", name: "company_expression_index" if supports_expression_index?
+ t.index "(CASE WHEN rating > 0 THEN lower(name) END) DESC", name: "company_expression_index" if supports_expression_index?
end
create_table :content, force: true do |t|
@@ -601,33 +601,55 @@ ActiveRecord::Schema.define do
t.integer :non_poly_two_id
end
- create_table :parrots, force: true do |t|
- t.column :name, :string
- t.column :color, :string
- t.column :parrot_sti_class, :string
- t.column :killer_id, :integer
- t.column :updated_count, :integer, default: 0
- if subsecond_precision_supported?
- t.column :created_at, :datetime, precision: 0
- t.column :created_on, :datetime, precision: 0
- t.column :updated_at, :datetime, precision: 0
- t.column :updated_on, :datetime, precision: 0
- else
- t.column :created_at, :datetime
- t.column :created_on, :datetime
- t.column :updated_at, :datetime
- t.column :updated_on, :datetime
+ disable_referential_integrity do
+ create_table :parrots, force: :cascade do |t|
+ t.string :name
+ t.string :color
+ t.string :parrot_sti_class
+ t.integer :killer_id
+ t.integer :updated_count, :integer, default: 0
+ if subsecond_precision_supported?
+ t.datetime :created_at, precision: 0
+ t.datetime :created_on, precision: 0
+ t.datetime :updated_at, precision: 0
+ t.datetime :updated_on, precision: 0
+ else
+ t.datetime :created_at
+ t.datetime :created_on
+ t.datetime :updated_at
+ t.datetime :updated_on
+ end
end
- end
- create_table :parrots_pirates, id: false, force: true do |t|
- t.column :parrot_id, :integer
- t.column :pirate_id, :integer
- end
+ create_table :pirates, force: :cascade do |t|
+ t.string :catchphrase
+ t.integer :parrot_id
+ t.integer :non_validated_parrot_id
+ if subsecond_precision_supported?
+ t.datetime :created_on, precision: 6
+ t.datetime :updated_on, precision: 6
+ else
+ t.datetime :created_on
+ t.datetime :updated_on
+ end
+ end
- create_table :parrots_treasures, id: false, force: true do |t|
- t.column :parrot_id, :integer
- t.column :treasure_id, :integer
+ create_table :treasures, force: :cascade do |t|
+ t.string :name
+ t.string :type
+ t.references :looter, polymorphic: true
+ t.references :ship
+ end
+
+ create_table :parrots_pirates, id: false, force: true do |t|
+ t.references :parrot, foreign_key: true
+ t.references :pirate, foreign_key: true
+ end
+
+ create_table :parrots_treasures, id: false, force: true do |t|
+ t.references :parrot, foreign_key: true
+ t.references :treasure, foreign_key: true
+ end
end
create_table :people, force: true do |t|
@@ -673,19 +695,6 @@ ActiveRecord::Schema.define do
t.column :rainbow_color, :string
end
- create_table :pirates, force: true do |t|
- t.column :catchphrase, :string
- t.column :parrot_id, :integer
- t.integer :non_validated_parrot_id
- if subsecond_precision_supported?
- t.column :created_on, :datetime, precision: 6
- t.column :updated_on, :datetime, precision: 6
- else
- t.column :created_on, :datetime
- t.column :updated_on, :datetime
- end
- end
-
create_table :posts, force: true do |t|
t.references :author
t.string :title, null: false
@@ -916,14 +925,6 @@ ActiveRecord::Schema.define do
t.datetime :updated_at
end
- create_table :treasures, force: true do |t|
- t.column :name, :string
- t.column :type, :string
- t.column :looter_id, :integer
- t.column :looter_type, :string
- t.belongs_to :ship
- end
-
create_table :tuning_pegs, force: true do |t|
t.integer :guitar_id
t.float :pitch
@@ -983,14 +984,16 @@ ActiveRecord::Schema.define do
t.references :wheelable, polymorphic: true
end
- create_table :countries, force: true, id: false, primary_key: "country_id" do |t|
- t.string :country_id
+ create_table :countries, force: true, id: false do |t|
+ t.string :country_id, primary_key: true
t.string :name
end
- create_table :treaties, force: true, id: false, primary_key: "treaty_id" do |t|
- t.string :treaty_id
+
+ create_table :treaties, force: true, id: false do |t|
+ t.string :treaty_id, primary_key: true
t.string :name
end
+
create_table :countries_treaties, force: true, primary_key: [:country_id, :treaty_id] do |t|
t.string :country_id, null: false
t.string :treaty_id, null: false
diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb
index 4bdd1c0224..13758d9179 100644
--- a/activestorage/app/models/active_storage/attachment.rb
+++ b/activestorage/app/models/active_storage/attachment.rb
@@ -3,9 +3,8 @@
require "active_support/core_ext/module/delegation"
# Attachments associate records with blobs. Usually that's a one record-many blobs relationship,
-# but it is possible to associate many different records with the same blob. If you're doing that,
-# you'll want to declare with <tt>has_one/many_attached :thingy, dependent: false</tt>, so that destroying
-# any one record won't destroy the blob as well. (Then you'll need to do your own garbage collecting, though).
+# but it is possible to associate many different records with the same blob. A foreign-key constraint
+# on the attachments table prevents blobs from being purged if they’re still attached to any records.
class ActiveStorage::Attachment < ActiveRecord::Base
self.table_name = "active_storage_attachments"
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index b796df26aa..98d232da33 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,40 @@
+* Fix bug where `#to_options` for `ActiveSupport::HashWithIndifferentAccess`
+ would not act as alias for `#symbolize_keys`.
+
+ *Nick Weiland*
+
+* Improve the logic that detects non-autoloaded constants.
+
+ *Jan Habermann*, *Xavier Noria*
+
+* Deprecate `ActiveSupport::Multibyte::Unicode#pack_graphemes(array)` and `ActiveSuppport::Multibyte::Unicode#unpack_graphemes(string)`
+ in favor of `array.flatten.pack("U*")` and `string.scan(/\X/).map(&:codepoints)`, respectively.
+
+ *Francesco Rodríguez*
+
+* Deprecate `ActiveSupport::Multibyte::Chars.consumes?` in favor of `String#is_utf8?`.
+
+ *Francesco Rodríguez*
+
+* Fix duration being rounded to a full second.
+ ```
+ time = DateTime.parse("2018-1-1")
+ time += 0.51.seconds
+ ```
+ Will now correctly add 0.51 second and not 1 full second.
+
+ *Edouard Chin*
+
+* Deprecate `ActiveSupport::Multibyte::Unicode#normalize` and `ActiveSuppport::Multibyte::Chars#normalize`
+ in favor of `String#unicode_normalize`
+
+ *Francesco Rodríguez*
+
+* Deprecate `ActiveSupport::Multibyte::Unicode#downcase/upcase/swapcase` in favor of
+ `String#downcase/upcase/swapcase`.
+
+ *Francesco Rodríguez*
+
* Add `ActiveSupport::ParameterFilter`.
*Yoshiyuki Kinjo*
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 2be79d86dd..e8518645d9 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -411,8 +411,6 @@ module ActiveSupport
# to the cache. If you do not want to write the cache when the cache is
# not found, use #read_multi.
#
- # Options are passed to the underlying cache implementation.
- #
# Returns a hash with the data for each of the names. For example:
#
# cache.write("bim", "bam")
@@ -422,6 +420,17 @@ module ActiveSupport
# # => { "bim" => "bam",
# # "unknown_key" => "Fallback value for key: unknown_key" }
#
+ # Options are passed to the underlying cache implementation. For example:
+ #
+ # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
+ # "buzz"
+ # end
+ # # => {"fizz"=>"buzz"}
+ # cache.read("fizz")
+ # # => "buzz"
+ # sleep(6)
+ # cache.read("fizz")
+ # # => nil
def fetch_multi(*names)
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 487fe79f41..d0644a0f7e 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -23,6 +23,9 @@ module ActiveSupport
# +ClassMethods.set_callback+), and run the installed callbacks at the
# appropriate times (via +run_callbacks+).
#
+ # By default callbacks are halted by throwing +:abort+.
+ # See +ClassMethods.define_callbacks+ for details.
+ #
# Three kinds of callbacks are supported: before callbacks, run before a
# certain event; after callbacks, run after the event; and around callbacks,
# blocks that surround the event, triggering it when they yield. Callback code
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 6159e45230..9acf674c40 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -2,7 +2,6 @@
require "active_support/concern"
require "active_support/ordered_options"
-require "active_support/core_ext/array/extract_options"
module ActiveSupport
# Configurable provides a <tt>config</tt> method to store and retrieve
@@ -105,9 +104,7 @@ module ActiveSupport
# end
#
# User.hair_colors # => [:brown, :black, :blonde, :red]
- def config_accessor(*names) #:doc:
- options = names.extract_options!
-
+ def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true) # :doc:
names.each do |name|
raise NameError.new("invalid config attribute name") unless /\A[_A-Za-z]\w*\z/.match?(name)
@@ -117,9 +114,9 @@ module ActiveSupport
singleton_class.class_eval reader, __FILE__, reader_line
singleton_class.class_eval writer, __FILE__, writer_line
- unless options[:instance_accessor] == false
- class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false
- class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false
+ if instance_accessor
+ class_eval reader, __FILE__, reader_line if instance_reader
+ class_eval writer, __FILE__, writer_line if instance_writer
end
send("#{name}=", yield) if block_given?
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index e61b23f842..bc670c3e76 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -110,7 +110,7 @@ class DateTime
# instance time. Do not use this method in combination with x.months, use
# months_since instead!
def since(seconds)
- self + Rational(seconds.round, 86400)
+ self + Rational(seconds, 86400)
end
alias :in :since
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 281eaa7d5f..5850e0193f 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/array/extract_options"
-
# Extends the module object with class/module and instance accessors for
# class/module attributes, just like the native attr* accessors for instance
# attributes.
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
index b1788a000b..cb996e9e6d 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/array/extract_options"
-
# Extends the module object with class/module and instance accessors for
# class/module attributes, just like the native attr* accessors for instance
# attributes, but does so on a per-thread basis.
@@ -35,9 +33,7 @@ class Module
# end
#
# Current.new.user # => NoMethodError
- def thread_mattr_reader(*syms) # :nodoc:
- options = syms.extract_options!
-
+ def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true) # :nodoc:
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
@@ -49,7 +45,7 @@ class Module
end
EOS
- unless options[:instance_reader] == false || options[:instance_accessor] == false
+ if instance_reader && instance_accessor
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
self.class.#{sym}
@@ -78,8 +74,7 @@ class Module
# end
#
# Current.new.user = "DHH" # => NoMethodError
- def thread_mattr_writer(*syms) # :nodoc:
- options = syms.extract_options!
+ def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true) # :nodoc:
syms.each do |sym|
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
@@ -91,7 +86,7 @@ class Module
end
EOS
- unless options[:instance_writer] == false || options[:instance_accessor] == false
+ if instance_writer && instance_accessor
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}=(obj)
self.class.#{sym} = obj
@@ -141,9 +136,9 @@ class Module
#
# Current.new.user = "DHH" # => NoMethodError
# Current.new.user # => NoMethodError
- def thread_mattr_accessor(*syms)
- thread_mattr_reader(*syms)
- thread_mattr_writer(*syms)
+ def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true)
+ thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor)
+ thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor)
end
alias :thread_cattr_accessor :thread_mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 7fcd0d0311..8acad6164a 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -4,127 +4,129 @@ require "active_support/core_ext/big_decimal/conversions"
require "active_support/number_helper"
require "active_support/core_ext/module/deprecation"
-module ActiveSupport::NumericWithFormat
- # Provides options for converting numbers into formatted strings.
- # Options are provided for phone numbers, currency, percentage,
- # precision, positional notation, file size and pretty printing.
- #
- # ==== Options
- #
- # For details on which formats use which options, see ActiveSupport::NumberHelper
- #
- # ==== Examples
- #
- # Phone Numbers:
- # 5551234.to_s(:phone) # => "555-1234"
- # 1235551234.to_s(:phone) # => "123-555-1234"
- # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234"
- # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234"
- # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
- # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234"
- # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
- # # => "+1.123.555.1234 x 1343"
- #
- # Currency:
- # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
- # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
- # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
- # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €"
- # -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
- # # => "($1,234,567,890.50)"
- # 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '')
- # # => "&pound;1234567890,50"
- # 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
- # # => "1234567890,50 &pound;"
- #
- # Percentage:
- # 100.to_s(:percentage) # => "100.000%"
- # 100.to_s(:percentage, precision: 0) # => "100%"
- # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
- # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%"
- # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%"
- # 100.to_s(:percentage, format: '%n %') # => "100.000 %"
- #
- # Delimited:
- # 12345678.to_s(:delimited) # => "12,345,678"
- # 12345678.05.to_s(:delimited) # => "12,345,678.05"
- # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
- # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
- # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
- # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
- # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
- # # => "98 765 432,98"
- #
- # Rounded:
- # 111.2345.to_s(:rounded) # => "111.235"
- # 111.2345.to_s(:rounded, precision: 2) # => "111.23"
- # 13.to_s(:rounded, precision: 5) # => "13.00000"
- # 389.32314.to_s(:rounded, precision: 0) # => "389"
- # 111.2345.to_s(:rounded, significant: true) # => "111"
- # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
- # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
- # 111.234.to_s(:rounded, locale: :fr) # => "111,234"
- # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
- # # => "13"
- # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
- # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
- # # => "1.111,23"
- #
- # Human-friendly size in Bytes:
- # 123.to_s(:human_size) # => "123 Bytes"
- # 1234.to_s(:human_size) # => "1.21 KB"
- # 12345.to_s(:human_size) # => "12.1 KB"
- # 1234567.to_s(:human_size) # => "1.18 MB"
- # 1234567890.to_s(:human_size) # => "1.15 GB"
- # 1234567890123.to_s(:human_size) # => "1.12 TB"
- # 1234567890123456.to_s(:human_size) # => "1.1 PB"
- # 1234567890123456789.to_s(:human_size) # => "1.07 EB"
- # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB"
- # 483989.to_s(:human_size, precision: 2) # => "470 KB"
- # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
- # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
- # 524288000.to_s(:human_size, precision: 5) # => "500 MB"
- #
- # Human-friendly format:
- # 123.to_s(:human) # => "123"
- # 1234.to_s(:human) # => "1.23 Thousand"
- # 12345.to_s(:human) # => "12.3 Thousand"
- # 1234567.to_s(:human) # => "1.23 Million"
- # 1234567890.to_s(:human) # => "1.23 Billion"
- # 1234567890123.to_s(:human) # => "1.23 Trillion"
- # 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
- # 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
- # 489939.to_s(:human, precision: 2) # => "490 Thousand"
- # 489939.to_s(:human, precision: 4) # => "489.9 Thousand"
- # 1234567.to_s(:human, precision: 4,
- # significant: false) # => "1.2346 Million"
- # 1234567.to_s(:human, precision: 1,
- # separator: ',',
- # significant: false) # => "1,2 Million"
- def to_s(format = nil, options = nil)
- case format
- when nil
- super()
- when Integer, String
- super(format)
- when :phone
- ActiveSupport::NumberHelper.number_to_phone(self, options || {})
- when :currency
- ActiveSupport::NumberHelper.number_to_currency(self, options || {})
- when :percentage
- ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
- when :delimited
- ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
- when :rounded
- ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
- when :human
- ActiveSupport::NumberHelper.number_to_human(self, options || {})
- when :human_size
- ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
- when Symbol
- super()
- else
- super(format)
+module ActiveSupport
+ module NumericWithFormat
+ # Provides options for converting numbers into formatted strings.
+ # Options are provided for phone numbers, currency, percentage,
+ # precision, positional notation, file size and pretty printing.
+ #
+ # ==== Options
+ #
+ # For details on which formats use which options, see ActiveSupport::NumberHelper
+ #
+ # ==== Examples
+ #
+ # Phone Numbers:
+ # 5551234.to_s(:phone) # => "555-1234"
+ # 1235551234.to_s(:phone) # => "123-555-1234"
+ # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234"
+ # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234"
+ # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
+ # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234"
+ # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
+ # # => "+1.123.555.1234 x 1343"
+ #
+ # Currency:
+ # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
+ # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
+ # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
+ # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €"
+ # -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
+ # # => "($1,234,567,890.50)"
+ # 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '')
+ # # => "&pound;1234567890,50"
+ # 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
+ # # => "1234567890,50 &pound;"
+ #
+ # Percentage:
+ # 100.to_s(:percentage) # => "100.000%"
+ # 100.to_s(:percentage, precision: 0) # => "100%"
+ # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
+ # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%"
+ # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%"
+ # 100.to_s(:percentage, format: '%n %') # => "100.000 %"
+ #
+ # Delimited:
+ # 12345678.to_s(:delimited) # => "12,345,678"
+ # 12345678.05.to_s(:delimited) # => "12,345,678.05"
+ # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
+ # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
+ # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
+ # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
+ # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
+ # # => "98 765 432,98"
+ #
+ # Rounded:
+ # 111.2345.to_s(:rounded) # => "111.235"
+ # 111.2345.to_s(:rounded, precision: 2) # => "111.23"
+ # 13.to_s(:rounded, precision: 5) # => "13.00000"
+ # 389.32314.to_s(:rounded, precision: 0) # => "389"
+ # 111.2345.to_s(:rounded, significant: true) # => "111"
+ # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
+ # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
+ # 111.234.to_s(:rounded, locale: :fr) # => "111,234"
+ # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
+ # # => "13"
+ # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
+ # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
+ # # => "1.111,23"
+ #
+ # Human-friendly size in Bytes:
+ # 123.to_s(:human_size) # => "123 Bytes"
+ # 1234.to_s(:human_size) # => "1.21 KB"
+ # 12345.to_s(:human_size) # => "12.1 KB"
+ # 1234567.to_s(:human_size) # => "1.18 MB"
+ # 1234567890.to_s(:human_size) # => "1.15 GB"
+ # 1234567890123.to_s(:human_size) # => "1.12 TB"
+ # 1234567890123456.to_s(:human_size) # => "1.1 PB"
+ # 1234567890123456789.to_s(:human_size) # => "1.07 EB"
+ # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB"
+ # 483989.to_s(:human_size, precision: 2) # => "470 KB"
+ # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
+ # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
+ # 524288000.to_s(:human_size, precision: 5) # => "500 MB"
+ #
+ # Human-friendly format:
+ # 123.to_s(:human) # => "123"
+ # 1234.to_s(:human) # => "1.23 Thousand"
+ # 12345.to_s(:human) # => "12.3 Thousand"
+ # 1234567.to_s(:human) # => "1.23 Million"
+ # 1234567890.to_s(:human) # => "1.23 Billion"
+ # 1234567890123.to_s(:human) # => "1.23 Trillion"
+ # 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
+ # 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
+ # 489939.to_s(:human, precision: 2) # => "490 Thousand"
+ # 489939.to_s(:human, precision: 4) # => "489.9 Thousand"
+ # 1234567.to_s(:human, precision: 4,
+ # significant: false) # => "1.2346 Million"
+ # 1234567.to_s(:human, precision: 1,
+ # separator: ',',
+ # significant: false) # => "1,2 Million"
+ def to_s(format = nil, options = nil)
+ case format
+ when nil
+ super()
+ when Integer, String
+ super(format)
+ when :phone
+ ActiveSupport::NumberHelper.number_to_phone(self, options || {})
+ when :currency
+ ActiveSupport::NumberHelper.number_to_currency(self, options || {})
+ when :percentage
+ ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
+ when :delimited
+ ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
+ when :rounded
+ ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
+ when :human
+ ActiveSupport::NumberHelper.number_to_human(self, options || {})
+ when :human_size
+ ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
+ when Symbol
+ super()
+ else
+ super(format)
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb
index 8832fbcb3c..024e32db40 100644
--- a/activesupport/lib/active_support/core_ext/range/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/range/conversions.rb
@@ -1,39 +1,41 @@
# frozen_string_literal: true
-module ActiveSupport::RangeWithFormat
- RANGE_FORMATS = {
- db: -> (start, stop) do
- case start
- when String then "BETWEEN '#{start}' AND '#{stop}'"
+module ActiveSupport
+ module RangeWithFormat
+ RANGE_FORMATS = {
+ db: -> (start, stop) do
+ case start
+ when String then "BETWEEN '#{start}' AND '#{stop}'"
+ else
+ "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
+ end
+ end
+ }
+
+ # Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
+ #
+ # range = (1..100) # => 1..100
+ #
+ # range.to_s # => "1..100"
+ # range.to_s(:db) # => "BETWEEN '1' AND '100'"
+ #
+ # == Adding your own range formats to to_s
+ # You can add your own formats to the Range::RANGE_FORMATS hash.
+ # Use the format name as the hash key and a Proc instance.
+ #
+ # # config/initializers/range_formats.rb
+ # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
+ def to_s(format = :default)
+ if formatter = RANGE_FORMATS[format]
+ formatter.call(first, last)
else
- "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
+ super()
end
end
- }
- # Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
- #
- # range = (1..100) # => 1..100
- #
- # range.to_s # => "1..100"
- # range.to_s(:db) # => "BETWEEN '1' AND '100'"
- #
- # == Adding your own range formats to to_s
- # You can add your own formats to the Range::RANGE_FORMATS hash.
- # Use the format name as the hash key and a Proc instance.
- #
- # # config/initializers/range_formats.rb
- # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
- def to_s(format = :default)
- if formatter = RANGE_FORMATS[format]
- formatter.call(first, last)
- else
- super()
- end
+ alias_method :to_default_s, :to_s
+ alias_method :to_formatted_s, :to_s
end
-
- alias_method :to_default_s, :to_s
- alias_method :to_formatted_s, :to_s
end
Range.prepend(ActiveSupport::RangeWithFormat)
diff --git a/activesupport/lib/active_support/current_attributes.rb b/activesupport/lib/active_support/current_attributes.rb
index 4e6d8e4585..3145ff87a1 100644
--- a/activesupport/lib/active_support/current_attributes.rb
+++ b/activesupport/lib/active_support/current_attributes.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/callbacks"
+
module ActiveSupport
# Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
# before and after each request. This allows you to keep all the per-request attributes easily
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 66e0bea00e..d5d00b5e6e 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -103,6 +103,8 @@ module ActiveSupport #:nodoc:
# parent.rb then requires namespace/child.rb, the stack will look like
# [[Object], [Namespace]].
+ attr_reader :watching
+
def initialize
@watching = []
@stack = Hash.new { |h, k| h[k] = [] }
@@ -254,7 +256,9 @@ module ActiveSupport #:nodoc:
def load_dependency(file)
if Dependencies.load? && Dependencies.constant_watch_stack.watching?
- Dependencies.new_constants_in(Object) { yield }
+ descs = Dependencies.constant_watch_stack.watching.flatten.uniq
+
+ Dependencies.new_constants_in(*descs) { yield }
else
yield
end
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
index f48c586cad..ca810db584 100644
--- a/activesupport/lib/active_support/execution_wrapper.rb
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "active_support/callbacks"
+require "concurrent/hash"
module ActiveSupport
class ExecutionWrapper
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 2fac579469..f1af76019a 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -289,6 +289,7 @@ module ActiveSupport
undef :symbolize_keys!
undef :deep_symbolize_keys!
def symbolize_keys; to_hash.symbolize_keys! end
+ alias_method :to_options, :symbolize_keys
def deep_symbolize_keys; to_hash.deep_symbolize_keys! end
def to_options!; self end
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index dbc8b8a2fa..0d2a17970f 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -62,9 +62,9 @@ module ActiveSupport
raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
I18n.transliterate(
- ActiveSupport::Multibyte::Unicode.normalize(
- ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
- replacement: replacement)
+ ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc),
+ replacement: replacement
+ )
end
# Replaces special characters in a string so that it may be used as part of
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 499a206f49..a1e23aeaca 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -17,7 +17,7 @@ module ActiveSupport #:nodoc:
# through the +mb_chars+ method. Methods which would normally return a
# String object now return a Chars object so methods can be chained.
#
- # 'The Perfect String '.mb_chars.downcase.strip.normalize
+ # 'The Perfect String '.mb_chars.downcase.strip
# # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
#
# Chars objects are perfectly interchangeable with String objects as long as
@@ -76,6 +76,11 @@ module ActiveSupport #:nodoc:
# Returns +true+ when the proxy class can handle the string. Returns
# +false+ otherwise.
def self.consumes?(string)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport::Multibyte::Chars.consumes? is deprecated and will be
+ removed from Rails 6.1. Use string.is_utf8? instead.
+ MSG
+
string.encoding == Encoding::UTF_8
end
@@ -108,7 +113,7 @@ module ActiveSupport #:nodoc:
#
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
def reverse
- chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*"))
+ chars(@wrapped_string.scan(/\X/).reverse.join)
end
# Limits the byte size of the string to a number of bytes without breaking
@@ -117,35 +122,7 @@ module ActiveSupport #:nodoc:
#
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
def limit(limit)
- slice(0...translate_offset(limit))
- end
-
- # Converts characters in the string to uppercase.
- #
- # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
- def upcase
- chars Unicode.upcase(@wrapped_string)
- end
-
- # Converts characters in the string to lowercase.
- #
- # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
- def downcase
- chars Unicode.downcase(@wrapped_string)
- end
-
- # Converts characters in the string to the opposite case.
- #
- # 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN"
- def swapcase
- chars Unicode.swapcase(@wrapped_string)
- end
-
- # Converts the first character to uppercase and the remainder to lowercase.
- #
- # 'über'.mb_chars.capitalize.to_s # => "Über"
- def capitalize
- (slice(0) || chars("")).upcase + (slice(1..-1) || chars("")).downcase
+ truncate_bytes(limit, omission: nil)
end
# Capitalizes the first letter of every word, when possible.
@@ -153,7 +130,7 @@ module ActiveSupport #:nodoc:
# "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
# "日本語".mb_chars.titleize.to_s # => "日本語"
def titleize
- chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { $1.upcase })
end
alias_method :titlecase, :titleize
@@ -165,7 +142,24 @@ module ActiveSupport #:nodoc:
# <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
# ActiveSupport::Multibyte::Unicode.default_normalization_form
def normalize(form = nil)
- chars(Unicode.normalize(@wrapped_string, form))
+ form ||= Unicode.default_normalization_form
+
+ # See https://www.unicode.org/reports/tr15, Table 1
+ if alias_form = Unicode::NORMALIZATION_FORM_ALIASES[form]
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
+ removed from Rails 6.1. Use #unicode_normalize(:#{alias_form}) instead.
+ MSG
+
+ send(:unicode_normalize, alias_form)
+ else
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
+ removed from Rails 6.1. Use #unicode_normalize instead.
+ MSG
+
+ raise ArgumentError, "#{form} is not a valid normalization variant", caller
+ end
end
# Performs canonical decomposition on all the characters.
@@ -189,7 +183,7 @@ module ActiveSupport #:nodoc:
# 'क्षि'.mb_chars.length # => 4
# 'क्षि'.mb_chars.grapheme_length # => 3
def grapheme_length
- Unicode.unpack_graphemes(@wrapped_string).length
+ @wrapped_string.scan(/\X/).length
end
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
@@ -205,7 +199,7 @@ module ActiveSupport #:nodoc:
to_s.as_json(options)
end
- %w(capitalize downcase reverse tidy_bytes upcase).each do |method|
+ %w(reverse tidy_bytes).each do |method|
define_method("#{method}!") do |*args|
@wrapped_string = send(method, *args).to_s
self
@@ -214,18 +208,6 @@ module ActiveSupport #:nodoc:
private
- def translate_offset(byte_offset)
- return nil if byte_offset.nil?
- return 0 if @wrapped_string == ""
-
- begin
- @wrapped_string.byteslice(0...byte_offset).unpack("U*").length
- rescue ArgumentError
- byte_offset -= 1
- retry
- end
- end
-
def chars(string)
self.class.new(string)
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 4f0e1165ef..ce8ecece69 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -6,10 +6,17 @@ module ActiveSupport
extend self
# A list of all available normalization forms.
- # See http://www.unicode.org/reports/tr15/tr15-29.html for more
+ # See https://www.unicode.org/reports/tr15/tr15-29.html for more
# information about normalization.
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
+ NORMALIZATION_FORM_ALIASES = { # :nodoc:
+ c: :nfc,
+ d: :nfd,
+ kc: :nfkc,
+ kd: :nfkd
+ }
+
# The Unicode version that is supported by the implementation
UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
@@ -27,6 +34,11 @@ module ActiveSupport
# Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]]
# Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]]
def unpack_graphemes(string)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport::Multibyte::Unicode#unpack_graphemes is deprecated and will be
+ removed from Rails 6.1. Use string.scan(/\X/).map(&:codepoints) instead.
+ MSG
+
string.scan(/\X/).map(&:codepoints)
end
@@ -34,6 +46,11 @@ module ActiveSupport
#
# Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि'
def pack_graphemes(unpacked)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport::Multibyte::Unicode#pack_graphemes is deprecated and will be
+ removed from Rails 6.1. Use array.flatten.pack("U*") instead.
+ MSG
+
unpacked.flatten.pack("U*")
end
@@ -100,31 +117,34 @@ module ActiveSupport
# Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
def normalize(string, form = nil)
form ||= @default_normalization_form
- # See http://www.unicode.org/reports/tr15, Table 1
- case form
- when :d
- string.unicode_normalize(:nfd)
- when :c
- string.unicode_normalize(:nfc)
- when :kd
- string.unicode_normalize(:nfkd)
- when :kc
- string.unicode_normalize(:nfkc)
+
+ # See https://www.unicode.org/reports/tr15, Table 1
+ if alias_form = NORMALIZATION_FORM_ALIASES[form]
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
+ removed from Rails 6.1. Use String#unicode_normalize(:#{alias_form}) instead.
+ MSG
+
+ string.unicode_normalize(alias_form)
else
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
+ removed from Rails 6.1. Use String#unicode_normalize instead.
+ MSG
+
raise ArgumentError, "#{form} is not a valid normalization variant", caller
end
end
- def downcase(string)
- string.downcase
- end
+ %w(downcase upcase swapcase).each do |method|
+ define_method(method) do |string|
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport::Multibyte::Unicode##{method} is deprecated and
+ will be removed from Rails 6.1. Use String methods directly.
+ MSG
- def upcase(string)
- string.upcase
- end
-
- def swapcase(string)
- string.swapcase
+ string.send(method)
+ end
end
private
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 6207de8094..01cc363e2b 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -34,7 +34,7 @@ module ActiveSupport
# name # => String, name of the event (such as 'render' from above)
# start # => Time, when the instrumented block started execution
# finish # => Time, when the instrumented block ended execution
- # id # => String, unique ID for this notification
+ # id # => String, unique ID for the instrumenter that fired the event
# payload # => Hash, the payload
# end
#
@@ -59,7 +59,7 @@ module ActiveSupport
# event.payload # => { extra: :information }
#
# The block in the <tt>subscribe</tt> call gets the name of the event, start
- # timestamp, end timestamp, a string with a unique identifier for that event
+ # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter
# (something like "535801666f04d0298cd6"), and a hash with the payload, in
# that order.
#
@@ -171,6 +171,24 @@ module ActiveSupport
end
end
+ # Subscribe to a given event name with the passed +block+.
+ #
+ # You can subscribe to events by passing a String to match exact event
+ # names, or by passing a Regexp to match all events that match a pattern.
+ #
+ # ActiveSupport::Notifications.subscribe(/render/) do |*args|
+ # ...
+ # end
+ #
+ # The +block+ will receive five parameters with information about the event:
+ #
+ # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
+ # name # => String, name of the event (such as 'render' from above)
+ # start # => Time, when the instrumented block started execution
+ # finish # => Time, when the instrumented block ended execution
+ # id # => String, unique ID for the instrumenter that fired the event
+ # payload # => Hash, the payload
+ # end
def subscribe(*args, &block)
notifier.subscribe(*args, &block)
end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index c75ad52b0c..d19a2f64d4 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/dependencies/autoload"
+
module ActiveSupport
module NumberHelper
extend ActiveSupport::Autoload
diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
index aef5b62aed..0e8ae82dd5 100644
--- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/number_helper/number_converter"
+
module ActiveSupport
module NumberHelper
class NumberToCurrencyConverter < NumberConverter # :nodoc:
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
index 05427fef53..467a580a2e 100644
--- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/number_helper/number_converter"
+
module ActiveSupport
module NumberHelper
class NumberToDelimitedConverter < NumberConverter #:nodoc:
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
index 908f788ee3..494408fc01 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/number_helper/number_converter"
+
module ActiveSupport
module NumberHelper
class NumberToHumanConverter < NumberConverter # :nodoc:
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
index 0c72096b72..91262fa656 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/number_helper/number_converter"
+
module ActiveSupport
module NumberHelper
class NumberToHumanSizeConverter < NumberConverter #:nodoc:
diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
index 6618ecffd5..0c2e190f8a 100644
--- a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/number_helper/number_converter"
+
module ActiveSupport
module NumberHelper
class NumberToPercentageConverter < NumberConverter # :nodoc:
diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
index 96410f4995..d5e72981b4 100644
--- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/number_helper/number_converter"
+
module ActiveSupport
module NumberHelper
class NumberToPhoneConverter < NumberConverter #:nodoc:
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index 0ee5ef92dd..6ceb9a572e 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/number_helper/number_converter"
+
module ActiveSupport
module NumberHelper
class NumberToRoundedConverter < NumberConverter # :nodoc:
diff --git a/activesupport/lib/active_support/parameter_filter.rb b/activesupport/lib/active_support/parameter_filter.rb
index 59945e9daa..1389d82523 100644
--- a/activesupport/lib/active_support/parameter_filter.rb
+++ b/activesupport/lib/active_support/parameter_filter.rb
@@ -28,22 +28,36 @@ module ActiveSupport
class ParameterFilter
FILTERED = "[FILTERED]" # :nodoc:
- def initialize(filters = [])
+ # Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+.
+ # Other types of filters are treated as +String+ using +to_s+.
+ # For +Proc+ filters, key, value, and optional original hash is passed to block arguments.
+ #
+ # ==== Options
+ #
+ # * <tt>:mask</tt> - A replaced object when filtered. Defaults to +"[FILTERED]"+
+ def initialize(filters = [], mask: FILTERED)
@filters = filters
+ @mask = mask
end
+ # Mask value of +params+ if key matches one of filters.
def filter(params)
compiled_filter.call(params)
end
+ # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated.
+ def filter_param(key, value)
+ @filters.empty? ? value : compiled_filter.value_for_key(key, value)
+ end
+
private
def compiled_filter
- @compiled_filter ||= CompiledFilter.compile(@filters)
+ @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask)
end
class CompiledFilter # :nodoc:
- def self.compile(filters)
+ def self.compile(filters, mask:)
return lambda { |params| params.dup } if filters.empty?
strings, regexps, blocks = [], [], []
@@ -65,42 +79,46 @@ module ActiveSupport
regexps << Regexp.new(strings.join("|"), true) unless strings.empty?
deep_regexps << Regexp.new(deep_strings.join("|"), true) unless deep_strings.empty?
- new regexps, deep_regexps, blocks
+ new regexps, deep_regexps, blocks, mask: mask
end
attr_reader :regexps, :deep_regexps, :blocks
- def initialize(regexps, deep_regexps, blocks)
+ def initialize(regexps, deep_regexps, blocks, mask:)
@regexps = regexps
@deep_regexps = deep_regexps.any? ? deep_regexps : nil
@blocks = blocks
+ @mask = mask
end
def call(params, parents = [], original_params = params)
filtered_params = params.class.new
params.each do |key, value|
- parents.push(key) if deep_regexps
- if regexps.any? { |r| key =~ r }
- value = FILTERED
- elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| joined =~ r }
- value = FILTERED
- elsif value.is_a?(Hash)
- value = call(value, parents, original_params)
- elsif value.is_a?(Array)
- value = value.map { |v| v.is_a?(Hash) ? call(v, parents, original_params) : v }
- elsif blocks.any?
- key = key.dup if key.duplicable?
- value = value.dup if value.duplicable?
- blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
- end
- parents.pop if deep_regexps
-
- filtered_params[key] = value
+ filtered_params[key] = value_for_key(key, value, parents, original_params)
end
filtered_params
end
+
+ def value_for_key(key, value, parents = [], original_params = nil)
+ parents.push(key) if deep_regexps
+ if regexps.any? { |r| r.match?(key) }
+ value = @mask
+ elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) }
+ value = @mask
+ elsif value.is_a?(Hash)
+ value = call(value, parents, original_params)
+ elsif value.is_a?(Array)
+ value = value.map { |v| v.is_a?(Hash) ? call(v, parents, original_params) : v }
+ elsif blocks.any?
+ key = key.dup if key.duplicable?
+ value = value.dup if value.duplicable?
+ blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
+ end
+ parents.pop if deep_regexps
+ value
+ end
end
end
end
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
index b26d9c3665..fea18e9712 100644
--- a/activesupport/lib/active_support/reloader.rb
+++ b/activesupport/lib/active_support/reloader.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "active_support/execution_wrapper"
+require "active_support/executor"
module ActiveSupport
#--
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 9562149f8d..f3e902f9dd 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -79,12 +79,12 @@ module ActiveSupport
end
def start(name, id, payload)
- e = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload)
- e.start!
+ event = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload)
+ event.start!
parent = event_stack.last
- parent << e if parent
+ parent << event if parent
- event_stack.push e
+ event_stack.push event
end
def finish(name, id, payload)
diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb
index ad923d1aab..4eb7a88576 100644
--- a/activesupport/lib/active_support/testing/file_fixtures.rb
+++ b/activesupport/lib/active_support/testing/file_fixtures.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/concern"
+
module ActiveSupport
module Testing
# Adds simple access to sample files called file fixtures.
diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb
index fdc70e1cd3..eba41aa907 100644
--- a/activesupport/lib/active_support/testing/method_call_assertions.rb
+++ b/activesupport/lib/active_support/testing/method_call_assertions.rb
@@ -17,7 +17,7 @@ module ActiveSupport
assert_equal times, times_called, error
end
- def assert_called_with(object, method_name, args = [], returns: nil)
+ def assert_called_with(object, method_name, args, returns: nil)
mock = Minitest::Mock.new
if args.all? { |arg| arg.is_a?(Array) }
diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb
index 615fe0238e..9c8dffa9d8 100644
--- a/activesupport/lib/active_support/testing/parallelization.rb
+++ b/activesupport/lib/active_support/testing/parallelization.rb
@@ -2,6 +2,7 @@
require "drb"
require "drb/unix"
+require "active_support/core_ext/module/attribute_accessors"
module ActiveSupport
module Testing
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index fd07a3a6a2..d9e033e23b 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -182,8 +182,9 @@ module ActiveSupport
"Samoa" => "Pacific/Apia"
}
- UTC_OFFSET_WITH_COLON = "%s%02d:%02d"
- UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(":", "")
+ UTC_OFFSET_WITH_COLON = "%s%02d:%02d" # :nodoc:
+ UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(":", "") # :nodoc:
+ private_constant :UTC_OFFSET_WITH_COLON, :UTC_OFFSET_WITHOUT_COLON
@lazy_zones_map = Concurrent::Map.new
@country_zones = Concurrent::Map.new
diff --git a/activesupport/test/autoloading_fixtures/module_folder/nested_with_require.rb b/activesupport/test/autoloading_fixtures/module_folder/nested_with_require.rb
new file mode 100644
index 0000000000..f9d6e675d7
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/module_folder/nested_with_require.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require "dependencies/module_folder/lib_class"
+
+module ModuleFolder
+ class NestedWithRequire
+ end
+end
diff --git a/activesupport/test/autoloading_fixtures/nested_with_require_parent.rb b/activesupport/test/autoloading_fixtures/nested_with_require_parent.rb
new file mode 100644
index 0000000000..e8fb321077
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/nested_with_require_parent.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class NestedWithRequireParent
+ ModuleFolder::NestedWithRequire
+end
diff --git a/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb b/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb
index 4e8ff60eb3..a4abdd37b9 100644
--- a/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb
+++ b/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb
@@ -2,7 +2,7 @@
module CacheInstrumentationBehavior
def test_fetch_multi_uses_write_multi_entries_store_provider_interface
- assert_called_with(@cache, :write_multi_entries) do
+ assert_called(@cache, :write_multi_entries) do
@cache.fetch_multi "a", "b", "c" do |key|
key * 2
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 894fb80cba..f9f6b21c9b 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -152,8 +152,8 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2005, 2, 22, 11, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).since(3600)
assert_equal DateTime.civil(2005, 2, 24, 10, 10, 10), DateTime.civil(2005, 2, 22, 10, 10, 10).since(86400 * 2)
assert_equal DateTime.civil(2005, 2, 24, 11, 10, 35), DateTime.civil(2005, 2, 22, 10, 10, 10).since(86400 * 2 + 3600 + 25)
- assert_equal DateTime.civil(2005, 2, 22, 10, 10, 11), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.333)
- assert_equal DateTime.civil(2005, 2, 22, 10, 10, 12), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.667)
+ assert_not_equal DateTime.civil(2005, 2, 22, 10, 10, 11), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.333)
+ assert_not_equal DateTime.civil(2005, 2, 22, 10, 10, 12), DateTime.civil(2005, 2, 22, 10, 10, 10).since(1.667)
end
def test_change
diff --git a/activesupport/test/dependencies/module_folder/lib_class.rb b/activesupport/test/dependencies/module_folder/lib_class.rb
new file mode 100644
index 0000000000..c6b52610c1
--- /dev/null
+++ b/activesupport/test/dependencies/module_folder/lib_class.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+ConstFromLib = 1
+
+module ModuleFolder
+ class LibClass
+ end
+end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index e144971e9f..9f2755a25c 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -282,6 +282,32 @@ class DependenciesTest < ActiveSupport::TestCase
remove_constants(:ModuleFolder)
end
+ def test_module_with_nested_class_requiring_lib_class
+ with_autoloading_fixtures do
+ ModuleFolder::NestedWithRequire
+
+ assert defined?(ModuleFolder::LibClass)
+ assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ModuleFolder::LibClass")
+ assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ConstFromLib")
+ end
+ ensure
+ remove_constants(:ModuleFolder)
+ remove_constants(:ConstFromLib)
+ end
+
+ def test_module_with_nested_class_and_parent_requiring_lib_class
+ with_autoloading_fixtures do
+ NestedWithRequireParent
+
+ assert defined?(ModuleFolder::LibClass)
+ assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ModuleFolder::LibClass")
+ assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ConstFromLib")
+ end
+ ensure
+ remove_constants(:ModuleFolder)
+ remove_constants(:ConstFromLib)
+ end
+
def test_directories_may_manifest_as_nested_classes
with_autoloading_fixtures do
assert_kind_of Class, ClassFolder
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
index af67ed21c8..f81e0dc70f 100644
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -57,6 +57,13 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase
assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
end
+ def test_to_options_for_hash_with_indifferent_access
+ assert_instance_of Hash, @symbols.with_indifferent_access.to_options
+ assert_equal @symbols, @symbols.with_indifferent_access.to_options
+ assert_equal @symbols, @strings.with_indifferent_access.to_options
+ assert_equal @symbols, @mixed.with_indifferent_access.to_options
+ end
+
def test_deep_symbolize_keys_for_hash_with_indifferent_access
assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index ebc5df14dd..8062873386 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -157,6 +157,16 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal({ "foo" => "hello" }, JSON.parse(json))
end
+ def test_struct_to_json_with_options_nested
+ klass = Struct.new(:foo, :bar)
+ struct = klass.new "hello", "world"
+ parent_struct = klass.new struct, "world"
+ json = parent_struct.to_json only: [:foo]
+
+ assert_equal({ "foo" => { "foo" => "hello" } }, JSON.parse(json))
+ end
+
+
def test_hash_should_pass_encoding_options_to_children_in_as_json
person = {
name: "John",
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 11c4822748..f87099566b 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -73,9 +73,15 @@ class MultibyteCharsTest < ActiveSupport::TestCase
end
def test_consumes_utf8_strings
- assert @proxy_class.consumes?(UNICODE_STRING)
- assert @proxy_class.consumes?(ASCII_STRING)
- assert_not @proxy_class.consumes?(BYTE_STRING)
+ ActiveSupport::Deprecation.silence do
+ assert @proxy_class.consumes?(UNICODE_STRING)
+ assert @proxy_class.consumes?(ASCII_STRING)
+ assert_not @proxy_class.consumes?(BYTE_STRING)
+ end
+ end
+
+ def test_consumes_is_deprecated
+ assert_deprecated { @proxy_class.consumes?(UNICODE_STRING) }
end
def test_concatenation_should_return_a_proxy_class_instance
@@ -165,7 +171,9 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
assert chars("").upcase.kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").downcase.kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").capitalize.kind_of?(ActiveSupport::Multibyte.proxy_class)
- assert chars("").normalize.kind_of?(ActiveSupport::Multibyte.proxy_class)
+ ActiveSupport::Deprecation.silence do
+ assert chars("").normalize.kind_of?(ActiveSupport::Multibyte.proxy_class)
+ end
assert chars("").decompose.kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").compose.kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars("").tidy_bytes.kind_of?(ActiveSupport::Multibyte.proxy_class)
@@ -383,10 +391,12 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
def test_reverse_should_work_with_normalized_strings
str = "bös"
reversed_str = "söb"
- assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse
- assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse
- assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse
- assert_equal chars(reversed_str).normalize(:kd), chars(str).normalize(:kd).reverse
+ ActiveSupport::Deprecation.silence do
+ assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse
+ assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse
+ assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse
+ assert_equal chars(reversed_str).normalize(:kd), chars(str).normalize(:kd).reverse
+ end
assert_equal chars(reversed_str).decompose, chars(str).decompose.reverse
assert_equal chars(reversed_str).compose, chars(str).compose.reverse
end
@@ -477,7 +487,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
def test_method_works_for_proxyed_methods
assert_equal "ll", "hello".mb_chars.method(:slice).call(2..3) # Defined on Chars
- chars = "hello".mb_chars
+ chars = +"hello".mb_chars
assert_equal "Hello", chars.method(:capitalize!).call # Defined on Chars
assert_equal "Hello", chars
assert_equal "jello", "hello".mb_chars.method(:gsub).call(/h/, "j") # Defined on String
@@ -568,7 +578,9 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
def test_composition_exclusion_is_set_up_properly
# Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly
qa = [0x915, 0x93c].pack("U*")
- assert_equal qa, chars(qa).normalize(:c)
+ ActiveSupport::Deprecation.silence do
+ assert_equal qa, chars(qa).normalize(:c)
+ end
end
# Test for the Public Review Issue #29, bad explanation of composition might lead to a
@@ -578,17 +590,21 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
[0x0B47, 0x0300, 0x0B3E],
[0x1100, 0x0300, 0x1161]
].map { |c| c.pack("U*") }.each do |c|
- assert_equal_codepoints c, chars(c).normalize(:c)
+ ActiveSupport::Deprecation.silence do
+ assert_equal_codepoints c, chars(c).normalize(:c)
+ end
end
end
def test_normalization_shouldnt_strip_null_bytes
null_byte_str = "Test\0test"
- assert_equal null_byte_str, chars(null_byte_str).normalize(:kc)
- assert_equal null_byte_str, chars(null_byte_str).normalize(:c)
- assert_equal null_byte_str, chars(null_byte_str).normalize(:d)
- assert_equal null_byte_str, chars(null_byte_str).normalize(:kd)
+ ActiveSupport::Deprecation.silence do
+ assert_equal null_byte_str, chars(null_byte_str).normalize(:kc)
+ assert_equal null_byte_str, chars(null_byte_str).normalize(:c)
+ assert_equal null_byte_str, chars(null_byte_str).normalize(:d)
+ assert_equal null_byte_str, chars(null_byte_str).normalize(:kd)
+ end
assert_equal null_byte_str, chars(null_byte_str).decompose
assert_equal null_byte_str, chars(null_byte_str).compose
end
@@ -601,11 +617,13 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
323 # COMBINING DOT BELOW
].pack("U*")
- assert_equal_codepoints "", chars("").normalize
- assert_equal_codepoints [44, 105, 106, 328, 323].pack("U*"), chars(comp_str).normalize(:kc).to_s
- assert_equal_codepoints [44, 307, 328, 323].pack("U*"), chars(comp_str).normalize(:c).to_s
- assert_equal_codepoints [44, 307, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:d).to_s
- assert_equal_codepoints [44, 105, 106, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:kd).to_s
+ ActiveSupport::Deprecation.silence do
+ assert_equal_codepoints "", chars("").normalize
+ assert_equal_codepoints [44, 105, 106, 328, 323].pack("U*"), chars(comp_str).normalize(:kc).to_s
+ assert_equal_codepoints [44, 307, 328, 323].pack("U*"), chars(comp_str).normalize(:c).to_s
+ assert_equal_codepoints [44, 307, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:d).to_s
+ assert_equal_codepoints [44, 105, 106, 110, 780, 78, 769].pack("U*"), chars(comp_str).normalize(:kd).to_s
+ end
end
def test_should_compute_grapheme_length
@@ -719,6 +737,41 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
assert_equal BYTE_STRING.dup.mb_chars.class, ActiveSupport::Multibyte::Chars
end
+ def test_unicode_normalize_deprecation
+ # String#unicode_normalize default form is `:nfc`, and
+ # different than Multibyte::Unicode default, `:nkfc`.
+ # Deprecation should suggest the right form if no params
+ # are given and default is used.
+ assert_deprecated(/unicode_normalize\(:nfkc\)/) do
+ ActiveSupport::Multibyte::Unicode.normalize("")
+ end
+
+ assert_deprecated(/unicode_normalize\(:nfd\)/) do
+ ActiveSupport::Multibyte::Unicode.normalize("", :d)
+ end
+ end
+
+ def test_chars_normalize_deprecation
+ # String#unicode_normalize default form is `:nfc`, and
+ # different than Multibyte::Unicode default, `:nkfc`.
+ # Deprecation should suggest the right form if no params
+ # are given and default is used.
+ assert_deprecated(/unicode_normalize\(:nfkc\)/) do
+ "".mb_chars.normalize
+ end
+
+ assert_deprecated(/unicode_normalize\(:nfc\)/) { "".mb_chars.normalize(:c) }
+ assert_deprecated(/unicode_normalize\(:nfd\)/) { "".mb_chars.normalize(:d) }
+ assert_deprecated(/unicode_normalize\(:nfkc\)/) { "".mb_chars.normalize(:kc) }
+ assert_deprecated(/unicode_normalize\(:nfkd\)/) { "".mb_chars.normalize(:kd) }
+ end
+
+ def test_unicode_deprecations
+ assert_deprecated { ActiveSupport::Multibyte::Unicode.downcase("") }
+ assert_deprecated { ActiveSupport::Multibyte::Unicode.upcase("") }
+ assert_deprecated { ActiveSupport::Multibyte::Unicode.swapcase("") }
+ end
+
private
def string_from_classes(classes)
@@ -732,21 +785,3 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
end.pack("U*")
end
end
-
-class MultibyteInternalsTest < ActiveSupport::TestCase
- include MultibyteTestHelpers
-
- test "Chars translates a character offset to a byte offset" do
- example = chars("Puisque c'était son erreur, il m'a aidé")
- [
- [0, 0],
- [3, 3],
- [12, 11],
- [14, 13],
- [41, 39]
- ].each do |byte_offset, character_offset|
- assert_equal character_offset, example.send(:translate_offset, byte_offset),
- "Expected byte offset #{byte_offset} to translate to #{character_offset}"
- end
- end
-end
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index a704505fc6..b19689723f 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -18,64 +18,72 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
end
def test_normalizations_C
- each_line_of_norm_tests do |*cols|
- col1, col2, col3, col4, col5, comment = *cols
+ ActiveSupport::Deprecation.silence do
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
- # CONFORMANCE:
- # 1. The following invariants must be true for all conformant implementations
- #
- # NFC
- # c2 == NFC(c1) == NFC(c2) == NFC(c3)
- assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
- assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
- assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
- #
- # c4 == NFC(c4) == NFC(c5)
- assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
+ # CONFORMANCE:
+ # 1. The following invariants must be true for all conformant implementations
+ #
+ # NFC
+ # c2 == NFC(c1) == NFC(c2) == NFC(c3)
+ assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
+ #
+ # c4 == NFC(c4) == NFC(c5)
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
+ end
end
end
def test_normalizations_D
- each_line_of_norm_tests do |*cols|
- col1, col2, col3, col4, col5, comment = *cols
- #
- # NFD
- # c3 == NFD(c1) == NFD(c2) == NFD(c3)
- assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
- assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
- assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
- # c5 == NFD(c4) == NFD(c5)
- assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
+ ActiveSupport::Deprecation.silence do
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFD
+ # c3 == NFD(c1) == NFD(c2) == NFD(c3)
+ assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
+ # c5 == NFD(c4) == NFD(c5)
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
+ end
end
end
def test_normalizations_KC
- each_line_of_norm_tests do | *cols |
- col1, col2, col3, col4, col5, comment = *cols
- #
- # NFKC
- # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
- assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
+ ActiveSupport::Deprecation.silence do
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKC
+ # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
+ assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
+ end
end
end
def test_normalizations_KD
- each_line_of_norm_tests do | *cols |
- col1, col2, col3, col4, col5, comment = *cols
- #
- # NFKD
- # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
- assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
+ ActiveSupport::Deprecation.silence do
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKD
+ # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
+ assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
+ end
end
end
diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
index 61b171a8d4..97963279af 100644
--- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb
+++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
@@ -17,10 +17,12 @@ class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase
end
def test_breaks
- each_line_of_break_tests do |*cols|
- *clusters, comment = *cols
- packed = ActiveSupport::Multibyte::Unicode.pack_graphemes(clusters)
- assert_equal clusters, ActiveSupport::Multibyte::Unicode.unpack_graphemes(packed), comment
+ ActiveSupport::Deprecation.silence do
+ each_line_of_break_tests do |*cols|
+ *clusters, comment = *cols
+ packed = ActiveSupport::Multibyte::Unicode.pack_graphemes(clusters)
+ assert_equal clusters, ActiveSupport::Multibyte::Unicode.unpack_graphemes(packed), comment
+ end
end
end
diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb
index 3674ea44f3..82edf69294 100644
--- a/activesupport/test/multibyte_normalization_conformance_test.rb
+++ b/activesupport/test/multibyte_normalization_conformance_test.rb
@@ -18,64 +18,72 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
end
def test_normalizations_C
- each_line_of_norm_tests do |*cols|
- col1, col2, col3, col4, col5, comment = *cols
+ ActiveSupport::Deprecation.silence do
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
- # CONFORMANCE:
- # 1. The following invariants must be true for all conformant implementations
- #
- # NFC
- # c2 == NFC(c1) == NFC(c2) == NFC(c3)
- assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
- assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
- assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
- #
- # c4 == NFC(c4) == NFC(c5)
- assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
+ # CONFORMANCE:
+ # 1. The following invariants must be true for all conformant implementations
+ #
+ # NFC
+ # c2 == NFC(c1) == NFC(c2) == NFC(c3)
+ assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
+ #
+ # c4 == NFC(c4) == NFC(c5)
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
+ end
end
end
def test_normalizations_D
- each_line_of_norm_tests do |*cols|
- col1, col2, col3, col4, col5, comment = *cols
- #
- # NFD
- # c3 == NFD(c1) == NFD(c2) == NFD(c3)
- assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
- assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
- assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
- # c5 == NFD(c4) == NFD(c5)
- assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
+ ActiveSupport::Deprecation.silence do
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFD
+ # c3 == NFD(c1) == NFD(c2) == NFD(c3)
+ assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
+ # c5 == NFD(c4) == NFD(c5)
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
+ end
end
end
def test_normalizations_KC
- each_line_of_norm_tests do | *cols |
- col1, col2, col3, col4, col5, comment = *cols
- #
- # NFKC
- # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
- assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
- assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
+ ActiveSupport::Deprecation.silence do
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKC
+ # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
+ assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
+ end
end
end
def test_normalizations_KD
- each_line_of_norm_tests do | *cols |
- col1, col2, col3, col4, col5, comment = *cols
- #
- # NFKD
- # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
- assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
- assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
+ ActiveSupport::Deprecation.silence do
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKD
+ # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
+ assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
+ end
end
end
diff --git a/activesupport/test/parameter_filter_test.rb b/activesupport/test/parameter_filter_test.rb
index 3403a3188b..d2dc71061d 100644
--- a/activesupport/test/parameter_filter_test.rb
+++ b/activesupport/test/parameter_filter_test.rb
@@ -36,6 +36,51 @@ class ParameterFilterTest < ActiveSupport::TestCase
end
end
+ test "filter should return mask option when value is filtered" do
+ mask = Object.new.freeze
+ test_hashes = [
+ [{ "foo" => "bar" }, { "foo" => "bar" }, %w'food'],
+ [{ "foo" => "bar" }, { "foo" => mask }, %w'foo'],
+ [{ "foo" => "bar", "bar" => "foo" }, { "foo" => mask, "bar" => "foo" }, %w'foo baz'],
+ [{ "foo" => "bar", "baz" => "foo" }, { "foo" => mask, "baz" => mask }, %w'foo baz'],
+ [{ "bar" => { "foo" => "bar", "bar" => "foo" } }, { "bar" => { "foo" => mask, "bar" => "foo" } }, %w'fo'],
+ [{ "foo" => { "foo" => "bar", "bar" => "foo" } }, { "foo" => mask }, %w'f banana'],
+ [{ "deep" => { "cc" => { "code" => "bar", "bar" => "foo" }, "ss" => { "code" => "bar" } } }, { "deep" => { "cc" => { "code" => mask, "bar" => "foo" }, "ss" => { "code" => "bar" } } }, %w'deep.cc.code'],
+ [{ "baz" => [{ "foo" => "baz" }, "1"] }, { "baz" => [{ "foo" => mask }, "1"] }, [/foo/]]]
+
+ test_hashes.each do |before_filter, after_filter, filter_words|
+ parameter_filter = ActiveSupport::ParameterFilter.new(filter_words, mask: mask)
+ assert_equal after_filter, parameter_filter.filter(before_filter)
+
+ filter_words << "blah"
+ filter_words << lambda { |key, value|
+ value.reverse! if key =~ /bargain/
+ }
+ filter_words << lambda { |key, value, original_params|
+ value.replace("world!") if original_params["barg"]["blah"] == "bar" && key == "hello"
+ }
+
+ parameter_filter = ActiveSupport::ParameterFilter.new(filter_words, mask: mask)
+ before_filter["barg"] = { :bargain => "gain", "blah" => "bar", "bar" => { "bargain" => { "blah" => "foo", "hello" => "world" } } }
+ after_filter["barg"] = { :bargain => "niag", "blah" => mask, "bar" => { "bargain" => { "blah" => mask, "hello" => "world!" } } }
+
+ assert_equal after_filter, parameter_filter.filter(before_filter)
+ end
+ end
+
+ test "filter_param" do
+ parameter_filter = ActiveSupport::ParameterFilter.new(["foo", /bar/])
+ assert_equal "[FILTERED]", parameter_filter.filter_param("food", "secret vlaue")
+ assert_equal "[FILTERED]", parameter_filter.filter_param("baz.foo", "secret vlaue")
+ assert_equal "[FILTERED]", parameter_filter.filter_param("barbar", "secret vlaue")
+ assert_equal "non secret value", parameter_filter.filter_param("baz", "non secret value")
+ end
+
+ test "filter_param can work with empty filters" do
+ parameter_filter = ActiveSupport::ParameterFilter.new
+ assert_equal "bar", parameter_filter.filter_param("foo", "bar")
+ end
+
test "parameter filter should maintain hash with indifferent access" do
test_hashes = [
[{ "foo" => "bar" }.with_indifferent_access, ["blah"]],
@@ -48,4 +93,13 @@ class ParameterFilterTest < ActiveSupport::TestCase
parameter_filter.filter(before_filter)
end
end
+
+ test "filter_param should return mask option when value is filtered" do
+ mask = Object.new.freeze
+ parameter_filter = ActiveSupport::ParameterFilter.new(["foo", /bar/], mask: mask)
+ assert_equal mask, parameter_filter.filter_param("food", "secret vlaue")
+ assert_equal mask, parameter_filter.filter_param("baz.foo", "secret vlaue")
+ assert_equal mask, parameter_filter.filter_param("barbar", "secret vlaue")
+ assert_equal "non secret value", parameter_filter.filter_param("baz", "non secret value")
+ end
end
diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb
index 7438a0490e..669463bd31 100644
--- a/activesupport/test/testing/method_call_assertions_test.rb
+++ b/activesupport/test/testing/method_call_assertions_test.rb
@@ -60,12 +60,6 @@ class MethodCallAssertionsTest < ActiveSupport::TestCase
assert_match(/dang it.\nExpected increment/, error.message)
end
- def test_assert_called_with
- assert_called_with(@object, :increment) do
- @object.increment
- end
- end
-
def test_assert_called_with_arguments
assert_called_with(@object, :<<, [ 2 ]) do
@object << 2
@@ -88,12 +82,6 @@ class MethodCallAssertionsTest < ActiveSupport::TestCase
end
end
- def test_assert_called_with_returns
- assert_called_with(@object, :increment, returns: 1) do
- @object.increment
- end
- end
-
def test_assert_called_with_multiple_expected_arguments
assert_called_with(@object, :<<, [ [ 1 ], [ 2 ] ]) do
@object << 1
diff --git a/guides/assets/images/rails_guides_logo_1x.png b/guides/assets/images/rails_guides_logo_1x.png
new file mode 100644
index 0000000000..8c6810c312
--- /dev/null
+++ b/guides/assets/images/rails_guides_logo_1x.png
Binary files differ
diff --git a/guides/assets/images/rails_guides_logo_2x.png b/guides/assets/images/rails_guides_logo_2x.png
new file mode 100644
index 0000000000..accc6bbfa4
--- /dev/null
+++ b/guides/assets/images/rails_guides_logo_2x.png
Binary files differ
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index 2657a84a91..bdc3e21977 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -401,14 +401,10 @@ a, a:link, a:visited {
}
#guides {
- width: 27em;
+ width: 37em;
display: block;
background: #980905;
border-radius: 1em;
- -webkit-border-radius: 1em;
- -moz-border-radius: 1em;
- -webkit-box-shadow: 0.25em 0.25em 1em rgba(0,0,0,0.25);
- -moz-box-shadow: rgba(0,0,0,0.25) 0.25em 0.25em 1em;
color: #f1938c;
padding: 1.5em 2em;
position: absolute;
@@ -422,17 +418,44 @@ a, a:link, a:visited {
display: block !important;
}
-#guides dt, #guides dd {
+.guides-section dt, .guides-section dd {
font-weight: normal;
font-size: 0.722em;
margin: 0;
padding: 0;
}
-#guides dt {padding:0; margin: 0.5em 0 0;}
-#guides a {color: #FFF; background: none !important; text-decoration: none;}
-#guides a:hover {text-decoration: underline;}
-#guides .L, #guides .R {float: left; width: 50%; margin: 0; padding: 0;}
-#guides .R {float: right;}
+.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;
@@ -515,13 +538,26 @@ h6 {
#header h1 {
float: left;
- background: url(../images/rails_guides_logo.gif) no-repeat;
+ 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;
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index f339635fb7..6c74200761 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -42,9 +37,6 @@ end
require "minitest/autorun"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
class BugTest < Minitest::Test
include Rack::Test::Methods
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index ffd81c0079..6d53e957d9 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb
index b260f0835b..eb9d1316e9 100644
--- a/guides/bug_report_templates/active_job_gem.rb
+++ b/guides/bug_report_templates/active_job_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -19,9 +14,6 @@ end
require "minitest/autorun"
require "active_job"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
class BuggyJob < ActiveJob::Base
def perform
puts "performed"
diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb
index 894581da96..ae3ef7752f 100644
--- a/guides/bug_report_templates/active_job_master.rb
+++ b/guides/bug_report_templates/active_job_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -18,9 +13,6 @@ end
require "active_job"
require "minitest/autorun"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
class BuggyJob < ActiveJob::Base
def perform
puts "performed"
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index 5f70dbbe69..d88304a219 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -21,9 +16,6 @@ require "active_record"
require "minitest/autorun"
require "logger"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 914f04f51a..780456b7b6 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb
index 7f7359fa78..5dfd49fb38 100644
--- a/guides/bug_report_templates/active_record_migrations_gem.rb
+++ b/guides/bug_report_templates/active_record_migrations_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -21,9 +16,6 @@ require "active_record"
require "minitest/autorun"
require "logger"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb
index 106d94491c..b0fe3bc660 100644
--- a/guides/bug_report_templates/active_record_migrations_master.rb
+++ b/guides/bug_report_templates/active_record_migrations_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -20,9 +15,6 @@ require "active_record"
require "minitest/autorun"
require "logger"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb
index 046572148b..4a8ce787c7 100644
--- a/guides/bug_report_templates/benchmark.rb
+++ b/guides/bug_report_templates/benchmark.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
index aec5bf0577..3fd54437f7 100644
--- a/guides/bug_report_templates/generic_gem.rb
+++ b/guides/bug_report_templates/generic_gem.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
@@ -20,9 +15,6 @@ require "active_support"
require "active_support/core_ext/object/blank"
require "minitest/autorun"
-# Ensure backward compatibility with minitest 4.
-Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
-
class BugTest < Minitest::Test
def test_stuff
assert "zomg".present?
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index 727f428960..ec65fee292 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -1,11 +1,6 @@
# frozen_string_literal: true
-begin
- require "bundler/inline"
-rescue LoadError => e
- $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
- raise e
-end
+require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md
index d26d3d3b95..a5a7eb4b2e 100644
--- a/guides/source/5_1_release_notes.md
+++ b/guides/source/5_1_release_notes.md
@@ -399,7 +399,7 @@ Please refer to the [Changelog][action-view] for detailed changes.
* Change `datetime_field` and `datetime_field_tag` to generate `datetime-local`
fields.
- ([Pull Request](https://github.com/rails/rails/pull/28061))
+ ([Pull Request](https://github.com/rails/rails/pull/25469))
* New Builder-style syntax for HTML tags (`tag.div`, `tag.br`, etc.)
([Pull Request](https://github.com/rails/rails/pull/25543))
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 43bc9306ce..aa746e4731 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -166,7 +166,7 @@ NOTE: Support for parsing XML parameters has been extracted into a gem named `ac
The `params` hash will always contain the `:controller` and `:action` keys, but you should use the methods `controller_name` and `action_name` instead to access these values. Any other parameters defined by the routing, such as `:id`, will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the `:status` parameter in a "pretty" URL:
```ruby
-get '/clients/:status' => 'clients#index', foo: 'bar'
+get '/clients/:status', to: 'clients#index', foo: 'bar'
```
In this case, when a user opens the URL `/clients/active`, `params[:status]` will be set to "active". When this route is used, `params[:foo]` will also be set to "bar", as if it were passed in the query string. Your controller will also receive `params[:action]` as "index" and `params[:controller]` as "clients".
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 041a427f7c..1acb993cad 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -839,13 +839,14 @@ Mailer Testing
You can find detailed instructions on how to test your mailers in the
[testing guide](testing.html#testing-your-mailers).
-Intercepting Emails
+Intercepting and Observing Emails
-------------------
-There are situations where you need to edit an email before it's
-delivered. Fortunately Action Mailer provides hooks to intercept every
-email. You can register an interceptor to make modifications to mail messages
-right before they are handed to the delivery agents.
+Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to register classes that are called during the mail delivery life cycle of every email sent.
+
+### Intercepting Emails
+
+Interceptors allow you to make modifications to emails before they are handed off to the delivery agents. An interceptor class must implement the `:delivering_email(message)` method which will be called before the email is sent.
```ruby
class SandboxEmailInterceptor
@@ -869,3 +870,21 @@ NOTE: The example above uses a custom environment called "staging" for a
production like server but for testing purposes. You can read
[Creating Rails environments](configuring.html#creating-rails-environments)
for more information about custom Rails environments.
+
+### Observing Emails
+
+Observers give you access to the email message after it has been sent. An observer class must implement the `:delivered_email(message)` method, which will be called after the email is sent.
+
+```ruby
+class EmailDeliveryObserver
+ def self.delivered_email(message)
+ EmailDelivery.log(message)
+ end
+end
+```
+Like interceptors, you need to register observers with the Action Mailer framework. You can do this in an initializer file
+`config/initializers/email_delivery_observer.rb`
+
+```ruby
+ActionMailer::Base.register_observer(EmailDeliveryObserver)
+```
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index fad4c19827..b9e24099b1 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -82,9 +82,9 @@ of two or more words, the model class name should follow the Ruby conventions,
using the CamelCase form, while the table name must contain the words separated
by underscores. Examples:
-* Database Table - Plural with underscores separating words (e.g., `book_clubs`).
* Model Class - Singular with the first letter of each word capitalized (e.g.,
`BookClub`).
+* Database Table - Plural with underscores separating words (e.g., `book_clubs`).
| Model / Class | Table / Schema |
| ---------------- | -------------- |
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 9963125fa2..64db141381 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -623,7 +623,7 @@ The block receives the following arguments:
* The name of the event
* Time when it started
* Time when it finished
-* A unique ID for this event
+* A unique ID for the instrumenter that fired the event
* The payload (described in previous sections)
```ruby
@@ -672,7 +672,8 @@ Creating custom events
Adding your own events is easy as well. `ActiveSupport::Notifications` will take care of
all the heavy lifting for you. Simply call `instrument` with a `name`, `payload` and a block.
The notification will be sent after the block returns. `ActiveSupport` will generate the start and end times
-as well as the unique ID. All data passed into the `instrument` call will make it into the payload.
+and add the instrumenter's unique ID. All data passed into the `instrument` call will make
+it into the payload.
Here's an example:
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 66cf9da33b..500e230ff9 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -233,11 +233,6 @@ code for JavaScript plugins and CSS frameworks. Keep in mind that third party
code with references to other files also processed by the asset Pipeline (images,
stylesheets, etc.), will need to be rewritten to use helpers like `asset_path`.
-WARNING: If you are upgrading from Rails 3, please take into account that assets
-under `lib/assets` or `vendor/assets` are available for inclusion via the
-application manifests but no longer part of the precompile array. See
-[Precompiling Assets](#precompiling-assets) for guidance.
-
#### Search Paths
When a file is referenced from a manifest or a helper, Sprockets searches the
@@ -1234,60 +1229,3 @@ it as a preprocessor for your mime type.
Sprockets.register_preprocessor 'text/css', AddComment
```
-Upgrading from Old Versions of Rails
-------------------------------------
-
-There are a few issues when upgrading from Rails 3.0 or Rails 2.x. The first is
-moving the files from `public/` to the new locations. See [Asset
-Organization](#asset-organization) above for guidance on the correct locations
-for different file types.
-
-Next is updating the various environment files with the correct default
-options.
-
-In `application.rb`:
-
-```ruby
-# Version of your assets, change this if you want to expire all your assets
-config.assets.version = '1.0'
-
-# Change the path that assets are served from config.assets.prefix = "/assets"
-```
-
-In `development.rb`:
-
-```ruby
-# Expands the lines which load the assets
-config.assets.debug = true
-```
-
-And in `production.rb`:
-
-```ruby
-# Choose the compressors to use (if any)
-config.assets.js_compressor = :uglifier
-# config.assets.css_compressor = :yui
-
-# Don't fallback to assets pipeline if a precompiled asset is missed
-config.assets.compile = false
-
-# Generate digests for assets URLs.
-config.assets.digest = true
-
-# Precompile additional assets (application.js, application.css, and all
-# non-JS/CSS are already added)
-# config.assets.precompile += %w( admin.js admin.css )
-```
-
-Rails 4 and above no longer set default config values for Sprockets in `test.rb`, so
-`test.rb` now requires Sprockets configuration. The old defaults in the test
-environment are: `config.assets.compile = true`, `config.assets.compress = false`,
-`config.assets.debug = false` and `config.assets.digest = false`.
-
-The following should also be added to your `Gemfile`:
-
-```ruby
-gem 'sass-rails', "~> 3.2.3"
-gem 'coffee-rails', "~> 3.2.1"
-gem 'uglifier'
-```
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index a2231c55d7..f75517c5ab 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -109,7 +109,7 @@ class CreateBooks < ActiveRecord::Migration[5.0]
end
create_table :books do |t|
- t.belongs_to :author, index: true
+ t.belongs_to :author
t.datetime :published_at
t.timestamps
end
@@ -140,7 +140,7 @@ class CreateSuppliers < ActiveRecord::Migration[5.0]
end
create_table :accounts do |t|
- t.belongs_to :supplier, index: true
+ t.belongs_to :supplier
t.string :account_number
t.timestamps
end
@@ -184,7 +184,7 @@ class CreateAuthors < ActiveRecord::Migration[5.0]
end
create_table :books do |t|
- t.belongs_to :author, index: true
+ t.belongs_to :author
t.datetime :published_at
t.timestamps
end
@@ -231,8 +231,8 @@ class CreateAppointments < ActiveRecord::Migration[5.0]
end
create_table :appointments do |t|
- t.belongs_to :physician, index: true
- t.belongs_to :patient, index: true
+ t.belongs_to :physician
+ t.belongs_to :patient
t.datetime :appointment_date
t.timestamps
end
@@ -312,13 +312,13 @@ class CreateAccountHistories < ActiveRecord::Migration[5.0]
end
create_table :accounts do |t|
- t.belongs_to :supplier, index: true
+ t.belongs_to :supplier
t.string :account_number
t.timestamps
end
create_table :account_histories do |t|
- t.belongs_to :account, index: true
+ t.belongs_to :account
t.integer :credit_rating
t.timestamps
end
@@ -358,8 +358,8 @@ class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
end
create_table :assemblies_parts, id: false do |t|
- t.belongs_to :assembly, index: true
- t.belongs_to :part, index: true
+ t.belongs_to :assembly
+ t.belongs_to :part
end
end
end
@@ -487,7 +487,7 @@ class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :name
- t.references :imageable, polymorphic: true, index: true
+ t.references :imageable, polymorphic: true
t.timestamps
end
end
@@ -517,7 +517,7 @@ In your migrations/schema, you will add a references column to the model itself.
class CreateEmployees < ActiveRecord::Migration[5.0]
def change
create_table :employees do |t|
- t.references :manager, index: true
+ t.references :manager
t.timestamps
end
end
@@ -868,7 +868,7 @@ While Rails uses intelligent defaults that will work well in most situations, th
```ruby
class Book < ApplicationRecord
- belongs_to :author, dependent: :destroy,
+ belongs_to :author, touch: :books_updated_at,
counter_cache: true
end
```
@@ -1048,8 +1048,7 @@ There may be times when you wish to customize the query used by `belongs_to`. Su
```ruby
class Book < ApplicationRecord
- belongs_to :author, -> { where active: true },
- dependent: :destroy
+ belongs_to :author, -> { where active: true }
end
```
@@ -1259,7 +1258,7 @@ Controls what happens to the associated object when its owner is destroyed:
* `:destroy` causes the associated object to also be destroyed
* `:delete` causes the associated object to be deleted directly from the database (so callbacks will not execute)
* `:nullify` causes the foreign key to be set to `NULL`. Callbacks are not executed.
-* `:restrict_with_exception` causes an exception to be raised if there is an associated record
+* `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there is an associated record
* `:restrict_with_error` causes an error to be added to the owner if there is an associated object
It's necessary not to set or leave `:nullify` option for those associations
@@ -1660,7 +1659,7 @@ Controls what happens to the associated objects when their owner is destroyed:
* `:destroy` causes all the associated objects to also be destroyed
* `:delete_all` causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
* `:nullify` causes the foreign keys to be set to `NULL`. Callbacks are not executed.
-* `:restrict_with_exception` causes an exception to be raised if there are any associated records
+* `: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
##### `:foreign_key`
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 812565b207..d03943ba4a 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -200,8 +200,6 @@ The full set of methods that can be used in this block are as follows:
* `helper` defines whether or not to generate helpers. Defaults to `true`.
* `integration_tool` defines which integration tool to use to generate integration tests. Defaults to `:test_unit`.
* `system_tests` defines which integration tool to use to generate system tests. Defaults to `:test_unit`.
-* `javascripts` turns on the hook for JavaScript files in generators. Used in Rails for when the `scaffold` generator is run. Defaults to `true`.
-* `javascript_engine` configures the engine to be used (for eg. coffee) when generating assets. Defaults to `:js`.
* `orm` defines which orm to use. Defaults to `false` and will use Active Record by default.
* `resource_controller` defines which generator to use for generating a controller when using `rails generate resource`. Defaults to `:controller`.
* `resource_route` defines whether a resource route definition should be generated
@@ -1013,6 +1011,7 @@ If you choose to use MySQL or MariaDB instead of the shipped SQLite3 database, y
```yaml
development:
adapter: mysql2
+ encoding: utf8mb4
database: blog_development
pool: 5
username: root
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 1beadd78d6..ed47a0de0f 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -596,13 +596,13 @@ $ git rebase -i rails/master
< Choose 'squash' for all of your commits except the first one. >
< Edit the commit message to make sense, and describe all your changes. >
-$ git push fork my_new_branch -f
+$ git push fork my_new_branch --force-with-lease
```
You should be able to refresh the pull request on GitHub and see that it has
been updated.
-#### Updating pull request
+#### Updating a pull request
Sometimes you will be asked to make some changes to the code you have
already committed. This can include amending existing commits. In this
@@ -612,12 +612,13 @@ you can force push to your branch on GitHub as described earlier in
squashing commits section:
```bash
-$ git push fork my_new_branch -f
+$ git push fork my_new_branch --force-with-lease
```
-This will update the branch and pull request on GitHub with your new code. Do
-note that using force push may result in commits being lost on the remote branch; use it with care.
-
+This will update the branch and pull request on GitHub with your new code.
+By force pushing with `--force-with-lease`, git will more safely update
+the remote than with a typical `-f`, which can delete work from the remote
+that you don't already have.
### Older Versions of Ruby on Rails
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 88d205e1ab..3a383cbd4d 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -186,14 +186,17 @@ end
Here's an example of the log generated when this controller action is executed:
```
-Started POST "/articles" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
+Started POST "/articles" for 127.0.0.1 at 2018-10-18 20:09:23 -0400
Processing by ArticlesController#create as HTML
- Parameters: {"utf8"=>"✓", "authenticity_token"=>"xhuIbSBFytHCE1agHgvrlKnSVIOGD6jltW2tO+P6a/ACjQ3igjpV4OdbsZjIhC98QizWH9YdKokrqxBCJrtoqQ==", "article"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, "commit"=>"Create Article"}
-New article: {"id"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>false, "created_at"=>nil, "updated_at"=>nil}
+ Parameters: {"utf8"=>"✓", "authenticity_token"=>"XLveDrKzF1SwaiNRPTaMtkrsTzedtebPPkmxEFIU0ordLjICSnXsSNfrdMa4ccyBjuGwnnEiQhEoMN6H1Gtz3A==", "article"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs.", "published"=>"0"}, "commit"=>"Create Article"}
+New article: {"id"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs.", "published"=>false, "created_at"=>nil, "updated_at"=>nil}
Article should be valid: true
- (0.1ms) BEGIN
- SQL (0.4ms) INSERT INTO "articles" ("title", "body", "published", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["title", "Debugging Rails"], ["body", "I'm learning how to print in logs!!!"], ["published", "f"], ["created_at", "2017-08-20 11:53:10.010435"], ["updated_at", "2017-08-20 11:53:10.010435"]]
- (0.3ms) COMMIT
+ (0.0ms) begin transaction
+ ↳ app/controllers/articles_controller.rb:31
+ Article Create (0.5ms) INSERT INTO "articles" ("title", "body", "published", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["title", "Debugging Rails"], ["body", "I'm learning how to print in logs."], ["published", 0], ["created_at", "2018-10-19 00:09:23.216549"], ["updated_at", "2018-10-19 00:09:23.216549"]]
+ ↳ app/controllers/articles_controller.rb:31
+ (2.3ms) commit transaction
+ ↳ app/controllers/articles_controller.rb:31
The article was saved and now the user is going to be redirected...
Redirected to http://localhost:3000/articles/1
Completed 302 Found in 4ms (ActiveRecord: 0.8ms)
@@ -201,6 +204,40 @@ Completed 302 Found in 4ms (ActiveRecord: 0.8ms)
Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels to avoid filling your production logs with useless trivia.
+### Verbose Query Logs
+
+When looking at database query output in logs, it may not be immediately clear why multiple database queries are triggered when a single method is called:
+
+```
+irb(main):001:0> Article.pamplemousse
+ Article Load (0.4ms) SELECT "articles".* FROM "articles"
+ Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 1]]
+ Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 2]]
+ Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 3]]
+=> #<Comment id: 2, author: "1", body: "Well, actually...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">
+```
+
+After running `ActiveRecord::Base.verbose_query_logs = true` in the `rails console` session to enable verbose query logs and running the method again, it becomes obvious what single line of code is generating all these discrete database calls:
+
+```
+irb(main):003:0> Article.pamplemousse
+ Article Load (0.2ms) SELECT "articles".* FROM "articles"
+ ↳ app/models/article.rb:5
+ Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 1]]
+ ↳ app/models/article.rb:6
+ Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 2]]
+ ↳ app/models/article.rb:6
+ Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 3]]
+ ↳ app/models/article.rb:6
+=> #<Comment id: 2, author: "1", body: "Well, actually...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">
+```
+
+Below each database statement you can see arrows pointing to the specific source filename (and line number) of the method that resulted in a database call. This can help you identify and address performance problems caused by N+1 queries: single database queries that generates multiple additional queries.
+
+Verbose query logs are enabled by default in the development environment logs after Rails 5.2.
+
+WARNING: We recommend against using this setting in production environments. It relies on Ruby's `Kernel#caller` method which tends to allocate a lot of memory in order to generate stacktraces of method calls.
+
### Tagged Logging
When running multi-user, multi-account applications, it's often useful
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 8f2312458d..25c159d471 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -173,7 +173,7 @@
description: This guide describes the considerations needed and tools available when working directly with concurrency in a Rails application.
work_in_progress: true
-
- name: Contributing to Ruby on Rails
+ name: Contributions
documents:
-
name: Contributing to Ruby on Rails
@@ -184,14 +184,14 @@
url: api_documentation_guidelines.html
description: This guide documents the Ruby on Rails API documentation guidelines.
-
- name: Ruby on Rails Guides Guidelines
+ name: Guides Guidelines
url: ruby_on_rails_guides_guidelines.html
description: This guide documents the Ruby on Rails guides guidelines.
-
- name: Maintenance Policy
+ name: Policies
documents:
-
- name: Maintenance Policy for Ruby on Rails
+ name: Maintenance Policy
url: maintenance_policy.html
description: What versions of Ruby on Rails are currently supported, and when to expect new versions.
-
@@ -202,51 +202,51 @@
url: upgrading_ruby_on_rails.html
description: This guide helps in upgrading applications to latest Ruby on Rails versions.
-
- name: Ruby on Rails 6.0 Release Notes
+ name: 6.0 Release Notes
work_in_progress: true
url: 6_0_release_notes.html
description: Release notes for Rails 6.0.
-
- name: Ruby on Rails 5.2 Release Notes
+ name: Version 5.2 - April 2018
url: 5_2_release_notes.html
description: Release notes for Rails 5.2.
-
- name: Ruby on Rails 5.1 Release Notes
+ name: Version 5.1 - April 2017
url: 5_1_release_notes.html
description: Release notes for Rails 5.1.
-
- name: Ruby on Rails 5.0 Release Notes
+ name: Version 5.0 - June 2016
url: 5_0_release_notes.html
description: Release notes for Rails 5.0.
-
- name: Ruby on Rails 4.2 Release Notes
+ name: Version 4.2 - December 2014
url: 4_2_release_notes.html
description: Release notes for Rails 4.2.
-
- name: Ruby on Rails 4.1 Release Notes
+ name: Version 4.1 - April 2014
url: 4_1_release_notes.html
description: Release notes for Rails 4.1.
-
- name: Ruby on Rails 4.0 Release Notes
+ name: Version 4.0 - June 2013
url: 4_0_release_notes.html
description: Release notes for Rails 4.0.
-
- name: Ruby on Rails 3.2 Release Notes
+ name: Version 3.2 - January 2012
url: 3_2_release_notes.html
description: Release notes for Rails 3.2.
-
- name: Ruby on Rails 3.1 Release Notes
+ name: Version 3.1 - August 2011
url: 3_1_release_notes.html
description: Release notes for Rails 3.1.
-
- name: Ruby on Rails 3.0 Release Notes
+ name: Version 3.0 - August 2010
url: 3_0_release_notes.html
description: Release notes for Rails 3.0.
-
- name: Ruby on Rails 2.3 Release Notes
+ name: Version 2.3 - March 2009
url: 2_3_release_notes.html
description: Release notes for Rails 2.3.
-
- name: Ruby on Rails 2.2 Release Notes
+ name: Version 2.2 - November 2008
url: 2_2_release_notes.html
description: Release notes for Rails 2.2.
diff --git a/guides/source/generators.md b/guides/source/generators.md
index f028d14998..88ce4be8da 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -219,7 +219,7 @@ If we want to avoid generating the default `app/assets/stylesheets/scaffolds.scs
end
```
-The next customization on the workflow will be to stop generating stylesheet, JavaScript, and test fixture files for scaffolds altogether. We can achieve that by changing our configuration to the following:
+The next customization on the workflow will be to stop generating stylesheet and test fixture files for scaffolds altogether. We can achieve that by changing our configuration to the following:
```ruby
config.generators do |g|
@@ -227,7 +227,6 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :test_unit, fixture: false
g.stylesheets false
- g.javascripts false
end
```
@@ -285,7 +284,6 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :test_unit, fixture: false
g.stylesheets false
- g.javascripts false
g.helper :my_helper
end
```
@@ -350,7 +348,6 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :test_unit, fixture: false
g.stylesheets false
- g.javascripts false
end
```
@@ -385,7 +382,6 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :shoulda, fixture: false
g.stylesheets false
- g.javascripts false
# Add a fallback!
g.fallbacks[:shoulda] = :test_unit
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 78e5f27448..10b1a6de7e 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -116,7 +116,7 @@ NOTE: The backend lazy-loads these translations when a translation is looked up
You can change the default locale as well as configure the translations load paths in `config/application.rb` as follows:
```ruby
- config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
+ config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')]
config.i18n.default_locale = :de
```
@@ -135,19 +135,22 @@ I18n.available_locales = [:en, :pt]
I18n.default_locale = :pt
```
+Note that appending directly to `I18n.load_paths` instead of to the application's configured i18n will _not_ override translations from external gems.
+
### Managing the Locale across Requests
The default locale is used for all translations unless `I18n.locale` is explicitly set.
A localized application will likely need to provide support for multiple locales. To accomplish this, the locale should be set at the beginning of each request so that all strings are translated using the desired locale during the lifetime of that request.
-The locale can be set in a `before_action` in the `ApplicationController`:
+The locale can be set in an `around_action` in the `ApplicationController`:
```ruby
-before_action :set_locale
+around_action :switch_locale
-def set_locale
- I18n.locale = params[:locale] || I18n.default_locale
+def switch_locale(&action)
+ locale = params[:locale] || I18n.default_locale
+ I18n.with_locale(locale, &action)
end
```
@@ -167,10 +170,11 @@ One option you have is to set the locale from the domain name where your applica
You can implement it like this in your `ApplicationController`:
```ruby
-before_action :set_locale
+around_action :switch_locale
-def set_locale
- I18n.locale = extract_locale_from_tld || I18n.default_locale
+def switch_locale(&action)
+ locale = extract_locale_from_tld || I18n.default_locale
+ I18n.with_locale(locale, &action)
end
# Get locale from top-level domain or return +nil+ if such locale is not available
@@ -210,7 +214,7 @@ This solution has aforementioned advantages, however, you may not be able or may
#### Setting the Locale from URL Params
-The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case.
+The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.with_locale(params[:locale], &action)` _around_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case.
This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though.
@@ -273,8 +277,11 @@ NOTE: Have a look at various gems which simplify working with routes: [routing_f
An application with authenticated users may allow users to set a locale preference through the application's interface. With this approach, a user's selected locale preference is persisted in the database and used to set the locale for authenticated requests by that user.
```ruby
-def set_locale
- I18n.locale = current_user.try(:locale) || I18n.default_locale
+around_action :switch_locale
+
+def switch_locale(&action)
+ locale = current_user.try(:locale) || I18n.default_locale
+ I18n.with_locale(locale, &action)
end
```
@@ -289,10 +296,11 @@ The `Accept-Language` HTTP header indicates the preferred language for request's
A trivial implementation of using an `Accept-Language` header would be:
```ruby
-def set_locale
+def switch_locale(&action)
logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
- I18n.locale = extract_locale_from_accept_language_header
+ locale = extract_locale_from_accept_language_header
logger.debug "* Locale set to '#{I18n.locale}'"
+ I18n.with_locale(locale, &action)
end
private
@@ -333,10 +341,12 @@ end
```ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
- before_action :set_locale
- def set_locale
- I18n.locale = params[:locale] || I18n.default_locale
+ around_action :switch_locale
+
+ def switch_locale(&action)
+ locale = params[:locale] || I18n.default_locale
+ I18n.with_locale(locale, &action)
end
end
```
@@ -662,6 +672,26 @@ I18n.t 'activerecord.errors.messages'
# => {:inclusion=>"is not included in the list", :exclusion=> ... }
```
+If you want to perform interpolation on a bulk hash of translations, you need to pass `deep_interpolation: true` as a parameter. When you have the following dictionary:
+
+```yaml
+en:
+ welcome:
+ title: "Welcome!"
+ content: "Welcome to the %{app_name}"
+```
+
+then the nested interpolation will be ignored without the setting:
+
+```ruby
+I18n.t 'welcome', app_name: 'book store'
+# => {:title=>"Welcome!", :content=>"Welcome to the %{app_name}"}
+
+I18n.t 'welcome', deep_interpolation: true, app_name: 'book store'
+# => {:title=>"Welcome!", :content=>"Welcome to the book store"}
+```
+
+
#### "Lazy" Lookup
Rails implements a convenient way to look up the locale inside _views_. When you have the following dictionary:
@@ -1103,7 +1133,7 @@ For several reasons the Simple backend shipped with Active Support only does the
That does not mean you're stuck with these limitations, though. The Ruby I18n gem makes it very easy to exchange the Simple backend implementation with something else that fits better for your needs, by passing a backend instance to the `I18n.backend=` setter.
-For example, you can replace the Simple backend with the the Chain backend to chain multiple backends together. This is useful when you want to use standard translations with a Simple backend but store custom application translations in a database or other backends.
+For example, you can replace the Simple backend with the Chain backend to chain multiple backends together. This is useful when you want to use standard translations with a Simple backend but store custom application translations in a database or other backends.
With the Chain backend, you could use the Active Record backend and fall back to the (default) Simple backend:
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index dd9175e312..1f42d72756 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -45,16 +45,16 @@
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">Guides Index</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
- <% ['L', 'R'].each do |position| %>
- <dl class="<%= position %>">
- <% docs_for_menu(position).each do |section| %>
- <dt><%= section['name'] %></dt>
- <% finished_documents(section['documents']).each do |document| %>
- <dd><a href="<%= document['url'] %>"><%= document['name'] %></a></dd>
- <% end %>
+ <div class="guides-section-container">
+ <% documents_by_section.each do |section| %>
+ <div class="guides-section">
+ <dt><%= section['name'] %></dt>
+ <% finished_documents(section['documents']).each do |document| %>
+ <dd><a href="<%= document['url'] %>"><%= document['name'] %></a></dd>
+ <% end %>
+ </div>
<% end %>
- </dl>
- <% end %>
+ </div>
</div>
</li>
<li><a class="nav-item" href="contributing_to_ruby_on_rails.html">Contribute</a></li>
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 00da65b784..ad08e5a5a9 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -1266,7 +1266,7 @@ You can also pass in arbitrary local variables to any partial you are rendering
In this case, the partial will have access to a local variable `title` with the value "Products Page".
-TIP: Rails also makes a counter variable available within a partial called by the collection, named after the title of the partial followed by `_counter`. For example, when rendering a collection `@products` the partial `_product.html.erb` can access the variable `product_counter` which indexes the number of times it has been rendered within the enclosing view.
+TIP: Rails also makes a counter variable available within a partial called by the collection, named after the title of the partial followed by `_counter`. For example, when rendering a collection `@products` the partial `_product.html.erb` can access the variable `product_counter` which indexes the number of times it has been rendered within the enclosing view. Note that it also applies for when the partial name was changed by using the `as:` option. For example, the counter variable for the code above would be `item_counter`.
You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option:
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 9de2229672..8c21ccfba6 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1562,7 +1562,7 @@ class UserMailerTest < ActionMailer::TestCase
end
```
-In the test we send the email and store the returned object in the `email`
+In the test we create the email and store the returned object in the `email`
variable. We then ensure that it was sent (the first assert), then, in the
second batch of assertions, we ensure that the email does indeed contain what we
expect. The helper `read_fixture` is used to read in the content from this file.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 925dac7b3c..f94b67a0ac 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,14 @@
+* Use Ids instead of memory addresses when displaying references in scaffold views.
+
+ Fixes #29200.
+
+ *Rasesh Patel*
+
+* Adds support for multiple databases to `rails db:migrate:status`.
+ Subtasks are also added to get the status of individual databases (eg. `rails db:migrate:status:animals`).
+
+ *Gannon McGibbon*
+
* Use Webpacker by default to manage app-level JavaScript through the new app/javascript directory.
Sprockets is now solely in charge, by default, of compiling CSS and other static assets.
Action Cable channel generators will create ES6 stubs rather than use CoffeeScript.
@@ -6,34 +17,18 @@
*DHH*, *Lachlan Sylvester*
-* Refactors `migrations_paths` command option in generators
- to `database` (aliased as `db`). Now, the migrations paths
- will be read from the specified database configuration in the
- current environment.
-
- ```
- bin/rails g model Chair brand:string --database=kingston
- invoke active_record
- create db/kingston_migrate/20180830151055_create_chairs.rb
- ```
-
- `--database` can be used with the migration, model, and scaffold generators.
-
- *Gannon McGibbon*
-
-* Adds an option to the model generator to allow setting the
- migrations paths for that migration. This is useful for
- applications that use multiple databases and put migrations
- per database in their own directories.
+* Add `database` (aliased as `db`) option to model generator to allow
+ setting the database. This is useful for applications that use
+ multiple databases and put migrations per database in their own directories.
```
- bin/rails g model Room capacity:integer --migrations-paths=db/kingston_migrate
+ bin/rails g model Room capacity:integer --database=kingston
invoke active_record
create db/kingston_migrate/20180830151055_create_rooms.rb
```
Because rails scaffolding uses the model generator, you can
- also specify migrations paths with the scaffold generator.
+ also specify a database with the scaffold generator.
*Gannon McGibbon*
@@ -61,15 +56,15 @@
*Yoshiyuki Kinjo*
-* Add `--migrations_paths` option to migration generator.
+* Add `database` (aliased as `db`) option to migration generator.
If you're using multiple databases and have a folder for each database
for migrations (ex db/migrate and db/new_db_migrate) you can now pass the
- `--migrations_paths` option to the generator to make sure the the migration
+ `--database` option to the generator to make sure the the migration
is inserted into the correct folder.
```
- rails g migration CreateHouses --migrations_paths=db/kingston_migrate
+ rails g migration CreateHouses --database=kingston
invoke active_record
create db/kingston_migrate/20180830151055_create_houses.rb
```
diff --git a/railties/lib/rails/app_updater.rb b/railties/lib/rails/app_updater.rb
index a243968a39..19d136e041 100644
--- a/railties/lib/rails/app_updater.rb
+++ b/railties/lib/rails/app_updater.rb
@@ -21,7 +21,7 @@ module Rails
private
def generator_options
options = { api: !!Rails.application.config.api_only, update: true }
- options[:skip_yarn] = !File.exist?(Rails.root.join("bin", "yarn"))
+ options[:skip_javascript] = !File.exist?(Rails.root.join("bin", "yarn"))
options[:skip_active_record] = !defined?(ActiveRecord::Railtie)
options[:skip_active_storage] = !defined?(ActiveStorage::Engine) || !defined?(ActiveRecord::Railtie)
options[:skip_action_mailer] = !defined?(ActionMailer::Railtie)
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index eae902a938..d6f8c4f47c 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -202,7 +202,7 @@ module Rails
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
"Error: #{e.message}"
rescue => e
- raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace
+ raise e, "Cannot load database configuration:\n#{e.message}", e.backtrace
end
def colorize_logging
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index cd8f16e247..5e8cebc50a 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -54,8 +54,6 @@ module Rails
force_plural: false,
helper: true,
integration_tool: nil,
- javascripts: true,
- javascript_engine: :js,
orm: false,
resource_controller: :controller,
resource_route: true,
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 410e53b4c0..4dc4d27a46 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -31,9 +31,6 @@ module Rails
class_option :database, type: :string, aliases: "-d", default: "sqlite3",
desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :skip_yarn, type: :boolean, default: false,
- desc: "Don't use Yarn for managing JavaScript dependencies"
-
class_option :skip_gemfile, type: :boolean, default: false,
desc: "Don't create a Gemfile"
@@ -399,6 +396,10 @@ module Rails
!options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin")
end
+ def webpack_install?
+ !(options[:skip_javascript] || options[:skip_webpack_install])
+ end
+
def depends_on_system_test?
!(options[:skip_system_test] || options[:skip_test] || options[:api])
end
@@ -420,7 +421,7 @@ module Rails
end
def run_webpack
- unless options[:skip_javascript]
+ if webpack_install?
rails_command "webpacker:install"
rails_command "webpacker:install:#{options[:webpack]}" if options[:webpack] && options[:webpack] != "webpack"
end
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt
index e1ede7c713..2cf4e5c9d0 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt
@@ -16,7 +16,7 @@
<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
<tr>
<% attributes.reject(&:password_digest?).each do |attribute| -%>
- <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
+ <td><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></td>
<% end -%>
<td><%%= link_to 'Show', <%= model_resource_name %> %></td>
<td><%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_name %>) %></td>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
index 5e634153be..7deba07926 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
@@ -3,7 +3,7 @@
<% attributes.reject(&:password_digest?).each do |attribute| -%>
<p>
<strong><%= attribute.human_name %>:</strong>
- <%%= @<%= singular_table_name %>.<%= attribute.name %> %>
+ <%%= @<%= singular_table_name %>.<%= attribute.column_name %> %>
</p>
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index cefaffcb20..f56f79b8d4 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -95,7 +95,7 @@ module Rails
def bin_when_updating
bin
- if options[:skip_yarn]
+ if options[:skip_javascript]
remove_file "bin/yarn"
end
end
@@ -261,6 +261,9 @@ module Rails
class_option :webpack, type: :string, 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"
+
def initialize(*args)
super
@@ -271,7 +274,7 @@ module Rails
# Force sprockets and yarn to be skipped when generating API only apps.
# Can't modify options hash as it's frozen by default.
if options[:api]
- self.options = options.merge(skip_sprockets: true, skip_javascript: true, skip_yarn: true).freeze
+ self.options = options.merge(skip_sprockets: true, skip_javascript: true).freeze
end
end
@@ -286,7 +289,7 @@ module Rails
build(:gitignore) unless options[:skip_git]
build(:gemfile) unless options[:skip_gemfile]
build(:version_control)
- build(:package_json) unless options[:skip_yarn]
+ build(:package_json) unless options[:skip_javascript]
end
def create_app_files
@@ -435,7 +438,6 @@ module Rails
def delete_action_cable_files_skipping_action_cable
if options[:skip_action_cable]
- remove_file "app/javascript/channels/consumer.js"
remove_dir "app/javascript/channels"
remove_dir "app/channels"
end
@@ -460,8 +462,8 @@ module Rails
end
end
- def delete_bin_yarn_if_skip_yarn_option
- remove_file "bin/yarn" if options[:skip_yarn]
+ def delete_bin_yarn
+ remove_file "bin/yarn" if options[:skip_javascript]
end
def finish_template
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
index 1567333023..fb264935bd 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
@@ -45,6 +45,7 @@ group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end
+<% end -%>
group :development do
<%- unless options.api? -%>
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
@@ -75,7 +76,6 @@ group :test do
gem 'chromedriver-helper'
end
<%- end -%>
-<% end -%>
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
diff --git a/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/index.js b/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/index.js
index 5da1ce2dce..0cfcf74919 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/index.js
+++ b/railties/lib/rails/generators/rails/app/templates/app/javascript/channels/index.js
@@ -1,5 +1,5 @@
-// Load all the channels within this directory and all subdirectories.
+// Load all the channels within this directory and all subdirectories.
// Channel files must be named *_channel.js.
-const channels = require.context('.', true, /\_channel\.js$/)
+const channels = require.context('.', true, /_channel\.js$/)
channels.keys().forEach(channels)
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 955a424878..a18e03e7db 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
@@ -14,9 +14,9 @@ FileUtils.chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
-<% unless options.skip_yarn? -%>
+<% unless options.skip_javascript? -%>
- # Install JavaScript dependencies if using Yarn
+ # Install JavaScript dependencies
# system('bin/yarn')
<% end -%>
<% unless options.skip_active_record? -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
index ed17959e1d..03b77d0d46 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
@@ -14,9 +14,9 @@ FileUtils.chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
-<% unless options.skip_yarn? -%>
+<% unless options.skip_javascript? -%>
- # Install JavaScript dependencies if using Yarn
+ # Install JavaScript dependencies
# system('bin/yarn')
<% end -%>
<% unless options.skip_active_record? -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
index 5860563908..b6c2e7448a 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
@@ -11,6 +11,7 @@
#
default: &default
adapter: mysql2
+ encoding: utf8mb4
pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password:
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
index 51196ae743..fe48fc34ee 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
@@ -5,10 +5,6 @@ Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
-<%- unless options[:skip_yarn] -%>
-# Add Yarn node_modules folder to the asset load path.
-Rails.application.config.assets.paths << Rails.root.join('node_modules')
-<%- end -%>
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore.tt b/railties/lib/rails/generators/rails/app/templates/gitignore.tt
index 4e114fb1d9..860baa1595 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore.tt
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore.tt
@@ -22,19 +22,14 @@
<% end -%>
<% unless skip_active_storage? -%>
-# Ignore uploaded files in development
+# Ignore uploaded files in development.
/storage/*
<% if keeps? -%>
!/storage/.keep
<% end -%>
<% end -%>
-
-<% unless options.skip_yarn? -%>
-/node_modules
-/yarn-error.log
-
-<% end -%>
<% unless options.api? -%>
+
/public/assets
<% end -%>
.byebug_history
diff --git a/railties/lib/rails/generators/rails/app/templates/package.json.tt b/railties/lib/rails/generators/rails/app/templates/package.json.tt
index a654cba39c..7174116989 100644
--- a/railties/lib/rails/generators/rails/app/templates/package.json.tt
+++ b/railties/lib/rails/generators/rails/app/templates/package.json.tt
@@ -5,7 +5,7 @@
"rails-ujs": ">=5.2.1"<% unless options[:skip_turbolinks] %>,
"turbolinks": "5.1.1"<% end -%><% unless skip_active_storage? %>,
"activestorage": ">=5.2.1"<% end -%><% unless options[:skip_action_cable] %>,
- "actioncable": ">=5.2.1"<% end -%>
+ "actioncable": ">=5.2.1"<% end %>
},
"version": "0.1.0"
}
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 9ec0ccbe7a..239b3a5739 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -88,7 +88,7 @@ task default: :test
PASSTHROUGH_OPTIONS = [
:skip_active_record, :skip_active_storage, :skip_action_mailer, :skip_javascript, :skip_action_cable, :skip_sprockets, :database,
- :javascript, :skip_yarn, :api, :quiet, :pretend, :skip
+ :api, :quiet, :pretend, :skip
]
def generate_test_dummy(force = false)
@@ -98,6 +98,7 @@ task default: :test
opts[:skip_listen] = true
opts[:skip_git] = true
opts[:skip_turbolinks] = true
+ opts[:skip_webpack_install] = true
opts[:dummy_app] = true
invoke Rails::Generators::AppGenerator,
diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
index 7a68da5c4b..0aabf09252 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
@@ -7,7 +7,7 @@ pkg/
<%= dummy_path %>/db/*.sqlite3-journal
<% end -%>
<%= dummy_path %>/log/*.log
-<% unless options[:skip_yarn] -%>
+<% unless options[:skip_javascript] -%>
<%= dummy_path %>/node_modules/
<%= dummy_path %>/yarn-error.log
<% end -%>
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
index 238ffdd677..ec29ad12ba 100644
--- a/railties/lib/rails/generators/testing/behaviour.rb
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -68,7 +68,7 @@ module Rails
capture(:stdout) do
args += ["--skip-bundle"] unless args.include? "--dev"
args |= ["--skip-bootsnap"] unless args.include? "--no-skip-bootsnap"
- args |= ["--skip-javascript"] unless args.include? "--no-skip-javascript"
+ args |= ["--skip-webpack-install"] unless args.include? "--no-skip-webpack-install"
generator_class.start(args, config.reverse_merge(destination_root: destination_root))
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 9b01d42b1e..fa418f564b 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -2109,7 +2109,7 @@ module ApplicationTests
RUBY
app "development"
assert_equal [ :password, :credit_card_number ], Rails.application.config.filter_parameters
- assert_equal [ "password", "credit_card_number" ].to_set, ActiveRecord::Base.filter_attributes
+ assert_equal [ :password, :credit_card_number ], ActiveRecord::Base.filter_attributes
end
test "ActiveStorage.routes_prefix can be configured via config.active_storage.routes_prefix" do
diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb
index bc6708c89e..6478e06250 100644
--- a/railties/test/application/rake/multi_dbs_test.rb
+++ b/railties/test/application/rake/multi_dbs_test.rb
@@ -55,11 +55,19 @@ module ApplicationTests
end
end
- def db_migrate_and_schema_dump_and_load(namespace, expected_database, format)
+ def db_migrate_and_migrate_status
Dir.chdir(app_path) do
- rails "generate", "model", "book", "title:string"
- rails "generate", "model", "dog", "name:string"
- write_models_for_animals
+ generate_models_for_animals
+ rails "db:migrate"
+ output = rails "db:migrate:status"
+ assert_match(/up \d+ Create books/, output)
+ assert_match(/up \d+ Create dogs/, output)
+ end
+ end
+
+ def db_migrate_and_schema_dump_and_load(format)
+ Dir.chdir(app_path) do
+ generate_models_for_animals
rails "db:migrate", "db:#{format}:dump"
if format == "schema"
@@ -86,9 +94,7 @@ module ApplicationTests
def db_migrate_namespaced(namespace, expected_database)
Dir.chdir(app_path) do
- rails "generate", "model", "book", "title:string"
- rails "generate", "model", "dog", "name:string"
- write_models_for_animals
+ generate_models_for_animals
output = rails("db:migrate:#{namespace}")
if namespace == "primary"
assert_match(/CreateBooks: migrated/, output)
@@ -98,6 +104,18 @@ module ApplicationTests
end
end
+ def db_migrate_status_namespaced(namespace, expected_database)
+ Dir.chdir(app_path) do
+ generate_models_for_animals
+ output = rails("db:migrate:status:#{namespace}")
+ if namespace == "primary"
+ assert_match(/up \d+ Create books/, output)
+ else
+ assert_match(/up \d+ Create dogs/, output)
+ end
+ end
+ end
+
def write_models_for_animals
# make a directory for the animals migration
FileUtils.mkdir_p("#{app_path}/db/animals_migrate")
@@ -117,17 +135,22 @@ module ApplicationTests
# create the base model for dog to inherit from
File.open("#{app_path}/app/models/animals_base.rb", "w") do |file|
- file.write(<<-EOS
-class AnimalsBase < ActiveRecord::Base
- self.abstract_class = true
+ file.write(<<~EOS)
+ class AnimalsBase < ActiveRecord::Base
+ self.abstract_class = true
- establish_connection :animals
-end
-EOS
-)
+ establish_connection :animals
+ end
+ EOS
end
end
+ def generate_models_for_animals
+ rails "generate", "model", "book", "title:string"
+ rails "generate", "model", "dog", "name:string"
+ write_models_for_animals
+ end
+
test "db:create and db:drop works on all databases for env" do
require "#{app_path}/config/environment"
ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
@@ -144,22 +167,31 @@ EOS
test "db:migrate and db:schema:dump and db:schema:load works on all databases" do
require "#{app_path}/config/environment"
- ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config|
- db_migrate_and_schema_dump_and_load db_config.spec_name, db_config.config["database"], "schema"
- end
+ db_migrate_and_schema_dump_and_load "schema"
end
test "db:migrate and db:structure:dump and db:structure:load works on all databases" do
require "#{app_path}/config/environment"
+ db_migrate_and_schema_dump_and_load "structure"
+ end
+
+ 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_and_schema_dump_and_load db_config.spec_name, db_config.config["database"], "structure"
+ db_migrate_namespaced db_config.spec_name, db_config.config["database"]
end
end
- test "db:migrate:namespace works" do
+ test "db:migrate:status works on all databases" do
+ require "#{app_path}/config/environment"
+ db_migrate_and_migrate_status
+ end
+
+ 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"]
end
end
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index cd6bdd11c0..bb3aaa9d14 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -15,6 +15,8 @@ DEFAULT_APP_FILES = %w(
app/assets/images
app/javascript
app/javascript/channels
+ app/javascript/channels/consumer.js
+ app/javascript/channels/index.js
app/javascript/packs/application.js
app/assets/stylesheets
app/assets/stylesheets/application.css
@@ -104,7 +106,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_skip_bundle
- assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do
+ assert_not_called(generator([destination_root], skip_bundle: true, skip_webpack_install: true), :bundle_command) do
quietly { generator.invoke_all }
# skip_bundle is only about running bundle install, ensure the Gemfile is still
# generated.
@@ -113,7 +115,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_assets
- run_generator [destination_root, "--no-skip-javascript"]
+ run_generator
assert_file("app/views/layouts/application.html.erb", /stylesheet_link_tag\s+'application', media: 'all', 'data-turbolinks-track': 'reload'/)
assert_file("app/views/layouts/application.html.erb", /javascript_pack_tag\s+'application', 'data-turbolinks-track': 'reload'/)
@@ -209,6 +211,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_new_application_doesnt_need_defaults
+ run_generator
assert_no_file "config/initializers/new_framework_defaults_6_0.rb"
end
@@ -297,10 +300,10 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_app_update_does_not_generate_yarn_contents_when_bin_yarn_is_not_used
app_root = File.join(destination_root, "myapp")
- run_generator [app_root, "--skip-yarn"]
+ run_generator [app_root, "--skip-javascript"]
stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], { update: true, skip_yarn: true }, { destination_root: app_root, shell: @shell }
+ generator = Rails::Generators::AppGenerator.new ["rails"], { update: true, skip_javascript: true }, { destination_root: app_root, shell: @shell }
generator.send(:app_const)
quietly { generator.send(:update_bin_files) }
@@ -607,7 +610,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "app/views/layouts/application.html.erb" do |contents|
assert_match(/stylesheet_link_tag\s+'application', media: 'all' %>/, contents)
- assert_no_match(/javascript_include_tag\s+'application' \%>/, contents)
+ assert_no_match(/javascript_pack_tag\s+'application'/, contents)
end
end
@@ -709,7 +712,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_web_console_with_dev_option
- run_generator [destination_root, "--dev"]
+ run_generator [destination_root, "--dev", "--skip-bundle"]
assert_file "Gemfile" do |content|
assert_match(/gem 'web-console',\s+github: 'rails\/web-console'/, content)
@@ -727,13 +730,13 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_generation_runs_bundle_install
- generator([destination_root], {})
+ generator([destination_root], skip_webpack_install: true)
assert_bundler_command_called("install")
end
def test_dev_option
- generator([destination_root], dev: true)
+ generator([destination_root], dev: true, skip_webpack_install: true)
assert_bundler_command_called("install")
rails_path = File.expand_path("../../..", Rails.root)
@@ -741,7 +744,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_edge_option
- generator([destination_root], edge: true)
+ generator([destination_root], edge: true, skip_webpack_install: true)
assert_bundler_command_called("install")
assert_file "Gemfile", %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$}
@@ -753,12 +756,16 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_bundler_binstub
+ generator([destination_root], skip_webpack_install: true)
+
assert_bundler_command_called("binstubs bundler")
end
def test_spring_binstubs
jruby_skip "spring doesn't run on JRuby"
+ generator([destination_root], skip_webpack_install: true)
+
assert_bundler_command_called("exec spring binstub --all")
end
@@ -779,7 +786,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_spring_with_dev_option
- run_generator [destination_root, "--dev"]
+ run_generator [destination_root, "--dev", "--skip-bundle"]
assert_no_gem "spring"
end
@@ -789,7 +796,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
@called ||= 0
if command == "webpacker:install"
@called += 1
- assert_equal 0, @called, "webpacker:install expected not to be called once, but was called #{@called} times."
+ assert_equal 0, @called, "webpacker:install expected not to be called, but was called #{@called} times."
end
end
@@ -825,8 +832,22 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_gem "webpacker"
end
+ def test_skip_webpack_install
+ command_check = -> command do
+ if command == "webpacker:install"
+ assert false, "webpacker:install expected not to be called."
+ end
+ end
+
+ generator([destination_root], skip_webpack_install: true).stub(:rails_command, command_check) do
+ quietly { generator.invoke_all }
+ end
+
+ assert_gem "webpacker"
+ end
+
def test_generator_if_skip_turbolinks_is_given
- run_generator [destination_root, "--skip-turbolinks", "--no-skip-javascript"]
+ run_generator [destination_root, "--skip-turbolinks"]
assert_no_gem "turbolinks"
assert_file "app/views/layouts/application.html.erb" do |content|
@@ -863,7 +884,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_bootsnap_with_dev_option
- run_generator [destination_root, "--dev"]
+ run_generator [destination_root, "--dev", "--skip-bundle"]
assert_no_gem "bootsnap"
assert_file "config/boot.rb" do |content|
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 21b5013484..3cffdddd38 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -435,8 +435,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
end
- def test_scaffold_generator_belongs_to
- run_generator ["account", "name", "currency:belongs_to"]
+ def test_scaffold_generator_belongs_to_and_references
+ run_generator ["account", "name", "currency:belongs_to", "user:references"]
assert_file "app/models/account.rb", /belongs_to :currency/
@@ -449,7 +449,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/controllers/accounts_controller.rb" do |content|
assert_instance_method :account_params, content do |m|
- assert_match(/permit\(:name, :currency_id\)/, m)
+ assert_match(/permit\(:name, :currency_id, :user_id\)/, m)
end
end
@@ -457,6 +457,16 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/^\W{4}<%= form\.text_field :name %>/, content)
assert_match(/^\W{4}<%= form\.text_field :currency_id %>/, content)
end
+
+ assert_file "app/views/accounts/index.html.erb" do |content|
+ assert_match(/^\W{8}<td><%= account\.name %><\/td>/, content)
+ assert_match(/^\W{8}<td><%= account\.user_id %><\/td>/, content)
+ end
+
+ assert_file "app/views/accounts/show.html.erb" do |content|
+ assert_match(/^\W{2}<%= @account\.name %>/, content)
+ assert_match(/^\W{2}<%= @account\.user_id %>/, content)
+ end
end
def test_scaffold_generator_database
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index b766fa1a71..2dda856f25 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -27,7 +27,7 @@ module SharedGeneratorTests
end
def test_skeleton_is_created
- run_generator [destination_root, "--no-skip-javascript"]
+ run_generator
default_files.each { |path| assert_file path }
end
@@ -91,7 +91,7 @@ module SharedGeneratorTests
template
end
- generator([destination_root], template: path).stub(:open, check_open, template) do
+ generator([destination_root], template: path, skip_webpack_install: true).stub(:open, check_open, template) do
generator.stub :bundle_command, nil do
quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
end
@@ -99,7 +99,7 @@ module SharedGeneratorTests
end
def test_skip_gemfile
- assert_not_called(generator([destination_root], skip_gemfile: true), :bundle_command) do
+ assert_not_called(generator([destination_root], skip_gemfile: true, skip_webpack_install: true), :bundle_command) do
quietly { generator.invoke_all }
assert_no_file "Gemfile"
end
@@ -196,7 +196,7 @@ module SharedGeneratorTests
end
def test_generator_for_active_storage
- run_generator [destination_root, "--no-skip-javascript"]
+ run_generator
unless generator_class.name == "Rails::Generators::PluginGenerator"
assert_file "#{application_path}/app/javascript/packs/application.js" do |content|
@@ -226,12 +226,12 @@ module SharedGeneratorTests
end
def test_generator_if_skip_active_storage_is_given
- run_generator [destination_root, "--skip-active-storage", "--no-skip-javascript"]
+ run_generator [destination_root, "--skip-active-storage"]
assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']active_storage\/engine["']/
assert_file "#{application_path}/app/javascript/packs/application.js" do |content|
- assert_no_match(/^\/\/= require activestorage/, content)
+ assert_no_match(/activestorage/, content)
end
assert_file "#{application_path}/config/environments/development.rb" do |content|
@@ -256,12 +256,12 @@ module SharedGeneratorTests
end
def test_generator_does_not_generate_active_storage_contents_if_skip_active_record_is_given
- run_generator [destination_root, "--skip-active-record", "--no-skip-javascript"]
+ run_generator [destination_root, "--skip-active-record"]
assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']active_storage\/engine["']/
assert_file "#{application_path}/app/javascript/packs/application.js" do |content|
- assert_no_match(/^import * as ActiveStorage from "activestorage"\nActiveStorage.start()/, content)
+ assert_no_match(/^import * as ActiveStorage from "activestorage"\nActiveStorage.start\(\)/, content)
end
assert_file "#{application_path}/config/environments/development.rb" do |content|
@@ -336,28 +336,15 @@ module SharedGeneratorTests
end
def test_generator_for_yarn
+ skip "#34009 disabled JS by default for plugins" if generator_class.name == "Rails::Generators::PluginGenerator"
run_generator
assert_file "#{application_path}/package.json", /dependencies/
- assert_file "#{application_path}/config/initializers/assets.rb", /node_modules/
-
- assert_file ".gitignore" do |content|
- assert_match(/node_modules/, content)
- assert_match(/yarn-error\.log/, content)
- end
+ assert_file "#{application_path}/bin/yarn"
end
def test_generator_for_yarn_skipped
- run_generator([destination_root, "--skip-yarn"])
+ run_generator([destination_root, "--skip-javascript"])
assert_no_file "#{application_path}/package.json"
assert_no_file "#{application_path}/bin/yarn"
-
- assert_file "#{application_path}/config/initializers/assets.rb" do |content|
- assert_no_match(/node_modules/, content)
- end
-
- assert_file ".gitignore" do |content|
- assert_no_match(/node_modules/, content)
- assert_no_match(/yarn-error\.log/, content)
- end
end
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index e44f21380e..d4eed69a87 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -16,6 +16,9 @@ require "active_support/testing/autorun"
require "active_support/testing/stream"
require "active_support/testing/method_call_assertions"
require "active_support/test_case"
+require "minitest/retry"
+
+Minitest::Retry.use!(verbose: false, retry_count: 1)
RAILS_FRAMEWORK_ROOT = File.expand_path("../../..", __dir__)