Rails + Active Adminで、Active Admin Controllerのcreate/update/destroyをオーバーライドする

Railsでは、Active Adminを使うことで管理者画面を容易に作ることができます。
Active Admin | The administration framework for Ruby on Rails

 
そんな中、Active AdminでModelの作成・更新・削除を行うと同時に、Modelとは関係ないAPIを呼ぶ処理を追加したくなったため、調べた時のメモを残します。

 
目次

 

環境

  • Rails 6.1.4
  • Active Admin 2.9.0
  • Devise 4.8.0
    • Active Adminの管理ページをログイン必須にするために使用

 

ベースとなるActive Adminアプリの作成

Active Adminのセットアップ

まずは、追加する前のActive Adminアプリを作成します。

今回はRails6.1系のため、assets generatorにWebpackerを使ったアプリを作成します。

 
rails new します。

% rails new rails_with_active_admin_app

 
Gemfileにactive adminとdeviseを追加して、 bundle install します。

gem 'activeadmin'
gem 'devise'

 
このまま起動してもWebpackerまわりでエラーになるため、Webpackerのセットアップを行います。

% bin/rails webpacker:install
      create  config/webpacker.yml
Copying webpack core config
      create  config/webpack
      create  config/webpack/development.js
      create  config/webpack/environment.js
      create  config/webpack/production.js
      create  config/webpack/test.js
Copying postcss.config.js to app root directory
      create  postcss.config.js
Copying babel.config.js to app root directory
      create  babel.config.js
Copying .browserslistrc to app root directory
      create  .browserslistrc
The JavaScript app source directory already exists
       apply  path/to/rails_with_active_admin_app/vendor/bundle/ruby/3.0.0/gems/webpacker-5.4.0/lib/install/binstubs.rb
  Copying binstubs
       exist    bin
      create    bin/webpack
      create    bin/webpack-dev-server
      append  .gitignore
Installing all JavaScript dependencies [5.4.0]
         run  yarn add @rails/webpacker@5.4.0 from "."
...
✨  Done in 6.49s.
Webpacker successfully installed 🎉 🍰

 
Active Adminの初期化を行います。

Webpackerはオプトインのため、 use_webpacker オプションを追加してWebpackerを使うようにします。
webpacker | Installation | Active Admin | The administration framework for Ruby on Rails

% bin/rails g active_admin:install --use_webpacker
Running via Spring preloader in process 11165
      create  config/initializers/active_admin.rb
      create  app/admin
      create  app/admin/dashboard.rb
       route  ActiveAdmin.routes(self)
    generate  active_admin:webpacker
       rails  generate active_admin:webpacker 
Running via Spring preloader in process 11173
      create  app/javascript/packs/active_admin.js
      create  app/javascript/stylesheets/active_admin.scss
      create  app/javascript/packs/active_admin/print.scss
      create  config/webpack/plugins/jquery.js
      insert  config/webpack/environment.js
      insert  config/webpack/environment.js
         run  yarn add @activeadmin/activeadmin from "."
yarn add v1.22.10
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 4 new dependencies.
info Direct dependencies
└─ @activeadmin/activeadmin@2.9.0
info All dependencies
├─ @activeadmin/activeadmin@2.9.0
├─ jquery-ui@1.12.1
├─ jquery-ujs@1.2.3
└─ jquery@3.6.0
✨  Done in 6.09s.
      create  db/migrate/20210715221110_create_active_admin_comments.rb

 
ちなみに、今回はDeviseを使っているため関係ないですが、Deviseを使っていない場合は --skip-users オプションを追加してActiveAdminを初期化します。

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

 
続いてDBまわりのセットアップを行います。
Setting up Active Admin | Active Admin | The administration framework for Ruby on Rails

% bin/rails db:migrate

% bin/rails db:seed

 
ここまでで準備ができたため、Railsを起動します。

% bin/rails s

この状態で http://localhost:3000/admin にアクセスし、以下の情報でログインできればOKです。

 

Modelを作成

今回は以下の項目を持つ Fruit モデルを用意します。

論理名 物理名 制約
名前 name string unique
color string -
販売開始日時 start_of_sales datetime -

 
Model Fruit を作成します。

% bin/rails g model Fruit name:string:unique color:string start_of_sales:datetime

 
マイグレーションします。

% bin/rails db:migrate

 

Active Adminで管理できるようにする

Modelができたため、次はActive Adminで管理できるようにします。

app/admin/fruit.rb を作成します。

