Rails に ActiveAdmin で管理画面を追加してみた

More than 3 years have passed since last update.


はじめに

Rails の管理画面作成において、便利な Gem ActiveAdmin で簡単に管理画面が実装できるということで、調べながら実装した際のメモ

今回は、既存の User モデルのユーザーを使用して管理画面へのログインをし、

User の新規作成、一覧、詳細、更新、削除のページを用意

デフォルトでは、route は /admin で作成されるが、それを /manage に変えて作成


準備

Gemfile に下記を追加して、bundle install


Gemfile

# for manage page

gem 'activeadmin', github: 'activeadmin'

今回は、既存の User モデルを使用するので、AdminUser モデルを作成しないように --skip-users オプションを追加して install

$ bin/rails g active_admin:install --skip-users

create config/initializers/active_admin.rb
create app/admin # ActiveAdmin で編集させたいリソース毎にファイルを設置するフォルダ
create app/admin/dashboard.rb # ログイン後のダッシュボードのページ
route ActiveAdmin.routes(self)
generate active_admin:assets
create app/assets/javascripts/active_admin.js.coffee
create app/assets/stylesheets/active_admin.css.scss
create db/migrate/20150218033243_create_active_admin_comments.rb

model が追加されたので、migrate する

active_admin_comments というテーブルが作成される

$ bin/rake db:migrate

ActiveAdmin の設定を編集する


config/initializers/active_admin.rb

  config.site_title = 'hoge'                               # サイトのタイトル

config.site_title_link = '/manage' # ロゴのリンク設定
config.site_title_image = '/assets/hoge_logo.png' # ロゴの画像を用意
config.default_namespace = :manage # route がデフォルトの /admin ではなく、/manage になる
config.namespace :manage do |manage|
manage.site_title = 'hoge 管理ページ'
end
config.favicon = '/assets/favicon.ico' # favicon を用意できたら
config.authentication_method = :authenticate_admin_user! # 管理画面にアクセスする際の認証メソッド ( concerns に追加するメソッド )
config.current_user_method = :current_admin_user # ログインユーザー情報を取得するメソッド ( concerns に追加するメソッド )
config.logout_link_path = :destroy_admin_session_path # ログアウト時のパス 新たに定義する routes
config.logout_link_method = :delete # ログアウト時の METHOD を DELETE へ


上記設定をすると、下記 URL でアクセスできる

http://アプリケーションのドメイン/manage

環境によっては、undefined methodenvironment' for nil:NilClass が発生するかも

調べてみたら、sass-railssprocket のバージョンの問題みたい

http://stackoverflow.com/questions/22392862/undefined-method-environment-for-nilnilclass-when-importing-bootstrap

Gemfile を修正して、sass-rails を bundle update したらアクセスできた


ActiveAdmin の routes

ActiveAdnin の initializer で自動的に命名規則にのっとった routes が追加される

下記の manage_root 以外は、追加した resouce 毎に追加されるが、resource ファイル内で利用する action を設定すると記載された actionに対応する routes のみが追加される

Prefix
Verb
URI Pattern
Controller#Action

manage_root
GET
/manage(.:format)
manage/dashboard#index

batch_action_manage_resources
POST
/manage/resources/batch_action(.:format)
manage/resources#batch_action

manage_resources
GET
/manage/resources(.:format)
manage/resources#index

POST
/manage/resources(.:format)
manage/resources#create

new_manage_resource
GET
/manage/resources/new(.:format)
manage/resources#new

edit_manage_resource
GET
/manage/resources/:id/edit(.:format)
manage/resources#edit

manage_resource
GET
/manage/resources/:id(.:format)
manage/resources#show

PATCH
/manage/resources/:id(.:format)
manage/resources#update

PUT
/manage/resources/:id(.:format)
manage/resources#update

default_namespace を変更しない場合は、manageadmin になる


ログイン処理までの各種実装

管理画面用のログイン処理系は、controller の concern に追加した。

ログイン画面、ログアウトの routes を追加

ログイン画面の URL を http://www.example/manage/login みたいにしたかったので、scope を切った


config/routes.rb

  ActiveAdmin.routes(self) # active_admin:install に自動的に追加されてる

scope :manage do
get :login, to: 'users#new_admin_session', as: :new_admin_session
post :login, to: 'users#create_admin_session', as: :create_admin_session
delete :logout,to: 'users#destroy_admin_session', as: :destroy_admin_session
end

