rails_adminを使用した管理画面の作成

## 環境
Ruby: 3.2.2
Rails: 7.0.6
db: postgresql
css: tailwind css
※ユーザー機能はDeviseを使用

## rails_adminとは
Railsアプリケーションの管理画面を自動で作成してくれるgemのこと。
管理画面内でデータのCRUDができるようになる。

## cancancanとは
権限管理ができるgemのことで、特定のユーザーがアクセスできるリソースを制限することができる。
例）管理者（admin）ユーザーしか管理画面を使用できないようにする。

## 導入
```ruby:Gemfile
gem 'rails_admin', '~> 3.0'
gem 'cancancan'
```

```bash
$ bundle
```

## rails_adminのインストール
```bash
$ bin/rails g rails_admin:install
	?  Where do you want to mount rails_admin? Press <enter> for [admin] > #この表示はURLを指定するもので、デフォルトのURLは/adminとなっているため、特に変更の必要がなければ「Enter」
			route  mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
      create  config/initializers/rails_admin.rb
           -  Using [sprockets] for asset delivery method
     gemfile  sassc-rails

```

このコマンドで管理画面が作成される。

Gemfileに’sassc-rails’が追記されているが、これは管理画面がsassで実装されているため、sassをcssにコンパイルするために必要。

bundle installをしてsassc-railsをインストールしておく。

## Abilityクラスの作成（cancancan）

```bash
$ bin/rails g cancan:acility
		create  app/models/ability.rb
```

このコマンドで、Abilityクラスが作成される。

Abilityクラスでは、認可ルールの定義、ユーザー権限の判定、条件の定義を記述する。

例）管理者権限を守るユーザーは管理画面にログインできて、全ての操作をできる。

## rails_admin.rbの設定
gem「Devise」を利用してユーザーの認証を実装しているかつ、「cancancan」を利用して特定のユーザーのみのアクセス制限を実装する場合は、以下のコメントアウトされている部分のコメントを外す。

```ruby:config/initializers/rails_admin.rb
RailsAdmin.config do |config|
  config.asset_source = :sprockets

  ### Popular gems integration

  ## == Devise ==
  config.authenticate_with do
    warden.authenticate! scope: :user
  end
  config.current_user_method(&:current_user)

  ## == CancanCan ==
  config.authorize_with :cancancan
end
```

- 「config.authenticate_with do … end」は、管理画面にアクセスする前に認証を行うための設定。具体的には、Deviseの「warden.authenticate!」メソッドを使用して、:userスコープのユーザーを認証している。:userスコープは、Deviseにおいてデフォルトで使用されているスコープ。
- 「config.current_user_method(&:current_user)」は、Rails_adminでcurrent_userを使用するためのメソッド。
- 「config.authorize_with :cancancan」は、Rails_adminでのアクセス制限にcancancanを使用するための設定。Abilityクラスで定義されたアクセス権限が適用される。

## Abilityクラスの設定

```ruby:app/models/ability.rb
class Ability
	include CanCan:Ability
	def initialize(user)
		if user && user.admin?
			can :access, :rails_admin
			can :manage, :all
		end
	end
end
```
- 「if user && user.admin?」で、ログインしてるユーザーかつ管理者の場合、処理を実行する。
- 「can :access, :rails_admin」で管理画面にアクセスできる。
- 「can :manage, :all」で管理画面での全ての機能を使用できる。

## 管理画面の日本語化
gem ‘rails-i18n’をインストールし、公式ドキュメントの翻訳ファイルをコピーする。

