From c67797d49fc48b8d6c0ca34f6d01b8696ab7da43 Mon Sep 17 00:00:00 2001 From: Lucas Carlson Date: Tue, 18 Aug 2026 07:23:27 -0700 Subject: [PATCH] Support Rails 7.1 and 7.2 The gemspec required Rails 8.0 from the first release, so applications on the two prior Rails lines could not add the gem at all. Measuring the suite against those lines shows the floor was higher than the code needs. On Rails 7.1 and 7.2 the suite fails one test, and that test asserts a Rails 8 error class rather than a behaviour: Rails 8 maps an exhausted SQLite busy handler to StatementTimeout, and 7.1 and 7.2 leave it as the StatementInvalid that timeout subclasses. The message the caller relies on is identical, so the assertion moves to the parent class and keeps the message match. The bundled migrations declared ActiveRecord::Migration[8.0], which raises Unknown migration version on 7.x. They now declare [7.1]. Under Rails 8 the compatibility layer between the two only changes remove_foreign_key, and no Solid Objects migration calls it, so an install built at [7.1] gets the same schema as one built at [8.0]. Rails 7.0 stays out of range. Its SQLite adapter requires sqlite3 ~> 1.4, and the busy-handler control this gem depends on arrived in sqlite3 2.x, so the two cannot be loaded together. The compatibility matrix now runs Ruby 3.3, 3.4, and 4.0 against Rails 7.1, 7.2, 8.0, and 8.1. That job runs SQLite only, so adapter behaviour on the older lines is unmeasured against PostgreSQL and MySQL, and the roadmap says so. --- .github/workflows/ci.yml | 2 +- AGENTS.md | 2 +- CHANGELOG.md | 7 +++++++ Gemfile.lock | 12 ++++++------ README.md | 3 ++- .../20260805000000_create_solid_objects_tables.rb | 2 +- ..._add_state_revision_to_solid_objects_instances.rb | 2 +- ...20260813000000_rename_message_dispatch_columns.rb | 2 +- docs/development.md | 2 +- docs/roadmap.md | 8 ++++++-- solid_objects.gemspec | 12 ++++++------ test/integration/enqueue_lock_retry_test.rb | 5 ++++- 12 files changed, 37 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fffa6e1..a832474 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: fail-fast: false matrix: ruby-version: [ "3.3", "3.4", "4.0" ] - rails-version: [ "8.0", "8.1" ] + rails-version: [ "7.1", "7.2", "8.0", "8.1" ] env: RAILS_VERSION: ${{ matrix.rails-version }} steps: diff --git a/AGENTS.md b/AGENTS.md index 28c0a3a..357ddb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ Use dedicated, empty databases for adapter tests and benchmarks. ## Coding Style & Naming Conventions -Target Ruby 3.3+ and Rails 8+. Use two-space indentation and let Standard Ruby plus the Solid Queue-derived RuboCop policy decide formatting. Prefer descriptive `snake_case` methods and variables, `CamelCase` constants, early returns, and keyword shorthand such as `Message.new(actor_id:)`. Avoid boolean parameters and abbreviations. +Target Ruby 3.3+ and Rails 7.1+. Use two-space indentation and let Standard Ruby plus the Solid Queue-derived RuboCop policy decide formatting. Prefer descriptive `snake_case` methods and variables, `CamelCase` constants, early returns, and keyword shorthand such as `Message.new(actor_id:)`. Avoid boolean parameters and abbreviations. Every owned Ruby file must enable inline RBS with `# rbs_inline: enabled`; annotate methods and instance variables using `# @rbs`. Keep database behavior portable across SQLite, PostgreSQL, and MySQL. Model queue state through table membership rather than partial indexes. diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d6ae9..3d1d0cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ `config/initializers` no longer loses them. - Apply the configured `connects_to` in the record class body, so the connection follows the class through a development reload. +- Lower the supported Rails floor from 8.0 to 7.1. The gem dependencies, the + bundled migrations, and the compatibility CI matrix now cover Rails 7.1, 7.2, + 8.0, and 8.1. The migrations declare `ActiveRecord::Migration[7.1]`, which + builds the same schema as `[8.0]` because the compatibility layer between the + two only changes `remove_foreign_key`, which no Solid Objects migration calls. + Rails 7.0 stays out of range: its SQLite adapter requires `sqlite3 ~> 1.4`, + and the busy-handler control this gem needs arrived in `sqlite3` 2.x. ## 0.13.2 - 2026-08-17 diff --git a/Gemfile.lock b/Gemfile.lock index e7b922f..937b83d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,13 +2,13 @@ PATH remote: . specs: solid_objects (0.13.3) - actioncable (>= 8.0) - actionpack (>= 8.0) - actionview (>= 8.0) - activerecord (>= 8.0) - activesupport (>= 8.0) + actioncable (>= 7.1) + actionpack (>= 7.1) + actionview (>= 7.1) + activerecord (>= 7.1) + activesupport (>= 7.1) rack (>= 3.1) - railties (>= 8.0) + railties (>= 7.1) thor (>= 1.3) GEM diff --git a/README.md b/README.md index 296e6ff..5acaa9d 100644 --- a/README.md +++ b/README.md @@ -341,7 +341,8 @@ mount SolidObjects::Engine => "/solid_objects" ## Installation -Solid Objects requires Ruby 3.3 or newer and Rails 8.0 or newer. +Solid Objects requires Ruby 3.3 or newer and Rails 7.1 or newer. CI runs the +suite against Rails 7.1, 7.2, 8.0, and 8.1. Add the gem, install its initializer and migration, then migrate: diff --git a/db/migrate/20260805000000_create_solid_objects_tables.rb b/db/migrate/20260805000000_create_solid_objects_tables.rb index ca0cb45..d5b62af 100644 --- a/db/migrate/20260805000000_create_solid_objects_tables.rb +++ b/db/migrate/20260805000000_create_solid_objects_tables.rb @@ -1,6 +1,6 @@ # rbs_inline: enabled -class CreateSolidObjectsTables < ActiveRecord::Migration[8.0] +class CreateSolidObjectsTables < ActiveRecord::Migration[7.1] # @rbs () -> void def change create_processes diff --git a/db/migrate/20260806000000_add_state_revision_to_solid_objects_instances.rb b/db/migrate/20260806000000_add_state_revision_to_solid_objects_instances.rb index ee279c3..8f0c762 100644 --- a/db/migrate/20260806000000_add_state_revision_to_solid_objects_instances.rb +++ b/db/migrate/20260806000000_add_state_revision_to_solid_objects_instances.rb @@ -1,6 +1,6 @@ # rbs_inline: enabled -class AddStateRevisionToSolidObjectsInstances < ActiveRecord::Migration[8.0] +class AddStateRevisionToSolidObjectsInstances < ActiveRecord::Migration[7.1] # @rbs () -> void def change add_column SolidObjects.table_name(:instances), diff --git a/db/migrate/20260813000000_rename_message_dispatch_columns.rb b/db/migrate/20260813000000_rename_message_dispatch_columns.rb index 339be1f..c6dc48f 100644 --- a/db/migrate/20260813000000_rename_message_dispatch_columns.rb +++ b/db/migrate/20260813000000_rename_message_dispatch_columns.rb @@ -1,6 +1,6 @@ # rbs_inline: enabled -class RenameMessageDispatchColumns < ActiveRecord::Migration[8.0] +class RenameMessageDispatchColumns < ActiveRecord::Migration[7.1] # @rbs () -> void def up remove_check_constraint messages_table, name: "chk_so_messages_kind" diff --git a/docs/development.md b/docs/development.md index 41568cd..df148cf 100644 --- a/docs/development.md +++ b/docs/development.md @@ -3,7 +3,7 @@ ## Requirements - Ruby 3.3 or newer -- Rails 8.0 or newer +- Rails 7.1 or newer - SQLite 3.35+, PostgreSQL 14+, and MySQL 8.0/InnoDB for the full matrix Install dependencies: diff --git a/docs/roadmap.md b/docs/roadmap.md index d2f2b45..185f341 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -66,8 +66,12 @@ - Inline RBS generation/validation, Steep, Standard Ruby, Solid Queue's exact RuboCop policy, and a warning-free Brakeman scan - Compatibility CI across the supported span: Ruby 3.3, 3.4, and 4.0 against - Rails 8.0 and 8.1, pinned through `RAILS_VERSION` so the advertised range is - verified rather than assumed + Rails 7.1, 7.2, 8.0, and 8.1, pinned through `RAILS_VERSION` so the advertised + range is verified rather than assumed. The compatibility job runs SQLite only; + the PostgreSQL and MySQL jobs run on the newest Rails, so adapter behavior on + Rails 7.1 and 7.2 is unmeasured against those servers. Rails 7.0 is out of + range because its SQLite adapter requires `sqlite3 ~> 1.4`, and this gem needs + the busy-handler control that arrived in `sqlite3` 2.x - A JavaScript suite covering every browser module, run in CI with Node's test runner and jsdom, plus a browser suite running the same modules against real Chromium and a real Turbo build, with every GitHub Actions reference pinned to diff --git a/solid_objects.gemspec b/solid_objects.gemspec index 263b1bf..e1e993b 100644 --- a/solid_objects.gemspec +++ b/solid_objects.gemspec @@ -35,16 +35,16 @@ Gem::Specification.new do |spec| spec.executables = [ "solid_objects" ] spec.require_paths = [ "lib" ] - spec.add_dependency "actioncable", ">= 8.0" - spec.add_dependency "actionpack", ">= 8.0" - spec.add_dependency "actionview", ">= 8.0" - spec.add_dependency "activerecord", ">= 8.0" - spec.add_dependency "activesupport", ">= 8.0" + spec.add_dependency "actioncable", ">= 7.1" + spec.add_dependency "actionpack", ">= 7.1" + spec.add_dependency "actionview", ">= 7.1" + spec.add_dependency "activerecord", ">= 7.1" + spec.add_dependency "activesupport", ">= 7.1" # The operator dashboard is a Rack application. Rack arrives with Action Pack # in every supported Rails version; the floor is stated because the dashboard # writes lowercase response headers, which Rack 3 requires. spec.add_dependency "rack", ">= 3.1" - spec.add_dependency "railties", ">= 8.0" + spec.add_dependency "railties", ">= 7.1" spec.add_dependency "thor", ">= 1.3" spec.add_development_dependency "benchmark" diff --git a/test/integration/enqueue_lock_retry_test.rb b/test/integration/enqueue_lock_retry_test.rb index 5aadb8d..5cc23ca 100644 --- a/test/integration/enqueue_lock_retry_test.rb +++ b/test/integration/enqueue_lock_retry_test.rb @@ -74,7 +74,10 @@ def add(product_id:) reference = CartActor.ref("alice") lock = hold_write_lock - error = assert_raises(ActiveRecord::StatementTimeout) do + # Rails 8 maps an exhausted SQLite busy handler to StatementTimeout, and + # Rails 7.1 and 7.2 leave it as the StatementInvalid that timeout subclasses. + # The message is what the caller relies on, so it carries the assertion. + error = assert_raises(ActiveRecord::StatementInvalid) do Timeout.timeout(20) do SolidObjects::Record.connection_pool.with_connection do |connection| suspend_sqlite_busy_wait(connection) do