はじめに
Rails の管理画面作成において、便利な Gem ActiveAdmin で簡単に管理画面が実装できるということで、調べながら実装した際のメモ
今回は、既存の User モデルのユーザーを使用して管理画面へのログインをし、
User の新規作成、一覧、詳細、更新、削除のページを用意
デフォルトでは、route は /admin
で作成されるが、それを /manage
に変えて作成
準備
Gemfile に下記を追加して、bundle install
# 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.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 method ``environment' for nil:NilClass
が発生するかも
調べてみたら、sass-rails
と sprocket
のバージョンの問題みたい
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
を変更しない場合は、manage
が admin
になる
ログイン処理までの各種実装
管理画面用のログイン処理系は、controller の concern に追加した。
ログイン画面、ログアウトの routes を追加
ログイン画面の URL を http://www.example/manage/login
みたいにしたかったので、scope を切った
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 する
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 する
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
class ApplicationController < ActionController::Base
include AdminUserAuthenticationFilter # include しておく
# 色々な処理
# 色々な処理
end
class UsersController < ApplicationController
include AdminUserAuthenticationAction # include しておく
# 色々な処理
# 色々な処理
end
管理者ユーザーのログインメソッドを追加
role
については、Enumerize を使ってます。
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 は下記のような感じ
: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 のページを作った際のコード
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.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 ユーザー
のように表示される
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
タグになっており入力しづらい。
それを下記のように書くと Formtastic
の datetime picke
rが使えるようになる
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_link
と member_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 関連はこちら