【Rails】AdminLTEを用いて管理画面機能を搭載

##手順
1.AdminLTE3をインストール
2.管理者画面用のマニフェストファイルの設定
3.管理者権限を判定する為のカラムを追加
4.管理者用のコントローラー作成
5.ルーティングの設定
6.ビューファイルの作成

##1.AdminLTE3をインストール
``AdminLTE3``とは、Bootstrap3をベースにした、管理画面等のテンプレートテーマ。
今回は``yarn``を使って``AdminLTE3``をインストールする。

ターミナルにて**``yarn add admin-lte@^3.1``**を実施。
すると``node_modules``/``package.json``/``yarn.lock``というファイルが生成される。

多数テンプレートファイルが用意されているので、その中の``node_modules/admin-lte/starter.html``を参照。

読み込むべきCSSファイル・JSファイルが記載されているので、これを参照しながら``admin.scss``、``admin.js``2つのマニュフェストファイルを作成。

>**マニフェストファイル**というのは、どのCSSファイルやJavascriptファイルを呼び出すかを記載したファイルのこと。

##2.管理者画面用のマニフェストファイルの設定
今まで一般ユーザー用のマニフェストファイルは

``app/assets/javascripts/application.js``と
``app/assets/stylesheets/application.scss``だったが、

管理者画面は一般ユーザーの画面と構成や見た目が大きく異なる事から、別々に管理するのが一般的なのでその設定をしていく。

まずはjsファイルから設定していく。

```app/assets/javascripts/application.js
#この1文を削除
//= require_tree .　
```
```app/assets/javascripts/admin.js
#admin.jsを作成し、以下を追記
//= require jquery3
//= require jquery_ujs

//= require admin-lte/plugins/bootstrap/js/bootstrap.bundle.min
//= require admin-lte/dist/js/adminlte.min

# これは必要ない → //= require admin-lte/plugins/jquery/jquery.min
#これはjQuery本体を読み込む記述なので、Gemfileに記述してインストールしているgemのjquery-railsと重複しているので不要
```

``require_tree``には以下の特徴がある。

■指定したディレクトリ以下のすべてのJSファイルを読み込む
``require_tree .``の場合は同じ階層以下すべてのJSファイルを読み込むということ。

■読み込む順序は指定できない。
そのため、利用しているJSファイルが特定の読み込み順に依存している場合に不都合がある。

今回は``application.js``と同じ階層に``admin.js``を作成したので、``application.js``で``require_tree .``してしまうと、``application.js``で``admin.js``を読み込んでしまう。

つまり管理画面でしか使わないはずのファイルまで、``application.js``で読み込んでしままう。それを避けるために``require_tree .``は使わずに、ファイルを、個別に必要な順序で読み込む必要がある。

次にscssファイルを設定。

```admin.scss
#以下の2文を追加
@import 'admin-lte/plugins/fontawesome-free/css/all.min.css';
@import 'admin-lte/dist/css/adminlte.css';
```

``application``以外のマニフェストファイルを個別に読み込む場合は、プリコンパイルの設定をしないと、そのファイルは対象外とされてしまうためエラーが起きてしまうので、その設定も行う。
>プリコンパイルとは、「事前に(pre)」コンパイルを行うこと。
コンパイルとは、SCSS等ブラウザが直接は読めない形式のファイルを、JSやCSSに変換してあげるということ。
何に対して「事前」かというと、**「rails serverが走るよりも前」**に該当する。

```config/initializers/aseets.rb
#この１行がコメントアウトされいるので、表示させる
Rails.application.config.assets.precompile += %w( admin.js admin.css )
```

##3.管理者権限を判定する為のカラムを追加
管理者権限を判定するカラムを今回は、一般ユーザと管理者を区別し、整数の``integer型``で``roleカラム``を追加する。

ターミナルで``rails g migration add_to_role_users``を実行。

```add_to_role_users.rb
class AddRoleToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :role, :integer, default: 0, null: false
     #↑一般ユーザが0・管理者は1とし、デフォルトは一般ユーザである0と指定
  end
end
```
``usersモデル``に``enum``の設定をしていく。一般ユーザーを``general``、管理者を``admin``として定義。

```model/user.rb
#この1文を追記
enum role: { general: 0, admin: 1 }
```

