Kayaのブログ

プログラミング学習用です

パスワードリセット機能の実装②

・letter_opener_webの使用

・'config' gemの使用

についてアウトプットしていく

 

 

○ letter_opener_webの使用

これもreset_password同様、Githubに沿って記述していく

letter_opener_webをインストールする

# Gemfile
group :development do
gem 'letter_opener_web', '~> 1.0'
end

下記の通り入力していく

# config/routes.rb
mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development
# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener_web
config.action_mailer.default_url_options = Settings.default_url_options.to_h

これでOK

 

 

○ 'config' gemの使用

'config' gemを用いることで、定数の管理をすることができる

これもGithubに従って進めていく

まずconfigをインストールする

# Gemfile
gem 'config', '2.0.0'

下記コマンドを実行する

bundle exec rails g config:install

これにより、カスタマイズ可能な構成ファイルconfig / initializers /config.rbと一連のデフォルト設定ファイルが生成されます。

config/settings.yml
config/settings.local.yml
config/settings/development.yml
config/settings/production.yml
config/settings/test.yml

 

それぞれのファイルの役割は以下の通り

  • config/initializers/config.rb configの設定ファイル
  • config/settings.yml すべての環境で利用する定数を定義
  • config/settings.local.yml ローカル環境のみで利用する定数を定義
  • config/settings/development.yml 開発環境のみで利用する定数を定義
  • config/settings/production.yml 本番環境のみで利用する定数を定義
  • config/settings/test.yml テスト環境のみで利用する定数を定義

基本的にはconfig/settings.ymlを編集していく(全ての環境で利用、だから)

環境ごとに定数を定義したい場合はそれぞれ対応するファイルを編集していけばよい

今回は開発環境、テスト環境に定数を定義したいためともに定義していく

# config/settings/development.yml
# config/settings/test.yml
default_url_options:
host: 'localhost:3000'

controllerやviewで、'Settings.~'で呼び出すことができる

# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener_web
config.action_mailer.default_url_options = Settings.default_url_options.to_h

先ほどletter_opener_webの説明にて、上記のような記述になったが、この

config.action_mailer.default_url_options = Settings.default_url_options.to_h

これがその呼び出しに該当する

config.action_mailer.default_url_options = { host: 'localhost:3000' }

定義せずこれでもいいけれど、あちこちでこのような記述をしているとごちゃごちゃになってしまうので、1つにまとめておいたほうが後で変更もできるし面倒にもならない

 

 

 

 

参考

https://github.com/fgrehm/letter_opener_web

https://qiita.com/Atelier-Mirai/items/3e272f23eda6b002e9ed

http://vdeep.net/rubyonrails-config-gem

https://github.com/rubyconfig/config

https://qiita.com/beanbeenzou/items/87fb89f73e1b9e6490e1

パスワードリセット機能の実装①

ポイントは大きく以下4点

・reset_passwordモジュールのインストール

・ActionMailerの定義

・letter_opener_webの使用

・'config' gemの使用

 

 

○ reset_passwordモジュールのインストール

sorceryのreset_passwordに従って進めていく

rails g sorcery:install reset_password --only-submodules

パスワードリセットのためのモジュールのインストールをする

下記のようなmigrationファイルが作成される

class SorceryResetPassword < ActiveRecord::Migration
def change
add_column :users, :reset_password_token, :string, default: nil
add_column :users, :reset_password_token_expires_at, :datetime, default: nil
add_column :users, :reset_password_email_sent_at, :datetime, default: nil
end
end

この時、sorceryの定義ファイルにpassword_resetサブモジュールを使用するための記述'reset_password'が追加されていることを確認する

# config/initializers/sorcery.rb
Rails.application.config.sorcery.submodules = [:reset_password]

○ ActionMailerの定義

Action Mailerを使うと、アプリケーションのメイラークラスやビューでメールを送信することができます。メイラーの動作はコントローラときわめて似通っています。メイラーはActionMailer::Baseを継承し、app/mailersに配置され、app/viewsにあるビューと結び付けられます。

Action Mailer の基礎 - Railsガイド 

rails g mailer UserMailer reset_password_email

sorceryの定義ファイルに、password_resetに使用するActionMailerとしてUserMailerを定義する