今回、Active Adminの画面では

  • name
  • color

のみ編集可能とするため、 permit_params を使って指定します。
Setting up Strong Parameters | Working with Resources | Active Admin | The administration framework for Ruby on Rails

ActiveAdmin.register Fruit do
  permit_params :name, :color
end

これで、Controller Admin::Fruit が生成されます。
Content rendering API · activeadmin/activeadmin Wiki

 

動作確認

http://localhost:3000/admin/fruits/new にアクセスします。

color は color pickerになっています。もし color pickerを使いたくない場合は、以下のようにしてカスタマイズできそうです。
ruby on rails - ActiveAdmin: how to have a text field instead a color picker to input a color? - Stack Overflow

また、permit_paramsで指定していない start_of_sales も表示されています。

 
入力して作成します。

 
作成した後です。 start_of_sales は入力したはずですが、登録されていません。

 

Controllerのcreate/update/destroyをオーバーライドする

本題になります。APIを呼ぶ処理を追加するために、Controllerのメソッドをオーバーライドしていきます。

まず、上で見たController Admin::Fruit の親の定義を見たところ、以下にありました。
https://github.com/activeadmin/activeadmin/blob/v2.9.0/lib/active_admin/base_controller.rb#L7

module ActiveAdmin
  # BaseController for ActiveAdmin.
  # It implements ActiveAdmin controllers core features.
  class BaseController < ::InheritedResources::Base

 
さらに、 InheritedResources::Base は、別のgem inherited_resources にて定義してあります。
https://github.com/activeadmin/inherited_resources/blob/v1.13.0/app/controllers/inherited_resources/base.rb#L11

さらにこの中でincludeされているのが InheritedResources:: Actions です。
https://github.com/activeadmin/inherited_resources/blob/v1.13.0/lib/inherited_resources/actions.rb

そのため、 InheritedResources:: Actions の各メソッドをオーバーライドすることで、APIを呼ぶ処理を追加できそうです。

 
オーバーライドは controller メソッドのブロックに定義します。

 
今回は inherited_resources でのオーバーライド方法にならい、 super do な形にしてみます。
Overwriting actions | activeadmin/inherited_resources

ActiveAdmin.register Fruit do
  permit_params :name, :color

  # コントローラのcreate/update/destroyをオーバーライド
  controller do
    def create
      super do |format|
        call_api(:create)
      end
    end

    def update
      super do |format|
        call_api(:update)
      end
    end

    def destroy
      super do |format|
        call_api(:destroy)
      end
    end

    private

    def call_api(method)
      # 外部APIを呼んだつもり
      # (今回はログに出力する)
      logger.info("======> called api by #{method}")
    end
  end
end

 

動作確認

Fruitに対して作成・更新・削除を行ったところ、以下のようなログが出力されていました。

メソッドをオーバーライドできました。

# 作成
Started POST "/admin/fruits" for 127.0.0.1 at 2021-07-17 21:34:04 +0900
...
  Fruit Create (1.3ms)  INSERT INTO "fruits" ("name", "color", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "みかん"], ["color", "#f2ad18"], ["created_at", "2021-07-17 12:34:04.759054"], ["updated_at", "2021-07-17 12:34:04.759054"]]
  ↳ app/admin/fruit.rb:7:in `create'
  TRANSACTION (1.3ms)  commit transaction
  ↳ app/admin/fruit.rb:7:in `create'
======> called api by create


# 更新
Started PATCH "/admin/fruits/9" for 127.0.0.1 at 2021-07-17 21:34:13 +0900
...
  Fruit Update (0.5ms)  UPDATE "fruits" SET "name" = ?, "updated_at" = ? WHERE "fruits"."id" = ?  [["name", "夏みかん"], ["updated_at", "2021-07-17 12:34:13.249457"], ["id", 9]]
  ↳ app/admin/fruit.rb:13:in `update'
  TRANSACTION (0.9ms)  commit transaction
  ↳ app/admin/fruit.rb:13:in `update'
======> called api by update


# 削除
Started DELETE "/admin/fruits/9" for 127.0.0.1 at 2021-07-17 21:34:16 +0900
Processing by Admin::FruitsController#destroy as HTML
...
  ↳ app/admin/fruit.rb:19:in `destroy'
======> called api by destroy
Redirected to http://localhost:3000/admin/fruits

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi-sandbox/rails_with_active_admin_app

 
関係するプルリクはこちらです。
https://github.com/thinkAmi-sandbox/rails_with_active_admin_app/pull/1