🩹
データベースに接続中のセッションがあっても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 db:drop
を実行した際の処理の流れ
Railsで 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