🩹
データベースに接続中のセッションがあってもrails db:resetを成功させる
rails serverが起動している状態でrails db:reset等を実行すると以下のようなエラーを出力して失敗してしまいます。
$ ./bin/rails db:reset
PG::ObjectInUse: ERROR: database "app" is being accessed by other users
DETAIL: There is 1 other session using the database.
Couldn't drop database 'app'
rails aborted!
ActiveRecord::StatementInvalid: PG::ObjectInUse: ERROR: database "app" is being accessed by other users
DETAIL: There is 1 other session using the database.
/workspace/app/bin/rails:4:in `<main>'
ローカル・プレビュー環境など、何度もdb:resetすることになるので、このエラーを回避したいですね。
原因
PostgreSQLはデータベースに接続中のセッションが別にある状態でDROP DATABASEを実行することを許していません。
PostgreSQL13以降ではDROP DATABASEにFORCEオプションが追加された
Allow DROP DATABASE to disconnect sessions using the target database, allowing the drop to succeed (Pavel Stehule, Amit Kapila)
This is enabled by the FORCE option.
対処
Railsで rails db:drop を実行した際の処理の流れ
PostgreSQLを使用しているRailsアプリケーションでrails db:dropを実行した時、以下のタスクが実行されます。
connection.drop_databaseでActiveRecord::Base.connectionの#drop_databaseを実行しています。PostgreSQLを使用している場合は下記が実行されます。
パッチを当てる
FORCEオプション未対応の最後のメジャーバージョンであるPostgreSQL 12のEOLは2024年11月であるそうです。まだ少し先ですね。
今回はモンキーパッチを当てることで対応します。
WITH (FORCE)を追加するパッチを作成
以下のようなモンキーパッチを作成しました。
lib/patches/activerecord_postgresql.rb
# PostgreSQL13>で使えるFORCEオプションを使用してコネクションがあってもDROP DATABASEできるようにする
ActiveSupport.on_load(:active_record) do
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(
Module.new do
def drop_database(name)
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)} WITH (FORCE)"
end
end
)
end
パッチが読み込まれるように設定
initializersにファイルを作成して、モンキーパッチがRailsの起動時に読み込まれるようにします。
config/initializers/000_monkey_patches.rb
Dir[Rails.root.join('lib/patches/**/*.rb')].each do |file|
require file
end
テスト
$ ./bin/rails db:reset
Dropped database 'app'
Created database 'app'
うまくいきました!
Discussion