この記事は何
Railsにはポリモーフィック関連付けという機能があります。
この機能は使い方によっては便利な一方、依存を複雑化させる原因にもなりうるものです。
この記事では、ポリモーフィック関連を使うときにやりがちな「特定のモデルへの依存」を避けるテクニックをご紹介します。
ポリモーフィック関連の具体的な使い方はドキュメントや、以下の記事などをご参考ください。
ポリモーフィック関連を使っている時に起こること
ポリモーフィック関連を使っていると、特定のモデルの場合にのみ実行したくなる処理などが生まれた時に、以下のような実装をしてしまうことがあります。
class Like < ApplicationRecord
belongs_to :like_user, User
belongs_to :liked_user, User
belongs_to :item, polymorphic: true
after_create :log_liked_data, if: :article?
def log_liked_data
item.track(user: like_user)
end
def article?
item.is_a?(Article)
end
end
class Article < ApplicationRecord
has_many :likes, as: :item
end
class Question < ApplicationRecord
has_many :likes, as: :item
end
このような実装は、正しくは動きますが、本来Like側では依存するべきではないArticleに依存した処理になってしまっており、依存関係が複雑化してしまっています。
このような実装はバグも生みやすくなり、できれば避けた方が良い実装です。
解決方法
このような問題を解決する方法は、SOLID原則でいう、いわゆる「インターフェース依存の原則」と「依存関係逆転の原則」を適用した実装にしていきます。
実装する流れは以下の通りです。
- Likeもしくは同じネームスペース内に、moduleでインターフェースの定義を行う
- ポリモーフィック関連で依存する側のモデルに、実装したインターフェースをincludeし、処理の実装を行う
- Likeはmoduleで定義したインターフェースにのみ依存するように実装を変える
コードにすると以下の通りです。
class Like < ApplicationRecord
belongs_to :like_user, User
belongs_to :liked_user, User
belongs_to :item, polymorphic: true
after_create :create_side_effect
def create_side_effect
item.like_create_side_effect(like: self)
end
module Likable
extend ActiveSupport::Concern
included do
has_many :likes, as: :item
end
def like_create_side_effect(like:)
raise NotImplementedError
end
end
end
class Article < ApplicationRecord
include Like::Likable
concerning :Likable do
def like_create_side_effect(like:)
Tracker.track(user: like.like_user, target: self)
end
end
end
class Question < ApplicationRecord
include Like::Likable
concerning :Likable do
def like_create_side_effect(like:); end
end
end
まとめ
ポリモーフィック関連は、依存先を抽象化するテクニックです。
抽象化する場合は、特定のモデルに依存するような処理は基本的に書かずに、インターフェースに依存するような実装をするようにしましょう。