diff options
22 files changed, 195 insertions, 40 deletions
diff --git a/.travis.yml b/.travis.yml index f3d3c2181a..7bf508b7a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ bundler_args: --jobs 3 --retry 3 before_install: - "rm ${BUNDLE_GEMFILE}.lock" - "travis_retry gem update --system" - - "travis_retry gem install bundler -v '2.0.0.pre.2'" + - "travis_retry gem install bundler" - "[[ -z $encrypted_0fb9444d0374_key && -z $encrypted_0fb9444d0374_iv ]] || openssl aes-256-cbc -K $encrypted_0fb9444d0374_key -iv $encrypted_0fb9444d0374_iv -in activestorage/test/service/configurations.yml.enc -out activestorage/test/service/configurations.yml -d" - "[[ $GEM != 'actioncable:integration' ]] || yarn install" - "[[ $GEM != 'actionview:ujs' ]] || nvm install node" diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index a643484d96..94ccd48203 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -276,9 +276,6 @@ module ActionController # after calling +post+. If the various assert methods are not sufficient, then you # may use this object to inspect the HTTP response in detail. # - # (Earlier versions of \Rails required each functional test to subclass - # Test::Unit::TestCase and define @controller, @request, @response in +setup+.) - # # == Controller is automatically inferred # # ActionController::TestCase will automatically infer the controller under test diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 07e86afe9a..eefe621feb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -157,13 +157,9 @@ module ActiveRecord end end - def types_which_need_no_typecasting - [nil, Numeric, String] - end - def _quote(value) case value - when String, ActiveSupport::Multibyte::Chars + when String, Symbol, ActiveSupport::Multibyte::Chars "'#{quote_string(value.to_s)}'" when true then quoted_true when false then quoted_false @@ -174,7 +170,6 @@ module ActiveRecord when Type::Binary::Data then quoted_binary(value) when Type::Time::Value then "'#{quoted_time(value)}'" when Date, Time then "'#{quoted_date(value)}'" - when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value}'" else raise TypeError, "can't quote #{value.class.name}" end @@ -188,10 +183,9 @@ module ActiveRecord when false then unquoted_false # BigDecimals need to be put in a non-normalized form and quoted. when BigDecimal then value.to_s("F") + when nil, Numeric, String then value when Type::Time::Value then quoted_time(value) when Date, Time then quoted_date(value) - when *types_which_need_no_typecasting - value else raise TypeError end end diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index c29cf1f9a1..c10765f42d 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -77,6 +77,11 @@ module ActiveRecord }] end + # Checks whether the columns hash is already cached for a table. + def columns_hash?(table_name) + @columns_hash.key?(table_name) + end + # Clears out internal caches def clear! @columns.clear diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index fc564d59e2..600825659b 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -282,6 +282,10 @@ module ActiveRecord TypeCaster::Map.new(self) end + def _internal? # :nodoc: + false + end + private def cached_find_by_statement(key, &block) diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 3626a13d7c..88b0c828ae 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -8,6 +8,10 @@ module ActiveRecord # as which environment migrations were run in. class InternalMetadata < ActiveRecord::Base # :nodoc: class << self + def _internal? + true + end + def primary_key "key" end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 7bf8d568df..c2b60610ce 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -96,11 +96,13 @@ module ActiveRecord # When running callbacks is not needed for each record update, # it is preferred to use {update_all}[rdoc-ref:Relation#update_all] # for updating all records in a single query. - def update(id, attributes) + def update(id = :all, attributes) if id.is_a?(Array) id.map { |one_id| find(one_id) }.each_with_index { |object, idx| object.update(attributes[idx]) } + elsif id == :all + all.each { |record| record.update(attributes) } else if ActiveRecord::Base === id raise ArgumentError, diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 538659d6bd..2ee6119158 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -140,7 +140,19 @@ end_error initializer "active_record.define_attribute_methods" do |app| config.after_initialize do ActiveSupport.on_load(:active_record) do - descendants.each(&:define_attribute_methods) if app.config.eager_load + if app.config.eager_load + descendants.each do |model| + # SchemaMigration and InternalMetadata both override `table_exists?` + # to bypass the schema cache, so skip them to avoid the extra queries. + next if model._internal? + + # If there's no connection yet, or the schema cache doesn't have the columns + # hash for the model cached, `define_attribute_methods` would trigger a query. + next unless model.connected? && model.connection.schema_cache.columns_hash?(model.table_name) + + model.define_attribute_methods + end + end end end end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 3ef6e7928f..c2c4a5a882 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -401,7 +401,7 @@ module ActiveRecord case operation when "count" then value.to_i when "sum" then type.deserialize(value || 0) - when "average" then value.respond_to?(:to_d) ? value.to_d : value + when "average" then value&.respond_to?(:to_d) ? value.to_d : value else type.deserialize(value) end end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index f2d8b038fa..1fca1a18f6 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -10,6 +10,10 @@ module ActiveRecord # to be executed the next time. class SchemaMigration < ActiveRecord::Base # :nodoc: class << self + def _internal? + true + end + def primary_key "version" end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 5b5202d167..ade6b9e832 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -57,12 +57,8 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 3, value end - def test_should_return_nil_to_d_as_average - if nil.respond_to?(:to_d) - assert_equal BigDecimal(0), NumericData.average(:bank_balance) - else - assert_nil NumericData.average(:bank_balance) - end + def test_should_return_nil_as_average + assert_nil NumericData.average(:bank_balance) end def test_should_get_maximum_of_field diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index 67496381d1..727cab77f5 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -91,6 +91,22 @@ module ActiveRecord @cache.clear_data_source_cache!("posts") end + test "#columns_hash? is populated by #columns_hash" do + assert_not @cache.columns_hash?("posts") + + @cache.columns_hash("posts") + + assert @cache.columns_hash?("posts") + end + + test "#columns_hash? is not populated by #data_source_exists?" do + assert_not @cache.columns_hash?("posts") + + @cache.data_source_exists?("posts") + + assert_not @cache.columns_hash?("posts") + end + private def schema_dump_path diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 4830ff2b5f..d5057ad381 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -53,6 +53,20 @@ class PersistenceTest < ActiveRecord::TestCase assert_not_equal "2 updated", Topic.find(2).content end + def test_class_level_update_without_ids + topics = Topic.all + assert_equal 5, topics.length + topics.each do |topic| + assert_not_equal "updated", topic.content + end + + updated = Topic.update(content: "updated") + assert_equal 5, updated.length + updated.each do |topic| + assert_equal "updated", topic.content + end + end + def test_class_level_update_is_affected_by_scoping topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } } diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb index b630f782bc..402ddcf05a 100644 --- a/activerecord/test/cases/reaper_test.rb +++ b/activerecord/test/cases/reaper_test.rb @@ -48,7 +48,7 @@ module ActiveRecord reaper = ConnectionPool::Reaper.new(fp, 0.0001) reaper.run - until fp.reaped + until fp.flushed Thread.pass end assert fp.reaped diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 3c8fce222d..938cfdb914 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -16,7 +16,7 @@ module ActiveSupport # module ActiveRecord # class LogSubscriber < ActiveSupport::LogSubscriber # def sql(event) - # "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}" + # info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}" # end # end # end @@ -47,8 +47,8 @@ module ActiveSupport # if exception # exception_object = event.payload[:exception_object] # - # "[ERROR] #{event.payload[:name]}: #{exception.join(', ')} " \ - # "(#{exception_object.backtrace.first})" + # error "[ERROR] #{event.payload[:name]}: #{exception.join(', ')} " \ + # "(#{exception_object.backtrace.first})" # else # # standard logger code # end diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index f9b8f3208d..c1c3832b79 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -255,13 +255,16 @@ Active Record ### sql.active_record -| Key | Value | -| ---------------- | ---------------------------------------- | -| `:sql` | SQL statement | -| `:name` | Name of the operation | -| `:connection_id` | `self.object_id` | -| `:binds` | Bind parameters | -| `:cached` | `true` is added when cached queries used | +| Key | Value | +| -------------------- | ---------------------------------------- | +| `:sql` | SQL statement | +| `:name` | Name of the operation | +| `:connection_id` | Object ID of the connection object | +| `:connection` | Connection object | +| `:binds` | Bind parameters | +| `:type_casted_binds` | Typecasted bind parameters | +| `:statement_name` | SQL Statement name | +| `:cached` | `true` is added when cached queries used | INFO. The adapters will add their own data as well. @@ -270,7 +273,10 @@ INFO. The adapters will add their own data as well. sql: "SELECT \"posts\".* FROM \"posts\" ", name: "Post Load", connection_id: 70307250813140, - binds: [] + connection: #<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f7a838850>, + binds: [#<ActiveModel::Attribute::WithCastValue:0x00007fe19d15dc00>], + type_casted_binds: [11], + statement_name: nil } ``` diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 3b21197ae4..36e7f8ff80 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -441,7 +441,7 @@ The schema dumper adds two additional configuration options: * `config.action_controller.per_form_csrf_tokens` configures whether CSRF tokens are only valid for the method/action they were generated for. -* `config.action_controller.default_protect_from_forgery` determines whether forgery protection is added on `ActionController:Base`. This is false by default, but enabled when loading defaults for Rails 5.2. +* `config.action_controller.default_protect_from_forgery` determines whether forgery protection is added on `ActionController:Base`. This is false by default. * `config.action_controller.relative_url_root` can be used to tell Rails that you are [deploying to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). The default is `ENV['RAILS_RELATIVE_URL_ROOT']`. @@ -738,7 +738,7 @@ There are a few configuration options available in Active Support: * `config.active_support.use_sha1_digests` specifies whether to use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. Defaults to false. -* `config.active_support.use_authenticated_message_encryption` specifies whether to use AES-256-GCM authenticated encryption as the default cipher for encrypting messages instead of AES-256-CBC. This is false by default, but enabled when loading defaults for Rails 5.2. +* `config.active_support.use_authenticated_message_encryption` specifies whether to use AES-256-GCM authenticated encryption as the default cipher for encrypting messages instead of AES-256-CBC. This is false by default. * `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`. @@ -876,6 +876,36 @@ text/javascript image/svg+xml application/postscript application/x-shockwave-fla The default is `/rails/active_storage` +### Results of `load_defaults` + +#### With '5.0': + +- `config.action_controller.per_form_csrf_tokens`: `true` +- `config.action_controller.forgery_protection_origin_check`: `true` +- `ActiveSupport.to_time_preserves_timezone`: `true` +- `config.active_record.belongs_to_required_by_default`: `true` +- `config.ssl_options`: `{ hsts: { subdomains: true } }` + +#### With '5.1': + +- `config.assets.unknown_asset_fallback`: `false` +- `config.action_view.form_with_generates_remote_forms`: `true` + +#### With '5.2': + +- `config.active_record.cache_versioning`: `true` +- `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer`: `true` +- `action_dispatch.use_authenticated_cookie_encryption`: `true` +- `config.active_support.use_authenticated_message_encryption`: `true` +- `config.active_support.use_sha1_digests`: `true` +- `config.action_controller.default_protect_from_forgery`: `true` +- `config.action_view.form_with_generates_ids`: `true` + +#### With '6.0': + +- `config.action_view.default_enforce_utf8`: `false` +- `config.action_dispatch.use_cookies_with_metadata`: `true` +- `config.active_job.return_false_on_aborted_enqueue`: `true` ### Configuring a Database diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 576f3be019..7b30f8b9ca 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -44,6 +44,9 @@ module Rails default: false, desc: "Skip Action Mailer files" + class_option :skip_action_mailbox, type: :boolean, default: false, + desc: "Skip Action Mailbox gem" + class_option :skip_active_record, type: :boolean, aliases: "-O", default: false, desc: "Skip Active Record files" @@ -231,7 +234,7 @@ module Rails end def skip_action_mailbox? # :doc: - skip_active_storage? + options[:skip_action_mailbox] || skip_active_storage? end class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out) diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index fb264935bd..1ad3a4b1f7 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -26,9 +26,6 @@ ruby <%= "'#{RUBY_VERSION}'" -%> # gem 'image_processing', '~> 1.2' <% end -%> -# Use Capistrano for deployment -# gem 'capistrano-rails', group: :development - <% if depend_on_bootsnap? -%> # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.1.0', require: false diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 886fb0f843..8eaf07586e 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -331,7 +331,7 @@ module ApplicationTests assert_not_includes Post.instance_methods, :title end - test "eager loads attribute methods in production" do + test "does not eager load attribute methods in production when the schema cache is empty" do app_file "app/models/post.rb", <<-RUBY class Post < ActiveRecord::Base end @@ -354,9 +354,71 @@ module ApplicationTests app "production" + assert_not_includes Post.instance_methods, :title + end + + test "eager loads attribute methods in production when the schema cache is populated" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app_file "config/initializers/schema_cache.rb", <<-RUBY + ActiveRecord::Base.connection.schema_cache.add("posts") + RUBY + + app "production" + assert_includes Post.instance_methods, :title end + test "does not attempt to eager load attribute methods for models that aren't connected" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app_file "app/models/comment.rb", <<-RUBY + class Comment < ActiveRecord::Base + establish_connection(adapter: "mysql2", database: "does_not_exist") + end + RUBY + + assert_nothing_raised do + app "production" + end + end + test "initialize an eager loaded, cache classes app" do add_to_config <<-RUBY config.eager_load = true diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 7d3b031416..f287827f81 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -435,6 +435,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_file "#{app_root}/config/storage.yml" end + def test_generator_skips_action_mailbox_when_skip_action_mailbox_is_given + run_generator [destination_root, "--skip-action-mailbox"] + assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailbox\/engine["']/ + end + def test_generator_skips_action_mailbox_when_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_file "#{application_path}/config/application.rb", /#\s+require\s+["']action_mailbox\/engine["']/ diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 7441ab0603..94f69277f5 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -127,6 +127,7 @@ module SharedGeneratorTests "--skip-active-record", "--skip-active-storage", "--skip-action-mailer", + "--skip-action-mailbox", "--skip-action-cable", "--skip-sprockets" ] @@ -138,6 +139,9 @@ module SharedGeneratorTests assert_file "#{application_path}/config/application.rb", /^# require\s+["']active_storage\/engine["']/ assert_file "#{application_path}/config/application.rb", /^require\s+["']action_controller\/railtie["']/ assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_mailer\/railtie["']/ + unless generator_class.name == "Rails::Generators::PluginGenerator" + assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_mailbox\/engine["']/ + end assert_file "#{application_path}/config/application.rb", /^require\s+["']action_view\/railtie["']/ assert_file "#{application_path}/config/application.rb", /^# require\s+["']action_cable\/engine["']/ assert_file "#{application_path}/config/application.rb", /^# require\s+["']sprockets\/railtie["']/ |