Programming Journal

学習したことの整理用です。

AdminLTEを使って管理者用機能を実装する(トップページ)

実装したいこと

  • 管理者専用ページを作りたい
  • とりあえず今回はトップページのみ。
  • AdminLTEを使用し、このようなページに仕上げる。

f:id:Study-Diary:20200902130906p:plain
AdminLTE

実装の流れ

  • AdminLTE version3をインストール
  • マニフェストファイル設定
  • 管理者用コントローラー設定
  • Userモデルに管理者判定用カラム追加
  • ルーティング設定
  • ビューの設定

アセットパイプラインについて全く理解できておらず、AdminLTEを反映させるのに時間がかかってしまいました。
現場RailsP266が分かりやすい。

AdminLTEをインストールする

AdminLTEとは、管理画面用のテンプレートです。

公式はこちら GitHub - ColorlibHQ/AdminLTE: AdminLTE - Free admin dashboard template based on Bootstrap 4
公式、私の見方が悪いのかもしれないけど、ダウンロード方法しか見つからなかった。

yarn add admin-lte@^3.0

これで、node_modules/admin-lteが作成されます。
このディレクトリ内に各種テンプレートがあるので、コピペして管理者用画面を作成していきます。
今回は、同ディレクトリ内のstarter.htmlを使用していきます。 

ただし、インストールしただけでは、ブラウザでCSSやJSを読み込むことができません。
読み込むための設定をしていきます。 

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

今までは、app/assets/javascripts/application.js app/assets/stylesheets/application.scssに全て記述していましたが、 管理者画面は一般ユーザー用と見た目が大きく異るため、別々で管理していきます。

//= require jquery3
//= require rails-ujs
//= require activestorage
//= require popper
//= require bootstrap-sprockets
//= require edit_comment 
//= require preview

以前は、//= require tree.でapplication.js配下の全ファイルを読み込んでいました。
しかし、今回は同じ階層に管理者用のマニフェストファイルをファイルを配置するため、このままだと不必要な管理者用ファイルを読み込んでしまいます。

そのため、個別にファイルを読み込む記述に変更しました。

新しく、管理者用ページのマニフェストファイルを作成します。

//= require jquery3
//= require jquery_ujs   #rails_ujsにしたら何故かエラーになってしまったのでこっち
//= require admin-lte/plugins/bootstrap/js/bootstrap.bundle.min  #拡張子は省略できる
//= require admin-lte/dist/js/adminlte.min
@import "font-awesome-sprockets";
@import "font-awesome";
@import 'admin-lte/plugins/fontawesome-free/css/all.min.css';
@import 'admin-lte/dist/css/adminlte.min.css';

アセット関連の設定

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] #コメントアウトを外す

他のマニフェストや、個別のスタイルシート/JavaScriptファイルをインクルードしたい場合は、config/initializers/assets.rbのprecompileという配列を使用します。 (Railsガイド)

これで、admin.js admin.cssをプリコンパイルします。

Controller

管理系の機能を持つコントローラーを作成していきます。

  • 管理系コントローラーに共通する機能を持つ基底クラス、Admin::BaseControllerを作成する

  • 他の管理系コントローラーは、Admin::BaseControllerを継承する

Admin::BaseController作成

最初、admin/baseってどうやってディレクトリ階層を作るのか悩んで手動で作ろうとしていましたが、Admin::とすれば階層を自動で作ってくれる。
今後、管理系のコントローラーを追加したいときも同様。

rails g controller Admin::Base

他にも、ダッシュボード(トップページ)とログイン用のコントローラーを作っておきます。

rails g controller Admin::Dashboards index

rails g controller Admin::User_sessions new

中身については、ビューファイルを作るときに。

Userモデルにadmin判定用のカラムを追加する

rails g migration add_role_to_users 
class AddRoleToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :role, :integer, default: 0, null: false #デフォルトは0(一般ユーザー)にする。
  end
end

一般ユーザーと管理者を区別するために、roleというカラムを新たに作成し、 整数(integer)で管理していきます。
最初、boolean型でtrue false判定で区別しようかと思っていましたが、今後権限の種類が増えることを考慮し、このような構造にします。

一般人 管理者
0 1

追記したらrails db:migrateする。

enumを設定する

enumとは、先程作成したカラムの整数に定数を定義できるというものです。

enum role: { general: 0, admin: 1 }

このように定義すれば、この定数を使用することができます。
例えば、現ユーザーがadmin(管理者)か判定したいとき、current_user.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

