質問: RailsにてベンチャーでBtoCの自社サービス開発をしている初心者です。 前提として、まだ割と小さなアプリケーションです。 対象のテーブルに関して、モデル名は伏せますがシステムの中心となるデータのモデルです(`Model`とします)。 これには住所のデータがあり、管理側で入力しユーザーに表示されます。モデルの性質上、住所はほぼ必須です。また住所は一つだけしか紐づけられえません。 他に登場するモデルは、都道府県と市区町村を表す`Prefecture`と`City`があり、それぞれマスターテーブルとなっています。 さて、Modelの住所データの持たせ方として、今までは`prefecture_id` `city_id`といったカラムを直接作成し、マスターテーブルに対する外部キーとしていました。`Model`と `Prefecture` `City`は一対一の関係です。 しかしながら、これらのカラムには`NOT NULL`の制約は課されていませんでした。なぜなら、`Model`のデータをCSVから大量にインポートするのですが、CSVの都道府県や市区町村のデータでマスターとマッチしない文字列がある場合はNULLにするしかないからです。 先輩によると、これはよくない設計のため以下のようなリファクタリングをすると言われました。 それは、以下のような中間テーブルを作成し、prefecture_idとcity_idを切り出すということです。 ``` モデル名 - カラム名 ModelPrefecture - model_id - prefecture_id ModelCity - model_id - city_id ``` associationは ``` Model has_one ModelPrefecture Model has_one ModelCity ``` のようになります。 これでNULLを排除できるというわけです。 私はNULL排除の意味もなんとなく知っていましたし、同じアプリケーション内でマスターデータに対して全く同じように設計されている箇所が他にも多くあります。 しかし、私の感覚としては、`prefecture_id` `city_id`は必須に近いデータなので、`Model`のカラムとしてあるのが感覚的には自然でした。 また、ただ`prefecture_id` `city_id`のデータがNULLになるという例外のためにこのような実装をしていると無駄に複雑性が増していくのではないかとも思いました。 住所の表示をするコードでdecoratorなどでprefectureやcityの存在確認をするということは変わりませんし、普通にアプリケーションを書けば致命的なエラーに繋がる部分でもないので、なんとなく腑に落ちない感じです。 私はこれ以外のプロジェクトをあまり見たことがないのでわかりませんが、ネットで検索するとNULL許容派の方もいらっしゃるようです。 このケースでは、 * 上のようなテーブル分割を行う * テーブルは分けず、他の方法で解決する のどちらが良いのでしょうか?
回答: RDBMSの大前提は例外を作らないこと、常に正の綺麗なデータが入っていることです。 ファイアボールというアニメでは 「ルールを曲げてしまわれては、それはもはや**ただの曲がった物**でございます。」 と説いています。 これをしっかり守る事により下記のようなメリットを得られます、というかRDBMSの存在意義です。 * RDBMSから出てきたデータは常に信頼できるからバリデーションが不要 * 最小限のSQLやコードで書けるから利用する際のコードが短く綺麗、そして高速に動作する > > これらのカラムにはNOT NULLの制約は課されていませんでした。 > > > 外部キーのNOT NULL制約が剥げてしまい、 `Model`は**ただの曲がった物**になってしまいました。 もしちゃんとルールを守っていれば`ModelPrefecture`と`ModelCity`は不要でしたね。 従って、上記のテーブルを追加するリファクタリング案は不要です。 ルールを守らないで出来た不備を継ぎ接ぎで直してたらどんどん糞なシステムになってしまいますので、 出来るだけ綺麗なデータに修復する方向で頑張ってください。 > > CSVの都道府県や市区町村のデータでマスターとマッチしない文字列がある場合はNULLにするしかないからです。 > > > 住所があるのになんで都道府県や市区町村がわからないの? 国外に住んでるの?住所不定者? そうではなく妥当ではないCSVなだけでしょう。 読み込めないCSVを無理やり読み込もうとしているのが今回の一番駄目な所です。 清書されていないCSVファイルの信頼度はユーザーの入力値と同じであり、 ちゃんとした値になるようシステム側が死ぬ気でチェックして守るべきです。 もし、そのCSVが社員の人海戦術等によって作られており、ある程度の曖昧さを許容しなければならないのであれば、 Ruby側で死ぬほど考えてなんとかしましょう。 不正な住所データや郵便番号を探してきたりして、何とか都道府県を見つけて宛がうとか、 どうにも見つからなかったら、不正なデータだけCSVに固めて出した人に差し戻すとか ...えっ?であるべき論ではなくて、今すぐなんとかしたい? しょうがないなぁ...じゃあ私だったらこう動くってやり方を共有するよ。 まずはPrefectureとCityのマスターテーブルに「見当たらなかった」用のUnknownレコードを追加しよう。 日本語名称は空文字がいいかな?falsyだし。 それと共に現行のModelの問題のレコードをunknown_idに書き換え、これでNot Null制約を付与できる。 短期的にはCSV登録時見つからなければUnknownIDを付与するよう作り変えるといいね。 長期的に不正なCSVが送られてくる状況ははっきり言ってまずいのでバリデートをしっかり作り込むのは必須。 ToDoやタスクのカードに登録しておこう!