# config/initializers/sorcery.rb
Rails.application.config.sorcery.submodules = [:reset_password]

Rails.application.config.sorcery.configure do |config|
config.user_config do |user|
user.reset_password_mailer = UserMailer
end
end

メイラーはRailsのコントローラと非常に似通っています。メイラーには「アクション」と呼ばれるメソッドがあり、メールのコンテンツを構成するのにビューを使います。コントローラでHTMLなどのメールコンテンツを生成して顧客に送信したい場合、その箇所でメイラーを使って、送信したいメッセージを作成します。

reset_password_emailという名前のメソッドを定義し、userが登録したaddressにmailを送信できるようにする

# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
def reset_password_email(user)
@user = User.find(user.id)
@url = edit_password_reset_url(@user.reset_password_token)
mail(to: user.email, subject: t('defaults.password_reset'))
end
end

メイラービューを作成する

# app/views/user_mailer/reset_password_email.html.erb
<h1><%= @user.decorate.full_name %>様</h1>

<p>
パスワード再発行のご依頼を受け付けました。
こちらのリンクからパスワードの再発行を行ってください。
</p>
<p><a href="<%= @url %>"><%= @url %></a></p>
# app/views/user_mailer/reset_password_email.text.erb
<%= @user.decorate.full_name %>様

パスワード再発行のご依頼を受け付けました。
こちらのリンクからパスワードの再発行を行ってください。
<%= @url %>

password_resetを行うcontrollerを作成する

rails g controller PasswordResets create edit update
# app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
skip_before_action :require_login

# passwordリセット申請フォーム用のアクション
def new; end

# passwordリセットのリクエストアクション
# userがpasswordのリセットフォームにメールアドレスを入力して送信すると、ここに表示される
def create
@user = User.find_by(email: params[:email])

# password(ランダムトークンを含むURL)をリセットする方法を説明した電子メールをuserに送信する
# @user.deliver_reset_password_instructions! if @user と同じ意味
@user&.deliver_reset_password_instructions!

# 「存在しないメールアドレスです」という旨の文言を表示すると、逆に存在するメールアドレスを特定されてしまうため、
# あえて成功時のメッセージを送信させている
redirect_to login_path, success: t('.success')
end

# passwordリセットフォーム
def edit
@token = params[:id]
@user = User.load_from_reset_password_token(@token)

# @userがnilまたは空の場合、not_authenticatedメソッドを実行する
not_authenticated if @user.blank?
end
# userがpasswordのリセットフォームを送信した時に発生する
def update
@token = params[:id]
@user = User.load_from_reset_password_token(@token)

return not_authenticated if @user.blank?

@user.password_confirmation = params[:user][:password_confirmation]

if @user.change_password(params[:user][:password])
redirect_to login_path, success: t('.success')
else
render :edit
end
end
end

routing設定する

resources :password_resets, only: %i[new create edit update]

 

passwordを忘れた場合にpassword_reset申請用のフォームを作成する

# app/views/user_sessions/new.html.erb
<%= link_to t('.password_forget'), new_password_reset_path %>


# app/views/password_resets/new.html.erb
<% content_for(:title, t('.title')) %>
<%= form_with url: password_resets_path, local: true, method: :post do |form| %>
<div class=" col-md-10 offset-md-1 col-lg-8 offset-lg-2">
<h1><%= t '.title' %></h1>
<div class="form-group">
<%= form.label :email, User.human_attribute_name(:email) %><br />
<%= form.text_field :email, class: 'form-control' %>
</div>
<%= form.submit t('password_resets.new.submit'), class: 'btn btn-primary' %>
</div>
<% end %>

password_reset用のフォームを作成

 
# app/views/password_resets/edit.html.erb
<% content_for(:title, t('.title')) %>
<div class="container">
<div class="row">
<div class="col col-md-10 offset-md-1 col-lg-8 offset-lg-2">
<h1><%= t('.title') %></h1>
<%= form_with model: @user, url: password_reset_path(@token), local: true do |f| %>
<%= render 'shared/error_messages', object: f.object %>

<div class="form-group">
<%= f.label :email %>
<%= @user.email %>
</div>
<div class="form-group">
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
</div>
<div class="actions">
<p class="text-center">
<%= f.submit class: 'btn btn-primary' %>
</p>
</div>
<% end %>
</div>
</div>
</div>