```ruby:config/locales/views/rails_admin.ja.yml
# 日本語化テンプレ
ja:
  activerecord:
    # 全てのモデル記載
    models:
      user: ユーザー
      post: 投稿
      comment: コメント
      like: いいね
      contact: お問い合わせ
    attributes:
      # 各モデルのカラム名を記載
      user:
        name: 名前
        email: メールアドレス
        password: パスワード
      post:
        content: 説明
        image: 画像
      comment:
        comment: コメント内容
      contact:
        name: 名前
        email: メールアドレス
        content: お問い合わせ内容
    errors:
      messages:
        record_invalid: 'バリデーションに失敗しました: %{errors}'
        restrict_dependent_destroy:
          has_one: "%{record}が存在しているので削除できません"
          has_many: "%{record}が存在しているので削除できません"
  date:
    abbr_day_names:
    - 日
    - 月
    - 火
    - 水
    - 木
    - 金
    - 土
    abbr_month_names:
    -
    - 1月
    - 2月
    - 3月
    - 4月
    - 5月
    - 6月
    - 7月
    - 8月
    - 9月
    - 10月
    - 11月
    - 12月
    day_names:
    - 日曜日
    - 月曜日
    - 火曜日
    - 水曜日
    - 木曜日
    - 金曜日
    - 土曜日
    formats:
      default: "%Y/%m/%d"
      long: "%Y年%m月%d日(%a)"
      short: "%m/%d"
    month_names:
    -
    - 1月
    - 2月
    - 3月
    - 4月
    - 5月
    - 6月
    - 7月
    - 8月
    - 9月
    - 10月
    - 11月
    - 12月
    order:
    - :year
    - :month
    - :day
  datetime:
    distance_in_words:
      about_x_hours:
        one: 約1時間
        other: 約%{count}時間
      about_x_months:
        one: 約1ヶ月
        other: 約%{count}ヶ月
      about_x_years:
        one: 約1年
        other: 約%{count}年
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_seconds:
        one: 1秒
        other: "%{count}秒"
      x_minutes:
        one: 1分
        other: "%{count}分"
      x_days:
        one: 1日
        other: "%{count}日"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1年
        other: "%{count}年"
    prompts:
      second: 秒
      minute: 分
      hour: 時
      day: 日
      month: 月
      year: 年
  errors:
    format: "%{attribute}%{message}"
    messages:
      accepted: を受諾してください
      blank: を入力してください
      confirmation: と%{attribute}の入力が一致しません
      empty: を入力してください
      equal_to: は%{count}にしてください
      even: は偶数にしてください
      exclusion: は予約されています
      greater_than: は%{count}より大きい値にしてください
      greater_than_or_equal_to: は%{count}以上の値にしてください
      inclusion: は一覧にありません
      invalid: は不正な値です
      less_than: は%{count}より小さい値にしてください
      less_than_or_equal_to: は%{count}以下の値にしてください
      model_invalid: 'バリデーションに失敗しました: %{errors}'
      not_a_number: は数値で入力してください
      not_an_integer: は整数で入力してください
      odd: は奇数にしてください
      other_than: は%{count}以外の値にしてください
      present: は入力しないでください
      required: を入力してください
      taken: はすでに存在します
      too_long: は%{count}文字以内で入力してください
      too_short: は%{count}文字以上で入力してください
      wrong_length: は%{count}文字で入力してください
    template:
      body: 次の項目を確認してください
      header:
        one: "%{model}にエラーが発生しました"
        other: "%{model}に%{count}個のエラーが発生しました"
  helpers:
    select:
      prompt: 選択してください
    submit:
      create: 登録する
      submit: 保存する
      update: 更新する
  number:
    currency:
      format:
        delimiter: ","
        format: "%n%u"
        precision: 0
        separator: "."
        significant: false
        strip_insignificant_zeros: false
        unit: 円
    format:
      delimiter: ","
      precision: 3
      separator: "."
      significant: false
      strip_insignificant_zeros: false
    human:
      decimal_units:
        format: "%n %u"
        units:
          billion: 十億
          million: 百万
          quadrillion: 千兆
          thousand: 千
          trillion: 兆
          unit: ''
      format:
        delimiter: ''
        precision: 3
        significant: true
        strip_insignificant_zeros: true
      storage_units:
        format: "%n%u"
        units:
          byte: バイト
          eb: EB
          gb: GB
          kb: KB
          mb: MB
          pb: PB
          tb: TB
    percentage:
      format:
        delimiter: ''
        format: "%n%"
    precision:
      format:
        delimiter: ''
  support:
    array:
      last_word_connector: "、"
      two_words_connector: "、"
      words_connector: "、"
  time:
    am: 午前
    formats:
      default: "%Y年%m月%d日(%a) %H時%M分%S秒 %z"
      long: "%Y/%m/%d %H:%M"
      short: "%m/%d %H:%M"
    pm: 午後
  # rails_admin日本語化
  admin:
    js:
      true: True
      false: False
      is_present: 存在する
      is_blank: 空白
      date: 日付 ...
      between_and_: ... から ...
      today: 今日
      yesterday: 昨日
      this_week: 今週
      last_week: 先週
      number: 数字 ...
      contains: 含む
      is_exactly: 完全に一致
      starts_with: で始まる
      ends_with: で終わる
      too_many_objects: "対象が多すぎます、上の検索ボックスを使用してください"
      no_objects: "対象が見つかりません"
    loading: "読み込み中..."
    toggle_navigation: ナビゲーション切り替え
    home:
      name: "ホーム"
    pagination:
      previous: "&laquo; 前"
      next: "次 &raquo;"
      truncate: "…"
    misc:
      search: "検索"
      filter: "検索"
      refresh: "更新"
      show_all: "すべて表示"
      add_filter: "絞り込む..."
      bulk_menu_title: "選択項目を..."
      remove: "削除"
      add_new: "新規作成"
      chosen: "選択された%{name}"
      chose_all: "すべて選択"
      clear_all: "選択解除"
      up: "上"
      down: "下"
      navigation: "ナビゲーション"
      root_navigation: "アクション"
      navigation_static_label: "リンク"
      log_out: "ログアウト"
      time_ago: "%{time}前"
      ago: "前"
      more: "さらに %{count} 個以上の %{models_name}"
    flash:
      successful: "%{name}の%{action}に成功しました"
      error: "%{name}の%{action}に失敗しました"
      noaction: "操作を取り消しました"
      model_not_found: "モデル'%{model}'はありません"
      object_not_found: "'ID:%{id}'の%{model}はありません"
    table_headers:
      model_name: "モデル名"
      last_used: "最終アクセス日"
      records: "レコード数"
      username: "ユーザ"
      changes: "変更"
      created_at: "日時"
      item: "アイテム"
      message: "メッセージ"
    actions:
      dashboard:
        title: "サイト管理"
        menu: "ダッシュボード"
        breadcrumb: "ダッシュボード"
      index:
        title: "%{model_label_plural}の一覧"
        menu: "一覧"
        breadcrumb: "%{model_label_plural}"
      show:
        title: "%{model_label} '%{object_label}'の詳細"
        menu: "詳細"
        breadcrumb: "%{object_label}"
      show_in_app:
        menu: "表示"
      new:
        title: "新規%{model_label}"
        menu: "新規作成"
        breadcrumb: "新規"
        link: "新規%{model_label}追加"
        done: "作成"
      edit:
        title: "%{model_label} '%{object_label}'を編集"
        menu: "編集"
        breadcrumb: "編集"
        link: "この%{model_label}を編集"
        done: "更新"
      delete:
        title: "%{model_label} '%{object_label}'を削除"
        menu: "削除"
        breadcrumb: "削除"
        link: "'%{object_label}'削除"
        done: "削除"
      bulk_delete:
        title: "%{model_label_plural}を一括削除"
        menu: "一括削除"
        breadcrumb: "一括削除"
        bulk_link: "選択した%{model_label_plural}を削除"
      export:
        title: "%{model_label}をエクスポート"
        menu: "エクスポート"
        breadcrumb: "エクスポート"
        link: "%{model_label_plural}をエクスポート"
        bulk_link: "選択した%{model_label_plural}をエクスポート"
        done: "エクスポート"
      history_index:
        title: "%{model_label_plural}の更新履歴"
        menu: "履歴"
        breadcrumb: "履歴"
      history_show:
        title: "%{model_label} '%{object_label}'の履歴"
        menu: "履歴"
        breadcrumb: "履歴"
    form:
      cancel: "キャンセル"
      basic_info: "基本情報"
      required: "必須"
      optional: "オプション"
      one_char: "文字"
      char_length_up_to: "最大文字数:"
      char_length_of: "文字数:"
      save: "保存"
      save_and_add_another: "保存して次へ"
      save_and_edit: "保存して編集"
      all_of_the_following_related_items_will_be_deleted: "を削除してよろしいですか? 以下の項目が削除され、もしくは関係を失います:"
      are_you_sure_you_want_to_delete_the_object: "%{model_name}の項目"
      confirmation: "実行する"
      bulk_delete: "以下の項目が削除され、それによって関連する項目が削除され、もしくは関係を失います:"
      new_model: "%{name} (新規)"
    export:
      confirmation: "%{name}としてエクスポート"
      select: "エクスポートするフィールドを選択してください"
      select_all_fields: "全フィールドを選択"
      fields_from: "%{name}のフィールド"
      fields_from_associated: "関連する%{name}のフィールド"
      display: "カラム:%{name} 型:%{type}"
      options_for: "%{name}の出力オプション"
      empty_value_for_associated_objects: "<empty>"
      click_to_reverse_selection: 'クリックで選択を反転します'
      csv:
        header_for_root_methods: "%{name}" # 'model' is available
        header_for_association_methods: "%{name} [%{association}]"
        encoding_to: "文字コード"
        encoding_to_help: "空白の場合は%{name}になります"
        skip_header: "ヘッダなし"
        skip_header_help: "チェックすると出力にヘッダ行を含めません"
        default_col_sep: ","
        col_sep: "区切り文字"
        col_sep_help: "空白の場合は'%{value}'区切りです" # value is default_col_sep
```