▷[enumについての詳細はここ](https://qiita.com/mmaumtjgj/items/3b5903814fdda767fb1a)

##4.管理者用のコントローラー作成
管理者用のコントローラ-を作成するにあたって、「管理系」のカテゴリとして区分したいため、``Admin::BaseController``という名前で作成。
この名前は、``Admin``というモジュールの名前空間の中に``BaseController``というクラスを定義するという意味になる。

``Rails``ではモジュール階層を、コードを保存するためのディレクトリ階層に対応させているので、``admin/base_controller.rb``というファイルが対応する事になる。

後々、管理系の機能を増やす時に、``Admin::``のついたコントローラーを追加していけば、コードが``admin``ディレクトリ配下にまとまるので視認性が良くなる。

```ターミナル.
rails g controller Admin::Base

#↓管理者用のトップページ用のコントローラーを作成
rails g controller Admin::Dashboards index

#↓管理者用のログイン用のコントローラーを作成
rails g controller Admin::User_sessions
```
図で表すとこんな感じ。
[![Image from Gyazo](https://i.gyazo.com/8a6d632b02c3d69543e3b5b6f25d811b.png)](https://gyazo.com/8a6d632b02c3d69543e3b5b6f25d811b)

ではコントローラーの中身を記述していく。
管理者機能の根幹となる``Admin::BaseController``に

```controllers/admin/base_controller.rb
class Admin::BaseController < ApplicationController
  bofore_action :check_admin
   #↓layout宣言
  layout 'admin/layouts/application'

  private

  def not_authenticated
    redirect_to admin_login_path, warning: "ログインしてください"
  end

　　　　#↓管理者権限を持っているか確認する
  def check_admin
    redirect_to root_path, warning: "管理者権限を持っていません" unless current_user.admin?
  end
end
```
通常、何も指定が無ければコントローラーは``app/views/layouts/application.html.erb``をレイアウトとして探索する。

管理者機能の根幹クラスである``Admin::BaseController``で個別の管理者用画面のレイアウトファイルを呼び込むように**layout宣言**がいる。

```controllers/admin/user_sessions_controller.rb
class Admin::UserSessionsController < Admin::BaseController
  skip_before_action :require_login, only: %i[new create]
  skip_before_action :check_admin, only: %i[new create]
   # ↓ログインページ用のレイアウトを用意するので宣言
  layout 'admin/layouts/admin_login'

  def new; end

  def create
  　@user = login(params[:email], params[:password])
　  if @user
      redirect_to admin_root_path  , success: 'ログインしました'
    else
      flash.now[:danger] = 'ログインに失敗しました'
      render :new
    end
  end

  def destroy
    logout
    redirect_to admin_login_path, success: 'ログアウトしました'
  end
end
```

``Admin::UserSessionsコントローラー``も``Admin::Baseコントローラー``を継承してるで、そのままでは``admin/layouts/application``をレイアウトとして使用する。

しかしログイン画面は違う見た目にするので、別のログインページ用のレイアウトファイルを用意してlayout宣言をする必要がある。

また、ログインしていないユーザや、管理者権限のないユーザでも、ログイン画面が表示されるように``skip_before_action``でフィルタをスキップさせる。

ログインした後は``admin_root_path``へリダイレクトされ、``admin/dashboards#index``が呼び出される。

このとき、管理者ではない一般ユーザでログインした場合は、``before_action :check_admin``フィルタによって、一般ユーザ用のトップページへリダイレクトされる。

##5.ルーティングの設定
``Admin``モジュールの名前空間を使用したコントローラを作成したので、それに対応するようにルーティングにも名前空間を設定。

```routes.rb
namespace :admin do
  root to: 'dashboards#index'
  get 'login', to: 'user_sessions#new'
  post 'login', to: 'user_sessions#create'
  delete 'logout', to: 'user_sessions#destroy'
end
```

##6.ビューファイルの作成
``starter.html``から流用してレイアウトファイルを作成。
管理者画面用のマニフェストファイル``admin.scss``と``admin.js``を読み込む記述を忘れずに注意。

```views/admin/layouts/application.html.rb
<!DOCTYPE html>

<html>
  <html>
  <head>
    <meta charset="utf-8">
    <meta lang='ja'>
    <meta name="robots" content="noindex, nofollow">
    <title><%= page_title(yield(:title), admin: true) %></title>
    <%= csrf_meta_tags %>
　　　　　　　　　　# ↓ここでadmin.scssを読み込む
    <%= stylesheet_link_tag 'admin', media: 'all' %>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
  </head>

  <body class="hold-transition sidebar-mini">
  <div class="wrapper">

    <%= render 'admin/shared/header' %>
    <%= render 'admin/shared/sidebar' %>
 
    <!-- Content Wrapper. Contains page content -->
    <div class="content-wrapper">
     <%= render 'shared/flash_message' %>
     <%= yield %>　
    </div>
    <!-- /.content-wrapper -->

    <%= render 'admin/shared/footer' %>

  </div>
　　　　　　# ↓ここでadmin.jsを読み込む
  <%= javascript_include_tag 'admin' %>
  </body>
</html>
```

・ヘッダー

```admin/shared/_header.html.rb
<!-- Navbar -->
  <nav class="main-header navbar navbar-expand navbar-white navbar-light">
    <!-- Left navbar links -->
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" data-widget="pushmenu" href="#"><i class="fas fa-bars"></i></a>
      </li>
    </ul>

    <!-- Right navbar links -->
    <ul class="navbar-nav ml-auto">
      <!-- Navbar Search -->
      <li class="nav-item">
        <%= link_to t('defaults.logout'), admin_logout_path, method: :delete, class: 'nav-link' %>
      </li>
    </ul>
  </nav>
<!-- /.navbar -->
```

・フッター

```admin/shared/_footer.html.rb
<!-- Main Footer -->
<footer class="main-footer">
  <strong>Copyright © 2022 SUMPLE.</strong>
  All rights reserved. 
</footer>
```

・サイドバー

```admin/shared/_sidebar.html.rb
<!-- Main Sidebar Container -->
<aside class="main-sidebar sidebar-dark-primary elevation-4">
  <!-- Brand Logo -->
  <a href="index3.html" class="brand-link">
    <%= image_tag 'AdminLTELogo.png', class: 'brand-image img-circle elevation-3' %>
    <span class="brand-text font-weight-light">AdminLTE 3</span>
  </a>

  <!-- Sidebar -->
  <div class="sidebar">
    <!-- Sidebar user panel (optional) -->
    <div class="user-panel mt-3 pb-3 mb-3 d-flex">
      <div class="image">
        <%= image_tag current_user.avatar_url, class: 'img-circle elevation-2' %>
      </div>
      <div class="info">
        <a href="#" class="d-block"><%= current_user.decorate.full_name %></a>
      </div>
    </div>
    <!-- Sidebar Menu -->
    <nav class="mt-2">
      <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
        <li class="nav-item">
          <%= link_to '#', class: "nav-link" do %>
            <i class="nav-icon far fa-file"></i>
            <p>
              掲示板一覧
            </p>
          <% end %>

        <li class="nav-item">
          <%= link_to '#', class: "nav-link" do %>
            <i class="nav-icon far fa-user"></i>
            <p>
              ユーザ一一覧
            </p>
          <% end %>
        </li>
      </ul>
    </nav>
    <!-- /.sidebar-menu -->
  </div>
  <!-- /.sidebar -->
</aside>
```

・管理者ログイン画面
adminログインフォームのテンプレートは``node_modules/admin-lte/pages/example/login.html``を参考に作成。

```views/admin/layouts/admin_login.html.rb
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title><%= page_title(yield(:title), admin: true) %></title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
     # ↓admin.scssを読み込む
    <%= stylesheet_link_tag 'admin', media: 'all' %>
  </head>
  <body class="hold-transition login-page">
    <div>
      <%= render 'shared/flash_message' %>
      <%= yield %>
    </div>
  </body>
</html>
```

```views/admin/user_sessions/new.html.rb
<%= content_for(:title, t('.title')) %>
<div class="login-box">
  <div class="login-logo">
    <h1><%= t('.title') %></h1>
  </div>
  <!-- /.login-logo -->
  <div class="card">
    <div class="card-body login-card-body">

      <%= form_with url: admin_login_path, locale: true do |f| %>
        <%= f.label :email, User.human_attribute_name(:email) %>
        <div class="input-group mb-3">
          <%= f.email_field :email, class: 'form-control', placeholder: 'Email'%>
          <div class="input-group-append">
            <div class="input-group-text">
              <span class="fas fa-envelope"></span>
            </div>
          </div>
        </div>
        <%= f.label :password, User.human_attribute_name(:password) %>
        <div class="input-group mb-3">
          <%= f.password_field :password, class: 'form-control', placeholder: :password %>
          <div class="input-group-append">
            <div class="input-group-text">
              <span class="fas fa-lock"></span>
            </div>
          </div>
        </div>

        <div class="row">
          <div class="col-12">
            <%= f.submit t('defaults.login'), class: 'btn btn-block btn-primary' %>
          </div>
        </div>
      <% end %>
    </div>
  </div>
</div>
```

・管理者画面用トップページ

```views/admin/dashboards/index.html.rb
<%= content_for(:title, t('.title')) %>
<div class="content-wrapper">
  <div class="row">
    <p>ダッシュボードです</p>
  </div>
</div>
```

**管理画面用のページタイトルの設定**
管理者画面が出ている時だけ、ページタイトルの末尾に``（管理画面）``と表記させる。

```app/helpers/applicaton_helper.rb
module ApplicationHelper
  def page_title(page_title = '', admin = false)
    base_title = if admin
                  'SAMPLE BOARD APP(管理画面)'
                 else
                  'SAMPLE BOARD APP'
                 end

    page_title.empty? ? base_title : page_title + ' | ' + base_title
  end
end
```
この設定で、デフォルトを``admin = false``とすることによって、既存の一般ファイルに影響が出ないようにし、管理者用レイアウトファイルで``admin: true``を設定しておくことで、ページタイトルに``（管理画面）``がつく。
（この**admin: true**の部分のこと。
``<title><%= page_title(yield(:title), admin: true) %></title>``）

##参考記事
[[Rails] AdminLTEを使って管理画面機能を作成](https://osamudaira.com/257/)
[Ruby on Rails プリコンパイル という言葉の定義は？](https://teratail.com/questions/143276)
[]()
[]()