Active RecordとNull Objectパターン
Active RecordでNull Objectパターンを使うことはできるのか考えてみました。
RubyでNull Objectパターン
RubyでもNull Objectパターンを使えます。下記の記事を御覧ください。
Active RecordとNull Objectパターン
どんなときに使いたいか
アソシエーションを張っているレコードがない場合の処理を書きたいときに、Null Objectパターンを使えたら便利そうです。
実装
class Customer < ApllicationRecord has_one :actual_license, class_name: 'License' def license actual_license || NullLicense.new end end class License < ApplicationRecord belongs_to :customer end class NullLicense end
このようにすれば、アソシエーションしているLicenseレコードが無い場合はNullLILicenseを渡すことができます。実際のlicenseとの関連はCustomer#actual_licenseを呼び出すことになります。
実装のポイント - has_oneのclass_nameオプションを使ってLicenseを取得するメソッドの名前を変更する - licenseメソッドのゲッタを作ることでNullLicenseのインスタンスを返せるようにする
困りそうなポイント
NullLicenseのほうに値の無い処理が書けるようになりましたが、その代わり、構造上のややこしさを生み出しました。
- ライセンスのない場合の処理を外部に書きたいとき、Customer#license.nil?のような判定をすると意図していない結果になる(NullLicenseが取得できるため)
- licenseかlicense_actualかを呼び出し側で考慮しないと行けない部分が出てくるかもしれない
といったところです。2点目の回避策としては、NullLicenseの方にLicenseインスタンスが外から呼び出されるメソッドを実装してしまうという方法があります。例えばLicense#destroyが呼ばれる場合、このようにしてダミーのメソッドを作ります。
class NullLicense def destroy # 何もしない end end
この方法は、NullLicenseの方に値がないときの処理がまとまる利点がありますが、構造の把握をややこしくしてしまうという欠点もあります。
Active RecordでNull Objectパターンはややこしいコードを生んでしまうかも
Null Objectパターンを使うためにアソシエーションの張り方を歪めているので、新たな問題を生んでしまう可能性があります。
このあたりのデメリットを許容した上で、値がないときのややこしい処理がビューやコントローラーに散ってしまうことの対策として使うのがよさそうです。