例
以下の 2 つの Model があるとします。
app/models/user.rb
class User < ActiveRecord::Base
has_one :setting, dependent: :destroy
end
app/models/setting.rb
class Setting < ActiveRecord::Base
belongs_to :user
enum format: %i(plain markdown textile)
enum notification: %i(none email push)
end
User は 0 件あるいは 1 件の Setting を持ちます。
user = User.find_by(name: '佐倉 杏子') #=> #<User>
user.setting #=> #<Setting>
user.setting.format #=> "markdown"
user.setting.notification #=> "email"
user = User.find_by(name: '美樹 さやか') #=> #<User>
user.setting #=> nil
ここで Setting の値を使用しているコードのことを考えてみます。
case user.setting.format
when 'plain'
# プレーンテキストを処理
when 'markdown'
# markdown 記法を処理
when 'textile'
# textile 記法を処理
end
上記の case 文では user.setting
が nil のケースを考慮できていません。そのため以下の様に書き換えます。
case user.setting&.format || 'plain'
when 'plain'
# プレーンテキストを処理
when 'markdown'
# markdown 記法を処理
when 'textile'
# textile 記法を処理
end
Setting が存在しない場合、デフォルト値として 'plain' を指定するようにしました。
しかし、このアプローチでは user.setting
を参照しているすべてのコードで同じような記述をしなければならないでしょう。DRY ではないし、そもそも呼び出し元で設定があるかないかを逐一調べたくはありません。
ここで便利なのが、デザインパターンの Null Object パターン です。
空の Setting を表すクラスを定義し、そこにデフォルト値の設定を集約するのです。
app/models/setting/null.rb
class Setting
class Null
def format
'plain'.freeze
end
def notification
'none'.freeze
end
end
end
NullSetting というクラス名にしてもよかったんですが、そうすると app/models
直下がごちゃごちゃしそうなので Setting::Null というクラス名にしてみました。
そして User に setting_or_missing
というメソッドを定義します。
app/models/user.rb
class User < ActiveRecord::Base
has_one :setting, dependent: :destroy
def setting_or_missing
setting || Setting::Null.new
end
end
この setting_or_missing
メソッドを使用すれば、呼び出し元は User が Setting を持っているか否かに関心を持たずに済みます。
case user.setting_or_missing.format
when 'plain'
# プレーンテキストを処理
when 'markdown'
# markdown 記法を処理
when 'textile'
# textile 記法を処理
end
参考
- Rails Refactoring Example: Introduce Null Object
-
How to apply Null object pattern for ActiveRecord associations?
-
hoge_or_missing
というメソッド名はこの記事から拝借しました。
-