Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
168
151

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-03-08

はじめに

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 method ``environment' 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 関連はこちら

168
151
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
168
151

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?