・letter_opener_webの使用

・'config' gemの使用

は②でアウトプットします

 

 

参考

https://github.com/Sorcery/sorcery/wiki/Reset-password

https://railsguides.jp/action_mailer_basics.html

https://dev.classmethod.jp/articles/ruby-on-rails_sorcery_auth_no4/

https://qiita.com/ryota21/items/2f4084a8f255e2f7d058

プロフィール編集機能

プロフィール編集機能の実装を行う

・ルーティングには単数形リソース(resource)を使用すること

アバター画像用のカラムを追加すること

・新しくprofiles_controllerを作ること

・プロフィール詳細ページ(/profile)と編集ページ(/profile/edit)を作ること

とする

 

 

ルーティングには単数形リソース(resource)を使用すること

resource :profile

2.5 単数形リソース

ユーザーがページを表示する際にidを一切参照しないリソースが使われることがあります。たとえば、/profileでは常に「現在ログインしているユーザー自身」のプロファイルを表示し、他のユーザーidを参照する必要がないとします。このような場合には、単数形リソース (singular resource) を使ってshowアクションに (/profile/:idではなく) /profileを割り当てることができます。

Rails のルーティング - Railsガイド

複数作成できるboardと違ってuserに対するprofileは1つしか存在しない

他のuserのprofileを編集することはないため、idを表示させるメリットがない

=> 今回は単数系リソースを使用する

 

 

アバター画像用のカラムの追加

以前掲示板画像用のカラムを追加したことがあるのでおさらいのような形

'CarrierWave'というgemを使用する(bundle install済み)

rails g migration AddAvatarsToUsers avatar:string

Usersテーブルにavatarカラムを追加する

rails g uploader avatar

にてuploader(画像アップロード用機能)を生成する

app/uploaders/avatar_uploader.rbがつくられる

実装に必要な部分だけコメントインしていく

class AvatarUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
# include CarrierWave::MiniMagick

# Choose what kind of storage to use for this uploader:
storage :file
# storage :fog

# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end

# Provide a default URL as a default if there hasn't been a file uploaded:
def default_url
'sample.jpg'
end

# Process files as they are uploaded:
# process scale: [200, 300]
#
# def scale(width, height)
# # do something
# end

# Create different versions of your uploaded files:
# version :thumb do
# process resize_to_fit: [50, 50]
# end

# Add an allowlist of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_whitelist
%w[jpg jpeg gif png]
end

# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename
# "something.jpg" if original_filename
# end
end

app/models/user.rb

mount_uploader :avatar, AvatarUploader

uploaderを実装できる

 

 

新しくprofiles_controllerを作ること

rails g controller profiles show edit

で新しくprofiles_controllerを作る

app/controllers/profiles_controller

class ProfilesController < ApplicationController
before_action :set_user, only: %i[edit update]
def show; end

def edit; end

def update
if @user.update(user_params)
redirect_to profile_path, success: t('defaults.message.updated',
item: User.model_name.human)
else
flash.now['danger'] = t('defaults.message.not_updated', item: User.model_name.human)
render :edit
end
end

private

def set_user
@user = User.find(current_user.id)
end

def user_params
params.require(:user).permit(:last_name, :first_name, :email, :avatar)
end
end

 

 

 

○ プロフィール詳細ページ(/profile)と編集ページ(/profile/edit)を作ること

 プロフィール詳細ページ(/profile)

<% content_for(:title, t('.title')) %>
<div class="container pt-3">
<div class="row">
<div class="col-md-10 offset-md-1">
<h1 class="float-left mb-5"><%= t('.title') %></h1>
<%= link_to t('defaults.edit'), edit_profile_path,
class: 'btn btn-success float-right' %>

<table class="table">
<tr>
<th scope="row"><%= t(User.human_attribute_name(:email)) %></th>
<td><%= current_user.email %></td>
</tr>
<tr>
<th scope="row"><%= t(User.human_attribute_name(:full_name)) %></th>
<td><%= current_user.decorate.full_name %></td>
</tr>
<tr>
<th scope="row"><%= t(User.human_attribute_name(:avatar)) %></th>
<td><%= image_tag current_user.avatar.url, class: 'rounded-circle',
size: '50x50' %></td>
</tr>
</table>
</div>
</div>
</div>

 

