YOUTRUSTで利用しているポリモーフィック関連付け機能

こんにちは!webエンジニアの奥村一貴(YOUTRUST / X)です。

入社してから二ヶ月が経ち、開発や業務フローにも少しずつ慣れてきました。

先日初めて「ポリモーフィック関連付け」という機能を知りました。

とても便利な機能だと感じたので、本日はYOUTRUSTでの利用方法と紐づけてポリモーフィック関連付けについてご紹介します。

ポリモーフィズム、ポリモーフィックとは

ポリモーフィズム

オブジェクト指向プログラミングにおいて、多様性や柔軟性を意味する概念です。

具体的には、異なるクラスを同様に扱えるようにし、同じメソッドを通じてそれらを操作できるようになります。

ポリモーフィック

ポリモーフィズムの概念に基づき、異なる型に柔軟に対応できる仕組みや特性のことを表します。

Railsのポリモーフィック関連付けは、この考え方をデータベースのリレーションやモデル間の関連付けに活かし、柔軟なデータ操作を可能にしています。

ポリモーフィックがないと何が困るのか

例えばCommentモデルと紐づく Post、Photoモデルがあるとします。

class Post < ApplicationRecord
  # Postに対するコメントの関連付け
  has_many :post_comments
end

class Photo < ApplicationRecord
  # Photoに対するコメントの関連付け
  has_many :photo_comments
end

class PostComment < ApplicationRecord
  # Postに紐づくコメント
  belongs_to :post
end

class PhotoComment < ApplicationRecord
  # Photoに紐づくコメント
  belongs_to :photo
end

ポリモーフィックを利用しないと新たにコメント機能を実装したいリソースが増えるたびに、新しいコメントのテーブルを作成する必要があります。

その結果データベースの設計が複雑になったり、保守性が悪くなります。

Railsのポリモーフィック関連付け機能の紹介

railsguides.jp

ActiveRecordの機能の一つで以下のようなことができます。

  • データベースの設計において、ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現できる。

  • 通常、モデル同士の関連付けは特定の2つのモデル間で定義されるが、ポリモーフィック関連付けを使うことで、1つのモデルがさまざまなモデルと動的に関連付けることが可能になる。

それではRailsにおけるポリモーフィック関連付けがどういった機能か具体的に紹介します。

使い方

モデルの定義

class Post < ApplicationRecord
  # コメントがつけられることを示す関連付け
  has_many :comments, as: :commentable
end

class Photo < ApplicationRecord
  # Photoにもコメントがつけられる
  has_many :comments, as: :commentable
end

class Comment < ApplicationRecord
  # commentableはPostやPhotoなど、異なるモデルに紐づけられる
  belongs_to :commentable, polymorphic: true
end


データ作成

実際にデータを作成します。

上記で polymorphic を使ってリレーションを作成することで、〇〇.comments.createで関連するコメントを作成できます。

# Postに対してCommentを作成する
post = Post.create(title: "初投稿!")
comment = post.comments.create(content: "初投稿おめでとうございます!")

# Photoに対してCommentを作成する
photo = Photo.create(title: "写真を撮りました!")
photo_comment = photo.comments.create(content: "とても綺麗です!")


データの取得方法

例えばとあるコメントが Post に紐付いているのか、Photo に紐づいているのか確認したい時などに、この polymorphic はとても便利です。

comment = Comment.first
comment.commentable # => PostオブジェクトまたはPhotoオブジェクト

モデルで設定した、commentableメソッドを使うことで、コメントに紐づくPost または Photo のいずれかの関連モデルを取得することができます。

polymorphic を使うことで、コメントの関連モデルを簡単に取得でき、複雑な外部キーのチェックなどのロジックが不要になります。

YOUTRUSTで実際に使用している機能

ここでは、YOUTRUSTで実際に使用しているポリモーフィック関連付けの具体例をご紹介します。


YOUTRUSTではホーム画面にいわゆるタイムラインのような機能があり、表示している要素の一つ一つをフィードアイテムと呼んでいます。

フィードに表示するコンテンツは「投稿」や「ユーザープロフィールの更新」などリソースの種類が多岐にわたります。

このフィードアイテムは、FeedItemモデルとしてポリモーフィック関連付けを適用し、フィードに表示する様々なリソースとの紐付けを実現しています。

実際のソースコード (一部抜粋)

models/feed_item.rb

# Table name: feed_items
#
#  id             :bigint           unsigned, not null, primary key
#  display_type   :integer          unsigned, not null
#  content_id     :integer          unsigned, not null
#  content_type   :string(255)      not null
#  created_at     :datetime         not null
#  updated_at     :datetime         not null
#
# Indexes
#
#  i1  (content_id,content_type) UNIQUE
#
class FeedItem < ApplicationRecord
  belongs_to :content, polymorphic: true

# 以下省略

このようにFeedItemモデルには :contentというプロパティに対してpolymorphicを設定しています。

content というモデルは実際には存在しませんが、独自のモジュールを作成し、これをモデルにincludeすることで実現しています。

models/post.rb

class Post < ApplicationRecord

  # フィード表示用
  include FeedContent

# 以下省略


FeedContentモジュールでは以下の様なリレーションが定義されているので、feed_itempostのリレーションができます。

has_one :feed_item, as: :content

以下はfeed_itemのデータ形式です。

紐付けたいモデルのレコードを作成後、そのレコードに対してmoduleのメソッドを実行することでFeedItemモデルのレコードを作成します。

display_type

以下の様にFeedItemモデルに定義してあるenumの値が入ります。(一部抜粋)

enum display_type: {
  post: 1, # 投稿
  recruitment_post: 4, # 募集
  community_post: 5, # コミュニティ投稿
}

content_id

関連するモデルのid。

例えばPostモデルのフィードアイテムなら、その投稿のレコードのidが入る

content_type

クラス名が文字列で入る

このようにクラス名とidが格納されることにより、feed_itemのレコードを見るだけでどのモデルのどのレコードかが分かります。

呼び出し方

フィードアイテムのidさえ分かれば紐づいているさまざまなモデルのレコードを取得できます。

feed_item = FeedItem.find(1)
puts feed_item.content

=> #<RecruitmentPost
 id: 1,
# 以下省略

YOUTRUSTではこのような使い方で様々なリソースをタイムラインへ表示しています。

最後に

このような複雑なリレーションは最初は理解が難しかったですが、調べていくうちに非常に便利な機能であることがわかりました。

入社して2ヶ月が経ちましたが毎日新しい発見や学びがあり、できることが増えていくのを実感しています。

これからもさらなる成長を目指して頑張っていきます!


また、YOUTRUSTではさまざまなポジションでメンバーを募集しています。もしご興味があれば、ぜひお気軽にお話ししましょう! herp.careers