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

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

rails g sorcery:install reset_password --only-submodules

 

作成されるマイグレーションファイル

class SorceryResetPassword < ActiveRecord::Migration[5.2]
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
add_column :users, :access_count_to_reset_password_page, :integer, default: 0

add_index :users, :reset_password_token
end
end

マイグレーションファイルを反映

rake db:migrate

 

メイラーの作成

rails g mailer UserMailer reset_password_email

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: 'パスワードリセット')
end
end

 

パスワードリセット用のメイラーを指定

user.reset_password_mailer = UserMailer

 

コントローラーの作成

rails g controller PasswordResets create edit update

class PasswordResetsController < ApplicationController
skip_before_action :require_login

def create
@user = User.find_by(email: params[:id])
@user&.deliver_reset_password_instructions!
redirect_to login_path
flash[:success] = "送信しました"
end

def edit
@token = params[:id]
@user = User.load_from_reset_password_token(@token)
not_authenticated if @user.blank?
end

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
flash[:success] = '更新しました'
else
render :edit
flash.now[:danger] = '更新できませんでした'
end
end
end

 

ルーティングを追加

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

 

メイラービューの設定

reset_password_email.html.erb

<h1><%= @user.nickname%>様</h1>

<p>==========================================</p>

<p>パスワード再発行のご依頼を受け付けました</p>

<p>こちらのリンクからパスワードの再発行を行ってください。</p>
<p><a href= "<%=@url %>"><%= @url %></a></p>

reset_password_email.text.erb

<%= @user.nickname%>様

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

 

パスワードリセット用のフォームの作成

<%= form_with url: password_resets_path, local: true do |f|%>
<%= f.label :email%>
<%= f.email_field :email %>
<%= f.submit '送信'%>
<% end %>

 

<%= form_with model: @user, url: password_reset_path(@token), local: true do |f| %>
<%= f.label :email %><br>
<%= @user.email %>
 
<%= f.label :password %><br>
<%= f.password_field :password %>
 
<%= f.label :password_confirmation %><br>
<%= f.password_field :password_confirmation %>
 
<%= f.submit '更新' %>
<% end %>

 

letter_opener_webを追加する

gem 'letter_opener_web', '~> 1.0'

bundle installする

 

ルーティングを追加

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

 

環境ごとに異なる定数を管理できるgemをインストール

gem 'config'

bundle installする

 

設定ファイルの生成

rails g config:install

 

config/settings/development.yml

default_url_options:
host: 'localhost:3000'

 

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

profiles_controllerの作成

rails g controller profiles

 

profiles_controllerにアクションを追加する

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

def update
if @user.update(user_params)
redirect_to profile_path
flash[:success] = '更新しました'
else
flash.now[:danger] = '更新できませんでした'
render :edit
end
end

def show; end

private

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

def user_params
params.require(:user).permit(:email, :nickname)
end
end

 

ビューの設定


<div class="container">
<%= form_with model: @user, url: profiles_path, local: true do |f| %>
<div class="form-group">
<%= f.label :email%>
<%= f.text_field :email, class:'form-control'%>
</div>
<div class="form-group">
<%= f.label :nickname%>
<%= f.text_field :nickname, class:'form-control'%>
</div>
<%= f.submit '編集', class:'btn btn-primary'%>
<% end %>
</div>

 

ルーティングの設定

resource :profiles, only: %i[show edit update]

掲示板の検索機能を実装

Gemfileに追記

gem 'ransack'

bundle installする

 

コントローラーへ検索機能を追加する

def index
@q = Board.ransack(params[:q])
@boards = @q.result(distinct: true).includes(:user).order(created_at: :desc)
.page(params[:page])
end

 

ビューに検索フォームを追加する

<%= search_form_for @q, class: 'mb-5' do |f| %>
<div class="input-group mb-3">
<%= f.search_field :title_or_body_cont, class:'form-contro'%>
<div class="input-group-append">
<%= f.submit '検索', class:'btn btn-primary'%>
</div>
</div>
<% end%>

f.search_fieldの:title_or_body_contを変更することで検索する内容を変更できる

(例 :title_cont, :body_cont)

 

パーシャルを使用する場合

<%= search_form_for q, url: url do |f|%>

 

renderする

<%= render 'ファイル名', q: @q, url: boards_path %>

掲示板のページネーション

Gemfileに追記

gem 'kaminari'

bundle installする

 

コントローラーの変更

def index
@boards = Board.all.includes(:user).order(created_at: :desc)

 

def index
@boards = Board.all.includes(:user).order(created_at: :desc).page(params[:page])
end

 

kaminariで用意されている.page(params[:page])を使うことで簡単に使うことができる。

デフォルトで1ページで表示されるのは25件になっている

 

ビューに表示する

<%= paginate @boards %>

 

bootstrapを使ってデザインを変更する

rails g kaminari:views bootstrap4

 

表示件数を変更する

rails g kaminari:config

# frozen_string_literal: true
Kaminari.configure do |config|
config.default_per_page = 50
# 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.params_on_first_page = false
end

 

config.default_per_page = 50

数字の部分を変更すれば表示件数が変わる

ブックマーク機能の追加

Bookmarkモデルの作成

rails g model Bookmark user:references board:references

class CreateBookmarks < ActiveRecord::Migration[5.2]
def change
create_table :bookmarks do |t|
t.references :user, foreign_key: true
t.references :board, foreign_key: true

