aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.codeclimate.yml2
-rw-r--r--.travis.yml12
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock13
-rw-r--r--actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb.tt (renamed from actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb.tt (renamed from actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/cable.js.tt (renamed from actioncable/lib/rails/generators/channel/templates/assets/cable.js)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/channel.coffee.tt (renamed from actioncable/lib/rails/generators/channel/templates/assets/channel.coffee)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/channel.js.tt (renamed from actioncable/lib/rails/generators/channel/templates/assets/channel.js)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/channel.rb.tt (renamed from actioncable/lib/rails/generators/channel/templates/channel.rb)0
-rw-r--r--actioncable/test/subscription_adapter/redis_test.rb23
-rw-r--r--actionmailer/lib/action_mailer/preview.rb2
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt (renamed from actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb)0
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/mailer.rb.tt (renamed from actionmailer/lib/rails/generators/mailer/templates/mailer.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb4
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb2
-rw-r--r--actionpack/test/dispatch/request_test.rb4
-rw-r--r--actionview/CHANGELOG.md6
-rw-r--r--actionview/RUNNING_UNIT_TESTS.rdoc2
-rw-r--r--actionview/Rakefile4
-rw-r--r--actionview/lib/action_view/helpers/active_model_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb2
-rw-r--r--actionview/test/template/active_model_helper_test.rb74
-rw-r--r--actionview/test/template/form_options_helper_test.rb19
-rw-r--r--activejob/lib/rails/generators/job/templates/application_job.rb.tt (renamed from activejob/lib/rails/generators/job/templates/application_job.rb)0
-rw-r--r--activejob/lib/rails/generators/job/templates/job.rb.tt (renamed from activejob/lib/rails/generators/job/templates/job.rb)0
-rw-r--r--activejob/test/support/integration/adapters/resque.rb5
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb11
-rw-r--r--activemodel/lib/active_model.rb3
-rw-r--r--activemodel/lib/active_model/attribute.rb (renamed from activerecord/lib/active_record/attribute.rb)7
-rw-r--r--activemodel/lib/active_model/attribute/user_provided_default.rb (renamed from activerecord/lib/active_record/attribute/user_provided_default.rb)6
-rw-r--r--activemodel/lib/active_model/attribute_mutation_tracker.rb (renamed from activerecord/lib/active_record/attribute_mutation_tracker.rb)7
-rw-r--r--activemodel/lib/active_model/attribute_set.rb (renamed from activerecord/lib/active_record/attribute_set.rb)6
-rw-r--r--activemodel/lib/active_model/attribute_set/builder.rb (renamed from activerecord/lib/active_record/attribute_set/builder.rb)6
-rw-r--r--activemodel/lib/active_model/attribute_set/yaml_encoder.rb (renamed from activerecord/lib/active_record/attribute_set/yaml_encoder.rb)6
-rw-r--r--activemodel/lib/active_model/attributes.rb57
-rw-r--r--activemodel/lib/active_model/dirty.rb177
-rw-r--r--activemodel/lib/active_model/errors.rb10
-rw-r--r--activemodel/lib/active_model/type.rb4
-rw-r--r--activemodel/test/cases/attribute_set_test.rb (renamed from activerecord/test/cases/attribute_set_test.rb)5
-rw-r--r--activemodel/test/cases/attribute_test.rb (renamed from activerecord/test/cases/attribute_test.rb)4
-rw-r--r--activemodel/test/cases/attributes_dirty_test.rb22
-rw-r--r--activemodel/test/cases/attributes_test.rb28
-rw-r--r--activemodel/test/cases/dirty_test.rb4
-rw-r--r--activemodel/test/cases/errors_test.rb7
-rw-r--r--activemodel/test/cases/type/big_integer_test.rb1
-rw-r--r--activemodel/test/cases/type/binary_test.rb1
-rw-r--r--activemodel/test/cases/type/boolean_test.rb1
-rw-r--r--activemodel/test/cases/type/date_test.rb1
-rw-r--r--activemodel/test/cases/type/date_time_test.rb1
-rw-r--r--activemodel/test/cases/type/decimal_test.rb1
-rw-r--r--activemodel/test/cases/type/float_test.rb1
-rw-r--r--activemodel/test/cases/type/immutable_string_test.rb1
-rw-r--r--activemodel/test/cases/type/integer_test.rb1
-rw-r--r--activemodel/test/cases/type/registry_test.rb1
-rw-r--r--activemodel/test/cases/type/string_test.rb1
-rw-r--r--activemodel/test/cases/type/time_test.rb1
-rw-r--r--activemodel/test/cases/type/value_test.rb1
-rw-r--r--activerecord/CHANGELOG.md50
-rw-r--r--activerecord/lib/active_record.rb9
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb31
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb12
-rw-r--r--activerecord/lib/active_record/associations/preloader/belongs_to.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/collection_association.rb16
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_one.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_one_through.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader/singular_association.rb15
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb17
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb40
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb96
-rw-r--r--activerecord/lib/active_record/attributes.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/core.rb8
-rw-r--r--activerecord/lib/active_record/errors.rb32
-rw-r--r--activerecord/lib/active_record/legacy_yaml_adapter.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb21
-rw-r--r--activerecord/lib/active_record/model_schema.rb6
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb12
-rw-r--r--activerecord/lib/active_record/railties/databases.rake48
-rw-r--r--activerecord/lib/active_record/railties/jdbcmysql_error.rb18
-rw-r--r--activerecord/lib/active_record/relation.rb3
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb1
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb3
-rw-r--r--activerecord/lib/active_record/relation/merger.rb18
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_attribute.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb23
-rw-r--r--activerecord/lib/active_record/sanitization.rb15
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb47
-rw-r--r--activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt (renamed from activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb)0
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt (renamed from activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb)0
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt (renamed from activerecord/lib/rails/generators/active_record/migration/templates/migration.rb)0
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt (renamed from activerecord/lib/rails/generators/active_record/model/templates/model.rb)0
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt (renamed from activerecord/lib/rails/generators/active_record/model/templates/module.rb)0
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb22
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb55
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/transaction_test.rb57
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb22
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb10
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb21
-rw-r--r--activerecord/test/cases/calculations_test.rb10
-rw-r--r--activerecord/test/cases/finder_test.rb4
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb25
-rw-r--r--activerecord/test/cases/locking_test.rb13
-rw-r--r--activerecord/test/cases/relation_test.rb16
-rw-r--r--activerecord/test/cases/relations_test.rb68
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb30
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb79
-rw-r--r--activerecord/test/cases/transaction_isolation_test.rb4
-rw-r--r--activerecord/test/cases/transactions_test.rb28
-rw-r--r--activerecord/test/cases/unsafe_raw_sql_test.rb299
-rw-r--r--activerecord/test/fixtures/other_posts.yml1
-rw-r--r--activerecord/test/fixtures/posts.yml8
-rw-r--r--activerecord/test/models/post.rb4
-rw-r--r--activestorage/app/models/active_storage/blob.rb2
-rw-r--r--activestorage/lib/active_storage/attached/many.rb6
-rw-r--r--activestorage/lib/active_storage/attached/one.rb15
-rw-r--r--activestorage/test/models/attachments_test.rb51
-rw-r--r--activestorage/test/test_helper.rb4
-rw-r--r--activesupport/CHANGELOG.md63
-rw-r--r--activesupport/lib/active_support/cache.rb90
-rw-r--r--activesupport/lib/active_support/cache/redis_cache_store.rb404
-rw-r--r--activesupport/lib/active_support/core_ext/range.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/range/each.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb23
-rw-r--r--activesupport/lib/active_support/deprecation/constant_accessor.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb3
-rw-r--r--activesupport/lib/active_support/encrypted_configuration.rb8
-rw-r--r--activesupport/lib/active_support/encrypted_file.rb2
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb20
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb4
-rw-r--r--activesupport/lib/active_support/test_case.rb1
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb9
-rw-r--r--activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb8
-rw-r--r--activesupport/test/cache/behaviors/cache_store_behavior.rb10
-rw-r--r--activesupport/test/cache/behaviors/local_cache_behavior.rb6
-rw-r--r--activesupport/test/cache/cache_entry_test.rb13
-rw-r--r--activesupport/test/cache/stores/redis_cache_store_test.rb151
-rw-r--r--activesupport/test/core_ext/load_error_test.rb2
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb9
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb2
-rw-r--r--activesupport/test/encrypted_configuration_test.rb8
-rw-r--r--activesupport/test/inflector_test.rb6
-rw-r--r--activesupport/test/inflector_test_cases.rb2
-rw-r--r--activesupport/test/test_case_test.rb5
-rw-r--r--guides/source/active_record_callbacks.md2
-rw-r--r--guides/source/api_documentation_guidelines.md6
-rw-r--r--guides/source/caching_with_rails.md51
-rw-r--r--guides/source/command_line.md2
-rw-r--r--guides/source/configuring.md2
-rw-r--r--guides/source/layouts_and_rendering.md2
-rw-r--r--railties/lib/rails/commands/credentials/credentials_command.rb1
-rw-r--r--railties/lib/rails/commands/secrets/secrets_command.rb19
-rw-r--r--railties/lib/rails/generators.rb1
-rw-r--r--railties/lib/rails/generators/app_base.rb6
-rw-r--r--railties/lib/rails/generators/erb/controller/templates/view.html.erb.tt (renamed from railties/lib/rails/generators/erb/controller/templates/view.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/view.html.erb.tt (renamed from railties/lib/rails/generators/erb/mailer/templates/view.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/view.text.erb.tt (renamed from railties/lib/rails/generators/erb/mailer/templates/view.text.erb)0
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/index.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/new.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/show.html.erb)0
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile.tt (renamed from railties/lib/rails/generators/rails/app/templates/Gemfile)1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README.md.tt (renamed from railties/lib/rails/generators/rails/app/templates/README.md)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Rakefile.tt (renamed from railties/lib/rails/generators/rails/app/templates/Rakefile)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/bundle.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/bundle)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/rails.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/rails)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/rake.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/rake)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/yarn.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/yarn)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru.tt (renamed from railties/lib/rails/generators/rails/app/templates/config.ru)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/application.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/boot.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/cable.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/cable.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environment.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/environment.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/puma.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/routes.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/spring.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/spring.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/storage.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore.tt (renamed from railties/lib/rails/generators/rails/app/templates/gitignore)4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/package.json.tt (renamed from railties/lib/rails/generators/rails/app/templates/package.json)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/ruby-version.tt (renamed from railties/lib/rails/generators/rails/app/templates/ruby-version)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/test/test_helper.rb)0
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/stylesheet.css2
-rw-r--r--railties/lib/rails/generators/rails/controller/templates/controller.rb.tt (renamed from railties/lib/rails/generators/rails/controller/templates/controller.rb)0
-rw-r--r--railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb72
-rw-r--r--railties/lib/rails/generators/rails/helper/templates/helper.rb.tt (renamed from railties/lib/rails/generators/rails/helper/templates/helper.rb)0
-rw-r--r--railties/lib/rails/generators/rails/master_key/master_key_generator.rb32
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb1
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/Gemfile)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/README.md.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/README.md)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/Rakefile)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/config/routes.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/config/routes.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/gitignore.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/gitignore)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/application.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/application.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb)0
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt (renamed from railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb)0
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt (renamed from railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb)0
-rw-r--r--railties/lib/rails/generators/rails/task/templates/task.rb.tt (renamed from railties/lib/rails/generators/rails/task/templates/task.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/job/job_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb)0
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/preview.rb.tt (renamed from railties/lib/rails/generators/test_unit/mailer/templates/preview.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt (renamed from railties/lib/rails/generators/test_unit/model/templates/fixtures.yml)0
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/unit_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/model/templates/unit_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb.tt (renamed from railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/system/templates/system_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/system/templates/system_test.rb)0
-rw-r--r--railties/lib/rails/secrets.rb17
-rw-r--r--railties/lib/rails/tasks/engine.rake3
-rw-r--r--railties/test/application/bin_setup_test.rb4
-rw-r--r--railties/test/application/generators_test.rb3
-rw-r--r--railties/test/application/rackup_test.rb1
-rw-r--r--railties/test/application/rake/dbs_test.rb24
-rw-r--r--railties/test/application/test_runner_test.rb5
-rw-r--r--railties/test/application/test_test.rb5
-rw-r--r--railties/test/commands/secrets_test.rb49
-rw-r--r--railties/test/engine/test_test.rb31
-rw-r--r--railties/test/generators/app_generator_test.rb20
-rw-r--r--railties/test/generators/encrypted_secrets_generator_test.rb44
-rw-r--r--railties/test/generators/generators_test_helper.rb4
-rw-r--r--railties/test/generators/plugin_generator_test.rb4
-rw-r--r--railties/test/generators/plugin_test_runner_test.rb2
-rw-r--r--railties/test/generators/shared_generator_tests.rb3
-rw-r--r--railties/test/generators/test_runner_in_engine_test.rb2
-rw-r--r--railties/test/railties/engine_test.rb4
-rw-r--r--railties/test/secrets_test.rb16
287 files changed, 2355 insertions, 1043 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 1f4a1bb201..1f0f067c5b 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,7 +1,7 @@
engines:
rubocop:
enabled: true
- channel: rubocop-0-50
+ channel: rubocop-0-51
ratings:
paths:
diff --git a/.travis.yml b/.travis.yml
index e357a0ca3e..290e0b5f2b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,6 +11,7 @@ cache:
services:
- memcached
+ - redis-server
addons:
postgresql: "9.6"
@@ -41,7 +42,6 @@ before_script:
# Decodes to e.g. `export VARIABLE=VALUE`
- $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX0FDQ0VTU19LRVk9YTAzNTM0M2YtZTkyMi00MGIzLWFhM2MtMDZiM2VhNjM1YzQ4")
- $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX1VTRVJOQU1FPXJ1YnlvbnJhaWxz")
- - redis-server --bind 127.0.0.1 --port 6379 --requirepass 'password' --daemonize yes
script: 'ci/travis.rb'
@@ -73,21 +73,25 @@ matrix:
env: "GEM=aj:integration"
services:
- memcached
+ - redis-server
- rabbitmq
- rvm: 2.3.5
env: "GEM=aj:integration"
services:
- memcached
+ - redis-server
- rabbitmq
- rvm: 2.4.2
env: "GEM=aj:integration"
services:
- memcached
+ - redis-server
- rabbitmq
- rvm: ruby-head
env: "GEM=aj:integration"
services:
- memcached
+ - redis-server
- rabbitmq
- rvm: 2.3.5
env:
@@ -102,17 +106,17 @@ matrix:
- "GEM=ar:postgresql POSTGRES=9.2"
addons:
postgresql: "9.2"
- - rvm: jruby-9.1.13.0
+ - rvm: jruby-9.1.14.0
jdk: oraclejdk8
env:
- "GEM=ap"
- - rvm: jruby-9.1.13.0
+ - rvm: jruby-9.1.14.0
jdk: oraclejdk8
env:
- "GEM=am,amo,aj"
allow_failures:
- rvm: ruby-head
- - rvm: jruby-9.1.13.0
+ - rvm: jruby-9.1.14.0
- env: "GEM=ac:integration"
fast_finish: true
diff --git a/Gemfile b/Gemfile
index 2704e1e806..3f8cd57853 100644
--- a/Gemfile
+++ b/Gemfile
@@ -80,8 +80,7 @@ group :cable do
gem "hiredis", require: false
gem "redis", "~> 4.0", require: false
- # For Redis 4.0 support. Unreleased 9cb81bf.
- gem "redis-namespace", github: "resque/redis-namespace"
+ gem "redis-namespace"
gem "websocket-client-simple", github: "matthewd/websocket-client-simple", branch: "close-race", require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index cc3b0508c5..86f6ed3baf 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -53,13 +53,6 @@ GIT
tilt (>= 1.1, < 3)
GIT
- remote: https://github.com/resque/redis-namespace.git
- revision: 1403f511f6ae1ec9a8f330298a4cacf73fb10afc
- specs:
- redis-namespace (1.5.3)
- redis (>= 3.0.4)
-
-GIT
remote: https://github.com/robin850/sdoc.git
revision: 0e340352f3ab2f196c8a8743f83c2ee286e4f71c
branch: upgrade
@@ -409,6 +402,8 @@ GEM
rdoc (5.1.0)
redcarpet (3.2.3)
redis (4.0.1)
+ redis-namespace (1.6.0)
+ redis (>= 3.0.4)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@@ -420,7 +415,7 @@ GEM
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
retriable (3.1.1)
- rubocop (0.50.0)
+ rubocop (0.51.0)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
@@ -563,7 +558,7 @@ DEPENDENCIES
rb-inotify!
redcarpet (~> 3.2.3)
redis (~> 4.0)
- redis-namespace!
+ redis-namespace
resque
resque-scheduler!
rubocop (>= 0.47)
diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb.tt
index d672697283..d672697283 100644
--- a/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb
+++ b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb.tt
index 0ff5442f47..0ff5442f47 100644
--- a/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb
+++ b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/cable.js b/actioncable/lib/rails/generators/channel/templates/assets/cable.js.tt
index 739aa5f022..739aa5f022 100644
--- a/actioncable/lib/rails/generators/channel/templates/assets/cable.js
+++ b/actioncable/lib/rails/generators/channel/templates/assets/cable.js.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee b/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee.tt
index 5467811aba..5467811aba 100644
--- a/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee
+++ b/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/channel.js b/actioncable/lib/rails/generators/channel/templates/assets/channel.js.tt
index ab0e68b11a..ab0e68b11a 100644
--- a/actioncable/lib/rails/generators/channel/templates/assets/channel.js
+++ b/actioncable/lib/rails/generators/channel/templates/assets/channel.js.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/channel.rb b/actioncable/lib/rails/generators/channel/templates/channel.rb.tt
index 4bcfb2be4d..4bcfb2be4d 100644
--- a/actioncable/lib/rails/generators/channel/templates/channel.rb
+++ b/actioncable/lib/rails/generators/channel/templates/channel.rb.tt
diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb
index 69120d5753..63823d6ef0 100644
--- a/actioncable/test/subscription_adapter/redis_test.rb
+++ b/actioncable/test/subscription_adapter/redis_test.rb
@@ -4,12 +4,15 @@ require "test_helper"
require_relative "common"
require_relative "channel_prefix"
+require "active_support/testing/method_call_assertions"
+require "action_cable/subscription_adapter/redis"
+
class RedisAdapterTest < ActionCable::TestCase
include CommonSubscriptionAdapterTest
include ChannelPrefixTest
def cable_config
- { adapter: "redis", driver: "ruby", url: "redis://:password@127.0.0.1:6379/12" }
+ { adapter: "redis", driver: "ruby" }
end
end
@@ -23,6 +26,22 @@ class RedisAdapterTest::AlternateConfiguration < RedisAdapterTest
def cable_config
alt_cable_config = super.dup
alt_cable_config.delete(:url)
- alt_cable_config.merge(host: "127.0.0.1", port: 6379, db: 12, password: "password")
+ alt_cable_config.merge(host: "127.0.0.1", port: 6379, db: 12)
+ end
+end
+
+class RedisAdapterTest::Connector < ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+
+ test "slices url, host, port, db, and password from config" do
+ config = { url: 1, host: 2, port: 3, db: 4, password: 5 }
+
+ assert_called_with ::Redis, :new, [ config ] do
+ connect config.merge(other: "unrelated", stuff: "here")
+ end
+ end
+
+ def connect(config)
+ ActionCable::SubscriptionAdapter::Redis.redis_connector.call(config)
end
end
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index 730ac89c94..0aea84fd2b 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -104,7 +104,7 @@ module ActionMailer
private
def load_previews
if preview_path
- Dir["#{preview_path}/**/*_preview.rb"].each { |file| require_dependency file }
+ Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
end
end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt
index 00fb9bd48f..00fb9bd48f 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt
diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb.tt
index 348d314758..348d314758 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb.tt
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 08d9b094f3..0c8132684a 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -248,7 +248,7 @@ module ActionController
def decode_credentials(header)
ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair|
key, value = pair.split("=", 2)
- [key.strip, value.to_s.gsub(/^"|"$/, "").delete('\'')]
+ [key.strip, value.to_s.gsub(/^"|"$/, "").delete("'")]
end]
end
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 41a47f2c82..ec86b8bc47 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -9,7 +9,7 @@ module ActionDispatch
# sub-hashes of the params hash to filter. Filtering only certain sub-keys
# from a hash is possible by using the dot notation: 'credit_card.number'.
# If a block is given, each key and value of the params hash and all
- # sub-hashes is passed to it, the value or key can be replaced using
+ # sub-hashes is passed to it, where the value or the key can be replaced using
# String#replace or similar method.
#
# env["action_dispatch.parameter_filter"] = [:password]
@@ -48,7 +48,7 @@ module ActionDispatch
@filtered_env ||= env_filter.filter(@env)
end
- # Reconstructed a path with all sensitive GET parameters replaced.
+ # Reconstructs a path with all sensitive GET parameters replaced.
def filtered_path
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
end
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index f0f106c8ba..be455642de 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -98,7 +98,7 @@ class ACLogSubscriberTest < ActionController::TestCase
@old_logger = ActionController::Base.logger
- @cache_path = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname("tmp", "cache")
+ @cache_path = Dir.mktmpdir(%w[tmp cache])
@controller.cache_store = :file_store, @cache_path
ActionController::LogSubscriber.attach_to :action_controller
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 2a18395aac..8661dc56d6 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -763,7 +763,7 @@ class RequestMethod < BaseRequestTest
test "post uneffected by local inflections" do
existing_acronyms = ActiveSupport::Inflector.inflections.acronyms.dup
- existing_acronym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup
+ assert_deprecated { ActiveSupport::Inflector.inflections.acronym_regex.dup }
begin
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym "POS"
@@ -777,7 +777,7 @@ class RequestMethod < BaseRequestTest
# Reset original acronym set
ActiveSupport::Inflector.inflections do |inflect|
inflect.send(:instance_variable_set, "@acronyms", existing_acronyms)
- inflect.send(:instance_variable_set, "@acronym_regex", existing_acronym_regex)
+ inflect.send(:define_acronym_regex_patterns)
end
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index e3227103d6..c700cb72ec 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,9 @@
+* Fix issues with `field_error_proc` wrapping `optgroup` and select divider `option`.
+
+ Fixes #31088
+
+ *Matthias Neumayr*
+
* Remove deprecated Erubis ERB handler.
*Rafael Mendonça França*
diff --git a/actionview/RUNNING_UNIT_TESTS.rdoc b/actionview/RUNNING_UNIT_TESTS.rdoc
index 6c4e5e983a..e99d5ca1df 100644
--- a/actionview/RUNNING_UNIT_TESTS.rdoc
+++ b/actionview/RUNNING_UNIT_TESTS.rdoc
@@ -4,7 +4,7 @@ The easiest way to run the unit tests is through Rake. The default task runs
the entire test suite for all classes. For more information, checkout the
full array of rake tasks with "rake -T"
-Rake can be found at http://docs.seattlerb.org/rake/.
+Rake can be found at https://ruby.github.io/rake/.
== Running by hand
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 9e5ef35334..8650f541b0 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -32,7 +32,7 @@ namespace :test do
task :ujs do
begin
Dir.mkdir("log")
- pid = spawn("bundle exec rackup test/ujs/config.ru -p 4567 -s puma > log/test.log 2>&1")
+ pid = spawn("bundle exec rackup test/ujs/config.ru -p 4567 -s puma > log/test.log 2>&1", pgroup: true)
start_time = Time.now
@@ -48,7 +48,7 @@ namespace :test do
system("npm run lint && bundle exec ruby ../ci/qunit-selenium-runner.rb http://localhost:4567/")
status = $?.exitstatus
ensure
- Process.kill("KILL", pid) if pid
+ Process.kill("KILL", -pid) if pid
FileUtils.rm_rf("log")
end
diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb
index f1ef715710..e41a95d2ce 100644
--- a/actionview/lib/action_view/helpers/active_model_helper.rb
+++ b/actionview/lib/action_view/helpers/active_model_helper.rb
@@ -17,8 +17,8 @@ module ActionView
end
end
- def content_tag(*)
- error_wrapping(super)
+ def content_tag(type, options, *)
+ select_markup_helper?(type) ? super : error_wrapping(super)
end
def tag(type, options, *)
@@ -43,6 +43,10 @@ module ActionView
object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
end
+ def select_markup_helper?(type)
+ ["optgroup", "option"].include?(type)
+ end
+
def tag_generate_errors?(options)
options["type"] != "hidden"
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 80d53c2c6e..e86e18dd78 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -115,7 +115,7 @@ module ActionView
# # <option>Write</option></select>
#
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: true
- # # => <select id="people" name="people"><option value=""></option><option value="1">David</option></select>
+ # # => <select id="people" name="people"><option value="" label=" "></option><option value="1">David</option></select>
#
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: "All"
# # => <select id="people" name="people"><option value="">All</option><option value="1">David</option></select>
diff --git a/actionview/test/template/active_model_helper_test.rb b/actionview/test/template/active_model_helper_test.rb
index a929d8dc3d..36afed6dd6 100644
--- a/actionview/test/template/active_model_helper_test.rb
+++ b/actionview/test/template/active_model_helper_test.rb
@@ -6,7 +6,7 @@ class ActiveModelHelperTest < ActionView::TestCase
tests ActionView::Helpers::ActiveModelHelper
silence_warnings do
- Post = Struct.new(:author_name, :body, :updated_at) do
+ Post = Struct.new(:author_name, :body, :category, :published, :updated_at) do
include ActiveModel::Conversion
include ActiveModel::Validations
@@ -22,10 +22,14 @@ class ActiveModelHelperTest < ActionView::TestCase
@post = Post.new
@post.errors[:author_name] << "can't be empty"
@post.errors[:body] << "foo"
+ @post.errors[:category] << "must exist"
+ @post.errors[:published] << "must be accepted"
@post.errors[:updated_at] << "bar"
@post.author_name = ""
@post.body = "Back to the hill and over it again!"
+ @post.category = "rails"
+ @post.published = false
@post.updated_at = Date.new(2004, 6, 15)
end
@@ -56,6 +60,25 @@ class ActiveModelHelperTest < ActionView::TestCase
assert_dom_equal(expected_dom, select("post", "author_name", [:a, :b], prompt: "Choose one..."))
end
+ def test_select_grouped_options_with_errors
+ grouped_options = [
+ ["A", [["A1"], ["A2"]]],
+ ["B", [["B1"], ["B2"]]],
+ ]
+
+ assert_dom_equal(
+ %(<div class="field_with_errors"><select name="post[category]" id="post_category"><optgroup label="A"><option value="A1">A1</option>\n<option value="A2">A2</option></optgroup><optgroup label="B"><option value="B1">B1</option>\n<option value="B2">B2</option></optgroup></select></div>),
+ select("post", "category", grouped_options)
+ )
+ end
+
+ def test_collection_select_with_errors
+ assert_dom_equal(
+ %(<div class="field_with_errors"><select name="post[author_name]" id="post_author_name"><option value="a">a</option>\n<option value="b">b</option></select></div>),
+ collection_select("post", "author_name", [:a, :b], :to_s, :to_s)
+ )
+ end
+
def test_date_select_with_errors
assert_dom_equal(
%(<div class="field_with_errors"><select id="post_updated_at_1i" name="post[updated_at(1i)]">\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n</select>\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" />\n</div>),
@@ -77,6 +100,55 @@ class ActiveModelHelperTest < ActionView::TestCase
)
end
+ def test_label_with_errors
+ assert_dom_equal(
+ %(<div class="field_with_errors"><label for="post_body">Body</label></div>),
+ label("post", "body")
+ )
+ end
+
+ def test_check_box_with_errors
+ assert_dom_equal(
+ %(<input name="post[published]" type="hidden" value="0" /><div class="field_with_errors"><input type="checkbox" value="1" name="post[published]" id="post_published" /></div>),
+ check_box("post", "published")
+ )
+ end
+
+ def test_check_boxes_with_errors
+ assert_dom_equal(
+ %(<input name="post[published]" type="hidden" value="0" /><div class="field_with_errors"><input type="checkbox" value="1" name="post[published]" id="post_published" /></div><input name="post[published]" type="hidden" value="0" /><div class="field_with_errors"><input type="checkbox" value="1" name="post[published]" id="post_published" /></div>),
+ check_box("post", "published") + check_box("post", "published")
+ )
+ end
+
+ def test_radio_button_with_errors
+ assert_dom_equal(
+ %(<div class="field_with_errors"><input type="radio" value="rails" checked="checked" name="post[category]" id="post_category_rails" /></div>),
+ radio_button("post", "category", "rails")
+ )
+ end
+
+ def test_radio_buttons_with_errors
+ assert_dom_equal(
+ %(<div class="field_with_errors"><input type="radio" value="rails" checked="checked" name="post[category]" id="post_category_rails" /></div><div class="field_with_errors"><input type="radio" value="java" name="post[category]" id="post_category_java" /></div>),
+ radio_button("post", "category", "rails") + radio_button("post", "category", "java")
+ )
+ end
+
+ def test_collection_check_boxes_with_errors
+ assert_dom_equal(
+ %(<input type="hidden" name="post[category][]" value="" /><div class="field_with_errors"><input type="checkbox" value="ruby" name="post[category][]" id="post_category_ruby" /></div><label for="post_category_ruby">ruby</label><div class="field_with_errors"><input type="checkbox" value="java" name="post[category][]" id="post_category_java" /></div><label for="post_category_java">java</label>),
+ collection_check_boxes("post", "category", [:ruby, :java], :to_s, :to_s)
+ )
+ end
+
+ def test_collection_radio_buttons_with_errors
+ assert_dom_equal(
+ %(<input type="hidden" name="post[category]" value="" /><div class="field_with_errors"><input type="radio" value="ruby" name="post[category]" id="post_category_ruby" /></div><label for="post_category_ruby">ruby</label><div class="field_with_errors"><input type="radio" value="java" name="post[category]" id="post_category_java" /></div><label for="post_category_java">java</label>),
+ collection_radio_buttons("post", "category", [:ruby, :java], :to_s, :to_s)
+ )
+ end
+
def test_hidden_field_does_not_render_errors
assert_dom_equal(
%(<input id="post_author_name" name="post[author_name]" type="hidden" value="" />),
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index a66db2f3dc..f0eed1e290 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -1251,6 +1251,25 @@ class FormOptionsHelperTest < ActionView::TestCase
html
end
+ def test_time_zone_select_with_priority_zones_and_errors
+ @firm = Firm.new("D")
+ @firm.extend ActiveModel::Validations
+ @firm.errors[:time_zone] << "invalid"
+ zones = [ ActiveSupport::TimeZone.new("A"), ActiveSupport::TimeZone.new("D") ]
+ html = time_zone_select("firm", "time_zone", zones)
+ assert_dom_equal "<div class=\"field_with_errors\">" \
+ "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>" \
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"E\">E</option>" \
+ "</select>" \
+ "</div>",
+ html
+ end
+
def test_time_zone_select_with_default_time_zone_and_nil_value
@firm = Firm.new()
@firm.time_zone = nil
diff --git a/activejob/lib/rails/generators/job/templates/application_job.rb b/activejob/lib/rails/generators/job/templates/application_job.rb.tt
index f93745a31a..f93745a31a 100644
--- a/activejob/lib/rails/generators/job/templates/application_job.rb
+++ b/activejob/lib/rails/generators/job/templates/application_job.rb.tt
diff --git a/activejob/lib/rails/generators/job/templates/job.rb b/activejob/lib/rails/generators/job/templates/job.rb.tt
index 4ad2914a45..4ad2914a45 100644
--- a/activejob/lib/rails/generators/job/templates/job.rb
+++ b/activejob/lib/rails/generators/job/templates/job.rb.tt
diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb
index 7d5174b957..2ed8302277 100644
--- a/activejob/test/support/integration/adapters/resque.rb
+++ b/activejob/test/support/integration/adapters/resque.rb
@@ -3,11 +3,12 @@
module ResqueJobsManager
def setup
ActiveJob::Base.queue_adapter = :resque
- Resque.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.new(url: "redis://:password@127.0.0.1:6379/12", thread_safe: true)
+ Resque.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.new(url: "redis://127.0.0.1:6379/12", thread_safe: true)
Resque.logger = Rails.logger
unless can_run?
puts "Cannot run integration tests for resque. To be able to run integration tests for resque you need to install and start redis.\n"
- exit
+ status = ENV["CI"] ? false : true
+ exit status
end
end
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
index ceb7fb61f2..4b01a81ec5 100644
--- a/activejob/test/support/integration/adapters/sidekiq.rb
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -5,20 +5,13 @@ require "sidekiq/api"
require "sidekiq/testing"
Sidekiq::Testing.disable!
-Sidekiq.configure_server do |config|
- config.redis = { url: "redis://:password@127.0.0.1:6379/12" }
-end
-
-Sidekiq.configure_client do |config|
- config.redis = { url: "redis://:password@127.0.0.1:6379/12" }
-end
-
module SidekiqJobsManager
def setup
ActiveJob::Base.queue_adapter = :sidekiq
unless can_run?
puts "Cannot run integration tests for sidekiq. To be able to run integration tests for sidekiq you need to install and start redis.\n"
- exit
+ status = ENV["CI"] ? false : true
+ exit status
end
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index a97d7989be..a3884206a6 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -30,6 +30,8 @@ require "active_model/version"
module ActiveModel
extend ActiveSupport::Autoload
+ autoload :Attribute
+ autoload :Attributes
autoload :AttributeAssignment
autoload :AttributeMethods
autoload :BlockValidator, "active_model/validator"
@@ -45,6 +47,7 @@ module ActiveModel
autoload :SecurePassword
autoload :Serialization
autoload :Translation
+ autoload :Type
autoload :Validations
autoload :Validator
diff --git a/activerecord/lib/active_record/attribute.rb b/activemodel/lib/active_model/attribute.rb
index fc474edc15..b75ff80b31 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activemodel/lib/active_model/attribute.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
-module ActiveRecord
+require "active_support/core_ext/object/duplicable"
+
+module ActiveModel
class Attribute # :nodoc:
class << self
def from_database(name, value, type)
@@ -130,8 +132,6 @@ module ActiveRecord
coder["value"] = value if defined?(@value)
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :original_attribute
@@ -237,6 +237,7 @@ module ActiveRecord
self.class.new(name, type)
end
end
+
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
end
end
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activemodel/lib/active_model/attribute/user_provided_default.rb
index f746960fac..f274b687d4 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activemodel/lib/active_model/attribute/user_provided_default.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require "active_record/attribute"
+require "active_model/attribute"
-module ActiveRecord
+module ActiveModel
class Attribute # :nodoc:
class UserProvidedDefault < FromUser # :nodoc:
def initialize(name, value, type, database_default)
@@ -22,8 +22,6 @@ module ActiveRecord
self.class.new(name, user_provided_value, type, original_attribute)
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :user_provided_value
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activemodel/lib/active_model/attribute_mutation_tracker.rb
index 94bf641a5d..c67e1b809a 100644
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ b/activemodel/lib/active_model/attribute_mutation_tracker.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
-module ActiveRecord
+require "active_support/core_ext/hash/indifferent_access"
+
+module ActiveModel
class AttributeMutationTracker # :nodoc:
OPTION_NOT_GIVEN = Object.new
@@ -107,5 +109,8 @@ module ActiveRecord
def original_value(*)
end
+
+ def force_change(*)
+ end
end
end
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activemodel/lib/active_model/attribute_set.rb
index 9785666e77..a892accbc6 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activemodel/lib/active_model/attribute_set.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require "active_record/attribute_set/builder"
-require "active_record/attribute_set/yaml_encoder"
+require "active_model/attribute_set/builder"
+require "active_model/attribute_set/yaml_encoder"
-module ActiveRecord
+module ActiveModel
class AttributeSet # :nodoc:
delegate :each_value, :fetch, to: :attributes
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activemodel/lib/active_model/attribute_set/builder.rb
index 349cc7e403..f94f47370f 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activemodel/lib/active_model/attribute_set/builder.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require "active_record/attribute"
+require "active_model/attribute"
-module ActiveRecord
+module ActiveModel
class AttributeSet # :nodoc:
class Builder # :nodoc:
attr_reader :types, :always_initialized, :default
@@ -92,8 +92,6 @@ module ActiveRecord
@materialized = true
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :types, :values, :additional_types, :delegate_hash, :default
diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activemodel/lib/active_model/attribute_set/yaml_encoder.rb
index 9254ce16ab..4ea945b956 100644
--- a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
+++ b/activemodel/lib/active_model/attribute_set/yaml_encoder.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-module ActiveRecord
+module ActiveModel
class AttributeSet
# Attempts to do more intelligent YAML dumping of an
- # ActiveRecord::AttributeSet to reduce the size of the resulting string
+ # ActiveModel::AttributeSet to reduce the size of the resulting string
class YAMLEncoder # :nodoc:
def initialize(default_types)
@default_types = default_types
@@ -33,8 +33,6 @@ module ActiveRecord
end
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
protected
attr_reader :default_types
diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb
index 3e34d3b83a..cac461b549 100644
--- a/activemodel/lib/active_model/attributes.rb
+++ b/activemodel/lib/active_model/attributes.rb
@@ -1,24 +1,29 @@
# frozen_string_literal: true
-require "active_model/type"
+require "active_support/core_ext/object/deep_dup"
+require "active_model/attribute_set"
+require "active_model/attribute/user_provided_default"
module ActiveModel
module Attributes #:nodoc:
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
- include ActiveModel::Dirty
included do
attribute_method_suffix "="
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
- self.attribute_types = {}
- self._default_attributes = {}
+ self.attribute_types = Hash.new(Type.default_value)
+ self._default_attributes = AttributeSet.new({})
end
module ClassMethods
- def attribute(name, cast_type = Type::Value.new, **options)
- self.attribute_types = attribute_types.merge(name.to_s => cast_type)
- self._default_attributes = _default_attributes.merge(name.to_s => options[:default])
+ def attribute(name, type = Type::Value.new, **options)
+ name = name.to_s
+ if type.is_a?(Symbol)
+ type = ActiveModel::Type.lookup(type, **options.except(:default))
+ end
+ self.attribute_types = attribute_types.merge(name => type)
+ define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
define_attribute_methods(name)
end
@@ -37,11 +42,29 @@ module ActiveModel
undef_method :__temp__#{safe_name}=
STR
end
+
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
+ private_constant :NO_DEFAULT_PROVIDED
+
+ def define_default_attribute(name, value, type)
+ self._default_attributes = _default_attributes.deep_dup
+ if value == NO_DEFAULT_PROVIDED
+ default_attribute = _default_attributes[name].with_type(type)
+ else
+ default_attribute = Attribute::UserProvidedDefault.new(
+ name,
+ value,
+ type,
+ _default_attributes.fetch(name.to_s) { nil },
+ )
+ end
+ _default_attributes[name] = default_attribute
+ end
end
def initialize(*)
+ @attributes = self.class._default_attributes.deep_dup
super
- clear_changes_information
end
private
@@ -53,21 +76,17 @@ module ActiveModel
attr_name.to_s
end
- cast_type = self.class.attribute_types[name]
-
- deserialized_value = ActiveModel::Type.lookup(cast_type).cast(value)
- attribute_will_change!(name) unless deserialized_value == attribute(name)
- instance_variable_set("@#{name}", deserialized_value)
- deserialized_value
+ @attributes.write_from_user(name, value)
+ value
end
- def attribute(name)
- if instance_variable_defined?("@#{name}")
- instance_variable_get("@#{name}")
+ def attribute(attr_name)
+ name = if self.class.attribute_alias?(attr_name)
+ self.class.attribute_alias(attr_name).to_s
else
- default = self.class._default_attributes[name]
- default.respond_to?(:call) ? default.call : default
+ attr_name.to_s
end
+ @attributes.fetch_value(name)
end
# Handle *= for method_missing.
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 943db0ab52..d2ebd18107 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -2,6 +2,7 @@
require "active_support/hash_with_indifferent_access"
require "active_support/core_ext/object/duplicable"
+require "active_model/attribute_mutation_tracker"
module ActiveModel
# == Active \Model \Dirty
@@ -130,6 +131,24 @@ module ActiveModel
attribute_method_affix prefix: "restore_", suffix: "!"
end
+ def initialize_dup(other) # :nodoc:
+ super
+ if self.class.respond_to?(:_default_attributes)
+ @attributes = self.class._default_attributes.map do |attr|
+ attr.with_value_from_user(@attributes.fetch_value(attr.name))
+ end
+ end
+ @mutations_from_database = nil
+ end
+
+ def changes_applied # :nodoc:
+ @previously_changed = changes
+ @mutations_before_last_save = mutations_from_database
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
+ forget_attribute_assignments
+ @mutations_from_database = nil
+ end
+
# Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
#
# person.changed? # => false
@@ -148,6 +167,60 @@ module ActiveModel
changed_attributes.keys
end
+ # Handles <tt>*_changed?</tt> for +method_missing+.
+ def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
+ !!changes_include?(attr) &&
+ (to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
+ (from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
+ end
+
+ # Handles <tt>*_was</tt> for +method_missing+.
+ def attribute_was(attr) # :nodoc:
+ attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr)
+ end
+
+ # Handles <tt>*_previously_changed?</tt> for +method_missing+.
+ def attribute_previously_changed?(attr) #:nodoc:
+ previous_changes_include?(attr)
+ end
+
+ # Restore all previous data of the provided attributes.
+ def restore_attributes(attributes = changed)
+ attributes.each { |attr| restore_attribute! attr }
+ end
+
+ # Clears all dirty data: current changes and previous changes.
+ def clear_changes_information
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
+ @mutations_before_last_save = nil
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
+ forget_attribute_assignments
+ @mutations_from_database = nil
+ end
+
+ def clear_attribute_changes(attr_names)
+ attributes_changed_by_setter.except!(*attr_names)
+ attr_names.each do |attr_name|
+ clear_attribute_change(attr_name)
+ end
+ end
+
+ # Returns a hash of the attributes with unsaved changes indicating their original
+ # values like <tt>attr => original value</tt>.
+ #
+ # person.name # => "bob"
+ # person.name = 'robert'
+ # person.changed_attributes # => {"name" => "bob"}
+ def changed_attributes
+ # This should only be set by methods which will call changed_attributes
+ # multiple times when it is known that the computed value cannot change.
+ if defined?(@cached_changed_attributes)
+ @cached_changed_attributes
+ else
+ attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze
+ end
+ end
+
# Returns a hash of changed attributes indicating their original
# and new values like <tt>attr => [original value, new value]</tt>.
#
@@ -155,7 +228,9 @@ module ActiveModel
# person.name = 'bob'
# person.changes # => { "name" => ["bill", "bob"] }
def changes
- ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
+ cache_changed_attributes do
+ ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
+ end
end
# Returns a hash of attributes that were changed before the model was saved.
@@ -166,45 +241,51 @@ module ActiveModel
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
+ @previously_changed.merge(mutations_before_last_save.changes)
end
- # Returns a hash of the attributes with unsaved changes indicating their original
- # values like <tt>attr => original value</tt>.
- #
- # person.name # => "bob"
- # person.name = 'robert'
- # person.changed_attributes # => {"name" => "bob"}
- def changed_attributes
- @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
+ def attribute_changed_in_place?(attr_name) # :nodoc:
+ mutations_from_database.changed_in_place?(attr_name)
end
- # Handles <tt>*_changed?</tt> for +method_missing+.
- def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
- !!changes_include?(attr) &&
- (to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
- (from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
- end
+ private
+ def clear_attribute_change(attr_name)
+ mutations_from_database.forget_change(attr_name)
+ end
- # Handles <tt>*_was</tt> for +method_missing+.
- def attribute_was(attr) # :nodoc:
- attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr)
- end
+ def mutations_from_database
+ unless defined?(@mutations_from_database)
+ @mutations_from_database = nil
+ end
+ @mutations_from_database ||= if defined?(@attributes)
+ ActiveModel::AttributeMutationTracker.new(@attributes)
+ else
+ NullMutationTracker.instance
+ end
+ end
- # Handles <tt>*_previously_changed?</tt> for +method_missing+.
- def attribute_previously_changed?(attr) #:nodoc:
- previous_changes_include?(attr)
- end
+ def forget_attribute_assignments
+ @attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes)
+ end
- # Restore all previous data of the provided attributes.
- def restore_attributes(attributes = changed)
- attributes.each { |attr| restore_attribute! attr }
- end
+ def mutations_before_last_save
+ @mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
+ end
- private
+ def cache_changed_attributes
+ @cached_changed_attributes = changed_attributes
+ yield
+ ensure
+ clear_changed_attributes_cache
+ end
+
+ def clear_changed_attributes_cache
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
+ end
# Returns +true+ if attr_name is changed, +false+ otherwise.
def changes_include?(attr_name)
- attributes_changed_by_setter.include?(attr_name)
+ attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name)
end
alias attribute_changed_by_setter? changes_include?
@@ -214,18 +295,6 @@ module ActiveModel
previous_changes.include?(attr_name)
end
- # Removes current changes and makes them accessible through +previous_changes+.
- def changes_applied # :doc:
- @previously_changed = changes
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
- end
-
- # Clears all dirty data: current changes and previous changes.
- def clear_changes_information # :doc:
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
- end
-
# Handles <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
@@ -238,15 +307,16 @@ module ActiveModel
# Handles <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
- return if attribute_changed?(attr)
+ unless attribute_changed?(attr)
+ begin
+ value = _read_attribute(attr)
+ value = value.duplicable? ? value.clone : value
+ rescue TypeError, NoMethodError
+ end
- begin
- value = _read_attribute(attr)
- value = value.duplicable? ? value.clone : value
- rescue TypeError, NoMethodError
+ set_attribute_was(attr, value)
end
-
- set_attribute_was(attr, value)
+ mutations_from_database.force_change(attr)
end
# Handles <tt>restore_*!</tt> for +method_missing+.
@@ -257,18 +327,13 @@ module ActiveModel
end
end
- # This is necessary because `changed_attributes` might be overridden in
- # other implementations (e.g. in `ActiveRecord`)
- alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
+ def attributes_changed_by_setter
+ @attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
+ end
# Force an attribute to have a particular "before" value
def set_attribute_was(attr, old_value)
attributes_changed_by_setter[attr] = old_value
end
-
- # Remove changes information for the provided attributes.
- def clear_attribute_changes(attributes) # :doc:
- attributes_changed_by_setter.except!(*attributes)
- end
end
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 971bdd08b1..275e3f1313 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -322,9 +322,13 @@ module ActiveModel
# person.errors.added? :name, :too_long # => false
# person.errors.added? :name, "is too long" # => false
def added?(attribute, message = :invalid, options = {})
- message = message.call if message.respond_to?(:call)
- message = normalize_message(attribute, message, options)
- self[attribute].include? message
+ if message.is_a? Symbol
+ self.details[attribute].map { |e| e[:error] }.include? message
+ else
+ message = message.call if message.respond_to?(:call)
+ message = normalize_message(attribute, message, options)
+ self[attribute].include? message
+ end
end
# Returns all the full error messages in an array.
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
index b0ed67f28e..39324999c9 100644
--- a/activemodel/lib/active_model/type.rb
+++ b/activemodel/lib/active_model/type.rb
@@ -32,6 +32,10 @@ module ActiveModel
def lookup(*args, **kwargs) # :nodoc:
registry.lookup(*args, **kwargs)
end
+
+ def default_value # :nodoc:
+ @default_value ||= Value.new
+ end
end
register(:big_integer, Type::BigInteger)
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activemodel/test/cases/attribute_set_test.rb
index 8be77ed88f..02c44c5d45 100644
--- a/activerecord/test/cases/attribute_set_test.rb
+++ b/activemodel/test/cases/attribute_set_test.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
require "cases/helper"
+require "active_model/attribute_set"
-module ActiveRecord
- class AttributeSetTest < ActiveRecord::TestCase
+module ActiveModel
+ class AttributeSetTest < ActiveModel::TestCase
test "building a new set from raw attributes" do
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
diff --git a/activerecord/test/cases/attribute_test.rb b/activemodel/test/cases/attribute_test.rb
index 1731e7926e..14d86cef97 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activemodel/test/cases/attribute_test.rb
@@ -2,8 +2,8 @@
require "cases/helper"
-module ActiveRecord
- class AttributeTest < ActiveRecord::TestCase
+module ActiveModel
+ class AttributeTest < ActiveModel::TestCase
setup do
@type = Minitest::Mock.new
end
diff --git a/activemodel/test/cases/attributes_dirty_test.rb b/activemodel/test/cases/attributes_dirty_test.rb
index 26b0e85db3..83a86371e0 100644
--- a/activemodel/test/cases/attributes_dirty_test.rb
+++ b/activemodel/test/cases/attributes_dirty_test.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/attributes"
class AttributesDirtyTest < ActiveModel::TestCase
class DirtyModel
include ActiveModel::Model
include ActiveModel::Attributes
+ include ActiveModel::Dirty
attribute :name, :string
attribute :color, :string
attribute :size, :integer
@@ -69,12 +69,10 @@ class AttributesDirtyTest < ActiveModel::TestCase
end
test "attribute mutation" do
- @model.instance_variable_set("@name", "Yam".dup)
+ @model.name = "Yam"
+ @model.save
assert !@model.name_changed?
@model.name.replace("Hadad")
- assert !@model.name_changed?
- @model.name_will_change!
- @model.name.replace("Baal")
assert @model.name_changed?
end
@@ -190,4 +188,18 @@ class AttributesDirtyTest < ActiveModel::TestCase
assert_equal "Dmitry", @model.name
assert_equal "White", @model.color
end
+
+ test "changing the attribute reports a change only when the cast value changes" do
+ @model.size = "2.3"
+ @model.save
+ @model.size = "2.1"
+
+ assert_equal false, @model.changed?
+
+ @model.size = "5.1"
+
+ assert_equal true, @model.changed?
+ assert_equal true, @model.size_changed?
+ assert_equal({ "size" => [2, 5] }, @model.changes)
+ end
end
diff --git a/activemodel/test/cases/attributes_test.rb b/activemodel/test/cases/attributes_test.rb
index 064cba40e3..914aee1ac0 100644
--- a/activemodel/test/cases/attributes_test.rb
+++ b/activemodel/test/cases/attributes_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/attributes"
module ActiveModel
class AttributesTest < ActiveModel::TestCase
@@ -13,7 +12,7 @@ module ActiveModel
attribute :string_field, :string
attribute :decimal_field, :decimal
attribute :string_with_default, :string, default: "default string"
- attribute :date_field, :string, default: -> { Date.new(2016, 1, 1) }
+ attribute :date_field, :date, default: -> { Date.new(2016, 1, 1) }
attribute :boolean_field, :boolean
end
@@ -48,31 +47,6 @@ module ActiveModel
assert_equal true, data.boolean_field
end
- test "dirty" do
- data = ModelForAttributesTest.new(
- integer_field: "2.3",
- string_field: "Rails FTW",
- decimal_field: "12.3",
- boolean_field: "0"
- )
-
- assert_equal false, data.changed?
-
- data.integer_field = "2.1"
-
- assert_equal false, data.changed?
-
- data.string_with_default = "default string"
-
- assert_equal false, data.changed?
-
- data.integer_field = "5.1"
-
- assert_equal true, data.changed?
- assert_equal true, data.integer_field_changed?
- assert_equal({ "integer_field" => [2, 5] }, data.changes)
- end
-
test "nonexistent attribute" do
assert_raise ActiveModel::UnknownAttributeError do
ModelForAttributesTest.new(nonexistent: "nonexistent")
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index 2cd9e185e6..dfe041ff50 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -219,4 +219,8 @@ class DirtyTest < ActiveModel::TestCase
assert_equal "Dmitry", @model.name
assert_equal "White", @model.color
end
+
+ test "model can be dup-ed without Attributes" do
+ assert @model.dup
+ end
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index ab18af0de1..d5c282b620 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -223,6 +223,13 @@ class ErrorsTest < ActiveModel::TestCase
assert !person.errors.added?(:name)
end
+ test "added? returns false when checking for an error by symbol and a different error with same message is present" do
+ I18n.backend.store_translations("en", errors: { attributes: { name: { wrong: "is wrong", used: "is wrong" } } })
+ person = Person.new
+ person.errors.add(:name, :wrong)
+ assert !person.errors.added?(:name, :used)
+ end
+
test "size calculates the number of error messages" do
person = Person.new
person.errors.add(:name, "cannot be blank")
diff --git a/activemodel/test/cases/type/big_integer_test.rb b/activemodel/test/cases/type/big_integer_test.rb
index 3d29235d52..0fa0200df4 100644
--- a/activemodel/test/cases/type/big_integer_test.rb
+++ b/activemodel/test/cases/type/big_integer_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/binary_test.rb b/activemodel/test/cases/type/binary_test.rb
index ef4f125a3b..3221a73e49 100644
--- a/activemodel/test/cases/type/binary_test.rb
+++ b/activemodel/test/cases/type/binary_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/boolean_test.rb b/activemodel/test/cases/type/boolean_test.rb
index 97b165ab48..2d33579595 100644
--- a/activemodel/test/cases/type/boolean_test.rb
+++ b/activemodel/test/cases/type/boolean_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/date_test.rb b/activemodel/test/cases/type/date_test.rb
index 15c40a37b7..e8cf178612 100644
--- a/activemodel/test/cases/type/date_test.rb
+++ b/activemodel/test/cases/type/date_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/date_time_test.rb b/activemodel/test/cases/type/date_time_test.rb
index 598ccf485e..60f62becc2 100644
--- a/activemodel/test/cases/type/date_time_test.rb
+++ b/activemodel/test/cases/type/date_time_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb
index a0acdc2736..b91d67f95f 100644
--- a/activemodel/test/cases/type/decimal_test.rb
+++ b/activemodel/test/cases/type/decimal_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/float_test.rb b/activemodel/test/cases/type/float_test.rb
index 46e8b34dfe..28318e06f8 100644
--- a/activemodel/test/cases/type/float_test.rb
+++ b/activemodel/test/cases/type/float_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/immutable_string_test.rb b/activemodel/test/cases/type/immutable_string_test.rb
index 72f5779dfb..751f753ddb 100644
--- a/activemodel/test/cases/type/immutable_string_test.rb
+++ b/activemodel/test/cases/type/immutable_string_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/integer_test.rb b/activemodel/test/cases/type/integer_test.rb
index d2e635b447..8c5d18c9b3 100644
--- a/activemodel/test/cases/type/integer_test.rb
+++ b/activemodel/test/cases/type/integer_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
require "active_support/core_ext/numeric/time"
module ActiveModel
diff --git a/activemodel/test/cases/type/registry_test.rb b/activemodel/test/cases/type/registry_test.rb
index f34104286c..0633ea2538 100644
--- a/activemodel/test/cases/type/registry_test.rb
+++ b/activemodel/test/cases/type/registry_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb
index d39389718b..825c8bb246 100644
--- a/activemodel/test/cases/type/string_test.rb
+++ b/activemodel/test/cases/type/string_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/time_test.rb b/activemodel/test/cases/type/time_test.rb
index 0bea95768d..f7102d1e97 100644
--- a/activemodel/test/cases/type/time_test.rb
+++ b/activemodel/test/cases/type/time_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activemodel/test/cases/type/value_test.rb b/activemodel/test/cases/type/value_test.rb
index 671343b0c8..55b5d9d584 100644
--- a/activemodel/test/cases/type/value_test.rb
+++ b/activemodel/test/cases/type/value_test.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "cases/helper"
-require "active_model/type"
module ActiveModel
module Type
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index e6a41ec281..2088b018d7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,49 @@
+* Add `#only_up` to database migrations for code that is only relevant when
+ migrating up, e.g. populating a new column.
+
+ *Rich Daley*
+
+* Require raw SQL fragments to be explicitly marked when used in
+ relation query methods.
+
+ Before:
+ ```
+ Article.order("LENGTH(title)")
+ ```
+
+ After:
+ ```
+ Article.order(Arel.sql("LENGTH(title)"))
+ ```
+
+ This prevents SQL injection if applications use the [strongly
+ discouraged] form `Article.order(params[:my_order])`, under the
+ mistaken belief that only column names will be accepted.
+
+ Raw SQL strings will now cause a deprecation warning, which will
+ become an UnknownAttributeReference error in Rails 6.0. Applications
+ can opt in to the future behavior by setting `allow_unsafe_raw_sql`
+ to `:disabled`.
+
+ Common and judged-safe string values (such as simple column
+ references) are unaffected:
+ ```
+ Article.order("title DESC")
+ ```
+
+ *Ben Toews*
+
+* `update_all` will now pass its values to `Type#cast` before passing them to
+ `Type#serialize`. This means that `update_all(foo: 'true')` will properly
+ persist a boolean.
+
+ *Sean Griffin*
+
+* Add new error class `StatementTimeout` which will be raised
+ when statement timeout exceeded.
+
+ *Ryuta Kamizono*
+
* Fix `bin/rails db:migrate` with specified `VERSION`.
`bin/rails db:migrate` with empty VERSION behaves as without `VERSION`.
Check a format of `VERSION`: Allow a migration version number
@@ -144,8 +190,8 @@
*Jeremy Green*
-* Add new error class `TransactionTimeout` for MySQL adapter which will be raised
- when lock wait time expires.
+* Add new error class `TransactionTimeout` which will be raised
+ when lock wait timeout exceeded.
*Gabriel Courtemanche*
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index bf6dfd46e1..5de6503144 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -27,14 +27,14 @@ require "active_support"
require "active_support/rails"
require "active_model"
require "arel"
+require "yaml"
require "active_record/version"
-require "active_record/attribute_set"
+require "active_model/attribute_set"
module ActiveRecord
extend ActiveSupport::Autoload
- autoload :Attribute
autoload :Base
autoload :Callbacks
autoload :Core
@@ -104,6 +104,7 @@ module ActiveRecord
autoload :Result
autoload :TableMetadata
+ autoload :Type
end
module Coders
@@ -181,3 +182,7 @@ end
ActiveSupport.on_load(:i18n) do
I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
end
+
+YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet"
+YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase"
+YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash"
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 07f7303f8d..8b4a48a38c 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -988,6 +988,12 @@ module ActiveRecord
load_target == other
end
+ ##
+ # :method: to_ary
+ #
+ # :call-seq:
+ # to_ary()
+ #
# Returns a new array of objects from the collection. If the collection
# hasn't been loaded, it fetches the records from the database.
#
@@ -1021,10 +1027,6 @@ module ActiveRecord
# # #<Pet id: 5, name: "Brain", person_id: 1>,
# # #<Pet id: 6, name: "Boss", person_id: 1>
# # ]
- def to_ary
- load_target.dup
- end
- alias_method :to_a, :to_ary
def records # :nodoc:
load_target
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index e1754d4a19..e1087be9b3 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -44,16 +44,8 @@ module ActiveRecord
extend ActiveSupport::Autoload
eager_autoload do
- autoload :Association, "active_record/associations/preloader/association"
- autoload :SingularAssociation, "active_record/associations/preloader/singular_association"
- autoload :CollectionAssociation, "active_record/associations/preloader/collection_association"
- autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
-
- autoload :HasMany, "active_record/associations/preloader/has_many"
- autoload :HasManyThrough, "active_record/associations/preloader/has_many_through"
- autoload :HasOne, "active_record/associations/preloader/has_one"
- autoload :HasOneThrough, "active_record/associations/preloader/has_one_through"
- autoload :BelongsTo, "active_record/associations/preloader/belongs_to"
+ autoload :Association, "active_record/associations/preloader/association"
+ autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
end
# Eager loads the named associations for the given Active Record record(s).
@@ -166,8 +158,6 @@ module ActiveRecord
end
class AlreadyLoaded # :nodoc:
- attr_reader :owners, :reflection
-
def initialize(klass, owners, reflection, preload_scope)
@owners = owners
@reflection = reflection
@@ -178,11 +168,13 @@ module ActiveRecord
def preloaded_records
owners.flat_map { |owner| owner.association(reflection.name).target }
end
+
+ protected
+ attr_reader :owners, :reflection
end
# Returns a class containing the logic needed to load preload the data
- # and attach it to a relation. For example +Preloader::Association+ or
- # +Preloader::HasManyThrough+. The class returned implements a `run` method
+ # and attach it to a relation. The class returned implements a `run` method
# that accepts a preloader.
def preloader_for(reflection, owners)
if owners.first.association(reflection.name).loaded?
@@ -190,13 +182,10 @@ module ActiveRecord
end
reflection.check_preloadable!
- case reflection.macro
- when :has_many
- reflection.options[:through] ? HasManyThrough : HasMany
- when :has_one
- reflection.options[:through] ? HasOneThrough : HasOne
- when :belongs_to
- BelongsTo
+ if reflection.options[:through]
+ ThroughAssociation
+ else
+ Association
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index e77761692d..735da152b7 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -4,7 +4,6 @@ module ActiveRecord
module Associations
class Preloader
class Association #:nodoc:
- attr_reader :owners, :reflection, :preload_scope, :model, :klass
attr_reader :preloaded_records
def initialize(klass, owners, reflection, preload_scope)
@@ -28,6 +27,9 @@ module ActiveRecord
end
end
+ protected
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
+
private
# The name of the key on the associated records
def association_key_name
@@ -40,7 +42,13 @@ module ActiveRecord
end
def associate_records_to_owner(owner, records)
- raise NotImplementedError
+ association = owner.association(reflection.name)
+ if reflection.collection?
+ association.loaded!
+ association.target.concat(records)
+ else
+ association.target = records.first
+ end
end
def owner_keys
diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb
deleted file mode 100644
index a8e3340b23..0000000000
--- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class BelongsTo < SingularAssociation #:nodoc:
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb
deleted file mode 100644
index fc2029f54a..0000000000
--- a/activerecord/lib/active_record/associations/preloader/collection_association.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class CollectionAssociation < Association #:nodoc:
- private
- def associate_records_to_owner(owner, records)
- association = owner.association(reflection.name)
- association.loaded!
- association.target.concat(records)
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many.rb b/activerecord/lib/active_record/associations/preloader/has_many.rb
deleted file mode 100644
index 72f55bc43f..0000000000
--- a/activerecord/lib/active_record/associations/preloader/has_many.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class HasMany < CollectionAssociation #:nodoc:
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
deleted file mode 100644
index 3e17d07a33..0000000000
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class HasManyThrough < CollectionAssociation #:nodoc:
- include ThroughAssociation
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb
deleted file mode 100644
index e339b65fb5..0000000000
--- a/activerecord/lib/active_record/associations/preloader/has_one.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class HasOne < SingularAssociation #:nodoc:
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/has_one_through.rb b/activerecord/lib/active_record/associations/preloader/has_one_through.rb
deleted file mode 100644
index 17734d0257..0000000000
--- a/activerecord/lib/active_record/associations/preloader/has_one_through.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class HasOneThrough < SingularAssociation #:nodoc:
- include ThroughAssociation
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb
deleted file mode 100644
index 30a92411e3..0000000000
--- a/activerecord/lib/active_record/associations/preloader/singular_association.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class SingularAssociation < Association #:nodoc:
- private
- def associate_records_to_owner(owner, records)
- association = owner.association(reflection.name)
- association.target = records.first
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index b1813ba66b..a6b7ab80a2 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -3,15 +3,7 @@
module ActiveRecord
module Associations
class Preloader
- module ThroughAssociation #:nodoc:
- def through_reflection
- reflection.through_reflection
- end
-
- def source_reflection
- reflection.source_reflection
- end
-
+ class ThroughAssociation < Association # :nodoc:
def run(preloader)
already_loaded = owners.first.association(through_reflection.name).loaded?
through_scope = through_scope()
@@ -48,6 +40,13 @@ module ActiveRecord
end
private
+ def through_reflection
+ reflection.through_reflection
+ end
+
+ def source_reflection
+ reflection.source_reflection
+ end
def preload_index
@preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 23d2aef214..64f81ca582 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -167,6 +167,46 @@ module ActiveRecord
end
end
+ # Regexp whitelist. Matches the following:
+ # "#{table_name}.#{column_name}"
+ # "#{column_name}"
+ COLUMN_NAME_WHITELIST = /\A(?:\w+\.)?\w+\z/i
+
+ # Regexp whitelist. Matches the following:
+ # "#{table_name}.#{column_name}"
+ # "#{table_name}.#{column_name} #{direction}"
+ # "#{column_name}"
+ # "#{column_name} #{direction}"
+ COLUMN_NAME_ORDER_WHITELIST = /\A(?:\w+\.)?\w+(?:\s+asc|\s+desc)?\z/i
+
+ def enforce_raw_sql_whitelist(args, whitelist: COLUMN_NAME_WHITELIST) # :nodoc:
+ unexpected = args.reject do |arg|
+ arg.kind_of?(Arel::Node) ||
+ arg.is_a?(Arel::Nodes::SqlLiteral) ||
+ arg.is_a?(Arel::Attributes::Attribute) ||
+ arg.to_s.split(/\s*,\s*/).all? { |part| whitelist.match?(part) }
+ end
+
+ return if unexpected.none?
+
+ if allow_unsafe_raw_sql == :deprecated
+ ActiveSupport::Deprecation.warn(
+ "Dangerous query method (method whose arguments are used as raw " \
+ "SQL) called with non-attribute argument(s): " \
+ "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
+ "arguments will be disallowed in Rails 6.0. This method should " \
+ "not be called with user-provided values, such as request " \
+ "parameters or model attributes. Known-safe values can be passed " \
+ "by wrapping them in Arel.sql()."
+ )
+ else
+ raise(ActiveRecord::UnknownAttributeReference,
+ "Query method called with non-attribute argument(s): " +
+ unexpected.map(&:inspect).join(", ")
+ )
+ end
+ end
+
# Returns true if the given attribute exists, otherwise false.
#
# class Person < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 79110d04f4..3de6fe566d 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "active_support/core_ext/module/attribute_accessors"
-require "active_record/attribute_mutation_tracker"
module ActiveRecord
module AttributeMethods
@@ -33,65 +32,13 @@ module ActiveRecord
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@mutations_before_last_save = nil
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
@mutations_from_database = nil
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
end
- def initialize_dup(other) # :nodoc:
- super
- @attributes = self.class._default_attributes.map do |attr|
- attr.with_value_from_user(@attributes.fetch_value(attr.name))
- end
- @mutations_from_database = nil
- end
-
- def changes_applied # :nodoc:
- @mutations_before_last_save = mutations_from_database
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
- forget_attribute_assignments
- @mutations_from_database = nil
- end
-
- def clear_changes_information # :nodoc:
- @mutations_before_last_save = nil
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
- forget_attribute_assignments
- @mutations_from_database = nil
- end
-
- def clear_attribute_changes(attr_names) # :nodoc:
- super
- attr_names.each do |attr_name|
- clear_attribute_change(attr_name)
- end
- end
-
- def changed_attributes # :nodoc:
- # This should only be set by methods which will call changed_attributes
- # multiple times when it is known that the computed value cannot change.
- if defined?(@cached_changed_attributes)
- @cached_changed_attributes
- else
- super.reverse_merge(mutations_from_database.changed_values).freeze
- end
- end
-
- def changes # :nodoc:
- cache_changed_attributes do
- super
- end
- end
-
- def previous_changes # :nodoc:
- mutations_before_last_save.changes
- end
-
- def attribute_changed_in_place?(attr_name) # :nodoc:
- mutations_from_database.changed_in_place?(attr_name)
- end
-
# Did this attribute change when we last saved? This method can be invoked
# as +saved_change_to_name?+ instead of <tt>saved_change_to_attribute?("name")</tt>.
# Behaves similarly to +attribute_changed?+. This method is useful in
@@ -182,26 +129,6 @@ module ActiveRecord
result
end
- def mutations_from_database
- unless defined?(@mutations_from_database)
- @mutations_from_database = nil
- end
- @mutations_from_database ||= AttributeMutationTracker.new(@attributes)
- end
-
- def changes_include?(attr_name)
- super || mutations_from_database.changed?(attr_name)
- end
-
- def clear_attribute_change(attr_name)
- mutations_from_database.forget_change(attr_name)
- end
-
- def attribute_will_change!(attr_name)
- super
- mutations_from_database.force_change(attr_name)
- end
-
def _update_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
@@ -213,25 +140,6 @@ module ActiveRecord
def keys_for_partial_write
changed_attribute_names_to_save & self.class.column_names
end
-
- def forget_attribute_assignments
- @attributes = @attributes.map(&:forgetting_assignment)
- end
-
- def mutations_before_last_save
- @mutations_before_last_save ||= NullMutationTracker.instance
- end
-
- def cache_changed_attributes
- @cached_changed_attributes = changed_attributes
- yield
- ensure
- clear_changed_attributes_cache
- end
-
- def clear_changed_attributes_cache
- remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
- end
end
end
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 9224d58928..0b7c9398a8 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "active_record/attribute/user_provided_default"
+require "active_model/attribute/user_provided_default"
module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
@@ -250,14 +250,14 @@ module ActiveRecord
if value == NO_DEFAULT_PROVIDED
default_attribute = _default_attributes[name].with_type(type)
elsif from_user
- default_attribute = Attribute::UserProvidedDefault.new(
+ default_attribute = ActiveModel::Attribute::UserProvidedDefault.new(
name,
value,
type,
_default_attributes.fetch(name.to_s) { nil },
)
else
- default_attribute = Attribute.from_database(name, value, type)
+ default_attribute = ActiveModel::Attribute.from_database(name, value, type)
end
_default_attributes[name] = default_attribute
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index e3aab8dad8..345983a655 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-require "active_record/type"
require "active_record/connection_adapters/determine_if_preparable_visitor"
require "active_record/connection_adapters/schema_cache"
require "active_record/connection_adapters/sql_type_metadata"
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 bfec6fb784..ca651ef390 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -635,6 +635,7 @@ module ActiveRecord
ER_CANNOT_ADD_FOREIGN = 1215
ER_CANNOT_CREATE_TABLE = 1005
ER_LOCK_WAIT_TIMEOUT = 1205
+ ER_QUERY_TIMEOUT = 3024
def translate_exception(exception, message)
case error_number(exception)
@@ -660,6 +661,8 @@ module ActiveRecord
Deadlocked.new(message)
when ER_LOCK_WAIT_TIMEOUT
TransactionTimeout.new(message)
+ when ER_QUERY_TIMEOUT
+ StatementTimeout.new(message)
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2c3c1df2a9..5ce6765dd8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -391,6 +391,8 @@ module ActiveRecord
UNIQUE_VIOLATION = "23505"
SERIALIZATION_FAILURE = "40001"
DEADLOCK_DETECTED = "40P01"
+ LOCK_NOT_AVAILABLE = "55P03"
+ QUERY_CANCELED = "57014"
def translate_exception(exception, message)
return exception unless exception.respond_to?(:result)
@@ -410,6 +412,10 @@ module ActiveRecord
SerializationFailure.new(message)
when DEADLOCK_DETECTED
Deadlocked.new(message)
+ when LOCK_NOT_AVAILABLE
+ TransactionTimeout.new(message)
+ when QUERY_CANCELED
+ StatementTimeout.new(message)
else
super
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 0f7a503c90..b97b14644e 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -76,6 +76,14 @@ module ActiveRecord
# scope being ignored is error-worthy, rather than a warning.
mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
+ # :singleton-method:
+ # Specify the behavior for unsafe raw query methods. Values are as follows
+ # deprecated - Warnings are logged when unsafe raw SQL is passed to
+ # query methods.
+ # disabled - Unsafe raw SQL passed to query methods results in
+ # UnknownAttributeReference exception.
+ mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated
+
##
# :singleton-method:
# Specify whether or not to use timestamps for migration versions
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 9ef3316393..7382879fce 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -335,8 +335,36 @@ module ActiveRecord
class IrreversibleOrderError < ActiveRecordError
end
- # TransactionTimeout will be raised when lock wait timeout expires.
- # Wait time value is set by innodb_lock_wait_timeout.
+ # TransactionTimeout will be raised when lock wait timeout exceeded.
class TransactionTimeout < StatementInvalid
end
+
+ # StatementTimeout will be raised when statement timeout exceeded.
+ class StatementTimeout < StatementInvalid
+ end
+
+ # UnknownAttributeReference is raised when an unknown and potentially unsafe
+ # value is passed to a query method when allow_unsafe_raw_sql is set to
+ # :disabled. For example, passing a non column name value to a relation's
+ # #order method might cause this exception.
+ #
+ # When working around this exception, caution should be taken to avoid SQL
+ # injection vulnerabilities when passing user-provided values to query
+ # methods. Known-safe values can be passed to query methods by wrapping them
+ # in Arel.sql.
+ #
+ # For example, with allow_unsafe_raw_sql set to :disabled, the following
+ # code would raise this exception:
+ #
+ # Post.order("length(title)").first
+ #
+ # The desired result can be accomplished by wrapping the known-safe string
+ # in Arel.sql:
+ #
+ # Post.order(Arel.sql("length(title)")).first
+ #
+ # Again, such a workaround should *not* be used when passing user-provided
+ # values, such as request parameters or model attributes to query methods.
+ class UnknownAttributeReference < ActiveRecordError
+ end
end
diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb
index 23644aab8f..ffa095dd94 100644
--- a/activerecord/lib/active_record/legacy_yaml_adapter.rb
+++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb
@@ -8,7 +8,7 @@ module ActiveRecord
case coder["active_record_yaml_version"]
when 1, 2 then coder
else
- if coder["attributes"].is_a?(AttributeSet)
+ if coder["attributes"].is_a?(ActiveModel::AttributeSet)
Rails420.convert(klass, coder)
else
Rails41.convert(klass, coder)
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index d12a979a7f..67c8c9fc08 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -581,7 +581,8 @@ module ActiveRecord
def load_schema_if_pending!
if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations?
# Roundtrip to Rake to allow plugins to hook into database initialization.
- FileUtils.cd Rails.root do
+ root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
+ FileUtils.cd(root) do
current_config = Base.connection_config
Base.clear_all_connections!
system("bin/rails db:test:prepare")
@@ -733,6 +734,24 @@ module ActiveRecord
execute_block { yield helper }
end
+ # Used to specify an operation that is only run when migrating up
+ # (for example, populating a new column with its initial values).
+ #
+ # In the following example, the new column `published` will be given
+ # the value `true` for all existing records.
+ #
+ # class AddPublishedToPosts < ActiveRecord::Migration[5.3]
+ # def change
+ # add_column :posts, :published, :boolean, default: false
+ # up_only do
+ # execute "update posts set published = 'true'"
+ # end
+ # end
+ # end
+ def up_only
+ execute_block { yield } unless reverting?
+ end
+
# Runs the given migration classes.
# Last argument can specify options:
# - :direction (default is :up)
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index bed9400f51..12ee4a4137 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -323,7 +323,7 @@ module ActiveRecord
end
def attributes_builder # :nodoc:
- @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name|
+ @attributes_builder ||= ActiveModel::AttributeSet::Builder.new(attribute_types, primary_key) do |name|
unless columns_hash.key?(name)
_default_attributes[name].dup
end
@@ -346,7 +346,7 @@ module ActiveRecord
end
def yaml_encoder # :nodoc:
- @yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types)
+ @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
end
# Returns the type of the attribute with the given name, after applying
@@ -376,7 +376,7 @@ module ActiveRecord
end
def _default_attributes # :nodoc:
- @default_attributes ||= AttributeSet.new({})
+ @default_attributes ||= ActiveModel::AttributeSet.new({})
end
# Returns an array of column names as strings.
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 435c81c153..fa20bce3a9 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -63,6 +63,18 @@ module ActiveRecord
# member.update params[:member]
# member.avatar.icon # => 'sad'
#
+ # If you want to update the current avatar without providing the id, you must add <tt>:update_only</tt> option.
+ #
+ # class Member < ActiveRecord::Base
+ # has_one :avatar
+ # accepts_nested_attributes_for :avatar, update_only: true
+ # end
+ #
+ # params = { member: { avatar_attributes: { icon: 'sad' } } }
+ # member.update params[:member]
+ # member.avatar.id # => 2
+ # member.avatar.icon # => 'sad'
+ #
# By default you will only be able to set and update attributes on the
# associated model. If you want to destroy the associated model through the
# attributes hash, you have to enable it first using the
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 3bca2982e0..fce3e1c5cf 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -4,16 +4,16 @@ require "active_record"
db_namespace = namespace :db do
desc "Set the environment value for the database"
- task "environment:set" => [:environment, :load_config] do
+ task "environment:set" => :load_config do
ActiveRecord::InternalMetadata.create_table
ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
- task check_protected_environments: [:environment, :load_config] do
+ task check_protected_environments: :load_config do
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
end
- task :load_config do
+ task load_config: :environment do
ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
end
@@ -56,7 +56,7 @@ db_namespace = namespace :db do
end
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
- task migrate: [:environment, :load_config] do
+ task migrate: :load_config do
ActiveRecord::Tasks::DatabaseTasks.migrate
db_namespace["_dump"].invoke
end
@@ -78,7 +78,7 @@ db_namespace = namespace :db do
namespace :migrate do
# desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
- task redo: [:environment, :load_config] do
+ task redo: :load_config do
raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
if ENV["VERSION"]
@@ -94,7 +94,7 @@ db_namespace = namespace :db do
task reset: ["db:drop", "db:create", "db:migrate"]
# desc 'Runs the "up" for a given migration VERSION.'
- task up: [:environment, :load_config] do
+ task up: :load_config do
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
ActiveRecord::Tasks::DatabaseTasks.check_target_version
@@ -108,7 +108,7 @@ db_namespace = namespace :db do
end
# desc 'Runs the "down" for a given migration VERSION.'
- task down: [:environment, :load_config] do
+ task down: :load_config do
raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty?
ActiveRecord::Tasks::DatabaseTasks.check_target_version
@@ -122,7 +122,7 @@ db_namespace = namespace :db do
end
desc "Display status of migrations"
- task status: [:environment, :load_config] do
+ task status: :load_config do
unless ActiveRecord::SchemaMigration.table_exists?
abort "Schema migrations table does not exist yet."
end
@@ -140,14 +140,14 @@ db_namespace = namespace :db do
end
desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)."
- task rollback: [:environment, :load_config] do
+ task rollback: :load_config do
step = ENV["STEP"] ? ENV["STEP"].to_i : 1
ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
db_namespace["_dump"].invoke
end
# desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
- task forward: [:environment, :load_config] do
+ task forward: :load_config do
step = ENV["STEP"] ? ENV["STEP"].to_i : 1
ActiveRecord::Migrator.forward(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
db_namespace["_dump"].invoke
@@ -157,12 +157,12 @@ db_namespace = namespace :db do
task reset: [ "db:drop", "db:setup" ]
# desc "Retrieves the charset for the current environment's database"
- task charset: [:environment, :load_config] do
+ task charset: :load_config do
puts ActiveRecord::Tasks::DatabaseTasks.charset_current
end
# desc "Retrieves the collation for the current environment's database"
- task collation: [:environment, :load_config] do
+ task collation: :load_config do
begin
puts ActiveRecord::Tasks::DatabaseTasks.collation_current
rescue NoMethodError
@@ -171,12 +171,12 @@ db_namespace = namespace :db do
end
desc "Retrieves the current schema version number"
- task version: [:environment, :load_config] do
+ task version: :load_config do
puts "Current version: #{ActiveRecord::Migrator.current_version}"
end
# desc "Raises an error if there are pending migrations"
- task abort_if_pending_migrations: [:environment, :load_config] do
+ task abort_if_pending_migrations: :load_config do
pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Tasks::DatabaseTasks.migrations_paths).pending_migrations
if pending_migrations.any?
@@ -199,7 +199,7 @@ db_namespace = namespace :db do
namespace :fixtures do
desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task load: [:environment, :load_config] do
+ task load: :load_config do
require "active_record/fixtures"
base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path
@@ -221,7 +221,7 @@ db_namespace = namespace :db do
end
# desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task identify: [:environment, :load_config] do
+ task identify: :load_config do
require "active_record/fixtures"
label, id = ENV["LABEL"], ENV["ID"]
@@ -247,7 +247,7 @@ db_namespace = namespace :db do
namespace :schema do
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record"
- task dump: [:environment, :load_config] do
+ task dump: :load_config do
require "active_record/schema_dumper"
filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema.rb")
File.open(filename, "w:utf-8") do |file|
@@ -257,7 +257,7 @@ db_namespace = namespace :db do
end
desc "Loads a schema.rb file into the database"
- task load: [:environment, :load_config, :check_protected_environments] do
+ task load: [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV["SCHEMA"])
end
@@ -267,14 +267,14 @@ db_namespace = namespace :db do
namespace :cache do
desc "Creates a db/schema_cache.yml file."
- task dump: [:environment, :load_config] do
+ task dump: :load_config do
conn = ActiveRecord::Base.connection
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename)
end
desc "Clears a db/schema_cache.yml file."
- task clear: [:environment, :load_config] do
+ task clear: :load_config do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
rm_f filename, verbose: false
end
@@ -284,7 +284,7 @@ db_namespace = namespace :db do
namespace :structure do
desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql"
- task dump: [:environment, :load_config] do
+ task dump: :load_config do
filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
@@ -299,7 +299,7 @@ db_namespace = namespace :db do
end
desc "Recreates the databases from the structure.sql file"
- task load: [:environment, :load_config, :check_protected_environments] do
+ task load: [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV["SCHEMA"])
end
@@ -338,12 +338,12 @@ db_namespace = namespace :db do
end
# desc "Empty the test database"
- task purge: %w(environment load_config check_protected_environments) do
+ task purge: %w(load_config check_protected_environments) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"]
end
# desc 'Load the test schema'
- task prepare: %w(environment load_config) do
+ task prepare: :load_config do
unless ActiveRecord::Base.configurations.blank?
db_namespace["test:load"].invoke
end
diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
deleted file mode 100644
index 72c75ddd52..0000000000
--- a/activerecord/lib/active_record/railties/jdbcmysql_error.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-#FIXME Remove if ArJdbcMysql will give.
-module ArJdbcMySQL #:nodoc:
- class Error < StandardError #:nodoc:
- attr_accessor :error_number, :sql_state
-
- def initialize(msg)
- super
- @error_number = nil
- @sql_state = nil
- end
-
- # Mysql gem compatibility
- alias_method :errno, :error_number
- alias_method :error, :message
- end
-end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 7615fb6ee9..e2d2f45503 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -243,9 +243,10 @@ module ActiveRecord
end
# Converts relation objects to Array.
- def to_a
+ def to_ary
records.dup
end
+ alias to_a to_ary
def records # :nodoc:
load
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 11256ab3d9..d49472fc70 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -183,6 +183,7 @@ module ActiveRecord
relation = apply_join_dependency
relation.pluck(*column_names)
else
+ enforce_raw_sql_whitelist(column_names)
relation = spawn
relation.select_values = column_names.map { |cn|
@klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 48af777b69..4863befec8 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -38,7 +38,7 @@ module ActiveRecord
# may vary depending on the klass of a relation, so we create a subclass of Relation
# for each different klass, and the delegations are compiled into that subclass only.
- delegate :to_xml, :encode_with, :length, :each, :uniq, :to_ary, :join,
+ delegate :to_xml, :encode_with, :length, :each, :uniq, :join,
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
:to_sentence, :to_formatted_s, :as_json,
:shuffle, :split, :slice, :index, :rindex, to: :records
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 18566b5662..706fd57704 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -284,7 +284,7 @@ module ActiveRecord
# * Hash - Finds the record that matches these +find+-style conditions
# (such as <tt>{name: 'David'}</tt>).
# * +false+ - Returns always +false+.
- # * No args - Returns +false+ if the table is empty, +true+ otherwise.
+ # * No args - Returns +false+ if the relation is empty, +true+ otherwise.
#
# For more information about specifying conditions as a hash or array,
# see the Conditions section in the introduction to ActiveRecord::Base.
@@ -300,6 +300,7 @@ module ActiveRecord
# Person.exists?(name: 'David')
# Person.exists?(false)
# Person.exists?
+ # Person.where(name: 'Spartacus', rating: 4).exists?
def exists?(conditions = :none)
if Base === conditions
raise ArgumentError, <<-MSG.squish
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index ebc72d28fd..b736b21525 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -112,22 +112,20 @@ module ActiveRecord
if other.klass == relation.klass
relation.joins!(*other.joins_values)
else
- joins_dependency, rest = other.joins_values.partition do |join|
+ alias_tracker = nil
+ joins_dependency = other.joins_values.map do |join|
case join
when Hash, Symbol, Array
- true
+ alias_tracker ||= other.alias_tracker
+ ActiveRecord::Associations::JoinDependency.new(
+ other.klass, other.table, join, alias_tracker
+ )
else
- false
+ join
end
end
- join_dependency = ActiveRecord::Associations::JoinDependency.new(
- other.klass, other.table, joins_dependency, other.alias_tracker
- )
-
- relation.joins! rest
-
- @relation = relation.joins join_dependency
+ relation.joins!(*joins_dependency)
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb
index f51ea4fde0..c8bbfa5051 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb
@@ -4,6 +4,10 @@ module ActiveRecord
class PredicateBuilder
class RelationHandler # :nodoc:
def call(attribute, value)
+ if value.eager_loading?
+ value = value.send(:apply_join_dependency)
+ end
+
if value.select_values.empty?
value = value.select(value.arel_attribute(value.klass.primary_key))
end
diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb
index fad08e2613..3532f28858 100644
--- a/activerecord/lib/active_record/relation/query_attribute.rb
+++ b/activerecord/lib/active_record/relation/query_attribute.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require "active_record/attribute"
+require "active_model/attribute"
module ActiveRecord
class Relation
- class QueryAttribute < Attribute # :nodoc:
+ class QueryAttribute < ActiveModel::Attribute # :nodoc:
def type_cast(value)
value
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 897ff5c8af..749223422f 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -295,6 +295,7 @@ module ActiveRecord
spawn.order!(*args)
end
+ # Same as #order but operates on relation in-place instead of copying.
def order!(*args) # :nodoc:
preprocess_order_args(args)
@@ -316,6 +317,7 @@ module ActiveRecord
spawn.reorder!(*args)
end
+ # Same as #reorder but operates on relation in-place instead of copying.
def reorder!(*args) # :nodoc:
preprocess_order_args(args)
@@ -930,7 +932,7 @@ module ActiveRecord
arel.where(where_clause.ast) unless where_clause.empty?
arel.having(having_clause.ast) unless having_clause.empty?
if limit_value
- limit_attribute = Attribute.with_cast_value(
+ limit_attribute = ActiveModel::Attribute.with_cast_value(
"LIMIT".freeze,
connection.sanitize_limit(limit_value),
Type.default_value,
@@ -938,7 +940,7 @@ module ActiveRecord
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
end
if offset_value
- offset_attribute = Attribute.with_cast_value(
+ offset_attribute = ActiveModel::Attribute.with_cast_value(
"OFFSET".freeze,
offset_value.to_i,
Type.default_value,
@@ -963,6 +965,9 @@ module ActiveRecord
name = from_clause.name
case opts
when Relation
+ if opts.eager_loading?
+ opts = opts.send(:apply_join_dependency)
+ end
name ||= "subquery"
opts.arel.as(name.to_s)
else
@@ -1035,6 +1040,8 @@ module ActiveRecord
def build_select(arel)
if select_values.any?
arel.project(*arel_columns(select_values.uniq))
+ elsif @klass.ignored_columns.any?
+ arel.project(*arel_columns(@klass.column_names))
else
arel.project(table[Arel.star])
end
@@ -1071,7 +1078,7 @@ module ActiveRecord
end
o.split(",").map! do |s|
s.strip!
- s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || s.concat(" DESC")
+ s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
end
else
o
@@ -1080,6 +1087,10 @@ module ActiveRecord
end
def does_not_support_reverse?(order)
+ # Account for String subclasses like Arel::Nodes::SqlLiteral that
+ # override methods like #count.
+ order = String.new(order) unless order.instance_of?(String)
+
# Uses SQL function with multiple arguments.
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
# Uses "nulls first" like construction.
@@ -1113,6 +1124,12 @@ module ActiveRecord
klass.send(:sanitize_sql_for_order, arg)
end
order_args.flatten!
+
+ @klass.enforce_raw_sql_whitelist(
+ order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
+ whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
+ )
+
validate_order_args(order_args)
references = order_args.grep(String)
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 1c3099f55c..21f8bc7cb2 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -63,7 +63,17 @@ module ActiveRecord
# # => "id ASC"
def sanitize_sql_for_order(condition) # :doc:
if condition.is_a?(Array) && condition.first.to_s.include?("?")
- sanitize_sql_array(condition)
+ enforce_raw_sql_whitelist([condition.first],
+ whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
+ )
+
+ # Ensure we aren't dealing with a subclass of String that might
+ # override methods we use (eg. Arel::Nodes::SqlLiteral).
+ if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
+ condition = [String.new(condition.first), *condition[1..-1]]
+ end
+
+ Arel.sql(sanitize_sql_array(condition))
else
condition
end
@@ -110,7 +120,8 @@ module ActiveRecord
def sanitize_sql_hash_for_assignment(attrs, table) # :doc:
c = connection
attrs.map do |attr, value|
- value = type_for_attribute(attr.to_s).serialize(value)
+ type = type_for_attribute(attr.to_s)
+ value = type.serialize(type.cast(value))
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
end.join(", ")
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 2f91884f59..e697fa6def 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -3,8 +3,6 @@
module ActiveRecord
module Tasks # :nodoc:
class MySQLDatabaseTasks # :nodoc:
- ACCESS_DENIED_ERROR = 1045
-
delegate :connection, :establish_connection, to: ActiveRecord::Base
def initialize(configuration)
@@ -21,20 +19,6 @@ module ActiveRecord
else
raise
end
- rescue error_class => error
- if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR
- $stdout.print error.message
- establish_connection root_configuration_without_database
- connection.create_database configuration["database"], creation_options
- if configuration["username"] != "root"
- connection.execute grant_statement.gsub(/\s+/, " ").strip
- end
- establish_connection configuration
- else
- $stderr.puts error.inspect
- $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
- $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration["encoding"]
- end
end
def drop
@@ -99,37 +83,6 @@ module ActiveRecord
end
end
- def error_class
- if configuration["adapter"].include?("jdbc")
- require "active_record/railties/jdbcmysql_error"
- ArJdbcMySQL::Error
- elsif defined?(Mysql2)
- Mysql2::Error
- else
- StandardError
- end
- end
-
- def grant_statement
- <<-SQL
-GRANT ALL PRIVILEGES ON `#{configuration['database']}`.*
- TO '#{configuration['username']}'@'localhost'
-IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
- SQL
- end
-
- def root_configuration_without_database
- configuration_without_database.merge(
- "username" => "root",
- "password" => root_password
- )
- end
-
- def root_password
- $stdout.print "Please provide the root password for your MySQL installation\n>"
- $stdin.gets.strip
- end
-
def prepare_command_options
args = {
"host" => "--host",
diff --git a/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb b/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt
index 60050e0bf8..60050e0bf8 100644
--- a/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb
+++ b/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt
index 5f7201cfe1..5f7201cfe1 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt
index 481c70201b..481c70201b 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt
index 55dc65c8ad..55dc65c8ad 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/module.rb b/activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt
index a3bf1c37b6..a3bf1c37b6 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/module.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 2736f7cf0e..b8e778f0b0 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -1,23 +1,23 @@
# frozen_string_literal: true
require "cases/helper"
-require "models/developer"
-require "models/computer"
+require "models/author"
+require "models/post"
class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
+ explain = Author.where(id: 1).explain
+ assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain
+ assert_match %r(authors |.* const), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
- assert_match %r(audit_logs |.* ALL), explain
+ explain = Author.where(id: 1).includes(:posts).explain
+ assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain
+ assert_match %r(authors |.* const), explain
+ assert_match %(EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`author_id` = 1), explain
+ assert_match %r(posts |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
index 25d9f69a89..4a3a4503de 100644
--- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -60,9 +60,60 @@ module ActiveRecord
end
end
- test "raises TransactionTimeout when mysql raises ER_LOCK_WAIT_TIMEOUT" do
+ test "raises TransactionTimeout when lock wait timeout exceeded" do
assert_raises(ActiveRecord::TransactionTimeout) do
- ActiveRecord::Base.connection.execute("SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = 'Testing error', MYSQL_ERRNO = 1205;")
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET innodb_lock_wait_timeout = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET innodb_lock_wait_timeout = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises StatementTimeout when statement timeout exceeded" do
+ skip unless ActiveRecord::Base.connection.show_variable("max_execution_time")
+ assert_raises(ActiveRecord::StatementTimeout) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET max_execution_time = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET max_execution_time = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 16fec94ede..be525383e9 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -1,22 +1,22 @@
# frozen_string_literal: true
require "cases/helper"
-require "models/developer"
-require "models/computer"
+require "models/author"
+require "models/post"
class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
+ explain = Author.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
assert_match %(QUERY PLAN), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
+ explain = Author.where(id: 1).includes(:posts).explain
assert_match %(QUERY PLAN), explain
- assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
- assert_match %r(EXPLAIN for: SELECT "audit_logs"\.\* FROM "audit_logs" WHERE "audit_logs"\."developer_id" = (?:\$1 \[\["developer_id", 1\]\]|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\$1 \[\["author_id", 1\]\]|1)), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
index f56adf4a5e..4d63bbce59 100644
--- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
@@ -91,6 +91,63 @@ module ActiveRecord
end
end
+ test "raises TransactionTimeout when lock wait timeout exceeded" do
+ skip unless ActiveRecord::Base.connection.postgresql_version >= 90300
+ assert_raises(ActiveRecord::TransactionTimeout) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET lock_timeout = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET lock_timeout = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises StatementTimeout when statement timeout exceeded" do
+ assert_raises(ActiveRecord::StatementTimeout) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET statement_timeout = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET statement_timeout = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
private
def with_warning_suppression
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index 3b081d34e1..b6d2ccdb53 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -1,23 +1,23 @@
# frozen_string_literal: true
require "cases/helper"
-require "models/developer"
-require "models/computer"
+require "models/author"
+require "models/post"
class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ explain = Author.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
+ assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain)
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- assert_match %r(EXPLAIN for: SELECT "audit_logs"\.\* FROM "audit_logs" WHERE "audit_logs"\."developer_id" = (?:\? \[\["developer_id", 1\]\]|1)), explain
- assert_match(/(SCAN )?TABLE audit_logs/, explain)
+ explain = Author.where(id: 1).includes(:posts).explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
+ assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain)
+ assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\? \[\["author_id", 1\]\]|1)), explain
+ assert_match(/(SCAN )?TABLE posts/, explain)
end
end
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index e69cfe5e52..829e12fbc8 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -37,7 +37,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations
authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).to_a
- assert_equal 1, assert_no_queries { authors.size }
+ assert_equal 3, assert_no_queries { authors.size }
assert_equal 10, assert_no_queries { authors[0].comments.size }
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 9afe6a893c..9a042c74db 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -427,7 +427,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name
quoted_posts_id = Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id")
assert_nothing_raised do
- Comment.includes(:post).references(:posts).order(quoted_posts_id)
+ Comment.includes(:post).references(:posts).order(Arel.sql(quoted_posts_id))
end
end
@@ -874,14 +874,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts(:thinking, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: "UPPER(posts.title)", limit: 2, offset: 1
+ order: Arel.sql("UPPER(posts.title)"), limit: 2, offset: 1
).to_a
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: "UPPER(posts.title) DESC", limit: 2, offset: 1
+ order: Arel.sql("UPPER(posts.title) DESC"), limit: 2, offset: 1
).to_a
)
end
@@ -891,14 +891,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts(:thinking, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: ["UPPER(posts.title)", "posts.id"], limit: 2, offset: 1
+ order: [Arel.sql("UPPER(posts.title)"), "posts.id"], limit: 2, offset: 1
).to_a
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: ["UPPER(posts.title) DESC", "posts.id"], limit: 2, offset: 1
+ order: [Arel.sql("UPPER(posts.title) DESC"), "posts.id"], limit: 2, offset: 1
).to_a
)
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 6bd11a5d81..4ca11af791 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -831,7 +831,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_scoped_grouped_having
- assert_equal 1, authors(:david).popular_grouped_posts.length
+ assert_equal 2, authors(:david).popular_grouped_posts.length
assert_equal 0, authors(:mary).popular_grouped_posts.length
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index f0ef522515..0ae88ee6a0 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1476,4 +1476,25 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal(%w(first_name last_name), Developer.ignored_columns)
assert_equal(%w(first_name last_name), SymbolIgnoredDeveloper.ignored_columns)
end
+
+ test "when #reload called, ignored columns' attribute methods are not defined" do
+ developer = Developer.create!(name: "Developer")
+ refute developer.respond_to?(:first_name)
+ refute developer.respond_to?(:first_name=)
+
+ developer.reload
+
+ refute developer.respond_to?(:first_name)
+ refute developer.respond_to?(:first_name=)
+ end
+
+ test "ignored columns not included in SELECT" do
+ query = Developer.all.to_sql
+
+ # ignored column
+ refute query.include?("first_name")
+
+ # regular column
+ assert query.include?("name")
+ end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 66bc14b5ab..55b50e4f84 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -663,14 +663,14 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck_with_selection_clause
- assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT credit_limit").sort
- assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT accounts.credit_limit").sort
- assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT(credit_limit)").sort
+ assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT credit_limit")).sort
+ assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT accounts.credit_limit")).sort
+ assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT(credit_limit)")).sort
# MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless
# an alias is provided. Without the alias, the column cannot be found
# and properly typecast.
- assert_equal [50 + 53 + 55 + 60], Account.pluck("SUM(DISTINCT(credit_limit)) as credit_limit")
+ assert_equal [50 + 53 + 55 + 60], Account.pluck(Arel.sql("SUM(DISTINCT(credit_limit)) as credit_limit"))
end
def test_plucks_with_ids
@@ -772,7 +772,7 @@ class CalculationsTest < ActiveRecord::TestCase
companies = Company.order(:name).limit(3).load
assert_queries 1 do
- assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck("DISTINCT name")
+ assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck(Arel.sql("DISTINCT name"))
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index d8bc917e7f..1268949ba9 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -239,7 +239,7 @@ class FinderTest < ActiveRecord::TestCase
# Ensure +exists?+ runs without an error by excluding order value.
def test_exists_with_order
- assert_equal true, Topic.order("invalid sql here").exists?
+ assert_equal true, Topic.order(Arel.sql("invalid sql here")).exists?
end
def test_exists_with_joins
@@ -652,7 +652,7 @@ class FinderTest < ActiveRecord::TestCase
def test_last_with_irreversible_order
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("coalesce(author_name, title)").last
+ Topic.order(Arel.sql("coalesce(author_name, title)")).last
end
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 60c628511f..e0f40710eb 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -161,6 +161,13 @@ module ActiveRecord
end
end
+ class UpOnlyMigration < SilentMigration
+ def change
+ add_column :horses, :oldie, :boolean, default: false
+ up_only { execute "update horses set oldie = 'true'" }
+ end
+ end
+
setup do
@verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false
end
@@ -378,5 +385,23 @@ module ActiveRecord
"horses_index_named index should not exist"
end
end
+
+ def test_up_only
+ InvertibleMigration.new.migrate(:up)
+ horse1 = Horse.create
+ # populates existing horses with oldie=true but new ones have default false
+ UpOnlyMigration.new.migrate(:up)
+ Horse.reset_column_information
+ horse1.reload
+ horse2 = Horse.create
+
+ assert horse1.oldie? # created before migration
+ assert !horse2.oldie? # created after migration
+
+ UpOnlyMigration.new.migrate(:down) # should be no error
+ connection = ActiveRecord::Base.connection
+ assert !connection.column_exists?(:horses, :oldie)
+ Horse.reset_column_information
+ end
end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 6791d50940..e857180bd1 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -611,14 +611,12 @@ unless in_memory_db?
end
end
- if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
- def test_no_locks_no_wait
- first, second = duel { Person.find 1 }
- assert first.end > second.end
- end
-
- private
+ def test_no_locks_no_wait
+ first, second = duel { Person.find 1 }
+ assert first.end > second.end
+ end
+ private
def duel(zzz = 5)
t0, t1, t2, t3 = nil, nil, nil, nil
@@ -646,6 +644,5 @@ unless in_memory_db?
assert t3 > t2
[t0.to_f..t1.to_f, t2.to_f..t3.to_f]
end
- end
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index fd5985ffe7..a71d8de521 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -274,18 +274,32 @@ module ActiveRecord
assert_equal({ 2 => 1, 4 => 3, 5 => 1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
end
+ def test_relation_merging_keeps_joining_order
+ authors = Author.where(id: 1)
+ posts = Post.joins(:author).merge(authors)
+ comments = Comment.joins(:post).merge(posts)
+ ratings = Rating.joins(:comment).merge(comments)
+
+ assert_equal 3, ratings.count
+ end
+
class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value
def type
:string
end
+ def cast(value)
+ raise value unless value == "value from user"
+ "cast value"
+ end
+
def deserialize(value)
raise value unless value == "type cast for database"
"type cast from database"
end
def serialize(value)
- raise value unless value == "value from user"
+ raise value unless value == "cast value"
"type cast for database"
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 72433d1e8e..50ad1d5b26 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -195,6 +195,18 @@ class RelationTest < ActiveRecord::TestCase
assert_equal(relation.map(&:post_count).sort, subquery.values.sort)
end
+ def test_finding_with_subquery_with_eager_loading_in_from
+ relation = Comment.includes(:post).where("posts.type": "Post")
+ assert_equal relation.to_a, Comment.select("*").from(relation).to_a
+ assert_equal relation.to_a, Comment.select("subquery.*").from(relation).to_a
+ assert_equal relation.to_a, Comment.select("a.*").from(relation, :a).to_a
+ end
+
+ def test_finding_with_subquery_with_eager_loading_in_where
+ relation = Comment.includes(:post).where("posts.type": "Post")
+ assert_equal relation.sort_by(&:id), Comment.where(id: relation).sort_by(&:id)
+ end
+
def test_finding_with_conditions
assert_equal ["David"], Author.where(name: "David").map(&:name)
assert_equal ["Mary"], Author.where(["name = ?", "Mary"]).map(&:name)
@@ -238,7 +250,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_reverse_order_with_function
- topics = Topic.order("length(title)").reverse_order
+ topics = Topic.order(Arel.sql("length(title)")).reverse_order
assert_equal topics(:second).title, topics.first.title
end
@@ -248,24 +260,24 @@ class RelationTest < ActiveRecord::TestCase
end
def test_reverse_order_with_function_other_predicates
- topics = Topic.order("author_name, length(title), id").reverse_order
+ topics = Topic.order(Arel.sql("author_name, length(title), id")).reverse_order
assert_equal topics(:second).title, topics.first.title
- topics = Topic.order("length(author_name), id, length(title)").reverse_order
+ topics = Topic.order(Arel.sql("length(author_name), id, length(title)")).reverse_order
assert_equal topics(:fifth).title, topics.first.title
end
def test_reverse_order_with_multiargument_function
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(author_name, title)").reverse_order
+ Topic.order(Arel.sql("concat(author_name, title)")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(lower(author_name), title)").reverse_order
+ Topic.order(Arel.sql("concat(lower(author_name), title)")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(author_name, lower(title))").reverse_order
+ Topic.order(Arel.sql("concat(author_name, lower(title))")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(lower(author_name), title, length(title)").reverse_order
+ Topic.order(Arel.sql("concat(lower(author_name), title, length(title)")).reverse_order
end
end
@@ -277,10 +289,10 @@ class RelationTest < ActiveRecord::TestCase
def test_reverse_order_with_nulls_first_or_last
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("title NULLS FIRST").reverse_order
+ Topic.order(Arel.sql("title NULLS FIRST")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("title nulls last").reverse_order
+ Topic.order(Arel.sql("title nulls last")).reverse_order
end
end
@@ -373,29 +385,29 @@ class RelationTest < ActiveRecord::TestCase
def test_finding_with_cross_table_order_and_limit
tags = Tag.includes(:taggings).
- order("tags.name asc", "taggings.taggable_id asc", "REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").
+ order("tags.name asc", "taggings.taggable_id asc", Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).
limit(1).to_a
assert_equal 1, tags.length
end
def test_finding_with_complex_order_and_limit
- tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a
+ tags = Tag.includes(:taggings).references(:taggings).order(Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).limit(1).to_a
assert_equal 1, tags.length
end
def test_finding_with_complex_order
- tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a
+ tags = Tag.includes(:taggings).references(:taggings).order(Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).to_a
assert_equal 3, tags.length
end
def test_finding_with_sanitized_order
- query = Tag.order(["field(id, ?)", [1, 3, 2]]).to_sql
+ query = Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]).to_sql
assert_match(/field\(id, 1,3,2\)/, query)
- query = Tag.order(["field(id, ?)", []]).to_sql
+ query = Tag.order([Arel.sql("field(id, ?)"), []]).to_sql
assert_match(/field\(id, NULL\)/, query)
- query = Tag.order(["field(id, ?)", nil]).to_sql
+ query = Tag.order([Arel.sql("field(id, ?)"), nil]).to_sql
assert_match(/field\(id, NULL\)/, query)
end
@@ -927,13 +939,13 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 11, posts.count(:all)
assert_equal 11, posts.count(:id)
- assert_equal 1, posts.where("comments_count > 1").count
- assert_equal 9, posts.where(comments_count: 0).count
+ assert_equal 3, posts.where("comments_count > 1").count
+ assert_equal 6, posts.where(comments_count: 0).count
end
def test_count_with_block
posts = Post.all
- assert_equal 10, posts.count { |p| p.comments_count.even? }
+ assert_equal 8, posts.count { |p| p.comments_count.even? }
end
def test_count_on_association_relation
@@ -950,10 +962,10 @@ class RelationTest < ActiveRecord::TestCase
def test_count_with_distinct
posts = Post.all
- assert_equal 3, posts.distinct(true).count(:comments_count)
+ assert_equal 4, posts.distinct(true).count(:comments_count)
assert_equal 11, posts.distinct(false).count(:comments_count)
- assert_equal 3, posts.distinct(true).select(:comments_count).count
+ assert_equal 4, posts.distinct(true).select(:comments_count).count
assert_equal 11, posts.distinct(false).select(:comments_count).count
end
@@ -992,7 +1004,7 @@ class RelationTest < ActiveRecord::TestCase
best_posts = posts.where(comments_count: 0)
best_posts.load # force load
- assert_no_queries { assert_equal 9, best_posts.size }
+ assert_no_queries { assert_equal 6, best_posts.size }
end
def test_size_with_limit
@@ -1003,7 +1015,7 @@ class RelationTest < ActiveRecord::TestCase
best_posts = posts.where(comments_count: 0)
best_posts.load # force load
- assert_no_queries { assert_equal 9, best_posts.size }
+ assert_no_queries { assert_equal 6, best_posts.size }
end
def test_size_with_zero_limit
@@ -1026,7 +1038,7 @@ class RelationTest < ActiveRecord::TestCase
def test_count_complex_chained_relations
posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0")
- expected = { 1 => 2 }
+ expected = { 1 => 4, 2 => 1 }
assert_equal expected, posts.count
end
@@ -1567,7 +1579,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body")
assert_equal ["comments"], scope.references_values
- scope = Post.order("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
+ scope = Post.order(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
if current_adapter?(:OracleAdapter)
assert_equal ["COMMENTS"], scope.references_values
else
@@ -1584,7 +1596,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body asc")
assert_equal ["comments"], scope.references_values
- scope = Post.order("foo(comments.body)")
+ scope = Post.order(Arel.sql("foo(comments.body)"))
assert_equal [], scope.references_values
end
@@ -1592,7 +1604,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body")
assert_equal %w(comments), scope.references_values
- scope = Post.reorder("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
+ scope = Post.reorder(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
if current_adapter?(:OracleAdapter)
assert_equal ["COMMENTS"], scope.references_values
else
@@ -1609,7 +1621,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body asc")
assert_equal %w(comments), scope.references_values
- scope = Post.reorder("foo(comments.body)")
+ scope = Post.reorder(Arel.sql("foo(comments.body)"))
assert_equal [], scope.references_values
end
@@ -1781,7 +1793,7 @@ class RelationTest < ActiveRecord::TestCase
end
test "arel_attribute respects a custom table" do
- assert_equal [posts(:welcome)], custom_post_relation.ranked_by_comments.limit_by(1).to_a
+ assert_equal [posts(:sti_comments)], custom_post_relation.ranked_by_comments.limit_by(1).to_a
end
test "alias_tracker respects a custom table" do
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 716ca29eda..fdfeabaa3b 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -120,49 +120,49 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscope_with_where_attributes
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(name: "David").unscope(where: :name).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
expected_2 = Developer.order("salary DESC").collect(&:name)
received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({ where: :name }, :select).collect(&:name)
- assert_equal expected_2, received_2
+ assert_equal expected_2.sort, received_2.sort
expected_3 = Developer.order("salary DESC").collect(&:name)
received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name)
- assert_equal expected_3, received_3
+ assert_equal expected_3.sort, received_3.sort
expected_4 = Developer.order("salary DESC").collect(&:name)
received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name)
- assert_equal expected_4, received_4
+ assert_equal expected_4.sort, received_4.sort
expected_5 = Developer.order("salary DESC").collect(&:name)
received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name)
- assert_equal expected_5, received_5
+ assert_equal expected_5.sort, received_5.sort
expected_6 = Developer.order("salary DESC").collect(&:name)
received_6 = DeveloperOrderedBySalary.where(Developer.arel_table["name"].eq("David")).unscope(where: :name).collect(&:name)
- assert_equal expected_6, received_6
+ assert_equal expected_6.sort, received_6.sort
expected_7 = Developer.order("salary DESC").collect(&:name)
received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq("David")).unscope(where: :name).collect(&:name)
- assert_equal expected_7, received_7
+ assert_equal expected_7.sort, received_7.sort
end
def test_unscope_comparison_where_clauses
# unscoped for WHERE (`developers`.`id` <= 2)
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name }
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
# unscoped for WHERE (`developers`.`id` < 2)
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name }
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_unscope_multiple_where_clauses
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(name: "Jamis").where(id: 1).unscope(where: [:name, :id]).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_unscope_string_where_clauses_involved
@@ -172,23 +172,23 @@ class DefaultScopingTest < ActiveRecord::TestCase
dev_ordered_relation = DeveloperOrderedBySalary.where(name: "Jamis").where("created_at > ?", 1.year.ago)
received = dev_ordered_relation.unscope(where: [:name]).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_unscope_with_grouping_attributes
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
expected_2 = Developer.order("salary DESC").collect(&:name)
received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name)
- assert_equal expected_2, received_2
+ assert_equal expected_2.sort, received_2.sort
end
def test_unscope_with_limit_in_query
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_order_to_unscope_reordering
@@ -472,7 +472,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
test "a scope can remove the condition from the default scope" do
scope = DeveloperCalledJamis.david2
assert_equal 1, scope.where_clause.ast.children.length
- assert_equal Developer.where(name: "David"), scope
+ assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id)
end
def test_with_abstract_class_where_clause_should_not_be_duplicated
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 98fe24baa0..047153e7cc 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -75,7 +75,7 @@ if current_adapter?(:Mysql2Adapter)
end
end
- class MysqlDBCreateAsRootTest < ActiveRecord::TestCase
+ class MysqlDBCreateWithInvalidPermissionsTest < ActiveRecord::TestCase
def setup
@connection = stub("Connection", create_database: true)
@error = Mysql2::Error.new("Invalid permissions")
@@ -86,13 +86,8 @@ if current_adapter?(:Mysql2Adapter)
"password" => "wossname"
}
- $stdin.stubs(:gets).returns("secret\n")
- $stdout.stubs(:print).returns(nil)
- @error.stubs(:errno).returns(1045)
ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).
- raises(@error).
- then.returns(true)
+ ActiveRecord::Base.stubs(:establish_connection).raises(@error)
$stdout, @original_stdout = StringIO.new, $stdout
$stderr, @original_stderr = StringIO.new, $stderr
@@ -102,75 +97,11 @@ if current_adapter?(:Mysql2Adapter)
$stdout, $stderr = @original_stdout, @original_stderr
end
- def test_root_password_is_requested
- assert_permissions_granted_for("pat")
- $stdin.expects(:gets).returns("secret\n")
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_connection_established_as_root
- assert_permissions_granted_for("pat")
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "mysql2",
- "database" => nil,
- "username" => "root",
- "password" => "secret"
- )
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_database_created_by_root
- assert_permissions_granted_for("pat")
- @connection.expects(:create_database).
- with("my-app-db", {})
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_grant_privileges_for_normal_user
- assert_permissions_granted_for("pat")
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_do_not_grant_privileges_for_root_user
- @configuration["username"] = "root"
- @configuration["password"] = ""
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- def test_connection_established_as_normal_user
- assert_permissions_granted_for("pat")
- ActiveRecord::Base.expects(:establish_connection).returns do
- ActiveRecord::Base.expects(:establish_connection).with(
- "adapter" => "mysql2",
- "database" => "my-app-db",
- "username" => "pat",
- "password" => "secret"
- )
-
- raise @error
+ def test_raises_error
+ assert_raises(Mysql2::Error) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
-
- def test_sends_output_to_stderr_when_other_errors
- @error.stubs(:errno).returns(42)
-
- $stderr.expects(:puts).at_least_once.returns(nil)
-
- ActiveRecord::Tasks::DatabaseTasks.create @configuration
- end
-
- private
-
- def assert_permissions_granted_for(db_user)
- db_name = @configuration["database"]
- db_password = @configuration["password"]
- @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON `#{db_name}`.* TO '#{db_user}'@'localhost' IDENTIFIED BY '#{db_password}' WITH GRANT OPTION;")
- end
end
class MySQLDBDropTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb
index b1ebccdcc3..eaafd13360 100644
--- a/activerecord/test/cases/transaction_isolation_test.rb
+++ b/activerecord/test/cases/transaction_isolation_test.rb
@@ -15,9 +15,7 @@ unless ActiveRecord::Base.connection.supports_transaction_isolation?
end
end
end
-end
-
-if ActiveRecord::Base.connection.supports_transaction_isolation?
+else
class TransactionIsolationTest < ActiveRecord::TestCase
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 7fd125ab74..5c8ae4d3cb 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -954,27 +954,25 @@ class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase
end
end if Topic.connection.supports_savepoints?
-if current_adapter?(:PostgreSQLAdapter)
+if ActiveRecord::Base.connection.supports_transaction_isolation?
class ConcurrentTransactionTest < TransactionTest
# This will cause transactions to overlap and fail unless they are performed on
# separate database connections.
- unless in_memory_db?
- def test_transaction_per_thread
- threads = 3.times.map do
- Thread.new do
- Topic.transaction do
- topic = Topic.find(1)
- topic.approved = !topic.approved?
- assert topic.save!
- topic.approved = !topic.approved?
- assert topic.save!
- end
- Topic.connection.close
+ def test_transaction_per_thread
+ threads = 3.times.map do
+ Thread.new do
+ Topic.transaction do
+ topic = Topic.find(1)
+ topic.approved = !topic.approved?
+ assert topic.save!
+ topic.approved = !topic.approved?
+ assert topic.save!
end
+ Topic.connection.close
end
-
- threads.each(&:join)
end
+
+ threads.each(&:join)
end
# Test for dirty reads among simultaneous transactions.
diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb
new file mode 100644
index 0000000000..72d4997d0b
--- /dev/null
+++ b/activerecord/test/cases/unsafe_raw_sql_test.rb
@@ -0,0 +1,299 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+require "models/comment"
+
+class UnsafeRawSqlTest < ActiveRecord::TestCase
+ fixtures :posts, :comments
+
+ test "order: allows string column name" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("title").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows symbol column name" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:title).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:title).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows downcase symbol direction" do
+ ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: :asc).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :asc).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows upcase symbol direction" do
+ ids_expected = Post.order(Arel.sql("title") => Arel.sql("ASC")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: :ASC).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :ASC).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows string direction" do
+ ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: "asc").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: "asc").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows multiple columns" do
+ ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:author_id, :title).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, :title).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows mixed" do
+ ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title") => Arel.sql("asc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:author_id, title: :asc).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, title: :asc).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows table and column name" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("posts.title").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows column name and direction in string" do
+ ids_expected = Post.order(Arel.sql("title desc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("title desc").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title desc").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows table name, column name and direction in string" do
+ ids_expected = Post.order(Arel.sql("title desc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title desc").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("posts.title desc").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: disallows invalid column name" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order("len(title) asc").pluck(:id)
+ end
+ end
+ end
+
+ test "order: disallows invalid direction" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ArgumentError) do
+ Post.order(title: :foo).pluck(:id)
+ end
+ end
+ end
+
+ test "order: disallows invalid column with direction" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order("len(title)" => :asc).pluck(:id)
+ end
+ end
+ end
+
+ test "order: always allows Arel" do
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("length(title)")).pluck(:title) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("length(title)")).pluck(:title) }
+
+ assert_equal ids_depr, ids_disabled
+ end
+
+ test "order: allows Arel.sql with binds" do
+ ids_expected = Post.order(Arel.sql("REPLACE(title, 'misc', 'zzzz'), id")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order([Arel.sql("REPLACE(title, ?, ?), id"), "misc", "zzzz"]).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order([Arel.sql("REPLACE(title, ?, ?), id"), "misc", "zzzz"]).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: disallows invalid bind statement" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order(["REPLACE(title, ?, ?), id", "misc", "zzzz"]).pluck(:id)
+ end
+ end
+ end
+
+ test "order: disallows invalid Array arguments" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order(["author_id", "length(title)"]).pluck(:id)
+ end
+ end
+ end
+
+ test "order: allows valid Array arguments" do
+ ids_expected = Post.order(Arel.sql("author_id, length(title)")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: logs deprecation warning for unrecognized column" do
+ with_unsafe_raw_sql_deprecated do
+ assert_deprecated(/Dangerous query method/) do
+ Post.order("length(title)")
+ end
+ end
+ end
+
+ test "pluck: allows string column name" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("title") }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("title") }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
+ test "pluck: allows symbol column name" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:title) }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title) }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
+ test "pluck: allows multiple column names" do
+ values_expected = Post.pluck(Arel.sql("title"), Arel.sql("id"))
+
+ values_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:title, :id) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title, :id) }
+
+ assert_equal values_expected, values_depr
+ assert_equal values_expected, values_disabled
+ end
+
+ test "pluck: allows column names with includes" do
+ values_expected = Post.includes(:comments).pluck(Arel.sql("title"), Arel.sql("id"))
+
+ values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, :id) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, :id) }
+
+ assert_equal values_expected, values_depr
+ assert_equal values_expected, values_disabled
+ end
+
+ test "pluck: allows auto-generated attributes" do
+ values_expected = Post.pluck(Arel.sql("tags_count"))
+
+ values_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:tags_count) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:tags_count) }
+
+ assert_equal values_expected, values_depr
+ assert_equal values_expected, values_disabled
+ end
+
+ test "pluck: allows table and column names" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("posts.title") }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("posts.title") }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
+ test "pluck: disallows invalid column name" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.pluck("length(title)")
+ end
+ end
+ end
+
+ test "pluck: disallows invalid column name amongst valid names" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.pluck(:title, "length(title)")
+ end
+ end
+ end
+
+ test "pluck: disallows invalid column names with includes" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.includes(:comments).pluck(:title, "length(title)")
+ end
+ end
+ end
+
+ test "pluck: always allows Arel" do
+ values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) }
+
+ assert_equal values_depr, values_disabled
+ end
+
+ test "pluck: logs deprecation warning" do
+ with_unsafe_raw_sql_deprecated do
+ assert_deprecated(/Dangerous query method/) do
+ Post.includes(:comments).pluck(:title, "length(title)")
+ end
+ end
+ end
+
+ def with_unsafe_raw_sql_disabled(&blk)
+ with_config(:disabled, &blk)
+ end
+
+ def with_unsafe_raw_sql_deprecated(&blk)
+ with_config(:deprecated, &blk)
+ end
+
+ def with_config(new_value, &blk)
+ old_value = ActiveRecord::Base.allow_unsafe_raw_sql
+ ActiveRecord::Base.allow_unsafe_raw_sql = new_value
+ blk.call
+ ensure
+ ActiveRecord::Base.allow_unsafe_raw_sql = old_value
+ end
+end
diff --git a/activerecord/test/fixtures/other_posts.yml b/activerecord/test/fixtures/other_posts.yml
index 39ff763547..3e11a33802 100644
--- a/activerecord/test/fixtures/other_posts.yml
+++ b/activerecord/test/fixtures/other_posts.yml
@@ -5,3 +5,4 @@ second_welcome:
author_id: 1
title: Welcome to the another weblog
body: It's really nice today
+ comments_count: 1
diff --git a/activerecord/test/fixtures/posts.yml b/activerecord/test/fixtures/posts.yml
index 86d46f753a..8d7e1e0ae7 100644
--- a/activerecord/test/fixtures/posts.yml
+++ b/activerecord/test/fixtures/posts.yml
@@ -28,6 +28,7 @@ sti_comments:
author_id: 1
title: sti comments
body: hello
+ comments_count: 5
type: Post
sti_post_and_comments:
@@ -35,6 +36,7 @@ sti_post_and_comments:
author_id: 1
title: sti me
body: hello
+ comments_count: 2
type: StiPost
sti_habtm:
@@ -50,6 +52,8 @@ eager_other:
title: eager loading with OR'd conditions
body: hello
type: Post
+ comments_count: 1
+ tags_count: 3
misc_by_bob:
id: 8
@@ -57,6 +61,7 @@ misc_by_bob:
title: misc post by bob
body: hello
type: Post
+ tags_count: 1
misc_by_mary:
id: 9
@@ -64,6 +69,7 @@ misc_by_mary:
title: misc post by mary
body: hello
type: Post
+ tags_count: 1
other_by_bob:
id: 10
@@ -71,6 +77,7 @@ other_by_bob:
title: other post by bob
body: hello
type: Post
+ tags_count: 1
other_by_mary:
id: 11
@@ -78,3 +85,4 @@ other_by_mary:
title: other post by mary
body: hello
type: Post
+ tags_count: 1
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 7f064bf3dd..780a2c17f5 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -319,5 +319,9 @@ class FakeKlass
def arel_attribute(name, table)
table[name]
end
+
+ def enforce_raw_sql_whitelist(*args)
+ # noop
+ end
end
end
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index 99823e14c6..2aa05d665e 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -249,7 +249,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
# You won't ordinarily need to call this method from a Rails application. New blobs are automatically and asynchronously
# analyzed via #analyze_later when they're attached for the first time.
def analyze
- update! metadata: extract_metadata_via_analyzer
+ update! metadata: metadata.merge(extract_metadata_via_analyzer)
end
# Enqueues an ActiveStorage::AnalyzeJob which calls #analyze.
diff --git a/activestorage/lib/active_storage/attached/many.rb b/activestorage/lib/active_storage/attached/many.rb
index 1e0657c33c..0b3400bccf 100644
--- a/activestorage/lib/active_storage/attached/many.rb
+++ b/activestorage/lib/active_storage/attached/many.rb
@@ -13,7 +13,6 @@ module ActiveStorage
end
# Associates one or several attachments with the current record, saving them to the database.
- # Examples:
#
# document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
# document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
@@ -36,6 +35,11 @@ module ActiveStorage
attachments.any?
end
+ # Deletes associated attachments without purging them, leaving their respective blobs in place.
+ def detach
+ attachments.destroy_all if attached?
+ end
+
# Directly purges each associated attachment (i.e. destroys the blobs and
# attachments and deletes the files on the service).
def purge
diff --git a/activestorage/lib/active_storage/attached/one.rb b/activestorage/lib/active_storage/attached/one.rb
index dc19512484..7092f6b109 100644
--- a/activestorage/lib/active_storage/attached/one.rb
+++ b/activestorage/lib/active_storage/attached/one.rb
@@ -14,7 +14,6 @@ module ActiveStorage
end
# Associates a given attachment with the current record, saving it to the database.
- # Examples:
#
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
@@ -39,6 +38,14 @@ module ActiveStorage
attachment.present?
end
+ # Deletes the attachment without purging it, leaving its blob in place.
+ def detach
+ if attached?
+ attachment.destroy
+ write_attachment nil
+ end
+ end
+
# Directly purges the attachment (i.e. destroys the blob and
# attachment and deletes the file on the service).
def purge
@@ -59,16 +66,12 @@ module ActiveStorage
def replace(attachable)
blob.tap do
transaction do
- destroy_attachment
+ detach
write_attachment create_attachment_from(attachable)
end
end.purge_later
end
- def destroy_attachment
- attachment.destroy
- end
-
def create_attachment_from(attachable)
ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable))
end
diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb
index 47f2bd7911..e645d868ce 100644
--- a/activestorage/test/models/attachments_test.rb
+++ b/activestorage/test/models/attachments_test.rb
@@ -88,6 +88,27 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
end
end
+ test "preserve existing metadata when analyzing a newly-attached blob" do
+ blob = create_file_blob(metadata: { foo: "bar" })
+
+ perform_enqueued_jobs do
+ @user.avatar.attach blob
+ end
+
+ assert_equal "bar", blob.reload.metadata[:foo]
+ end
+
+ test "detach blob" do
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+ avatar_blob_id = @user.avatar.blob.id
+ avatar_key = @user.avatar.key
+
+ @user.avatar.detach
+ assert_not @user.avatar.attached?
+ assert ActiveStorage::Blob.exists?(avatar_blob_id)
+ assert ActiveStorage::Blob.service.exist?(avatar_key)
+ end
+
test "purge attached blob" do
@user.avatar.attach create_blob(filename: "funky.jpg")
avatar_key = @user.avatar.key
@@ -193,6 +214,36 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
end
end
+ test "preserve existing metadata when analyzing newly-attached blobs" do
+ blobs = [
+ create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: { foo: "bar" }),
+ create_file_blob(filename: "video.mp4", content_type: "video/mp4", metadata: { foo: "bar" })
+ ]
+
+ perform_enqueued_jobs do
+ @user.highlights.attach(blobs)
+ end
+
+ blobs.each do |blob|
+ assert_equal "bar", blob.reload.metadata[:foo]
+ end
+ end
+
+ test "detach blobs" do
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
+ highlight_blob_ids = @user.highlights.collect { |highlight| highlight.blob.id }
+ highlight_keys = @user.highlights.collect(&:key)
+
+ @user.highlights.detach
+ assert_not @user.highlights.attached?
+
+ assert ActiveStorage::Blob.exists?(highlight_blob_ids.first)
+ assert ActiveStorage::Blob.exists?(highlight_blob_ids.second)
+
+ assert ActiveStorage::Blob.service.exist?(highlight_keys.first)
+ assert ActiveStorage::Blob.service.exist?(highlight_keys.second)
+ end
+
test "purge attached blobs" do
@user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
highlight_keys = @user.highlights.collect(&:key)
diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb
index 38408cdad3..55da781f2a 100644
--- a/activestorage/test/test_helper.rb
+++ b/activestorage/test/test_helper.rb
@@ -45,8 +45,8 @@ class ActiveSupport::TestCase
ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type
end
- def create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg")
- ActiveStorage::Blob.create_after_upload! io: file_fixture(filename).open, filename: filename, content_type: content_type
+ def create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: nil)
+ ActiveStorage::Blob.create_after_upload! io: file_fixture(filename).open, filename: filename, content_type: content_type, metadata: metadata
end
def create_blob_before_direct_upload(filename: "hello.txt", byte_size:, checksum:, content_type: "text/plain")
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 9c27c68919..24c7f362e9 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,66 @@
+* Redis cache store.
+
+ # Defaults to `redis://localhost:6379/0`. Only use for dev/test.
+ config.cache_store = :redis_cache_store
+
+ # Supports all common cache store options (:namespace, :compress,
+ # :compress_threshold, :expires_in, :race_condition_tool) and all
+ # Redis options.
+ cache_password = Rails.application.secrets.redis_cache_password
+ config.cache_store = :redis_cache_store, driver: :hiredis,
+ namespace: 'myapp-cache', compress: true, timeout: 1,
+ url: "redis://:#{cache_password}@myapp-cache-1:6379/0"
+
+ # Supports Redis::Distributed with multiple hosts
+ config.cache_store = :redis_cache_store, driver: :hiredis
+ namespace: 'myapp-cache', compress: true,
+ url: %w[
+ redis://myapp-cache-1:6379/0
+ redis://myapp-cache-1:6380/0
+ redis://myapp-cache-2:6379/0
+ redis://myapp-cache-2:6380/0
+ redis://myapp-cache-3:6379/0
+ redis://myapp-cache-3:6380/0
+ ]
+
+ # Or pass a builder block
+ config.cache_store = :redis_cache_store,
+ namespace: 'myapp-cache', compress: true,
+ redis: -> { Redis.new … }
+
+ Deployment note: Take care to use a *dedicated Redis cache* rather
+ than pointing this at your existing Redis server. It won't cope well
+ with mixed usage patterns and it won't expire cache entries by default.
+
+ Redis cache server setup guide: https://redis.io/topics/lru-cache
+
+ *Jeremy Daer*
+
+* Cache: Enable compression by default for values > 1kB.
+
+ Compression has long been available, but opt-in and at a 16kB threshold.
+ It wasn't enabled by default due to CPU cost. Today it's cheap and typical
+ cache data is eminently compressible, such as HTML or JSON fragments.
+ Compression dramatically reduces Memcached/Redis mem usage, which means
+ the same cache servers can store more data, which means higher hit rates.
+
+ To disable compression, pass `compress: false` to the initializer.
+
+ *Jeremy Daer*
+
+* Allow `Range#include?` on TWZ ranges
+
+ In #11474 we prevented TWZ ranges being iterated over which matched
+ Ruby's handling of Time ranges and as a consequence `include?`
+ stopped working with both Time ranges and TWZ ranges. However in
+ ruby/ruby@b061634 support was added for `include?` to use `cover?`
+ for 'linear' objects. Since we have no way of making Ruby consider
+ TWZ instances as 'linear' we have to override `Range#include?`.
+
+ Fixes #30799.
+
+ *Andrew White*
+
* Fix acronym support in `humanize`
Acronym inflections are stored with lowercase keys in the hash but
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 976104e0c4..395aa5e8f1 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -12,10 +12,11 @@ require "active_support/core_ext/string/inflections"
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
module Cache
- autoload :FileStore, "active_support/cache/file_store"
- autoload :MemoryStore, "active_support/cache/memory_store"
- autoload :MemCacheStore, "active_support/cache/mem_cache_store"
- autoload :NullStore, "active_support/cache/null_store"
+ autoload :FileStore, "active_support/cache/file_store"
+ autoload :MemoryStore, "active_support/cache/memory_store"
+ autoload :MemCacheStore, "active_support/cache/mem_cache_store"
+ autoload :NullStore, "active_support/cache/null_store"
+ autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
# These options mean something to all cache implementations. Individual cache
# implementations may support additional options.
@@ -90,11 +91,16 @@ module ActiveSupport
private
def retrieve_cache_key(key)
case
- when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
- when key.respond_to?(:cache_key) then key.cache_key
- when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
- when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
- else key.to_param
+ when key.respond_to?(:cache_key_with_version)
+ key.cache_key_with_version
+ when key.respond_to?(:cache_key)
+ key.cache_key
+ when key.is_a?(Hash)
+ key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }.to_param
+ when key.respond_to?(:to_a)
+ key.to_a.collect { |element| retrieve_cache_key(element) }.to_param
+ else
+ key.to_param
end.to_s
end
@@ -148,12 +154,11 @@ module ActiveSupport
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
#
- # Caches can also store values in a compressed format to save space and
- # reduce time spent sending data. Since there is overhead, values must be
- # large enough to warrant compression. To turn on compression either pass
- # <tt>compress: true</tt> in the initializer or as an option to +fetch+
- # or +write+. To specify the threshold at which to compress values, set the
- # <tt>:compress_threshold</tt> option. The default threshold is 16K.
+ # Cached data larger than 1kB are compressed by default. To turn off
+ # compression, pass <tt>compress: false</tt> to the initializer or to
+ # individual +fetch+ or +write+ method calls. The 1kB compression
+ # threshold is configurable with the <tt>:compress_threshold</tt> option,
+ # specified in bytes.
class Store
cattr_accessor :logger, instance_writer: true
@@ -212,8 +217,7 @@ module ActiveSupport
# ask whether you should force a cache write. Otherwise, it's clearer to
# just call <tt>Cache#write</tt>.
#
- # Setting <tt>:compress</tt> will store a large cache entry set by the call
- # in a compressed format.
+ # Setting <tt>compress: false</tt> disables compression of the cache entry.
#
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
# All caches support auto-expiring content after a specified number of
@@ -562,34 +566,34 @@ module ActiveSupport
end
end
- # Prefixes a key with the namespace. Namespace and key will be delimited
- # with a colon.
- def normalize_key(key, options)
- key = expanded_key(key)
- namespace = options[:namespace] if options
- prefix = namespace.is_a?(Proc) ? namespace.call : namespace
- key = "#{prefix}:#{key}" if prefix
- key
+ # Expands and namespaces the cache key. May be overridden by
+ # cache stores to do additional normalization.
+ def normalize_key(key, options = nil)
+ namespace_key Cache.expand_cache_key(key), options
end
- # Expands key to be a consistent string value. Invokes +cache_key+ if
- # object responds to +cache_key+. Otherwise, +to_param+ method will be
- # called. If the key is a Hash, then keys will be sorted alphabetically.
- def expanded_key(key)
- return key.cache_key.to_s if key.respond_to?(:cache_key)
+ # Prefix the key with a namespace string:
+ #
+ # namespace_key 'foo', namespace: 'cache'
+ # # => 'cache:foo'
+ #
+ # With a namespace block:
+ #
+ # namespace_key 'foo', namespace: -> { 'cache' }
+ # # => 'cache:foo'
+ def namespace_key(key, options = nil)
+ options = merged_options(options)
+ namespace = options[:namespace]
- case key
- when Array
- if key.size > 1
- key = key.collect { |element| expanded_key(element) }
- else
- key = key.first
- end
- when Hash
- key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
+ if namespace.respond_to?(:call)
+ namespace = namespace.call
end
- key.to_param
+ if namespace
+ "#{namespace}:#{key}"
+ else
+ key
+ end
end
def normalize_version(key, options = nil)
@@ -658,7 +662,7 @@ module ActiveSupport
class Entry # :nodoc:
attr_reader :version
- DEFAULT_COMPRESS_LIMIT = 16.kilobytes
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
# Creates a new cache entry for the specified value. Options supported are
# +:compress+, +:compress_threshold+, and +:expires_in+.
@@ -733,8 +737,8 @@ module ActiveSupport
private
def should_compress?(value, options)
- if value && options[:compress]
- compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
+ if value && options.fetch(:compress, true)
+ compress_threshold = options.fetch(:compress_threshold, DEFAULT_COMPRESS_LIMIT)
serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
return true if serialized_value_size >= compress_threshold
diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb
new file mode 100644
index 0000000000..358de9203d
--- /dev/null
+++ b/activesupport/lib/active_support/cache/redis_cache_store.rb
@@ -0,0 +1,404 @@
+# frozen_string_literal: true
+
+begin
+ gem "redis", ">= 4.0.1"
+ require "redis"
+ require "redis/distributed"
+rescue LoadError
+ warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \"~> 4.0\"`"
+ raise
+end
+
+# Prefer the hiredis driver but don't require it.
+begin
+ require "redis/connection/hiredis"
+rescue LoadError
+end
+
+require "digest/sha2"
+require "active_support/core_ext/marshal"
+
+module ActiveSupport
+ module Cache
+ # Redis cache store.
+ #
+ # Deployment note: Take care to use a *dedicated Redis cache* rather
+ # than pointing this at your existing Redis server. It won't cope well
+ # with mixed usage patterns and it won't expire cache entries by default.
+ #
+ # Redis cache server setup guide: https://redis.io/topics/lru-cache
+ #
+ # * Supports vanilla Redis, hiredis, and Redis::Distributed.
+ # * Supports Memcached-like sharding across Redises with Redis::Distributed.
+ # * Fault tolerant. If the Redis server is unavailable, no exceptions are
+ # raised. Cache fetches are all misses and writes are dropped.
+ # * Local cache. Hot in-memory primary cache within block/middleware scope.
+ # * `read_/write_multi` support for Redis mget/mset. Use Redis::Distributed
+ # 4.0.1+ for distributed mget support.
+ # * `delete_matched` support for Redis KEYS globs.
+ class RedisCacheStore < Store
+ # Keys are truncated with their own SHA2 digest if they exceed 1kB
+ MAX_KEY_BYTESIZE = 1024
+
+ DEFAULT_REDIS_OPTIONS = {
+ connect_timeout: 20,
+ read_timeout: 1,
+ write_timeout: 1,
+ reconnect_attempts: 0,
+ }
+
+ DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) {
+ logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{e.class}: #{e.message}" } if logger
+ }
+
+ DELETE_GLOB_LUA = "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end"
+ private_constant :DELETE_GLOB_LUA
+
+ # Support raw values in the local cache strategy.
+ module LocalCacheWithRaw # :nodoc:
+ private
+ def read_entry(key, options)
+ entry = super
+ if options[:raw] && local_cache && entry
+ entry = deserialize_entry(entry.value)
+ end
+ entry
+ end
+
+ def write_entry(key, entry, options)
+ if options[:raw] && local_cache
+ raw_entry = Entry.new(entry.value.to_s)
+ raw_entry.expires_at = entry.expires_at
+ super(key, raw_entry, options)
+ else
+ super
+ end
+ end
+
+ def write_multi_entries(entries, options)
+ if options[:raw] && local_cache
+ raw_entries = entries.map do |key, entry|
+ raw_entry = Entry.new(entry.value.to_s)
+ raw_entry.expires_at = entry.expires_at
+ end.to_h
+
+ super(raw_entries, options)
+ else
+ super
+ end
+ end
+ end
+
+ prepend Strategy::LocalCache
+ prepend LocalCacheWithRaw
+
+ class << self
+ # Factory method to create a new Redis instance.
+ #
+ # Handles four options: :redis block, :redis instance, single :url
+ # string, and multiple :url strings.
+ #
+ # Option Class Result
+ # :redis Proc -> options[:redis].call
+ # :redis Object -> options[:redis]
+ # :url String -> Redis.new(url: …)
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
+ #
+ def build_redis(redis: nil, url: nil, **redis_options) #:nodoc:
+ urls = Array(url)
+
+ if redis.respond_to?(:call)
+ redis.call
+ elsif redis
+ redis
+ elsif urls.size > 1
+ build_redis_distributed_client urls: urls, **redis_options
+ else
+ build_redis_client url: urls.first, **redis_options
+ end
+ end
+
+ private
+ def build_redis_distributed_client(urls:, **redis_options)
+ ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
+ urls.each { |u| dist.add_node url: u }
+ end
+ end
+
+ def build_redis_client(url:, **redis_options)
+ ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url))
+ end
+ end
+
+ attr_reader :redis_options
+ attr_reader :max_key_bytesize
+
+ # Creates a new Redis cache store.
+ #
+ # Handles three options: block provided to instantiate, single URL
+ # provided, and multiple URLs provided.
+ #
+ # :redis Proc -> options[:redis].call
+ # :url String -> Redis.new(url: …)
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
+ #
+ # No namespace is set by default. Provide one if the Redis cache
+ # server is shared with other apps: `namespace: 'myapp-cache'`.
+ #
+ # Compression is enabled by default with a 1kB threshold, so cached
+ # values larger than 1kB are automatically compressed. Disable by
+ # passing `cache: false` or change the threshold by passing
+ # `compress_threshold: 4.kilobytes`.
+ #
+ # No expiry is set on cache entries by default. Redis is expected to
+ # be configured with an eviction policy that automatically deletes
+ # least-recently or -frequently used keys when it reaches max memory.
+ # See https://redis.io/topics/lru-cache for cache server setup.
+ #
+ # Race condition TTL is not set by default. This can be used to avoid
+ # "thundering herd" cache writes when hot cache entries are expired.
+ # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
+ def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
+ @redis_options = redis_options
+
+ @max_key_bytesize = MAX_KEY_BYTESIZE
+ @error_handler = error_handler
+
+ super namespace: namespace,
+ compress: compress, compress_threshold: compress_threshold,
+ expires_in: expires_in, race_condition_ttl: race_condition_ttl
+ end
+
+ def redis
+ @redis ||= self.class.build_redis(**redis_options)
+ end
+
+ def inspect
+ instance = @redis || @redis_options
+ "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
+ end
+
+ # Cache Store API implementation.
+ #
+ # Read multiple values at once. Returns a hash of requested keys ->
+ # fetched values.
+ def read_multi(*names)
+ if mget_capable?
+ read_multi_mget(*names)
+ else
+ super
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Supports Redis KEYS glob patterns:
+ #
+ # h?llo matches hello, hallo and hxllo
+ # h*llo matches hllo and heeeello
+ # h[ae]llo matches hello and hallo, but not hillo
+ # h[^e]llo matches hallo, hbllo, ... but not hello
+ # h[a-b]llo matches hallo and hbllo
+ #
+ # Use \ to escape special characters if you want to match them verbatim.
+ #
+ # See https://redis.io/commands/KEYS for more.
+ #
+ # Failsafe: Raises errors.
+ def delete_matched(matcher, options = nil)
+ instrument :delete_matched, matcher do
+ case matcher
+ when String
+ redis.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)]
+ else
+ raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
+ end
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Increment a cached value. This method uses the Redis incr atomic
+ # operator and can only be used on values written with the :raw option.
+ # Calling it on a value not stored with :raw will initialize that value
+ # to zero.
+ #
+ # Failsafe: Raises errors.
+ def increment(name, amount = 1, options = nil)
+ instrument :increment, name, amount: amount do
+ redis.incrby normalize_key(name, options), amount
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Decrement a cached value. This method uses the Redis decr atomic
+ # operator and can only be used on values written with the :raw option.
+ # Calling it on a value not stored with :raw will initialize that value
+ # to zero.
+ #
+ # Failsafe: Raises errors.
+ def decrement(name, amount = 1, options = nil)
+ instrument :decrement, name, amount: amount do
+ redis.decrby normalize_key(name, options), amount
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Removes expired entries. Handled natively by Redis least-recently-/
+ # least-frequently-used expiry, so manual cleanup is not supported.
+ def cleanup(options = nil)
+ super
+ end
+
+ # Clear the entire cache on all Redis servers. Safe to use on
+ # shared servers if the cache is namespaced.
+ #
+ # Failsafe: Raises errors.
+ def clear(options = nil)
+ failsafe :clear do
+ if namespace = merged_options(options)[namespace]
+ delete_matched "*", namespace: namespace
+ else
+ redis.flushdb
+ end
+ end
+ end
+
+ def mget_capable? #:nodoc:
+ set_redis_capabilities unless defined? @mget_capable
+ @mget_capable
+ end
+
+ def mset_capable? #:nodoc:
+ set_redis_capabilities unless defined? @mset_capable
+ @mset_capable
+ end
+
+ private
+ def set_redis_capabilities
+ case redis
+ when Redis::Distributed
+ @mget_capable = true
+ @mset_capable = false
+ else
+ @mget_capable = true
+ @mset_capable = true
+ end
+ end
+
+ # Store provider interface:
+ # Read an entry from the cache.
+ def read_entry(key, options = nil)
+ failsafe :read_entry do
+ deserialize_entry redis.get(key)
+ end
+ end
+
+ def read_multi_mget(*names)
+ options = names.extract_options!
+ options = merged_options(options)
+
+ keys_to_names = names.map { |name| [ normalize_key(name, options), name ] }.to_h
+ values = redis.mget(*keys_to_names.keys)
+
+ keys_to_names.zip(values).each_with_object({}) do |((key, name), value), results|
+ if value
+ entry = deserialize_entry(value)
+ unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
+ results[name] = entry.value
+ end
+ end
+ end
+ end
+
+ # Write an entry to the cache.
+ #
+ # Requires Redis 2.6.12+ for extended SET options.
+ def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options)
+ value = raw ? entry.value.to_s : serialize_entry(entry)
+
+ # If race condition TTL is in use, ensure that cache entries
+ # stick around a bit longer after they would have expired
+ # so we can purposefully serve stale entries.
+ if race_condition_ttl && expires_in && expires_in > 0 && !raw
+ expires_in += 5.minutes
+ end
+
+ failsafe :write_entry do
+ if unless_exist || expires_in
+ modifiers = {}
+ modifiers[:nx] = unless_exist
+ modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
+
+ redis.set key, value, modifiers
+ else
+ redis.set key, value
+ end
+ end
+ end
+
+ # Delete an entry from the cache.
+ def delete_entry(key, options)
+ failsafe :delete_entry, returning: false do
+ redis.del key
+ end
+ end
+
+ # Nonstandard store provider API to write multiple values at once.
+ def write_multi_entries(entries, expires_in: nil, **options)
+ if entries.any?
+ if mset_capable? && expires_in.nil?
+ failsafe :write_multi_entries do
+ redis.mapped_mset(entries)
+ end
+ else
+ super
+ end
+ end
+ end
+
+ # Truncate keys that exceed 1kB.
+ def normalize_key(key, options)
+ truncate_key super
+ end
+
+ def truncate_key(key)
+ if key.bytesize > max_key_bytesize
+ suffix = ":sha2:#{Digest::SHA2.hexdigest(key)}"
+ truncate_at = max_key_bytesize - suffix.bytesize
+ "#{key.byteslice(0, truncate_at)}#{suffix}"
+ else
+ key
+ end
+ end
+
+ def deserialize_entry(raw_value)
+ if raw_value
+ entry = Marshal.load(raw_value) rescue raw_value
+ entry.is_a?(Entry) ? entry : Entry.new(entry)
+ end
+ end
+
+ def serialize_entry(entry)
+ Marshal.dump(entry)
+ end
+
+ def failsafe(method, returning: nil)
+ yield
+ rescue ::Redis::BaseConnectionError => e
+ handle_exception exception: e, method: method, returning: returning
+ returning
+ end
+
+ def handle_exception(exception:, method:, returning:)
+ if @error_handler
+ @error_handler.(method: method, exception: exception, returning: returning)
+ end
+ rescue => failsafe
+ warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}"
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb
index 51ae0ddd21..4074e91d17 100644
--- a/activesupport/lib/active_support/core_ext/range.rb
+++ b/activesupport/lib/active_support/core_ext/range.rb
@@ -2,5 +2,6 @@
require "active_support/core_ext/range/conversions"
require "active_support/core_ext/range/include_range"
+require "active_support/core_ext/range/include_time_with_zone"
require "active_support/core_ext/range/overlaps"
require "active_support/core_ext/range/each"
diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb
index cdff6393d7..2f22cd0e92 100644
--- a/activesupport/lib/active_support/core_ext/range/each.rb
+++ b/activesupport/lib/active_support/core_ext/range/each.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_support/time_with_zone"
+
module ActiveSupport
module EachTimeWithZone #:nodoc:
def each(&block)
@@ -15,7 +17,7 @@ module ActiveSupport
private
def ensure_iteration_allowed
- raise TypeError, "can't iterate from #{first.class}" if first.is_a?(Time)
+ raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb b/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb
new file mode 100644
index 0000000000..5f80acf68e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require "active_support/time_with_zone"
+
+module ActiveSupport
+ module IncludeTimeWithZone #:nodoc:
+ # Extends the default Range#include? to support ActiveSupport::TimeWithZone.
+ #
+ # (1.hour.ago..1.hour.from_now).include?(Time.current) # => true
+ #
+ def include?(value)
+ if first.is_a?(TimeWithZone)
+ cover?(value)
+ elsif last.is_a?(TimeWithZone)
+ cover?(value)
+ else
+ super
+ end
+ end
+ end
+end
+
+Range.prepend(ActiveSupport::IncludeTimeWithZone)
diff --git a/activesupport/lib/active_support/deprecation/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb
index 3d7eedf637..dd515cd6f4 100644
--- a/activesupport/lib/active_support/deprecation/constant_accessor.rb
+++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/inflector/methods"
-
module ActiveSupport
class Deprecation
# DeprecatedConstantAccessor transforms a constant into a deprecated one by
@@ -29,6 +27,8 @@ module ActiveSupport
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
module DeprecatedConstantAccessor
def self.included(base)
+ require "active_support/inflector/methods"
+
extension = Module.new do
def const_missing(missing_const_name)
if class_variable_defined?(:@@_deprecated_constants)
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index f6c6648917..782ad2519c 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-require "active_support/inflector/methods"
require "active_support/core_ext/regexp"
module ActiveSupport
@@ -125,6 +124,8 @@ module ActiveSupport
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")
+ require "active_support/inflector/methods"
+
@old_const = old_const
@new_const = new_const
@deprecator = deprecator
diff --git a/activesupport/lib/active_support/encrypted_configuration.rb b/activesupport/lib/active_support/encrypted_configuration.rb
index b403048627..c52d3869de 100644
--- a/activesupport/lib/active_support/encrypted_configuration.rb
+++ b/activesupport/lib/active_support/encrypted_configuration.rb
@@ -22,6 +22,12 @@ module ActiveSupport
""
end
+ def write(contents)
+ deserialize(contents)
+
+ super
+ end
+
def config
@config ||= deserialize(read).deep_symbolize_keys
end
@@ -36,7 +42,7 @@ module ActiveSupport
end
def deserialize(config)
- config.present? ? YAML.load(config) : {}
+ config.present? ? YAML.load(config, content_path) : {}
end
end
end
diff --git a/activesupport/lib/active_support/encrypted_file.rb b/activesupport/lib/active_support/encrypted_file.rb
index 1c4c1cb457..3d1455fb95 100644
--- a/activesupport/lib/active_support/encrypted_file.rb
+++ b/activesupport/lib/active_support/encrypted_file.rb
@@ -2,8 +2,6 @@
require "pathname"
require "active_support/message_encryptor"
-require "active_support/core_ext/string/strip"
-require "active_support/core_ext/module/delegation"
module ActiveSupport
class EncryptedFile
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index a639eddf10..0450a4be4c 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -4,6 +4,7 @@ require "concurrent/map"
require "active_support/core_ext/array/prepend_and_append"
require "active_support/core_ext/regexp"
require "active_support/i18n"
+require "active_support/deprecation"
module ActiveSupport
module Inflector
@@ -67,16 +68,21 @@ module ActiveSupport
end
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
+ deprecate :acronym_regex
+
+ attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
def initialize
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
+ @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {}
+ define_acronym_regex_patterns
end
# Private, for the test suite.
def initialize_dup(orig) # :nodoc:
- %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
+ %w(plurals singulars uncountables humans acronyms).each do |scope|
instance_variable_set("@#{scope}", orig.send(scope).dup)
end
+ define_acronym_regex_patterns
end
# Specifies a new acronym. An acronym must be specified as it will appear
@@ -130,7 +136,7 @@ module ActiveSupport
# camelize 'mcdonald' # => 'McDonald'
def acronym(word)
@acronyms[word.downcase] = word
- @acronym_regex = /#{@acronyms.values.join("|")}/
+ define_acronym_regex_patterns
end
# Specifies a new pluralization rule and its replacement. The rule can
@@ -225,6 +231,14 @@ module ActiveSupport
instance_variable_set "@#{scope}", []
end
end
+
+ private
+
+ def define_acronym_regex_patterns
+ @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/
+ @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/
+ @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/
+ end
end
# Yields a singleton instance of Inflector::Inflections so you can specify
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 3357b5eb32..60eeaa77cb 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -71,7 +71,7 @@ module ActiveSupport
if uppercase_first_letter
string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
else
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
+ string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase }
end
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
string.gsub!("/".freeze, "::".freeze)
@@ -92,7 +92,7 @@ module ActiveSupport
def underscore(camel_cased_word)
return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze)
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
+ word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_'.freeze }#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
word.tr!("-".freeze, "_".freeze)
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index ee5286bd55..d1f7e6ea09 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -11,7 +11,6 @@ require "active_support/testing/isolation"
require "active_support/testing/constant_lookup"
require "active_support/testing/time_helpers"
require "active_support/testing/file_fixtures"
-require "active_support/core_ext/kernel/reporting"
module ActiveSupport
class TestCase < ::Minitest::Test
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 46eed73d24..b24aa36ede 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -159,7 +159,7 @@ module ActiveSupport
if to == UNTRACKED
error = "#{expression.inspect} didn't change"
error = "#{message}.\n#{error}" if message
- assert_not_equal before, after, error
+ assert before != after, error
else
error = "#{expression.inspect} didn't change to #{to}"
error = "#{message}.\n#{error}" if message
@@ -190,12 +190,7 @@ module ActiveSupport
error = "#{expression.inspect} did change to #{after}"
error = "#{message}.\n#{error}" if message
-
- if before.nil?
- assert_nil after, error
- else
- assert_equal before, after, error
- end
+ assert before == after, error
retval
end
diff --git a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
index 2fa2d7af88..16b7abc679 100644
--- a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
+++ b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
@@ -8,7 +8,9 @@ module CacheIncrementDecrementBehavior
assert_equal 2, @cache.read("foo").to_i
assert_equal 3, @cache.increment("foo")
assert_equal 3, @cache.read("foo").to_i
- assert_nil @cache.increment("bar")
+
+ missing = @cache.increment("bar")
+ assert(missing.nil? || missing == 1)
end
def test_decrement
@@ -18,6 +20,8 @@ module CacheIncrementDecrementBehavior
assert_equal 2, @cache.read("foo").to_i
assert_equal 1, @cache.decrement("foo")
assert_equal 1, @cache.read("foo").to_i
- assert_nil @cache.decrement("bar")
+
+ missing = @cache.decrement("bar")
+ assert(missing.nil? || missing == -1)
end
end
diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb
index 582e902f72..73a9b2a71c 100644
--- a/activesupport/test/cache/behaviors/cache_store_behavior.rb
+++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb
@@ -146,6 +146,16 @@ module CacheStoreBehavior
assert_nil @cache.read("foo")
end
+ def test_read_and_write_uncompressed_small_data
+ @cache.write("foo", "bar", compress: false)
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_read_and_write_uncompressed_nil
+ @cache.write("foo", nil, compress: false)
+ assert_nil @cache.read("foo")
+ end
+
def test_cache_key
obj = Object.new
def obj.cache_key
diff --git a/activesupport/test/cache/behaviors/local_cache_behavior.rb b/activesupport/test/cache/behaviors/local_cache_behavior.rb
index 8dec8090b1..f7302df4c8 100644
--- a/activesupport/test/cache/behaviors/local_cache_behavior.rb
+++ b/activesupport/test/cache/behaviors/local_cache_behavior.rb
@@ -20,7 +20,11 @@ module LocalCacheBehavior
end
def test_cleanup_clears_local_cache_but_not_remote_cache
- skip unless @cache.class.instance_methods(false).include?(:cleanup)
+ begin
+ @cache.cleanup
+ rescue NotImplementedError
+ skip
+ end
@cache.with_local_cache do
@cache.write("foo", "bar")
diff --git a/activesupport/test/cache/cache_entry_test.rb b/activesupport/test/cache/cache_entry_test.rb
index 51b214ad8f..80ff7ad564 100644
--- a/activesupport/test/cache/cache_entry_test.rb
+++ b/activesupport/test/cache/cache_entry_test.rb
@@ -14,16 +14,23 @@ class CacheEntryTest < ActiveSupport::TestCase
end
end
- def test_compress_values
+ def test_compressed_values
value = "value" * 100
entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1)
assert_equal value, entry.value
assert(value.bytesize > entry.size, "value is compressed")
end
- def test_non_compress_values
+ def test_compressed_by_default
value = "value" * 100
- entry = ActiveSupport::Cache::Entry.new(value)
+ entry = ActiveSupport::Cache::Entry.new(value, compress_threshold: 1)
+ assert_equal value, entry.value
+ assert(value.bytesize > entry.size, "value is compressed")
+ end
+
+ def test_uncompressed_values
+ value = "value" * 100
+ entry = ActiveSupport::Cache::Entry.new(value, compress: false)
assert_equal value, entry.value
assert_equal value.bytesize, entry.size
end
diff --git a/activesupport/test/cache/stores/redis_cache_store_test.rb b/activesupport/test/cache/stores/redis_cache_store_test.rb
new file mode 100644
index 0000000000..988de9207f
--- /dev/null
+++ b/activesupport/test/cache/stores/redis_cache_store_test.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+require "active_support/cache/redis_cache_store"
+require_relative "../behaviors"
+
+module ActiveSupport::Cache::RedisCacheStoreTests
+ class LookupTest < ActiveSupport::TestCase
+ test "may be looked up as :redis_cache_store" do
+ assert_kind_of ActiveSupport::Cache::RedisCacheStore,
+ ActiveSupport::Cache.lookup_store(:redis_cache_store)
+ end
+ end
+
+ class InitializationTest < ActiveSupport::TestCase
+ test "omitted URL uses Redis client with default settings" do
+ assert_called_with Redis, :new, [
+ url: nil,
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0,
+ ] do
+ build
+ end
+ end
+
+ test "no URLs uses Redis client with default settings" do
+ assert_called_with Redis, :new, [
+ url: nil,
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0,
+ ] do
+ build url: []
+ end
+ end
+
+ test "singular URL uses Redis client" do
+ assert_called_with Redis, :new, [
+ url: "redis://localhost:6379/0",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0,
+ ] do
+ build url: "redis://localhost:6379/0"
+ end
+ end
+
+ test "one URL uses Redis client" do
+ assert_called_with Redis, :new, [
+ url: "redis://localhost:6379/0",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0,
+ ] do
+ build url: %w[ redis://localhost:6379/0 ]
+ end
+ end
+
+ test "multiple URLs uses Redis::Distributed client" do
+ assert_called_with Redis, :new, [
+ [ url: "redis://localhost:6379/0",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0 ],
+ [ url: "redis://localhost:6379/1",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0 ],
+ ], returns: Redis.new do
+ @cache = build url: %w[ redis://localhost:6379/0 redis://localhost:6379/1 ]
+ assert_kind_of ::Redis::Distributed, @cache.redis
+ end
+ end
+
+ test "block argument uses yielded client" do
+ block = -> { :custom_redis_client }
+ assert_called block, :call do
+ build redis: block
+ end
+ end
+
+ private
+ def build(**kwargs)
+ ActiveSupport::Cache::RedisCacheStore.new(**kwargs).tap do |cache|
+ cache.redis
+ end
+ end
+ end
+
+ class StoreTest < ActiveSupport::TestCase
+ setup do
+ @namespace = "namespace"
+
+ @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace, expires_in: 60)
+ #@cache.logger = Logger.new($stdout) # For test debugging
+
+ # For LocalCacheBehavior tests
+ @peek = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace)
+ end
+
+ teardown do
+ @cache.clear
+ @cache.redis.disconnect!
+ end
+ end
+
+ class RedisCacheStoreCommonBehaviorTest < StoreTest
+ include CacheStoreBehavior
+ include CacheStoreVersionBehavior
+ include LocalCacheBehavior
+ include CacheIncrementDecrementBehavior
+ include AutoloadingCacheBehavior
+ end
+
+ # Separate test class so we can omit the namespace which causes expected,
+ # appropriate complaints about incompatible string encodings.
+ class KeyEncodingSafetyTest < StoreTest
+ include EncodedKeyCacheBehavior
+
+ setup do
+ @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1)
+ @cache.logger = nil
+ end
+ end
+
+ class StoreAPITest < StoreTest
+ end
+
+ class FailureSafetyTest < StoreTest
+ test "fetch read failure returns nil" do
+ end
+
+ test "fetch read failure does not attempt to write" do
+ end
+
+ test "write failure returns nil" do
+ end
+ end
+
+ class DeleteMatchedTest < StoreTest
+ test "deletes keys matching glob" do
+ @cache.write("foo", "bar")
+ @cache.write("fu", "baz")
+ @cache.delete_matched("foo*")
+ assert !@cache.exist?("foo")
+ assert @cache.exist?("fu")
+ end
+
+ test "fails with regexp matchers" do
+ assert_raise ArgumentError do
+ @cache.delete_matched(/OO/i)
+ end
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index 4fdd228ff8..41b11d0c33 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -5,7 +5,7 @@ require "active_support/core_ext/load_error"
class TestLoadError < ActiveSupport::TestCase
def test_with_require
- assert_raise(LoadError) { require 'no_this_file_don\'t_exist' }
+ assert_raise(LoadError) { require "no_this_file_don't_exist" }
end
def test_with_load
assert_raise(LoadError) { load "nor_does_this_one" }
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 0467123e55..049fac8fd4 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -121,9 +121,12 @@ class RangeTest < ActiveSupport::TestCase
def test_include_on_time_with_zone
twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
- assert_raises TypeError do
- ((twz - 1.hour)..twz).include?(twz)
- end
+ assert ((twz - 1.hour)..twz).include?(twz)
+ end
+
+ def test_case_equals_on_time_with_zone
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
+ assert ((twz - 1.hour)..twz) === twz
end
def test_date_time_with_each
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 9fd6d8ac0f..5c5abe9fd1 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -197,7 +197,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_string_parameterized_underscore_preserve_case
- StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged|
+ StringToParameterizePreserveCaseWithUnderscore.each do |normal, slugged|
assert_equal(slugged, normal.parameterize(separator: "_", preserve_case: true))
end
end
diff --git a/activesupport/test/encrypted_configuration_test.rb b/activesupport/test/encrypted_configuration_test.rb
index 471faa8c12..0bc915be82 100644
--- a/activesupport/test/encrypted_configuration_test.rb
+++ b/activesupport/test/encrypted_configuration_test.rb
@@ -51,6 +51,14 @@ class EncryptedConfigurationTest < ActiveSupport::TestCase
assert_equal "things", @credentials[:new]
end
+ test "raise error when writing an invalid format value" do
+ assert_raise(Psych::SyntaxError) do
+ @credentials.change do |config_file|
+ config_file.write "login: *login\n username: dummy"
+ end
+ end
+ end
+
test "raises key error when accessing config via bang method" do
assert_raise(KeyError) { @credentials.something! }
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 6cc1039d8e..0e3e576a70 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -224,6 +224,12 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal("json_html_api", ActiveSupport::Inflector.underscore("JSONHTMLAPI"))
end
+ def test_acronym_regexp_is_deprecated
+ assert_deprecated do
+ ActiveSupport::Inflector.inflections.acronym_regex
+ end
+ end
+
def test_underscore
CamelToUnderscore.each do |camel, underscore|
assert_equal(underscore, ActiveSupport::Inflector.underscore(camel))
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index f1214671ce..689370cccf 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -221,7 +221,7 @@ module InflectorTestCases
"Test with malformed utf8 \251" => "test_with_malformed_utf8"
}
- StringToParameterizePreserceCaseWithUnderscore = {
+ StringToParameterizePreserveCaseWithUnderscore = {
"Donald E. Knuth" => "Donald_E_Knuth",
"Random text with *(bad)* characters" => "Random_text_with_bad_characters",
"With-some-dashes" => "With-some-dashes",
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 9bc9183668..84e4953fe2 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -179,6 +179,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
def test_assert_changes_works_with_any_object
+ # Silences: instance variable @new_object not initialized.
retval = silence_warnings do
assert_changes :@new_object, from: nil, to: 42 do
@new_object = 42
@@ -201,7 +202,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
def test_assert_changes_with_to_and_case_operator
token = nil
- assert_changes -> { token }, to: /\w{32}/ do
+ assert_changes -> { token }, to: /\w{32}/ do
token = SecureRandom.hex
end
end
@@ -236,7 +237,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
- assert_equal "@object.num should not change.\n\"@object.num\" did change to 1.\nExpected: 0\n Actual: 1", error.message
+ assert_equal "@object.num should not change.\n\"@object.num\" did change to 1", error.message
end
end
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 53417f012e..630dafe632 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -213,6 +213,7 @@ The following methods trigger callbacks:
* `save!`
* `save(validate: false)`
* `toggle!`
+* `touch`
* `update_attribute`
* `update`
* `update!`
@@ -245,7 +246,6 @@ Just as with validations, it is also possible to skip callbacks by using the fol
* `increment`
* `increment_counter`
* `toggle`
-* `touch`
* `update_column`
* `update_columns`
* `update_all`
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 2c153d3783..10b89433e7 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -16,7 +16,7 @@ RDoc
----
The [Rails API documentation](http://api.rubyonrails.org) is generated with
-[RDoc](http://docs.seattlerb.org/rdoc/). To generate it, make sure you are
+[RDoc](https://ruby.github.io/rdoc/). To generate it, make sure you are
in the rails root directory, run `bundle install` and execute:
```bash
@@ -26,9 +26,9 @@ in the rails root directory, run `bundle install` and execute:
Resulting HTML files can be found in the ./doc/rdoc directory.
Please consult the RDoc documentation for help with the
-[markup](http://docs.seattlerb.org/rdoc/RDoc/Markup.html),
+[markup](https://ruby.github.io/rdoc/RDoc/Markup.html),
and also take into account these [additional
-directives](http://docs.seattlerb.org/rdoc/RDoc/Parser/Ruby.html).
+directives](https://ruby.github.io/rdoc/RDoc/Parser/Ruby.html).
Wording
-------
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 96650b5be9..31bc478015 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -366,9 +366,9 @@ There are some common options used by all cache implementations. These can be pa
* `:namespace` - This option can be used to create a namespace within the cache store. It is especially useful if your application shares a cache with other applications.
-* `:compress` - This option can be used to indicate that compression should be used in the cache. This can be useful for transferring large cache entries over a slow network.
+* `:compress` - Enabled by default. Compresses cache entries so more data can be stored in the same memory footprint, leading to fewer cache evictions and higher hit rates.
-* `:compress_threshold` - This option is used in conjunction with the `:compress` option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes.
+* `:compress_threshold` - Defaults to 1kB. Cache entries larger than this threshold, specified in bytes, are compressed.
* `:expires_in` - This option sets an expiration time in seconds for the cache entry when it will be automatically removed from the cache.
@@ -444,6 +444,53 @@ The `write` and `fetch` methods on this cache accept two additional options that
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
```
+### ActiveSupport::Cache::RedisCacheStore
+
+The Redis cache store takes advantage of Redis support for least-recently-used
+and least-frequently-used key eviction when it reaches max memory, allowing it
+to behave much like a Memcached cache server.
+
+Deployment note: Redis doesn't expire keys by default, so take care to use a
+dedicated Redis cache server. Don't fill up your persistent-Redis server with
+volatile cache data! Read the
+[Redis cache server setup guide](https://redis.io/topics/lru-cache) in detail.
+
+For an all-cache Redis server, set `maxmemory-policy` to an `allkeys` policy.
+Redis 4+ support least-frequently-used (`allkeys-lfu`) eviction, an excellent
+default choice. Redis 3 and earlier should use `allkeys-lru` for
+least-recently-used eviction.
+
+Set cache read and write timeouts relatively low. Regenerating a cached value
+is often faster than waiting more than a second to retrieve it. Both read and
+write timeouts default to 1 second, but may be set lower if your network is
+consistently low latency.
+
+Cache reads and writes never raise exceptions. They just return `nil` instead,
+behaving as if there was nothing in the cache. To gauge whether your cache is
+hitting exceptions, you may provide an `error_handler` to report to an
+exception gathering service. It must accept three keyword arguments: `method`,
+the cache store method that was originally called; `returning`, the value that
+was returned to the user, typically `nil`; and `exception`, the exception that
+was rescued.
+
+Putting it all together, a production Redis cache store may look something
+like this:
+
+```ruby
+cache_servers = %w[ "redis://cache-01:6379/0", "redis://cache-02:6379/0", … ],
+config.cache_store = :redis_cache_store, url: cache_servers,
+
+ connect_timeout: 30, # Defaults to 20 seconds
+ read_timeout: 0.2, # Defaults to 1 second
+ write_timeout: 0.2, # Defaults to 1 second
+
+ error_handler: -> (method:, returning:, exception:) {
+ # Report errors to Sentry as warnings
+ Raven.capture_exception exception, level: 'warning",
+ tags: { method: method, returning: returning }
+ }
+```
+
### ActiveSupport::Cache::NullStore
This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with `Rails.cache` but caching may interfere with being able to see the results of code changes. With this cache store, all `fetch` and `read` operations will result in a miss.
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 88c559921c..648645af7c 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -290,7 +290,7 @@ INFO: You can also use the alias "c" to invoke the console: `rails c`.
You can specify the environment in which the `console` command should operate.
```bash
-$ bin/rails console staging
+$ bin/rails console -e staging
```
If you wish to test out some code without changing any data, you can do that by invoking `rails console --sandbox`.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 7a32607eb7..2d03f0a61e 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -973,7 +973,7 @@ By default Rails ships with three environments: "development", "test", and "prod
Imagine you have a server which mirrors the production environment but is only used for testing. Such a server is commonly called a "staging server". To define an environment called "staging" for this server, just create a file called `config/environments/staging.rb`. Please use the contents of any existing file in `config/environments` as a starting point and make the necessary changes from there.
-That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console staging`, `Rails.env.staging?` works, etc.
+That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console -e staging`, `Rails.env.staging?` works, etc.
### Deploy to a subdirectory (relative url root)
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index b9b327252f..f4597b0e60 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 member of the collection followed by `_counter`. For example, if you're rendering `@products`, within the partial you can refer to `product_counter` to tell you how many times the partial has been rendered. This does not work in conjunction with the `as: :value` option.
+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.
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/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb
index 1ef7c1f343..e5d3d01431 100644
--- a/railties/lib/rails/commands/credentials/credentials_command.rb
+++ b/railties/lib/rails/commands/credentials/credentials_command.rb
@@ -53,6 +53,7 @@ module Rails
def ensure_master_key_has_been_added
master_key_generator.add_master_key_file
+ master_key_generator.ignore_master_key_file
end
def ensure_credentials_have_been_added
diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb
index c91139e33b..73a88767e2 100644
--- a/railties/lib/rails/commands/secrets/secrets_command.rb
+++ b/railties/lib/rails/commands/secrets/secrets_command.rb
@@ -15,7 +15,7 @@ module Rails
end
def setup
- generator.start
+ deprecate_in_favor_of_credentials_and_exit
end
def edit
@@ -42,11 +42,10 @@ module Rails
rescue Rails::Secrets::MissingKeyError => error
say error.message
rescue Errno::ENOENT => error
- raise unless error.message =~ /secrets\.yml\.enc/
-
- Rails::Secrets.read_template_for_editing do |tmp_path|
- system("#{ENV["EDITOR"]} #{tmp_path}")
- generator.skip_secrets_file { setup }
+ if error.message =~ /secrets\.yml\.enc/
+ deprecate_in_favor_of_credentials_and_exit
+ else
+ raise
end
end
@@ -55,11 +54,11 @@ module Rails
end
private
- def generator
- require "rails/generators"
- require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
+ def deprecate_in_favor_of_credentials_and_exit
+ say "Encrypted secrets is deprecated in favor of credentials. Run:"
+ say "bin/rails credentials --help"
- Rails::Generators::EncryptedSecretsGenerator
+ exit 1
end
end
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 2d265818f7..5592e8d78e 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -218,6 +218,7 @@ module Rails
rails.delete("app")
rails.delete("plugin")
rails.delete("encrypted_secrets")
+ rails.delete("credentials")
hidden_namespaces.each { |n| groups.delete(n.to_s) }
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 59ca4c849f..bdeddff645 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -461,7 +461,11 @@ module Rails
def run_active_storage
unless skip_active_storage?
- rails_command "active_storage:install", capture: options[:quiet]
+ if bundle_install?
+ rails_command "active_storage:install", capture: options[:quiet]
+ else
+ log("Active Storage installation was skipped. Please run `bin/rails active_storage:install` to install Active Storage files.")
+ end
end
end
diff --git a/railties/lib/rails/generators/erb/controller/templates/view.html.erb b/railties/lib/rails/generators/erb/controller/templates/view.html.erb.tt
index cd54d13d83..cd54d13d83 100644
--- a/railties/lib/rails/generators/erb/controller/templates/view.html.erb
+++ b/railties/lib/rails/generators/erb/controller/templates/view.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb.tt
index b5045671b3..b5045671b3 100644
--- a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb
+++ b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb.tt
index 342285df19..342285df19 100644
--- a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb
+++ b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb.tt
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt
index 0eb9d82bbb..0eb9d82bbb 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt
index 81329473d9..81329473d9 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt
index e1ede7c713..e1ede7c713 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt
index 9b2b2f4875..9b2b2f4875 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
index 5e634153be..5e634153be 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 1c32fad3ea..a99037576d 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -159,23 +159,17 @@ module Rails
end
def master_key
- return if options[:pretend]
+ return if options[:pretend] || options[:dummy_app]
require "rails/generators/rails/master_key/master_key_generator"
-
- after_bundle do
- Rails::Generators::MasterKeyGenerator.new([], quiet: options[:quiet]).add_master_key_file
- end
+ Rails::Generators::MasterKeyGenerator.new([], quiet: options[:quiet]).add_master_key_file_silently
end
def credentials
- return if options[:pretend]
+ return if options[:pretend] || options[:dummy_app]
require "rails/generators/rails/credentials/credentials_generator"
-
- after_bundle do
- Rails::Generators::CredentialsGenerator.new([], quiet: options[:quiet]).add_credentials_file_silently
- end
+ Rails::Generators::CredentialsGenerator.new([], quiet: options[:quiet]).add_credentials_file_silently
end
def database_yml
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
index ee9db83ca2..61026f5182 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
@@ -21,6 +21,7 @@ ruby <%= "'#{RUBY_VERSION}'" -%>
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
<% unless skip_active_storage? -%>
+
# Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8'
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/README.md b/railties/lib/rails/generators/rails/app/templates/README.md.tt
index 7db80e4ca1..7db80e4ca1 100644
--- a/railties/lib/rails/generators/rails/app/templates/README.md
+++ b/railties/lib/rails/generators/rails/app/templates/README.md.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile.tt
index e85f913914..e85f913914 100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js.tt
index 739aa5f022..739aa5f022 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css.tt
index d05ea0f511..d05ea0f511 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb.tt
index d672697283..d672697283 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb.tt
index 0ff5442f47..0ff5442f47 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb.tt
index de6be7945c..de6be7945c 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb.tt
index a009ace51c..a009ace51c 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb b/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb.tt
index 286b2239d1..286b2239d1 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb b/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb.tt
index 10a4cba84d..10a4cba84d 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/bundle b/railties/lib/rails/generators/rails/app/templates/bin/bundle.tt
index a84f0afe47..a84f0afe47 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/bundle
+++ b/railties/lib/rails/generators/rails/app/templates/bin/bundle.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rails b/railties/lib/rails/generators/rails/app/templates/bin/rails.tt
index 513a2e0183..513a2e0183 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/rails
+++ b/railties/lib/rails/generators/rails/app/templates/bin/rails.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rake b/railties/lib/rails/generators/rails/app/templates/bin/rake.tt
index d14fc8395b..d14fc8395b 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/rake
+++ b/railties/lib/rails/generators/rails/app/templates/bin/rake.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn b/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt
index b4e4d95286..b4e4d95286 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/yarn
+++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru.tt
index f7ba0b527b..f7ba0b527b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt
index 9e03e86771..9e03e86771 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt
index b9e460cef3..b9e460cef3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml.tt
index 8e53156c71..8e53156c71 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
index 917b52e535..917b52e535 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
index d40117a27f..d40117a27f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
index 563be77710..563be77710 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
index 2a67bdca25..2a67bdca25 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
index 70df04079d..70df04079d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt
index 371415e6a8..371415e6a8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
index 04afaa0596..04afaa0596 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
index 6da0601b24..6da0601b24 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
index 145cfb7f74..145cfb7f74 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt
index 9510568124..9510568124 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
index 049de65f22..049de65f22 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb.tt
index 426333bb46..426333bb46 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 2746aedd6f..b383228dc0 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -26,8 +26,8 @@ Rails.application.configure do
config.cache_store = :null_store
end
-
<%- unless skip_active_storage? -%>
+
# Store uploaded files on the local file system (see config/storage.yml for options)
config.active_storage.service = :local
<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index a19f490d91..ff4c89219a 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -40,8 +40,8 @@ Rails.application.configure do
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
- <%- end -%>
+ <%- end -%>
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb.tt
index 89d2efab2b..89d2efab2b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb.tt
index 59385cdf37..59385cdf37 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb.tt
index 5a6a32d371..5a6a32d371 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt
index 3b1c1b5ed1..3b1c1b5ed1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt
index 4a994e1e7b..4a994e1e7b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb.tt
index ac033bf9dc..ac033bf9dc 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb.tt
index dc1899682b..dc1899682b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
index 1e19380dcb..1e19380dcb 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt
index 787824f888..787824f888 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/spring.rb b/railties/lib/rails/generators/rails/app/templates/config/spring.rb.tt
index 9fa7863f99..9fa7863f99 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/spring.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/spring.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/storage.yml b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt
index 9bada4b66d..9bada4b66d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/storage.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore.tt
index bd6b34346a..2cd8335aba 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore.tt
@@ -24,13 +24,13 @@
<% unless skip_active_storage? -%>
# Ignore uploaded files in development
/storage/*
-<% end -%>
+<% end -%>
<% unless options.skip_yarn? -%>
/node_modules
/yarn-error.log
-<% end -%>
+<% end -%>
<% unless options.api? -%>
/public/assets
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/package.json b/railties/lib/rails/generators/rails/app/templates/package.json.tt
index 46db57dcbe..46db57dcbe 100644
--- a/railties/lib/rails/generators/rails/app/templates/package.json
+++ b/railties/lib/rails/generators/rails/app/templates/package.json.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/ruby-version b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt
index c444f33b0f..c444f33b0f 100644
--- a/railties/lib/rails/generators/rails/app/templates/ruby-version
+++ b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb b/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt
index d19212abd5..d19212abd5 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
index 6ad1f11781..6ad1f11781 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
diff --git a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
index 7594abf268..afad32db02 100644
--- a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
+++ b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
@@ -1,4 +1,4 @@
-/*
+/*
Place all the styles related to the matching controller here.
They will automatically be included in application.css.
*/
diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb.tt
index 633e0b3177..633e0b3177 100644
--- a/railties/lib/rails/generators/rails/controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/controller/templates/controller.rb.tt
diff --git a/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb b/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
deleted file mode 100644
index 1aa7a2622a..0000000000
--- a/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-require "rails/generators/base"
-require "rails/secrets"
-
-module Rails
- module Generators
- class EncryptedSecretsGenerator < Base
- def add_secrets_key_file
- unless File.exist?("config/secrets.yml.key") || File.exist?("config/secrets.yml.enc")
- key = Rails::Secrets.generate_key
-
- say "Adding config/secrets.yml.key to store the encryption key: #{key}"
- say ""
- say "Save this in a password manager your team can access."
- say ""
- say "If you lose the key, no one, including you, can access any encrypted secrets."
-
- say ""
- create_file "config/secrets.yml.key", key
- say ""
- end
- end
-
- def ignore_key_file
- if File.exist?(".gitignore")
- unless File.read(".gitignore").include?(key_ignore)
- say "Ignoring config/secrets.yml.key so it won't end up in Git history:"
- say ""
- append_to_file ".gitignore", key_ignore
- say ""
- end
- else
- say "IMPORTANT: Don't commit config/secrets.yml.key. Add this to your ignore file:"
- say key_ignore, :on_green
- say ""
- end
- end
-
- def add_encrypted_secrets_file
- unless (defined?(@@skip_secrets_file) && @@skip_secrets_file) || File.exist?("config/secrets.yml.enc")
- say "Adding config/secrets.yml.enc to store secrets that needs to be encrypted."
- say ""
- say "For now the file contains this but it's been encrypted with the generated key:"
- say ""
- say Secrets.template, :on_green
- say ""
-
- Secrets.write(Secrets.template)
-
- say "You can edit encrypted secrets with `bin/rails secrets:edit`."
- say ""
- end
-
- say "Add this to your config/environments/production.rb:"
- say "config.read_encrypted_secrets = true"
- end
-
- def self.skip_secrets_file
- @@skip_secrets_file = true
- yield
- ensure
- @@skip_secrets_file = false
- end
-
- private
- def key_ignore
- [ "", "# Ignore encrypted secrets key file.", "config/secrets.yml.key", "" ].join("\n")
- end
- end
- end
-end
diff --git a/railties/lib/rails/generators/rails/helper/templates/helper.rb b/railties/lib/rails/generators/rails/helper/templates/helper.rb.tt
index b4173151b4..b4173151b4 100644
--- a/railties/lib/rails/generators/rails/helper/templates/helper.rb
+++ b/railties/lib/rails/generators/rails/helper/templates/helper.rb.tt
diff --git a/railties/lib/rails/generators/rails/master_key/master_key_generator.rb b/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
index 23dbed20ac..29d83f5d81 100644
--- a/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
+++ b/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
@@ -20,29 +20,31 @@ module Rails
log "If you lose the key, no one, including you, can access anything encrypted with it."
log ""
- create_file MASTER_KEY_PATH, key
+ add_master_key_file_silently key
log ""
-
- ignore_master_key_file
end
end
- private
- def ignore_master_key_file
- if File.exist?(".gitignore")
- unless File.read(".gitignore").include?(key_ignore)
- log "Ignoring #{MASTER_KEY_PATH} so it won't end up in Git history:"
- log ""
- append_to_file ".gitignore", key_ignore
- log ""
- end
- else
- log "IMPORTANT: Don't commit #{MASTER_KEY_PATH}. Add this to your ignore file:"
- log key_ignore, :on_green
+ def add_master_key_file_silently(key = nil)
+ create_file MASTER_KEY_PATH, key || ActiveSupport::EncryptedFile.generate_key
+ end
+
+ def ignore_master_key_file
+ if File.exist?(".gitignore")
+ unless File.read(".gitignore").include?(key_ignore)
+ log "Ignoring #{MASTER_KEY_PATH} so it won't end up in Git history:"
+ log ""
+ append_to_file ".gitignore", key_ignore
log ""
end
+ else
+ log "IMPORTANT: Don't commit #{MASTER_KEY_PATH}. Add this to your ignore file:"
+ log key_ignore, :on_green
+ log ""
end
+ end
+ private
def key_ignore
[ "", "# Ignore master key for decrypting credentials and more.", "/#{MASTER_KEY_PATH}", "" ].join("\n")
end
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index bc2dcf008e..4762dfddd4 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -98,6 +98,7 @@ task default: :test
opts[:skip_listen] = true
opts[:skip_git] = true
opts[:skip_turbolinks] = true
+ opts[:dummy_app] = true
invoke Rails::Generators::AppGenerator,
[ File.expand_path(dummy_path, destination_root) ], opts
diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt
index 9a8c4bf098..9a8c4bf098 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile.tt
index 290259b4db..290259b4db 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt
index ff2fb3ba4e..ff2fb3ba4e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE
+++ b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/README.md b/railties/lib/rails/generators/rails/plugin/templates/README.md.tt
index 1632409bea..1632409bea 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/README.md
+++ b/railties/lib/rails/generators/rails/plugin/templates/README.md.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt
index f3efe21cf1..f3efe21cf1 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb.tt
index 154452bfe5..154452bfe5 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
index 7a68da5c4b..7a68da5c4b 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/gitignore
+++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb.tt
index 3285055eb7..3285055eb7 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt
index 8938770fc4..8938770fc4 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt
index 7bdf4ee5fb..7bdf4ee5fb 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb.tt
index b08f4ef9ae..b08f4ef9ae 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake.tt
index 88a2c4120f..88a2c4120f 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb.tt
index 06ffe2f1ed..06ffe2f1ed 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb.tt
index c9aef85d40..c9aef85d40 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js.tt
index 03937cf8ff..03937cf8ff 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js.tt
index 2f23844f5e..2f23844f5e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt
index f3d80c87f5..f3d80c87f5 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb.tt
index 694510edc0..694510edc0 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb.tt
index 1ee05d7871..1ee05d7871 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb b/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt
index d19212abd5..d19212abd5 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb.tt
index 29e59d8407..29e59d8407 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt
index 7fa9973931..7fa9973931 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt
index 400afec6dc..400afec6dc 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt
index 05f1c2b2d3..05f1c2b2d3 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt
diff --git a/railties/lib/rails/generators/rails/task/templates/task.rb b/railties/lib/rails/generators/rails/task/templates/task.rb.tt
index 1e3ed5f158..1e3ed5f158 100644
--- a/railties/lib/rails/generators/rails/task/templates/task.rb
+++ b/railties/lib/rails/generators/rails/task/templates/task.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb.tt
index ff41fef9e9..ff41fef9e9 100644
--- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb.tt
index a7f1fc4fba..a7f1fc4fba 100644
--- a/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb
+++ b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb.tt
index 118e0f1271..118e0f1271 100644
--- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
+++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/job/job_generator.rb b/railties/lib/rails/generators/test_unit/job/job_generator.rb
index 9225af4e0c..a99ce914c0 100644
--- a/railties/lib/rails/generators/test_unit/job/job_generator.rb
+++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb
@@ -8,7 +8,7 @@ module TestUnit # :nodoc:
check_class_collision suffix: "JobTest"
def create_test_file
- template "unit_test.rb.erb", File.join("test/jobs", class_path, "#{file_name}_job_test.rb")
+ template "unit_test.rb", File.join("test/jobs", class_path, "#{file_name}_job_test.rb")
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.tt
index f5351d0ec6..f5351d0ec6 100644
--- a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb
+++ b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb.tt
index a2f2d30de5..a2f2d30de5 100644
--- a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb.tt
index b063cbc47b..b063cbc47b 100644
--- a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt
index 0681780c97..0681780c97 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
+++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt
diff --git a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb.tt
index c9bc7d5b90..c9bc7d5b90 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb
+++ b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb.tt
index f21861d8e6..f21861d8e6 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb.tt
index 195d60be20..195d60be20 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt
index f83f5a5c62..f83f5a5c62 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb b/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb.tt
index d19212abd5..d19212abd5 100644
--- a/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb
+++ b/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/system/templates/system_test.rb b/railties/lib/rails/generators/test_unit/system/templates/system_test.rb.tt
index b5ce2ba5c8..b5ce2ba5c8 100644
--- a/railties/lib/rails/generators/test_unit/system/templates/system_test.rb
+++ b/railties/lib/rails/generators/test_unit/system/templates/system_test.rb.tt
diff --git a/railties/lib/rails/secrets.rb b/railties/lib/rails/secrets.rb
index aea72b2d01..30e3478c9b 100644
--- a/railties/lib/rails/secrets.rb
+++ b/railties/lib/rails/secrets.rb
@@ -32,23 +32,10 @@ module Rails
end
end
- def generate_key
- SecureRandom.hex(OpenSSL::Cipher.new(@cipher).key_len)
- end
-
def key
ENV["RAILS_MASTER_KEY"] || read_key_file || handle_missing_key
end
- def template
- <<-end_of_template.strip_heredoc
- # See `secrets.yml` for tips on generating suitable keys.
- # production:
- # external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289
-
- end_of_template
- end
-
def encrypt(data)
encryptor.encrypt_and_sign(data)
end
@@ -70,10 +57,6 @@ module Rails
writing(read, &block)
end
- def read_template_for_editing(&block)
- writing(template, &block)
- end
-
private
def handle_missing_key
raise MissingKeyError
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
index c24d1ad532..9db9d78ec4 100644
--- a/railties/lib/rails/tasks/engine.rake
+++ b/railties/lib/rails/tasks/engine.rake
@@ -70,6 +70,9 @@ namespace :db do
desc "Retrieves the current schema version number"
app_task "version"
+
+ # desc 'Load the test schema'
+ app_task "test:prepare"
end
def find_engine_path(path)
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
index 9fac0435e4..54934dbe24 100644
--- a/railties/test/application/bin_setup_test.rb
+++ b/railties/test/application/bin_setup_test.rb
@@ -16,8 +16,6 @@ module ApplicationTests
def test_bin_setup
Dir.chdir(app_path) do
- FileUtils.rm_rf("db/migrate")
-
app_file "db/schema.rb", <<-RUBY
ActiveRecord::Schema.define(version: 20140423102712) do
create_table(:articles) {}
@@ -39,8 +37,6 @@ module ApplicationTests
def test_bin_setup_output
Dir.chdir(app_path) do
- FileUtils.rm_rf("db/migrate")
-
app_file "db/schema.rb", ""
output = `bin/setup 2>&1`
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 47c815d221..e5e557d204 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -188,10 +188,11 @@ module ApplicationTests
Rails::Command.send(:remove_const, "APP_PATH")
end
- test "help does not show hidden namespaces" do
+ test "help does not show hidden namespaces and hidden commands" do
FileUtils.cd(rails_root) do
output = rails("generate", "--help")
assert_no_match "active_record:migration", output
+ assert_no_match "credentials", output
output = rails("destroy", "--help")
assert_no_match "active_record:migration", output
diff --git a/railties/test/application/rackup_test.rb b/railties/test/application/rackup_test.rb
index 1dcc2826f0..383f18a7da 100644
--- a/railties/test/application/rackup_test.rb
+++ b/railties/test/application/rackup_test.rb
@@ -26,7 +26,6 @@ module ApplicationTests
test "config.ru can be racked up" do
Dir.chdir app_path do
- FileUtils.rm_rf("db/migrate")
@app = rackup
assert_welcome get("/")
end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 009f2887c9..0235210fdd 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -26,12 +26,12 @@ module ApplicationTests
FileUtils.rm_rf("#{app_path}/config/database.yml")
end
- def db_create_and_drop(expected_database)
+ def db_create_and_drop(expected_database, environment_loaded: true)
Dir.chdir(app_path) do
output = rails("db:create")
assert_match(/Created database/, output)
assert File.exist?(expected_database)
- assert_equal expected_database, ActiveRecord::Base.connection_config[:database]
+ assert_equal expected_database, ActiveRecord::Base.connection_config[:database] if environment_loaded
output = rails("db:drop")
assert_match(/Dropped database/, output)
assert !File.exist?(expected_database)
@@ -49,6 +49,22 @@ module ApplicationTests
db_create_and_drop database_url_db_name
end
+ test "db:create and db:drop respect environment setting" do
+ app_file "config/database.yml", <<-YAML
+ development:
+ database: <%= Rails.application.config.database %>
+ adapter: sqlite3
+ YAML
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ config.database = "db/development.sqlite3"
+ end
+ RUBY
+
+ db_create_and_drop "db/development.sqlite3", environment_loaded: false
+ end
+
def with_database_existing
Dir.chdir(app_path) do
set_database_url
@@ -287,8 +303,6 @@ module ApplicationTests
ENV.delete "RAILS_ENV"
ENV.delete "RACK_ENV"
- Dir.chdir(app_path) { FileUtils.rm_rf("db/migrate") }
-
app_file "db/schema.rb", <<-RUBY
ActiveRecord::Schema.define(version: "1") do
create_table :users do |t|
@@ -310,8 +324,6 @@ module ApplicationTests
end
test "db:setup sets ar_internal_metadata" do
- Dir.chdir(app_path) { FileUtils.rm_rf("db/migrate") }
-
app_file "db/schema.rb", ""
rails "db:setup"
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 30bd283b48..e92a0466dd 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -11,7 +11,6 @@ module ApplicationTests
def setup
build_app
create_schema
- remove_migrations
end
def teardown
@@ -728,10 +727,6 @@ module ApplicationTests
app_file "db/schema.rb", ""
end
- def remove_migrations
- Dir.chdir(app_path) { FileUtils.rm_rf("db/migrate") }
- end
-
def create_test_file(path = :unit, name = "test", pass: true)
app_file "test/#{path}/#{name}_test.rb", <<-RUBY
require 'test_helper'
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index 50238ed682..0a51e98656 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -8,7 +8,6 @@ module ApplicationTests
def setup
build_app
- remove_migrations
end
def teardown
@@ -321,10 +320,6 @@ Expected: ["id", "name"]
end
private
- def remove_migrations
- Dir.chdir(app_path) { FileUtils.rm_rf("db/migrate") }
- end
-
def assert_unsuccessful_run(name, message)
result = run_test_file(name)
assert_not_equal 0, $?.to_i
diff --git a/railties/test/commands/secrets_test.rb b/railties/test/commands/secrets_test.rb
index 66a81826e3..6b9f284a0c 100644
--- a/railties/test/commands/secrets_test.rb
+++ b/railties/test/commands/secrets_test.rb
@@ -8,21 +8,38 @@ require "rails/commands/secrets/secrets_command"
class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation, EnvHelpers
- def setup
- build_app
+ setup :build_app
+ teardown :teardown_app
+
+ test "edit without editor gives hint" do
+ assert_match "No $EDITOR to open decrypted secrets in", run_edit_command(editor: "")
end
- def teardown
- teardown_app
+ test "encrypted secrets are deprecated when using credentials" do
+ assert_match "Encrypted secrets is deprecated", run_setup_command
+ assert_equal 1, $?.exitstatus
+ assert_not File.exist?("config/secrets.yml.enc")
end
- test "edit without editor gives hint" do
- assert_match "No $EDITOR to open decrypted secrets in", run_edit_command(editor: "")
+ test "encrypted secrets are deprecated when running edit without setup" do
+ assert_match "Encrypted secrets is deprecated", run_setup_command
+ assert_equal 1, $?.exitstatus
+ assert_not File.exist?("config/secrets.yml.enc")
+ end
+
+ test "encrypted secrets are deprecated for 5.1 config/secrets.yml apps" do
+ Dir.chdir(app_path) do
+ FileUtils.rm("config/credentials.yml.enc")
+ FileUtils.touch("config/secrets.yml")
+
+ assert_match "Encrypted secrets is deprecated", run_setup_command
+ assert_equal 1, $?.exitstatus
+ assert_not File.exist?("config/secrets.yml.enc")
+ end
end
test "edit secrets" do
- # Runs setup before first edit.
- assert_match(/Adding config\/secrets\.yml\.key to store the encryption key/, run_edit_command)
+ prevent_deprecation
# Run twice to ensure encrypted secrets can be reread after first edit pass.
2.times do
@@ -31,22 +48,30 @@ class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
end
test "show secrets" do
- run_setup_command
+ prevent_deprecation
+
assert_match(/external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289/, run_show_command)
end
private
+ def prevent_deprecation
+ Dir.chdir(app_path) do
+ File.write("config/secrets.yml.key", "f731758c639da2604dfb6bf3d1025de8")
+ File.write("config/secrets.yml.enc", "sEB0mHxDbeP1/KdnMk00wyzPFACl9K6t0cZWn5/Mfx/YbTHvnI07vrneqHg9kaH3wOS7L6pIQteu1P077OtE4BSx/ZRc/sgQPHyWu/tXsrfHqnPNpayOF/XZqizE91JacSFItNMWpuPsp9ynbzz+7cGhoB1S4aPNIU6u0doMrzdngDbijsaAFJmsHIQh6t/QHoJx--8aMoE0PvUWmw1Iqz--ldFqnM/K0g9k17M8PKoN/Q==")
+ end
+ end
+
def run_edit_command(editor: "cat")
switch_env("EDITOR", editor) do
- rails "secrets:edit"
+ rails "secrets:edit", allow_failure: true
end
end
def run_show_command
- rails "secrets:show"
+ rails "secrets:show", allow_failure: true
end
def run_setup_command
- rails "secrets:setup"
+ rails "secrets:setup", allow_failure: true
end
end
diff --git a/railties/test/engine/test_test.rb b/railties/test/engine/test_test.rb
new file mode 100644
index 0000000000..18af85a0aa
--- /dev/null
+++ b/railties/test/engine/test_test.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class Rails::Engine::TestTest < ActiveSupport::TestCase
+ setup do
+ @destination_root = Dir.mktmpdir("bukkits")
+ Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --mountable` }
+ end
+
+ teardown do
+ FileUtils.rm_rf(@destination_root)
+ end
+
+ test "automatically synchronize test schema" do
+ Dir.chdir(plugin_path) do
+ # In order to confirm that migration files are loaded, generate multiple migration files.
+ `bin/rails generate model user name:string;
+ bin/rails generate model todo name:string;
+ RAILS_ENV=development bin/rails db:migrate`
+
+ output = `bin/rails test test/models/bukkits/user_test.rb`
+ assert_includes(output, "0 runs, 0 assertions, 0 failures, 0 errors, 0 skips")
+ end
+ end
+
+ private
+ def plugin_path
+ "#{@destination_root}/bukkits"
+ end
+end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index c5a59edab2..78962ee30b 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -68,7 +68,6 @@ DEFAULT_APP_FILES = %w(
config/spring.rb
config/storage.yml
db
- db/migrate
db/seeds.rb
lib
lib/tasks
@@ -304,6 +303,21 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /^# gem 'mini_magick'/
end
+ def test_active_storage_install
+ command_check = -> command, _ do
+ @binstub_called ||= 0
+ case command
+ when "active_storage:install"
+ @binstub_called += 1
+ assert_equal 1, @binstub_called, "active_storage:install expected to be called once, but was called #{@install_called} times."
+ end
+ end
+
+ generator.stub :rails_command, command_check do
+ quietly { generator.invoke_all }
+ end
+ end
+
def test_app_update_does_not_generate_active_storage_contents_when_skip_active_storage_is_given
app_root = File.join(destination_root, "myapp")
run_generator [app_root, "--skip-active-storage"]
@@ -409,7 +423,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_config_jdbcmysql_database
- run_generator([destination_root, "-d", "jdbcmysql", "--skip-active-storage"])
+ run_generator([destination_root, "-d", "jdbcmysql"])
assert_file "config/database.yml", /mysql/
assert_gem "activerecord-jdbcmysql-adapter"
end
@@ -427,7 +441,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_config_jdbc_database
- run_generator([destination_root, "-d", "jdbc", "--skip-active-storage"])
+ run_generator([destination_root, "-d", "jdbc"])
assert_file "config/database.yml", /jdbc/
assert_file "config/database.yml", /mssql/
assert_gem "activerecord-jdbc-adapter"
diff --git a/railties/test/generators/encrypted_secrets_generator_test.rb b/railties/test/generators/encrypted_secrets_generator_test.rb
deleted file mode 100644
index eacb5166c0..0000000000
--- a/railties/test/generators/encrypted_secrets_generator_test.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-require "generators/generators_test_helper"
-require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
-
-class EncryptedSecretsGeneratorTest < Rails::Generators::TestCase
- include GeneratorsTestHelper
-
- def setup
- super
- cd destination_root
- end
-
- def test_generates_key_file_and_encrypted_secrets_file
- run_generator
-
- assert_file "config/secrets.yml.key", /\w+/
-
- assert File.exist?("config/secrets.yml.enc")
- assert_no_match(/# production:\n# external_api_key: \w+/, IO.binread("config/secrets.yml.enc"))
- assert_match(/# production:\n# external_api_key: \w+/, Rails::Secrets.read)
- end
-
- def test_appends_to_gitignore
- FileUtils.touch(".gitignore")
-
- run_generator
-
- assert_file ".gitignore", /config\/secrets.yml.key/, /(?!config\/secrets.yml.enc)/
- end
-
- def test_warns_when_ignore_is_missing
- assert_match(/Add this to your ignore file/i, run_generator)
- end
-
- def test_doesnt_generate_a_new_key_file_if_already_opted_in_to_encrypted_secrets
- FileUtils.mkdir("config")
- File.open("config/secrets.yml.enc", "w") { |f| f.puts "already secrety" }
-
- run_generator
-
- assert_no_file "config/secrets.yml.key"
- end
-end
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index cb5d8da7b1..ad2a55f496 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -43,9 +43,9 @@ module GeneratorsTestHelper
end
def copy_routes
- routes = File.expand_path("../../lib/rails/generators/rails/app/templates/config/routes.rb", __dir__)
+ routes = File.expand_path("../../lib/rails/generators/rails/app/templates/config/routes.rb.tt", __dir__)
destination = File.join(destination_root, "config")
FileUtils.mkdir_p(destination)
- FileUtils.cp routes, destination
+ FileUtils.cp routes, File.join(destination, "routes.rb")
end
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 06d223e26d..2723f5b256 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -230,7 +230,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
def test_ensure_that_tests_work
- run_generator [destination_root, "--skip-active-storage"]
+ run_generator
FileUtils.cd destination_root
quietly { system "bundle install" }
assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bin/test 2>&1`)
@@ -474,6 +474,8 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_file "test/dummy/Gemfile"
assert_no_file "test/dummy/public/robots.txt"
assert_no_file "test/dummy/README.md"
+ assert_no_file "test/dummy/config/master.key"
+ assert_no_file "test/dummy/config/credentials.yml.enc"
assert_no_directory "test/dummy/lib/tasks"
assert_no_directory "test/dummy/test"
assert_no_directory "test/dummy/vendor"
diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb
index 48a3bcadcd..89c3f1e496 100644
--- a/railties/test/generators/plugin_test_runner_test.rb
+++ b/railties/test/generators/plugin_test_runner_test.rb
@@ -7,7 +7,7 @@ class PluginTestRunnerTest < ActiveSupport::TestCase
def setup
@destination_root = Dir.mktmpdir("bukkits")
- Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --skip-bundle --skip-active-storage` }
+ Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --skip-bundle` }
plugin_file "test/dummy/db/schema.rb", ""
end
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index ed09847443..6b746f3fed 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -63,7 +63,7 @@ module SharedGeneratorTests
end
def test_shebang_is_added_to_rails_file
- run_generator [destination_root, "--ruby", "foo/bar/baz", "--full", "--skip-active-storage"]
+ run_generator [destination_root, "--ruby", "foo/bar/baz", "--full"]
assert_file "bin/rails", /#!foo\/bar\/baz/
end
@@ -222,7 +222,6 @@ module SharedGeneratorTests
end
assert_file "#{application_path}/config/storage.yml"
- assert_directory "#{application_path}/db/migrate"
assert_directory "#{application_path}/storage"
assert_directory "#{application_path}/tmp/storage"
diff --git a/railties/test/generators/test_runner_in_engine_test.rb b/railties/test/generators/test_runner_in_engine_test.rb
index 2acd96ecd4..0e15b5e388 100644
--- a/railties/test/generators/test_runner_in_engine_test.rb
+++ b/railties/test/generators/test_runner_in_engine_test.rb
@@ -7,7 +7,7 @@ class TestRunnerInEngineTest < ActiveSupport::TestCase
def setup
@destination_root = Dir.mktmpdir("bukkits")
- Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --full --skip-bundle --skip-active-storage` }
+ Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --full --skip-bundle` }
plugin_file "test/dummy/db/schema.rb", ""
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 132b3b3a6e..fc710feb63 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -90,6 +90,8 @@ module RailtiesTest
boot_rails
Dir.chdir(app_path) do
+ # Install Active Storage migration file first so as not to affect test.
+ `bundle exec rake active_storage:install`
output = `bundle exec rake bukkits:install:migrations`
["CreateUsers", "AddLastNameToUsers", "CreateSessions"].each do |migration_name|
@@ -175,6 +177,8 @@ module RailtiesTest
boot_rails
Dir.chdir(app_path) do
+ # Install Active Storage migration file first so as not to affect test.
+ `bundle exec rake active_storage:install`
output = `bundle exec rake railties:install:migrations`.split("\n")
assert_match(/Copied migration \d+_create_users\.core_engine\.rb from core_engine/, output.first)
diff --git a/railties/test/secrets_test.rb b/railties/test/secrets_test.rb
index 888fee173a..06877bc76a 100644
--- a/railties/test/secrets_test.rb
+++ b/railties/test/secrets_test.rb
@@ -1,20 +1,13 @@
# frozen_string_literal: true
require "isolation/abstract_unit"
-require "rails/generators"
-require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
require "rails/secrets"
class Rails::SecretsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
- def setup
- build_app
- end
-
- def teardown
- teardown_app
- end
+ setup :build_app
+ teardown :teardown_app
test "setting read to false skips parsing" do
run_secrets_generator do
@@ -172,9 +165,8 @@ class Rails::SecretsTest < ActiveSupport::TestCase
private
def run_secrets_generator
Dir.chdir(app_path) do
- capture(:stdout) do
- Rails::Generators::EncryptedSecretsGenerator.start
- end
+ File.write("config/secrets.yml.key", "f731758c639da2604dfb6bf3d1025de8")
+ File.write("config/secrets.yml.enc", "sEB0mHxDbeP1/KdnMk00wyzPFACl9K6t0cZWn5/Mfx/YbTHvnI07vrneqHg9kaH3wOS7L6pIQteu1P077OtE4BSx/ZRc/sgQPHyWu/tXsrfHqnPNpayOF/XZqizE91JacSFItNMWpuPsp9ynbzz+7cGhoB1S4aPNIU6u0doMrzdngDbijsaAFJmsHIQh6t/QHoJx--8aMoE0PvUWmw1Iqz--ldFqnM/K0g9k17M8PKoN/Q==")
add_to_config <<-RUBY
config.read_encrypted_secrets = true