プログラミングのメモ帳

自分のメモとして残したいアウトプット

いいね機能のDB設計

よくある「いいね機能」のDB設計を考える。
ツイッターで言えばいわゆる「ファボ」機能。

前提条件

・UserモデルとPostモデルが存在する
・User has_many posts という関連付けが行われている

f:id:d4192:20190416190444p:plain ER図

Like Modelを作る

Postにいいねしたユーザー一覧を入れるという力技も出来そうですが
SQLアンチパターンで言う所の「ジェイウォーク」になりそうです。
カラムに複数の値を入れると将来運用上のデメリットしかありません。
なので定石ではありますが新しくLikeモデルを作成し、
1レコード=1つのLike
を表して、1カラムに1つの値だけが入るようにします。

f:id:d4192:20190416191124p:plain

外部キーを設定してLIkeモデルは必ずUser, Postと整合性が保たれるようにします。

$ rails g model Like user:references post:references
$ rais db:migrate

レコードの挿入と削除

あとはいつも通りActiveRecordを使ってSQLを発行するだけ。
ただLike専用のビューは作らないと思うのでuserやpostのビューから
効率的にアクセスする為に関連付けが必要

#models/user.rb
has_many :likes, dependent: :destroy
#models/post.rb
has_many :posts, dependent: :destroy

これで

User.find(1).posts.find(1).likes.count

などをしてlikeモデルにアクセスできる。
また、User, Postどちらが削除された時に紐付くLikeも削除されます。

一意性のバリデーション

Likeはuser_id属性とpost_id属性を持ちますが、
この2つの組み合わせは常に一意(unique)です。
なのでこの組み合わせに対して一意性のバリデーションを設定しておきます。

#models/like.rb
validates :user_id, uniqueness: { scope: :post_id }

なお、このバリデーションのエラーメッセージは通常のuniquenessのままなので
状況に合わせてmessageオプションで変更しておきましょう。

throughオプションを使う。

f:id:d4192:20190416191124p:plain

こちらの図のような関連付けが行われている時に
「あるuserがlikeしたpostを抽出したい」という場面があると思います。
そんな時は
1.Userに紐付くLikeを抽出し、selectメソッドを利用してpost_idだけを抜き出す
2.Post.where(post_id: ? )に1の結果を入れる

といったようなややこしいことをしないといけません。
そこでthroughオプションを使って簡潔にアクセスできるように変更します。

#/app/models/user.rb
has_many :liked_posts, through: :likes, source: :post

上記の記述でlikesを通してpostへアクセスする
liked_postsというメソッドが出来上がります。
これで新しいメソッドliked_postsが使えます。

@user.liked_posts

とするだけでuserがlikeしたpostオブジェクトが返ってきます。

Controllerへの実装

controllerへの実装は他のモデルと同じようにすれば大丈夫ですね。
ルーティングはlikes#createとlikes#destoryだけで良いのではないでしょうか。