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.

Rails7 で TODOアプリを作ろう ⑥ (Deviseの導入)

Last updated at Posted at 2023-11-08

はじめに

前回は

i18nで日本語化 として

  • I18n
  • t メソッド
  • l メソッド

について学びました。

今回は

Deviseの導入 として

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

について学びます。

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

1. Deviseのインストール

gemの追加

Deviseとは認証機能を一式追加できるgemです。

かなりの高機能なので、全てを覚えるのは大変ですが、今回は最もベーシックな機能だけを使ってみます。

早速Gemfileに gem 'Devise' を追加します。

Gemfile【追記】


  gem "capybara"
  gem "selenium-webdriver"
end

gem "enum_help"

gem "devise" #←ここに追加

書けたら、

todo_app % bundle install

を実行します。

todo_app % bundle install
(略)
Bundle complete! 18 Gemfile dependencies, 91 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
todo_app %

インストールされたら、次にアプリケーション内にインストールします。

ターミナルで

todo_app % bundle install

と打ってみましょう。

以下のようなメッセージが出てくるはずです。

 todo_app (main)% rails g devise:install
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Depending on your application's configuration some manual setup may be required:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

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

     In production, :host should be set to the actual host of your application.

     * Required for all applications. *

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"
     
     * Not required for API-only Applications *

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

     * Not required for API-only Applications *

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views
       
     * Not required *

===============================================================================
 todo_app (main %)% 

以下の場所に、設定ファイルと翻訳ファイルが作成されました。

      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml

また、使いかたの説明もあるようです。4項目ありますね。

  • 1: 開発環境でのデフォルトのURLオプションを設定する。
  • 2: ルートのURLを設定する。
  • 3: フラッシュメッセージを追加する。
  • 4: DeviseのViewをカスタマイズする方法。

概ねこのような内容です。

2: のルートは既に設定していますし、その他は進めながら実装していきますので、ここは一旦無視します。

以下の訂正線部分は無視して下さい。
Deviseのバージョンの違いにより、設定が不要となっています。

@jnchito(Junichi Ito)様 よりコメントでご指摘頂きました。:bow_tone1:

Turbo対応の部分に関しての変更点が書かれた記事も頂いていますので、以前触ったけど大変だった・・という方はチェックしていただけると幸いです。

↓↓↓ 触った事ある方は、こちらご一読下さい。

Rails7の場合

あとは、Rails7で使われている turbo_stream に対応させるために、config ファイルに設定が必要となります。

config/initializes/devise.rb266 行目あたりを見てみましょう。

config/initializes/devise.rb【確認】


  # The "*/*" below is required to match Internet Explorer requests.
  # config.navigational_formats = ['*/*', :html, :turbo_stream]

config.navigational_formats = ['*/*', :html, :turbo_stream] このコメントアウトされている行が Turbo 用の設定です。

このコメントを外しておきましょう。

config/initializes/devise.rb【編集】


  # The "*/*" below is required to match Internet Explorer requests.
  config.navigational_formats = ['*/*', :html, :turbo_stream]

これでOKです。

次に、認証機能とその対象となるモデルを作成します。

2. 認証機能を持つモデルを作成

ルーティング

Userモデルで認証機能を作ります。

todo_app % rails g devise user

と打ってみましょう。カラム名などの指定は必要ありません。

すると、

 todo_app % rails g devise user
      invoke  active_record
      create    db/migrate/20231108012222_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

このようなログが流れて、マイグレーションファイルやモデル、その他ルーティングも作成されている事が分かります。

ルーティングを確認していきましょう。

config/routes.rb【確認】

Rails.application.routes.draw do
  devise_for :users #←自動追加されている
  root "tasks#index"
  resources :tasks
  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  get "up" => "rails/health#show", as: :rails_health_check
end

devise_for :users というルーティングが出来ていますね。 これも確認しておきましょう。

rails routes -c devise とターミナルで打ってみます。

-c はコントローラー名で絞り込みをするオプションで、 devise と名のつくコントローラーを表示させます。

 todo_app (main *%)% rails routes -c devise
                  Prefix Verb   URI Pattern                    Controller#Action
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
           user_password PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
                         POST   /users/password(.:format)      devise/passwords#create
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
       user_registration PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy
                         POST   /users(.:format)               devise/registrations#create

これだけのルーティングが出来ているのはすごいですね。