プロフィール編集ページ(/profile/edit)

% content_for(:title, t('.title')) %>
<div class="container">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<h1><%= t('.title') %></h1>
<%= form_with model: @user, url: profile_path, local: true do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :last_name %>
<%= f.text_field :last_name, class: 'form-control'%>
</div>
<div class="form-group">
<%= f.label :first_name %>
<%= f.text_field :first_name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :avatar %>
<%= f.file_field :avatar, onchange: 'previewImage()', class: 'form-control mb-3',
accept: 'image/*' %>
<%= f.hidden_field :avatar_cache %>
</div>
<div class='mt-3 mb-3'>
<%= image_tag @user.avatar.url, class: 'rounded-circle', id: 'preview',
size: '300x200' %>
</div>
<%= f.submit t('defaults'), class:'btn btn-primary' %>
<% end %>
</div>
</div>
</div>

 

 

 

参考

Rails のルーティング - Railsガイド

【Rails】ユーザー登録・編集機能をプログラミング作成しました | 👑おすすめのプログラミングスクール比較!最短で上達するなら?

【CarrierWave】Railsで画像をアップロードするカンタンな方法は? | にょけんのボックス

 

検索機能の実装

掲示板一覧と、ブックマーク掲示板一覧ページの検索機能の実装を行う

https://i.gyazo.com/4fbea89202019de9004e4b0c8a954ce6.gif

 

今回はransackというgemを使い実装をする

GitHub - activerecord-hackery/ransack: Object-based searching.

 

① controller

app/controllers/boards_controller.rb

class BoardsController < ApplicationController
def index
@q = Board.ransack(params[:q])
@boards = @q.result(distinct: true).includes(:user).order(created_at: :desc)
.page(params[:page])
end
def bookmarks
@q = current_user.bookmark_boards.ransack(params[:q])
@bookmark_boards = @q.result(distinct: true).includes(:user).order(created_at: :desc)
.page(params[:page])
end

params[:q]には検索パラメータが渡される

それを@q = Board.ransack(params[:q])としてあげれば、@qという検索オブジェクトが作成される

加えてこの@qに対して@boards = @q.resultとしてあげれば検索結果が得られる

 

※ distinct: trueオプション => 結果の重複を防ぐことができる

今回の実装には不必要だが、後に内容変更にて必要になる場合もある

癖として毎回つけておくと良い

 

 

② view

今回は、検索機能が共通している(掲示板一覧とブックマーク掲示板一覧)

同じコードが複数のファイルにある場合はpartialに切り出す

ただ、2つのローカル変数の内容が異なる

・q(検索オブジェクト)

・url(リクエストするurl)

これらは、それぞれのpartialでローカル変数に値を渡す

app/views/boards/_search_form.html.erb

<%= search_form_for @q, url: url do |f| %>
<div class="input-group mb-3">
<%= f.search_field :title_or_body_cont, class:'form-control',
placeholder: t('defaults.search_word') %>
<div class="input-group-append">
<%= f.submit class: 'btn btn-primary' %>
</div>
</div>
<% end %>

search_form_forは、form_forのransack版

search_form_forの引数に@qを取ることで検索フォームがつくれる

titleとbody、いずれからも検索をかけるための記述->title_or_body_contで一発

contはcontainの略

 

 

掲示板一覧とブックマーク掲示板一覧、それぞれで表示させる

ローカル変数に値を渡す

app/views/boards/index.html.erb

<%= render 'search_form', url: boards_path, q: @q %>

 

app/views/boards/bookmarks.html.erb

<%= render 'search_form', url: bookmarks_boards_path, q: @q %>

 

 

※ search_form_forを使用する際、urlを指定しないと、

ブックマーク一覧で検索した場合に、ブックマークしていない掲示板が結果として表示されてしまう     (<=ここ!わからなかったとこ!!)

これは、urlを指定しない場合、ブックマーク一覧の検索でリクエストするurlが自動的に/boardsになってしまうから => BoardsController#indexにルーティングされる

BoardsController#bookmarksにルーティングされてほしいので、urlを指定する

 

 

 

 参考

