まずはNGなコードから
前提:
・メール通知機能を実装する
・通知を表すモデルは Notification
とする
・通知機能は User
Company
Customer
Admin
等、メールアドレスを持つモデル全てが通知対象の候補となる
・Notification
と通知対象モデルはポリモーフィック関連を持つ
↑この条件で以下のようなコードを書くのはやめましょう。
app/models/notification.rb
class Notification < ApplicationRecord
belongs_to :notifable, polymorphic: true
# 通知処理の実装
def notify
# 送信先取得処理
# ここのように is_a? でクラス名を見て分岐するのがNG
to = if notifable.is_a?(User)
notifable.email
elsif notifable.is_a?(Company)
notifable.company_settings.email
elsif notifable.is_a?(Customer)
notifable.email
elsif notifable.is_a?(Admin)
notifable.email
end
# メール送信処理
send_mail(to: to)
end
end
何故良くないのか?
今はまだ比較的シンプルですが、機能追加がされるたびに notify
メソッドに処理が集中して、コードが読みづらくなります。
例えば以下の仕様が追加されたとしましょう:
- User, Company, Customer は通知をunsubscribeできる(インターフェースはそれぞれ違う)
- Admin は email がからのことがある。その場合はAdmin共用のアドレスに通知する
app/models/notification.rb
class Notification < ApplicationRecord
belongs_to :notifable, polymorphic: true
# 通知処理の実装
def notify
# 通知不要の対象に送信しない処理
is_unsubscribe = if notifable.is_a?(User)
notifable.user_settings.unsubscribe?
elsif notifable.is_a?(Company)
notifable.company_settings.unsubscribe?
elsif notifable.is_a?(Customer)
notifable.unsubscribe?
end
return if is_unsubscribe
# 送信先取得処理
to = if notifable.is_a?(User)
notifable.email
elsif notifable.is_a?(Company)
notifable.company_settings.email
elsif notifable.is_a?(Customer)
notifable.email
elsif notifable.is_a?(Admin)
notifable.email.presence || "developers@hoge.com"
end
# メール送信処理
send_mail(to: to)
end
end
こうなるとだんだん複雑になってきました。
実際の業務では更に細かい条件分岐があとから追加されることが多く、コードが一箇所に集中してカオスになることが多いです。
こうならないためには?
ダックタイピングを使い、特定のクラスに依存した処理は移譲しましょう。
具体的には:
app/models/notification.rb
class Notification < ApplicationRecord
belongs_to :notifable, polymorphic: true
# 通知処理の実装
def notify
return if notifable.unsubscribe_notification?
to = notifable.notifable_email
# メール送信処理
send_mail(to: to)
end
end
app/models/user.rb
class User < ApplicationRecord
def unsubscribe_notification?
user_settings.unsubscribe?
end
def notifable_email
email
end
end
app/models/admin.rb
class Admin < ApplicationRecord
def unsubscribe_notification?
false # admin には常に通知を送るので false 固定
end
def notifable_email
email.presence || "developers@hoge.com"
end
end
app/models/company.rb
class Company < ApplicationRecord
def unsubscribe_notification?
company_settings.unsubscribe?
end
def notifable_email
company_settings.email
end
end
こうすることで処理が一箇所に集中してカオスになるのを防ぐことができます。