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