📝

ruby on railsのActiveAdminをカスタマイズする備忘録

2023/11/16に公開

どういった記事なのか

ruby on railsにおいて簡単に管理画面を実装できるActiveAdminではあるが、案件に応じて多くのカスタマイズを必要とするが失念しがち
カスタマイズの内容を別の案件で適応時に簡単に再現できるよう、ソースのメモを備忘録として記事に残す

どんな人間が書いているか

  • プログラマー歴10年ちょいの初心者
  • ITに関わった当初はテストエンジニア、その後プログラマー・SEに転身
  • C# -> php -> ruby とプログラム未経験から職場とともにメイン言語を渡り歩いてきた

メニュー関連の制御

メニューのラベルを指定

  • メニューのラベルを変更する場合、modelのリソースに「menu :label 'hoge'」を追記する

例) Userのメニューを「フロントユーザー」と表示する

app/admin/users.rb
ActiveAdmin.register User do
  menu :label 'フロントユーザー'
end

メニューの並び順を制御

  • それぞれのメニューの並び順を指定する場合、modelのリソースに「menu :priority xx」を追記する(デフォルトは10扱い)

例) Userのメニューを1番上に表示する

app/admin/users.rb
ActiveAdmin.register User do
  menu :priority 1
end

メニューをグループ化

  • メニューをグループ化して階層表示する場合、modelのリソースに「menu :parent 'hoge'」を追記する

例) Bookメニューの下にAuthorメニューを加える

app/admin/author.rb
ActiveAdmin.register Author do
  menu :parent 'Book'
end

メニューへ表示したくないリソース

  • メニューに表示したくない場合、modelのリソースに「menu :false」を追記する

例) Userのメニューを表示しない

app/admin/users.rb
ActiveAdmin.register User do
  menu :false
end

もっと簡単に複雑なメニュー制御を行いたい場合のgem

ActiveAdmin::MenuTree

許可する操作を指定

  • modelのリソースにおいて、操作(一覧表示、登録、詳細表示など)を制限する場合は該当のmodelのリソースに許可するactionsを追記して制御する
記述内容 許可対象
:index 一覧
:show 詳細
:new 作成画面
:create 作成
:edit 編集
:update 更新
:destroy 削除

例) Userに対しては一覧表示と詳細表示のみ許可する

app/admin/users.rb
ActiveAdmin.register User do
  actions :index, :show
end

取り扱うレコードに条件を指定する

  • 特定の条件にマッチしたレコードのみを管理画面で扱いたい場合、modelのリソースにcontrollerを定義のうえ、scoped_collectionのメソッドをオーバーライドすることで実現できる

例) 管理画面からの登録したUser(users.is_register_admin = 1で定義)のみを表示する

app/admin/users.rb
  controller do
    def scoped_collection
      User.where(is_register_admin: 1)
    end
  end

一覧画面の制御

一覧に表示する項目を制御

  • modelのリソースにindexを定義して一覧画面に表示したい内容を記述する
記述内容 表示対象
selectable_column 一括操作用のチェックボックス
id_column ID列
column :hoge 指定column列
actions 編集、削除などの操作ボタン列

例) Userの一覧表示の項目はIDと名前とメールアドレスのみにする

app/admin/users.rb
  index do
    id_column
    column :name
    column :email
    actions
  end

表示項目の値を置換して表示

  • columnの値を置換して表示することも出来る

例) Userが管理画面からの登録か(users.is_register_adminで定義)を日本語で表示する

app/admin/users.rb
  index do
    ...
    column '登録元' do |u|
      if u.is_register_admin == 0
        'フロントから登録'
      elsif c.order_status == 1
        '管理画面から登録'
      end
    end
    actions
  end

CSVに出力される項目を制御

  • modelのリソースにcsvを定義してCSVで出力したい内容を記述するが、ここではindexと違いid_columnやactionsは使用しない

例) UserのCSV出力項目はIDと名前とメールアドレスのみにする

app/admin/users.rb
 csv do
    column :id
    column :name
    column :email
  end

一覧画面における検索項目を制御

  • modelのリソースにfilterを記述することにより、一覧画面での検索項目を制御することができる

例) Userの検索は名前のみにする

app/admin/users.rb
  filter :name

例) Userの検索において管理画面から登録か(users.is_register_adminで定義)のプルダウン検索を追加する

