はじめに
以下のQiita記事を拝見しました。
エラーが解決しない時は、元を断つという考え方 - Qiita
詳しくは上記記事を読んでもらいたいのですが、簡単に要約すると「ローカル環境のデータがおかしくなったら、(データを消しても問題ないことを確認したうえで)データベースを再作成しよう」という内容です。
ただ、個人的には「うーん、あまりよくないな」と思ってしまったので、返信コメントを書こうと思いました。
コメントを書き始めたらかなり長くなってしまったので、独立した記事にしてしまおうと思いました。
それが本記事になります。
というわけで、ローカル環境のデータがおかしいから全部リセットしたい!と思うその前にすべきことを以下にまとめます。
ローカル環境のデータがおかしいから全部リセットしたい!と思うその前にすべきこと
(注:以下の内容はもともと冒頭に載せたQiita記事への返信コメントとして書いたものです。その前提で読み進めてください)
データベースのリセットは「データを全部消しても問題ない」というときは有効ですが、そうでないときは使えません。
個人の趣味で開発しているRailsアプリなら良いかもしれませんが、仕事で開発するRailsアプリはローカル環境といえど、「今までよく使ってきたテストデータ」がたくさん蓄積されているので、往々にして「これを全部消すわけにはいかない」ということになります。
また、開発環境でなく、本番環境で同じ問題が起きたときは「データを全部消す」という作戦は、まず使えません。
さらに言うと、データを全部消してしまうとなぜエラーを引き起こすデータが発生したのか、という原因調査もしづらくなります。
上記のような理由から、「データを全部消す」というのは本当に最後の手段として残しておき、データを消す前にできる限りの調査と対処を実施すべきです。
僕であれば以下のような対処法をとります。
不具合の原因を調査し、データ異常かそうでないかを見極める
まず、不具合の原因を見極めます。
今回の記事であれば、post.user
がnil
になっているのが原因のようです。
次に、その状態が妥当かどうかを判断します。
post.user
がnil
ということは、「投稿に紐付く投稿者がいない」という状態です。
これは常識的に考えると、まずありえない状況(=データの異常)だと思います。
データ異常が発生した原因を突き止め、必要に応じてコードを修正する(再発防止)
それから、なぜ「投稿者がいない投稿が生まれてしまったのか?」という原因を考えます。
その原因を突き止めないと、もしデータベースを再作成してエラーを出なくしても、しばらくするとまた「投稿者がいない投稿」がひょっこり現れるかもしれません。
ふつうに画面を操作していて投稿者無しで投稿できる仕組みになっているのであれば、それは不具合ですので、不具合が再発しないよう、必ず投稿者が設定される仕組みにコードを修正しましょう。
以下に想定できそうな原因と対処法を載せます。
optional: true
が付いていた場合
最近のRailsだとbelongs_to
で定義した関連レコードはデフォルトで必須になっているので、可能性は低いと思いますが、もしoptional: true
が付いていたらこれを外します。こうすれば、user
がnil
の状態でPostを保存しようとするとバリデーションエラーが発生します。
class Post < ApplicationRecord
- belongs_to :user, optional: true
+ belongs_to :user
end
あとからpostsテーブルにuser_id
列を追加した場合
もしくは、最初にPostモデルが作成され、その後時間を空けて別のmigrationでuser_id
がpostsテーブルに追加されたのかもしれません。
この場合はmigration実行後に、既存のPostレコードに対して何らかのUserレコードを紐付けて保存する対応(既存データの修正)が必要になります。
この対応を忘れていると、「投稿者がいない投稿」が生まれてしまいます。
Userレコードが削除されていた場合
もしかすると一番ありえるのは、「Postレコードを作成したあとに、投稿者のUserレコードを削除してしまったこと」が原因かもしれません。
この場合は、
- 関連するPostレコードを持つUserレコードは削除できないようにする
- または、Userレコードを削除したら関連するPostレコードも一緒に削除する
- または、投稿者がいない投稿を正常なデータとして受け入れた上で、「退会済みユーザー」のような表示を画面に出す
といった対処方法が考えられます。
最初の2つについてはRailsの dependent
オプションで設定可能です。
詳しくは以下の記事をご覧ください。
dependent: :restrict_with_error と :restrict_with_exception の違い - Qiita
ぱっと思いつく原因はこれぐらいですが、他にも「これが原因だった」という原因(=不具合)を見つけたら、今後二度と「投稿者がいない投稿」が生まれないようにプログラムを修正します。
再発防止策がとれたら、既存の異常データを修正する
おかしなデータが生まれない状況を作れたら、今度は既存の異常データを修正します。
たとえば、以下のようなスクリプトを実行すれば、「投稿者がいない投稿」に対して投稿者を設定できます。(あくまで例ですので、適宜スクリプトを修正してください)
user = User.first
Post.all.each do |post|
if post.user.nil?
post.user = user
post.save!
end
end
もしくは、「投稿者がいない投稿」を削除する、というデータ修正も選択肢のひとつになります。
Post.all.each do |post|
if post.user.nil?
post.destroy!
end
end
こうすれば「すべての投稿に投稿者が紐付いている状態」になるので、post.user.image_name
を呼びだしてもエラーは出なくなるはずです。
データ異常ではなかった場合は、コードを適切に修正する
もし仮に「投稿に紐付く投稿者がいない」という状況が不具合ではない(仕様として妥当である)場合は、post.user
がnil
かどうかでviewの処理を分ける必要があります。
<% if post.user %>
<%# 投稿者がいる場合の処理 %>
<% else %>
<%# 投稿者がいない場合の処理 %>
<% end %>
まとめ
このように対処すれば、「データを全部消す」という手段をとらずに済みます。
というか、「ローカル環境で個人的な趣味で作ってるだけです」という場合を除き、通常はこのような対応を取るのが適切だろうと僕は考えます。