ちなみに resources :tasks で作られている、 tasks のルーティングの場合だと、

 todo_app % rails routes -c tasks 
   Prefix Verb   URI Pattern               Controller#Action
     root GET    /                         tasks#index
    tasks GET    /tasks(.:format)          tasks#index
          POST   /tasks(.:format)          tasks#create
 new_task GET    /tasks/new(.:format)      tasks#new
edit_task GET    /tasks/:id/edit(.:format) tasks#edit
     task GET    /tasks/:id(.:format)      tasks#show
          PATCH  /tasks/:id(.:format)      tasks#update
          PUT    /tasks/:id(.:format)      tasks#update
          DELETE /tasks/:id(.:format)      tasks#destroy

このようになっています。

オプションなしの rails routes だと、かなり見にくくなるので、必要なものだけ取り出すと良いでしょう。

モデル

次にモデルを確認していきます。

app/models/user.rb【確認】

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

Taskモデルの時と違って少し記述があるようです。

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

特にこの部分は、追加する機能設定となる部分で、Viewの表示やルーティングに関わります。

今回メールによるパスワード再設定等は行いませんので、 :recoverable は外しておきましょう。

app/models/user.rb【編集】

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :rememberable, :validatable
end

マイグレーションファイル

続いて、マイグレーションファイルも確認しておきましょう。

db/migrate/xxxxxxxxxxxxxx_devise_create_user.rb【確認】

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

基本的に使うのは、

      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

email encrypted_passdord この、2つです。もちろん名前など必要であれば、migration前に追加しておく事も可能ですし、追加も可能です。

また、

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

この部分は先程モデルで消した機能のカラムです。パスワードのリセット等に使っているカラムで、使わない場合消すことも可能です。

今回はどちらも触る必要がないので、このまま進めます。

では、マイグレーションを実行しましょう。

task_app % rails db:migrate
 todo_app % rails db:migrate
== 20231108012222 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0012s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0006s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0003s
== 20231108012222 DeviseCreateUsers: migrated (0.0020s) =======================

出来たら起動してみましょう。

todo_app % bin/dev

でサーバーを起動します。(すでに起動している場合は再起動しましょう。)

そして、users/sign_in にアクセスしましょう。

Image from Gyazo

ログイン画面となります。 sign_up のリンクを押下すると、

Image from Gyazo

サインアップ画面になります。直接見るなら、users/sign_up ですね。

適当に情報をを入力してサインアップしてみましょう。

すると、

Image from Gyazo

トップページに遷移しました。これはDeviseのデフォルト設定で、サインアップ後にはルートのパスにリダイレクトするためです。

で、ここからなのですが、ログアウトする方法がありませんので、仮にログアウトのボタンを作ってみます。

ログアウトは、先程 rails routes でルーティングを調べた際に、

destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy

となっていましたので、

<%= button_to "logout", destroy_user_session_path, method: :delete %>

このように、 destroy_user_session というprefixに _path をつけてurlヘルパーで相対パスを作ります。

Rails7の場合、 deletebutton_to で作ります。(link_toでも出来なくはないのですが今は省きます)

これを、tasks/index.html.erb に書き足します。

app/views/tasks/indes.html.erb【追記】

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

<!-- ↓ここに足す -->
<%= button_to "logout", destroy_user_session_path, method: :delete %>

この状態で、ブラウザの更新をすると、

Image from Gyazo

ボタンが出てきますので、押すと、

Image from Gyazo

緑のメッセージが出てきてログアウトが完了します。

(現在このメッセージは ja の翻訳ファイルがないので、読めないという表示が出ています。)

これで、リダイレクトされずに、ログイン画面に入る事が出来ます。

Image from Gyazo

i18n

上記の 翻訳の不具合も、

devise-i18n というgem があるのですが、

ja のファイルだけでも

こちらから作成可能なので、localeに追加しておきましょう。

config/locales/devise.ja.yml【新規作成】