t.timestamps
end
add_index :bookmarks, [:user_id, :board_id], unique: true
end
end
 

rails db:migrateをする

(add_index :bookmarks, [:user_id, :board_id), unique: trueで同じユーザーが複数回ブックマークすることを防ぐ)

 

バリデーションの設定

class Bookmark < ApplicationRecord
belongs_to :user
belongs_to :board
validates :user_id, uniqueness: { scope: :board_id }
end
 

バリデーションは上のように設定します。

user_idとboard_idの組み合わせが一意であるようにするため。

1つの掲示板につきuserは1回のみブックマークできるようにするため

 

アソシエーション

user.rb

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

board.rb

has_many :bookmarks_boards, dependent: :destroy

 

ブックマーク機能

user.rb

def bookmark?(board)
bookmark_boards.include?(board)
end

def bookmark(board)
bookmark_boards << board
end

def unbookmark(board)
bookmark_boards.destroy(board)
end

bookmark? ブックマークされているか?

bookmark  ブックマークする

unbookmark ブックマークを外す

 

コントローラーの作成

rails g controller bookmarks

class BookmarksController < ApplicationController
def create
board = Board.find(params[:board_id])
current_user.bookmark(board)
redirect_back fallback_location: root_path
flash[:success] = "ブックマークしました"
end

def destroy
board = current_user.bookmarks.find(params[:id]).board
current_user.unbookmark(board)
redirect_back fallback_location: root_path
flash[:success] = "ブックマークを外しました"
end
end

redirect_backを使うと直前のページへ戻る

 

boards_controllerにbookmarksアクションを追加

def bookmarks
@bookmark_boards = current_user.bookmarks_boards.include(:user).order(created_at: :desc)
end

 

ルーティングの設定

resources :boards do
resources :comments, shallow: true
collection do
get :bookmarks
end
end
resources :bookmarks, only: %i[create destroy]
end

resourcesで定義された7つのアクション以外のアクションを定義するときにcollectionを使う

/boards/bookmarksになる

 

ボタン作成

お気に入りボタン

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

お気に入り解除ボタン

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

ボタンの判定

<% if current_user.bookmark?(board) %>
<%= render 'unbookmark', { board: board } %>
<% else%>
<%= render 'bookmark', { board: board } %>
<% end%>
ボタンをrenderする際に下のメソッドで判定する
def bookmark?(board)
bookmark_boards.include?(board)
end

 

お気に入り一覧画面の作成

<% if @bookmark_boards.present? %>
<%= render @bookmark_boards %>
<% else %>
<p>No result</p>
<% end %>

掲示板の編集、削除機能の実装

boards_controllerに追加(edit update destroy set_board)

class BoardsController < ApplicationController
beforeaction :set_board, only: [:edit, :update, :destroy]
def new
@board = Board.new
end

def create
@board = current_user.boards.build(board_params)
if @board.save
flash[:success] = '掲示板を作成しました'
redirect_to boards_path
else
flash.now[:danger] = '掲示板の作成にしました'
render :new
end
end

def show
@board = Board.find(params[:id])
@comment = Comment.new
@comments = @board.comments.includes(:user).order(created_at: :desc)
end

def index
@boards = Board.all.includes(:user).order(created_at: :desc)
end

def edit; end

def update
if @board,update(board_params)
flash[:success] = "掲示板を更新しました"
redirect_to @board
else
flash[:danger] = "掲示板の更新に失敗しました"
render :edit
else
end

def destroy
@board.destroy!
redirect_to boards_path
flash[:success] = "掲示板を削除しました"
end

private

def set_board
@board = current_user.boards.find(params[:id])
end

def board_params
params.require(:board).permit(:title, :body)
end
end

edit,update,destroyアクションはURLから他の人の掲示板を編集できないようにするためset_boardで対象のユーザーの掲示板のみ取得できるようにする

 

class User < ApplicationRecord
def own?(object)
object.user_id == id
end
end

current_userと掲示板を作ったuserが一緒かどうか確認

 

<ul class='crud-menu-btn list-inline float-right'>
<li class="list-inline-item">
<%= link_to edit_board_path(board), id: "button-edit-#{board.id}" do %>
<p>編集</p>
<% end %>
</li>
<li class="list-inline-item">
<%= link_to board_path(board), id: "button-delete-#{board.id}",
method: :delete, data: { confirm: "削除しますか?" } do %>
<p>削除</p>
<% end %>
</li>
</ul>

編集ボタン、削除ボタンの作成

 

<%= render 'shared/crud_menus', board: @board if current_user.own?(@board)%>

編集ボタン、削除ボタンが必要な場所に記述

 

タイトルを動的に出力する

helpers/application_helpers.rb

module ApplicationHelper
def page_title(page_title = '')
base_title = "TestApp"

page_title.empty? ? base_title : page_title + "|" + base_title
end
end
page_title.empty? ? base_title : page_title + "|" + base_title
page_title.empty?でpage_titleがtrueかfalseかを調べてtrueの場合はbase_title,
falseの場合はpage_title + "|" + base_titleを表示する.
 
layouts/application.html.erb
<title><%= page_title(yield(:title)) %></title>

 

各ページのviewの先頭に追加

<% content_for(:title, @board.title) %>