Files
activeadmin-quill_editor/docs/activeadmin-4-detailed-reference.md
Gleb Tv d7c3252c52 feat: Add ActiveAdmin 4 and Propshaft support
- Switch to ActiveAdmin 4 beta with Tailwind CSS
- Replace Sprockets with Propshaft for Rails 8
- Migrate from jQuery to vanilla JavaScript
- Implement proper module initialization without setTimeout hacks
- Add esbuild for JavaScript bundling with clean import aliases
- Configure Tailwind CSS build process with ActiveAdmin plugin
- Update engine configuration for both Propshaft and Sprockets
- Add comprehensive documentation for AA4 gem updates
- Maintain backward compatibility with legacy AA versions

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-23 21:14:49 +03:00

26 KiB

ActiveAdmin 4 Migration Guide for Gem Maintainers

Overview

This document provides a comprehensive guide for gem maintainers on how to update their gems to support ActiveAdmin 4, based on the changes made to the activeadmin-searchable_select gem. The migration involved addressing significant changes in asset handling, JavaScript module systems, dependency management, and CSS selectors.

Key Migration Steps

1. Update Dependency Constraints

Ruby Version Requirements

  • Minimum Ruby version: 3.2 (Ruby 3.0 and 3.1 are dropped in ActiveAdmin 4)
  • Update your gemspec: spec.required_ruby_version = '>= 3.2'

Rails Version Requirements

  • Minimum Rails version: 7.0 (Rails 6.1 support dropped)
  • ActiveAdmin 4 supports Rails 7.x and 8.x

ActiveAdmin Version

# In gemspec
spec.add_runtime_dependency 'activeadmin', ['>= 1.x', '< 5']

2. Asset Pipeline Migration

ActiveAdmin 4 moved away from the traditional Rails asset pipeline to modern JavaScript bundlers.

Key Changes:

  • ActiveAdmin 4 assumes cssbundling-rails and importmap-rails are installed
  • No longer uses register_stylesheet or register_javascript methods
  • Requires explicit JavaScript module initialization

CSS bundling pattern (Rails 7 cssbundling + Tailwind)

  • Build CSS to app/assets/builds/active_admin.css and expose it via app/assets/config/manifest.js:
    • //= link_tree ../builds
    • //= link active_admin.css
    • //= link active_admin.js
    • //= link trumbowyg/icons.svg (when using Trumbowyg)
  • Keep a single Tailwind config at the Rails app root (avoid duplicates). Using ESM works well:
    • tailwind.config.mjs with import activeAdminPlugin from '@activeadmin/activeadmin/plugin'
  • Source file app/assets/stylesheets/active_admin_source.css contains Tailwind directives, gem overrides and imports.
  • If Tailwind CLI does not inline vendor @import from node_modules, concatenate vendor CSS before building. Example build script:
// spec/internal/package.json
{
  "scripts": {
    "build:css": "node ./build_css.js"
  }
}
// spec/internal/build_css.js
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const root = __dirname;
const inputPath = path.join(root, 'app/assets/stylesheets/active_admin_source.css');
const vendorCssPath = path.join(root, 'node_modules/trumbowyg/dist/ui/trumbowyg.css');
const tmpPath = path.join(root, 'app/assets/stylesheets/__aa_tmp.css');
const outPath = path.join(root, 'app/assets/builds/active_admin.css');
const src = fs.readFileSync(inputPath, 'utf8').split(/\r?\n/);
const vendorCss = fs.readFileSync(vendorCssPath, 'utf8');
const tailwind = ['@tailwind base;','@tailwind components;','@tailwind utilities;'].join('\n');
const body = src.slice(3).filter(l => !l.includes('trumbowyg.css')).join('\n');
fs.writeFileSync(tmpPath, `${tailwind}\n\n${vendorCss}\n\n${body}`);
spawnSync('npx', ['tailwindcss','-c', path.join(root,'tailwind.config.mjs'),'-i', tmpPath,'-o', outPath], { stdio: 'inherit', cwd: root });
fs.unlinkSync(tmpPath);

This ensures vendor CSS (e.g., Trumbowyg) ships inside the built active_admin.css while keeping Tailwind at the top of the cascade so overrides behave as expected.

JavaScript Module Support

Create multiple module formats to support different bundlers:

  1. ESM Module (your_gem.esm.js):
