Skip to content

ci: drop EOL Ruby and Rails from the matrix - #28

Merged
Fivell merged 2 commits into
masterfrom
chore/drop-eol-ruby-rails
Oct 1, 2026
Merged

Fivell merged 2 commits into
masterfrom
chore/drop-eol-ruby-rails

Conversation

@Fivell

@Fivell Fivell commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Drops end-of-life Ruby and Rails from the CI matrix and raises the gemspec Ruby floor to match.

Matrix: before / after

before after
ruby ['3.2', '3.3', '3.4'] ['3.3', '3.4', '4.0']
rails ['7.1.0', '7.2.0', '8.0.0'] ['8.0.0', '8.1.0']
activeadmin ['3.2.0', '3.3.0', '3.4.0', '3.5.0'] ['3.2.0', '3.3.0', '3.4.0', '3.5.0'] (unchanged)
exclude rails: 8.0.0 × activeadmin: 3.2.0 none

36 legs minus 3 excluded = 33 before; 3 × 2 × 4 = 24 after, no excludes.

Why each removal

Source of truth: endoflife.date/ruby, endoflife.date/rails.

removed EOL date
Ruby 3.2 2026-03-31
Rails 7.1 2025-10-01
Rails 7.2 2026-08-09

Kept: Ruby 3.3 (EOL 2027-03-31, security-only — the new floor), Ruby 3.4 (2028-03-31), Ruby 4.0 (2029-03-31), Rails 8.0 (EOL 2026-11-07, oldest still supported), Rails 8.1 (2027-10-10). Rails 8.2 is not released (main is 8.2.0.alpha, no tag or pre-release), so it is not added.

ActiveAdmin versions: none dropped

Dropping Rails ≤ 7.2 looked like it would strand the old ActiveAdmin entries, and the existing exclude suggested AA 3.2.0 could not run Rails 8. That turned out to be false, so all four AA versions stay.

The Gemfile pins gem 'activeadmin', "~> #{ENV['AA'] || default}", so the 3.2.0 leg means ~> 3.2.0 → >= 3.2.0, < 3.3.0, which resolves to activeadmin 3.2.5 — a release that postdates the exclude and does support Rails 8. The rule was only hiding a cell that works.

bundle install evidence, Ruby 3.4.10, every AA × Rails 8.x pair — all resolved, nothing refused:

### rails=8.0.0 aa=3.2.0 → bundle install OK: activeadmin(3.2.5) actionview(8.0.5.1)
### rails=8.0.0 aa=3.3.0 → bundle install OK: activeadmin(3.3.0) actionview(8.0.5.1)
### rails=8.0.0 aa=3.4.0 → bundle install OK: activeadmin(3.4.0) actionview(8.0.5.1)
### rails=8.0.0 aa=3.5.0 → bundle install OK: activeadmin(3.5.2) actionview(8.0.5.1)
### rails=8.1.0 aa=3.2.0 → bundle install OK: activeadmin(3.2.5) actionview(8.1.4)
### rails=8.1.0 aa=3.3.0 → bundle install OK: activeadmin(3.3.0) actionview(8.1.4)
### rails=8.1.0 aa=3.4.0 → bundle install OK: activeadmin(3.4.0) actionview(8.1.4)
### rails=8.1.0 aa=3.5.0 → bundle install OK: activeadmin(3.5.2) actionview(8.1.4)

And the full suite on each of those eight pairs:

rb3.4.10_rails8.0.0_aa3.2.0  activeadmin(3.2.5) actionview(8.0.5.1)  13 examples, 0 failures
rb3.4.10_rails8.0.0_aa3.3.0  activeadmin(3.3.0) actionview(8.0.5.1)  13 examples, 0 failures
rb3.4.10_rails8.0.0_aa3.4.0  activeadmin(3.4.0) actionview(8.0.5.1)  13 examples, 0 failures
rb3.4.10_rails8.0.0_aa3.5.0  activeadmin(3.5.2) actionview(8.0.5.1)  13 examples, 0 failures
rb3.4.10_rails8.1.0_aa3.2.0  activeadmin(3.2.5) actionview(8.1.4)    13 examples, 0 failures
rb3.4.10_rails8.1.0_aa3.3.0  activeadmin(3.3.0) actionview(8.1.4)    13 examples, 0 failures
rb3.4.10_rails8.1.0_aa3.4.0  activeadmin(3.4.0) actionview(8.1.4)    13 examples, 0 failures
rb3.4.10_rails8.1.0_aa3.5.0  activeadmin(3.5.2) actionview(8.1.4)    13 examples, 0 failures

