Programming Journal

学習したことの整理用です。

掲示板にお気に入り機能を実装する①

今回は難しくてかなり詰まってしまいました。復習します。

今回実装したいこと

  • 掲示板を「お気に入り」すると★マーク、「お気に入り解除」すると☆マークに変わる。

  • 「お気に入り」「お気に入り解除」 それぞれで、フラッシュメッセージを表示する。

  • 「お気に入り」した掲示板を一覧で表示するページ作成する。

実装の流れ

  • モデルの設定
  • ルーティングの設定
  • コントローラーの設定
  • ビュー側の設定

モデルの設定

  • Userモデル、Boardモデルの中間モデル(Bookmark)を作成する
  • 同じ人が、複数回「お気に入り」できないようにする

中間モデルとは

多対多の関係は2つのモデルでは実現できません。

なくても設計はできますが、使用しないカラムが出てくるなど、良くない設計になるためNG

そのため、どのユーザーがどの掲示板にお気に入りしたかを保存する「中間モデル」を別に用意します。

ここでは、user_idとboard_idを保存するBookmarkモデルを作成します。

この中間モデルは、それぞれのidのみを保存しています。このidを使って(through)、各データを取ってきます。

このテーブルを経由して、掲示板のデータを取得している。実際に取得しているのは、boardsテーブルのレコード

Bookmark
user_id
board_id

それでは順番に実装していきます。

外部キー制約をつけてBookmarkモデルを生成する

$ rails g model Bookmark user:references board:references`
class CreateBookmarks < ActiveRecord::Migration[5.2]
  def change
    create_table :bookmarks do |t|
      t.references :user, foreign_key: true
      t.references :board, foreign_key: true

      t.timestamps
      t.index [:user_id, :board_id], unique: true #追記する
    end
  end
end

同じユーザーが、同じ掲示板に複数回「お気に入り」をすることを防ぐため、

t.index [:user_id, :board_id], unique: true 

を追記します。user_idboard_idの組み合わせがuniqueだよってこと。

OKだったら、$ rails db:migrate

更に、DB側だけではなくモデル側にも同様の内容を追記していきます。

class Bookmark < ApplicationRecord
  belongs_to :user
  belongs_to :board
  validates :user_id, uniqueness: { scope: :board_id } #追記
end

uniquenessこのヘルパーは、オブジェクトが保存される直前に、属性の値が一意(unique)であり重複していないことを検証します。このヘルパーは一意性の制約をデータベース自体には作成しないので、本来一意にすべきカラムに、たまたま2つのデータベース接続によって同じ値を持つレコードが2つ作成される可能性が残ります。これを避けるには、データベースの両方のカラムに一意インデックスを作成する必要があります。 このヘルパーには、一意性チェックの範囲を限定する別の属性を指定する:scopeオプションがあります。:scopeを用いる一意性バリデーションの違反を防止する目的でデータベース側に制約を作成したい場合は、データベース側で両方のカラムにuniqueインデックスを作成しなければなりません。

Active Record バリデーション - Railsガイド

各モデルにアソシエーションを設定していく

  has_many :boards, dependent: :destroy
  has_many :comments, dependent: :destroy
  has_many :bookmarks, dependent: :destroy #追記
  has_many :bookmark_boards, through: :bookmarks, source: :board #追記

post.rbも同様に設定しておく。
分かりにくい場所を見ていきます。

has_many :bookmarks, dependent: :destroy #追記

userは多数のbookmarksを持っている。

userが削除したら、一緒にbooksmarksも削除される。


has_many :bookmark_boards, through: :bookmarks, source: :board #追記

バラバラにして読み解くと、

has_many :bookmark_boards,

Userモデルは、多数のbookmark_boardsを持っている

bookmark_boardsとは??本当は、boardsが使いたかったけど、既に一行目で使ってしまっているので、それらしい名前を付けている

through: :bookmarks, bookmarksモデルを通して、

source: :board

boardモデルからデータを参照する

4.3.2.9 :source :sourceオプションは、has_many :through関連付けにおける「ソースの」関連付け名、つまり関連付け元の名前を指定します。このオプションは、関連付け名から関連付け元の名前が自動的に推論できない場合以外には使う必要はありません。

Active Record の関連付け - Railsガイド

has many :throughオプションで関連付けすると何がいいのか

ユーザーがお気に入りした掲示板を直接取得することができるようになる!

コントローラー設定のときに詳しく解説しますが、こんな風にお気に入りした掲示板情報を取得できる

今回、ユーザーがお気に入りした掲示板の一覧を取得するときに使います。

 def bookmarks
    @bookmarks_boards = current_user.bookmark_boards.includes(:user).order(created_at: :desc)
  end
アソシエーションの分かりやすい記事

【Rails】アソシエーションを図解形式で徹底的に理解しよう! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】🎸 - Qiita

Rails 6.0 対応でプログラミング学習 - Railsチュートリアル

Active Record の関連付け - Railsガイド