Railsでログイン機能を作る


<h1>Railsでログイン機能をつくってみました</h1>

今回作るログイン機能はタスク管理アプリで
初回利用時にユーザー登録を行い、その後利用するときは「私は先日に登録したユーザーです」と言う認証を行うやり方。

<h2>セッションとCookie</h2>
ログイン機能を実装する前に前提知識となるセッションとCookieについてはこちらの記事に書きました。
https://qiita.com/ombossk0720/items/5345b8ef267749e550fe


<h2>Userモデルを作る</h2>
認証機能を作るにはアプリに新しい概念を加える必要がある。
それはアプリケーションを利用する概念。
そこでユーザーを表すUserモデルを作る。
タスク管理アプリのUserモデルに最低限必要なのはメールアドレスとパスワード。
ただそれだけでは、ユーザーを表すのに常に長ったらしいメールアドレスか、暗号のような数字を
表示しなければいけなくなるので、扱いやすいようにユーザー名を別途保持することにする。

generateコマンドを使ってUserモデルをつくってみる


```% bin/rails g model user name:string email:string password_digest:string
```
generateコマンドによって、データベースにuserテーブルを作るためにマイグレーションファイルとUserモデルが作られた。
早速migrationしたいところだがまずマイグレーションファイルを編集。
名前やメールアドレスやパスワードには何かしらの文字列が必ず入るのでこれらの属性にNULLが入ることはない。
また、同じメールアドレスをもつユーザーが複数人いることはない。ありえない値が入ることを防ぐために生成した
マイグレーションファイルを開いて以下のように書き換える。


```ruby:db/migrate/xxxxxxxxxxxxx_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.string :email, null: false
      t.string :password_digest, null: false

      t.timestamps
      t.index :email, unique: true
    end
  end
end
```

書き換えたらマイグレーションを実行して、データベースにusersテーブルを作成する。

```% bin/rails db:migrate```

usersテーブルが作られ、generateコマンドでUserモデルも作られている。


<h2>ユーザー管理機能一式を追加する</h2>
次にアプリケーションの利用者をどうやってUserとしてデータベースに登録するか検討します。よくある形は

1,未登録のユーザーが自らサインアップを行う。
2,管理者がユーザーを登録することで、ユーザーがアプリケーションを利用できるようにする。

１は全世界の個人に向けて提供する場合に、2は特定のグループに属する提供する場合に適している。
そしてユーザー管理機能は管理者だけ触れるようにする必要がある。

<h2>Userモデルにadminフラグを追加する</h2>
まず、ユーザーが管理者かどうかを表すフラグを追加する。
以下のコマンドでマイグレーションファイルの雛形を作成する。

```% bin/rails g migration add_admin_to_users```

ファイルを開き以下のように編集。

```ruby:db/migrate/xxxxxxxxxxxxx_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :admin, :boolean, default: false, null: false
  end
end
```

このマイグレーションはusersテーブルにadminと言うフラグのカラムを追加する。早速適用。
```% bin/rails db:migrate```

<h2>ユーザー管理のためのコントローラを実装する</h2>
次にユーザー管理のためのコントローラを作成。
ここでは「管理系」の機能として「ユーザー管理」を行うのでAdmin::UsersControllerと言う名前をつける。
この名前は、Adminと言うモジュールの名前空間の中にUsersControllerと言うクラスを定義するという意味になる。
Railsではモジュール階層を、コードを保存するためのディレクトリ階層に対応させている。app/controllers/admin/users_controller.rbと言うファイルが対応するようになる。
これから「管理系」の機能を足したいとき、Admin::のついたコントローラを追加していけばコードがAdminディレクトリの下にまとまって
わかりやすくなる。

まず、Admin::UsersControllerを画面を伴うGETアクションのビューとともに以下のコマンドで作ります。

```% bin/rails g controller Admin::Users new edit show index ```


generateの機能によってnew,edit,show,indexのからのアクションとデフォルトのビューができた。
合わせてroutes.rbにも自動で定義が追加されるがこれは決めている仕様と合わないので、エディタで編集。
admin/users/で始まるURLの書かれた記述を全て削除して次のように内容を編集する。

```ruby:config/routes.rb
Rails.application.routes.draw do
  namespace :admin do
    resorces :users
  end
  root to: 'tasks#index'
  resources :tasks
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
```

これで想定したURLで適切なアクションにリクエストが飛ぶようになる。各機能のURLを得るには、admin_users_pathやadmin_user_pathなど
adminがついたヘルパーメソッドを使う。

ここからは各機能を作り込んでいく。

<h4>登録の機能</h4>
コントローラ(app/controllers/admin/users_controller.rb)とビュー(app/controllers/admin/users/new.html.slim)を以下のように変更。

