BLOG株式会社ラクーンホールディングスのエンジニア/デザイナーから技術情報をはじめ、世の中のためになることや社内のことなどを発信してます。by一緒にラクーンのサービスをつくりませんか?採用情報カジュアル面談2016.1.14開発Ruby[Rails]Model/テーブル設計で必ず覚えておきたいSTIRubyRails開発チームの下田です。Railsについて、基本的なこと(modelやcontrollerが何か知っている程度)を理解されている方を対象としています。BtoBクラウド受注・発注システムCORECはRailsで開発しています。開発初期のコードを見直してみると失敗したなと思うことがあります。Inheritance、単一テーブル継承)を活用しなかったことです。STIを簡単に言うと、モデルを永続化するときに、どのクラスなのかというメタ情報を含めてデータベースに保存します。これだけではわかりづらいと思うので、失敗した例と改善例からご紹介したいと思います。仮に下記の仕様のコードを書くとします。要件1:CORECではWEBアプリから発注書を送信できます。送信方法は次の3つから選べます。WEBアプリ内で送信するメールで送信するFAXで送信するこの時、次のようなコードにしていました。endend何が問題か当初は送信するときだから、送信手段ごとに処理をcase-whenで振り分けることに違和感はありませんでした。開発を進めていると、同じコードがあちこちに現れて読みづらいコードになり始めました。endendあるべき姿FAXもメールもすべて注文書の情報なのでOrderクラスにしたことは正しいのですが、送信方法によってふるまいが違います。役割が同じでふるまいが違うなら、サブクラスを作りポリモーフィズムを持たせるべきです。endendメソッドの中から冗長なcase文が消えます。relationやvalidationのふるまいも送信方法ごとに整理され、見通しが良くなりました。このようなクラス設計にしたい場合はSTIを使用します。STIを使用するにはtype:stringカラムを追加します。endtypeカラムがある場合とない場合を比較してみましょう。'Order::Mail'"データベースに保存し永続化した後でもふるまいを維持し続けてくれるので、クラス設計を適切に行えば複雑な仕様が追加されても、良い状態のコードを保てると思います。開発には非常に便利なSTIですが、運用を考えて開発しなければならないことが1点あります。typeカラムに存在しないclass名が設定されていると、ActiveRecord::SubclassNotFound例外が発生します。ActiveRecord::SubclassNotFound例外が発生するのは、2パターンがあります。リリース時に並行稼動している時に、新しいAPで保存した新しいサブクラスを古いAPでロードしたリリースしたがロールバックし、新しいAPで保存した新しいサブクラスを古いAPでロードした<どちらの場合も、何も変更しないサブクラスを作り、先行してリリースしておくと、予期せぬ例外が起きません。フェイルソフトになります。Orderendもしくはデフォルトスコープに現バージョンで存在しているclassのみを指定すると、ロードされないので例外が発生しません。サブクラスを追加した時に追加し忘れたり、where句が思わぬところで効いたりしないか確認が必要です。endおわりにSTI以外にも、ああすればよかったと思うことは多くあります。その中でもSTIについて書いたのは、知っているか知らないかでModelの設計に差が出てしまうため、気づいた時には手軽にはリファクタリングできなくなっているからです。チュートリアルに載っていなかったりと軽視されがちです。新しくmodelを設計するときには頭の片隅に置いておくといいと思います。