No Ruby 3.3 × Rails 8.1 exclude

This PR was originally briefed to exclude Ruby 3.3 × Rails 8.1, because actionview 8.1.4 contains

# actionview-8.1.4/lib/action_view/helpers/capture_helper.rb:50
def capture(*, **, &block)
  buffer = @output_buffer.capture { value = yield(*, **) }

which forwards anonymous rest args inside a block. The exclude is not needed and is not here. That parser restriction exists only in Ruby 3.3.0, the very first 3.3 release, and was lifted in later 3.3 patches. Measured locally:

ruby 3.3.0  (actionview 8.1 capture_helper form): anonymous rest parameter is also used within block (SyntaxError)
ruby 3.3.10 (actionview 8.1 capture_helper form): Syntax OK
ruby 3.3.12 (actionview 8.1 capture_helper form): Syntax OK
ruby 3.4.10 (actionview 8.1 capture_helper form): Syntax OK
ruby 4.0.6  (actionview 8.1 capture_helper form): Syntax OK

ruby/setup-ruby with ruby-version: '3.3' installs the newest 3.3 patch (3.3.12 today), so CI never sees it. The same applies to simplecov 1.3.2 (def synchronize(&) forwarded inside a block), which also only fails to parse on 3.3.0. Nothing was pinned or conditionalised to work around it. The Ruby 3.3 × Rails 8.1 cell is verified green below.

This gem does pull actionview, so the exclude would have applied had it been real:

$ bundle list | grep actionview
  * actionview (8.1.4)
  * kaminari-actionview (1.2.2)

Coverage badge / Pages gating

The badge and gh-pages deploy were gated on ruby == '3.4' && rails == '8.0.0' && activeadmin == '3.5.0'. Rails 8.0.0 is no longer the newest Rails in the matrix, so both if: conditions now point at the newest cell that exists:

if: matrix.ruby == '4.0' && matrix.rails == '8.1.0' && matrix.activeadmin == '3.5.0'

With no excludes in play that matches exactly one of the 24 legs, so the run still produces exactly one coverage-badge artifact — not zero, not several. Ruby 4.0 / Rails 8.1 / AA 3.5 is verified green locally (see below).

One real fix was needed: --skip-javascript

Adding Rails 8.1 to the matrix exposed a genuine break in the test harness, not just a matrix edit. spec/support/rails_template.rb ends with run "rm -f Gemfile Gemfile.lock" — the generated dummy app's own Gemfile is discarded and the app boots under this gem's bundle, which has sprockets-rails/sass-rails and no importmap-rails. Rails 8.1's rails new emits an importmap-dependent line into ApplicationController, so every 8.1 leg died at boot:

spec/rails/rails-8.1.4/app/controllers/application_controller.rb:6:in '<class:ApplicationController>':
undefined local variable or method 'stale_when_importmap_changes' for class ApplicationController (NameError)

Fixed at the source in tasks/test.rake by passing --skip-javascript to rails new, so the generator never adds the importmap dependency in the first place (rather than patching the generated file afterwards). The suite is sprockets-driven, so nothing is lost. Verified still green on Rails 8.0 as well.

gemspec

  • required_ruby_version: >= 3.1.0 → >= 3.3. Ruby 3.1 (EOL 2025-03-26) and 3.2 are both EOL and untested.
  • add_dependency "activeadmin", ">= 3.0", "< 4.0" — left alone. No AA version was dropped, and narrowing a runtime floor is a breaking change for consumers.
  • Gem version not bumped, nothing released.

There is no .rubocop.yml, no .ruby-version and no CHANGELOG in this repo, and the README has no supported-versions table or version-pinned badge, so nothing else needed updating. (With no .rubocop.yml there is no Gemspec/RequiredRubyVersion cop to keep in sync.)

Local verification

Ruby versions used: rbenv 3.3.0, 3.3.10, 3.3.12, 3.4.10, 4.0.6. Chrome present; suite is Capybara + cuprite.

Required run — Ruby 3.4.10 / Rails 8.1.0 / oldest AA kept (3.2.0):