app/admin/users.rb
  filter ...
  filter :is_register_admin, as: :select, collection: [['フロントから登録', 0], ['管理画面から登録', 1]]

登録画面の制御

登録時に入力できる項目を制御

  • modelのリソースにformを定義して登録・編集画面に表示したい内容を記述する
記述内容 表示対象
f.semantic_errors 入力エラーの表示枠
f.inputs 入力フィールド
f.input :hoge 指定columnの入力フィールド
f.actions 登録、キャンセルボタン

例) Userの登録項目は名前とメールアドレスのみにする

app/admin/users.rb
  form do |f|
    f.semantic_errors
    f.inputs do
      f.input :name
      f.input :email
    end
    f.actions
  end

登録項目をグループ化して表示する

  • 登録項目をグループ化して表示したい場合、formのinputsに名前を付けて記述する

例) Userの登録項目を種類ごとにグループ化して表示する

app/admin/users.rb
  form do |f|
    f.semantic_errors
    f.inputs '基本情報' do
      f.input :name
      f.input :email
    end
    f.inputs '趣味・嗜好' do
      f.input :hobby
      f.input :preference
    end
    f.actions
  end
  
  show do
    attributes_table title: '基本情報' do
      row :name
      row :email
    end
    attributes_table title: '趣味・嗜好' do
      row :hobby
      row :preference
    end
  end

hidden項目で固定の値を引き渡す

  • 管理画面からの登録時は特定のcolumnに固定の値を渡したいなどの場合は、hiddenで値を登録する方法がある
    ただし、そのレコード以外は管理対象外にしなければ編集時に値が上書きされるので注意

例) Userの登録時にtypeの値を固定で引き渡す

app/models/user.rb
class User < ApplicationRecord
  enum type: { hogehoge: 0, fugafuga: 1 }
end
app/admin/users.rb
  form do |f|
    f.semantic_errors(*f.object.errors.keys)
    f.inputs do
      f.input :type, :as => :hidden, input_html: {value: 'hogehoge'}
      ...
    end
    f.actions
  end
  
  # typeがhogehogeのレコードのみを扱う
  controller do
    def scoped_collection
      Notification.where(type: 'hogehoge')
    end
  end

子テーブルのレコードも同時に登録する

  • 親テーブルと同じ登録画面内で子テーブルのレコードを登録する場合、親modelのリソースのform内に子modelの入力も記載して、permit_paramsへ子model用のパラメータを追記する

例) Productの子modelであるProductCategories(id, product_id, category_id)を複数件登録可能とする

app/models/product.rb
class Product < ApplicationRecord
  has_many :product_categories, dependent: :destroy_async
  accepts_nested_attributes_for :product_categories, allow_destroy: true
  
  validate :product_categories_check
  
  def product_categories_check
    if product_categories.size == 0
      errors.add :base, 'カテゴリの設定は必須です'
    end
  end
end
app/admin/products.rb
  permit_params ...
                product_categories_attributes: [:id, :category_id, :_destroy]

  form do |f|
    f.semantic_errors(*f.object.errors.keys)
    f.inputs 'カテゴリ設定' do
      # allow_destroyとnew_recordをtrueにして削除と追加を許可
      f.has_many :product_categories, allow_destroy: true, heading: false, new_record: true do |ec|
        ec.input :category_id, as: :select, include_blank: false, collection: Category.all.order(sort: :DESC).map { |x| [x.name, x.id] }
      end
    end
    f.inputs '商品情報' do
      ...
    end
    f.actions
  end
  
  show do |product|
    attributes_table title: 'カテゴリ設定' do
      table_for product.product_categories do |_c|
        column :category
      end
    end
    attributes_table title: '商品情報' do
      ...
    end
  end

詳細画面の制御

詳細に表示する項目を制御

  • modelのリソースにshowを定義して詳細画面に表示したい内容を記述する
記述内容 表示対象
attributes_table デフォルト外観の維持
row :hoge 指定column行
active_admin_comments コメントフォーム

例) Userの詳細画面の項目はIDと名前とメールアドレスと登録日時のみにして、コメントを許可する

app/admin/users.rb
  show do
    attributes_table do
      row :id
      row :name
      row :email
      row :created_at
    end
    active_admin_comments
  end

actionの前に制御を入れる

  • 登録や更新前に独自の操作を入れる場合、modelのリソースにcontrollerを定義のうえ、createやupdateのメソッドをオーバーライドすることで実現できる