ログイン処理、ログアウト処理を concers に追加

下記は、UsersController で include する


app/controllers/concerns/admin_user_authentication_action.rb

module AdminUserAuthenticationAction

extend ActiveSupport::Concern

# include する UsersController とかに before_action が入っていた場合は、下記の様な感じで skip するようにする
# self.included do
# skip_before_action :authenticate_by_token, only: [:new_admin_session, :create_admin_session, :destroy_admin_session]
# end

def new_admin_session
redirect_to manage_root_path if current_admin_user
render template: 'users/admin_session/new' # view は users に別途フォルダをわけて作成した
end

# session 情報に、ログイン者の情報を追加
def create_admin_session
begin
@current_admin_user = User.admin_authenticate_by_email(params[:user][:email], params[:user][:password])
session[:admin_user_id] = @current_admin_user.id
session[:admin_session_expires_at] = 60.minutes.from_now # セッション保持期間は 1 時間
redirect_to manage_root_path
rescue User::LoginFailed
redirect_to new_admin_session_path
end
end

# ログアウト処理
def destroy_admin_session
if current_admin_user
session.clear
end
redirect_to new_admin_session_path
end
end


下記は、ApplicationController で include する


app/controllers/concerns/admin_user_authentication_filter.rb

module AdminUserAuthenticationFilter

extend ActiveSupport::Concern
class AdminUnauthenticated < StandardError; end

self.included do
rescue_from AdminUnauthenticated do
redirect_to manage_root_path
end
end

def authenticate_admin_user!
redirect_to new_admin_session_path unless current_admin_user
end

# active admin から呼ばれるログインしたユーザーを取得するメソッド
def current_admin_user
if session[:admin_user_id] && session[:admin_session_expires_at].try(:>, Time.zone.now)
session[:admin_session_expires_at] = 60.minutes.from_now
begin
# Enumerize で role が admin のユーザーを検索
@current_admin_user = User.with_role(:admin).find_by!(id: session[:admin_user_id])
session[:admin_user_id] = @current_admin_user.id
rescue ActiveRecord::RecordNotFound
raise AdminUnauthenticated
end
else
@current_admin_user = nil
end
@current_admin_user
end
end


それぞれ、作った concern を include


app/controllers/application_controller.rb

class ApplicationController < ActionController::Base

include AdminUserAuthenticationFilter # include しておく

# 色々な処理
# 色々な処理
end



app/controllers/users_controller.rb

class UsersController < ApplicationController

include AdminUserAuthenticationAction # include しておく

# 色々な処理
# 色々な処理
end


管理者ユーザーのログインメソッドを追加

role については、Enumerize を使ってます。


app/models/user.rb

class User < ActiveRecord::Base

class LoginFailed < StandardError; end

extend Enumerize
enumerize :role, in: { user: 1, admin: 2 }, default: :user, predicates: true, scope: true

belongs_to :prefecture # 都道府県

validates :email, :name, :prefecture, :address, presence: true

scope :active, -> { where(deleted_at: nil) }

def self.admin_authenticate_by_email(email, password)
user = self.with_role(:admin).find_by!(email: email)
user.authenticate(password) or raise LoginFailed # Devise は使ってないので、標準の authenticate を使用
rescue ActiveRecord::RecordNotFound
raise LoginFailed
end
end


ログイン画面の view は下記のような感じ


app/views/user/admin_session/new.html.haml

:sass

#login_form
width: 40%
margin: 30px auto
label
margin-bottom: 5px
#user_email, #user_password
width: 100%
margin-bottom: 5px
input
box-sizing: border-box
&[type="submit"]
width: 30%
.actions
text-align: center

= stylesheet_link_tag "active_admin"
= form_for :user, url: create_admin_session_path, :html=> {:id => 'login_form'}, method: :post do |f|
.field
= f.label :email
%br
= f.email_field :email, autofocus: true
.field
= f.label :password
%br
= f.password_field :password, autocomplete: "off"
.actions
= f.submit "ログイン"


画面デザインとか考えるの面倒臭かったので、Devise の session の erb をコピーして haml に書き直して、

ActiveAdmin で用意されてる css を = stylesheet_link_tag "active_admin" で読み込んで、

足りない分だけ実装した。

ちゃんとやるんだったら、sass をわけなきゃだけど。。。とりあえずです。


