はじめに
こんにちは! みんなのウェディングのエンジニア@kiyokuroです。 この記事はくふうカンパニーアドベントカレンダーの15日目になります。
今回はNullオブジェクトを使ってnil値のチェックを省略した実装について書きたいと思います。
例えば、必ずしもログインを要求しないアプリケーションでは、ユーザが存在しない(nil)こともしばしばあると思います。ユーザが存在しない時は何も表示しないのであれば問題ないですが、「名無しのユーザ」と言ったユーザが存在しないときには決まった値を表示する要件もあると思います。
そんなオブジェクトの存在性によって仕様がある時の対処方法を説明します。
#Nullオブジェクトとは
オブジェクトのフィールドへの問い合わせ時にnilチェックを行い、クライアントがフィールドに問い合わせる時にnilが含まれることを意識しなくて済むようにするためのパターンです。
使用例
今回の仕様は以下のものとします。
- ログインしていたらユーザ名とユーザのIDを表示する
- ログインしていなかったら「名無しのユーザ」と言う名前とブラウザーIDを表示する
Nullオブジェクトを使わないとき
nilチェックをすると次のようになると思います。
例えばviewでユーザ名とIDを表示する時。
- if @user
= @user.name -# => kiyokuro
= @user.user_id -# => 1234
- else
名無しのユーザ
@browser_id -# => 1234567890
このようにcurrent_userのフィールドにnilを許容しているものが多いと、表示処理の度にnilチェックが必要になりコード量が増え複雑になっていきます。
Nullオブジェクトを使う
そこでNullオブジェクトを使います。
まずはuserがnilの時のNullユーザオブジェクトとしてNullUserを作ります。
Userクラスを継承置き換えるフィールド以外はUserクラスとして振る舞うようにします。
class NullUser < User
def id
browser_id
end
def name
"名無しのユーザ"
end
end
次にUserクラスにNullUserオブジェクトを生成するファクトリメソッドを追加します。
そしてUserがnilであればNullUserを返すようにします。
class User < ApplicationRecord
def self.find(id)
find_by(id: id) || null_user
end
private
def self.null_user
NullUser.new
end
end
これで検索したUserが存在すればUserを返し、存在しなければNullUserを返すようになりました。
User.find(1) #存在するUserのID
=> #<User:
id: 1,
name: "kiyokuro">
User.find(2) #存在しないUserのID
-> #<User:
id: 1234567890
name: "名無しのユーザ">
クライアントのリクエストに対してUserの存在性に関係なく値を返せるようになったので、viewやmodelで条件記述を省くことができるようになりました。
上記の例ではfind
メソッドでActiveRecord::RecordNotFound
の例外が発生しなくなるため、アプリケーション内のクライアントでは上記の仕様を適用する場合でないと使用できません。
全てのクライアントが同じ返答を期待しないとき
Userが存在するか否かで別の振る舞いを期待するクライアントがある時は、ユーザがUserクラスであるかNullUserであるか判定できるようにします。
UserクラスとNullUserクラスにis_nil?メソッドを実装してUserクラスであればfalse
、NullUserクラスであればtrue
を返すようにします。
class User < ApplicationRecord
def is_nil?
false
end
def self.find(id)
find_by(id: id) || null_user
end
private
def self.null_user
NullUser.new
end
end
class NullUser < User
def is_nil?
true
end
def id
browser_id
end
def name
"名無しのユーザ"
end
end
これでUserの存在性を判定して処理を追加できます。
if @user.is_nil?
# 会員登録ページにリダイレクト
else
# マイページにリダイレクト
end
最後に
いつでもどこでも使える便利な方法という訳ではありませんが、viewやモデルで存在性判定の条件記述が増えてきた時には利用できないか検討できるのではないかと思います。
参考書籍
新装版 リファクタリング 既存のコードを安全に改善する
https://www.amazon.co.jp/dp/B01IGW5MG0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
明日は
くふうカンパニーアドベントカレンダー15日目はKotaSakuraiさんです