はじめに
Twitterのように、1人のユーザーは複数のツイートにいいね!ができて、1つのツイートは複数のユーザーにいいね
!されるような多対多の関係をよく実装することはあると思います。
あるユーザーは1つのツイートに対して1回しかいいね!はしないので組み合わせは一意である必要があります。
今回は中間テーブルのデータの組み合わせを一意にする方法を説明します。
開発環境
Rails 6.0.3
Ruby 2.7.1
テスト: Rspec, FactoryBot, shoulda-matchers
1. テーブル
今回使用するテーブルは下記の3つです。
Userテーブル
id | name | |
---|---|---|
1 | ユーザー1 | a@a.a |
2 | ユーザー2 | b@b.b |
Tweetテーブル
id | content |
---|---|
1 | tweet1 |
2 | tweet2 |
Likeテーブル(中間テーブル)
id | user_id | tweet_id |
---|---|---|
1 | 1 | 2 |
2 | 1 | 3 |
多対多の関係
多対多の説明に関しては今回は省略します。
class User < ApplicationRecord
has_many :likes, dependent: :destroy
end
class Tweet < ApplicationRecord
has_many_to :likes
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :tweet
end
2. 組み合わせを一意にする方法
そして今回のメインに実装です。
やることは2つです。
- migrationファイルにadd_indexでunique制約をつける (DBに制約をつける)
- 中間テーブルのmodelにvalidationを追加する (アプリ側でバリデーションを追加)
2.1 migrationファイルにadd_indexでunique制約をつける
add_index :likes, [:user_id, :tweet_id], unique: true
を中間テーブルのmigrationファイルに追加します。
追加した後はmigrateを忘れないように!
class CreateLikes < ActiveRecord::Migration[6.0]
def change
create_table :likes do |t|
t.references :user, null: false, foreign_key: true
t.references :tweet, null: false, foreign_key: true
t.timestamps
end
add_index :likes, [:user_id, :tweet_id], unique: true #ここを追加
end
end
2.2 中間テーブルのmodelにvalidationを追加する
validates :hotel_id, uniqueness: { scope: :staff_id }
を中間テーブルのモデルに追加します。
class Like < ApplicationRecord
belongs_to :user
belongs_to :tweet
validates :user_id, uniqueness: { scope: :tweet_id } #ここを追加
end
必要な実装は以上です。
コンソールで同一の組み合わせは一度しか登録できないことを確認してみてください。
3. 一意であることを確認するRspecテスト
参考までにテストもご紹介します。
Rspec, FactoryBot, shoulda-matchersの設定はできているものとします。
require 'rails_helper'
RSpec.describe Like, type: :model do
let(:user) { create(:user) }
let(:tweet) { create(:tweet) }
before { create(:like, user: user, tweet: tweet) }
it { should belong_to(:user) } # この行と下の行で多対多の関係を確認
it { should belong_to(:tweet) }
it { is_expected.to validate_uniqueness_of(:user_id).scoped_to(:tweet_id) } # ここで一意であることを確認
end
おわりに
ちゃんと組み合わせを一意にできましたでしょうか?
今回のような実装はよく使われると思うので参考になれば幸いです。