$ CI=true RBENV_VERSION=3.4.10 RAILS=8.1.0 AA=3.2.0 rbenv exec bundle install
Bundle complete! 12 Gemfile dependencies, 109 gems now installed.

$ CI=true RBENV_VERSION=3.4.10 RAILS=8.1.0 AA=3.2.0 rbenv exec bundle exec rake setup
(ok)

$ CI=true RBENV_VERSION=3.4.10 RAILS=8.1.0 AA=3.2.0 rbenv exec bundle exec rspec spec
Finished in 5.31 seconds (files took 1.35 seconds to load)
13 examples, 0 failures
Line coverage: 73 / 74 (98.64%)

The two new matrix corners, plus the cell the retracted exclude would have deleted:

rb3.3.12_rails8.0.0_aa3.2.0  activeadmin(3.2.5) actionview(8.0.5.1)  13 examples, 0 failures   <- new floor
rb3.3.12_rails8.0.0_aa3.5.0  activeadmin(3.5.2) actionview(8.0.5.1)  13 examples, 0 failures   <- new floor
rb3.3.12_rails8.1.0_aa3.5.0  activeadmin(3.5.2) actionview(8.1.4)    13 examples, 0 failures   <- would have been excluded
rb4.0.6_rails8.0.0_aa3.5.0   activeadmin(3.5.2) actionview(8.0.5.1)  13 examples, 0 failures
rb4.0.6_rails8.1.0_aa3.2.0   activeadmin(3.2.5) actionview(8.1.4)    13 examples, 0 failures
rb4.0.6_rails8.1.0_aa3.5.0   activeadmin(3.5.2) actionview(8.1.4)    13 examples, 0 failures   <- the badge leg

Every leg run locally — 17 combinations across 5 interpreters — is green. No matrix leg was dropped to make anything pass.

One local-only gotcha worth recording

A bare local rspec run fails 11 of 13 examples with undefined method 'values' for nil inside ActiveAdmin::BatchActions::ResourceExtension#batch_actions. This is not a bug in the gem and not a Rails 8.1 issue — it reproduces identically on unmodified master at the combination CI runs green. Rails generates config.eager_load = ENV["CI"].present? into the dummy app's config/environments/test.rb, so without CI set, ActiveAdmin's before_load hooks have not run when the specs call ActiveAdmin.register, and @batch_actions is still nil. Setting CI=true (as the GitHub runner does) gives 13/13. All runs above were made with CI=true.

Fivell added 2 commits October 1, 2026 13:30
Ruby 3.2 reached EOL 2026-03-31; Rails 7.1 on 2025-10-01 and Rails 7.2 on
2026-08-09 (endoflife.date). The matrix is now a plain product of Ruby
3.3/3.4/4.0 against Rails 8.0.0/8.1.0, with no version excludes.

All four ActiveAdmin entries are kept, and the stale `rails: 8.0.0 /
activeadmin: 3.2.0` exclude is deleted. Its premise no longer holds: the
Gemfile pins `~> #{ENV['AA']}`, so the '3.2.0' leg resolves to activeadmin
3.2.5, which does support Rails 8. Every AA x Rails 8.x pair was checked
with a real bundle install plus the full suite and all eight are green, so
the rule was only hiding a working cell.

The dummy-app generator needed --skip-javascript. rails_template.rb
discards the generated app's Gemfile and boots the app under this gem's
sprockets bundle, so importmap-rails is never present, while Rails 8.1's
`rails new` emits `stale_when_importmap_changes` in ApplicationController.
Without the flag every Rails 8.1 leg fails to boot with:

  app/controllers/application_controller.rb:6:in '<class:ApplicationController>':
  undefined local variable or method 'stale_when_importmap_changes' (NameError)

The coverage badge and Pages deploy now gate on the newest cell in the new
matrix, Ruby 4.0 / Rails 8.1.0 / AA 3.5.0, which matches exactly one of the
24 legs.
Ruby 3.1 and 3.2 are both EOL and no longer in CI, so `>= 3.1.0` advertised
support for versions nothing tests.

The activeadmin dependency stays at `>= 3.0, < 4.0`: no ActiveAdmin version
was dropped from the matrix, and narrowing a runtime floor would be a
breaking change for consumers regardless.
@Fivell
Fivell merged commit 035b5e0 into master Oct 1, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant