From 955798bd951b3f46e4919cc2c5bcf233b8745ac1 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Tue, 28 Nov 2023 14:41:21 +0200 Subject: [PATCH 01/25] Fix duplicate `DEFERRABLE` directive added for foreign keys in PostgreSQL and SQLite --- .../connection_adapters/postgresql/schema_creation.rb | 1 - .../connection_adapters/sqlite3/schema_creation.rb | 6 ------ activerecord/test/cases/migration/foreign_key_test.rb | 4 +++- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb index c28312d74fb34..14128a18320eb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb @@ -18,7 +18,6 @@ def visit_AlterTable(o) def visit_AddForeignKey(o) super.dup.tap do |sql| - sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable sql << " NOT VALID" unless o.validate? end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb index b8acbbc5cc497..0e7bf37bf77fc 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb @@ -5,12 +5,6 @@ module ConnectionAdapters module SQLite3 class SchemaCreation < SchemaCreation # :nodoc: private - def visit_AddForeignKey(o) - super.dup.tap do |sql| - sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable - end - end - def visit_ForeignKeyDefinition(o) super.dup.tap do |sql| sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index ac5b88aa3f768..5bd97e12abd48 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -519,7 +519,9 @@ def test_add_invalid_foreign_key if ActiveRecord::Base.connection.supports_deferrable_constraints? def test_deferrable_foreign_key - @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", deferrable: :immediate + assert_queries_match(/\("id"\)\s+DEFERRABLE INITIALLY IMMEDIATE\W*\z/i) do + @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", deferrable: :immediate + end foreign_keys = @connection.foreign_keys("astronauts") assert_equal 1, foreign_keys.size From 35d76643398e4d3f3d32770cd8283819d582ac27 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Mon, 2 Sep 2024 14:00:40 -0400 Subject: [PATCH 02/25] Hoist `nil` check out of loop --- .../lib/active_support/core_ext/object/json.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb index 77b7924f92c69..27dfd7ecf485e 100644 --- a/activesupport/lib/active_support/core_ext/object/json.rb +++ b/activesupport/lib/active_support/core_ext/object/json.rb @@ -164,7 +164,11 @@ def as_json(options = nil) # :nodoc: class Array def as_json(options = nil) # :nodoc: - map { |v| options ? v.as_json(options.dup) : v.as_json } + if options + map { |v| v.as_json(options.dup) } + else + map { |v| v.as_json } + end end end @@ -184,8 +188,10 @@ def as_json(options = nil) # :nodoc: end result = {} - subset.each do |k, v| - result[k.to_s] = options ? v.as_json(options.dup) : v.as_json + if options + subset.each { |k, v| result[k.to_s] = v.as_json(options.dup) } + else + subset.each { |k, v| result[k.to_s] = v.as_json } end result end From 91e8acc1a5212b128c55fa9996b68a26181f7a57 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Wed, 4 Sep 2024 13:14:58 +0300 Subject: [PATCH 03/25] Fix updating nested attributes for models with composite primary keys --- .../lib/active_record/nested_attributes.rb | 15 +++++++++++++-- activerecord/test/cases/nested_attributes_test.rb | 10 ++++++++++ activerecord/test/models/cpk/book.rb | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 35cddce6838d8..e1bab0e7a49b4 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -524,12 +524,12 @@ def assign_nested_attributes_for_collection_association(association_name, attrib unless reject_new_record?(association_name, attributes) association.reader.build(attributes.except(*UNASSIGNABLE_KEYS)) end - elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s } + elsif existing_record = find_record_by_id(existing_records, attributes["id"]) unless call_reject_if(association_name, attributes) # Make sure we are operating on the actual object which is in the association's # proxy_target array (either by finding it, or adding it if not found) # Take into account that the proxy_target may have changed due to callbacks - target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s } + target_record = find_record_by_id(association.target, attributes["id"]) if target_record existing_record = target_record else @@ -620,5 +620,16 @@ def raise_nested_attributes_record_not_found!(association_name, record_id) raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}", model, "id", record_id) end + + def find_record_by_id(records, id) + return if records.empty? + + if records.first.class.composite_primary_key? + id = Array(id).map(&:to_s) + records.find { |record| Array(record.id).map(&:to_s) == id } + else + records.find { |record| record.id.to_s == id.to_s } + end + end end end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index f40ed00766a32..1c8dd7d6d8895 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -14,6 +14,7 @@ require "models/pet" require "models/entry" require "models/message" +require "models/cpk" require "active_support/hash_with_indifferent_access" class TestNestedAttributesInGeneral < ActiveRecord::TestCase @@ -232,6 +233,15 @@ def test_should_not_create_duplicates_with_create_with ) end end + + def test_updating_models_with_cpk_provided_as_strings + book = Cpk::Book.create!(id: [1, 2], shop_id: 3) + book.chapters.create!(id: [1, 3], title: "Title") + + book.update!(chapters_attributes: { id: ["1", "3"], title: "New title" }) + assert_equal 1, book.reload.chapters.count + assert_equal "New title", book.chapters.first.title + end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/models/cpk/book.rb b/activerecord/test/models/cpk/book.rb index 10e73d933d3d0..d9464893372be 100644 --- a/activerecord/test/models/cpk/book.rb +++ b/activerecord/test/models/cpk/book.rb @@ -10,6 +10,7 @@ class Book < ActiveRecord::Base belongs_to :author, class_name: "Cpk::Author" has_many :chapters, foreign_key: [:author_id, :book_id] + accepts_nested_attributes_for :chapters before_destroy :prevent_destroy_if_set From 9259af6556678752418a2e37914802856e671dbb Mon Sep 17 00:00:00 2001 From: Jim Nanney Date: Tue, 3 Sep 2024 21:21:04 -0500 Subject: [PATCH 04/25] [Fix 52791] Revert "Remove `ActiveRecord::AttributeAssignment#assign_nested_parameter_attributes`" This reverts commit ded026b6c99d93f6b8b3711507717a9c02e0f0d5. This broke existing behavior that allowed a has_one to be updated when a new record was being created. --- activerecord/lib/active_record/attribute_assignment.rb | 10 +++++++++- activerecord/test/cases/nested_attributes_test.rb | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index 2fbdf4e2dc4fa..bd246578ab2ed 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -4,21 +4,29 @@ module ActiveRecord module AttributeAssignment private def _assign_attributes(attributes) - multi_parameter_attributes = nil + multi_parameter_attributes = nested_parameter_attributes = nil attributes.each do |k, v| key = k.to_s if key.include?("(") (multi_parameter_attributes ||= {})[key] = v + elsif v.is_a?(Hash) + (nested_parameter_attributes ||= {})[key] = v else _assign_attribute(key, v) end end + assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes end + # Assign any deferred nested attributes after the base attributes have been set. + def assign_nested_parameter_attributes(pairs) + pairs.each { |k, v| _assign_attribute(k, v) } + end + # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done # by calling new on the column type or aggregation type (through composed_of) object with these parameters. # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index f40ed00766a32..8c45fc69d3197 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -362,6 +362,15 @@ def test_should_work_with_update_as_well assert_equal "Mister Pablo", @pirate.ship.name end + def test_should_defer_updating_nested_associations_until_after_base_attributes_are_set + ship = @pirate.ship + + ship_part = ShipPart.new + ship_part.attributes = { ship_attributes: { name: "Prometheus" }, ship_id: ship.id } + + assert_equal "Prometheus", ship_part.ship.name + end + def test_should_not_destroy_the_associated_model_until_the_parent_is_saved @pirate.attributes = { ship_attributes: { id: @ship.id, _destroy: "1" } } From 28780de1d7fa22c452d479fb9fe4c70858833e0b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 4 Sep 2024 11:49:44 -0700 Subject: [PATCH 05/25] Add Solid Cache (and get ready for Solid Queue) (#52790) * Add Solid Cache (and get ready for Solid Queue) --- Gemfile | 1 + Gemfile.lock | 5 ++++ railties/CHANGELOG.md | 4 ++++ railties/lib/rails/generators/app_base.rb | 13 ++++++++++ .../generators/rails/app/app_generator.rb | 1 + .../generators/rails/app/templates/Gemfile.tt | 5 ++++ .../templates/config/databases/mysql.yml.tt | 13 ++++++++++ .../config/databases/postgresql.yml.tt | 13 ++++++++++ .../templates/config/databases/sqlite3.yml.tt | 24 ++++++++++++++++++- .../templates/config/databases/trilogy.yml.tt | 13 ++++++++++ .../rails/plugin/plugin_generator.rb | 1 + .../test/generators/app_generator_test.rb | 19 ++++++++++++++- 12 files changed, 110 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 43d2b165d293c..816f184a5c448 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,7 @@ gem "cssbundling-rails" gem "importmap-rails", ">= 1.2.3" gem "tailwindcss-rails" gem "dartsass-rails" +gem "solid_cache" gem "kamal", require: false gem "thruster", require: false # require: false so bcrypt is loaded only when has_secure_password is used. diff --git a/Gemfile.lock b/Gemfile.lock index 312a997fbe7d1..11fc857ee554a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -545,6 +545,10 @@ GEM rake serverengine (~> 2.0.5) thor + solid_cache (1.0.4) + activejob (>= 7.2) + activerecord (>= 7.2) + railties (>= 7.2) sorted_set (1.0.3) rbtree set (~> 1.0) @@ -691,6 +695,7 @@ DEPENDENCIES selenium-webdriver (>= 4.20.0) sidekiq sneakers + solid_cache sprockets-rails (>= 2.0.0) sqlite3 (>= 2.0) stackprof diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 1f66d454f90b3..ef64c4fb458ab 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Use [Solid Cache](https://github.com/rails/solid_cache) as the default Rails.cache backend in production, configured as a separate cache database in config/database.yml. + + *DHH* + * Add Rails::Rack::SilenceRequest middleware and use it via `config.silence_healthcheck_path = path` to silence requests to "/up". This prevents the Kamal-required healthchecks from clogging up the production logs. diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 7d519ce3cf71d..761af5f0de1b6 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -114,6 +114,9 @@ def self.add_shared_options_for(name) class_option :skip_kamal, type: :boolean, default: false, desc: "Skip Kamal setup" + class_option :skip_solid, type: :boolean, default: false, + desc: "Skip Solid Cache & Queue setup" + class_option :dev, type: :boolean, default: nil, desc: "Set up the #{name} with Gemfile pointing to your Rails checkout" @@ -428,6 +431,10 @@ def skip_kamal? options[:skip_kamal] end + def skip_solid? + options[:skip_active_record] || options[:skip_solid] + end + class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out) def initialize(name, version, comment, options = {}, commented_out = false) super @@ -746,6 +753,12 @@ def run_kamal template "config/deploy.yml", force: true end + def run_solid + return if skip_solid? || !bundle_install? + + rails_command "solid_cache:install" + end + def add_bundler_platforms if bundle_install? # The vast majority of Rails apps will be deployed on `x86_64-linux`. diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 39deff5dbee74..455a57e98d6d7 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -575,6 +575,7 @@ def finish_template public_task :run_hotwire public_task :run_css public_task :run_kamal + public_task :run_solid def run_after_bundle_callbacks @after_bundle_callbacks.each(&:call) diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index c27d9f87e0e99..17d1b1aafab1a 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -14,6 +14,11 @@ source "https://rubygems.org" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data", platforms: %i[ <%= bundler_windows_platforms %> jruby ] +<% unless options.skip_solid? -%> + +# Use the database-backed Solid Cache adapter for Rails.cache [https://github.com/rails/solid_cache] +gem "solid_cache" +<% end -%> <% if depend_on_bootsnap? -%> # Reduces boot times through caching; required in config/boot.rb diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt index e90be60d2f6d8..2f791f02ad737 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt @@ -52,8 +52,21 @@ test: # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database # for a full overview on how database connection configuration can be specified. # +<%- if options.skip_solid? -%> production: <<: *default database: <%= app_name %>_production username: <%= app_name %> password: <%%= ENV["<%= app_name.upcase %>_DATABASE_PASSWORD"] %> +<%- else -%> +production: + primary: &primary_production + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV["<%= app_name.upcase %>_DATABASE_PASSWORD"] %> + cache: + <<: *primary_production + database: <%= app_name %>_production_cache + migrations_paths: db/cache_migrate +<%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt index 590e5d5d93d03..fcb902bfb6deb 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt @@ -84,8 +84,21 @@ test: # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database # for a full overview on how database connection configuration can be specified. # +<%- if options.skip_solid? -%> production: <<: *default database: <%= app_name %>_production username: <%= app_name %> password: <%%= ENV["<%= app_name.upcase %>_DATABASE_PASSWORD"] %> +<%- else -%> +production: + primary: &primary_production + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV["<%= app_name.upcase %>_DATABASE_PASSWORD"] %> + cache: + <<: *primary_production + database: <%= app_name %>_production_cache + migrations_paths: db/cache_migrate +<%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt index 3875752cf72d9..8f855a756941d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt @@ -28,13 +28,35 @@ test: # # Similarly, if you deploy your application as a Docker container, you must # ensure the database is located in a persisted volume. +<%- if options.skip_solid? -%> production: <<: *default # database: path/to/persistent/storage/production.sqlite3 <%- else -%> +production: + primary: + <<: *default + # database: path/to/persistent/storage/production.sqlite3 + cache: + <<: *default + # database: path/to/persistent/storage/production_cache.sqlite3 + migrations_paths: db/cache_migrate +<%- end -%> +<%- else -%> # Store production database in the storage/ directory, which by default # is mounted as a persistent Docker volume in config/deploy.yml. +<%- if options.skip_solid? -%> production: <<: *default database: storage/production.sqlite3 -<%- end -%> \ No newline at end of file +<%- else -%> +production: + primary: + <<: *default + database: storage/production.sqlite3 + cache: + <<: *default + database: storage/production_cache.sqlite3 + migrations_paths: db/cache_migrate +<%- end -%> +<%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt index 73b9000f4cc04..7367e9fbe3b71 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt @@ -52,8 +52,21 @@ test: # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database # for a full overview on how database connection configuration can be specified. # +<%- if options.skip_solid? -%> production: <<: *default database: <%= app_name %>_production username: <%= app_name %> password: <%%= ENV["<%= app_name.upcase %>_DATABASE_PASSWORD"] %> +<%- else -%> +production: + primary: &primary_production + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV["<%= app_name.upcase %>_DATABASE_PASSWORD"] %> + cache: + <<: *primary_production + database: <%= app_name %>_production_cache + migrations_paths: db/cache_migrate +<%- end -%> diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index c3fbe27eea34d..85b664298417b 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -127,6 +127,7 @@ def generate_test_dummy(force = false) opts[:skip_bundle] = true opts[:skip_ci] = true opts[:skip_kamal] = true + opts[:skip_solid] = true opts[:skip_git] = true opts[:skip_hotwire] = true opts[:skip_rubocop] = true diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 0a6d1c83f78f7..2b7865efbf56d 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -638,6 +638,15 @@ def test_ci_files_are_skipped_if_required assert_no_file ".github/dependabot.yml" end + def test_configuration_of_solid + generator [destination_root] + run_generator_instance + assert_gem "solid_cache" + assert_file "config/database.yml" do |content| + assert_match(%r{cache:}, content) + end + end + def test_inclusion_of_kamal_files generator [destination_root] run_generator_instance @@ -795,6 +804,14 @@ def test_skip_active_job_option end end + def test_skip_solid_option + generator([destination_root], skip_solid: true) + run_generator_instance + + assert_not_includes @rails_commands, "solid_cache:install", "`solid_cache:install` expected to not be called." + assert_no_gem "solid_cache" + end + def test_skip_javascript_option generator([destination_root], skip_javascript: true) @@ -969,7 +986,7 @@ def test_default_generator_executes_all_rails_commands run_generator_instance expected_commands = [ - "credentials:diff --enroll", "importmap:install", "turbo:install stimulus:install" + "credentials:diff --enroll", "importmap:install", "turbo:install stimulus:install", "solid_cache:install" ] assert_equal expected_commands, @rails_commands end From 0952b2a0a2a741dc6d1b873890d1b8f1210ee9cd Mon Sep 17 00:00:00 2001 From: Alexandre Ruban Date: Wed, 4 Sep 2024 21:06:22 +0200 Subject: [PATCH 06/25] Fix authentication generator double signature (#52786) Before this commit, the session id stored in the cookies was signed twice: - Once with `cookies.signed` - Once with `session.signed_id` ```rb def set_current_session(session) # ... cookies.signed.permanent[:session_token] = { value: session.signed_id, httponly: true, same_site: :lax } end ``` This commit removes the double signature. --- .../templates/controllers/concerns/authentication.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/railties/lib/rails/generators/rails/authentication/templates/controllers/concerns/authentication.rb b/railties/lib/rails/generators/rails/authentication/templates/controllers/concerns/authentication.rb index 5873dd7b1bfe8..67c12b50a562d 100644 --- a/railties/lib/rails/generators/rails/authentication/templates/controllers/concerns/authentication.rb +++ b/railties/lib/rails/generators/rails/authentication/templates/controllers/concerns/authentication.rb @@ -29,9 +29,7 @@ def resume_session end def find_session_by_cookie - if token = cookies.signed[:session_token] - Session.find_signed(token) - end + Session.find_by(id: cookies.signed[:session_id]) end @@ -53,11 +51,11 @@ def start_new_session_for(user) def set_current_session(session) Current.session = session - cookies.signed.permanent[:session_token] = { value: session.signed_id, httponly: true, same_site: :lax } + cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax } end def terminate_session Current.session.destroy - cookies.delete(:session_token) + cookies.delete(:session_id) end end From 21670a846820be754f0262592d9d58bb3b3e904f Mon Sep 17 00:00:00 2001 From: Andrew Novoselac Date: Wed, 4 Sep 2024 20:20:37 -0400 Subject: [PATCH 07/25] Update devcontainer to use ruby 3.3.5 --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 8858b8410834b..9b89974cec15a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,7 +1,7 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.191.1/containers/ruby/.devcontainer/base.Dockerfile # [Choice] Ruby version: 3, 3.3, 3.2, 3.1, 3.0, 2, 2.7, 2.6 -ARG VARIANT="3.3.4" +ARG VARIANT="3.3.5" FROM ghcr.io/rails/devcontainer/images/ruby:${VARIANT} # [Optional] Uncomment this section to install additional OS packages. From fe92250a5c6182af0faec32c9518df48bcae51fd Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 12 Apr 2024 12:54:27 +0200 Subject: [PATCH 08/25] Add an internal API to trigger association loading asynchronously This is not yet a public API, I'd like to experiment with a few ideas and run it by a few people first. But that's pretty much all the necessary plumbing. One big limitation right now is with `has_many through`, as it requires two queries back to back. I'm not sure how to best handle that, but I think worst case we could at least trigger the first of the two, that would already be a win. --- .../active_record/associations/association.rb | 30 +++++++-- .../has_many_through_association.rb | 3 +- .../associations/singular_association.rb | 11 +++- activerecord/lib/active_record/core.rb | 4 +- activerecord/lib/active_record/querying.rb | 10 ++- activerecord/lib/active_record/relation.rb | 10 +++ .../lib/active_record/statement_cache.rb | 11 ++-- .../belongs_to_associations_test.rb | 34 ++++++++++ .../has_many_associations_test.rb | 33 ++++++++++ .../associations/has_one_associations_test.rb | 34 ++++++++++ activerecord/test/cases/helper.rb | 65 ++++++++++++------- .../test/cases/relation/load_async_test.rb | 15 ----- 12 files changed, 200 insertions(+), 60 deletions(-) diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 237234f07c763..ca89709cb8647 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -34,7 +34,7 @@ module Associations # the reflection object represents a :has_many macro. class Association # :nodoc: attr_accessor :owner - attr_reader :target, :reflection, :disable_joins + attr_reader :reflection, :disable_joins delegate :options, to: :reflection @@ -50,6 +50,13 @@ def initialize(owner, reflection) @skip_strict_loading = nil end + def target + if @target.is_a?(Promise) + @target = @target.value + end + @target + end + # Resets the \loaded flag to +false+ and sets the \target to +nil+. def reset @loaded = false @@ -172,7 +179,7 @@ def extensions # ActiveRecord::RecordNotFound is rescued within the method, and it is # not reraised. The proxy is \reset and +nil+ is the return value. def load_target - @target = find_target if (@stale_state && stale_target?) || find_target? + @target = find_target(async: false) if (@stale_state && stale_target?) || find_target? loaded! unless loaded? target @@ -180,6 +187,13 @@ def load_target reset end + def async_load_target # :nodoc: + @target = find_target(async: true) if (@stale_state && stale_target?) || find_target? + + loaded! unless loaded? + nil + end + # We can't dump @reflection and @through_reflection since it contains the scope proc def marshal_dump ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] } @@ -223,13 +237,19 @@ def ensure_klass_exists! klass end - def find_target + def find_target(async: false) if violates_strict_loading? Base.strict_loading_violation!(owner: owner.class, reflection: reflection) end scope = self.scope - return scope.to_a if skip_statement_cache?(scope) + if skip_statement_cache?(scope) + if async + return scope.load_async.then(&:to_a) + else + return scope.to_a + end + end sc = reflection.association_scope_cache(klass, owner) do |params| as = AssociationScope.create { params.bind } @@ -238,7 +258,7 @@ def find_target binds = AssociationScope.get_bind_values(owner, reflection.chain) klass.with_connection do |c| - sc.execute(binds, c) do |record| + sc.execute(binds, c, async: async) do |record| set_inverse_instance(record) if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many record.strict_loading! diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 700189f02a963..50ebb7ca32354 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -216,7 +216,8 @@ def delete_through_records(records) end end - def find_target + def find_target(async: false) + raise NotImplementedError, "No async loading for HasManyThroughAssociation yet" if async return [] unless target_reflection_has_associated_record? return scope.to_a if disable_joins super diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index f89936d0d06ea..66fa00e129a94 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -18,6 +18,7 @@ def reader def reset super @target = nil + @future_target = nil end # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar @@ -43,11 +44,15 @@ def scope_for_create super.except!(*Array(klass.primary_key)) end - def find_target + def find_target(async: false) if disable_joins - scope.first + if async + scope.load_async.then(&:first) + else + scope.first + end else - super.first + super.then(&:first) end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index c0e3c652d4053..f59cd256843af 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -432,8 +432,8 @@ def cached_find_by(keys, values) where(wheres).limit(1) } - begin - statement.execute(values.flatten, connection, allow_retry: true).first + statement.execute(values.flatten, connection, allow_retry: true).then do |r| + r.first rescue TypeError raise ActiveRecord::StatementInvalid end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 82f779639fb12..b106ebc10aed8 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -56,12 +56,10 @@ def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block) end # Same as #find_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise. - def async_find_by_sql(sql, binds = [], preparable: nil, &block) - result = with_connection do |c| - _query_by_sql(c, sql, binds, preparable: preparable, async: true) - end - - result.then do |result| + def async_find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block) + with_connection do |c| + _query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry, async: true) + end.then do |result| _load_from_sql(result, &block) end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 5528e2d670c15..668758c8483a9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1150,6 +1150,16 @@ def load_async self end + def then(&block) # :nodoc: + if @future_result + @future_result.then do + yield self + end + else + super + end + end + # Returns true if the relation was scheduled on the background # thread pool. def scheduled? diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index 5dfd78c9d1c18..15bf999767034 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -142,14 +142,17 @@ def initialize(query_builder, bind_map, model) @model = model end - def execute(params, connection, allow_retry: false, &block) + def execute(params, connection, allow_retry: false, async: false, &block) bind_values = @bind_map.bind params - sql = @query_builder.sql_for bind_values, connection - @model.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block) + if async + @model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block) + else + @model.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block) + end rescue ::RangeError - [] + async ? Promise.wrap([]) : [] end def self.unsupported_value?(value) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 834a55c181bc8..0e357dbf86e48 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1839,3 +1839,37 @@ def test_destroy_linked_models assert_not Author.exists?(author.id) end end + +class AsyncBelongsToAssociationsTest < ActiveRecord::TestCase + include WaitForAsyncTestHelper + + self.use_transactional_tests = false + + fixtures :companies + + unless in_memory_db? + def test_async_load_belongs_to + client = Client.find(3) + first_firm = companies(:first_firm) + + client.association(:firm).async_load_target + wait_for_async_query + + events = [] + callback = -> (event) do + events << event unless event.payload[:name] == "SCHEMA" + end + ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do + client.firm + end + + assert_no_queries do + assert_equal first_firm, client.firm + assert_equal first_firm.name, client.firm.name + end + + assert_equal 1, events.size + assert_equal true, events.first.payload[:async] + end + end +end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 68c29c00696f3..6357ffbc2722e 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -3252,3 +3252,36 @@ def force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.load_target end end + +class AsyncHasOneAssociationsTest < ActiveRecord::TestCase + include WaitForAsyncTestHelper + + self.use_transactional_tests = false + + fixtures :companies + + unless in_memory_db? + def test_async_load_has_many + firm = companies(:first_firm) + + firm.association(:clients).async_load_target + wait_for_async_query + + events = [] + callback = -> (event) do + events << event unless event.payload[:name] == "SCHEMA" + end + + ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do + assert_equal 3, firm.clients.size + end + + assert_no_queries do + assert_not_nil firm.clients[2] + end + + assert_equal 1, events.size + assert_equal true, events.first.payload[:async] + end + end +end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 3a9687a21db4a..fbc75c75502b9 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -943,3 +943,37 @@ def test_has_one_with_touch_option_on_nonpersisted_built_associations_doesnt_upd MESSAGE end end + +class AsyncHasOneAssociationsTest < ActiveRecord::TestCase + include WaitForAsyncTestHelper + + self.use_transactional_tests = false + + fixtures :companies, :accounts + + unless in_memory_db? + def test_async_load_has_one + firm = companies(:first_firm) + first_account = Account.find(1) + + firm.association(:account).async_load_target + wait_for_async_query + + events = [] + callback = -> (event) do + events << event unless event.payload[:name] == "SCHEMA" + end + ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do + firm.account + end + + assert_no_queries do + assert_equal first_account, firm.account + assert_equal first_account.credit_limit, firm.account.credit_limit + end + + assert_equal 1, events.size + assert_equal true, events.first.payload[:async] + end + end +end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 20c3504cbdd49..e7ccbc68761ec 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -45,36 +45,53 @@ ActiveRecord::ConnectionAdapters.register("abstract", "ActiveRecord::ConnectionAdapters::AbstractAdapter", "active_record/connection_adapters/abstract_adapter") ActiveRecord::ConnectionAdapters.register("fake", "FakeActiveRecordAdapter", File.expand_path("../support/fake_adapter.rb", __dir__)) -class SQLSubscriber - attr_reader :logged - attr_reader :payloads +class ActiveRecord::TestCase + class SQLSubscriber + attr_reader :logged + attr_reader :payloads + + def initialize + @logged = [] + @payloads = [] + end + + def start(name, id, payload) + @payloads << payload + @logged << [payload[:sql].squish, payload[:name], payload[:binds]] + end - def initialize - @logged = [] - @payloads = [] + def finish(name, id, payload); end end - def start(name, id, payload) - @payloads << payload - @logged << [payload[:sql].squish, payload[:name], payload[:binds]] + module InTimeZone + private + def in_time_zone(zone) + old_zone = Time.zone + old_tz = ActiveRecord::Base.time_zone_aware_attributes + + Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil + ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? + yield + ensure + Time.zone = old_zone + ActiveRecord::Base.time_zone_aware_attributes = old_tz + end end - def finish(name, id, payload); end -end + module WaitForAsyncTestHelper + private + def wait_for_async_query(connection = ActiveRecord::Base.lease_connection, timeout: 5) + return unless connection.async_enabled? -module InTimeZone - private - def in_time_zone(zone) - old_zone = Time.zone - old_tz = ActiveRecord::Base.time_zone_aware_attributes - - Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil - ActiveRecord::Base.time_zone_aware_attributes = !zone.nil? - yield - ensure - Time.zone = old_zone - ActiveRecord::Base.time_zone_aware_attributes = old_tz - end + executor = connection.pool.async_executor + (timeout * 100).times do + return unless executor.scheduled_task_count > executor.completed_task_count + sleep 0.01 + end + + raise Timeout::Error, "The async executor wasn't drained after #{timeout} seconds" + end + end end # Encryption diff --git a/activerecord/test/cases/relation/load_async_test.rb b/activerecord/test/cases/relation/load_async_test.rb index 99eccfd1739a4..c4250268f87fc 100644 --- a/activerecord/test/cases/relation/load_async_test.rb +++ b/activerecord/test/cases/relation/load_async_test.rb @@ -7,21 +7,6 @@ require "models/other_dog" module ActiveRecord - module WaitForAsyncTestHelper - private - def wait_for_async_query(connection = ActiveRecord::Base.lease_connection, timeout: 5) - return unless connection.async_enabled? - - executor = connection.pool.async_executor - (timeout * 100).times do - return unless executor.scheduled_task_count > executor.completed_task_count - sleep 0.01 - end - - raise Timeout::Error, "The async executor wasn't drained after #{timeout} seconds" - end - end - class LoadAsyncTest < ActiveRecord::TestCase include WaitForAsyncTestHelper From d437ae311f1b9dc40b442e40eb602e020cec4e49 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 5 Sep 2024 14:39:25 +0200 Subject: [PATCH 09/25] Fix a typo from PR #52807 --- .../test/cases/associations/has_many_associations_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 6357ffbc2722e..d8a30d9ce7303 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -3253,7 +3253,7 @@ def force_signal37_to_load_all_clients_of_firm end end -class AsyncHasOneAssociationsTest < ActiveRecord::TestCase +class AsyncHasManyAssociationsTest < ActiveRecord::TestCase include WaitForAsyncTestHelper self.use_transactional_tests = false From cf976b0897d63e875236a5c22ac8c6d187195a05 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 5 Sep 2024 14:26:23 -0700 Subject: [PATCH 10/25] Add Solid Queue alongside Solid Cache (#52804) * Add Solid Queue alongside Solid Cache --- Gemfile | 1 + Gemfile.lock | 16 +++++++++++---- railties/CHANGELOG.md | 4 ++++ railties/lib/rails/generators/app_base.rb | 2 +- .../generators/rails/app/templates/Gemfile.tt | 3 +++ .../templates/config/databases/mysql.yml.tt | 4 ++++ .../config/databases/postgresql.yml.tt | 4 ++++ .../templates/config/databases/sqlite3.yml.tt | 4 ++++ .../templates/config/databases/trilogy.yml.tt | 4 ++++ .../rails/app/templates/config/deploy.yml.tt | 20 +++++++++++++++++-- .../rails/app/templates/config/puma.rb.tt | 5 +++++ .../test/generators/app_generator_test.rb | 9 +++++++-- 12 files changed, 67 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 816f184a5c448..a7cace04c02bf 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ gem "importmap-rails", ">= 1.2.3" gem "tailwindcss-rails" gem "dartsass-rails" gem "solid_cache" +gem "solid_queue" gem "kamal", require: false gem "thruster", require: false # require: false so bcrypt is loaded only when has_secure_password is used. diff --git a/Gemfile.lock b/Gemfile.lock index 11fc857ee554a..6e9fb79afebf3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -212,7 +212,7 @@ GEM drb (2.2.1) ed25519 (1.3.0) erubi (1.13.0) - et-orbi (1.2.7) + et-orbi (1.2.11) tzinfo event_emitter (0.2.6) execjs (2.9.1) @@ -244,8 +244,8 @@ GEM ffi (1.17.0) ffi (1.17.0-x86_64-darwin) ffi (1.17.0-x86_64-linux-gnu) - fugit (1.9.0) - et-orbi (~> 1, >= 1.2.7) + fugit (1.11.1) + et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) @@ -549,6 +549,13 @@ GEM activejob (>= 7.2) activerecord (>= 7.2) railties (>= 7.2) + solid_queue (0.8.1) + activejob (>= 7.1) + activerecord (>= 7.1) + concurrent-ruby (>= 1.3.1) + fugit (~> 1.11.0) + railties (>= 7.1) + thor (~> 1.3.1) sorted_set (1.0.3) rbtree set (~> 1.0) @@ -585,7 +592,7 @@ GEM railties (>= 6.0.0) terser (1.1.20) execjs (>= 0.3.0, < 3) - thor (1.3.0) + thor (1.3.2) thruster (0.1.7) thruster (0.1.7-x86_64-darwin) thruster (0.1.7-x86_64-linux) @@ -696,6 +703,7 @@ DEPENDENCIES sidekiq sneakers solid_cache + solid_queue sprockets-rails (>= 2.0.0) sqlite3 (>= 2.0) stackprof diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index ef64c4fb458ab..a78bc542477e6 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Use [Solid Queue](https://github.com/rails/solid_queue) as the default Active Job backend in production, configured as a separate cache database in config/database.yml. In a single-server deployment, it'll run as a Puma plugin. This is configured in `config/deploy.yml` and can easily be changed to use a dedicated jobs machine. + + *DHH* + * Use [Solid Cache](https://github.com/rails/solid_cache) as the default Rails.cache backend in production, configured as a separate cache database in config/database.yml. *DHH* diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 761af5f0de1b6..4a89c79f43538 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -756,7 +756,7 @@ def run_kamal def run_solid return if skip_solid? || !bundle_install? - rails_command "solid_cache:install" + rails_command "solid_cache:install solid_queue:install" end def add_bundler_platforms diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt index 17d1b1aafab1a..50956135aae54 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile.tt +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt @@ -18,6 +18,9 @@ gem "tzinfo-data", platforms: %i[ <%= bundler_windows_platforms %> jruby ] # Use the database-backed Solid Cache adapter for Rails.cache [https://github.com/rails/solid_cache] gem "solid_cache" + +# Use the database-backed Solid Queue adapter for Active Job [https://github.com/rails/solid_queue] +gem "solid_queue" <% end -%> <% if depend_on_bootsnap? -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt index 2f791f02ad737..c30b17d94a35e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt @@ -69,4 +69,8 @@ production: <<: *primary_production database: <%= app_name %>_production_cache migrations_paths: db/cache_migrate + queue: + <<: *primary_production + database: <%= app_name %>_production_queue + migrations_paths: db/queue_migrate <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt index fcb902bfb6deb..06c323f389d17 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt @@ -101,4 +101,8 @@ production: <<: *primary_production database: <%= app_name %>_production_cache migrations_paths: db/cache_migrate + queue: + <<: *primary_production + database: <%= app_name %>_production_queue + migrations_paths: db/queue_migrate <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt index 8f855a756941d..07d91261df4c6 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt @@ -58,5 +58,9 @@ production: <<: *default database: storage/production_cache.sqlite3 migrations_paths: db/cache_migrate + queue: + <<: *default + database: storage/production_queue.sqlite3 + migrations_paths: db/queue_migrate <%- end -%> <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt index 7367e9fbe3b71..01bb71d896006 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt @@ -69,4 +69,8 @@ production: <<: *primary_production database: <%= app_name %>_production_cache migrations_paths: db/cache_migrate + queue: + <<: *primary_production + database: <%= app_name %>_production_queue + migrations_paths: db/queue_migrate <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/deploy.yml.tt b/railties/lib/rails/generators/rails/app/templates/config/deploy.yml.tt index 9c1e100b8743a..8136c453ae76f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/deploy.yml.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/deploy.yml.tt @@ -11,7 +11,7 @@ servers: # job: # hosts: # - 192.168.0.1 - # cmd: bin/solid_queue work + # cmd: bin/jobs # Credentials for your image host. registry: @@ -28,12 +28,28 @@ registry: env: secret: - RAILS_MASTER_KEY +<% if skip_solid? -%> # clear: # # Set number of cores available to the application on each server (default: 1). # WEB_CONCURRENCY: 2 - # # Match this to the database server to configure Active Record correctly + # # Match this to any external database server to configure Active Record correctly # DB_HOST: 192.168.0.2 +<% else -%> + clear: + # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. + # When you start using multiple servers, you should split out job processing to a dedicated machine. + SOLID_QUEUE_IN_PUMA: true + + # Set number of processes dedicated to Solid Queue (default: 1) + # JOB_CONCURRENCY: 3 + + # Set number of cores available to the application on each server (default: 1). + # WEB_CONCURRENCY: 2 + + # Match this to any external database server to configure Active Record correctly + # DB_HOST: 192.168.0.2 +<% end -%> <% unless skip_storage? %> # Use a persistent storage volume for sqlite database files and local Active Storage files. diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt index 787e4ce98e8ad..6af82983e8444 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt @@ -33,6 +33,11 @@ port ENV.fetch("PORT", 3000) # Allow puma to be restarted by `bin/rails restart` command. plugin :tmp_restart +<% unless skip_solid? -%> +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +<% end -%> # Specify the PID file. Defaults to tmp/pids/server.pid in development. # In other environments, only set the PID file if requested. pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 2b7865efbf56d..9bdad0964a68c 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -641,9 +641,13 @@ def test_ci_files_are_skipped_if_required def test_configuration_of_solid generator [destination_root] run_generator_instance + assert_gem "solid_cache" + assert_gem "solid_queue" + assert_file "config/database.yml" do |content| assert_match(%r{cache:}, content) + assert_match(%r{queue:}, content) end end @@ -808,8 +812,9 @@ def test_skip_solid_option generator([destination_root], skip_solid: true) run_generator_instance - assert_not_includes @rails_commands, "solid_cache:install", "`solid_cache:install` expected to not be called." + assert_not_includes @rails_commands, "solid_cache:install solid_queue:install", "`solid_cache:install solid_queue:install` expected to not be called." assert_no_gem "solid_cache" + assert_no_gem "solid_queue" end def test_skip_javascript_option @@ -986,7 +991,7 @@ def test_default_generator_executes_all_rails_commands run_generator_instance expected_commands = [ - "credentials:diff --enroll", "importmap:install", "turbo:install stimulus:install", "solid_cache:install" + "credentials:diff --enroll", "importmap:install", "turbo:install stimulus:install", "solid_cache:install solid_queue:install" ] assert_equal expected_commands, @rails_commands end From abdcbae28eb2ab961830b761700dc415d93176b2 Mon Sep 17 00:00:00 2001 From: chaadow Date: Thu, 5 Sep 2024 23:57:44 +0200 Subject: [PATCH 11/25] Use only one batch to generated form selectors This commit fixes the generation of form helpers by using one CodeGenerator instance instead of 17 --- actionview/lib/action_view/helpers/form_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index e5d5bce3c2a65..7da55e137cc4a 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -2020,8 +2020,8 @@ def field_name(method, *methods, multiple: false, index: @options[:index]) # # Please refer to the documentation of the base helper for details. - (field_helpers - [:label, :checkbox, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector| - ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator| + ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator| + (field_helpers - [:label, :checkbox, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector| code_generator.class_eval do |batch| batch << "def #{selector}(method, options = {})" << From 0b12b98f222e6e17211f6fb22147370f5d6a0067 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 10 Jun 2024 18:25:33 -0700 Subject: [PATCH 12/25] Update TimeWithZone#inspect to match Ruby 1.9+ --- activesupport/CHANGELOG.md | 4 + .../lib/active_support/time_with_zone.rb | 2 +- .../test/core_ext/time_with_zone_test.rb | 474 +++++++++--------- 3 files changed, 242 insertions(+), 238 deletions(-) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 1dddf5348f815..8855c3a79a696 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,7 @@ +* `ActiveSupport::TimeWithZone#inspect` now uses ISO 8601 style time like `Time#inspect` + + *John Hawthorn* + * `ActiveSupport::ErrorReporter#report` now assigns a backtrace to unraised exceptions. Previously reporting an un-raised exception would result in an error report without diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index c4570eaf915a3..7ec107ec31b94 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -138,7 +138,7 @@ def zone # # Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25.624541392 EST -05:00" def inspect - "#{time.strftime('%a, %d %b %Y %H:%M:%S.%9N')} #{zone} #{formatted_offset}" + "#{time.strftime('%F %H:%M:%S.%9N')} #{zone} #{formatted_offset}" end # Returns a string of the object's date and time in the ISO 8601 standard diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 3cca54147830a..6ef7027704417 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -114,19 +114,19 @@ def test_strftime_with_escaping end def test_inspect - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", @twz.inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", @twz.inspect nsec = Time.utc(1986, 12, 12, 6, 23, 00, Rational(1, 1000)) nsec = ActiveSupport::TimeWithZone.new(nsec, @time_zone) - assert_equal "Fri, 12 Dec 1986 01:23:00.000000001 EST -05:00", nsec.inspect + assert_equal "1986-12-12 01:23:00.000000001 EST -05:00", nsec.inspect hundred_nsec = Time.utc(1986, 12, 12, 6, 23, 00, Rational(100, 1000)) hundred_nsec = ActiveSupport::TimeWithZone.new(hundred_nsec, @time_zone) - assert_equal "Fri, 12 Dec 1986 01:23:00.000000100 EST -05:00", hundred_nsec.inspect + assert_equal "1986-12-12 01:23:00.000000100 EST -05:00", hundred_nsec.inspect one_third_sec = Time.utc(1986, 12, 12, 6, 23, 00, Rational(1000000, 3)) one_third_sec = ActiveSupport::TimeWithZone.new(one_third_sec, @time_zone) - assert_equal "Fri, 12 Dec 1986 01:23:00.333333333 EST -05:00", one_third_sec.inspect + assert_equal "1986-12-12 01:23:00.333333333 EST -05:00", one_third_sec.inspect end def test_to_s @@ -777,20 +777,20 @@ def test_local_to_utc_conversion_with_far_future_datetime end def test_change - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", @twz.inspect - assert_equal "Mon, 31 Dec 2001 19:00:00.000000000 EST -05:00", @twz.change(year: 2001).inspect - assert_equal "Wed, 31 Mar 1999 19:00:00.000000000 EST -05:00", @twz.change(month: 3).inspect - assert_equal "Wed, 03 Mar 1999 19:00:00.000000000 EST -05:00", @twz.change(month: 2).inspect - assert_equal "Wed, 15 Dec 1999 19:00:00.000000000 EST -05:00", @twz.change(day: 15).inspect - assert_equal "Fri, 31 Dec 1999 06:00:00.000000000 EST -05:00", @twz.change(hour: 6).inspect - assert_equal "Fri, 31 Dec 1999 19:15:00.000000000 EST -05:00", @twz.change(min: 15).inspect - assert_equal "Fri, 31 Dec 1999 19:00:30.000000000 EST -05:00", @twz.change(sec: 30).inspect - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 HST -10:00", @twz.change(offset: "-10:00").inspect - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 HST -10:00", @twz.change(offset: -36000).inspect - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 HST -10:00", @twz.change(zone: "Hawaii").inspect - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 HST -10:00", @twz.change(zone: -10).inspect - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 HST -10:00", @twz.change(zone: -36000).inspect - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 HST -10:00", @twz.change(zone: "Pacific/Honolulu").inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", @twz.inspect + assert_equal "2001-12-31 19:00:00.000000000 EST -05:00", @twz.change(year: 2001).inspect + assert_equal "1999-03-31 19:00:00.000000000 EST -05:00", @twz.change(month: 3).inspect + assert_equal "1999-03-03 19:00:00.000000000 EST -05:00", @twz.change(month: 2).inspect + assert_equal "1999-12-15 19:00:00.000000000 EST -05:00", @twz.change(day: 15).inspect + assert_equal "1999-12-31 06:00:00.000000000 EST -05:00", @twz.change(hour: 6).inspect + assert_equal "1999-12-31 19:15:00.000000000 EST -05:00", @twz.change(min: 15).inspect + assert_equal "1999-12-31 19:00:30.000000000 EST -05:00", @twz.change(sec: 30).inspect + assert_equal "1999-12-31 19:00:00.000000000 HST -10:00", @twz.change(offset: "-10:00").inspect + assert_equal "1999-12-31 19:00:00.000000000 HST -10:00", @twz.change(offset: -36000).inspect + assert_equal "1999-12-31 19:00:00.000000000 HST -10:00", @twz.change(zone: "Hawaii").inspect + assert_equal "1999-12-31 19:00:00.000000000 HST -10:00", @twz.change(zone: -10).inspect + assert_equal "1999-12-31 19:00:00.000000000 HST -10:00", @twz.change(zone: -36000).inspect + assert_equal "1999-12-31 19:00:00.000000000 HST -10:00", @twz.change(zone: "Pacific/Honolulu").inspect end def test_change_at_dst_boundary @@ -804,83 +804,83 @@ def test_round_at_dst_boundary end def test_advance - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", @twz.inspect - assert_equal "Mon, 31 Dec 2001 19:00:00.000000000 EST -05:00", @twz.advance(years: 2).inspect - assert_equal "Fri, 31 Mar 2000 19:00:00.000000000 EST -05:00", @twz.advance(months: 3).inspect - assert_equal "Tue, 04 Jan 2000 19:00:00.000000000 EST -05:00", @twz.advance(days: 4).inspect - assert_equal "Sat, 01 Jan 2000 01:00:00.000000000 EST -05:00", @twz.advance(hours: 6).inspect - assert_equal "Fri, 31 Dec 1999 19:15:00.000000000 EST -05:00", @twz.advance(minutes: 15).inspect - assert_equal "Fri, 31 Dec 1999 19:00:30.000000000 EST -05:00", @twz.advance(seconds: 30).inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", @twz.inspect + assert_equal "2001-12-31 19:00:00.000000000 EST -05:00", @twz.advance(years: 2).inspect + assert_equal "2000-03-31 19:00:00.000000000 EST -05:00", @twz.advance(months: 3).inspect + assert_equal "2000-01-04 19:00:00.000000000 EST -05:00", @twz.advance(days: 4).inspect + assert_equal "2000-01-01 01:00:00.000000000 EST -05:00", @twz.advance(hours: 6).inspect + assert_equal "1999-12-31 19:15:00.000000000 EST -05:00", @twz.advance(minutes: 15).inspect + assert_equal "1999-12-31 19:00:30.000000000 EST -05:00", @twz.advance(seconds: 30).inspect end def test_beginning_of_year - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", @twz.inspect - assert_equal "Fri, 01 Jan 1999 00:00:00.000000000 EST -05:00", @twz.beginning_of_year.inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", @twz.inspect + assert_equal "1999-01-01 00:00:00.000000000 EST -05:00", @twz.beginning_of_year.inspect end def test_end_of_year - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", @twz.inspect - assert_equal "Fri, 31 Dec 1999 23:59:59.999999999 EST -05:00", @twz.end_of_year.inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", @twz.inspect + assert_equal "1999-12-31 23:59:59.999999999 EST -05:00", @twz.end_of_year.inspect end def test_beginning_of_month - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", @twz.inspect - assert_equal "Wed, 01 Dec 1999 00:00:00.000000000 EST -05:00", @twz.beginning_of_month.inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", @twz.inspect + assert_equal "1999-12-01 00:00:00.000000000 EST -05:00", @twz.beginning_of_month.inspect end def test_end_of_month - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", @twz.inspect - assert_equal "Fri, 31 Dec 1999 23:59:59.999999999 EST -05:00", @twz.end_of_month.inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", @twz.inspect + assert_equal "1999-12-31 23:59:59.999999999 EST -05:00", @twz.end_of_month.inspect end def test_beginning_of_day - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", @twz.inspect - assert_equal "Fri, 31 Dec 1999 00:00:00.000000000 EST -05:00", @twz.beginning_of_day.inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", @twz.inspect + assert_equal "1999-12-31 00:00:00.000000000 EST -05:00", @twz.beginning_of_day.inspect end def test_end_of_day - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", @twz.inspect - assert_equal "Fri, 31 Dec 1999 23:59:59.999999999 EST -05:00", @twz.end_of_day.inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", @twz.inspect + assert_equal "1999-12-31 23:59:59.999999999 EST -05:00", @twz.end_of_day.inspect end def test_beginning_of_hour utc = Time.utc(2000, 1, 1, 0, 30) twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal "Fri, 31 Dec 1999 19:30:00.000000000 EST -05:00", twz.inspect - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", twz.beginning_of_hour.inspect + assert_equal "1999-12-31 19:30:00.000000000 EST -05:00", twz.inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", twz.beginning_of_hour.inspect end def test_end_of_hour utc = Time.utc(2000, 1, 1, 0, 30) twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal "Fri, 31 Dec 1999 19:30:00.000000000 EST -05:00", twz.inspect - assert_equal "Fri, 31 Dec 1999 19:59:59.999999999 EST -05:00", twz.end_of_hour.inspect + assert_equal "1999-12-31 19:30:00.000000000 EST -05:00", twz.inspect + assert_equal "1999-12-31 19:59:59.999999999 EST -05:00", twz.end_of_hour.inspect end def test_beginning_of_minute utc = Time.utc(2000, 1, 1, 0, 30, 10) twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal "Fri, 31 Dec 1999 19:30:10.000000000 EST -05:00", twz.inspect - assert_equal "Fri, 31 Dec 1999 19:00:00.000000000 EST -05:00", twz.beginning_of_hour.inspect + assert_equal "1999-12-31 19:30:10.000000000 EST -05:00", twz.inspect + assert_equal "1999-12-31 19:00:00.000000000 EST -05:00", twz.beginning_of_hour.inspect end def test_end_of_minute utc = Time.utc(2000, 1, 1, 0, 30, 10) twz = ActiveSupport::TimeWithZone.new(utc, @time_zone) - assert_equal "Fri, 31 Dec 1999 19:30:10.000000000 EST -05:00", twz.inspect - assert_equal "Fri, 31 Dec 1999 19:30:59.999999999 EST -05:00", twz.end_of_minute.inspect + assert_equal "1999-12-31 19:30:10.000000000 EST -05:00", twz.inspect + assert_equal "1999-12-31 19:30:59.999999999 EST -05:00", twz.end_of_minute.inspect end def test_since - assert_equal "Fri, 31 Dec 1999 19:00:01.000000000 EST -05:00", @twz.since(1).inspect + assert_equal "1999-12-31 19:00:01.000000000 EST -05:00", @twz.since(1).inspect end def test_in - assert_equal "Fri, 31 Dec 1999 19:00:01.000000000 EST -05:00", @twz.in(1).inspect + assert_equal "1999-12-31 19:00:01.000000000 EST -05:00", @twz.in(1).inspect end def test_ago - assert_equal "Fri, 31 Dec 1999 18:59:59.000000000 EST -05:00", @twz.ago(1).inspect + assert_equal "1999-12-31 18:59:59.000000000 EST -05:00", @twz.ago(1).inspect end def test_seconds_since_midnight @@ -889,263 +889,263 @@ def test_seconds_since_midnight def test_advance_1_year_from_leap_day twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2004, 2, 29)) - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", twz.advance(years: 1).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", twz.years_since(1).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", twz.since(1.year).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", twz.in(1.year).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", (twz + 1.year).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", twz.advance(years: 1).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", twz.years_since(1).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", twz.since(1.year).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", twz.in(1.year).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", (twz + 1.year).inspect end def test_advance_1_month_from_last_day_of_january twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2005, 1, 31)) - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", twz.advance(months: 1).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", twz.months_since(1).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", twz.since(1.month).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", twz.in(1.month).inspect - assert_equal "Mon, 28 Feb 2005 00:00:00.000000000 EST -05:00", (twz + 1.month).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", twz.advance(months: 1).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", twz.months_since(1).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", twz.since(1.month).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", twz.in(1.month).inspect + assert_equal "2005-02-28 00:00:00.000000000 EST -05:00", (twz + 1.month).inspect end def test_advance_1_month_from_last_day_of_january_during_leap_year twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000, 1, 31)) - assert_equal "Tue, 29 Feb 2000 00:00:00.000000000 EST -05:00", twz.advance(months: 1).inspect - assert_equal "Tue, 29 Feb 2000 00:00:00.000000000 EST -05:00", twz.months_since(1).inspect - assert_equal "Tue, 29 Feb 2000 00:00:00.000000000 EST -05:00", twz.since(1.month).inspect - assert_equal "Tue, 29 Feb 2000 00:00:00.000000000 EST -05:00", twz.in(1.month).inspect - assert_equal "Tue, 29 Feb 2000 00:00:00.000000000 EST -05:00", (twz + 1.month).inspect + assert_equal "2000-02-29 00:00:00.000000000 EST -05:00", twz.advance(months: 1).inspect + assert_equal "2000-02-29 00:00:00.000000000 EST -05:00", twz.months_since(1).inspect + assert_equal "2000-02-29 00:00:00.000000000 EST -05:00", twz.since(1.month).inspect + assert_equal "2000-02-29 00:00:00.000000000 EST -05:00", twz.in(1.month).inspect + assert_equal "2000-02-29 00:00:00.000000000 EST -05:00", (twz + 1.month).inspect end def test_advance_1_month_into_spring_dst_gap twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 3, 2, 2)) - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", twz.advance(months: 1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", twz.months_since(1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", twz.since(1.month).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", twz.in(1.month).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", (twz + 1.month).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", twz.advance(months: 1).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", twz.months_since(1).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", twz.since(1.month).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", twz.in(1.month).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", (twz + 1.month).inspect end def test_advance_1_second_into_spring_dst_gap twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 2, 1, 59, 59)) - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", twz.advance(seconds: 1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", (twz + 1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", (twz + 1.second).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", twz.since(1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", twz.since(1.second).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", twz.in(1).inspect - assert_equal "Sun, 02 Apr 2006 03:00:00.000000000 EDT -04:00", twz.in(1.second).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", twz.advance(seconds: 1).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", (twz + 1).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", (twz + 1.second).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", twz.since(1).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", twz.since(1.second).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", twz.in(1).inspect + assert_equal "2006-04-02 03:00:00.000000000 EDT -04:00", twz.in(1.second).inspect end def test_advance_1_day_across_spring_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 1, 10, 30)) # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long # When we advance 1 day, we want to end up at the same time on the next day - assert_equal "Sun, 02 Apr 2006 10:30:00.000000000 EDT -04:00", twz.advance(days: 1).inspect - assert_equal "Sun, 02 Apr 2006 10:30:00.000000000 EDT -04:00", twz.since(1.days).inspect - assert_equal "Sun, 02 Apr 2006 10:30:00.000000000 EDT -04:00", twz.in(1.days).inspect - assert_equal "Sun, 02 Apr 2006 10:30:00.000000000 EDT -04:00", (twz + 1.days).inspect - assert_equal "Sun, 02 Apr 2006 10:30:01.000000000 EDT -04:00", twz.since(1.days + 1.second).inspect - assert_equal "Sun, 02 Apr 2006 10:30:01.000000000 EDT -04:00", twz.in(1.days + 1.second).inspect - assert_equal "Sun, 02 Apr 2006 10:30:01.000000000 EDT -04:00", (twz + 1.days + 1.second).inspect + assert_equal "2006-04-02 10:30:00.000000000 EDT -04:00", twz.advance(days: 1).inspect + assert_equal "2006-04-02 10:30:00.000000000 EDT -04:00", twz.since(1.days).inspect + assert_equal "2006-04-02 10:30:00.000000000 EDT -04:00", twz.in(1.days).inspect + assert_equal "2006-04-02 10:30:00.000000000 EDT -04:00", (twz + 1.days).inspect + assert_equal "2006-04-02 10:30:01.000000000 EDT -04:00", twz.since(1.days + 1.second).inspect + assert_equal "2006-04-02 10:30:01.000000000 EDT -04:00", twz.in(1.days + 1.second).inspect + assert_equal "2006-04-02 10:30:01.000000000 EDT -04:00", (twz + 1.days + 1.second).inspect end def test_advance_1_day_across_spring_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 2, 10, 30)) # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long # When we advance back 1 day, we want to end up at the same time on the previous day - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.advance(days: -1).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.ago(1.days).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", (twz - 1.days).inspect - assert_equal "Sat, 01 Apr 2006 10:30:01.000000000 EST -05:00", twz.ago(1.days - 1.second).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.advance(days: -1).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.ago(1.days).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", (twz - 1.days).inspect + assert_equal "2006-04-01 10:30:01.000000000 EST -05:00", twz.ago(1.days - 1.second).inspect end def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 1, 10, 30)) # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", (twz + 86400).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", (twz + 86400.seconds).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.since(86400).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.since(86400.seconds).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.in(86400).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.in(86400.seconds).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.advance(seconds: 86400).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", (twz + 1440.minutes).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.since(1440.minutes).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.in(1440.minutes).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.advance(minutes: 1440).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", (twz + 24.hours).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.since(24.hours).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.in(24.hours).inspect - assert_equal "Sun, 02 Apr 2006 11:30:00.000000000 EDT -04:00", twz.advance(hours: 24).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", (twz + 86400).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", (twz + 86400.seconds).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.since(86400).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.since(86400.seconds).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.in(86400).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.in(86400.seconds).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.advance(seconds: 86400).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", (twz + 1440.minutes).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.since(1440.minutes).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.in(1440.minutes).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.advance(minutes: 1440).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", (twz + 24.hours).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.since(24.hours).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.in(24.hours).inspect + assert_equal "2006-04-02 11:30:00.000000000 EDT -04:00", twz.advance(hours: 24).inspect end def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 2, 11, 30)) # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", (twz - 86400).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", (twz - 86400.seconds).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.ago(86400).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.ago(86400.seconds).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.advance(seconds: -86400).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", (twz - 1440.minutes).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.ago(1440.minutes).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.advance(minutes: -1440).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", (twz - 24.hours).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.ago(24.hours).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.advance(hours: -24).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", (twz - 86400).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", (twz - 86400.seconds).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.ago(86400).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.ago(86400.seconds).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.advance(seconds: -86400).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", (twz - 1440.minutes).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.ago(1440.minutes).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.advance(minutes: -1440).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", (twz - 24.hours).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.ago(24.hours).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.advance(hours: -24).inspect end def test_advance_1_day_across_fall_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 28, 10, 30)) # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long # When we advance 1 day, we want to end up at the same time on the next day - assert_equal "Sun, 29 Oct 2006 10:30:00.000000000 EST -05:00", twz.advance(days: 1).inspect - assert_equal "Sun, 29 Oct 2006 10:30:00.000000000 EST -05:00", twz.since(1.days).inspect - assert_equal "Sun, 29 Oct 2006 10:30:00.000000000 EST -05:00", twz.in(1.days).inspect - assert_equal "Sun, 29 Oct 2006 10:30:00.000000000 EST -05:00", (twz + 1.days).inspect - assert_equal "Sun, 29 Oct 2006 10:30:01.000000000 EST -05:00", twz.since(1.days + 1.second).inspect - assert_equal "Sun, 29 Oct 2006 10:30:01.000000000 EST -05:00", twz.in(1.days + 1.second).inspect - assert_equal "Sun, 29 Oct 2006 10:30:01.000000000 EST -05:00", (twz + 1.days + 1.second).inspect + assert_equal "2006-10-29 10:30:00.000000000 EST -05:00", twz.advance(days: 1).inspect + assert_equal "2006-10-29 10:30:00.000000000 EST -05:00", twz.since(1.days).inspect + assert_equal "2006-10-29 10:30:00.000000000 EST -05:00", twz.in(1.days).inspect + assert_equal "2006-10-29 10:30:00.000000000 EST -05:00", (twz + 1.days).inspect + assert_equal "2006-10-29 10:30:01.000000000 EST -05:00", twz.since(1.days + 1.second).inspect + assert_equal "2006-10-29 10:30:01.000000000 EST -05:00", twz.in(1.days + 1.second).inspect + assert_equal "2006-10-29 10:30:01.000000000 EST -05:00", (twz + 1.days + 1.second).inspect end def test_advance_1_day_across_fall_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 29, 10, 30)) # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long # When we advance backwards 1 day, we want to end up at the same time on the previous day - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.advance(days: -1).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.ago(1.days).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", (twz - 1.days).inspect - assert_equal "Sat, 28 Oct 2006 10:30:01.000000000 EDT -04:00", twz.ago(1.days - 1.second).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.advance(days: -1).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.ago(1.days).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", (twz - 1.days).inspect + assert_equal "2006-10-28 10:30:01.000000000 EDT -04:00", twz.ago(1.days - 1.second).inspect end def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 28, 10, 30)) # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", (twz + 86400).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", (twz + 86400.seconds).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.since(86400).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.since(86400.seconds).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.in(86400).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.in(86400.seconds).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.advance(seconds: 86400).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", (twz + 1440.minutes).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.since(1440.minutes).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.in(1440.minutes).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.advance(minutes: 1440).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", (twz + 24.hours).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.since(24.hours).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.in(24.hours).inspect - assert_equal "Sun, 29 Oct 2006 09:30:00.000000000 EST -05:00", twz.advance(hours: 24).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", (twz + 86400).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", (twz + 86400.seconds).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.since(86400).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.since(86400.seconds).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.in(86400).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.in(86400.seconds).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.advance(seconds: 86400).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", (twz + 1440.minutes).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.since(1440.minutes).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.in(1440.minutes).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.advance(minutes: 1440).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", (twz + 24.hours).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.since(24.hours).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.in(24.hours).inspect + assert_equal "2006-10-29 09:30:00.000000000 EST -05:00", twz.advance(hours: 24).inspect end def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 29, 9, 30)) # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", (twz - 86400).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", (twz - 86400.seconds).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.ago(86400).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.ago(86400.seconds).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.advance(seconds: -86400).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", (twz - 1440.minutes).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.ago(1440.minutes).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.advance(minutes: -1440).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", (twz - 24.hours).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.ago(24.hours).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.advance(hours: -24).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", (twz - 86400).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", (twz - 86400.seconds).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.ago(86400).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.ago(86400.seconds).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.advance(seconds: -86400).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", (twz - 1440.minutes).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.ago(1440.minutes).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.advance(minutes: -1440).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", (twz - 24.hours).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.ago(24.hours).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.advance(hours: -24).inspect end def test_advance_1_week_across_spring_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 1, 10, 30)) - assert_equal "Sat, 08 Apr 2006 10:30:00.000000000 EDT -04:00", twz.advance(weeks: 1).inspect - assert_equal "Sat, 08 Apr 2006 10:30:00.000000000 EDT -04:00", twz.weeks_since(1).inspect - assert_equal "Sat, 08 Apr 2006 10:30:00.000000000 EDT -04:00", twz.since(1.week).inspect - assert_equal "Sat, 08 Apr 2006 10:30:00.000000000 EDT -04:00", twz.in(1.week).inspect - assert_equal "Sat, 08 Apr 2006 10:30:00.000000000 EDT -04:00", (twz + 1.week).inspect + assert_equal "2006-04-08 10:30:00.000000000 EDT -04:00", twz.advance(weeks: 1).inspect + assert_equal "2006-04-08 10:30:00.000000000 EDT -04:00", twz.weeks_since(1).inspect + assert_equal "2006-04-08 10:30:00.000000000 EDT -04:00", twz.since(1.week).inspect + assert_equal "2006-04-08 10:30:00.000000000 EDT -04:00", twz.in(1.week).inspect + assert_equal "2006-04-08 10:30:00.000000000 EDT -04:00", (twz + 1.week).inspect end def test_advance_1_week_across_spring_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 8, 10, 30)) - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.advance(weeks: -1).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.weeks_ago(1).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.ago(1.week).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", (twz - 1.week).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.advance(weeks: -1).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.weeks_ago(1).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.ago(1.week).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", (twz - 1.week).inspect end def test_advance_1_week_across_fall_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 28, 10, 30)) - assert_equal "Sat, 04 Nov 2006 10:30:00.000000000 EST -05:00", twz.advance(weeks: 1).inspect - assert_equal "Sat, 04 Nov 2006 10:30:00.000000000 EST -05:00", twz.weeks_since(1).inspect - assert_equal "Sat, 04 Nov 2006 10:30:00.000000000 EST -05:00", twz.since(1.week).inspect - assert_equal "Sat, 04 Nov 2006 10:30:00.000000000 EST -05:00", twz.in(1.week).inspect - assert_equal "Sat, 04 Nov 2006 10:30:00.000000000 EST -05:00", (twz + 1.week).inspect + assert_equal "2006-11-04 10:30:00.000000000 EST -05:00", twz.advance(weeks: 1).inspect + assert_equal "2006-11-04 10:30:00.000000000 EST -05:00", twz.weeks_since(1).inspect + assert_equal "2006-11-04 10:30:00.000000000 EST -05:00", twz.since(1.week).inspect + assert_equal "2006-11-04 10:30:00.000000000 EST -05:00", twz.in(1.week).inspect + assert_equal "2006-11-04 10:30:00.000000000 EST -05:00", (twz + 1.week).inspect end def test_advance_1_week_across_fall_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 11, 4, 10, 30)) - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.advance(weeks: -1).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.weeks_ago(1).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.ago(1.week).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", (twz - 1.week).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.advance(weeks: -1).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.weeks_ago(1).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.ago(1.week).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", (twz - 1.week).inspect end def test_advance_1_month_across_spring_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 4, 1, 10, 30)) - assert_equal "Mon, 01 May 2006 10:30:00.000000000 EDT -04:00", twz.advance(months: 1).inspect - assert_equal "Mon, 01 May 2006 10:30:00.000000000 EDT -04:00", twz.months_since(1).inspect - assert_equal "Mon, 01 May 2006 10:30:00.000000000 EDT -04:00", twz.since(1.month).inspect - assert_equal "Mon, 01 May 2006 10:30:00.000000000 EDT -04:00", twz.in(1.month).inspect - assert_equal "Mon, 01 May 2006 10:30:00.000000000 EDT -04:00", (twz + 1.month).inspect + assert_equal "2006-05-01 10:30:00.000000000 EDT -04:00", twz.advance(months: 1).inspect + assert_equal "2006-05-01 10:30:00.000000000 EDT -04:00", twz.months_since(1).inspect + assert_equal "2006-05-01 10:30:00.000000000 EDT -04:00", twz.since(1.month).inspect + assert_equal "2006-05-01 10:30:00.000000000 EDT -04:00", twz.in(1.month).inspect + assert_equal "2006-05-01 10:30:00.000000000 EDT -04:00", (twz + 1.month).inspect end def test_advance_1_month_across_spring_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 5, 1, 10, 30)) - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.advance(months: -1).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.months_ago(1).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", twz.ago(1.month).inspect - assert_equal "Sat, 01 Apr 2006 10:30:00.000000000 EST -05:00", (twz - 1.month).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.advance(months: -1).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.months_ago(1).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", twz.ago(1.month).inspect + assert_equal "2006-04-01 10:30:00.000000000 EST -05:00", (twz - 1.month).inspect end def test_advance_1_month_across_fall_dst_transition twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 10, 28, 10, 30)) - assert_equal "Tue, 28 Nov 2006 10:30:00.000000000 EST -05:00", twz.advance(months: 1).inspect - assert_equal "Tue, 28 Nov 2006 10:30:00.000000000 EST -05:00", twz.months_since(1).inspect - assert_equal "Tue, 28 Nov 2006 10:30:00.000000000 EST -05:00", twz.since(1.month).inspect - assert_equal "Tue, 28 Nov 2006 10:30:00.000000000 EST -05:00", twz.in(1.month).inspect - assert_equal "Tue, 28 Nov 2006 10:30:00.000000000 EST -05:00", (twz + 1.month).inspect + assert_equal "2006-11-28 10:30:00.000000000 EST -05:00", twz.advance(months: 1).inspect + assert_equal "2006-11-28 10:30:00.000000000 EST -05:00", twz.months_since(1).inspect + assert_equal "2006-11-28 10:30:00.000000000 EST -05:00", twz.since(1.month).inspect + assert_equal "2006-11-28 10:30:00.000000000 EST -05:00", twz.in(1.month).inspect + assert_equal "2006-11-28 10:30:00.000000000 EST -05:00", (twz + 1.month).inspect end def test_advance_1_month_across_fall_dst_transition_backwards twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006, 11, 28, 10, 30)) - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.advance(months: -1).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.months_ago(1).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", twz.ago(1.month).inspect - assert_equal "Sat, 28 Oct 2006 10:30:00.000000000 EDT -04:00", (twz - 1.month).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.advance(months: -1).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.months_ago(1).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", twz.ago(1.month).inspect + assert_equal "2006-10-28 10:30:00.000000000 EDT -04:00", (twz - 1.month).inspect end def test_advance_1_year twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008, 2, 15, 10, 30)) - assert_equal "Sun, 15 Feb 2009 10:30:00.000000000 EST -05:00", twz.advance(years: 1).inspect - assert_equal "Sun, 15 Feb 2009 10:30:00.000000000 EST -05:00", twz.years_since(1).inspect - assert_equal "Sun, 15 Feb 2009 10:30:00.000000000 EST -05:00", twz.since(1.year).inspect - assert_equal "Sun, 15 Feb 2009 10:30:00.000000000 EST -05:00", twz.in(1.year).inspect - assert_equal "Sun, 15 Feb 2009 10:30:00.000000000 EST -05:00", (twz + 1.year).inspect - assert_equal "Thu, 15 Feb 2007 10:30:00.000000000 EST -05:00", twz.advance(years: -1).inspect - assert_equal "Thu, 15 Feb 2007 10:30:00.000000000 EST -05:00", twz.years_ago(1).inspect - assert_equal "Thu, 15 Feb 2007 10:30:00.000000000 EST -05:00", (twz - 1.year).inspect + assert_equal "2009-02-15 10:30:00.000000000 EST -05:00", twz.advance(years: 1).inspect + assert_equal "2009-02-15 10:30:00.000000000 EST -05:00", twz.years_since(1).inspect + assert_equal "2009-02-15 10:30:00.000000000 EST -05:00", twz.since(1.year).inspect + assert_equal "2009-02-15 10:30:00.000000000 EST -05:00", twz.in(1.year).inspect + assert_equal "2009-02-15 10:30:00.000000000 EST -05:00", (twz + 1.year).inspect + assert_equal "2007-02-15 10:30:00.000000000 EST -05:00", twz.advance(years: -1).inspect + assert_equal "2007-02-15 10:30:00.000000000 EST -05:00", twz.years_ago(1).inspect + assert_equal "2007-02-15 10:30:00.000000000 EST -05:00", (twz - 1.year).inspect end def test_advance_1_year_during_dst twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008, 7, 15, 10, 30)) - assert_equal "Wed, 15 Jul 2009 10:30:00.000000000 EDT -04:00", twz.advance(years: 1).inspect - assert_equal "Wed, 15 Jul 2009 10:30:00.000000000 EDT -04:00", twz.years_since(1).inspect - assert_equal "Wed, 15 Jul 2009 10:30:00.000000000 EDT -04:00", twz.since(1.year).inspect - assert_equal "Wed, 15 Jul 2009 10:30:00.000000000 EDT -04:00", twz.in(1.year).inspect - assert_equal "Wed, 15 Jul 2009 10:30:00.000000000 EDT -04:00", (twz + 1.year).inspect - assert_equal "Sun, 15 Jul 2007 10:30:00.000000000 EDT -04:00", twz.advance(years: -1).inspect - assert_equal "Sun, 15 Jul 2007 10:30:00.000000000 EDT -04:00", twz.years_ago(1).inspect - assert_equal "Sun, 15 Jul 2007 10:30:00.000000000 EDT -04:00", (twz - 1.year).inspect + assert_equal "2009-07-15 10:30:00.000000000 EDT -04:00", twz.advance(years: 1).inspect + assert_equal "2009-07-15 10:30:00.000000000 EDT -04:00", twz.years_since(1).inspect + assert_equal "2009-07-15 10:30:00.000000000 EDT -04:00", twz.since(1.year).inspect + assert_equal "2009-07-15 10:30:00.000000000 EDT -04:00", twz.in(1.year).inspect + assert_equal "2009-07-15 10:30:00.000000000 EDT -04:00", (twz + 1.year).inspect + assert_equal "2007-07-15 10:30:00.000000000 EDT -04:00", twz.advance(years: -1).inspect + assert_equal "2007-07-15 10:30:00.000000000 EDT -04:00", twz.years_ago(1).inspect + assert_equal "2007-07-15 10:30:00.000000000 EDT -04:00", (twz - 1.year).inspect end def test_no_method_error_has_proper_context @@ -1170,12 +1170,12 @@ def teardown def test_in_time_zone Time.use_zone "Alaska" do - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @t.in_time_zone.inspect - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @dt.in_time_zone.inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @t.in_time_zone.inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @dt.in_time_zone.inspect end Time.use_zone "Hawaii" do - assert_equal "Fri, 31 Dec 1999 14:00:00.000000000 HST -10:00", @t.in_time_zone.inspect - assert_equal "Fri, 31 Dec 1999 14:00:00.000000000 HST -10:00", @dt.in_time_zone.inspect + assert_equal "1999-12-31 14:00:00.000000000 HST -10:00", @t.in_time_zone.inspect + assert_equal "1999-12-31 14:00:00.000000000 HST -10:00", @dt.in_time_zone.inspect end Time.use_zone nil do assert_equal @t, @t.in_time_zone @@ -1192,13 +1192,13 @@ def test_nil_time_zone def test_in_time_zone_with_argument Time.use_zone "Eastern Time (US & Canada)" do # Time.zone will not affect #in_time_zone(zone) - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @t.in_time_zone("Alaska").inspect - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @dt.in_time_zone("Alaska").inspect - assert_equal "Fri, 31 Dec 1999 14:00:00.000000000 HST -10:00", @t.in_time_zone("Hawaii").inspect - assert_equal "Fri, 31 Dec 1999 14:00:00.000000000 HST -10:00", @dt.in_time_zone("Hawaii").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 UTC +00:00", @t.in_time_zone("UTC").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 UTC +00:00", @dt.in_time_zone("UTC").inspect - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @t.in_time_zone(-9.hours).inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @t.in_time_zone("Alaska").inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @dt.in_time_zone("Alaska").inspect + assert_equal "1999-12-31 14:00:00.000000000 HST -10:00", @t.in_time_zone("Hawaii").inspect + assert_equal "1999-12-31 14:00:00.000000000 HST -10:00", @dt.in_time_zone("Hawaii").inspect + assert_equal "2000-01-01 00:00:00.000000000 UTC +00:00", @t.in_time_zone("UTC").inspect + assert_equal "2000-01-01 00:00:00.000000000 UTC +00:00", @dt.in_time_zone("UTC").inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @t.in_time_zone(-9.hours).inspect end end @@ -1214,7 +1214,7 @@ def test_in_time_zone_with_invalid_argument def test_in_time_zone_with_time_local_instance with_env_tz "US/Eastern" do time = Time.local(1999, 12, 31, 19) # == Time.utc(2000) - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", time.in_time_zone("Alaska").inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", time.in_time_zone("Alaska").inspect end end @@ -1377,10 +1377,10 @@ def setup def test_in_time_zone with_tz_default "Alaska" do - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 AKST -09:00", @d.in_time_zone.inspect + assert_equal "2000-01-01 00:00:00.000000000 AKST -09:00", @d.in_time_zone.inspect end with_tz_default "Hawaii" do - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 HST -10:00", @d.in_time_zone.inspect + assert_equal "2000-01-01 00:00:00.000000000 HST -10:00", @d.in_time_zone.inspect end with_tz_default nil do assert_equal @d.to_time, @d.in_time_zone @@ -1395,10 +1395,10 @@ def test_nil_time_zone def test_in_time_zone_with_argument with_tz_default "Eastern Time (US & Canada)" do # Time.zone will not affect #in_time_zone(zone) - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 AKST -09:00", @d.in_time_zone("Alaska").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 HST -10:00", @d.in_time_zone("Hawaii").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 UTC +00:00", @d.in_time_zone("UTC").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 AKST -09:00", @d.in_time_zone(-9.hours).inspect + assert_equal "2000-01-01 00:00:00.000000000 AKST -09:00", @d.in_time_zone("Alaska").inspect + assert_equal "2000-01-01 00:00:00.000000000 HST -10:00", @d.in_time_zone("Hawaii").inspect + assert_equal "2000-01-01 00:00:00.000000000 UTC +00:00", @d.in_time_zone("UTC").inspect + assert_equal "2000-01-01 00:00:00.000000000 AKST -09:00", @d.in_time_zone(-9.hours).inspect end end @@ -1420,14 +1420,14 @@ def setup def test_in_time_zone with_tz_default "Alaska" do - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 AKST -09:00", @s.in_time_zone.inspect - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @u.in_time_zone.inspect - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @z.in_time_zone.inspect + assert_equal "2000-01-01 00:00:00.000000000 AKST -09:00", @s.in_time_zone.inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @u.in_time_zone.inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @z.in_time_zone.inspect end with_tz_default "Hawaii" do - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 HST -10:00", @s.in_time_zone.inspect - assert_equal "Fri, 31 Dec 1999 14:00:00.000000000 HST -10:00", @u.in_time_zone.inspect - assert_equal "Fri, 31 Dec 1999 14:00:00.000000000 HST -10:00", @z.in_time_zone.inspect + assert_equal "2000-01-01 00:00:00.000000000 HST -10:00", @s.in_time_zone.inspect + assert_equal "1999-12-31 14:00:00.000000000 HST -10:00", @u.in_time_zone.inspect + assert_equal "1999-12-31 14:00:00.000000000 HST -10:00", @z.in_time_zone.inspect end with_tz_default nil do assert_equal @s.to_time, @s.in_time_zone @@ -1446,18 +1446,18 @@ def test_nil_time_zone def test_in_time_zone_with_argument with_tz_default "Eastern Time (US & Canada)" do # Time.zone will not affect #in_time_zone(zone) - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 AKST -09:00", @s.in_time_zone("Alaska").inspect - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @u.in_time_zone("Alaska").inspect - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @z.in_time_zone("Alaska").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 HST -10:00", @s.in_time_zone("Hawaii").inspect - assert_equal "Fri, 31 Dec 1999 14:00:00.000000000 HST -10:00", @u.in_time_zone("Hawaii").inspect - assert_equal "Fri, 31 Dec 1999 14:00:00.000000000 HST -10:00", @z.in_time_zone("Hawaii").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 UTC +00:00", @s.in_time_zone("UTC").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 UTC +00:00", @u.in_time_zone("UTC").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 UTC +00:00", @z.in_time_zone("UTC").inspect - assert_equal "Sat, 01 Jan 2000 00:00:00.000000000 AKST -09:00", @s.in_time_zone(-9.hours).inspect - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @u.in_time_zone(-9.hours).inspect - assert_equal "Fri, 31 Dec 1999 15:00:00.000000000 AKST -09:00", @z.in_time_zone(-9.hours).inspect + assert_equal "2000-01-01 00:00:00.000000000 AKST -09:00", @s.in_time_zone("Alaska").inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @u.in_time_zone("Alaska").inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @z.in_time_zone("Alaska").inspect + assert_equal "2000-01-01 00:00:00.000000000 HST -10:00", @s.in_time_zone("Hawaii").inspect + assert_equal "1999-12-31 14:00:00.000000000 HST -10:00", @u.in_time_zone("Hawaii").inspect + assert_equal "1999-12-31 14:00:00.000000000 HST -10:00", @z.in_time_zone("Hawaii").inspect + assert_equal "2000-01-01 00:00:00.000000000 UTC +00:00", @s.in_time_zone("UTC").inspect + assert_equal "2000-01-01 00:00:00.000000000 UTC +00:00", @u.in_time_zone("UTC").inspect + assert_equal "2000-01-01 00:00:00.000000000 UTC +00:00", @z.in_time_zone("UTC").inspect + assert_equal "2000-01-01 00:00:00.000000000 AKST -09:00", @s.in_time_zone(-9.hours).inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @u.in_time_zone(-9.hours).inspect + assert_equal "1999-12-31 15:00:00.000000000 AKST -09:00", @z.in_time_zone(-9.hours).inspect end end From 8902ac32c938bc7e695ec7c85089ff378a5b812f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Wn=C4=99trzak?= Date: Fri, 6 Sep 2024 07:58:42 +0200 Subject: [PATCH 13/25] Fix Solid Queue changelog db info [ci skip] --- railties/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index a78bc542477e6..0ef6a6e14b1ef 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,4 +1,4 @@ -* Use [Solid Queue](https://github.com/rails/solid_queue) as the default Active Job backend in production, configured as a separate cache database in config/database.yml. In a single-server deployment, it'll run as a Puma plugin. This is configured in `config/deploy.yml` and can easily be changed to use a dedicated jobs machine. +* Use [Solid Queue](https://github.com/rails/solid_queue) as the default Active Job backend in production, configured as a separate queue database in config/database.yml. In a single-server deployment, it'll run as a Puma plugin. This is configured in `config/deploy.yml` and can easily be changed to use a dedicated jobs machine. *DHH* From 83d8896ed98b54e6b6c814239b737b98653147e3 Mon Sep 17 00:00:00 2001 From: Rikita Ishikawa Date: Fri, 6 Sep 2024 17:14:09 +0900 Subject: [PATCH 14/25] Use `deliver_now!` instead of `deliver_now` The description of the test did not match the actual method of execution. --- actionmailer/test/callbacks_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionmailer/test/callbacks_test.rb b/actionmailer/test/callbacks_test.rb index 82adf59f5f7b5..3a2cf8f31822c 100644 --- a/actionmailer/test/callbacks_test.rb +++ b/actionmailer/test/callbacks_test.rb @@ -39,7 +39,7 @@ class ActionMailerCallbacksTest < ActiveSupport::TestCase end test "deliver_now! should call after_deliver callback" do - CallbackMailer.test_message.deliver_now + CallbackMailer.test_message.deliver_now! assert_kind_of CallbackMailer, CallbackMailer.after_deliver_instance end From 8421db704a48e1997a9e327286f257dd388f55fa Mon Sep 17 00:00:00 2001 From: Rafey Ahmad <71697384+glokta1@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:13:39 +0530 Subject: [PATCH 15/25] Update filter_parameters default in configuring guide [ci skip] --- guides/source/configuring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 0b966052f2f21..f296fb599dd61 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -409,7 +409,7 @@ default, Rails filters out passwords by adding the following filters in ```ruby Rails.application.config.filter_parameters += [ - :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc ] ``` From ded0cfecfbb43f13d29c891eaa7ba3c1c4337fd8 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Fri, 6 Sep 2024 23:02:39 +1000 Subject: [PATCH 16/25] Bring back deleted middleware test (#52812) --- railties/test/commands/middleware_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/railties/test/commands/middleware_test.rb b/railties/test/commands/middleware_test.rb index 00c1af3ab67fa..c64720c03a86f 100644 --- a/railties/test/commands/middleware_test.rb +++ b/railties/test/commands/middleware_test.rb @@ -196,6 +196,12 @@ def app assert_includes middleware, "ActionDispatch::AssumeSSL" end + test "ActionDispatch::SSL is present when force_ssl is set" do + add_to_config "config.force_ssl = true" + boot! + assert_includes middleware, "ActionDispatch::SSL" + end + test "silence healthcheck" do add_to_config "config.silence_healthcheck_path = '/up'" boot! From 6276488e5ae3efd3646381e3e01bc5454e975fdf Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 5 Sep 2024 12:56:50 +0200 Subject: [PATCH 17/25] Allow async queries in transactional fixtures This is possible since https://github.com/rails/rails/pull/50999. Now in transactional fixtures, the other threads do checkout the same connection as the same thread, it's just synchronized around performing the query. This allow to properly test async queries without having to disable transactional fixtures. --- activerecord/CHANGELOG.md | 10 ++++ .../asynchronous_queries_tracker.rb | 52 ++++++++++--------- .../abstract/database_statements.rb | 2 +- .../lib/active_record/future_result.rb | 22 ++++---- activerecord/lib/active_record/relation.rb | 5 +- .../lib/active_record/test_fixtures.rb | 12 +++++ .../test/cases/asynchronous_queries_test.rb | 13 ----- .../test/cases/relation/load_async_test.rb | 8 --- 8 files changed, 65 insertions(+), 59 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 0a1826f5ef636..7765e578f5387 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,13 @@ +* Make Active Record asynchronous queries compatible with transactional fixtures. + + Previously transactional fixtures would disable asynchronous queries, because transactional + fixtures impose all queries use the same connection. + + Now asynchronous queries will use the connection pinned by transactional fixtures, and behave + much closer to production. + + *Jean Boussier* + * Deserialize binary data before decrypting This ensures that we call `PG::Connection.unescape_bytea` on PostgreSQL before decryption. diff --git a/activerecord/lib/active_record/asynchronous_queries_tracker.rb b/activerecord/lib/active_record/asynchronous_queries_tracker.rb index d9cd4117e8704..322adb595cac7 100644 --- a/activerecord/lib/active_record/asynchronous_queries_tracker.rb +++ b/activerecord/lib/active_record/asynchronous_queries_tracker.rb @@ -1,29 +1,30 @@ # frozen_string_literal: true +require "concurrent/atomic/atomic_boolean" +require "concurrent/atomic/read_write_lock" + module ActiveRecord class AsynchronousQueriesTracker # :nodoc: - module NullSession # :nodoc: - class << self - def active? - true - end - - def finalize - end - end - end - class Session # :nodoc: def initialize - @active = true + @active = Concurrent::AtomicBoolean.new(true) + @lock = Concurrent::ReadWriteLock.new end def active? - @active + @active.true? end - def finalize - @active = false + def synchronize(&block) + @lock.with_read_lock(&block) + end + + def finalize(wait = false) + @active.make_false + if wait + # Wait until all thread with a read lock are done + @lock.with_write_lock { } + end end end @@ -33,7 +34,7 @@ def install_executor_hooks(executor = ActiveSupport::Executor) end def run - ActiveRecord::Base.asynchronous_queries_tracker.start_session + ActiveRecord::Base.asynchronous_queries_tracker.tap(&:start_session) end def complete(asynchronous_queries_tracker) @@ -41,20 +42,23 @@ def complete(asynchronous_queries_tracker) end end - attr_reader :current_session - def initialize - @current_session = NullSession + @stack = [] + end + + def current_session + @stack.last or raise ActiveRecordError, "Can't perform asynchronous queries without a query session" end def start_session - @current_session = Session.new - self + session = Session.new + @stack << session end - def finalize_session - @current_session.finalize - @current_session = NullSession + def finalize_session(wait = false) + session = @stack.pop + session&.finalize(wait) + self end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index e0299bccd93c1..1493cb01745ab 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -683,7 +683,7 @@ def select(sql, name = nil, binds = [], prepare: false, async: false, allow_retr binds, prepare: prepare, ) - if supports_concurrent_connections? && current_transaction.closed? + if supports_concurrent_connections? && !current_transaction.joinable? future_result.schedule!(ActiveRecord::Base.asynchronous_queries_session) else future_result.execute!(self) diff --git a/activerecord/lib/active_record/future_result.rb b/activerecord/lib/active_record/future_result.rb index 289fc6f761dcc..7ce1937d99e5c 100644 --- a/activerecord/lib/active_record/future_result.rb +++ b/activerecord/lib/active_record/future_result.rb @@ -100,17 +100,21 @@ def cancel def execute_or_skip return unless pending? - @pool.with_connection do |connection| - return unless @mutex.try_lock - begin - if pending? - @event_buffer = EventBuffer.new(self, @instrumenter) - connection.with_instrumenter(@event_buffer) do - execute_query(connection, async: true) + @session.synchronize do + return unless pending? + + @pool.with_connection do |connection| + return unless @mutex.try_lock + begin + if pending? + @event_buffer = EventBuffer.new(self, @instrumenter) + connection.with_instrumenter(@event_buffer) do + execute_query(connection, async: true) + end end + ensure + @mutex.unlock end - ensure - @mutex.unlock end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 668758c8483a9..f6ed82b19281b 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1124,9 +1124,6 @@ def delete_by(*args) # for queries to actually be executed concurrently. Otherwise it defaults to # executing them in the foreground. # - # +load_async+ will also fall back to executing in the foreground in the test environment when transactional - # fixtures are enabled. - # # If the query was actually executed in the background, the Active Record logs will show # it by prefixing the log line with ASYNC: # @@ -1136,7 +1133,7 @@ def load_async return load if !c.async_enabled? unless loaded? - result = exec_main_query(async: c.current_transaction.closed?) + result = exec_main_query(async: !c.current_transaction.joinable?) if result.is_a?(Array) @records = result diff --git a/activerecord/lib/active_record/test_fixtures.rb b/activerecord/lib/active_record/test_fixtures.rb index 56f2fdf7b3e79..3d617e78c12fa 100644 --- a/activerecord/lib/active_record/test_fixtures.rb +++ b/activerecord/lib/active_record/test_fixtures.rb @@ -137,12 +137,15 @@ def setup_fixtures(config = ActiveRecord::Base) invalidate_already_loaded_fixtures @loaded_fixtures = load_fixtures(config) end + setup_asynchronous_queries_session # Instantiate fixtures for every test if requested. instantiate_fixtures if use_instantiated_fixtures end def teardown_fixtures + teardown_asynchronous_queries_session + # Rollback changes if a transaction is active. if run_in_transaction? teardown_transactional_fixtures @@ -154,6 +157,14 @@ def teardown_fixtures ActiveRecord::Base.connection_handler.clear_active_connections!(:all) end + def setup_asynchronous_queries_session + @_async_queries_session = ActiveRecord::Base.asynchronous_queries_tracker.start_session + end + + def teardown_asynchronous_queries_session + ActiveRecord::Base.asynchronous_queries_tracker.finalize_session(true) if @_async_queries_session + end + def invalidate_already_loaded_fixtures @@already_loaded_fixtures.clear end @@ -190,6 +201,7 @@ def setup_transactional_fixtures def teardown_transactional_fixtures ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber + unless @fixture_connection_pools.map(&:unpin_connection!).all? # Something caused the transaction to be committed or rolled back # We can no longer trust the database is in a clean state. diff --git a/activerecord/test/cases/asynchronous_queries_test.rb b/activerecord/test/cases/asynchronous_queries_test.rb index 3d677cde0d785..ef54b537b250c 100644 --- a/activerecord/test/cases/asynchronous_queries_test.rb +++ b/activerecord/test/cases/asynchronous_queries_test.rb @@ -6,8 +6,6 @@ module AsynchronousQueriesSharedTests def test_async_select_failure - ActiveRecord::Base.asynchronous_queries_tracker.start_session - if in_memory_db? assert_raises ActiveRecord::StatementInvalid do @connection.select_all "SELECT * FROM does_not_exists", async: true @@ -19,13 +17,9 @@ def test_async_select_failure future_result.result end end - ensure - ActiveRecord::Base.asynchronous_queries_tracker.finalize_session end def test_async_query_from_transaction - ActiveRecord::Base.asynchronous_queries_tracker.start_session - assert_nothing_raised do @connection.select_all "SELECT * FROM posts", async: true end @@ -37,20 +31,15 @@ def test_async_query_from_transaction end end end - ensure - ActiveRecord::Base.asynchronous_queries_tracker.finalize_session end def test_async_query_cache - ActiveRecord::Base.asynchronous_queries_tracker.start_session - @connection.enable_query_cache! @connection.select_all "SELECT * FROM posts" result = @connection.select_all "SELECT * FROM posts", async: true assert_equal ActiveRecord::FutureResult::Complete, result.class ensure - ActiveRecord::Base.asynchronous_queries_tracker.finalize_session @connection.disable_query_cache! end @@ -103,7 +92,6 @@ def setup end def test_async_select_all - ActiveRecord::Base.asynchronous_queries_tracker.start_session status = {} subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |event| @@ -125,7 +113,6 @@ def test_async_select_all assert_kind_of ActiveRecord::Result, future_result.result assert_equal @connection.supports_concurrent_connections?, status[:async] ensure - ActiveRecord::Base.asynchronous_queries_tracker.finalize_session ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber end end diff --git a/activerecord/test/cases/relation/load_async_test.rb b/activerecord/test/cases/relation/load_async_test.rb index c4250268f87fc..6e2248080ba07 100644 --- a/activerecord/test/cases/relation/load_async_test.rb +++ b/activerecord/test/cases/relation/load_async_test.rb @@ -10,8 +10,6 @@ module ActiveRecord class LoadAsyncTest < ActiveRecord::TestCase include WaitForAsyncTestHelper - self.use_transactional_tests = false - fixtures :posts, :comments, :categories, :categories_posts def test_scheduled? @@ -240,8 +238,6 @@ def test_load_async_count_with_query_cache class LoadAsyncNullExecutorTest < ActiveRecord::TestCase unless in_memory_db? - self.use_transactional_tests = false - fixtures :posts, :comments def setup @@ -364,8 +360,6 @@ class LoadAsyncMultiThreadPoolExecutorTest < ActiveRecord::TestCase unless in_memory_db? include WaitForAsyncTestHelper - self.use_transactional_tests = false - fixtures :posts, :comments def setup @@ -505,8 +499,6 @@ class LoadAsyncMixedThreadPoolExecutorTest < ActiveRecord::TestCase unless in_memory_db? include WaitForAsyncTestHelper - self.use_transactional_tests = false - fixtures :posts, :comments, :other_dogs def setup From f686b75d012cedad15d3707b043b7c6347ae1614 Mon Sep 17 00:00:00 2001 From: Hartley McGuire Date: Fri, 6 Sep 2024 18:23:19 +0000 Subject: [PATCH 18/25] Use implication for skip_active_record/skip_solid (#52802) * Generator options should default to nil for unset The generator options are effectively tri-state, with true/false meaning set by the user and nil being unset. However, some of these options were defaulting to false, meaning they were being treated as set to false by the user. This commit fixes this issue by setting the values to nil by default instead of false so that they are properly "unset" by user. * Use implication for skip_active_record/skip_solid When skip_solid was added, it used the "old style" option implication where skip_*? methods delegate to all the options that should be implied. This has a few downsides such as making it unclear to users when conflicting options may be specified (for example, skip_active_record and no_skip_solid). This commit changes the skip_solid option to use the new style of implication so that error messages are better and the predicate method is simpler. Co-authored-by: Jon Rowe --------- Co-authored-by: Jon Rowe --- railties/lib/rails/generators/app_base.rb | 10 +++++----- railties/test/generators/shared_generator_tests.rb | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 4a89c79f43538..1cd962f09e225 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -111,16 +111,16 @@ def self.add_shared_options_for(name) class_option :skip_ci, type: :boolean, default: nil, desc: "Skip GitHub CI files" - class_option :skip_kamal, type: :boolean, default: false, + class_option :skip_kamal, type: :boolean, default: nil, desc: "Skip Kamal setup" - class_option :skip_solid, type: :boolean, default: false, + class_option :skip_solid, type: :boolean, default: nil, desc: "Skip Solid Cache & Queue setup" class_option :dev, type: :boolean, default: nil, desc: "Set up the #{name} with Gemfile pointing to your Rails checkout" - class_option :devcontainer, type: :boolean, default: false, + class_option :devcontainer, type: :boolean, default: nil, desc: "Generate devcontainer files" class_option :edge, type: :boolean, default: nil, @@ -206,7 +206,7 @@ def deduce_implied_options(options, option_reasons, meta_options) OPTION_IMPLICATIONS = { # :nodoc: skip_active_job: [:skip_action_mailer, :skip_active_storage], - skip_active_record: [:skip_active_storage], + skip_active_record: [:skip_active_storage, :skip_solid], skip_active_storage: [:skip_action_mailbox, :skip_action_text], skip_javascript: [:skip_hotwire], } @@ -432,7 +432,7 @@ def skip_kamal? end def skip_solid? - options[:skip_active_record] || options[:skip_solid] + options[:skip_solid] end class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out) diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index e3aa3d2f072c8..925d07724b7a3 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -279,6 +279,7 @@ def test_generator_if_skip_active_record_is_given assert_gitattributes_does_not_have_schema_file assert_file "Gemfile" do |contents| + assert_no_match(/solid_cache/, contents) assert_no_match(/sqlite/, contents) end end From f52525e53e4ed0cb9f286352f6c87b7acd982b00 Mon Sep 17 00:00:00 2001 From: Peter Sankauskas Date: Fri, 6 Sep 2024 11:26:16 -0700 Subject: [PATCH 19/25] Replace deprecated PWA tag with newer one (#52738) * Replace deprecated PWA tag with newer one * Include both tags for now --- .../app/templates/app/views/layouts/application.html.erb.tt | 1 + 1 file changed, 1 insertion(+) diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt index d236fdb2bd048..106afd28b3c58 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt @@ -4,6 +4,7 @@ <%%= content_for(:title) || "<%= app_name.titleize %>" %> + <%%= csrf_meta_tags %> <%%= csp_meta_tag %> From 950907b3d35b7fd07fe409a9fd6f65f4378a1c93 Mon Sep 17 00:00:00 2001 From: glaszig Date: Thu, 5 Sep 2024 02:41:13 -0300 Subject: [PATCH 20/25] system testing: disable chrome's search engine choice modal starting chrome with a fresh profile shows a search engine choice screen which will break any tests. passing a special argument will disable the screen. see SeleniumHQ/selenium#14431 also: - refactor default chrome option initialization - compress browser option initialization --- actionpack/CHANGELOG.md | 4 +++ .../action_dispatch/system_testing/browser.rb | 33 +++++++------------ .../dispatch/system_testing/driver_test.rb | 4 +-- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 1ba19456aa94a..0c28a52fc5204 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,7 @@ +* System Testing: Disable Chrome's search engine choice by default in system tests. + + *glaszig* + * Fix `Request#raw_post` raising `NoMethodError` when `rack.input` is `nil`. *Hartley McGuire* diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb index 40f9578198b95..e08e487691352 100644 --- a/actionpack/lib/action_dispatch/system_testing/browser.rb +++ b/actionpack/lib/action_dispatch/system_testing/browser.rb @@ -9,7 +9,6 @@ class Browser # :nodoc: def initialize(name) @name = name - set_default_options end def type @@ -27,9 +26,9 @@ def options @options ||= case type when :chrome - ::Selenium::WebDriver::Chrome::Options.new + default_chrome_options when :firefox - ::Selenium::WebDriver::Firefox::Options.new + default_firefox_options end end @@ -49,26 +48,18 @@ def preload end private - def set_default_options - case name - when :headless_chrome - set_headless_chrome_browser_options - when :headless_firefox - set_headless_firefox_browser_options - end + def default_chrome_options + options = ::Selenium::WebDriver::Chrome::Options.new + options.add_argument("--disable-search-engine-choice-screen") + options.add_argument("--headless") if name == :headless_chrome + options.add_argument("--disable-gpu") if Gem.win_platform? + options end - def set_headless_chrome_browser_options - configure do |capabilities| - capabilities.add_argument("--headless") - capabilities.add_argument("--disable-gpu") if Gem.win_platform? - end - end - - def set_headless_firefox_browser_options - configure do |capabilities| - capabilities.add_argument("-headless") - end + def default_firefox_options + options = ::Selenium::WebDriver::Firefox::Options.new + options.add_argument("-headless") if name == :headless_firefox + options end def resolve_driver_path(namespace) diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb index 138c1038649e8..fc79701026e71 100644 --- a/actionpack/test/dispatch/system_testing/driver_test.rb +++ b/actionpack/test/dispatch/system_testing/driver_test.rb @@ -82,7 +82,7 @@ class DriverTest < ActiveSupport::TestCase expected = { "goog:chromeOptions" => { - "args" => ["start-maximized"], + "args" => ["--disable-search-engine-choice-screen", "start-maximized"], "mobileEmulation" => { "deviceName" => "iphone 6" }, "prefs" => { "detach" => true } }, @@ -101,7 +101,7 @@ class DriverTest < ActiveSupport::TestCase expected = { "goog:chromeOptions" => { - "args" => ["--headless", "start-maximized"], + "args" => ["--disable-search-engine-choice-screen", "--headless", "start-maximized"], "mobileEmulation" => { "deviceName" => "iphone 6" }, "prefs" => { "detach" => true } }, From 6b1515aeb9647f7c5fd98799adedc1b90d019f8d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 6 Sep 2024 20:16:13 +0900 Subject: [PATCH 21/25] Drop MySQL 5.5 support MySQL 5.5 is the only version that does not support datetime with precision, which we have supported in the core. MySQL 5.5 is already EOL in December 2018, so let's drop this support and clean up the code for it. --- activerecord/CHANGELOG.md | 8 +++ .../abstract_mysql_adapter.rb | 6 +- .../datetime_precision_quoting_test.rb | 47 ------------ activerecord/test/cases/calculations_test.rb | 2 - .../test/cases/collection_cache_key_test.rb | 2 - activerecord/test/cases/date_time_test.rb | 1 - activerecord/test/cases/dirty_test.rb | 1 - activerecord/test/cases/insert_all_test.rb | 4 +- activerecord/test/cases/integration_test.rb | 2 - activerecord/test/cases/locking_test.rb | 2 - .../cases/migration/change_schema_test.rb | 3 +- .../test/cases/migration/foreign_key_test.rb | 2 - activerecord/test/cases/primary_keys_test.rb | 2 +- activerecord/test/cases/schema_dumper_test.rb | 8 +-- activerecord/test/cases/timestamp_test.rb | 4 -- .../test/cases/type/date_time_test.rb | 1 - .../test/schema/mysql2_specific_schema.rb | 22 +++--- activerecord/test/schema/schema.rb | 72 +++++-------------- .../test/schema/trilogy_specific_schema.rb | 22 +++--- 19 files changed, 52 insertions(+), 159 deletions(-) delete mode 100644 activerecord/test/cases/adapters/abstract_mysql_adapter/datetime_precision_quoting_test.rb diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 7765e578f5387..852f926e3b8c8 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,11 @@ +* Drop MySQL 5.5 support. + + MySQL 5.5 is the only version that does not support datetime with precision, + which we have supported in the core. Now we support MySQL 5.6.4 or later, which + is the first version to support datetime with precision. + + *Ryuta Kamizono* + * Make Active Record asynchronous queries compatible with transactional fixtures. Previously transactional fixtures would disable asynchronous queries, because transactional 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 eafbbaaad6083..c2b9257bd91a2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -138,7 +138,7 @@ def supports_views? end def supports_datetime_with_precision? - mariadb? || database_version >= "5.6.4" + true end def supports_virtual_columns? @@ -668,8 +668,8 @@ def build_insert_sql(insert) # :nodoc: end def check_version # :nodoc: - if database_version < "5.5.8" - raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8." + if database_version < "5.6.4" + raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.6.4." end end diff --git a/activerecord/test/cases/adapters/abstract_mysql_adapter/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/abstract_mysql_adapter/datetime_precision_quoting_test.rb deleted file mode 100644 index 9edcc1a2dfe36..0000000000000 --- a/activerecord/test/cases/adapters/abstract_mysql_adapter/datetime_precision_quoting_test.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require "cases/helper" - -class DatetimePrecisionQuotingTest < ActiveRecord::AbstractMysqlTestCase - setup do - @connection = ActiveRecord::Base.lease_connection - end - - test "microsecond precision for MySQL gte 5.6.4" do - stub_version "5.6.4" do - assert_microsecond_precision - end - end - - test "no microsecond precision for MySQL lt 5.6.4" do - stub_version "5.6.3" do - assert_no_microsecond_precision - end - end - - test "microsecond precision for MariaDB gte 5.3.0" do - stub_version "5.5.5-10.1.8-MariaDB-log" do - assert_microsecond_precision - end - end - - private - def assert_microsecond_precision - assert_match_quoted_microsecond_datetime(/\.123456\z/) - end - - def assert_no_microsecond_precision - assert_match_quoted_microsecond_datetime(/:55\z/) - end - - def assert_match_quoted_microsecond_datetime(match) - assert_match match, @connection.quoted_date(Time.now.change(sec: 55, usec: 123456)) - end - - def stub_version(full_version_string, &block) - @connection.pool.pool_config.server_version = nil - @connection.stub(:get_full_version, full_version_string, &block) - ensure - @connection.pool.pool_config.server_version = nil - end -end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 5e0f962784458..ac54b4148a632 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -1466,8 +1466,6 @@ def test_minimum_and_maximum_on_tz_aware_attributes end def assert_minimum_and_maximum_on_time_attributes(time_class) - skip unless supports_datetime_with_precision? # Remove once MySQL 5.5 support is dropped. - actual = Topic.minimum(:written_on) assert_equal Time.utc(2003, 7, 16, 14, 28, 11, 223300), actual assert_instance_of time_class, actual diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb index 669831e61e6b0..7f678300af96a 100644 --- a/activerecord/test/cases/collection_cache_key_test.rb +++ b/activerecord/test/cases/collection_cache_key_test.rb @@ -106,7 +106,6 @@ class CollectionCacheKeyTest < ActiveRecord::TestCase developers = Developer.where(name: "David") cache_key = developers.cache_key - sleep 1.0 unless supports_datetime_with_precision? # Remove once MySQL 5.5 support is dropped. developers.update_all(updated_at: Time.now.utc) assert_not_equal cache_key, developers.cache_key @@ -116,7 +115,6 @@ class CollectionCacheKeyTest < ActiveRecord::TestCase developers = Developer.includes(:projects).where("projects.name": "Active Record") cache_key = developers.cache_key - sleep 1.0 unless supports_datetime_with_precision? # Remove once MySQL 5.5 support is dropped. developers.update_all(updated_at: Time.now.utc) assert_not_equal cache_key, developers.cache_key diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index b660212fb27ef..bbe50dda8c479 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -72,7 +72,6 @@ def test_assign_in_local_timezone end def test_date_time_with_string_value_with_subsecond_precision - skip unless supports_datetime_with_precision? string_value = "2017-07-04 14:19:00.5" topic = Topic.create(written_on: string_value) assert_equal topic, Topic.find_by(written_on: string_value) diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 658309fe06a4e..96f379217819d 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -620,7 +620,6 @@ def test_field_named_field end def test_datetime_attribute_can_be_updated_with_fractional_seconds - skip "Fractional seconds are not supported" unless supports_datetime_with_precision? in_time_zone "Paris" do target = Class.new(ActiveRecord::Base) target.table_name = "topics" diff --git a/activerecord/test/cases/insert_all_test.rb b/activerecord/test/cases/insert_all_test.rb index 98cedc6a0ac6a..33aa28b621f6b 100644 --- a/activerecord/test/cases/insert_all_test.rb +++ b/activerecord/test/cases/insert_all_test.rb @@ -479,7 +479,7 @@ def test_upsert_all_touches_updated_at_and_updated_on_when_values_change end def test_upsert_all_respects_updated_at_precision_when_touched_implicitly - skip unless supports_insert_on_duplicate_update? && supports_datetime_with_precision? + skip unless supports_insert_on_duplicate_update? Book.insert_all [{ id: 101, name: "Out of the Silent Planet", published_on: Date.new(1938, 4, 1), updated_at: 5.years.ago, updated_on: 5.years.ago }] @@ -569,7 +569,7 @@ def test_upsert_all_implicitly_sets_timestamps_on_create_when_model_record_times end def test_upsert_all_respects_created_at_precision_when_touched_implicitly - skip unless supports_insert_on_duplicate_update? && supports_datetime_with_precision? + skip unless supports_insert_on_duplicate_update? # A single upsert can occur exactly at the seconds boundary (when usec is naturally zero), so try multiple times. has_subsecond_precision = (1..100).any? do |i| diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb index 87e6ac60164d1..82d72ed673513 100644 --- a/activerecord/test/cases/integration_test.rb +++ b/activerecord/test/cases/integration_test.rb @@ -174,7 +174,6 @@ def test_cache_key_for_newer_updated_on end def test_cache_key_format_is_precise_enough - skip("Subsecond precision is not supported") unless supports_datetime_with_precision? dev = Developer.first key = dev.cache_key travel_to dev.updated_at + 0.000001 do @@ -191,7 +190,6 @@ def test_cache_key_format_is_not_too_precise end def test_cache_version_format_is_precise_enough - skip("Subsecond precision is not supported") unless supports_datetime_with_precision? with_cache_versioning do dev = Developer.first version = dev.cache_version.to_param diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 8b2b37c4f2ae3..068a35af5b8ab 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -179,7 +179,6 @@ def test_touch_existing_lock p1 = Person.find(1) assert_equal 0, p1.lock_version - sleep 1.0 unless supports_datetime_with_precision? # Remove once MySQL 5.5 support is dropped. p1.touch assert_equal 1, p1.lock_version @@ -298,7 +297,6 @@ def test_touch_existing_lock_without_default_should_work_with_null_in_the_databa assert_equal 0, t1.lock_version assert_nil t1.lock_version_before_type_cast - sleep 1.0 unless supports_datetime_with_precision? # Remove once MySQL 5.5 support is dropped. t1.touch assert_equal 1, t1.lock_version diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index 05fd547ba7416..b5dce30202bca 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -294,8 +294,7 @@ def test_add_column_with_postgresql_datetime_type if current_adapter?(:PostgreSQLAdapter) assert_equal "timestamp(6) without time zone", column.sql_type elsif current_adapter?(:Mysql2Adapter, :TrilogyAdapter) - sql_type = supports_datetime_with_precision? ? "datetime(6)" : "datetime" - assert_equal sql_type, column.sql_type + assert_equal "datetime(6)", column.sql_type else assert_equal connection.type_to_sql("datetime(6)"), column.sql_type end diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index 05c479cf93d6f..720871cc169df 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -761,8 +761,6 @@ def test_add_foreign_key_with_if_not_exists_not_set if current_adapter?(:Mysql2Adapter, :TrilogyAdapter) if ActiveRecord::Base.lease_connection.mariadb? assert_match(/Duplicate key on write or update/, error.message) - elsif ActiveRecord::Base.lease_connection.database_version < "5.6" - assert_match(/Can't create table/, error.message) elsif ActiveRecord::Base.lease_connection.database_version < "8.0" assert_match(/Can't write; duplicate key in table/, error.message) else diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 0b7ef2ff663ee..4f430149b8dae 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -351,7 +351,7 @@ def test_any_type_primary_key assert_no_match %r{t\.index \["code"\]}, schema end - if current_adapter?(:Mysql2Adapter, :TrilogyAdapter) && supports_datetime_with_precision? + if current_adapter?(:Mysql2Adapter, :TrilogyAdapter) test "schema typed primary key column" do @connection.create_table(:scheduled_logs, id: :timestamp, precision: 6, force: true) schema = dump_table_schema("scheduled_logs") diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index d19a756d5ae62..26c0156dbb094 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -934,13 +934,7 @@ def test_schema_dump_defaults_with_universally_supported_types assert_match %r{t\.string\s+"string_with_default",.*?default: "Hello!"}, output assert_match %r{t\.date\s+"date_with_default",\s+default: "2014-06-05"}, output - - if supports_datetime_with_precision? - assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: "2014-06-05 07:17:04"}, output - else - assert_match %r{t\.datetime\s+"datetime_with_default",\s+precision: nil,\s+default: "2014-06-05 07:17:04"}, output - end - + assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: "2014-06-05 07:17:04"}, output assert_match %r{t\.time\s+"time_with_default",\s+default: "2000-01-01 07:17:04"}, output assert_match %r{t\.decimal\s+"decimal_with_default",\s+precision: 20,\s+scale: 10,\s+default: "1234567890.0123456789"}, output end diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 95d08a1cd87bb..2bd1198eaec36 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -34,8 +34,6 @@ def test_saving_a_unchanged_record_doesnt_update_its_timestamp end def test_touching_a_record_updates_its_timestamp - sleep 1.0 unless supports_datetime_with_precision? # Remove once MySQL 5.5 support is dropped. - previous_salary = @developer.salary @developer.salary = previous_salary + 10000 @developer.touch @@ -53,8 +51,6 @@ def test_touching_a_record_updates_its_timestamp end def test_touching_a_record_with_default_scope_that_excludes_it_updates_its_timestamp - sleep 1.0 unless supports_datetime_with_precision? # Remove once MySQL 5.5 support is dropped. - developer = @developer.becomes(DeveloperCalledJamis) developer.touch diff --git a/activerecord/test/cases/type/date_time_test.rb b/activerecord/test/cases/type/date_time_test.rb index a5abea22ead99..1ee8b55b56e0f 100644 --- a/activerecord/test/cases/type/date_time_test.rb +++ b/activerecord/test/cases/type/date_time_test.rb @@ -7,7 +7,6 @@ module ActiveRecord module Type class DateTimeTest < ActiveRecord::TestCase def test_datetime_seconds_precision_applied_to_timestamp - skip "This test is invalid if subsecond precision isn't supported" unless supports_datetime_with_precision? p = Task.create!(starting: ::Time.now) assert_equal p.starting.usec, p.reload.starting.usec end diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 66006fc0e1dec..ef8725ad89949 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -1,19 +1,17 @@ # frozen_string_literal: true ActiveRecord::Schema.define do - if connection.supports_datetime_with_precision? - create_table :datetime_defaults, force: true do |t| - t.datetime :modified_datetime, precision: nil, default: -> { "CURRENT_TIMESTAMP" } - t.datetime :precise_datetime, default: -> { "CURRENT_TIMESTAMP(6)" } - t.datetime :updated_datetime, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } - end + create_table :datetime_defaults, force: true do |t| + t.datetime :modified_datetime, precision: nil, default: -> { "CURRENT_TIMESTAMP" } + t.datetime :precise_datetime, default: -> { "CURRENT_TIMESTAMP(6)" } + t.datetime :updated_datetime, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } + end - create_table :timestamp_defaults, force: true do |t| - t.timestamp :nullable_timestamp - t.timestamp :modified_timestamp, precision: nil, default: -> { "CURRENT_TIMESTAMP" } - t.timestamp :precise_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6)" } - t.timestamp :updated_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } - end + create_table :timestamp_defaults, force: true do |t| + t.timestamp :nullable_timestamp + t.timestamp :modified_timestamp, precision: nil, default: -> { "CURRENT_TIMESTAMP" } + t.timestamp :precise_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6)" } + t.timestamp :updated_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } end create_table :defaults, force: true do |t| diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 18c5d6b4993b7..614229d884636 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -148,13 +148,8 @@ t.index :isbn, where: "published_on IS NOT NULL", unique: true t.index "(lower(external_id))", unique: true if supports_expression_index? - if supports_datetime_with_precision? - t.datetime :created_at, precision: 6 - t.datetime :updated_at, precision: 6 - else - t.datetime :created_at - t.datetime :updated_at - end + t.datetime :created_at + t.datetime :updated_at t.date :updated_on end @@ -541,17 +536,10 @@ t.integer :salary, default: 70000 t.references :firm, index: false t.integer :mentor_id - if supports_datetime_with_precision? - t.datetime :legacy_created_at, precision: 6 - t.datetime :legacy_updated_at, precision: 6 - t.datetime :legacy_created_on, precision: 6 - t.datetime :legacy_updated_on, precision: 6 - else - t.datetime :legacy_created_at - t.datetime :legacy_updated_at - t.datetime :legacy_created_on - t.datetime :legacy_updated_on - end + t.datetime :legacy_created_at + t.datetime :legacy_updated_at + t.datetime :legacy_created_on + t.datetime :legacy_updated_on end create_table :developers_projects, force: true, id: false do |t| @@ -685,11 +673,7 @@ create_table :invoices, force: true do |t| t.integer :balance - if supports_datetime_with_precision? - t.datetime :updated_at, precision: 6 - else - t.datetime :updated_at - end + t.datetime :updated_at end create_table :iris, force: true do |t| @@ -882,11 +866,7 @@ create_table :owners, primary_key: :owner_id, force: true do |t| t.string :name - if supports_datetime_with_precision? - t.column :updated_at, :datetime, precision: 6 - else - t.column :updated_at, :datetime - end + t.column :updated_at, :datetime t.column :happy_at, :datetime t.string :essay_id end @@ -907,30 +887,18 @@ t.string :parrot_sti_class t.integer :killer_id t.integer :updated_count, :integer, default: 0 - if supports_datetime_with_precision? - t.datetime :created_at, precision: 0 - t.datetime :created_on, precision: 0 - t.datetime :updated_at, precision: 0 - t.datetime :updated_on, precision: 0 - else - t.datetime :created_at - t.datetime :created_on - t.datetime :updated_at - t.datetime :updated_on - end + t.datetime :created_at, precision: 0 + t.datetime :created_on, precision: 0 + t.datetime :updated_at, precision: 0 + t.datetime :updated_on, precision: 0 end create_table :pirates, force: :cascade do |t| t.string :catchphrase t.integer :parrot_id t.integer :non_validated_parrot_id - if supports_datetime_with_precision? - t.datetime :created_on, precision: 6 - t.datetime :updated_on, precision: 6 - else - t.datetime :created_on - t.datetime :updated_on - end + t.datetime :created_on + t.datetime :updated_on end create_table :treasures, force: :cascade do |t| @@ -1154,11 +1122,7 @@ create_table :ship_parts, force: true do |t| t.string :name t.integer :ship_id - if supports_datetime_with_precision? - t.datetime :updated_at, precision: 6 - else - t.datetime :updated_at - end + t.datetime :updated_at end create_table :squeaks, force: true do |t| @@ -1234,11 +1198,7 @@ t.string :title, limit: 250 t.string :author_name t.string :author_email_address - if supports_datetime_with_precision? - t.datetime :written_on, precision: 6 - else - t.datetime :written_on - end + t.datetime :written_on t.time :bonus_time t.date :last_read t.text :content diff --git a/activerecord/test/schema/trilogy_specific_schema.rb b/activerecord/test/schema/trilogy_specific_schema.rb index 4a6b355eaeaa8..65222f0e1df4a 100644 --- a/activerecord/test/schema/trilogy_specific_schema.rb +++ b/activerecord/test/schema/trilogy_specific_schema.rb @@ -1,19 +1,17 @@ # frozen_string_literal: true ActiveRecord::Schema.define do - if connection.supports_datetime_with_precision? - create_table :datetime_defaults, force: true do |t| - t.datetime :modified_datetime, precision: nil, default: -> { "CURRENT_TIMESTAMP" } - t.datetime :precise_datetime, default: -> { "CURRENT_TIMESTAMP(6)" } - t.datetime :updated_datetime, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } - end + create_table :datetime_defaults, force: true do |t| + t.datetime :modified_datetime, precision: nil, default: -> { "CURRENT_TIMESTAMP" } + t.datetime :precise_datetime, default: -> { "CURRENT_TIMESTAMP(6)" } + t.datetime :updated_datetime, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } + end - create_table :timestamp_defaults, force: true do |t| - t.timestamp :nullable_timestamp - t.timestamp :modified_timestamp, precision: nil, default: -> { "CURRENT_TIMESTAMP" } - t.timestamp :precise_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6)" } - t.timestamp :updated_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } - end + create_table :timestamp_defaults, force: true do |t| + t.timestamp :nullable_timestamp + t.timestamp :modified_timestamp, precision: nil, default: -> { "CURRENT_TIMESTAMP" } + t.timestamp :precise_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6)" } + t.timestamp :updated_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)" } end create_table :defaults, force: true do |t| From c24450ac3f06bd405ec152eb6361ea01dabaa473 Mon Sep 17 00:00:00 2001 From: fatkodima Date: Sat, 7 Sep 2024 01:03:17 +0300 Subject: [PATCH 22/25] Fix rate limiting for `ActionController::API` controllers --- actionpack/lib/action_controller/api.rb | 1 + .../test/controller/api/rate_limiting_test.rb | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 actionpack/test/controller/api/rate_limiting_test.rb diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index 2c652df6a9aa2..b52430467b13f 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -123,6 +123,7 @@ def self.without_modules(*modules) BasicImplicitRender, StrongParameters, RateLimiting, + Caching, DataStreaming, DefaultHeaders, diff --git a/actionpack/test/controller/api/rate_limiting_test.rb b/actionpack/test/controller/api/rate_limiting_test.rb new file mode 100644 index 0000000000000..7710b285970b3 --- /dev/null +++ b/actionpack/test/controller/api/rate_limiting_test.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "abstract_unit" + +class ApiRateLimitedController < ActionController::API + self.cache_store = ActiveSupport::Cache::MemoryStore.new + rate_limit to: 2, within: 2.seconds, only: :limited_to_two + + def limited_to_two + head :ok + end +end + +class ApiRateLimitingTest < ActionController::TestCase + tests ApiRateLimitedController + + setup do + ApiRateLimitedController.cache_store.clear + end + + test "exceeding basic limit" do + get :limited_to_two + get :limited_to_two + assert_response :ok + + get :limited_to_two + assert_response :too_many_requests + end + + test "limit resets after time" do + get :limited_to_two + get :limited_to_two + assert_response :ok + + travel_to Time.now + 3.seconds do + get :limited_to_two + assert_response :ok + end + end +end From d5489754d58f0375435d173c474213ffd2b22f56 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 7 Sep 2024 17:44:21 +0900 Subject: [PATCH 23/25] Deprecate `unsigned_float` and `unsigned_decimal` short-hand column methods As of MySQL 8.0.17, the UNSIGNED attribute is deprecated for columns of type FLOAT, DOUBLE, and DECIMAL. Consider using a simple CHECK constraint instead for such columns. https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html Just in case existing `schema.rb` is still valid since the schema dumper dumps un unsigned attribute as `unsigned: true` option. https://github.com/rails/rails/blob/2ae883cc162446ae8ce602c2aa0d08a3ca15c917/activerecord/test/cases/adapters/abstract_mysql_adapter/unsigned_type_test.rb#L61-L67 --- activerecord/CHANGELOG.md | 9 +++++++++ .../connection_adapters/mysql/schema_definitions.rb | 10 ++-------- .../abstract_mysql_adapter/unsigned_type_test.rb | 13 +++++++++++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 852f926e3b8c8..f187835bb0ae3 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,12 @@ +* Deprecate `unsigned_float` and `unsigned_decimal` short-hand column methods. + + As of MySQL 8.0.17, the UNSIGNED attribute is deprecated for columns of type FLOAT, DOUBLE, + and DECIMAL. Consider using a simple CHECK constraint instead for such columns. + + https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html + + *Ryuta Kamizono* + * Drop MySQL 5.5 support. MySQL 5.5 is the only version that does not support datetime with precision, diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb index 33669cd7c2d41..8c50b4afd4670 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -42,18 +42,12 @@ module ColumnMethods # :method: unsigned_bigint # :call-seq: unsigned_bigint(*names, **options) - ## - # :method: unsigned_float - # :call-seq: unsigned_float(*names, **options) - - ## - # :method: unsigned_decimal - # :call-seq: unsigned_decimal(*names, **options) - included do define_column_methods :blob, :tinyblob, :mediumblob, :longblob, :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint, :unsigned_float, :unsigned_decimal + + deprecate :unsigned_float, :unsigned_decimal, deprecator: ActiveRecord.deprecator end end diff --git a/activerecord/test/cases/adapters/abstract_mysql_adapter/unsigned_type_test.rb b/activerecord/test/cases/adapters/abstract_mysql_adapter/unsigned_type_test.rb index e22f5a986709e..b602b398d726d 100644 --- a/activerecord/test/cases/adapters/abstract_mysql_adapter/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/abstract_mysql_adapter/unsigned_type_test.rb @@ -49,8 +49,6 @@ class UnsignedType < ActiveRecord::Base @connection.change_table("unsigned_types") do |t| t.unsigned_integer :unsigned_integer_t t.unsigned_bigint :unsigned_bigint_t - t.unsigned_float :unsigned_float_t - t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2 end @connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column| @@ -58,6 +56,17 @@ class UnsignedType < ActiveRecord::Base end end + test "deprecate unsigned_float and unsigned_decimal" do + @connection.change_table("unsigned_types") do |t| + assert_deprecated(ActiveRecord.deprecator) do + t.unsigned_float :unsigned_float_t + end + assert_deprecated(ActiveRecord.deprecator) do + t.unsigned_decimal :unsigned_decimal_t + end + end + end + test "schema dump includes unsigned option" do schema = dump_table_schema "unsigned_types" assert_match %r{t\.integer\s+"unsigned_integer",\s+unsigned: true$}, schema From e58a1a15219a6433c1c4e7d974059257e72d0a06 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 7 Sep 2024 19:48:59 +0900 Subject: [PATCH 24/25] Tweak specific db schema Follow up to #52781. `ActiveRecord::Schema` responds connection's methods. --- .../test/schema/mysql2_specific_schema.rb | 14 ++++++------- .../test/schema/postgresql_specific_schema.rb | 20 +++++++++---------- .../test/schema/trilogy_specific_schema.rb | 14 ++++++------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index ef8725ad89949..320911dbd9ad5 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -80,19 +80,17 @@ SELECT * FROM topics LIMIT num; END SQL -end -if ActiveRecord::Base.lease_connection.supports_insert_returning? - ActiveRecord::Base.lease_connection.create_table :pk_autopopulated_by_a_trigger_records, force: true, id: false do |t| - t.integer :id, null: false - end + if supports_insert_returning? + create_table :pk_autopopulated_by_a_trigger_records, force: true, id: false do |t| + t.integer :id, null: false + end - ActiveRecord::Base.lease_connection.execute( - <<-SQL + execute <<~SQL CREATE TRIGGER before_insert_trigger BEFORE INSERT ON pk_autopopulated_by_a_trigger_records FOR EACH ROW SET NEW.id = (SELECT COALESCE(MAX(id), 0) + 1 FROM pk_autopopulated_by_a_trigger_records); SQL - ) + end end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 6893e744a8492..62ba429aeb9da 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true ActiveRecord::Schema.define do - ActiveRecord::TestCase.enable_extension!("uuid-ossp", ActiveRecord::Base.lease_connection) - ActiveRecord::TestCase.enable_extension!("pgcrypto", ActiveRecord::Base.lease_connection) if ActiveRecord::Base.lease_connection.supports_pgcrypto_uuid? + ActiveRecord::TestCase.enable_extension!("uuid-ossp", connection) + ActiveRecord::TestCase.enable_extension!("pgcrypto", connection) if supports_pgcrypto_uuid? - uuid_default = connection.supports_pgcrypto_uuid? ? {} : { default: "uuid_generate_v4()" } + uuid_default = supports_pgcrypto_uuid? ? {} : { default: "uuid_generate_v4()" } create_table :chat_messages, id: :uuid, force: true, **uuid_default do |t| t.text :content @@ -196,15 +196,13 @@ end add_index(:companies, [:firm_id, :type], name: "company_include_index", include: [:name, :account_id]) -end -if ActiveRecord::Base.lease_connection.supports_insert_returning? - ActiveRecord::Base.lease_connection.create_table :pk_autopopulated_by_a_trigger_records, force: true, id: false do |t| - t.integer :id, null: false - end + if supports_insert_returning? + create_table :pk_autopopulated_by_a_trigger_records, force: true, id: false do |t| + t.integer :id, null: false + end - ActiveRecord::Base.lease_connection.execute( - <<-SQL + execute <<~SQL CREATE OR REPLACE FUNCTION populate_column() RETURNS TRIGGER AS $$ DECLARE @@ -221,5 +219,5 @@ FOR EACH ROW EXECUTE FUNCTION populate_column(); SQL - ) + end end diff --git a/activerecord/test/schema/trilogy_specific_schema.rb b/activerecord/test/schema/trilogy_specific_schema.rb index 65222f0e1df4a..a3bce50ae2217 100644 --- a/activerecord/test/schema/trilogy_specific_schema.rb +++ b/activerecord/test/schema/trilogy_specific_schema.rb @@ -79,19 +79,17 @@ SELECT * FROM topics LIMIT num; END SQL -end -if ActiveRecord::Base.lease_connection.supports_insert_returning? - ActiveRecord::Base.lease_connection.create_table :pk_autopopulated_by_a_trigger_records, force: true, id: false do |t| - t.integer :id, null: false - end + if supports_insert_returning? + create_table :pk_autopopulated_by_a_trigger_records, force: true, id: false do |t| + t.integer :id, null: false + end - ActiveRecord::Base.lease_connection.execute( - <<-SQL + execute <<~SQL CREATE TRIGGER before_insert_trigger BEFORE INSERT ON pk_autopopulated_by_a_trigger_records FOR EACH ROW SET NEW.id = (SELECT COALESCE(MAX(id), 0) + 1 FROM pk_autopopulated_by_a_trigger_records); SQL - ) + end end From 8b0c8079c6ee8460354f8b3cbe2296d3f3f3b21e Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 7 Sep 2024 20:10:39 +0900 Subject: [PATCH 25/25] Sync mysql2 and trilogy specific schema Follow up to #50733. ``` % diff -ub test/schema/mysql2_specific_schema.rb test/schema/trilogy_specific_schema.rb --- test/schema/mysql2_specific_schema.rb 2024-09-07 19:53:07 +++ test/schema/trilogy_specific_schema.rb 2024-09-07 19:50:17 @@ -21,7 +21,6 @@ t.string :char2, limit: 50, default: "a varchar field" if ActiveRecord::TestCase.supports_default_expression? t.binary :uuid, limit: 36, default: -> { "(uuid())" } - t.string :char2_concatenated, default: -> { "(concat(`char2`, '-'))" } end end ``` --- activerecord/test/cases/defaults_test.rb | 8 +++----- activerecord/test/schema/trilogy_specific_schema.rb | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 5b5bd7c6826a1..44d83b73c8470 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -176,11 +176,9 @@ class MysqlDefaultExpressionTest < ActiveRecord::TestCase assert_match %r/t\.binary\s+"uuid",\s+limit: 36,\s+default: -> { "\(?uuid\(\)\)?" }/i, output end - if current_adapter?(:Mysql2Adapter) - test "schema dump includes default expression with single quotes reflected correctly" do - output = dump_table_schema("defaults") - assert_match %r/t\.string\s+"char2_concatenated",\s+default: -> { "\(?concat\(`char2`,(_utf8mb4)?'-'\)\)?" }/i, output - end + test "schema dump includes default expression with single quotes reflected correctly" do + output = dump_table_schema("defaults") + assert_match %r/t\.string\s+"char2_concatenated",\s+default: -> { "\(?concat\(`char2`,(_utf8mb4)?'-'\)\)?" }/i, output end end diff --git a/activerecord/test/schema/trilogy_specific_schema.rb b/activerecord/test/schema/trilogy_specific_schema.rb index a3bce50ae2217..320911dbd9ad5 100644 --- a/activerecord/test/schema/trilogy_specific_schema.rb +++ b/activerecord/test/schema/trilogy_specific_schema.rb @@ -21,6 +21,7 @@ t.string :char2, limit: 50, default: "a varchar field" if ActiveRecord::TestCase.supports_default_expression? t.binary :uuid, limit: 36, default: -> { "(uuid())" } + t.string :char2_concatenated, default: -> { "(concat(`char2`, '-'))" } end end