```ruby:app/controllers/admin/users_controller.rb
class Admin::UsersController < ApplicationController
  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end
  def new
    @user = User.new
  end

  def edit
    @user = User.find(params[:id])
  end

  def create
    @user = User.new(user_params)

    if @user.save
      redirect_to admin_user_url(@user), notice: "ユーザー「#{@user.name}」を登録しました。"
    else
      render :new
    end
  end

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      redirect_to admin_user_url, notice: "ユーザー「#{@user.name}」を更新しました。"
    else
      render :edit
    end
  end

  def destroy
    @user = User.find(params[:id])
    @user.destroy
    redirect_to admin_users_url, notice: "ユーザー「#{@user.name}」を削除しました。"
  end
  private

  def user_params
    params.require(:user).permit(:name, :email, :admin, :password, :password_confirmation)
  end
end
```
ここからnew,edit,show,indexと次のようにコードを書いてもいいのですが、面倒なのでパーシャルを使います。
```ruby:app/controllers/admin/users/new.html.slim
h1 ユーザー登録

= form_with model: [:admin, @user], local: true do |f|
  .form-group
    = f.label :name, '名前'
    = f.text_field :name, class: 'form-control'
  .form-group
    = f.label :email, 'メールアドレス'
    = f.text_field :email, class: 'form-control'
  .form-check
    = f.label :admin, class: 'form-check-label' do
      = f.check_box :admin, class: 'form-check-input'
      | 管理者権限
  .form-group
    = f.label :password, 'パスワード'
    = f.password_field :password, class: 'form-control'
  = f.submit '登録する', class: 'btn btn-primary'
```
これを各それぞれのファイルに書き込むのはしんどそうですよね。
ここから先の手順でやれば書き込んだことと同じことになります。パーションファイルはファイル名を_から始めます。
今回はform系のことなのでviews/admin/users/index.html.slimと同じ階層に新規で_form.html.slimファイルを作成して
以下のようにコードを入力していきます。

```ruby:views/admin/users/_form.html.slim
- if user.errors.present?
  ul#error_explanation
    - user.errors.full_messages.each do |message|
      li= message

= form_with model: [:admin, user], local: true do |f|
  .form-group
    = f.label :name, '名前'
    = f.text_field :name, class: 'form-control'
  .form-group
    = f.label :email, 'メールアドレス'
    = f.text_field :email, class: 'form-control'
  .form-check
    = f.label :admin, class: 'form-check-label' do
      = f.check_box :admin, class: 'form-check-input'
      | 管理者権限
  .form-group
    = f.label :password, 'パスワード'
    = f.password_field :password, class: 'form-control'
  .form-group
    = f.label :password_confirmation, 'パスワード(確認)'
    = f.password_field :password_confirmation, class: 'form-control'
  = f.submit '登録する', class: 'btn btn-primary'
```
<strong>！インデントエラーがよく発生するので注意！</strong>

_form.html.slimが作成できたら
index,edit,show,newファイルを以下のように編集していきます。

```ruby:views/admin/users/index.html.slim
h1 ユーザー一覧

= link_to '新規登録', new_admin_user_path, class: 'btn btn-primary'

.mb-3
table.table.table-hover
  thed.thed-default
    tr
      th= User.human_attribute_name(:name)
      th= User.human_attribute_name(:email)
      th= User.human_attribute_name(:admin)
      th= User.human_attribute_name(:created_at)
      th= User.human_attribute_name(:updated_at)
      th
  tbody
    - @users.each do |user|
      tr
        td= link_to user.name,[:admin, user]
        td= user.email
        td= user.admin? ? 'あり' : 'なし'
        td= user.created_at
        td= user.updated_at
        td
          = link_to '編集', edit_admin_user_path(user), class: 'btn btn-primary mr-3'
          = link_to '削除', [:admin, user], method: :delete,data: { confirm: "ユーザー「#{user.name}」を削除します。よろしいですか？"}, class: 'btn btn-danger'
```

```ruby:views/admin/users/new.html.slim
h1 ユーザー登録

.nav.justify-content-end
  = link_to '一覧', admin_users_path, class: 'nav-link'

= render partial: 'form', locals: { user: @user }
```

```ruby:views/admin/users/edit.html.slim
h1 ユーザーの編集

.nav.justify-content-end
  = link_to '一覧', admin_users_path, class: 'nav-link'

= render partial: 'form', locals: { user: @user }
```

```ruby:views/admin/users/show.html.slim
h1 ユーザーの詳細

.nav.justify-content-end
  = link_to '一覧', admin_users_path, class: 'nav-link'
table.table.table-hover
  tbody
    tr
      th= User.human_attribute_name(:id)
      td= @user.id  
    tr
      th= User.human_attribute_name(:name)
      td= @user.name
    tr
      th= User.human_attribute_name(:email)
      td= @user.email
    tr
      th= User.human_attribute_name(:admin)
      td= @user.admin? ? 'あり' : 'なし'
    tr
      th= User.human_attribute_name(:created_at)
      td= @user.created_at
    tr
      th= User.human_attribute_name(:updated_at)
      td= @user.updated_at

= link_to '編集', edit_admin_user_path, class: 'btn btn-primary mr-3'
= link_to '削除', [:admin, @user], method: :delete, data: { confirm: "ユーザー「#{@user.name}」を削除します。よろしいですか？" }, class: 'btn btn-danger'
```

configも追加します

```ruby:config/locales/ja.yml
 models:
      task: タスク
    attributes:
      task:
        id: ID
        name: 名称
        description: 詳しい説明
        created_at: 登録日時
        updated_at: 更新日時
      
      user:
        name: 名前
        email: メールアドレス
        admin: 管理者権限
        password: パスワード
        password_confirmation: パスワード(確認)
        created_at: 登録日時
        updated_at: 更新日時
```

以上で、ユーザー管理機能の基礎となる部分は出来上がりで/admin/usersにアクセスすれば利用することができます。


長い道のりでした。



"参考書籍：現場で使える Ruby on Rails5速習実践ガイド"