例) Userの登録前にUserにメールを送信する

app/admin/users.rb
  controller do
    def create
      user_params = params[:user]
      UserMailer.send_user_for_admin(user_params).deliver
      super
    end
  end

例) Userの更新時にパスワードの入力がないならパスワードを既存のままにする

app/admin/users.rb
  controller do
    def update
      if params[:user][:password].blank? && params[:user][:password_confirmation].blank?
        params[:user].delete("password")
        params[:user].delete("password_confirmation")
      end
      super
    end
  end

独自のactionの追加

  • 一覧や登録などの固有のaction以外に独自のactionを追加したい場合は、modelのリソースにmember_actionで定義する

例) Userの子modelであるUserCommentsを承認するアクション(approve_comments)をgetアクションで追加

app/admin/users.rb
  show do |f|
    attributes_table do
    ...
      # Commentsを一覧表示して承認ボタン用のhtmlを読みこみ
     panel '' do
      render partial: "comment_title", locals: { f: f }
      table_for user.user_comments do
        column :body
        column :created_at
        column :is_approved
      end
    end
  end

  # 独自のアクションを追加
  member_action :approve_comments, :method => :get do
    UserComment.where(user_id: params[:id]).where(is_approved: 0).each do |comment|
      comment.is_approved = 1
      comment.save
    end
    user = Therapist.find(params[:id])
    redirect_to url_for({:action => :show}), :notice => 'コメントを承認しました'
  end
app/views/active_admin/resource/_comment_title.erb
<h3>
  ユーザコメント
  <label>
    <%
      # すべてのコメントが承認済みなら承認ボタンを表示させない
      is_approved = 1
      cnt = 0

      UserComment.where(user_id: f.id).each do |comment|
        if comment.is_approved != 1
          is_approved = 0
          cnt += 1
        end
      end

      if is_approved == 1 
    %>
      ステータス:承認済
    <%
      elsif cnt > 0
    %>
      ステータス:未承認<a href="/admin/users/<%= f.id %>/approve_comments">承認する</a>
    <%
      end
    %>
  </label>
</h3>

Active Storageとの連携

一覧画面にサムネイル画像を表示

  • modelにhas_one_attachedで定義した画像ファイルを一覧表示でサムネイル表示する場合、modelのリソースのindexに表示内容を追記する

例) Userの画像をサムネイル表示する

app/models/user.rb
class User < ApplicationRecord
  has_one_attached :image
end
app/admin/users.rb
  index do
    ...
    column :image do |f|
      f.image.present? ? image_tag(f.image, height: '40') : ''
    end
    ...
  end

登録画面で画像の登録と削除機能を追加

  • modelにhas_one_attachedで定義した画像ファイルを登録画面で登録、削除するためには、modelへ削除用のメソッドを定義したうえで、modelのリソースのformでそれを利用するよう表記する
app/models/user.rb
class User < ApplicationRecord
  has_one_attached :image

  # 画像登録時のvalidate
  validate :image_presence
  
  def image_presence
    errors.add(:image, 'にはjpegまたはpngファイルを添付してください') if image.attached? && !image.content_type.in?(%('image/jpeg image/png'))
  end
  
  # 画像削除用の記述
  attr_accessor :remove_image
  
  before_validation { image.purge if @remove_image.try!(:==, "1") }
  
  def remove_image
    @remove_image || false
  end
  
  def remove_image=(value)
    attribute_will_change!('remove_image') if remove_image != value
    @remove_image = value
  end
end
app/admin/users.rb
  permit_params ...
                :image :remove_image

  # multipart: true で画像送信を許可
  form html: { multipart: true } do |f|
    f.inputs do
      ...
      f.input :image, as: :file, hint: f.object.image.present? ? image_tag(f.object.image) : nil
      if f.object.image.present?
        f.input :remove_image, as: :boolean, required: :false, label: '画像を削除する'
      end
      ...
    end
    f.actions
  end

詳細画面に画像を表示

  • modelにhas_one_attachedで定義した画像ファイルを詳細画面で表示する場合、modelのリソースのshowに表示内容を追記する

例) Userの画像をサムネイル表示する

app/models/user.rb
class User < ApplicationRecord
  has_one_attached :image
end
app/admin/users.rb
  show do
    attributes_table do
      ...
      row :image do |f|
        f.image.present? ? image_tag(f.image) : ''
      end
      ...
    end
  end

Discussion