Rails ブックマークの実装part1

実装内容

  • 掲示板の☆ボタンを押すと、その掲示板をブックマーク/解除出来る機能を作成。
  • ブックマークすると★解除すると☆になるようにし、フラッシュメッセージも表示させる。
  • 同じユーザが同じ掲示板を複数回ブックマークできないようにする。
  • ブックマークの一覧ページを作成する。

実装の流れ

  • 中間モデルの定義
  • ルーティングを作成
  • bookmarkコントローラの実装
  • viewの実装

中間モデルの定義

userモデルとboardモデルの間にbookmarkモデルを作成する。
userモデル

id user
1 meo

bookmarkモデル←中間モデル

id user board
1 meo abcd

boardモデル

id board
1 abcd

user board bookmarkの関係

userが掲示板をブックマークするとその数だけ掲示板はブックマークを持っている。
つまり、userとboardはbookmarkを持っているので多対多の関係であると言える。

中間モデルについて

多対多である中間モデル(bookmark)には、外部キー制約(foreign_key)を付ける必要がある。
どのuserがどのboardをブックマークしたかuser_id,board_idが保存される。
bookmarkモデル

id user board
1 user_id board_id

bookmarkモデルを作成

外部キーをつけたいのでreference型を使い作成する。
紐づくモデル名+「_id」で外部キーと呼ぶ。
今回はuser,boardと紐付けるので以下のコマンドを実行する。

$ rails g model Bookmark user:references board:references
Running via Spring preloader in process 5265
      invoke  active_record
      create    db/migrate/20210603064703_create_bookmarks.rb
      create    app/models/bookmark.rb

同じユーザが同じ掲示板を重複してブックマークできないようにしたいので、 作成されたマイグレーションファイルへ以下の記述を追加する。
t.index [:user_id, :board_id], unique: true
unique:trueでテーブル内で重複するデータを禁止する一意性制約をつける。
マイグレーションを実行。

$ rails db:migrate
== 20210603064703 CreateBookmarks: migrating ==================================
-- create_table(:bookmarks)

各モデルのアソシエーション

  belongs_to :user
  belongs_to :board
  validates :user_id, uniqueness: { scope: :board_id }

データベース側に一意性制約をつけたが、モデル側にも同様に記述する。
uniquenessでオブジェクトが保存される前に属性の値が重複してないことを検証する。

  has_many :boards, dependent: :destroy
  has_many :bookmarks, dependent: :destroy
  has_many :bookmark_boards, through: :bookmarks, source: :board

親(user)を削除したら、一緒に子(booksmark)も削除されるように
dependent: :destroyを追加。

has_many :bookmark_boards, through: :bookmarks, source: :board

について
boardsは既にあるのでbookmark_boardsを定義し、ユーザがブックマークしている掲示板を取得する。
through: :bookmarks, source: :boardでbookmarksを通してboardモデルから掲示板を取得している。

 has_many :bookmarks, dependent: :destroy
 belongs_to :user
同じく、親(board)を削除したら、一緒に子(bookmarks)も削除されるように
dependent: :destroyを追加。

userモデルにbookmarkしたか判定するメソッドを追加

コントローラにも記述できるが、モデルに書くのが一般的。

#ブックマーク追加
  def bookmark(board)
    bookmark_boards << board
  end
<<で引数で渡したboardレコードが先程has_many throughで定義したbookmark_boardsに保存される。

#ブックマーク外す
  def unbookmark(board)
    bookmark_boards.destroy(board)
  end

bookmark_boardsから引数のboardレコードを削除する。

#ブックマークしてるか判定するメソッド
  def bookmark?(board)
     bookmark_boards.include?(board)
  end
includes?メソッドで引数のboardレコードが含まれてたらtrue、なければfalseを返す。
part2へ