はじめに
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 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_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 関連はこちら