GitHub - activerecord-hackery/ransack: Object-based searching.

Ransackで簡単に検索フォームを作る73のレシピ - 猫Rails

 

ページネーション

一覧画面にページネーションを実装していく

 

流れは大きく、

① kaminariをbundle installする

② controllerを修正する

③ viewでページネーションを表示させる

 

① kaminariをbundle installする

kaminariというgemを追加してinstall

GitHub - kaminari/kaminari: ⚡ A Scope & Engine based, clean, powerful, customizable and sophisticated paginator for Ruby webapps

 

% rails g kaminari:config
Running via Spring preloader in process 78986
create config/initializers/kaminari_config.rb

にてkaminariの設定を生成する

config/initializers/kaminari_config.rb

# frozen_string_literal: true

Kaminari.configure do |config|
# config.default_per_page = 25
# config.max_per_page = nil
# config.window = 4
# config.outer_window = 0
# config.left = 0
# config.right = 0
# config.page_method_name = :page
# config.param_name = :page
# config.max_pages = nil
# config.params_on_first_page = false
end

希望の表示事項によりコメントインさせたりアウトさせたりする

詳しくは↓

Kaminariの使い方 まとめ - 猫Rails

 

 

② controllerを修正する

今回は掲示板一覧、ブックマーク一覧にページネーションをつけたい

app/controllers/boards_controller.rb

class BoardsController < ApplicationController
def index
@boards = Board.all.includes(:user).order(created_at: :desc).page(params[:page])
end
def bookmarks
@bookmark_boards = current_user.bookmark_boards.includes(:user)
                       .order(created_at: :desc).page(params[:page])
end

ページ番号はparams[:page]に格納される

これをpageメソッドに食わせる

 

 

③ viewでページネーションを表示させる

掲示板一覧、ブックマーク一覧にpaginateを追加する

app/views/boards/index.html.erb

<!-- 掲示板一覧 -->
<div class="row">
<div class="col-12">
<div class="row">
<% if @boards.present? %>
<%= render partial: 'board', collection: @boards %>
<% else %>
<p>掲示板がありません。</p>
<% end %>
</div>
<%= paginate @boards, theme: 'twitter-bootstrap-4' %>
</div>
</div>

 

app/views/boards/bookmarks.html.erb

<div class="row">
<div class="col-12">
<div class="row">
<% if @bookmark_boards.present? %>
<%= render partial: 'board', collection: @bookmark_boards %>
<% else %>
<p><%= t('.no_result') %></p>
<% end %>
</div>
<%= paginate @bookmark_boards, theme: 'twitter-bootstrap-4' %>
</div>
</div>

 

 

○ Bootstrap4実装

今のままでは見栄えが悪いので、bootstrap4を利用してページネーションのデザインを変更する

 
$ rails g kaminari:views default

をするとパーシャルが7つ作成される

作成されたパーシャルはkaminari内部で使用されているものと同じ

こちらを変更することで、テンプレート(デザイン)を変更することができる

$ rails g kaminari:views bootstrap4

とすることで、bootstrap4が適用される

 

※ちなみに、bootstrap4を適用させたい場合は、defaultを作成する必要はなく、このコマンド1つで適用できる

$ rails g kaminari:views bootstrap4

 

 

 

参考

GitHub - kaminari/kaminari: ⚡ A Scope & Engine based, clean, powerful, customizable and sophisticated paginator for Ruby webapps

Kaminariの使い方 まとめ - 猫Rails

【Rails】kaminariを使用してページネーション機能を実装 - Qiita

Bootstrap4+kaminariでページネーションの実装 - Qiita

【Ruby on Rails】kaminariとbootstrap4で日本語に対応したページネーションを実装する。 - Qiita

 

コメント投稿、削除機能のajax化

ブックマーク機能のajax化と流れは同じ

 

①comments_controller.rbを修正

②comment作成・削除処理をajax

③js.erbの作成(作成・削除時の動的レンダリング処理を追加)

 

 

①comments_controller.rbを修正

app/controllers/comments_controller.rb

class CommentsController < ApplicationController
def create
@comment = current_user.comments.build(comment_params)
@comment.save
end

def destroy
@comment = current_user.comments.find(params[:id])
@comment.destroy!
end

private