import $ from 'jquery';
import select2 from 'select2';  // Or your jQuery plugin

// Critical: Initialize jQuery plugins on the jQuery object for production builds
// This ensures the plugin methods are available on jQuery selections
select2($);

// Ensure jQuery is globally available for other scripts
window.$ = window.jQuery = $;

// Your initialization code wrapped in a DOM ready handler
$(() => {
  // Initialize your plugin on specific selectors
  $('.your-selector').yourPlugin({
    // plugin options
  });
  
  // Listen for Turbo/Turbolinks events for dynamic content
  $(document).on('turbo:load turbolinks:load', () => {
    $('.your-selector').yourPlugin();
  });
  
  // For ActiveAdmin's dynamic content (filters, forms)
  $(document).on('has_many_add:after', '.has_many_container', () => {
    $('.your-selector').yourPlugin();
  });
});

// Export for use as a module
export default function initializeYourGem() {
  // Initialization logic
}
  1. Traditional Module (your_gem.js for backward compatibility):
//= require jquery
//= require select2

(function($) {
  'use strict';
  
  $(document).ready(function() {
    $('.your-selector').yourPlugin();
  });
  
  // Turbolinks/Turbo support
  $(document).on('turbo:load turbolinks:load', function() {
    $('.your-selector').yourPlugin();
  });
})(jQuery);
  1. CDN-compatible version (for importmap users):
// Assumes jQuery and plugins are loaded via CDN
(() => {
  'use strict';
  
  const $ = window.jQuery || window.$;
  
  if (!$) {
    console.error('jQuery is required for YourGem');
    return;
  }
  
  // Wait for DOM ready
  $(() => {
    $('.your-selector').yourPlugin();
  });
})();

3. Installation Generator

Create a generator to help users set up your gem with different bundlers:

module YourGem
  module Generators
    class InstallGenerator < Rails::Generators::Base
      class_option :bundler,
                   type: :string,
                   default: 'esbuild',
                   enum: %w[esbuild importmap webpack]

      def setup_javascript
        case options[:bundler]
        when 'esbuild'
          setup_esbuild
        when 'importmap'
          setup_importmap
        when 'webpack'
          setup_webpack
        end
      end
      
      private
      
      def setup_esbuild
        # Add imports to app/javascript/active_admin.js
        append_to_file 'app/javascript/active_admin.js' do
          <<~JS
            import $ from 'jquery';
            import yourPlugin from 'your-plugin';
            
            // Initialize plugin on jQuery
            yourPlugin($);
            window.$ = window.jQuery = $;
            
            import '@your-scope/your-gem';
          JS
        end
      end
      
      def setup_importmap
        # Add pins to config/importmap.rb
        append_to_file 'config/importmap.rb' do
          <<~RUBY
            pin "jquery", to: "https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"
            pin "your-plugin", to: "https://cdn.jsdelivr.net/npm/your-plugin/dist/plugin.min.js"
            pin "your-gem", to: "your-gem.js"
          RUBY
        end
      end
    end
  end
end

4. NPM Package Publishing

If your gem includes JavaScript, consider publishing an NPM package:

Package.json Configuration

{
  "name": "@activeadmin/your-gem",
  "version": "1.0.0",
  "description": "Your gem description for ActiveAdmin",
  "main": "src/index.js",
  "module": "src/index.js",
  "exports": {
    ".": {
      "import": "./src/index.js",
      "require": "./src/index.js",
      "default": "./src/index.js"
    },
    "./css": "./src/styles.scss"
  },
  "peerDependencies": {
    "jquery": ">= 3.0, < 5",
    "select2": "^4.0.13"  // Add your dependencies here
  },
  "files": [
    "src/**/*",
    "app/assets/**/*",
    "vendor/assets/**/*"
  ],
  "scripts": {
    "prepare_sources": "mkdir -p src && cp -r app/assets/javascripts/active_admin/* src/ && cp -r app/assets/stylesheets/active_admin/* src/",
    "prepublishOnly": "npm run prepare_sources"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/your-org/your-gem.git"
  },
  "keywords": ["activeadmin", "rails", "your-feature"],
  "author": "Your Name",
  "license": "MIT"
}

Preparing JavaScript Assets for NPM

Create a script to copy your assets to the NPM package structure:

#!/bin/bash
# scripts/prepare_npm_package.sh