/admin で始まるURLにしたいので、namespace :admin名前空間を設定します。

f:id:Study-Diary:20200902161918p:plain
rails routes

Views

現状は、views/layouts/application.html.erbviews/shared/_header.html.erbのような全てに共通するテンプレートファイルを使用しています。
特定のコントローラーだけテンプレートを切り替えたいとき、どうすればいいのか悩みました。

管理者機能用テンプレートをviews/admin/layout/に用意して、基底クラスのbase_controllerlayout宣言をすれば良さそうです。
(最初、dashboars_controllerなどにも個別で宣言を記述していましたが、base_controllerを継承する設定にするので個別の記載は不要でした。)

レイアウトとレンダリング - Railsガイド

コントローラやアクション毎に使用するレイアウトを切り替える - Ruby on Rails入門

admin/base_controller.rb

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

admin/dashboards_controller.rb

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

class Admin::DashboardsController < Admin::BaseController
ここの記述でbase_controllerを継承します。
そのため、レイアウト宣言は不要です。

管理者用テンプレートファイル

<!DOCTYPE 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 %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag    'admin', media: 'all' %>  #ブラウザにアセットを読み込ませる
    <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
  </head>

  <body class="hold-transition sidebar-mini layout-fixed">
  <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' %>  #ブラウザにアセットを読み込ませる
  </body>
</html>

node_modules/admin-lte/starter.htmlから必要部分を切り分けしています。
header/sidebar/footerは切り分けて、views/admin/sharedに配置しました。詳細は省略します。
管理者用共通ビューのこのファイルで、先程設定したadmin.js admin.scssを読み込んでいます。

ダッシュボード用ビュー

<% content_for(:title, t('.title')) %>   #タイトルは次節で解説
<div class="content-wrapper">
  <div class="row">
   <p>ダッシュボードです</p>
  </div>
</div>

タイトルの設定

タイトルは以前、カスタムヘルパーを使い動的に出力していました。 study-diary.hatenadiary.jp

今回、管理者用のページは「ダッシュボード | 固定タイトル(管理用)」のように、(管理用)と出力したいです。

module ApplicationHelper
   def page_title(page_title = '', admin = false)
     base_title = if admin
                    'Ruby on Rails 学習記録(管理画面)'
                  else
                    'Ruby on Rails 学習記録'
                  end

     page_title.empty? ? base_title : page_title + ' | ' + base_title
   end

デフォルトでadmin = falseにしておきます。
これで、今まで作ってきたタイトルはadmin: falseなので書き換えが不要です。
最後に評価された式を戻り値としてbase_titleに代入しています。

ちなみに、私が最初に実装したコードはこっち。
反省用に記録しておきます。
条件分岐するのではなく、admin用のカスタムヘルパーを新たに作成してしまいました。
これだとビューに反映させるときに、汎用的に使えないので没です。
三項演算子も使っていないのでコードが冗長になっています。

module ApplicationHelper
  # ページごとの完全なタイトルを返す
  def page_title(page_title = '')
    base_title = 'Ruby on Rails 学習記録'
    if page_title.empty?
      base_title
    else
      page_title + ' | ' + base_title
    end
  end

  def adminpage_title(page_title = '')
    base_title = 'Ruby on Rails 学習記録(管理画面)'
    if page_title.empty?
      base_title
    else
      page_title + ' | ' + base_title
    end
  end
end

管理用共通テンプレートに読み込み

繰り返しになるので、タイトル部分だけ抜粋
admin: trueにします。

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

各ビュー

<% content_for(:title, t('.title')) %>  #titleは翻訳ファイルに記入

各ビューでこのように読み込めばOK

翻訳ファイル

admin/dashboards_controllerのように階層があるコントローラーの翻訳ファイルはどういうふうに書けばいいのか迷いました。
このように翻訳ファイルでも階層を作ってあげれば大丈夫でした。

f:id:Study-Diary:20200903075344p:plain
ja.yml

参考

アセットパイプライン - Railsガイド

管理画面を作る:AdminLTE 基本編 - Qiita

[管理画面]Rails 5 に yarnでインストールした「AdminLTE3.0.0-alpha.2」 を適用させる方法 - Qiita

俺たちは雰囲気でAdminLTEを使っている - Qiita

コントローラやアクション毎に使用するレイアウトを切り替える - Ruby on Rails入門

https://pikawaka.com/rails/enum#boolean%E3%81%AE%E5%A0%B4%E5%90%88