Railsで多対多のお気に入り機能を作成する
もう何度も作成していますが、未だにややこしく、ググったりしているので一度まとめようと思いました。
まず、小文字大文字、複数形などが整理できていないときは確認する。
中間テーブルの作成
rails g model Subscribe user:references book:references
今回はbooksテーブルとそれを登録する(各自お気に入り登録でも)usersテーブル
そしてそれの中間テーブルであるsubscribesテーブルを作成する。
(ここでは既にbooks、usersテーブルは作成済みとしている)
userとuserなど同じモデル同士で中間テーブルを作成するときは、
rails g model Subscribe user:references friend:references
などとし、migrationファイルには
t.references :friend, foreign_key: { to_table: :users }
とする。
上記を実行した後、migrationファイルに下記を追加する。
t.index [:user_id, :book_id], unique: true
これはuser_idとbook_idの重複を防ぐため。
rails db:migrate
そして実行。
各modelファイルの編集
user.rbに下記を追加
has_many :subscribes #中間テーブルsubscribesに属するという意味 has_many :books, through: :subscribes #中間テーブルsubscribesを通してbooksに繋がっているという意味
ここで注意しなければいけないことがあって、既に別のhas_many: :booksがある場合は別の名前にしてsourceで指定する必要がある。 例
has_many :subbooks, through :subscribes, source: :book #source: :bookのbookはモデルを参照するので小文字単数形にする
book.rbに下記を追加
has_many :subscribes, foreign_key: 'book_id', dependent: :destroy #中間テーブルに属するという意味、foreign_keyはbooksにあるIDしか入らないという制約をかける #dependentはuser_idが仮に削除されたらbook_idを削除しますということ(紐づくオブジェクトを削除するということ) has_many :users, through: :subscribes #ここでも既に同じ名前のhas_manyがあれば仮の名前をつけてsourceで指定すること
メソッドを作成する
user.rbにメソッドを記述していく。
#登録メソッド def addsub(book) subscribes.find_or_create_by(book_id: book.id) end #登録解除メソッド def removesub(book) subscribe = subscribes.find_by(book_id: book.id) subscribe.destroy if subscribe end #確認メソッド def checksub?(book) self.books.include?(book) end #self.booksのbooksはhas_manyで定義したものなので、subbooksとかになってたらそうする。
この段階でrails cで動作確認をすると良い。
コントローラーの作成
rails g controller subscribes create destroy
登録機能と登録解除機能を付ける。
def create book = Book.find(params[:book_id]) current_user.addsub(book) flash[:success] = "登録完了しました!" redirect_back(fallback_location: root_path) end def destroy book = Book.find(params[:book_id]) current_user.removesub(book) flash[:success] = "登録解除しました!" redirect_back(fallback_location: root_path) end
ルーティング
routes.rbに記述
resources :subscribes, only: [:create, :destroy]
ビュー
ここは各自用途が変わると思うので、最低限のボタンを作成してみる。
<% if current_user.checksub?(book) %> <%= form_with(model: current_user.subscribes.find_by(book_id: book.id), local: true, method: :delete) do |f| %> <%= hidden_field_tag :book_id, book.id %> <%= f.submit '登録解除', class: 'btn btn-danger' %> <% end %> <% else %> <%= form_with(model: current_user.subscribes.build, local: true) do |f| %> <%= hidden_field_tag :book_id, book.id %> <%= f.submit 'お気に入り登録', class: 'btn btn-primary' %> <% end %> <% end %>
最低限の機能ですが、以上です。