Rails
polymorphic
NIJIBOXDay 13

ポリモーフィック関連で実装し運用してみて

More than 1 year has passed since last update.

はじめに

私が現在、保守運用・追加開発をしているシステムにて、ポリモーフィック関連を利用して実装し本番リリースした後、追加開発などでその機能に更新が入ったりして、ポリモーフィック関連の便利さや注意すべき点などを感じたのでその内容を書いていきます。

そもそもポリモーフィック関連とは何なのか、具体的にどうやって実装するのかは本記事では説明しません。「ポリモーフィック関連」で検索すれば、分かり易く書かれている記事が多数見つかりますので、そちらを参照されるのが良いかと思います。

ちなみに今回のシステムはRailsで作っています。

アプリ要件

まず、今回のシステムはSNSのようなサイトで、サイト内でユーザはEventとCommunityを作成することができ、他の人が作成したEventとCommunityに参加することができます。そして、EventとCommunityの管理者は参加者に対してメッセージを送ることができ、メッセージ履歴は各EventとCommunity内で管理されます。EventとCommunityにMessageが紐づくという構造になりますね。実際にはUserモデルも絡んでますが、本記事では触れません。

ポリモーフィック関連を利用しない場合

この実装に着手した当初、私はポリモーフィック関連のことを知らなかったため以下のように別々のテーブルを作って共通するロジックはconcernsに切り出そうかなーと思ってました。

event.rb
class Event < ActiveRecord::Base
  has_many :event_messages
end
event_message.rb
class EventMessage < ActiveRecord::Base
  belongs_to :event
end
community.rb
class Community < ActiveRecord::Base
  has_many :community_messages
end
community_message.rb
class CommunityMessage < ActiveRecord::Base
  belongs_to :community
end

しかし、この状況はそんなに珍しいものでもなく他の人もけっこう同じ問題にぶち当たってるだろう、みんなはどうやって解決しているのかなーと思い、調べてみたらポリモーフィック関連のことを知りました。ポリモーフィック関連について書かれた記事をいくつか読んで、やや批判的な意見の方が多かった気がしましたが、Railsでもサポートしている仕組みだし、アンチパターンを回避して実装すれば良さそうに思えたので、ポリモーフィック関連を利用して実装することを決めました。

実装した内容

ポリモーフィック関連を利用して以下のように実装しました。
※実際にはメソッドがいくつかありますが省略してます

event.rb
class Event < ActiveRecord::Base
  include Messageable
end
community.rb
class Community < ActiveRecord::Base
  include Messageable
end
messageable.rb
module Messageable
  extend ActiveSupport::Concern
  included do
    has_many :messages, as: :messageable, dependent: :destroy
  end
end
message.rb
class Message < ActiveRecord::Base
  belongs_to :messageable, polymorphic: true
end

メリットに感じたこと

まず最初にメリットと思ったことは、コーディング量が少なくすんだことですね。必要なバリデーションやメソッドはmessage.rbに書けばよいし、変更があったときもmessage.rbを修正すればよく、上記のポリモーフィック関連を利用しない場合のようにevent_message.rbとcommunity_message.rbの2つに対して実装する必要がありません。

そして、EventとCommunityという関連先を意識しながら実装する必要がなかったというのも思ったより実装が楽でした。これは作業量が減って楽になったというよりも気が楽になったという感じです。MessageからEventもしくはCommunityのidを取得したいときは、message.messageable.idと書け、頭の中でEventの場合とCommunityの場合を思考しなくても良いという安心感がありました。

私のようにまだまだオブジェクト指向できちんと実装できない人間にとっては、Railsが用意してくれたポリモーフィック関連の仕組みに乗っかることで、このロジックはどこに書くべきかを考えたときに実装場所を誘導してくれて、結果、整理された実装になりました。この経験でオブジェクト指向についての理解が一つ深まった気がします。

デメリットに感じたこと

他のポリモーフィック関連について書かれている記事の中でも指摘されてますが、外部キー制約を張ることができないのはデメリットに感じました。例えば、Messageに格納されているEventのidがeventsテーブルに存在しなかった場合、RailsではそのEventを取得しにいった時にnot foundで404ページに遷移してしまいます。そうなるとメッセージ一覧を表示できないといった状況に陥る場合もあります。そうならないようにdependent: :destroyで依存関係を作っていますが完全に防げるわけではないので、やはりDBで確実に防いで安心しておきたいと思いました。ちなみに、本番リリース後に上記のようなエラーはまだ起きていません。

保守運用という観点では、メンバーのポリモーフィック関連に対する理解が重要だと思いました。ポリモーフィック関連を理解していない人がソースコードを更新すると、そもそも意味がない実装になってしまったり、かえって複雑な実装になってしまったりする危険性があります。ポリモーフィック関連を利用していることをチームメンバーに共有したり、ソースコード上にコメントで記載しておいたり、ポリモーフィック関連という言葉をまず伝え、それを理解してからソースコードを更新しなければならないということを伝える必要があります。私のようにポリモーフィック関連を知らなかった人は実装前にまず「理解する」ということに時間を使うことになりますが、これを機に勉強すれば成長になるのでそれはそれで良いかと思います。

まとめ

私がまだ遭遇していないデメリットも沢山あると思いますが、ポリモーフィック関連について注意点も理解した上で利用すれば、コードが整理されるし変更に強くなるため有用かなと思います。