ログイン後の各画面の追加

上記までで、ログインから、ダッシュボード表示まで出来ているので、後は個別のモデルを表示する画面を実装

また、新規作成、更新のフォーム、検索条件について、string や、integer の型により自動的に表示される

例:

name が string の場合、filter では、あいまい検索、前方一致、後方一致、完全一致が自動で用意され、input では、type="text" で表示される

prefecture が belongs_to の場合、filter 、input 共に select タグが出力される


リソースの追加方法

下記を実行すると、/app/admin/user.rb が作成される

$ bin/rails g active_admin:resource User

このままでも既に動くようになっているが、DSL を書けば個別にカスタマイズもできる

よく使うものを下記

パラメータ
概要

filter
一覧ページの右の検索に表示する項目を追加するパラメータ

remove_filter
一覧ページの右の検索に表示する項目を削除するパラメータ(複数可)

index
一覧ページを定義するパラメータ

column
一覧ページに表示する項目を定義するパラメータ

actions
一覧ページ、編集ページに各アクションボタンを表示するパラメータ

show
詳細ページを定義するパラメータ

attributes_table
詳細ページで表示する項目を定義するパラメータ

panel
表示する項目の領域を分ける際に使用するパラメータ

form
編集ページを定義するパラメータ

input
編集ページの編集可能な項目を定義するパラメータ

permit_params
編集ページで実際に編集可能にするパラメータ(StrongParameters と同じ働きをする)

下記 User のページを作った際のコード


/app/admin/user.rb

ActiveAdmin.register User do

config.per_page = 15 # 一覧ページのページングの件数

# 一覧ページの検索条件
filter :id
filter :role
filter :email
filter :name
filter :prefecture # 自動で Prefecture#name の select タグが表示される
filter :address
filter :deleted_at
# 上記検索条件に表示する項目を remove_filter で書くと下記のようになる
# remove_filter :crated_at, :updated_at

# 一覧ページ
index do
column :id
column :role
column :email
column :name
column :prefecture # 勝手に prefecture.name を表示
column :address
column :deleted_at

actions
end

# 詳細ページ
show do
attributes_table do
row :id
row :role
row :email
row :name
row :prefecture # 勝手に prefecture.name を表示
row :address
row :deleted_at
end
end

# 新規作成/編集ページ
form do |f|
inputs do
input :role
input :email
input :name
input :prefecture
input :address
end

actions
end

permit_params :role, :email, :name, :prefecture, :address # 更新可能な attribute を記載
end



Tips


ログインユーザーの表示名を変更する

ログイン後の右上部のログインユーザーの情報は変更できる

email を表示する場合は、config に下記を追加


config/initializers/active_admin.rb

config.display_name_methods = [:email]


default は [ :display_name, :full_name, :name, :username, :login, :title, :email, :to_s ] の順に評価され、存在するメソッドの結果を表示されるみたい (source)


ページタイトルを変える

show や index に対して title を渡すと表示される

下記は、詳細ページのタイトルに name を表示

show title: :name do

row: :id
row :name
end


実行できる Action を制限する

新規作成は ActiveAdmin からできるようにしたいけど、更新はしたくないといった場合に対応できる


  • index だけ可能にする場合

ActiveAdmin.register User do

actions :index # index だけ可能

....
end


  • 更新はだけ不可にする場合

ActiveAdmin.register User do

actions :all, except: [:edit, :update]

....
end

指定できるアクションは、:index, :show, :new, :create, :edit, :update, :destroy


一覧にフィルターボタンを追加

よく使うフィルターの条件とかを、一覧画面の上部に追加できる

元ある scope を利用することもできるし、その場で条件を記載することも出来る

下記は、ユーザーのみ表示する

ActiveAdmin.register User do

scope 'Active', :active # User に定義された scope
scope('ユーザーのみ') { |scope| scope.with_role(:user) } # with_role は Enumerize で追加された scope
....
end


一覧に表示させる record を制限する

scope に、 default: true と記載すると、その scope に一致するものしか表示できなくなる

ActiveAdmin.register User do

scope 'Active', :active, default: true

....
end


フォームのボタンを日本語化

I18n で日本語用の辞書があれば、勝手に日本語化してくれるが、

作成画面とか、更新画面のボタンの日本語は、下記のように改めて辞書の用意が必要