def comment_params
params.require(:comment).permit(:body).merge(board_id: params[:board_id])
end
end

コメント作成・削除をajax化するため、redirect_backやflashメッセージのコードは削除する

 

 

②comment作成・削除処理をajax

・comment作成処理をajax

app/views/comments/_form.html.erb

<div class="row mb-3">
<div class="col-lg-8 offset-lg-2">
<%= form_with model: comment, url: [board, comment], id: 'new_comment' do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :body %>
<%= f.text_area :body, class: 'form-control mb-3', id: 'js-new-comment-body',
              row: 4, placeholder: Comment.human_attribute_name(:body) %>
<%= f.submit t('defaults.post'), class: 'btn btn-primary' %>
<% end %>
</div>
</div>

form_withのlocal: trueを削除して非同期処理にする

form_withでフォームを送信した場合は、デフォルトでjsファイルを探しにいくようになっている(remote: trueの記載不要)

htmlファイルを探しにいって欲しい場合にはlocal: trueを記述する

つまり、今回はcreate.js.erbを探しに行って欲しいため、local: trueを削除する

 

id: 'new_comment' -> エラーメッセージを表示させるための

id: 'js-new-comment-body' -> createの時はidは不要

 

 

・comment削除処理をajax

app/views/comments/_comment.html.erb

<tr id="comment-<%= comment.id %>">
<td style="width: 60px">
<%= image_tag 'sample.jpg', class: 'rounded-circle', size: '50x50' %>
</td>
<td>
<h3 class="small"><%= comment.user.decorate.full_name %></h3>
<div id="js-comment-<%= comment.id %>">
<p><%= simple_format(comment.body) %></p>
 
 
 
</td>

<% if current_user.own?(comment) %>
<td class="action">
<ul class="list-inline justify-content-center" style="float: right;">
<li class="list-inline-item">
 <%= link_to "#", method: :get, remote: true, class: "js-edit-comment-button",
id: 'button-edit-#{comment.id}' do %>
<%= icon 'fa', 'pen' %>
 <% end %>
</li>
<li class="list-inline-item">
 <%= link_to comment_path(comment),
         class: "js-delete-comment-button",
         method: :delete,
         remote: true,
         data: {confirm: '削除してもよろしいですか?'} do %>
<%= icon 'fa', 'trash' %>
 <% end %>
</li>
</ul>
</td>
<% end %>
</tr>

commentの削除ボタンのlink_toにremote: trueを追記する

 

 

③js.erbの作成(作成・削除時の動的レンダリング処理を追加)

・作成時

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 %>

commentの追加に成功した場合(エラーオブジェクトがなければ)、追加したcommentをコメント一覧に表示させるようにjavascriptで実装

prependメソッドを用いてcommentのpartialの部分をレンダリングする↓
$("#js-table-comment").prepend("<%= j(render('comments/comment',
                                     comment: @comment)) %>");
$("#js-new-comment-body").val('');
投稿成功時、valで空文字を指定してコメントフォームvalue属性をリセットする
 ここの("#js-table-comment")は、
app/views/comments/_comments
<div class="row">
<div class="col-lg-8 offset-lg-2">
<table id="js-table-comment" class="table">
<%= render comments %>
</table>
</div>
</div>
からのもの
 
commentの追加に失敗した場合はエラーメッセージを表示させる↓
$("#error_messages").remove();
<% if @comment.errors.present? %>
$("#new_comment").prepend("<%= j(render('shared/error_messages',
                                      object: @comment)) %>");
$("error_messages").remove()=>まずコメントの作成に失敗したらエラーメッセージが表示、次コメントの作成に成功した場合はエラーメッセージが消え、2回目も作成に失敗した場合はエラーメッセージを再度表示させる
$("#new_comment").prepend("<%= j(render('shared/error_messages', object: @comment)) %>")=>エラーオブジェクトを持っている場合、prependで指定した要素("#new_comment")の子要素の先頭にshared/error_messagesを追加する
※prepend()=>子要素の先頭に追加
 
 
・削除時
app/views/comments/destroy.js.erb
$('tr#comment-<%= @comment.id %>').remove();
removeメソッドで指定した要素全てを削除している
 
 
参考

ブックマークボタンのajax化

そもそもajaxって何??

