[Rails] AdminLTEを使って管理画面機能を作成

学習記録

はじめに

管理画面へのログイン機能を実装しましょう。管理画面のトップページも作成しましょう。

作業の流れ

  • AdminLTEバージョン3をyarnでインストール
  • 管理者画面用のマニフェストファイルを作成
  • マニフェストファイルの読み込み設定
  • usersテーブルに管理者権限を判定する為のroleカラムを追加
  • 管理者用のコントローラを作成(admin/base_controller)
  • 管理画面用のトップページに遷移するコントローラーを作成(admin/dashboards_controller.rb)
  • 管理者ログイン機能を担うコントローラを作成(admin/user_sessions_controller)
  • ルーティングの設定
  • ビューファイルの作成
  • 管理画面用のページタイトルの設定
  • 管理者ログイン機能を作成
  • ローケルファイル追記

AdminLTEバージョン3をyarnでインストール

今回はでパッケージ管理ツールであるyarn を使いましょう。

こちらを参考にします。

$ yarn add admin-lte@^3.1

node_modulespackage.jsonyarn.lockというファイルが生成されます。

node_modules/admin-lte ディレクトリ内に、多数テンプレートファイルが用意されていますので、その中のstarter.html を使います。

管理者画面用のマニフェストファイルを作成

今まで一般ユーザのマニフェストファイルはapp/assets/stylesheets/application.scssapp/assets/javascripts/application.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 common.js
  • //= require rails_ujs だと現在はエラ-が起こるようで、//= require jquery_ujs を記述する必要があります。

参考:https://qiita.com/Statham/items/372234e23749ff1f6bf8

  • //= require admin-lte/plugins/jquery/jquery.min は必要ない

こちらはjQuery本体を読み込む記述なので、Gemfileに記述してインストールしているgemのjquery-railsと重複しているので、不要です。

//= require jquery3は、jquery-rails を利用するための記述です。

  • 「.min」はminifyを表す

.min はパソコンにとって可読性が良いコードの形式に変換したものです。minifyすることで読み込み速度が向上します。

参考:https://webukatu.com/wordpress/blog/37071/

admin.scssの設定

@import 'admin-lte/plugins/fontawesome-free/css/all.min.css';
@import 'admin-lte/dist/css/adminlte.min.css';

はじめ私は @import 'font-awesome-sprockets';@import 'font-awesome'; も記入していましたが無くても動作しました。おそらくAdminLTEバージョン3からインポートしている上の記述だけでfontawesomeは使えるようになっているのだと思います。

application.jsの設定を変更

今までは//= require_tree .としていました。こちらを設定していると、同じ階層以下すべてのJSファイルを読み込む設定になります。また、読み込む順番も指定することができません。

今回、app/assets/javascripts/application.jsと同じ階層に、app/assets/javascripts/admin.jsを作っていますので、app/assets/javascripts/application.js//= require_tree .を記述していますと、app/assets/javascripts/application.jsapp/assets/javascripts/admin.jsを読み込んでしまいます。

不要なファイルを読み込むのを避けるために、//= require_tree . は使わず、必要なファイルを適切な順番で個別に記述しましょう。

//= require jquery3
//= require popper
//= require bootstrap-sprockets

//= require rails-ujs
//= require activestorage
//= require common.js
//= require cable.js

マニフェストファイルの読み込み設定

次にプリコンパイルの設定をします。application以外のマニフェストファイルを個別に読み込む場合は、プリコンパイルの設定をしないと、そのファイルは対象外とされてしまうためエラーが起きてしまいます。

# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'

# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules')

# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
Rails.application.config.assets.precompile += %w[admin.js admin.css]

Rails.application.config.assets.precompile += %w( admin.js admin.css ) のコメントアウトを外してプリコンパイルの設定をします。

usersテーブルに管理者権限を判定する為のroleカラムを追加

特定のuserだけが管理者ページにアクセスできるように権限を付与させたいので、role カラムを追加します。マイグレーションファイルを作成しましょう。

$ rails g migration add_role_to_users

作成されたマイグレーションファイルを見ます。

class AddRoleToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :role, :integer, default: 0, null: false
  end
end

一般ユーザと管理者を区別し、整数のinteger型でrole というカラムを追加しています。

一般ユーザが0、管理者は1とします。デフォルトは一般ユーザである0とします。

一般ユーザ0
管理者1

enumを設定

enumとは、モデルの数値カラムに対して文字列による名前を付けれるようになる仕組みです。

