【超解説】 Rails で多態性アソシエーションを使いこなす:外部キー制約、STI、その他の方法
Ruby on Rails における多態性アソシエーションと外部キー制約
外部キー制約は、データベースで関連レコード間の整合性を維持するために使用される制約です。多態性アソシエーションでは、関連レコードがどのモデルに属しているのかを明確に特定できないため、外部キー制約を設定することができません。
例:
Comment
モデルと Post
モデル、Article
モデルを想定します。Comment
モデルは、Post
または Article
のいずれかに関連付けられます。
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Article < ApplicationRecord
has_many :comments, as: :commentable
end
この場合、Comment
モデルの commentable_id
カラムには、関連する Post
または Article
の ID が格納されます。しかし、commentable_type
カラムには、関連するモデルの種類のみが格納されます。
問題点:
commentable_type
カラムだけでは、関連するモデルを特定できません。- 外部キー制約を設定できないため、データベースの整合性を維持できない可能性があります。
解決策:
多態性アソシエーションで外部キー制約を使用するには、**STI(Single Table Inheritance)**と呼ばれる手法を使用する必要があります。STI は、複数のモデルを単一のデータベーステーブルに格納する手法です。これにより、各レコードに type
カラムを追加し、関連するモデルを明確に特定することができます。
STI を使用する例:
class Commentable < ApplicationRecord
self.inheritance_column = :type
end
class Post < Commentable
end
class Article < Commentable
end
class Comment < ApplicationRecord
belongs_to :commentable
end
STI を使用することで、外部キー制約を設定することができます。
- 多態性アソシエーションでは、外部キー制約を設定できない場合があります。
- STI を使用することで、多態性アソシエーションで外部キー制約を使用できます。
Ruby on Rails で STI を使用した多態性アソシエーションと外部キー制約:サンプルコード
モデル
class Commentable < ApplicationRecord
self.inheritance_column = :type
end
class Post < Commentable
validates :title, presence: true
validates :body, presence: true
end
class Article < Commentable
validates :title, presence: true
validates :body, presence: true
end
class Comment < ApplicationRecord
belongs_to :commentable
validates :body, presence: true
end
マイグレーション
rails generate migration create_commentables
rails migrate
create_commentables
マイグレーションは次のようになります。
class CreateCommentables < ActiveRecord::Migration[6.1]
def up
create_table :commentables do |t|
t.string :type, null: false
t.string :title
t.text :body
t.timestamps
end
end
def down
drop_table :commentables
end
end
使い方
post = Post.create(title: "My First Post", body: "This is my first post.")
comment = post.comments.create(body: "Great post!")
article = Article.create(title: "My First Article", body: "This is my first article.")
another_comment = article.comments.create(body: "Interesting article!")
puts comment.commentable.title #=> "My First Post"
puts another_comment.commentable.title #=> "My First Article"
このコードは、以下のことを示しています。
Post
とArticle
モデルは、Commentable
モデルから継承しています。Comment
モデルは、Post
またはArticle
のいずれかに関連付けられます。- STI により、
Commentable
テーブルにはtype
カラムが追加されます。 - 外部キー制約により、
Comment
レコードは常に有効なCommentable
レコードに関連付けられます。
この例は、多態性アソシエーションと STI を使用して、Rails で外部キー制約を設定する方法を示す基本的なものです。実際のアプリケーションでは、より複雑なモデルとアソシエーションを使用する可能性があります。
Ruby on Rails で多態性アソシエーションと外部キー制約を実現するその他の方法
has_many :through
アソシエーションは、中間テーブルを使用して多対多関係を定義する方法です。この方法では、外部キー制約を定義するために中間テーブルにカラムを追加することができます。
class Tag < ApplicationRecord
has_many :taggings
has_many :posts, through: :taggings
end
class Post < ApplicationRecord
has_many :taggings
has_many :tags, through: :taggings
end
class Tagging < ApplicationRecord
belongs_to :tag
belongs_to :post
end
この例では、Tagging
という中間テーブルが使用されています。このテーブルには、tag_id
と post_id
という 2 つのカラムがあり、それぞれ関連する Tag
と Post
の ID を格納します。
カスタムバリデーションを使用して、関連レコードが存在するかどうかを確認することができます。
class Comment < ApplicationRecord
belongs_to :commentable
validate :commentable_must_be_present
private
def commentable_must_be_present
errors.add(:commentable, "must be present") unless commentable.present?
end
end
この例では、commentable_must_be_present
というカスタムバリデーションメソッドが定義されています。このメソッドは、commentable
アソシエーションが nil
でないことを確認します。
belongs_to
アソシエーションと optional: true
オプションを使用して、関連レコードがなくても有効なレコードを作成できるようにすることができます。ただし、この方法では外部キー制約を定義することはできません。
class Comment < ApplicationRecord
belongs_to :commentable, optional: true
end
この例では、commentable
アソシエーションに optional: true
オプションが設定されています。これにより、Comment
レコードは commentable
レコードに関連付けられていなくても作成することができます。
ruby-on-rails database foreign-key-relationship