0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

非同期のBookmark機能にトライ

Last updated at Posted at 2022-10-17

モデルの作成

まずは rails g model Bookmark でBookmarkモデルを作成。

rails g model Bookmark

rails db:migrate をするその前に、マイグレーションファイルを編集しましょう!

class CreateBookmarks < ActiveRecord::Migration[6.1]
  def change
    create_table :bookmarks do |t|
      t.references :user, foreign_key: true, null: false
      t.references :post, foreign_key: true, null: false
      t.timestamps
    end
  end
end

user=Bookmarkしたユーザー
post=Bookmarkされた投稿

マイグレーションファイルを編集し終わったらrails db:migrateを忘れずに!

rails db:migrate

アソシエーション

UserモデルのidやPostモデルのidと関連付けをしていきましょう!

☆Bookmarkモデル

app/models/bookmark.rb
class Bookmark < ApplicationRecord
  belongs_to :post
  belongs_to :user
end

☆Postモデルにも関連付けが必要

app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :bookmarks, dependent: :destroy
  
  def bookmarked_by?(user)
    bookmarks.where(user_id: user).exists?
  end
end

①dependent: :destroyは親(ここでいう投稿=post)が削除されると、関連する子(bookmark)も全部消しますよーという記述。
②bookmarked_by?メソッドってなに??
このメソッドで、引数で渡されたユーザーidがBookmarksテーブル内に存在(exists?)するかどうかを調べます。
存在していればtrue、存在していなければfalseを返すようにしています。その結果、
存在しない=「まだBookmarkしていない→createへ」
存在する=「すでにBookmark済み→destroyへ」の流れとなります。

☆最後にUserモデルにも関連付け追加

app/models/user.rb
class User < ApplicationRecord
   :
  has_many :posts, dependent: :destroy
  has_many :bookmarks, dependent: :destroy
end

これで各モデルへのアソシエーションの記述は終わりです!

ルーティング

routes.rb
resources :posts  do
      resource :bookmarks, only: [:create, :destroy]
end

さて次はルーティングに行きましょう。
「どの投稿をBookmarkしたのか」判別できないといけないため、posts コントローラーにネストします。
ここで「あれ、resourcesじゃなくてなぜBookmarkはresourceなの?」と思いませんでした?
resource、つまり単数形にすると「/:id」がURLにつかなくなります。
Bookmark機能は、1投稿に対して1ユーザーが1回しかBookmarkできませんよね?
つまりBookmark対象の投稿(post)idとユーザーidとがわかっていれば、
BookmarkのidはURLに含める必要がない、つまりparams[:id]を使わなくても良いため
resourceの単数形でURLに/:idを含めないようになっています。

つまりresourceは「それ自身のidが分からなくても、関連する他のモデルのidから特定できる」
という場合に使うことが多いと認識しておきましょう。

結果出来上がったルーティングはこうなります。

post_bookmarks  DELETE /posts/:post_id/bookmarks(.:format)     bookmarks#destroy
                POST   /posts/:post_id/bookmarks(.:format)     bookmarks#create

コントローラー

次にBookmarkのコントローラーを作成します。複数形で作成するのを忘れずに。

rails g controller bookmarks

コントローラーを作成したらアクションを定義しましょう。

app/bookmarks_controller.rb
class Public::BookmarksController < ApplicationController
  before_action :authenticate_user!

  def create
    @post = Post.find(params[:post_id])
    bookmark = current_user.bookmarks.new(post_id: @post.id)
    bookmark.save
  end

  def destroy
    @post = Post.find(params[:post_id])
    bookmark = current_user.bookmarks.find_by(post_id: @post.id)
    bookmark.destroy
  end
end

Bookmrakは専用のviewは必要ないため、アクションはcreateとdestroyのみでよいです。
①でBookmarkする投稿のidを(params[:post_id])で取得します。
②post_idにはルーティングに記載してある「:post_id」を指定し、
current_user(ログイン中のユーザー)がBookmarkした投稿がわかるようにしています。

Viewの作成

app/view/posts/show.html.erb
<% if @post.bookmarked_by?(current_user) %>
    <%= link_to post_bookmarks_path(@post), method: :delete, remote: true do %> ②
    ★ <%= @post.bookmarks.count %> Bookmark解除
    <% end %>
<% else %>
    <%= link_to post_bookmarks_path(@post), method: :post, remote: true do %>
    ☆ <%= @post.bookmarks.count %> Bookmark
    <% end %>
<% end %>

さて、いよいよ大詰めです。
①のbookmarked_by?メソッドは、先ほどPostモデルで説明したメソッドです。
引数で(current_user)を渡すことで、ユーザーが存在していればtrue(destroyアクション)、
存在していなければfalse(createアクション)を返します。
②非同期通信にしたいので、remote: trueを入れることでjsファイルを探しに行ってくれます。

最後に非同期の実装

まずはjsファイルの作成が必要です。
bookmarksのviewの下にcreate.js.erbとdestroy.js.erbを作成しましょう。

app/views/bookmarks/create.js.erb
$('#bookmarks_buttons_<%= @post.id %>').html("<%= j(render "bookmarks/bookmarks", post: @post) %>");
app/views/bookmarks/destroy.js.erb
$('#bookmarks_buttons_<%= @post.id %>').html("<%= j(render "bookmarks/bookmarks", post: @post) %>");

注意点

コードを書き終えたものの、最初は非同期にならないどころか、Bookmarkボタンを押しても反応しませんでした。
ちゃんと合っていると思うけどなー、なにがダメなんだろう。。としばらく考えきづいたことが!!

 def create
    @post = Post.find(params[:post_id])
    bookmark = current_user.bookmarks.new(post_id: @post.id)
    if bookmark.save
      redirect_to request.referer
    else
      redirect_to request.referer
    end
 end

最初はこんな風にコードを書いていたんですね。
このif以下の条件分岐を消して、redirect_toを削除、
bookmark.saveだけにすると非同期で反応しました!

これでBookmark機能が完成しました。

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?