これが無いと、ボタンに Update ユーザー のように表示される


config/locales/active_admin/ja.yml

ja:

formtastic:
:yes: 'はい'
:no: 'いいえ'
:create: '%{model}を作成'
:update: '%{model}を更新'
:submit: '%{model}を投稿'
:cancel: '%{model}を中止'
:reset: '%{model}を初期化'
:required: '必須'


カスタマイズした項目を表示する

上記の User#role が Enumerize を使用しているので、一覧、詳細で表示した場合に、user とか、 admin とかで表示されてしまうので、

それを I18n で定義した辞書を使用して表示する場合

index do

column :role do |user|
user.role_text # or user.role.text
end
end

show do
column :role do |user|
coupon.role_text # or user.role.text
end
end


filter や form の input をカスタマイズする

上記のように belong_to 等について自動で select タグを出力してくれるが、select の表示したい名称を変えた場合や、

Enumerize を select タグで表示したい時に、カスタマイズが必要になる

filter :role, as: :select,

collection: User.role.values.map { |value| [User.role.find_value(value).text, User.role.find_value(value).value] }

input :role, as: :select,

collection: User.role.values.map { |value| [User.role.find_value(value).text, User.role.find_value(value).value] }

collection に表示名、値の順の配列を渡すことで可能になる

ちなみに、filter で as: :check_boxes にするとチェックボックスに変わり、複数選択で検索できる


form の input に、attribute を追加する

フォームのファイルアップロードのファイル選択画面で画像だけを選択させるよう、

input タグに accept の attribute を追加したかった際に、ソース調べて試したら下記のやり方で出来た

form do |f|

inputs do
input :image, as: :file, input_html: { accept: 'image/*' }
end
end

input_html に追加したい attribute を Hash で渡すと変換して表示してくる

input要素のaccept属性のブラウザ対応状況

input_html の該当部分のソース


form の date カラムの日付をカレンダーから選択

ActiveAdmin のデータ作成画面は、標準では date カラムの入力フィールドは年月日が全て select タグになっており入力しづらい。

それを下記のように書くと Formtasticdatetime pickerが使えるようになる

form do |f|

inputs do
input :start_date, as: :date_picker
end

actions
end

datetime 型の場合は、just-datetime-picker を使うとカレンダーから選択できるようになる


index ページの actions を自前で書いてみる

一覧画面で、特定条件に一致するユーザーのみ削除ボタンを表示させる見たいのなことを実装するため、デフォルトの actions を false にして自前で実装してみた (詳細はこちら

item の後に表示名、path で作成できるが、デフォルトで作成されるタグには、view_linkmember_link の class が付いているので、それも追加

表示名については、I18n で取得

使用できる辞書ファイルはこちら

ActiveAdmin.register User do

index do
column :id
column :name

actions defaults: false do |user|
item I18n.t('active_admin.view'), manage_user_path(user), class: 'view_link member_link'
item I18n.t('active_admin.edit'), edit_manage_user_path(user), class: 'edit_link member_link'
item I18n.t('active_admin.delete'), manage_user_path(user), class: 'delete_link member_link'
end
end
end


カスタムなアクションを定義する

ユーザーの削除を、削除日を入れて更新するのではなく、標準の削除ボタンの見た目を装い、実はカスタムアクションを実行

ActiveAdmin.register Campaign do

# delete アクションを追加
# delete_manage_user_path が route に追加される
member_action :delete, method: :delete do
user = User.find params[:id]
user.touch(:deleted_at)
redirect_to manage_user_path, :notice => "Deleted!"
end

index do
column :id
column :name

actions defaults: false do |user|
item I18n.t('active_admin.view'), manage_user_path(user), class: 'view_link member_link'
item I18n.t('active_admin.delete'), delete_manage_user_path(user), class: 'delete_link member_link', method: :delete, data: { confirm: I18n.t('active_admin.delete_confirmation') } # クリック時に confirm も表示させた
end
end
end


まとめ

DSL なので、カスタマイズしたい場合に、どこまで出来るのか調べながら実装していくことになるので、

多少導入コストがかかるが、カスタマイズせず(一覧ページだけでいいとか)そのまま使う場合は、ホントに手軽にかつ素早く実装できる

上記のパラメータを覚えるだけで、必要な機能の8割方の機能は実装できると思う

ActiveAdmin 関連はこちら