⭐️コメント投稿、削除機能のajax化
前回に続き、ajax化
今回はコメント投稿、削除機能をajax化していきます!
(コメント編集機能も実装しました)
手順
1 . コメントコントローラを修正
2 . コメント作成、削除処理をajax化
3 . コメント作成、削除時の動的レンダリング処理を追加
1 . コメントコントローラを修正
・上記create
アクションに記載していた下記を削除し、コメント削除処理を追加。
if @comment.save redirect_to board_path(@comment.board_id), success: t('defaults.message.created', item: Comment.model_name.human) else redirect_to board_path(@comment.board_id), danger: t('defaults.message.not_created', item: Comment.model_name.human) end
・定義したdestroy
アクションをルーティングに追加します。
2 . コメント作成、削除処理をajax化
・form_with
のlocal: true
を削除して非同期処理にします。
(form_withはデフォルトでremote: true
の設定になっているため記述しなくても良い)
・コメントの削除ボタンlink_to
にremote: true
を追加します。
3 . コメント作成、削除時の動的レンダリング処理を追加
・コメントの追加に成功した場合、追加したコメントをコメント一覧に追加する処理をjavascriptで実装します。
・また、コメントの追加に失敗した場合は、エラーメッセージを表示するようにします。
・app/views/comments/create.js.erb
$("#error_messages").remove() <% if @comment.errors.present? %> $("#new_comment").prepend("<%= j(render('shared/error_messages', object: @comment)) %>") <% else %> $("#js-table-comment").prepend("<%= j(render('comments/comment', comment: @comment)) %>") $("#js-new-comment-body").val('') <% end %>
・コメントの追加と同様に、コメントの削除した時にコメントリストから対象のコメントを取り除きます。
・app/views/comments/destroy.js.erb
$("tr#comment-<%= @comment.id %>").remove();
✅総括
ajax化を実装する流れは頭に入ってきましたが、
手順3のコメント作成、削除時の動的レンダリング処理の実装での、
Javascript
の記法に前回に引き続き慣れないので、Javascript
の記法さえ頭に入れられればスイスイコードが書けそう。
Railsでは今後もJavascript
も使うことがたくさんありそうなので、ajax化する時にまた復習しながら進めていきたいです!
⭐️ブックマークボタンのajax化
◉Ajaxとは
Ajaxとは、Webブラウザ上で非同期通信を行い、ページ全体の再読み込み無しにページを更新する方法のことです。
同期通信
同期通信では、クライアントはwebページ全体の情報(HTMLとそれに紐づくcss,js,imageなどのアセット)をサーバーから受け取って、ページを一から作り直します。
例えばページの一部を変更するだけなのに、他の部分も組み立て直すってことはその分ページの表示に時間がかかっちゃいます。(サーバー側の処理を待つことになる)
しかも、このリクエスト〜レスポンスの処理を行っている間は、他の処理を行わずにサーバーからレスポンスが返ってくるのを待ち続ける必要があります(よくあるのが画面が真っ白になって何もできない状態)。
そこでAjaxのような非同期通信を使用すれば、ページ遷移無しに、高速で更新処理を行い、尚且つ、リクエスト〜レスポンスの処理を行っている間も他の処理が行えます。
非同期通信の方法は2種類
この便利なAjaxによる非同期通信を行う方法としては、
①remote:true形式
②ajax関数を使った形式
今回はremote: true形式を使ってajax化していきます。
✅手順
1 . ブックマークコントローラを修正
2 . ブックマークボタンをajax化
3 . ブックマークボタンの切り替え処理を追加
・1 . ブックマークコントローラを修正
redirect先の指定
をコメントアウト
・2 . ブックマークボタンをajax化
ブックマークボタンのコードを記載している
app/views/boards/_bookmark.html.erb
app/views/boards/_unbookmark.html.erb
の
<%= link_to 略 %>
に
remote: true
を追加。
# app/views/boards/_bookmark.html.erb <%= link_to bookmarks_path(board_id: board.id), id: "js-bookmark-button-for-board-#{board.id}", class: 'float-right', method: :post, remote: true do %> <%= icon 'far', 'star' %> <% end %>
3 . ブックマークボタンの切り替え処理を追加
上記の
id: "js-bookmark-button-for-board-#{board.id}"
を使って、下記viewファイルを作成。
# app/views/bookmarks/create.js.erb $("#js-bookmark-button-for-board-<%= @board.id %>").replaceWith("<%= j(render('boards/unbookmark', board: @board)) %>");
# app/views/bookmarks/destroy.js.erb $("#js-bookmark-button-for-board-<%= @board.id %>").replaceWith("<%= j(render('boards/bookmark', board: @board)) %>");
replaceWith
は要素を置換するメソッド。
$(置換対象).replaceWith(置換後の要素)
パーシャルを読み込んで、置換している
j()
はescape_javascriptのエイリアス
🟢総括
今回の課題を通して、Ajaxとは何かが分かりました。
自分が日頃、身近で使っているアプリでもよく搭載されているので、
次の課題を通してAjaxについてもっと理解を深めていきたいと思いました。
また、Javascriptの記法に対しても理解を深めていきたいです。
⭐️ブックマーク機能の追加
◉モデルのアソシエーション
uniqunessヘルパーのscopeオプションにboard_idを指定することで、
各掲示板(board_id)別にユーザー(user_id)との関係性を一意にすることができる
validates :user_id, uniqueness: { scope: :board_id }
この記載によって、
「1ユーザー」が「1つの掲示板」に対して「1ブックマーク」という範囲を限定した一意チェックができる
・app/model/user.rb
has_many :bookmark_boards, through: :bookmarks, source: :board
ユーザーモデルは複数のブックマークされた掲示板(bookmark_boards)を持っている。
through
オプションでbookmarkテーブルを経由してuserモデルとアソシエーションを作成。
bookmark_boardsは仮のテーブル名なので、source
オプションで参照するテーブルを指定している
・has many :throughオプションで関連付けすると何がいいのか
ユーザーがお気に入りした掲示板を直接取得することができるようになる!
ユーザーがお気に入りした掲示板の一覧を取得するときに使う。
app/controller/boards_controller
・collection
オプションを使うことでboardsリソースの中にbookmarks
アクションを追加できる、この時bookmarks_boards_urlやbookmarks_boards_pathといったルーティングヘルパーも使えるようになる。
ほぉ、、、collection
オプションって便利🤔✨
◉コントローラー、もろもろの設定の流れ
・bookmarks_controllerを生成する
・bookmarkする処理はモデルに定義する
・自分が作った掲示板かを判定するメソッドをモデルに定義する(定義済)
・bookmarkしてるかを判定するメソッドをモデルに定義する
・bookmarks_controllerのcreateアクション、destroyアクションを定義する
・bookmarksアクションを定義する
◉ブックマークの処理
・ブックマークの処理はモデルに記載するらしい!
理由はコントローラーに記載すると可読性が落ちるため
上記、補足※ブックマークのメソッドはrailsの7つのアクションと違う名前で定義するから
🟢総括
今回はブックマーク(お気に入り機能)の実装でしたが、ブックマーク機能はモデルにメソッドの定義をしたり、今までのUser、Board、Commentモデルに加えての実装だったので、各モデルとのアソシエーションやルーティング関係が特に難しかったです。
これから課題をこなすにあたって、include
だったり、path
などのヘルパーメソッドについても、これから深く勉強していかないとダメだなと感じました。
⭐️中間テスト アウトプット
・git clone
git clone クローンしたいurl
でclone。
cloneしたらcd
コマンドでclone先のディレクトリに移動して作業開始。
・bundle exec rspec spec
でバグの内容を1つずつ確認しながらバグを修正していく。
ちなみに今回のバグ内容は下記。
rspec ./spec/system/blogs_spec.rb:18 # Blogs blog一覧から詳細ページにアクセスした場合 blogの詳細ページが表示されること rspec ./spec/system/blogs_spec.rb:29 # Blogs blog詳細ページでコメントした場合 blogの詳細ページにコメントが表示されること rspec ./spec/system/blogs_spec.rb:44 # Blogs blog詳細ページでコメントを削除した場合 blogの詳細ページからコメントが削除されること rspec ./spec/system/blogs_spec.rb:62 # Blogs blog詳細ページで編集画面へのリンクをクリックした場合 blogの編集ページが表示されること rspec ./spec/system/blogs_spec.rb:72 # Blogs blog編集ページにアクセスした場合 blogの編集用フォームが表示されること rspec ./spec/system/blogs_spec.rb:80 # Blogs blog編集ページにアクセスした場合 blogの編集用フォームに編集前のblog情報が表示されること rspec ./spec/system/blogs_spec.rb:96 # Blogs blog新規作成ページにアクセスした場合 blogの新規作成ページが正しく表示されていること rspec ./spec/system/blogs_spec.rb:103 # Blogs blog新規作成ページにアクセスした場合 blogの新規作成ができること rspec ./spec/system/blogs_spec.rb:112 # Blogs blog新規作成ページにアクセスした場合 blogの新規作成でcontentも正しく作成できること rspec ./spec/system/blogs_spec.rb:124 # Blogs blog一覧ページでblogを削除しようとした場合 blogが一覧ページから削除されること
今回はサクサク進められたので、自分の手が止まってしまったバグだけ復習していきます。
・rspec ./spec/system/blogs_spec.rb:112 # Blogs blog新規作成ページにアクセスした場合 blogの新規作成でcontentも正しく作成できること
上記エラーでは、まず
1 . blogを作成した時にサーバーのログでparameterにどこまで送った情報が入っているかを確認しました。
結果、
titleはparameterに入っていて、
contentだけ入っていない状態だったので、
blogを作成して保存する動作である
BlogsController
のcreate
アクションを確認。
2 . 上記で@blog
がどこまで持ってこられているかをbinding.pry
を使って検証。
すると、contentの値がnil
だったので、
3 . @blog = Blog.new(blog_params)
の(blog_params)
の内容を確認。
permitの引数に:content
がなかったので追加。
def blog_params params.require(:blog).permit(:title, :content) end
ただ、contentにどんな文字を入力しても0
になって出力されるので、この0
はどこから来てるんだ??と疑問🤔
もしかしたらデータベースが怪しい?と感じたので、db/schema.rb
を確認。
すると、blogsテーブルのcontentのデータ型が、integer
になっている!
なので、マイグレーションファイルを新規作成してcontentのデータ型を変えることに。
・$ rails g migration ChangeDatatypeカラム名Ofテーブル名
・$ rails g migration ChangeDatatypeContentOfBlogs
※カラム名、テーブル名の頭文字は大文字
・生成されたファイルを編集
change_column :テーブル名, :カラム名, :データ型
最後に、rails db:migrate
した後しっかりrails db:migrate:status
してマイグレーションファイルがしっかりupされているか確認して、contentが文字列を表示してくれたことで解決!✨
🟢中間テスト総括
解説動画なしでほぼほぼ進められたので今後もこの調子で頑張る!
エラーログと
binding.pry
を使って、
どこまで変数を持ってこられているかを調査することで
問題の切り分けをし、 バグを1つずつ解決していけるんだな、
と今回の課題で再認識出来ました。
Railsチュートリアルの時は、エラーが起きた時にあれこれコードを書いて自分が書いたコードがどれか分からなくなり、沼にハマってしまっていたので、今後はそうならないように問題の切り分けをしっかりと行いながら開発を進めていきたいです!💪
⭐️掲示板の編集、削除機能の実装
・Userモデルに記載していた
def own?(object) id == object.user_id end
を利用して、自分が作成した掲示板のみ、編集、削除ボタンを表示させるように実装。
・app/views/boards/_board.html.erb
※重要
boards_controller.rb # Bad @board = Board.find(params[:id]) ※ この記載では、URL入力時のIDを変えると自分以外が作成した掲示板を対象にできてしまう。 このアプリは他の人の掲示板を見られる仕様のため、showアクションではBoardから取得させても問題ない。 しかし、editやupdate, destroyアクションでは、他人の作成した掲示板を変更できないように実装。 # Good @board = current_user.boards.find(params[:id]) ※ current_user.boardsによって、対象のユーザーに関連する掲示板の集合が取得できる。
def destroy @board.destroy!
!
を使っている理由は、削除処理は「必ず成功するもの」だから。
save
save!
は、処理が失敗したときの挙動が違います。 前者はfalse を返し、後者は例外を返します。
例えば、掲示板作成でタイトルを入力漏れし、falseが帰ってきたら、エラー表示箇所を訂正してまた作成を試みます。掲示板作成は「失敗する可能性がある」処理です。
一方、削除の処理は失敗する余地がない処理です。この処理が失敗したときは、意図的に処理を止めてデバッグが必要になります。
🟢総括
今回の課題で、
・モデルにメソッドを定義することで条件変更時にモデルメソッド1箇所の変更で済む保守性が良くな流ことが分かった。
・破壊的メソッド!
の使い方をよく理解できた!🔥
⭐️三項演算子について補足
・解答例ではapplication_helper.rbにて、
page_title.empty? ? base_title : page_title + " | " + base_title
上記のように条件式(.empty?) ? trueの処理 : falseの処理と言う記載を使って1行で済ませている。
⭐️タイトルを動的に出力する
・動的に出力とは?
「状況に合わせて選択できたりする柔軟性」
🟢実装方法
1 . app/views/layouts/application.html.erbのheadタグ内にyield文を記載。
<title><%= page_title(yield(:title)) %></title>
2 . app/helpers/application_helper.rbでpege_titleメソッドを定義。
page_titleの引数にはデフォルト値として空文字を指定。 これにより引数が渡されなかったとしても空文字(””)がpage_titleに代入され、メソッド内の演算で使用される。
そして、三項演算子によって、引数が渡されていなければ「RUNTEQ BORAD APP(base_title)」、引数が渡されていれば「引数の文字(page_title) |RUNTEQ BORAD APP」を返す。
三項演算子によって、if文の
if page_title.empty? base_title else page_title + " | " + base_title end
を
page_title.empty? ? base_title : page_title + " | " + base_title
このように一行で記載できる。
3 . あとはそれぞれのビューで、
<% content_for(:title, '渡したい文字列') %>
上記を記載するだけ!
これにより、'渡したい文字列'が共通レイアウトのページにあるyeildに渡され、page_titleメソッドの引数が'渡したい文字列'となり、成果物のようにタイトルが「「Access | AAA 電機」(ページ内容|渡したい文字列 )」などとなる。
☀️動的であるメリット
タイトルを変更する際、タイトル新しく変更しなければなりません。
"ヘルパーメソッドのbase_titleに入れる文字を変える"だけで、content_forが使われる他の全ページのタイトルを変えられる。
app/helpers/application_helper.rb
module ApplicationHelper def page_title(page_title = '') # 変更箇所 ----------- base_title = 'BBB 電機' # -------------------- page_title.empty? ? base_title : page_title + " | " + base_title end end