# Create src directory for NPM
mkdir -p src

# Copy JavaScript files
cp -r app/assets/javascripts/active_admin/* src/

# Copy SCSS files if needed
cp -r app/assets/stylesheets/active_admin/* src/

# Ensure ESM module is included
cp app/assets/javascripts/active_admin/your_gem.esm.js src/index.js

5. CSS Selector Updates

ActiveAdmin 4 introduced several CSS class changes:

ActiveAdmin 3.x ActiveAdmin 4.x
.filter_form .filters-form
.tabs component Removed - use divs with Tailwind
.columns component Replaced with Tailwind grid

Update your JavaScript and CSS accordingly:

// Old
$('.filter_form select').select2();

// New
$('.filters-form select').select2();

6. Testing Updates with Combustion

Complete Combustion Workflow for ActiveAdmin 4 Gems

CRITICAL: This workflow is specifically for testing ActiveAdmin extension gems with Combustion.

Step 1: Add Dependencies to Gemfile
# Gemfile (for development/testing)
gem 'combustion'
gem 'importmap-rails', '~> 2.0'  # Required for ActiveAdmin 4
Step 2: Run Combustion Generator (MANDATORY!)
# NEVER manually create spec/internal structure!
bundle exec combust

This creates:

  • spec/internal/ - minimal Rails app structure
  • config.ru - in gem root for bundle exec rackup
  • Basic Rails directories and config files
Step 3: Set Up Test App Structure

After generator, create these files:

# Create necessary directories
mkdir -p spec/internal/app/models
mkdir -p spec/internal/app/admin
mkdir -p spec/internal/app/assets/stylesheets
mkdir -p spec/internal/app/javascript
mkdir -p spec/internal/config/initializers
Basic Combustion Configuration
# spec/rails_helper.rb
ENV['RAILS_ENV'] ||= 'test'

require 'combustion'

# Initialize Combustion with only needed components
Combustion.path = 'spec/internal'
Combustion.initialize!(:active_record, :action_controller, :action_view) do
  config.load_defaults Rails::VERSION::STRING.to_f if Rails::VERSION::MAJOR >= 7
end

require 'rspec/rails'
require 'capybara/rails'
Step 4: Configure config.ru (CRITICAL Loading Order!)
# config.ru - MUST control loading order for ActiveAdmin!
require "rubygems"
require "bundler"

# DON'T use Bundler.require - it loads gems too early!
Bundler.setup(:default, :development)

# Load Rails and combustion first
require 'combustion'

# Initialize Combustion with Rails components
Combustion.initialize! :active_record, :action_controller, :action_view do
  config.load_defaults Rails::VERSION::STRING.to_f if Rails::VERSION::MAJOR >= 7
end

# NOW we can load ActiveAdmin and its dependencies after Rails is initialized
require 'importmap-rails'
require 'active_admin'
require 'your_activeadmin_gem'

run Combustion::Application
Step 5: Configure ActiveAdmin Assets
# spec/internal/app/assets/stylesheets/active_admin.css
@tailwind base;
@tailwind components;
@tailwind utilities;
# spec/internal/config/importmap.rb
pin "@activeadmin/activeadmin", to: "active_admin.js", preload: true
// spec/internal/app/javascript/active_admin.js
// Placeholder for ActiveAdmin JS
console.log("ActiveAdmin loaded");
Step 6: Set Up Test Models and Admin Resources
# spec/internal/db/schema.rb
ActiveRecord::Schema.define do
  create_table :active_admin_comments, force: true do |t|
    t.string :namespace
    t.text :body
    t.references :resource, polymorphic: true
    t.references :author, polymorphic: true
    t.timestamps
  end

  create_table :posts, force: true do |t|
    t.string :title
    t.text :body
    t.text :description
    t.timestamps
  end
end
# spec/internal/config/routes.rb
Rails.application.routes.draw do
  ActiveAdmin.routes(self)
  root to: 'admin/dashboard#index'
end
# spec/internal/config/initializers/active_admin.rb
ActiveAdmin.setup do |config|
  config.site_title = "Test App"
  config.authentication_method = false
  config.current_user_method = false
  config.batch_actions = true
end
Step 7: Configure rails_helper.rb
# spec/rails_helper.rb
ENV['RAILS_ENV'] ||= 'test'

require 'combustion'

Combustion.path = 'spec/internal'
Combustion.initialize!(:active_record, :action_controller, :action_view) do
  config.load_defaults Rails::VERSION::STRING.to_f if Rails::VERSION::MAJOR >= 7
end

require 'rspec/rails'
require 'capybara/rails'
Step 8: Running the Test App
# Start the test app server
bundle exec rackup
# Visit http://localhost:9292/admin

Critical Testing Pitfall: Model Registration Conflicts

Problem: Dynamic ActiveAdmin registrations in tests conflict with static admin files.

Solution: Choose ONE approach per model:

  1. Static Registration (for consistent configs):
# spec/internal/app/admin/users.rb
ActiveAdmin.register User do
  permit_params :name, :email
  # Fixed configuration
end
  1. Dynamic Registration (for varying configs):
# spec/support/active_admin_helpers.rb
module ActiveAdminHelpers
  module_function

  def setup
    ActiveAdmin.application = nil
    yield  # Dynamic registration block
    reload_routes!
  end
  
  def reload_routes!
    Rails.application.reload_routes!
  end
end

# In test - NO static admin file for Post model
ActiveAdminHelpers.setup do
  ActiveAdmin.register(Post) do
    # Test-specific configuration
  end
end

Important: Never mix static and dynamic registration for the same model!

Capybara Configuration with Playwright

# spec/support/capybara.rb
require 'capybara-playwright-driver'

Capybara.register_driver :playwright do |app|
  Capybara::Playwright::Driver.new(
    app,
    browser_type: :chromium,
    headless: true,
    viewport: { width: 1920, height: 1080 }
  )
end

Capybara.default_driver = :rack_test
Capybara.javascript_driver = :playwright

# Important: Set server for JS tests
Capybara.server = :puma, { Silent: true }

Waiting for JavaScript/AJAX in Tests

# spec/support/wait_helpers.rb
module WaitHelpers
  def wait_for_ajax
    Timeout.timeout(Capybara.default_max_wait_time) do
      sleep 0.1
      loop until finished_all_ajax_requests?
    end
  end
  
  def finished_all_ajax_requests?
    page.evaluate_script('jQuery.active').zero?
  end
  
  # For Select2 or similar plugins
  def wait_for_select2
    expect(page).to have_css('.select2-container', wait: 5)
  end
end

RSpec.configure do |config|
  config.include WaitHelpers, type: :feature
end

7. Production Build Issues

Common production issues and solutions:

Issue: JavaScript plugin not initialized

Solution: Explicitly initialize jQuery plugins

import select2 from 'select2';
import $ from 'jquery';

// This is critical for production builds
select2($);

Issue: jQuery not globally available

Solution: Ensure global assignment

window.$ = window.jQuery = $;

Issue: Assets not loading in production

Solution: Use CDN fallbacks or vendor assets

# In your gem's engine.rb
class Engine < ::Rails::Engine
  initializer 'your_gem.assets' do |app|
    if Rails.env.production?
      # Add fallback assets
    end
  end
end

8. CI/CD Updates

Update your GitHub Actions workflow:

name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        ruby: ['3.2', '3.3']
        rails: ['7.0', '7.1', '7.2', '8.0']
        activeadmin: ['4.0.0.beta16']
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby }}
          bundler-cache: true
      
      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: Install npm dependencies
        run: npm install
      
      - name: Install Playwright browsers
        run: npx playwright install chromium
      
      - name: Run tests
        run: bundle exec rspec

9. Appraisals Configuration

Use Appraisal gem to test against multiple versions:

# Appraisals file
appraise 'rails-7.x-active-admin-4.x' do
  gem 'rails', '~> 7.0'
  gem 'activeadmin', '~> 4.0.0.beta16'
  gem 'propshaft'  # Required - Sprockets not supported
end

appraise 'rails-8.x-active-admin-4.x' do
  gem 'rails', '~> 8.0'
  gem 'activeadmin', '~> 4.0.0.beta16'
  # Rails 8 includes Propshaft by default
end

10. Common Pitfalls and Solutions

Pitfall 1: Select2 or similar jQuery plugins not working

Root Cause: Plugin not attached to jQuery object in production Solution: Explicitly call plugin($) after importing

import select2 from 'select2';
import $ from 'jquery';
select2($);  // Critical - attaches plugin to jQuery

Pitfall 2: CSS classes not found

Root Cause: ActiveAdmin 4 changed many CSS selectors Solution: Search and replace old selectors with new ones

  • .filter_form.filters-form
  • .select2-container needs explicit initialization in tests

Pitfall 3: Tests passing locally but failing in CI

Root Cause: Missing JavaScript dependencies or browser drivers Solution:

# .github/workflows/ci.yml
- name: Install Playwright browsers
  run: npx playwright install chromium

Pitfall 4: Assets not compiling in production

Root Cause: Missing bundler configuration Solution: Provide clear setup instructions for each bundler type in your README

Pitfall 5: Model registration conflicts in tests

Root Cause: Static admin files override dynamic test registrations Solution:

  • Delete static admin files for models that need dynamic config
  • Keep static files only for models with consistent config
  • Never mix both approaches for the same model

Pitfall 6: Input HTML options not passing through

Root Cause: Options can be lost during form DSL processing Solution: Test with clean models not affected by other registrations

# Test with a model that has no static admin file
ActiveAdmin.register(TestModel) do
  form do |f|
    f.input :field, as: :searchable_select, 
            input_html: { class: 'custom-class' }
  end
end

Pitfall 7: Flaky JavaScript tests

Root Cause: Not waiting for AJAX/DOM updates Solution: Add proper wait helpers

def wait_for_select2
  expect(page).to have_css('.select2-container', wait: 5)
end

Pitfall 8: Rails 8 compatibility issues

Root Cause: Formtastic 5.0 changes, Ransack updates Solution:

  • Test against multiple Rails versions using Appraisal
  • Ensure Ransack methods are defined in models
def self.ransackable_attributes(_auth_object = nil)
  %w[name title]
end

Pitfall 9: Combustion and ActiveAdmin Loading Order Issues

Root Cause: ActiveAdmin requires Rails components at load time, conflicts with Combustion's initialization Critical Issue: ActiveAdmin's Bundler.require loads before Rails is initialized by Combustion Symptoms:

  • uninitialized constant Formtastic::ActionView
  • uninitialized constant ActiveSupport::Autoload
  • uninitialized constant #<Class:ActiveAdmin>::Importmap
  • Rackup fails with various Rails component loading errors Solution:
  • Use Bundler.setup instead of Bundler.require in config.ru
  • Load ActiveAdmin AFTER Combustion initializes Rails
  • Include importmap-rails for ActiveAdmin 4
  • Don't require ActiveAdmin components in gem's main file
# Bad: In lib/your_gem.rb
require 'active_admin'  # This loads too early!
require 'formtastic/inputs/your_input'

# Good: In engine.rb
initializer 'your_gem.setup', after: :load_config_initializers do
  require 'active_admin' if defined?(Rails.application)
  ActiveSupport.on_load(:active_admin) do
    require 'formtastic/inputs/your_input'
  end
end

Pitfall 10: ActiveAdmin 4 Asset Pipeline Requirements (CRITICAL FOR COMBUSTION GEMS)

Root Cause: ActiveAdmin 4 uses Tailwind CSS v3 with custom plugin, requires compilation Critical Issue: CSS must be compiled through Tailwind with ActiveAdmin plugin Symptoms:

  • Unstyled admin pages (no proper layout, just basic HTML)
  • CSS file exists but has 0 bytes or wrong content
  • The asset "active_admin.css" is not present in the asset pipeline

Complete Solution for Combustion-based Gems:

  1. Add Dependencies (Gemfile):
gem 'importmap-rails', '~> 2.0'
gem 'tailwindcss-rails'  # For bundled tailwindcss executable
  1. Install NPM packages (in spec/internal):
cd spec/internal
npm init -y
npm install --save-dev tailwindcss@^3  # Use v3, not v4!
npm install --save-dev @activeadmin/activeadmin  # For plugin (optional)
  1. Copy ActiveAdmin Plugin (from Ruby gem):
cp $(bundle show activeadmin)/plugin.js spec/internal/activeadmin-plugin.js
  1. Create Tailwind Config (spec/internal/tailwind.config.mjs):
import activeAdminPlugin from './activeadmin-plugin.js';
import { execSync } from 'child_process';

const activeAdminPath = execSync('bundle show activeadmin', { encoding: 'utf-8' }).trim();

export default {
  content: [
    `${activeAdminPath}/app/views/**/*.{arb,erb,html,rb}`,
    './app/admin/**/*.{arb,erb,html,rb}',
    './app/views/**/*.{arb,erb,html}',
    './app/javascript/**/*.js'
  ],
  darkMode: 'selector',
  plugins: [activeAdminPlugin]
}
  1. Create Source CSS (spec/internal/app/assets/stylesheets/active_admin_source.css):
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. Build CSS:
cd spec/internal
npx tailwindcss -c tailwind.config.mjs \
  -i app/assets/stylesheets/active_admin_source.css \
  -o app/assets/stylesheets/active_admin_compiled.css \
  --minify
  1. Configure Sprockets (spec/internal/app/assets/stylesheets/active_admin.css):
/*
 * This imports the compiled Tailwind CSS with ActiveAdmin styles
 *= require ./active_admin_compiled
 */
  1. Update Manifest (spec/internal/app/assets/config/manifest.js):
//= link_tree ../builds
//= link active_admin.css
  1. Create Build Task (lib/tasks/active_admin.rake):
namespace :active_admin do
  desc 'Build Active Admin Tailwind stylesheets'
  task :build do
    require 'fileutils'
    
    input = File.expand_path('../../spec/internal/app/assets/stylesheets/active_admin_source.css', __dir__)
    output = File.expand_path('../../spec/internal/app/assets/stylesheets/active_admin_compiled.css', __dir__)
    config = File.expand_path('../../spec/internal/tailwind.config.mjs', __dir__)
    
    FileUtils.mkdir_p(File.dirname(output))
    
    command = ['npx', 'tailwindcss', '-c', config, '-i', input, '-o', output, '--minify']
    puts "Building Tailwind CSS: #{command.join(' ')}"
    system(*command, exception: true)
    puts "Tailwind CSS build complete: #{output}"
  end
end

Pitfall 11: Formtastic Custom Inputs Not Loading in Combustion

Root Cause: Loading order issues with ActiveAdmin, Formtastic, and custom inputs Symptoms:

  • Formtastic::UnknownInputError: Unable to find input class YourInput
  • Input works in production but not in Combustion test environment

Solutions:

  1. Immediate Fix in config.ru (for Combustion):
# config.ru
require 'combustion'
Combustion.initialize! :active_record, :action_controller, :action_view

require 'importmap-rails'
require 'active_admin'
require 'your_gem'

# Critical: Explicitly require custom inputs after everything else
require 'formtastic/inputs/your_input'

run Combustion::Application
  1. Engine Initialization Fix:
# lib/your_gem/engine.rb
initializer 'your_gem.setup', after: :load_config_initializers do
  require 'active_admin' if defined?(Rails.application)
  
  # Load immediately AND hook into ActiveAdmin
  require 'formtastic/inputs/your_input'
  
  ActiveSupport.on_load(:active_admin) do
    require 'formtastic/inputs/your_input'
  end
end
  1. Workaround Using Standard Inputs:
# If custom input isn't loading, use standard input with same attributes
f.input :field, as: :text, input_html: { 
  class: 'your-input-class', 
  'data-your-attribute': true 
}

Note: After making these changes, restart the server for them to take effect.

Migration Checklist

  • Update Ruby version requirement to >= 3.2
  • Update Rails version requirement to >= 7.0
  • Update ActiveAdmin dependency to support 4.x
  • Create ESM JavaScript modules
  • Add installation generator for different bundlers
  • Publish NPM package (if applicable)
  • Update CSS selectors (.filter_form.filters-form)
  • Fix jQuery plugin initialization for production
  • Update test suite for new asset handling
  • Configure CI for multiple version testing
  • Update documentation with setup instructions
  • Test with esbuild, webpack, and importmap
  • Add CDN fallbacks for JavaScript dependencies
  • Handle both Sprockets and Propshaft

Example Implementation

See the full implementation in the activeadmin-searchable_select gem:

Resources

Conclusion

Migrating a gem to support ActiveAdmin 4 requires careful attention to:

  1. Modern JavaScript module systems
  2. Flexible asset pipeline support
  3. Updated CSS selectors and components
  4. Proper jQuery plugin initialization
  5. Comprehensive testing across different setups

The key to success is providing multiple paths for users with different asset pipeline configurations while maintaining backward compatibility where possible.