ja:
  activerecord:
    attributes:
      user:
        confirmation_sent_at: パスワード確認送信時刻
        confirmation_token: パスワード確認用トークン
        confirmed_at: パスワード確認時刻
        created_at: 作成日
        current_password: 現在のパスワード
        current_sign_in_at: 現在のログイン時刻
        current_sign_in_ip: 現在のログインIPアドレス
        email: Eメール
        encrypted_password: 暗号化パスワード
        failed_attempts: 失敗したログイン試行回数
        last_sign_in_at: 最終ログイン時刻
        last_sign_in_ip: 最終ログインIPアドレス
        locked_at: ロック時刻
        password: パスワード
        password_confirmation: パスワード(確認用)
        remember_created_at: ログイン記憶時刻
        remember_me: ログインを記憶する
        reset_password_sent_at: パスワードリセット送信時刻
        reset_password_token: パスワードリセット用トークン
        sign_in_count: ログイン回数
        unconfirmed_email: 未確認Eメール
        unlock_token: ロック解除用トークン
        updated_at: 更新日
    models:
      user: ユーザー
  devise:
    confirmations:
      confirmed: メールアドレスが確認できました。
      new:
        resend_confirmation_instructions: アカウント確認メール再送
      send_instructions: アカウントの有効化について数分以内にメールでご連絡します。
      send_paranoid_instructions: メールアドレスが登録済みの場合、本人確認用のメールが数分以内に送信されます。
    failure:
      already_authenticated: すでにログインしています。
      inactive: アカウントが有効化されていません。メールに記載された手順にしたがって、アカウントを有効化してください。
      invalid: "%{authentication_keys}またはパスワードが違います。"
      last_attempt: もう一回誤るとアカウントがロックされます。
      locked: アカウントはロックされています。
      not_found_in_database: "%{authentication_keys}またはパスワードが違います。"
      timeout: セッションがタイムアウトしました。もう一度ログインしてください。
      unauthenticated: ログインもしくはアカウント登録してください。
      unconfirmed: メールアドレスの本人確認が必要です。
    mailer:
      confirmation_instructions:
        action: メールアドレスの確認
        greeting: "%{recipient}様"
        instruction: 以下のリンクをクリックし、メールアドレスの確認手続を完了させてください。
        subject: メールアドレス確認メール
      email_changed:
        greeting: こんにちは、%{recipient}様。
        message: メールアドレスの(%{email})変更が完了したため、メールを送信しています。
        message_unconfirmed: メールアドレスが(%{email})変更されたため、メールを送信しています。
        subject: メール変更完了
      password_change:
        greeting: "%{recipient}様"
        message: パスワードが再設定されました。
        subject: パスワードの変更について
      reset_password_instructions:
        action: パスワード変更
        greeting: "%{recipient}様"
        instruction: パスワード再設定の依頼を受けたため、メールを送信しています。下のリンクからパスワードの再設定ができます。
        instruction_2: パスワード再設定の依頼をしていない場合、このメールを無視してください。
        instruction_3: パスワードの再設定は、上のリンクから新しいパスワードを登録するまで完了しません。
        subject: パスワードの再設定について
      unlock_instructions:
        action: アカウントのロック解除
        greeting: "%{recipient}様"
        instruction: アカウントのロックを解除するには下のリンクをクリックしてください。
        message: ログイン失敗が繰り返されたため、アカウントはロックされています。
        subject: アカウントのロック解除について
    omniauth_callbacks:
      failure: "%{kind} アカウントによる認証に失敗しました。理由:(%{reason})"
      success: "%{kind} アカウントによる認証に成功しました。"
    passwords:
      edit:
        change_my_password: パスワードを変更する
        change_your_password: パスワードを変更
        confirm_new_password: 確認用新しいパスワード
        new_password: 新しいパスワード
      new:
        forgot_your_password: パスワードを忘れましたか?
        send_me_reset_password_instructions: パスワードの再設定方法を送信する
      no_token: このページにはアクセスできません。パスワード再設定メールのリンクからアクセスされた場合には、URL をご確認ください。
      send_instructions: パスワードの再設定について数分以内にメールでご連絡いたします。
      send_paranoid_instructions: メールアドレスが登録済みの場合、パスワード再設定用のメールが数分以内に送信されます。
      updated: パスワードが正しく変更されました。
      updated_not_active: パスワードが正しく変更されました。
    registrations:
      destroyed: アカウントを削除しました。またのご利用をお待ちしております。
      edit:
        are_you_sure: 本当によろしいですか?
        cancel_my_account: アカウント削除
        currently_waiting_confirmation_for_email: "%{email} の確認待ち"
        leave_blank_if_you_don_t_want_to_change_it: 空欄のままなら変更しません
        title: "%{resource}編集"
        unhappy: 気に入りません
        update: 更新
        we_need_your_current_password_to_confirm_your_changes: 変更を反映するには現在のパスワードを入力してください
      new:
        sign_up: アカウント登録
      signed_up: アカウント登録が完了しました。
      signed_up_but_inactive: ログインするためには、アカウントを有効化してください。
      signed_up_but_locked: アカウントがロックされているためログインできません。
      signed_up_but_unconfirmed: 本人確認用のメールを送信しました。メール内のリンクからアカウントを有効化させてください。
      update_needs_confirmation: アカウント情報を変更しました。変更されたメールアドレスの本人確認のため、本人確認用メールより確認処理をおこなってください。
      updated: アカウント情報を変更しました。
      updated_but_not_signed_in: あなたのアカウントは正常に更新されましたが、パスワードが変更されたため、再度ログインしてください。
    sessions:
      already_signed_out: 既にログアウト済みです。
      new:
        sign_in: ログイン
      signed_in: ログインしました。
      signed_out: ログアウトしました。
    shared:
      links:
        back: 戻る
        didn_t_receive_confirmation_instructions: アカウント確認のメールを受け取っていませんか?
        didn_t_receive_unlock_instructions: アカウントのロック解除方法のメールを受け取っていませんか?
        forgot_your_password: パスワードを忘れましたか?
        sign_in: ログイン
        sign_in_with_provider: "%{provider}でログイン"
        sign_up: アカウント登録
      minimum_password_length: "(%{count}字以上)"
    unlocks:
      new:
        resend_unlock_instructions: アカウントのロック解除方法を再送する
      send_instructions: アカウントのロック解除方法を数分以内にメールでご連絡します。
      send_paranoid_instructions: アカウントが見つかった場合、アカウントのロック解除方法を数分以内にメールでご連絡します。
      unlocked: アカウントをロック解除しました。
  errors:
    messages:
      already_confirmed: は既に登録済みです。ログインしてください。
      confirmation_period_expired: の期限が切れました。%{period} までに確認する必要があります。 新しくリクエストしてください。
      expired: の有効期限が切れました。新しくリクエストしてください。
      not_found: は見つかりませんでした。
      not_locked: はロックされていません。
      not_saved:
        one: エラーが発生したため %{resource} は保存されませんでした。
        other: "%{count} 件のエラーが発生したため %{resource} は保存されませんでした。"

