Rails7 で TODOアプリを作ろう ⑦ (Deviseのビューをカスタム・フラッシュメッセージ)

## はじめに

### 前回は

#### Deviseの導入 として

https://qiita.com/tomoaki-kimura/items/463afb2ef68afcf2fdb1

- ログイン・ログアウト機能
- ナビゲーションバー

について学びました。

### 今回は

#### ビューのカスタム として

- Deviseのビュー
- Bootstrapの適用
- フラッシュメッセージ

について学びます。

では、はじめていきましょう。

## １. カスタムビュー

### View

ここまでの流れで、Viewの編集をやっておきたいのですが、現状では先程までの画面のViewは見える所にはありません。

[![Image from Gyazo](https://i.gyazo.com/0f5d81d357035152a9a64ac919299473.png)](https://gyazo.com/0f5d81d357035152a9a64ac919299473)

ですので、カスタム用の `view` を作って、デフォルトのViewを上書きします。

生成用のコマンドを実行してみましょう

```zsh
todo_app % rails g devise:views
```

すると、

```zsh
 todo_app % rails g devise:views
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb
 todo_app % 
```

かなりのファイルが作成されました。

[![Image from Gyazo](https://i.gyazo.com/459d0d3f487f3ef2e42cfc206ddb537e.png)](https://gyazo.com/459d0d3f487f3ef2e42cfc206ddb537e)

カスタムビューを読み込んでいるかのチェックをするなら、新規作成が `registrations/new.html.erb` なので、

app/views/devise/registrations/new.html.erb【確認】

```html
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
```

このファイルに何か落書きをしてみると良いでしょう。

[![Image from Gyazo](https://i.gyazo.com/38c2528f3da67d17b4f694c70e85725f.png)](https://gyazo.com/38c2528f3da67d17b4f694c70e85725f)

これでViewの編集が出来るようになりました。

## 2. ビューの場所を変更したい

特にこのまま進めても問題はないのですが、もし複数のモデルで認証機能が必要・・という場合など、ちょっと困る事が起こってきます。

`devise` というフォルダ内ネームスペース等なく管理されていますので、テーブル別に２〜３種類別のフォームを作る時など、今回生成されたViewしか見に行かないなどのトラブルが考えられます。

ですので、Railsの命名規則に則った `モデル名` に依存する形で Viewを置くようにしたいと思います。

具体的には、

`app/views/devise/`

から

`app/views/users/`

に引っ越しします。

初期段階ならコマンドで簡単に出来ますので、やってみましょう。

```zsh
todo_app % rails d devise:views
```

とすると、

```zsh
 todo_app % rails d devise:views      
      invoke  Devise::Generators::SharedViewsGenerator
      remove    app/views/devise/shared/_error_messages.html.erb
      remove    app/views/devise/shared/_links.html.erb
      invoke  form_for
      remove    app/views/devise/confirmations/new.html.erb
      remove    app/views/devise/passwords/edit.html.erb
      remove    app/views/devise/passwords/new.html.erb
      remove    app/views/devise/registrations/edit.html.erb
      remove    app/views/devise/registrations/new.html.erb
      remove    app/views/devise/sessions/new.html.erb
      remove    app/views/devise/unlocks/new.html.erb
      invoke  erb
      remove    app/views/devise/mailer/confirmation_instructions.html.erb
      remove    app/views/devise/mailer/email_changed.html.erb
      remove    app/views/devise/mailer/password_change.html.erb
      remove    app/views/devise/mailer/reset_password_instructions.html.erb
      remove    app/views/devise/mailer/unlock_instructions.html.erb
```

これで一気に消えますし、 `views/devise` フォルダごと消しても良いでしょう。

その後、

```zsh
todo_app % rails g devise:views users
```

と、 `users` を付けて実行します。

すると、`users` 直下に同じものが展開されます。

[![Image from Gyazo](https://i.gyazo.com/37065aaac306c24b5948fd3a5ef6dc35.png)](https://gyazo.com/37065aaac306c24b5948fd3a5ef6dc35)

しかし、実はこのままではこのViewは読んでもらえず、先程の落書きをしても、

[![Image from Gyazo](https://i.gyazo.com/11adc295171258575075830f1cdecd84.png)](https://gyazo.com/11adc295171258575075830f1cdecd84)

反映されません。

### config.scoped_views

この現象を解決するために、`config/initializers/devise.rb` を開いて設定を変更します。

config/initializers/devise.rb【確認】
```ruby
（略）
  # ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
# config.scoped_views = false #←これ

# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user).
# config.default_scope = :user

# Set this configuration to false if you want /users/sign_out to sign out
# only the current scope. By default, Devise signs out all scopes.
# config.sign_out_all_scopes = true
（略）
```

247行目辺りにあると思いますが、`config.scoped_views` の項目のコメントを外し、 `false` を `true` にしましょう。

config/initializers/devise.rb【編集】
```ruby
（略）
  # ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
config.scoped_views = true #←コメントを消してfalseをtrueに

# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user).
# config.default_scope = :user

# Set this configuration to false if you want /users/sign_out to sign out
# only the current scope. By default, Devise signs out all scopes.
# config.sign_out_all_scopes = true
（略）
```

これでモデル名し従ってViewを読みにいってくれます。

そして、 `config` を触っていますので、忘れずに `再起動` しておきましょう。

一旦サーバーを落として、

```
todo_app % bin/dev
```

で起動します。すると、

[![Image from Gyazo](https://i.gyazo.com/65a5c68d07640b58d84ff4da5899faea.png)](https://gyazo.com/65a5c68d07640b58d84ff4da5899faea)

いけました。

## 3. form_for から form_with へ

### form_with

次に、フォームで非推奨となっている書き方を変更しておきます。

以前、Railsのフォームには `form_for` と `form_tag` という２種類のフォームヘルパーが用意されていたのですが、 今は `form_with` に統合されています。

DeviseのView部分にはこの古い書き方がありますので、これを先に変えておきましょう。

### 新規作成

app/views/users/registrations/new.html.erb【確認】
```html
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  （略）

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
```

ありました。

```html
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
```

この行ですね。 `resource` という変数は作る時のモデルオブジェクト等が入ってきているようなので、Devise側でテンプレートを使い回せるように上手いことやっているようです。興味深いですね。

しかし、同時に `resource` に代入される値は、モデル名に依存して `@user` のようなインスタンス変数が普通に使えますので、

`form_with` の引数の取り方の違いも考慮すると、

```html
<%= form_with(model: @user, url: session_path(resource_name)) do |f| %>
```

このように書き換える事が可能です。ですので、

app/views/users/registrations/new.html.erb【編集】
```html
<h2>Sign up</h2>

<!-- ↓ここだけ書き換え -->
<%= form_with(model: @user, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>
  
  （略）
  
  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
```

このようになります。

### 編集

次に `registrations/edit.html.erb` も変更しましょう。

app/views/users/registrations/edit.html.erb【確認】
```html
<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  （略）

  <div class="actions">
    <%= f.submit "Update" %>
  </div>
<% end %>

<h3>Cancel my account</h3>

<div>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %></div>

<%= link_to "Back", :back %>
```

ここも同様に

app/views/users/registrations/edit.html.erb【編集】
```html
<h2>Edit <%= resource_name.to_s.humanize %></h2>

<!-- ↓ここだけ書き換え -->
<%= form_with(model: @user, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  （略）

  <div class="actions">
    <%= f.submit "Update" %>
  </div>
<% end %>

<h3>Cancel my account</h3>

<div>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %></div>

<%= link_to "Back", :back %>
```

### ログイン

最後にログインフォームです。

app/views/users/sessions/new.html.erb【確認】
```html
<h2>Log in</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  （略）

  <div class="actions">
    <%= f.submit "Log in" %>
  </div>
<% end %>

<%= render "users/shared/links" %>
```

これも以下のように書き換えましょう。

app/views/users/sessions/new.html.erb【編集】
```html
<h2>Log in</h2>

<!-- ↓ここだけ書き換え -->
<%= form_with(model: @user, url: session_path(resource_name)) do |f| %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  （略）

  <div class="actions">
    <%= f.submit "Log in" %>
  </div>
<% end %>

<%= render "users/shared/links" %>
```

これでOKです。特に見た目は変わりませんが、ミスがないか導線の確認しておくと良いですね。

## 4. Bootstrapを適用

### Bootstrap

このままでは、Userモデル用のフォームが寂しいので、Bootstrapを使って簡単に作ってしまいましょう。

先程の３つのファイルのうち、実際に使用する、

`registrations/new.html.erb`

`sessions/new.html.erb`

の２つのファイルに対して手を加えます。

編集後は以下のようになりますので、それぞれ編集してみましょう。

app/views/users/registrations/new.html.erb【編集】
```html
<h2>Sign up</h2>

<div class="row">
  <div class="col-lg-4 offset-lg-4 col-md-6 offset-md-3 col-sm-8 offset-sm-2">
    <div class="card">
      <div class="card-body">
        <%= form_with(model: @user, url: registration_path(resource_name)) do |f| %>
          <%= render "users/shared/error_messages", resource: resource %>

          <div class="mb-3">
            <%= f.label :email, class: "form-label" %>
            <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "form-control" %>
          </div>

          <div class="mb-3">
            <%= f.label :password, class: "form-label" %>
            <% if @minimum_password_length %>
              <em>(<%= @minimum_password_length %> characters minimum)</em>
            <% end %><br />
            <%= f.password_field :password, autocomplete: "new-password", class: "form-control" %>
          </div>

          <div class="mb-3">
            <%= f.label :password_confirmation, class: "form-label" %>
            <%= f.password_field :password_confirmation, autocomplete: "new-password", class: "form-control" %>
          </div>

          <div class="mb-3">
            <%= f.submit "Sign up", class: "btn btn-primary w-100" %>
          </div>
        <% end %>
      </div>
    </div>
  </div>
</div>


<%= render "users/shared/links" %>
```

プレビュー
[![Image from Gyazo](https://i.gyazo.com/7f230c1eddeb25f228799e569f3a9dd2.png)](https://gyazo.com/7f230c1eddeb25f228799e569f3a9dd2)

app/views/users/sessions/new.html.erb【編集】
```html
<h2>Log in</h2>

<div class="row">
  <div class="col-lg-4 offset-lg-4 col-md-6 offset-md-3 col-sm-8 offset-sm-2">
    <div class="card">
      <div class="card-body">
        <%= form_with(model: @user, url: session_path(resource_name)) do |f| %>
        <div class="mb-3">
          <%= f.label :email, class: "form-label" %><br />
          <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "form-control" %>
        </div>
        
        <div class="mb-3">
          <%= f.label :password, class: "form-label" %><br />
          <%= f.password_field :password, autocomplete: "current-password", class: "form-control" %>
        </div>
        
        <% if devise_mapping.rememberable? %>
        <div class="mb-3">
          <%= f.check_box :remember_me %> 
          <%= f.label :remember_me %>
        </div>
        <% end %>
        
        <div class="mb-3">
          <%= f.submit "Log in", class: "btn btn-primary w-100" %>
        </div>
        <% end %>
      </div>
    </div>
  </div>
</div>

<%= render "users/shared/links" %>
```

プレビュー

[![Image from Gyazo](https://i.gyazo.com/db0c36d61912838efc607e5870826b86.png)](https://gyazo.com/db0c36d61912838efc607e5870826b86)

このようになります。

Bootstrapのグリッドは、

ブレイクポイント別に幅やオフセット量を変更することができますので、

`col-lg-4 offset-lg-4`
`col-md-6 offset-md-3`
`col-sm-8 offset-sm-2`

これらの設定を一緒に書いておくことができます。

## 5. フラッシュメッセージ

### notice alert の表示

ここはDevise導入時に無視をしていた内容で、

```
<%= notice %>
<%= alert %>
```

この２つのメッセージ表示を作るように書かれていたのですが、これは元々 `Rails` で使えるフラッシュメッセージの表示となります。

ですので、Deviseを使う使わないに関わらず、最初にやっておくと良い部分ですが、今回のBootstrapに合わせて、それっぽく作ってみたいと思います。

app/views/layouts/_flash_messages.html.erb【新規作成】
```html
<% if notice %>
  <div class="alert alert-success">
    <%= notice %>
  </div>
<% end %>
<% if alert %>
  <div class="alert alert-danger">
    <%= alert %>
  </div>
<% end %>
```

`alert-success` や `alert-danger` で良い場合、悪い場合のメッセージの色分けをします。

条件式を付けている理由はメッセージのない `alert` の枠だけが表示されるのを防ぐためです。

### application.html.erb

次に、呼び出し部分の追加です。

app/views/layouts/application.html.erb【確認】
```html
<!DOCTYPE html>
<html>
<head>
  <title>TodoApp</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>

  <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  <%= javascript_importmap_tags %>
</head>

<body>
  <%= render "layouts/global_navigation" %>
  <div class="container my-4">
    <%= yield %>
  </div>
</body>
</html>
```

以下の部分に追加しましょう。container の内側に入れます。

app/views/layouts/application.html.erb【編集】
```html
<!DOCTYPE html>
<html>
  <head>
    <title>TodoApp</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>

  <body>
    <%= render "layouts/global_navigation" %>
    <div class="container my-4">
      <%= render "layouts/flash_messages" %><!-- ここに追加 -->
      <%= yield %>
    </div>
  </body>
</html>
```

### scaffoldの不要な記述

これでメッセージが出るようになりましたが、 `scaffold` を実行した際に出来ているフラッシュメッセージ部分と２重で表示されてしまいますので、

以下の `notice` の記述を削除しておきましょう。

app/views/tasks/index.html.erb【確認】
```html
<p style="color: green"><%= notice %></p><!-- これを消す -->

<h1><%= t "activerecord.models.task" %>リスト</h1>

<div id="tasks">
	<% @tasks.each do |task| %>
	<%= render task %>
	<% end %>
</div>

<%= link_to "新規#{ t 'activerecord.models.task' }の作成", new_task_path, class: "btn btn-primary me-2" %>
```

app/views/tasks/index.html.erb【編集】

```html
<h1><%= t "activerecord.models.task" %>リスト</h1>

<div id="tasks">
	<% @tasks.each do |task| %>
	<%= render task %>
	<% end %>
</div>

<%= link_to "新規#{ t 'activerecord.models.task' }の作成", new_task_path, class: "btn btn-primary me-2" %>
```

app/views/tasks/show.html.erb【確認】

```html
<p style="color: green"><%= notice %></p><!-- これを消す -->

<h1><%= t "activerecord.models.task" %>詳細</h1>

<%= render @task %>

<div class="d-flex">
	<%= link_to "この#{ t "activerecord.models.task" }を編集", edit_task_path(@task), class: "btn btn-warning me-2" %>
	<%= link_to "リストへ戻る", tasks_path, class: "btn btn-secondary me-2" %>
	<%= button_to "この#{ t "activerecord.models.task" }を削除", @task, method: :delete, class: "btn btn-danger me-2", data: { turbo_confirm: "本当に削除しますか？" } %>
</div>
```

app/views/tasks/show.html.erb【編集】

```html
<h1><%= t "activerecord.models.task" %>詳細</h1>

<%= render @task %>

<div class="d-flex">
	<%= link_to "この#{ t "activerecord.models.task" }を編集", edit_task_path(@task), class: "btn btn-warning me-2" %>
	<%= link_to "リストへ戻る", tasks_path, class: "btn btn-secondary me-2" %>
	<%= button_to "この#{ t "activerecord.models.task" }を削除", @task, method: :delete, class: "btn btn-danger me-2", data: { turbo_confirm: "本当に削除しますか？" } %>
</div>
```

ここまでで、ひとまずBootstrapの適用が完成です。

## 6. 【課題】リンクとBootstrapについての復習

課題です。ここは復習として自分で調べて解決してみましょう。

### 【課題1】　ナビゲーションバーに新規ユーザー登録のボタンを作る

現在ログアウト時に表示されるナビゲーションは、

[![Image from Gyazo](https://i.gyazo.com/669efcea46692a9fe9eb392f58d5c2dd.png)](https://gyazo.com/669efcea46692a9fe9eb392f58d5c2dd)

`ログイン` しかありません。

これに `新規登録` ボタンを追加してみましょう。

[![Image from Gyazo](https://i.gyazo.com/9ceb15dd3e66232fda7322d08ab6a098.png)](https://gyazo.com/9ceb15dd3e66232fda7322d08ab6a098)

`ヒント` リンクのパスは、 `rails routes -c devise` で調べられます。

### 【課題2】 ナビゲーションバーにハンバーガーボタンを適用

現在ナビゲーションバーは、右側に「ログイン」「ログアウト」 のボタンがあります。

[![Image from Gyazo](https://i.gyazo.com/8dbbda8da4c0da512e8eb69b9b063a2b.png)](https://gyazo.com/8dbbda8da4c0da512e8eb69b9b063a2b)

これを、 `Bootstrap` の機能を使って、ハンバーガーボタンを使ったレスポンシブ対応をしてみましょう。

課題を順番にクリアすると、ログアウト時は下記のような表示となります。

ボタン出現時
[![Image from Gyazo](https://i.gyazo.com/c3cfa3402fa7e1bfa4aa07353d3d3dc6.png)](https://gyazo.com/c3cfa3402fa7e1bfa4aa07353d3d3dc6)

ボタン押下時
[![Image from Gyazo](https://i.gyazo.com/2e2600f040464190d6943329aa67c750.png)](https://gyazo.com/2e2600f040464190d6943329aa67c750)

ヒント：　Bootstrapの公式サイトに参考となるコードがあります。
https://getbootstrap.jp/docs/5.3/components/navbar/

### 【課題3】　不要なリンクを削除

プレビューを見てみると、Bootstrapのデザインが適用されていないリンクが存在します。

[![Image from Gyazo](https://i.gyazo.com/67b246cf08bc4ca2ddb3e6950221d512.png)](https://gyazo.com/67b246cf08bc4ca2ddb3e6950221d512)

[![Image from Gyazo](https://i.gyazo.com/b31299e6b0de7808b581e8f4a96fbeb2.png)](https://gyazo.com/b31299e6b0de7808b581e8f4a96fbeb2)

ここまで課題が出来ていれば、ナビゲーションバーのリンクで代用できる内容なので、削除しておきましょう。

ヒント： `link_to` が直接書かれておらず、 `render` でボタンが呼び出されている箇所があります。 呼び出し部分のみを削除しましょう。

#### 課題は以上です。

## 7. git

### git

ここまでを保存しておきましょう。

```zsh
todo_app % git add .
todo_app % git commit -m "Deviseのビューをカスタム・フラッシュメッセージ"
```

### GitHub

Githubの設定ができているのであれば、同名のリポジトリを作成し、pushしておきましょう。

## おわりに

本チャプターはここまでとなります。

### 次回は

https://qiita.com/tomoaki-kimura/items/57eb705ceeb228161fe6

- Railsコンソール
- バリデーション
- errors
- バリデーションメッセージ

を学びます。

ここまでお疲れ様でした。