今回は一般ユーザをgeneral、管理者をadminとして定義していきます。

一般ユーザgeneral
管理者admin

user.rbにenumの設定を追記します

class User < ApplicationRecord
	enum role: { general: 0, admin: 1 }
end

管理者用のコントローラを作成

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

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

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

では、コントローラを作成するコマンドを入力しましょう。

$ rails g controller Admin::Base

管理画面用のトップページに遷移するコントローラーを作成

admin/dashboards_controller.rb となるように、管理者用のトップページ用のコントローラを作成します。

$ rails g controller Admin::Dashboards index

管理者のログイン機能を担うコントローラを作成

管理者ログイン機能に関するコントローラも作成します。

$ rails g controller Admin::User_sessions new

ルーティングの設定

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

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

ビューファイルの設定

管理者用画面は一般用の画面とは見た目も異なるので、専用のテンプレートファイルを用意します。

views/admin/layout/配下に作成しましょう。

管理者用画面のレイアウトファイルの作成

starter.htmlから流用してレイアウトファイルを作成していきましょう。

ヘッダー、メニュー、フッター部分は別の部分テンプレートとして切り分けてviews/admin/shared に配置しましょう。

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <meta lang='ja'>
    <meta name="robots" content="noindex, nofollow">                 # SEOに関する記述
    <title><%= page_title(yield(:title), admin: true) %></title>      # 後半で使う設定
    <%= csrf_meta_tags %>  # クロスサイトフォージェリ対策
    <%= stylesheet_link_tag 'admin', media: 'all' %>   # ここでadmin.scssを読み込む
    <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' %>   # headerをレンダーする
    <%= render 'admin/shared/sidebar' %>  # sidebarをレンダーする
 
    <!-- Content Wrapper. Contains page content -->
    <div class="content-wrapper">
     <%= render 'shared/flash_message' %>  # フラッシュメッセージを呼び込む
     <%= yield %>              # bodyを読み込む
    </div>
    <!-- /.content-wrapper -->

    <%= render 'admin/shared/footer' %>   # footerをレンダーする

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

管理者画面用のマニフェストファイル assets/stylesheets/admin.scssassets/javascripts/admin.jsを作成したことを思い出してください。

管理者用のレイアウトファイルでそれらを読み込む記述をしていることに注意しましょう。

ヘッダー、メニュー、フッターは部分テンプレートとしてadmin/shared 配下に作成

ヘッダー

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

サイドバー

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

フッター

<!-- Main Footer -->
<footer class="main-footer">
  <strong>Copyright © 2019 RUNTEQ.</strong>
  All rights reserved. 
</footer>

管理者画面用トップページのビューファイル

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

Admin::Baseコントローラ にlayout宣言を追記する

通常、何も指定が無ければコントローラはapp/views/layouts/application.html.erbをレイアウトとして探索するでしょう。

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

class Admin::BaseController < ApplicationController
  layout 'admin/layouts/application'    # layout宣言
end

Admin::Dashboardsコントローラ を修正

管理者画面トップページへ遷移させるためのコントローラは、管理者機能の根幹となるAdmin::BaseControlller を継承する設計へと変更させます。

そのためlayout宣言の記述は不要です。

class Admin::DashboardsController < Admin::BaseControlller
  def index; end
end

管理画面用のページタイトルの設定

以前にカスタムヘルパーを使ってタイトルを動的に出力する方法を実装しました。

module ApplicationHelper
  def page_title(page_title = '')
    base_title = 'SAMPLE BOARD APP'
  
    page_title.empty? ? base_title : page_title + ' | ' + base_title
  end
end

今回は管理者用画面のタイトルを、

「ダッシュボード | 固定タイトル(管理画面)」のように、語尾に(管理画面)の文字が表示されるような機能も追加しましょう。

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 とすることによって、既存の一般ファイルに影響が出ないようにしています。

管理者用レイアウトファイルでタイトル設定

今一度、管理者用のレイアウトファイル(views/admin/layouts/application.html.erb)を見てみてください。

<title><%= page_title(yield(:title), admin: true) %></title>

管理者用レイアウトファイルでadmin: true を設定しておくことで、すべての管理者画面はadmin: trueが付与される仕組みとなっています。

各管理者用ビューファイル

それぞれのビューファイルからcontent_for ヘルパーを使ってタイトルを設定します。

<% content_for(:title, t('.title')) %>     # titleはローケルファイルから読み込み

管理者ログイン機能を作成

