LoginSignup
0
0

More than 3 years have passed since last update.

[Rails]ポリモーフィック関連で、特定のクラスに依存した処理を書くのはやめよう

Posted at

まずは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

こうすることで処理が一箇所に集中してカオスになるのを防ぐことができます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0