すると、

Image from Gyazo

メッセージが日本語化されました。

では次に、よりスムーズに ログイン・ログアウト等の認証を行うために、ナビゲーションバーを作っていきたいと思います。

3. ナビゲーションバーに動的なボタンを作る

ナビゲーションバー

ナビゲーションバーはパーシャルで作っていきます。

下記のファイルを作成して下さい。

app/views/layouts/_global_navigation.html.erb【新規作成】

<nav class="navbar bg-body-tertiary">
  <div class="container">
    <a class="navbar-brand" href="/"><%= t "activerecord.models.task" %>リスト</a>
    <div>
      <% if current_user %>
        <ul class="navbar-nav ms-auto">
          <li class="nav-item">
            <%= button_to "ログアウト", destroy_user_session_path, class: "nav-link", method: :delete %>
          </li>
        </ul>
      <% else %>
        <ul class="navbar-nav ms-auto">
          <li class="nav-item">
            <%= link_to "ログイン", new_user_session_path, class: "nav-link" %>
          </li>
        </ul>
      <% end %>
    </div>
  </div>
</nav>

ここの条件式でボタンを変更する事ができます。

<% if user_signed_in? %>
  ログインしている時に有効なView
<% else %>
 それ以外の時に有効なView
<% end %>

モデル名_signed_in? は Deviseで実装されているヘルパーで、ログインしているかどうかを true false で返すメソッドです。

同様にDeviseで実装されている current_モデル名 でも代用可能ですが、こちらはログインしている Userインスタンス を返すか nil を返しますので、 条件式で同様な動きとなります。

<% if current_user %>
  ログインしている時に有効なView
<% else %>
 それ以外の時に有効なView
<% end %>

これでも同じ動きです。

そして、呼び出し側は layouts/application.html.erb ですので、

app/views/layouts/application.html.erb【確認】

<!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>
    <div class="container my-4">
      <%= yield %>
    </div>
  </body>
</html>

この記述の上に入れますので、

  <div class="container my-4">
    <%= yield %>
  </div>

app/views/layouts/application.html.erb【編集】

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

これでOKです。

ログイン時
Image from Gyazo

ログアウト時
Image from Gyazo

これで認証機能が完成です。

最後に、先程テストで作ったボタンは消しておきましょう。

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

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

4. git

git

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

todo_app % git add .
todo_app % git commit -m "Deviseの導入"

GitHub

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

おわりに

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

次回は

  • カスタムビュー
  • Bootstrapの適用
  • フラッシュメッセージ

を学びます。

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

0
0
2

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?