いよいよメインとなる管理者ログイン機能を実装していきます。

  • ログイン画面で読み込むレイアウトファイルはadmin_login.html.erbとし、views/layouts配下に作成
  • 管理者ログインページから一般ユーザーでログインした場合はroot_pathにリダイレクトされるよう設定
  • 管理者がログイン後は管理画面のトップページにリダイレクトされるように設定
  • 管理者がログインに失敗したら、管理者ログインページへリダイレクトされる
  • ログアウトしたらログインページへ

Admin::Baseコントローラの設定

管理者機能の根幹となるAdmin::BaseControllerに全体として使いたい機能を追記していきましょう。

class Admin::BaseController < ApplicationController
  bofore_action :check_admin
  layout 'admin/layouts/application'


  private



  def not_authenticated
    redirect_to admin_login_path, warning: t('defaults.message.require_login')
  end

  def check_admin
    redirect_to root_path, warning: t('defaults.message.not_authorized') unless current_user.admin?
  end
end

コードを紐解く

既にSorceryを導入していますので、 ApplicationControllerbefore_action :require_loginによってログインしていない場合は自動的にnot_authenticatedというメソッドが実行されます。

def require_login
  return if logged_in?

  if Config.save_return_to_url && request.get? && !request.xhr? && !request.format.json?
    session[:return_to_url] = request.url
  end

  send(Config.not_authenticated_action)
end


def not_authenticated
  redirect_to root_path
end

参考:https://github.com/Sorcery/sorcery/blob/6fdc703416b3ff8d05708b05d5a8228ab39032a5/lib/sorcery/controller.rb#L25

今回、このコントローラ内の処理ではログインしていないユーザを管理者用のログインページへリダイレクトさせたいので、not_authenticatedメソッドの中身をredirect_to_ admin_login_pathに書き換えています。

check_admin メソッドについてですが、unless current_user.admin? の部分で現在ログインしているユーザが管理者かどうかを判定しています。

admin? メソッドはというと、enumを設定した際にroleカラムの1adminとしたので使えるようになっているメソッドです。

before_action :check_admin とすることで、各アクションより先に実行されます。

Admin::UserSessionsコントローラの設定

作成しておいたAdmin::UserSessionsコントローラの中身を記述していきましょう。

class Admin::UserSessionsController < Admin::BaseController     # #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]) # Sorceryメソッド  emailとpasswordでログイン認証する
   if @user
      redirect_to admin_root_path  , success: 'ログインしました'
    else
      flash.now[:danger] = 'ログインに失敗しました'
      render :new
    end
  end

  def destroy
    logout                                      # ログアウトするためのSorceryメソッド
    redirect_to admin_login_path, success: 'ログアウトしました'
  end
end

Admin::Base コントローラではadmin/layouts/applicationをレイアウトとして使用するようlayout宣言していました。

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

しかしログイン画面は、またもや異なる見た目にするので、別のログインページ用のレイアウトファイルを用意してlayout宣言しています。

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

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

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

管理者ログイン画面のビューファイルを作成

adminログインフォームのテンプレートはnode_modules/admin-lte/pages/example/login.htmlを参考にします。

views/admin/layouts/ 配下にadmin_login.html.erbという名前で作成しましょう。

<!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 %>
    <%= stylesheet_link_tag 'admin', media: 'all' %>        # admin.scssを読み込む
  </head>
  <body class="hold-transition login-page">
    <div>
      <%= render 'shared/flash_message' %>
      <%= yield %>
    </div>
  </body>
</html>

admin.jsは読み込んでいない点に注意しましょう。

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

コードを紐解く

<%= form_with url: admin_login_path, locale: true do %>

ログイン機能のform_withはモデルと紐付かない為、送信先はadmin_login_pathです。

ロケールファイル追記

admin/dashboards_controller.rb のような階層のあるコントローラのローケルファイルはこのように記述しましょう。

admin:
  user_sessions:
    new:
      title: 'ログイン'
    create:
      success: 'ログインしました'
     fall: 'ログインに失敗しました'
    destroy:
      success:  'ログアウトしました'
  dashboards:
    index:
      title: 'ダッシュボード'

終わりに

非常に実装が多く大変でした。お疲れさまです。

参考記事

https://railsguides.jp/layouts_and_rendering.html#コントローラ用のレイアウトを指定する

https://github.com/Sorcery/sorcery/blob/6fdc703416b3ff8d05708b05d5a8228ab39032a5/lib/sorcery/controller.rb

https://railsguides.jp/routing.html#コントローラの名前空間とルーティング

コメント

タイトルとURLをコピーしました