## roleカラムの追加

Usersテーブルにroleカラムを追加して、管理者と一般ユーザーを区別できるようにする。

以下のコマンドでマイグレーションファイルを作成

```bash
$ bin/rails g migration AddRoleToUser
```

```ruby:20230806123456_add_role_to_user.rb
class AddRoleToUser < ActiveRecord::Migration[7.0]
  def change
    add_column :users, :role, :integer, default: 0, null: false
  end
end
```

```bash
$ bin/rails db:migrate
```

``` ruby:app/models/user.rb
class User < ApplicationRecord
  enum role: { general: 0, admin: 1 }
end

```

## enum-helpのインストール

enumで実装したカラムの日本語化もしておく。

```bash
gem 'enum-help'
```

```bash
$ bundle
```

翻訳ファイルに追記

```ruby:config/locales/activerecord/ja.yml
ja:
  enums:
    user:
      role:
        general: '一般'
        admin: '管理者'
```

日本語化ができているか確認する。

```bash
$ rails c
irb(main):001:0> user = User.create(name: 'a', email: 'a@example.com', role: 'admin')
=> [#<User_id: 1, name: "a", email: "a@example.com", role: "admin">]

irb(main):002:0> user.role
=> "admin"

irb(main):003:0> user.role_i18n
=> "管理者"
```

## 管理画面の確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2600084/232cc62f-0a92-8656-c7dd-51a5074d7eea.png)
- すでに作成しているモデルについては、CRUDができるようになっている。
- 日本語化対応がされている。

## 最後に
働きながら個人を開発をしている関係で、管理画面に時間を取りたくなかったので、かなり時間を削減することができたと思います。
手軽に管理画面を実装したい場合は、'rails_admin'をオススメします！
今回は以上です！