Rails 8.1 サポートとマトリクステストの導入
この PR では activerecord‑tenanted が Rails 8.1 以降を正式にサポートし、Appraisal を用いたマトリクステストと CI の拡張を行うと同時に、Rails 8.1 で不要となるパッチを条件付きで適用するよう変更しています。
背景
Rails 8.1 の正式リリースに合わせてライブラリの最低対象バージョンを更新する必要がありました。従来は >= 8.1.beta という緩やかな制約であったため、ベータ版の挙動に依存したコードが残り、将来的な互換性リスクが残っていました。また、テストは単一の Rails バージョンで実行されていたため、他バージョンでの破壊的変更を検出しにくい状態でした。
マトリクステストを導入することで、複数バージョンでの動作保証を自動化し、CI での回帰検知を強化します。Appraisal を利用すれば Gemfile の組み合わせ管理が容易になり、バージョン追加時の手間が削減されます。
この背景から、テスト基盤と依存バージョンの見直しが本 PR の主目的となります。
技術的な変更
GitHub Actions のワークフローにマトリクスジョブを追加し、各 Rails バージョン用 Gemfile を動的に列挙してテストを実行できるようになりました。
jobs:
rails_versions:
runs-on: ubuntu-latest
outputs:
gemfiles: ${{ steps.gemfiles.outputs.gemfiles }}
steps:
- uses: actions/checkout@v5
- id: gemfiles
run: echo "gemfiles=$(ruby -rjson -e 'puts Dir[\"gemfiles/*.gemfile\"].map { |f| File.basename(f, \".gemfile\") }.sort.to_json')" >> "$GITHUB_OUTPUT"
unit:
name: "unit (${{ matrix.gemfile }})"
needs: rails_versions
strategy:
fail-fast: false
matrix:
gemfile: ${{ fromJSON(needs.rails_versions.outputs.gemfiles) }}
continue-on-error: ${{ matrix.gemfile == 'rails_edge' }}
env:
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
Appraisal の設定を追加し、rails-8-1 と rails-edge の二つのプロファイルを定義しました。これによりローカルでも bundle exec appraisal rails-8-1 bin/test-unit のように単体または全体のテストを簡単に走らせられます。
# Appraisals
appraise "rails-8-1" do
gem "rails", "~> 8.1.0"
end
appraise "rails-edge" do
gem "rails", github: "rails/rails", branch: "main"
end
Gemfile と gemspec の依存バージョンを更新し、rails のエッジバージョンをオプションで取得できるようにすると同時に、activerecord と railties の最低要件を >= 8.1.0 に変更しました。Appraisal 用の appraisal gem と minitest のバージョン制約も明示しています。
# Gemfile (抜粋)
gem "rails", github: "rails/rails", branch: "main"
gem "appraisal", "2.5.0", require: false
gem "minitest", "< 6"
# activerecord-tenanted.gemspec (抜粋)
rails_requirement = ">= 8.1.0"
spec.add_dependency "activerecord", rails_requirement
spec.add_dependency "railties", rails_requirement
ActiveRecord::Tenanted::Patches::Attributes に条件付きパッチ適用ロジックを組み込み、Rails 8.1 以降では不要な monkey‑patch を自動的にスキップします。needs_patch? がバージョン要件を評価し、apply_patch が必要なら prepend を実行します。
module Attributes
class << self
def needs_patch?
Gem::Requirement.new("~> 8.1.0").satisfied_by?(Gem::Version.new(Rails::VERSION::STRING))
end
def apply_patch
ActiveRecord::Base.prepend(self) if needs_patch?
end
end
end
Railtie の初期化コードを変更し、apply_patch を呼び出すことで条件付きにしました。これによりRails 8.1 以降で余計なモジュールが読み込まれなくなります。
initializer "active_record_tenanted.monkey_patches" do
ActiveSupport.on_load(:active_record) do
ActiveRecord::Tenanted::Patches::Attributes.apply_patch
ActiveRecord::Tasks::DatabaseTasks.prepend ActiveRecord::Tenanted::Patches::DatabaseTasks
end
end
テストスクリプトとヘルパーに環境対応を追加し、bin/test-integration が ENV["BUNDLE_GEMFILE"] を参照して正しい Gemfile を使用できるようにしました。また、テストヘルパーは実行時に Rails バージョンを出力し、マトリクス実行時の可視性を向上させています。
# bin/test-integration (抜粋)
GEMFILE = File.expand_path(ENV["BUNDLE_GEMFILE"] || File.join(GEM_PATH, "Gemfile"))
puts "Testing against Rails #{Rails.version}"
これらの変更により、マトリクステストの自動化と不要パッチの除去が同時に実現され、コードベースは将来の Rails バージョンに対しても堅牢になります。
設計判断
条件付きパッチ適用とテストマトリクスの外部化という二本柱の設計方針が採用され、既存インターフェースへの破壊的変更を避けつつ拡張性を確保しました。
Appraisal を中心に Gemfile を管理することで、バージョン追加時の設定変更は gemfiles/*.gemfile と Appraisals の記述だけで完結し、CI でも自動的に検出されます。これにより手動でマトリクスを更新するリスクが減少します。
条件付きパッチロジックは Rails バージョン判定に依存し、将来的に Rails 本体が同様の変更を取り込んだ場合でもコードが自動的に無効化されるため、メンテナンスコストが低減します。
まとめ
Rails 8.1 以降の正式サポートとマトリクステストの導入により、activerecord‑tenanted は複数バージョンでの動作保証が自動化され、不要な monkey‑patch が除去されてコードベースがシンプルになりました。今後新しい Rails リリースが出ても、Appraisal と CI マトリクスを更新するだけで検証が可能になる点が大きな利点です。