小ネタ。
Rails 5から、production environmentでのDB破壊系のtaskの実行を防止する仕組みが入りました。
例えば、production environmentでdb:drop
を実行しようとすると、下記のようなエラー(ActiveRecord::ProtectedEnvironmentError
)が発生します。
$ RAILS_ENV=production ./bin/rails db:drop rails aborted! ActiveRecord::ProtectedEnvironmentError: You are attempting to run a destructive action against your 'production' database If you are sure you want to continue, run the same command with the environment variable DISABLE_DATABASE_ENVIRONMENT_CHECK=1
これは見ての通り、誤ってproduction environmentのDBを破壊してしまうのを防ぐ為の仕組みです(因みにPRのコメントによると、実際にHerokuで誤ってDBを破壊してしまい復旧をお願いしたい、という依頼は割ときていたそうです)。
意図的にproduction environmentのDBを破壊したい場合は、エラーメッセージにも表示されている通り、環境変数 DISABLE_DATABASE_ENVIRONMENT_CHECK=1
指定してあげればOKです。
防止処理の対象となるtaskは下記の通りです。
デフォルトではproduction environmentのみが防止対象となっていますが、これは変更可能となっています。production以外のenvironmentを指定したい場合は、ActiveRecord::Base.protected_environments
に防止対象となる environmentを指定してあげればOKです。
# config/application.rb ActiveRecord::Base.protected_environments = %w(production staging)
$ RAILS_ENV=staging ./bin/rails db:drop rails aborted! ActiveRecord::ProtectedEnvironmentError: You are attempting to run a destructive action against your 'staging' database If you are sure you want to continue, run the same command with the environment variable DISABLE_DATABASE_ENVIRONMENT_CHECK=1
ActiveRecord::Base.protected_environments
にはArrayを指定出来ますので、当然複数のenvironmentsを防止対象とする事が可能です(値は上書きされるので、production以外にプラスして environmentを指定したい場合、productionを明示的に指定する必要があります)。
因みに、environmentsのチェックは、最後にmigration処理等を実施したenvironmentとの比較で行われます。
これはどういう事かというと、Rails 5から、db:migrate
や、schemaのload処理を行った場合、その処理を行ったenvironmentの情報をDBに保持するようになっています。
具体的には、Rails内部でar_internal_metadata
という名前のテーブル(デフォルト場合)が作成され、そこにenvironmentの情報が保持されるようになっています。
irb(main):001:0* ActiveRecord::InternalMetadata.all ActiveRecord::InternalMetadata Load (0.2ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" => #<ActiveRecord::Relation [#<ActiveRecord::InternalMetadata key: "environment", value: "development", created_at: "2016-04-01 08:15:10", updated_at: "2016-04-01 08:15:10">]>
※ table_name_prefix / table_name_suffixが設定されていた場合、その値がテーブル名の前後に付きます。また、テーブル名はActiveRecord::Base.internal_metadata_table_name
で変更可能です。 更にいうと、Rails 5.0.0.beta2までは、デフォルトのテーブル名はactive_record_internal_metadatas
という名前でした。
ar_internal_metadata
テーブルに格納されている値と、処理を実行しようとするenvironmentを比較するので、当然ar_internal_metadata
テーブルが無いと処理が行えません。
そのため、ar_internal_metadata
が無い状態(Rails 4からRails 5に更新して、一度もdb:migrate
を実行していない状態の場合)でdb:drop
等の破壊系のtaskを実行しようとすると、Environmentの情報が無い旨エラー(ActiveRecord::NoEnvironmentInSchemaError
)が発生します。
$ ./bin/rails db:drop rails aborted! ActiveRecord::NoEnvironmentInSchemaError: Environment data not found in the schema. To resolve this issue, run: bin/rails db:environment:set RAILS_ENV=development
これは防止対象のenvironmentであるかどうかに関係無く発生します。
解消するには、エラーメッセージに表示されている通りdb:environment:set
taskを実行してあげればOKです(db:migrate
を実行するとかでもOKです)。
因みに、何らかの理由によりar_internal_metadata
テーブルのenvironment
に、処理を実行しよとしているenvironmentと異なる値が入っていた場合、これもまたエラー(ActiveRecord::EnvironmentMismatchError
)となります。
例えば、development environmentのar_internal_metadata
テーブルのenvironment
に、test
という値が入ってしまっていたとします。
irb(main):001:0* Rails.env => "development" irb(main):002:0> ActiveRecord::InternalMetadata.all ActiveRecord::InternalMetadata Load (0.2ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" => #<ActiveRecord::Relation [#<ActiveRecord::InternalMetadata key: "environment", value: "test", created_at: "2016-04-01 11:35:11", updated_at: "2016-04-01 11:35:31">]> irb(main):003:0>
この状態でDB破壊系のtaskを実行すると以下のようになります。
$ ./bin/rails db:drop rails aborted! ActiveRecord::EnvironmentMismatchError: You are attempting to modify a database that was last run in `test` environment. You are running in `development` environment.If you are sure you want to continue, first set the environment using: bin/rails db:environment:set RAILS_ENV=development
こちらの場合も、エラーメッセージに出ている通りにdb:environment:set
taskを実行してあげればOKです。
異なるenvironmentsで同じDBを使っている場合に、地味にトラブりそうな気がします。
Rails 5に入ったDB破壊系taskの防止処理がデフォルトで提供されるようになったの自体は大変良い事かと思います。完全に余談なのですが、同じような事を行う為のgemを作ったことがあるのですが、不要になりました。いや良いことなんですけどね。
あとちょいちょい ActiveRecord::EnvironmentMismatchError
がおきてしまう気がする…。