=> Asynchronous JavaScript + XML の略で、非同期通信と呼ばれる通信方法のことを指す

詳しくはこちら=> 初心者目線でAjaxの説明 - Qiita

 ajaxを使用したサービスでわかりやすいものとして、google mapがある

 

 

Railsにおいてajax通信を実装する場合、主に二つの方法がある。

・remote: trueを指定する方法
・JSファイルに任意のタイミングでajax処理を発火させるように記述を施す方法
今回は、remote: trueを使用する方法で実装する。

 

remote: trueオプション

Railsにおいて、リクエストしてからの流れはルーティング=>コントローラー=>ビューという流れになる(MVC)

例えばtests_controllerのindexアクションが呼び出された場合、対応するviewテンプレートは、views/tests/index.html.erb、となる

 

remote: trueを用いた場合(ajax)どうなるのか

例えば、

<%= link_to 'tests#indexへリンク', ○○(tests#indexへのパス), remote: true %>

のようなlinkをクリックした場合、ルーティング=>tests_controllerのindexアクションを通過=>index.js.erbとなる

そのため、今回の手順としては、

①remote: trueオプションをつける

②controllerでリダイレクト先を消す

③js.erbの記述

となる

 

①remote: trueオプションをつける

_unbookmark.html.erb

<%= link_to bookmark_path(current_user.bookmarks.find_by(board_id: board.id)),
class: 'float-right', id: "js-bookmark-button-for-board-#{board.id}",
remote: true, method: :delete do %>
<%= icon 'fas', 'star' %>
<% end %>

_bookmark.html.erb

<%= link_to bookmarks_path(board_id: board.id), class: 'float-right',
id: "js-bookmark-button-for-board-#{board.id}", remote: true, method: :post do %>
<%= icon 'far', 'star' %>
<% end %>

 

 

②controllerでリダイレクト先を消す

bookmark_controller.rb

class BookmarksController < ApplicationController
def create
@board = Board.find(params[:board_id])
current_user.bookmark(@board)
# redirect_to boards_path, success: t('.success')
end

def destroy
@board = current_user.bookmarks.find(params[:id]).board
current_user.unbookmark(@board)
# redirect_to boards_path, success: t('.success')
end
end

remote: trueオプションにより、

controllerのcreateアクションの時は、views/bookmarks/create.js.erb
controllerのdestroyアクションの時は、views/bookmarks/destroy.js.erbに自動的にレンダリングされます

redirect_toを指定してしまうと画面遷移が行われてしまい、非同期通信が行われなくなってしまいます

 

 

③js.erbの記述

create.js.erb

$("#js-bookmark-button-for-board-<%= @board.id %>").replaceWith("<%= j(render
'boards/unbookmark', board: @board ) %>");

destroy.js.erb

$("#js-bookmark-button-for-board-<%= @board.id %>").replaceWith("<%= j(render
'boards/bookmark', board: @board ) %>");

ここで、初めreplaceWith()ではなく、html()にしていたら、うまく実装されなかった。

調べてみると、

html() :  指定したタグ(h1やh2)の中身をhtmlタグ付きで変えるが、指定したタグ自体は変えない

replaceWith() : 指定したタグの中身をhtml付きで変えて、更に指定したタグ自体も変える

らしい

jQueryのreplaceWith()とhtml()の違いは何ですか? - Javaer101

replaceWith()の使い方〜jQuery | IT工房|AI入門とWeb開発

ajaxのhtmlとreplaceWithメソッドの違い - sho171のブログ

要は、html()だとhtml()の()の部分をそのまま指定したタグに入れてしまうのでネストする形になってしまう

replaceWith()だとその点、置き換えてくれるからネストしない、ということか

 

 

参考にしたサイト

Ajaxを用いて非同期いいねを実装 | FreeCamp

【rails】Ajaxを使ったフォローボタンの実装で躓いたところ - Qiita

Railsでいいね機能のAjax処理を実装してみた - Qiita

【Rails】簡単なajax処理 (remote true) - bokuの学習記録

replaceWith()の使い方〜jQuery | IT工房|AI入門とWeb開発

ajaxのhtmlとreplaceWithメソッドの違い - sho171のブログ

jQueryのreplaceWith()とhtml()の違いは何ですか? - Javaer101