From dd994404f4766dda2d85ec1cdfda78eb8ed9a102 Mon Sep 17 00:00:00 2001 From: Rosa Gutierrez Date: Fri, 24 Jul 2026 11:56:14 +0200 Subject: [PATCH] Add an update generator to copy new migrations Future schema changes will ship as regular migrations, optional at first and required in the next major version. `rails solid_queue:update` (or the solid_queue:update generator directly) copies any new migration files from the gem to the application, honoring the database the app uses for Solid Queue via the --database option. Together with this, add a deprecator for Solid Queue, registered with the application so it follows the app's deprecation behavior settings, and a warning helper that features guarded behind pending migrations can use to instruct users to update. Extracted from earlier work on linking claimed executions to processes by name, which ended up not being needed. Co-Authored-By: Claude Opus 4.8 --- .rubocop.yml | 1 + app/models/solid_queue/record.rb | 10 ++++ .../solid_queue/update/update_generator.rb | 19 ++++++ lib/solid_queue.rb | 4 ++ lib/solid_queue/engine.rb | 4 ++ lib/solid_queue/tasks.rb | 5 ++ lib/solid_queue/version.rb | 4 ++ test/unit/update_generator_test.rb | 60 +++++++++++++++++++ 8 files changed, 107 insertions(+) create mode 100644 lib/generators/solid_queue/update/update_generator.rb create mode 100644 test/unit/update_generator_test.rb diff --git a/.rubocop.yml b/.rubocop.yml index 75df11738..7299253bd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,3 +7,4 @@ AllCops: TargetRubyVersion: 3.3 Exclude: - "**/*_schema.rb" + - "lib/generators/solid_queue/update/templates/db/*" diff --git a/app/models/solid_queue/record.rb b/app/models/solid_queue/record.rb index 8617dc85b..72da869d3 100644 --- a/app/models/solid_queue/record.rb +++ b/app/models/solid_queue/record.rb @@ -24,6 +24,16 @@ def supports_insert_conflict_target? end end + def warn_about_pending_migrations + SolidQueue.deprecator.warn(<<~DEPRECATION) + Solid Queue has pending database migrations. To get the new migration files, run: + rails solid_queue:update + And then: + rails db:migrate + These migrations will be required after version #{SolidQueue.next_major_version}.0 + DEPRECATION + end + # Pass index hints to the query optimizer using SQL comment hints. # Uses MySQL 8 optimizer hint query comments, which SQLite and # PostgreSQL ignore. diff --git a/lib/generators/solid_queue/update/update_generator.rb b/lib/generators/solid_queue/update/update_generator.rb new file mode 100644 index 000000000..393991dfa --- /dev/null +++ b/lib/generators/solid_queue/update/update_generator.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "rails/generators/active_record" + +class SolidQueue::UpdateGenerator < Rails::Generators::Base + include ActiveRecord::Generators::Migration + + source_root File.expand_path("templates", __dir__) + + class_option :database, type: :string, aliases: %i[ --db ], default: "queue", + desc: "The database that Solid Queue uses. Defaults to `queue`" + + def copy_new_migrations + Dir.glob(File.join(self.class.source_root, "db", "*.rb")).each do |migration_file| + name = File.basename(migration_file) + migration_template File.join("db", name), File.join(db_migrate_path, name), skip: true + end + end +end diff --git a/lib/solid_queue.rb b/lib/solid_queue.rb index edc90de95..eb77df689 100644 --- a/lib/solid_queue.rb +++ b/lib/solid_queue.rb @@ -86,6 +86,10 @@ def preserve_finished_jobs? preserve_finished_jobs end + def deprecator + @deprecator ||= ActiveSupport::Deprecation.new(next_major_version, "SolidQueue") + end + def instrument(channel, **options, &block) ActiveSupport::Notifications.instrument("#{channel}.solid_queue", **options, &block) end diff --git a/lib/solid_queue/engine.rb b/lib/solid_queue/engine.rb index 71a9cc1af..312107a4c 100644 --- a/lib/solid_queue/engine.rb +++ b/lib/solid_queue/engine.rb @@ -43,5 +43,9 @@ class Engine < ::Rails::Engine include ActiveJob::ConcurrencyControls end end + + initializer "solid_queue.deprecator" do |app| + app.deprecators[:solid_queue] = SolidQueue.deprecator + end end end diff --git a/lib/solid_queue/tasks.rb b/lib/solid_queue/tasks.rb index 704225620..c3989a3b9 100644 --- a/lib/solid_queue/tasks.rb +++ b/lib/solid_queue/tasks.rb @@ -4,6 +4,11 @@ Rails::Command.invoke :generate, [ "solid_queue:install" ] end + desc "Copy any new Solid Queue migrations to the application" + task :update do + Rails::Command.invoke :generate, [ "solid_queue:update" ] + end + desc "start solid_queue supervisor to dispatch and process jobs" task start: :environment do SolidQueue::Supervisor.start diff --git a/lib/solid_queue/version.rb b/lib/solid_queue/version.rb index da6b7c906..967d4cc48 100644 --- a/lib/solid_queue/version.rb +++ b/lib/solid_queue/version.rb @@ -1,3 +1,7 @@ module SolidQueue VERSION = "1.5.0" + + def self.next_major_version + Gem::Version.new(VERSION).segments.first + 1 + end end diff --git a/test/unit/update_generator_test.rb b/test/unit/update_generator_test.rb new file mode 100644 index 000000000..6a59ff5a1 --- /dev/null +++ b/test/unit/update_generator_test.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "test_helper" +require "rails/generators/test_case" +require "generators/solid_queue/update/update_generator" + +class UpdateGeneratorTest < Rails::Generators::TestCase + tests SolidQueue::UpdateGenerator + destination Rails.root.join("tmp/update_generator_test") + setup :prepare_destination + + test "copies new migrations to the queue database migrations path" do + with_migration_template("add_batches_to_solid_queue") do + run_generator + + assert_migration "db/queue_migrate/add_batches_to_solid_queue.rb" do |migration| + assert_match(/class AddBatchesToSolidQueue/, migration) + end + end + end + + test "copies new migrations to another database's migrations path" do + with_migration_template("add_batches_to_solid_queue") do + run_generator %w[ --database primary ] + + assert_migration "db/migrate/add_batches_to_solid_queue.rb" + end + end + + test "skips migrations that have already been copied" do + with_migration_template("add_batches_to_solid_queue") do + run_generator + run_generator + + assert_equal 1, Dir.glob(File.join(destination_root, "db/queue_migrate/*_add_batches_to_solid_queue.rb")).count + end + end + + test "does nothing when there are no new migrations" do + run_generator + + assert_empty Dir.glob(File.join(destination_root, "db/**/*.rb")) + end + + private + def with_migration_template(name) + Dir.mktmpdir do |source_root| + FileUtils.mkdir_p File.join(source_root, "db") + File.write File.join(source_root, "db", "#{name}.rb"), <<~MIGRATION + class #{name.camelize} < ActiveRecord::Migration[7.1] + def change + end + end + MIGRATION + + SolidQueue::UpdateGenerator.stubs(:source_root).returns(source_root) + yield + end + end +end