rs-activeadmin-searchable_select 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.actrc +20 -0
  3. data/.claude/commands/fix-tests.md +203 -0
  4. data/.github/workflows/ci.yml +170 -0
  5. data/.github/workflows/npm-publish.yml +47 -0
  6. data/.gitignore +27 -0
  7. data/.npmignore +58 -0
  8. data/.rspec +2 -0
  9. data/.rubocop.yml +67 -0
  10. data/.yardopts +2 -0
  11. data/AGENTS.md +39 -0
  12. data/Appraisals +15 -0
  13. data/CHANGELOG.md +24 -0
  14. data/CLAUDE.md +104 -0
  15. data/Gemfile +11 -0
  16. data/Gemfile.lock +366 -0
  17. data/LICENSE.txt +25 -0
  18. data/README.md +439 -0
  19. data/Rakefile +4 -0
  20. data/bin/rspec +17 -0
  21. data/config/database.yml +16 -0
  22. data/config.ru +28 -0
  23. data/docs/activeadmin-4-detailed-reference.md +932 -0
  24. data/docs/activeadmin-4-gem-migration-guide.md +313 -0
  25. data/docs/combustion.md +213 -0
  26. data/docs/for-next-session.md +199 -0
  27. data/docs/guide-update-your-app.md +213 -0
  28. data/docs/propshaft-readme.md +121 -0
  29. data/docs/propshaft-upgrade.md +267 -0
  30. data/docs/rails-7-asset-pipeline.md +279 -0
  31. data/docs/setup-activeadmin-app.md +552 -0
  32. data/docs/setup-activeadmin-gem.md +535 -0
  33. data/docs/upload-system.md +225 -0
  34. data/gemfiles/rails_7.x_active_admin_4.x.gemfile +11 -0
  35. data/gemfiles/rails_7.x_active_admin_4.x.gemfile.lock +371 -0
  36. data/gemfiles/rails_8.x_active_admin_4.x.gemfile +11 -0
  37. data/gemfiles/rails_8.x_active_admin_4.x.gemfile.lock +366 -0
  38. data/lefthook.yml +17 -0
  39. data/lib/activeadmin/inputs/filters/searchable_select_input.rb +13 -0
  40. data/lib/activeadmin/inputs/searchable_select_input.rb +11 -0
  41. data/lib/activeadmin/searchable_select/engine.rb +17 -0
  42. data/lib/activeadmin/searchable_select/option_collection.rb +119 -0
  43. data/lib/activeadmin/searchable_select/resource_dsl_extension.rb +56 -0
  44. data/lib/activeadmin/searchable_select/resource_extension.rb +10 -0
  45. data/lib/activeadmin/searchable_select/select_input_extension.rb +159 -0
  46. data/lib/activeadmin/searchable_select/version.rb +5 -0
  47. data/lib/activeadmin/searchable_select.rb +20 -0
  48. data/lib/activeadmin-searchable_select.rb +4 -0
  49. data/lib/generators/active_admin/searchable_select/install/install_generator.rb +217 -0
  50. data/package-lock.json +18 -0
  51. data/package.json +45 -0
  52. data/rs-activeadmin-searchable_select.gemspec +38 -0
  53. data/sonar-project.properties +25 -0
  54. data/spec/features/ajax_params_spec.rb +31 -0
  55. data/spec/features/end_to_end_spec.rb +227 -0
  56. data/spec/features/filter_input_spec.rb +137 -0
  57. data/spec/features/form_input_spec.rb +122 -0
  58. data/spec/features/inline_ajax_setting_spec.rb +26 -0
  59. data/spec/features/input_errors_spec.rb +76 -0
  60. data/spec/features/input_html_options_spec.rb +30 -0
  61. data/spec/features/options_dsl_spec.rb +220 -0
  62. data/spec/features/production_build_spec.rb +108 -0
  63. data/spec/internal/app/admin/categories.rb +26 -0
  64. data/spec/internal/app/admin/dashboard.rb +29 -0
  65. data/spec/internal/app/admin/option_types.rb +19 -0
  66. data/spec/internal/app/admin/option_values.rb +30 -0
  67. data/spec/internal/app/admin/posts.rb +27 -0
  68. data/spec/internal/app/admin/products.rb +22 -0
  69. data/spec/internal/app/admin/rgb_colors.rb +25 -0
  70. data/spec/internal/app/admin/tag_names.rb +21 -0
  71. data/spec/internal/app/admin/test_ajax_params_category.rb +10 -0
  72. data/spec/internal/app/admin/test_ajax_params_post.rb +20 -0
  73. data/spec/internal/app/admin/test_form_post_class.rb +7 -0
  74. data/spec/internal/app/admin/test_form_post_custom.rb +11 -0
  75. data/spec/internal/app/admin/test_form_post_resource.rb +11 -0
  76. data/spec/internal/app/admin/test_form_post_resource_custom.rb +12 -0
  77. data/spec/internal/app/admin/test_inline_ajax_post.rb +9 -0
  78. data/spec/internal/app/admin/test_input_html_post.rb +11 -0
  79. data/spec/internal/app/admin/test_posts_display_text.rb +9 -0
  80. data/spec/internal/app/admin/test_posts_filter.rb +9 -0
  81. data/spec/internal/app/admin/test_posts_named.rb +9 -0
  82. data/spec/internal/app/admin/test_posts_pagination.rb +9 -0
  83. data/spec/internal/app/admin/test_posts_payload_lambda.rb +11 -0
  84. data/spec/internal/app/admin/test_posts_payload_proc.rb +9 -0
  85. data/spec/internal/app/admin/test_posts_scope_lambda.rb +8 -0
  86. data/spec/internal/app/admin/test_posts_scope_params.rb +8 -0
  87. data/spec/internal/app/admin/test_posts_scope_user.rb +8 -0
  88. data/spec/internal/app/admin/test_posts_text_attr.rb +5 -0
  89. data/spec/internal/app/admin/users.rb +23 -0
  90. data/spec/internal/app/admin/variants.rb +31 -0
  91. data/spec/internal/app/assets/config/manifest.js +1 -0
  92. data/spec/internal/app/assets/javascripts/active_admin.js +2 -0
  93. data/spec/internal/app/assets/javascripts/searchable_select_test.js +2 -0
  94. data/spec/internal/app/controllers/application_controller.rb +5 -0
  95. data/spec/internal/app/css/active_admin_source.css +81 -0
  96. data/spec/internal/app/js/active_admin.js +17 -0
  97. data/spec/internal/app/models/article.rb +12 -0
  98. data/spec/internal/app/models/category.rb +12 -0
  99. data/spec/internal/app/models/internal/tag_name.rb +14 -0
  100. data/spec/internal/app/models/internal_tag_name.rb +11 -0
  101. data/spec/internal/app/models/option_type.rb +12 -0
  102. data/spec/internal/app/models/option_value.rb +4 -0
  103. data/spec/internal/app/models/post.rb +15 -0
  104. data/spec/internal/app/models/product.rb +12 -0
  105. data/spec/internal/app/models/rgb_color.rb +16 -0
  106. data/spec/internal/app/models/user.rb +12 -0
  107. data/spec/internal/app/models/variant.rb +12 -0
  108. data/spec/internal/build_activeadmin_css.js +115 -0
  109. data/spec/internal/config/database.yml +7 -0
  110. data/spec/internal/config/environment.rb +48 -0
  111. data/spec/internal/config/initializers/active_admin.rb +53 -0
  112. data/spec/internal/config/initializers/assets.rb +9 -0
  113. data/spec/internal/config/initializers/searchable_select.rb +6 -0
  114. data/spec/internal/config/routes.rb +4 -0
  115. data/spec/internal/config.ru +4 -0
  116. data/spec/internal/db/schema.rb +63 -0
  117. data/spec/internal/db/seeds.rb +88 -0
  118. data/spec/internal/esbuild.config.js +30 -0
  119. data/spec/internal/inject-jquery.js +4 -0
  120. data/spec/internal/log/.gitignore +1 -0
  121. data/spec/internal/package/LICENSE.txt +25 -0
  122. data/spec/internal/package/README.md +439 -0
  123. data/spec/internal/package/package.json +45 -0
  124. data/spec/internal/package/src/index.js +1 -0
  125. data/spec/internal/package/src/searchable_select/init.js +1 -0
  126. data/spec/internal/package/src/searchable_select.css +1 -0
  127. data/spec/internal/package/src/searchable_select.scss +1 -0
  128. data/spec/internal/package-lock.json +1385 -0
  129. data/spec/internal/package.json +26 -0
  130. data/spec/internal/public/favicon.ico +0 -0
  131. data/spec/internal/spec/internal/app/css/active_admin_source.css +38 -0
  132. data/spec/internal/spec/internal/log/test.log +0 -0
  133. data/spec/internal/tailwind-active_admin.config.js +53 -0
  134. data/spec/rails_helper.rb +86 -0
  135. data/spec/spec_helper.rb +137 -0
  136. data/spec/support/active_admin_helpers.rb +17 -0
  137. data/spec/support/capybara.rb +8 -0
  138. data/spec/support/models.rb +88 -0
  139. data/spec/support/pluck_polyfill.rb +12 -0
  140. data/spec/support/reset_settings.rb +5 -0
  141. data/src/index.js +77 -0
  142. data/src/searchable_select/init.js +58 -0
  143. data/src/searchable_select.css +5 -0
  144. data/src/searchable_select.css.map +1 -0
  145. metadata +405 -0
@@ -0,0 +1,932 @@
1
+ # ActiveAdmin 4 Migration Guide for Gem Maintainers
2
+
3
+ ## Overview
4
+
5
+ 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.
6
+
7
+ ## Key Migration Steps
8
+
9
+ ### 1. Update Dependency Constraints
10
+
11
+ #### Ruby Version Requirements
12
+ - **Minimum Ruby version**: 3.2 (Ruby 3.0 and 3.1 are dropped in ActiveAdmin 4)
13
+ - Update your gemspec: `spec.required_ruby_version = '>= 3.2'`
14
+
15
+ #### Rails Version Requirements
16
+ - **Minimum Rails version**: 7.0 (Rails 6.1 support dropped)
17
+ - ActiveAdmin 4 supports Rails 7.x and 8.x
18
+
19
+ #### ActiveAdmin Version
20
+ ```ruby
21
+ # In gemspec
22
+ spec.add_runtime_dependency 'activeadmin', ['>= 1.x', '< 5']
23
+ ```
24
+
25
+ ### 2. Asset Pipeline Migration
26
+
27
+ ActiveAdmin 4 moved away from the traditional Rails asset pipeline to modern JavaScript bundlers.
28
+
29
+ #### Key Changes:
30
+ - ActiveAdmin 4 assumes `cssbundling-rails` and `importmap-rails` are installed
31
+ - No longer uses `register_stylesheet` or `register_javascript` methods
32
+ - Requires explicit JavaScript module initialization
33
+
34
+ #### CSS bundling pattern (Rails 7 cssbundling + Tailwind)
35
+
36
+ - Build CSS to `app/assets/builds/active_admin.css` and expose it via `app/assets/config/manifest.js`:
37
+ - `//= link_tree ../builds`
38
+ - `//= link active_admin.css`
39
+ - `//= link active_admin.js`
40
+ - `//= link trumbowyg/icons.svg` (when using Trumbowyg)
41
+ - Keep a single Tailwind config at the Rails app root (avoid duplicates). Using ESM works well:
42
+ - `tailwind.config.mjs` with `import activeAdminPlugin from '@activeadmin/activeadmin/plugin'`
43
+ - Source file `app/assets/stylesheets/active_admin_source.css` contains Tailwind directives, gem overrides and imports.
44
+ - If Tailwind CLI does not inline vendor `@import` from `node_modules`, concatenate vendor CSS before building. Example build script:
45
+
46
+ ```json
47
+ // spec/internal/package.json
48
+ {
49
+ "scripts": {
50
+ "build:css": "node ./build_css.js"
51
+ }
52
+ }
53
+ ```
54
+
55
+ ```js
56
+ // spec/internal/build_css.js
57
+ const fs = require('fs');
58
+ const path = require('path');
59
+ const { spawnSync } = require('child_process');
60
+ const root = __dirname;
61
+ const inputPath = path.join(root, 'app/assets/stylesheets/active_admin_source.css');
62
+ const vendorCssPath = path.join(root, 'node_modules/trumbowyg/dist/ui/trumbowyg.css');
63
+ const tmpPath = path.join(root, 'app/assets/stylesheets/__aa_tmp.css');
64
+ const outPath = path.join(root, 'app/assets/builds/active_admin.css');
65
+ const src = fs.readFileSync(inputPath, 'utf8').split(/\r?\n/);
66
+ const vendorCss = fs.readFileSync(vendorCssPath, 'utf8');
67
+ const tailwind = ['@tailwind base;','@tailwind components;','@tailwind utilities;'].join('\n');
68
+ const body = src.slice(3).filter(l => !l.includes('trumbowyg.css')).join('\n');
69
+ fs.writeFileSync(tmpPath, `${tailwind}\n\n${vendorCss}\n\n${body}`);
70
+ spawnSync('npx', ['tailwindcss','-c', path.join(root,'tailwind.config.mjs'),'-i', tmpPath,'-o', outPath], { stdio: 'inherit', cwd: root });
71
+ fs.unlinkSync(tmpPath);
72
+ ```
73
+
74
+ 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.
75
+
76
+ #### JavaScript Module Support
77
+
78
+ Create multiple module formats to support different bundlers:
79
+
80
+ 1. **ESM Module** (`your_gem.esm.js`):
81
+ ```javascript
82
+ import $ from 'jquery';
83
+ import select2 from 'select2'; // Or your jQuery plugin
84
+
85
+ // Critical: Initialize jQuery plugins on the jQuery object for production builds
86
+ // This ensures the plugin methods are available on jQuery selections
87
+ select2($);
88
+
89
+ // Ensure jQuery is globally available for other scripts
90
+ window.$ = window.jQuery = $;
91
+
92
+ // Your initialization code wrapped in a DOM ready handler
93
+ $(() => {
94
+ // Initialize your plugin on specific selectors
95
+ $('.your-selector').yourPlugin({
96
+ // plugin options
97
+ });
98
+
99
+ // Listen for Turbo/Turbolinks events for dynamic content
100
+ $(document).on('turbo:load turbolinks:load', () => {
101
+ $('.your-selector').yourPlugin();
102
+ });
103
+
104
+ // For ActiveAdmin's dynamic content (filters, forms)
105
+ $(document).on('has_many_add:after', '.has_many_container', () => {
106
+ $('.your-selector').yourPlugin();
107
+ });
108
+ });
109
+
110
+ // Export for use as a module
111
+ export default function initializeYourGem() {
112
+ // Initialization logic
113
+ }
114
+ ```
115
+
116
+ 2. **Traditional Module** (`your_gem.js` for backward compatibility):
117
+ ```javascript
118
+ //= require jquery
119
+ //= require select2
120
+
121
+ (function($) {
122
+ 'use strict';
123
+
124
+ $(document).ready(function() {
125
+ $('.your-selector').yourPlugin();
126
+ });
127
+
128
+ // Turbolinks/Turbo support
129
+ $(document).on('turbo:load turbolinks:load', function() {
130
+ $('.your-selector').yourPlugin();
131
+ });
132
+ })(jQuery);
133
+ ```
134
+
135
+ 3. **CDN-compatible version** (for importmap users):
136
+ ```javascript
137
+ // Assumes jQuery and plugins are loaded via CDN
138
+ (() => {
139
+ 'use strict';
140
+
141
+ const $ = window.jQuery || window.$;
142
+
143
+ if (!$) {
144
+ console.error('jQuery is required for YourGem');
145
+ return;
146
+ }
147
+
148
+ // Wait for DOM ready
149
+ $(() => {
150
+ $('.your-selector').yourPlugin();
151
+ });
152
+ })();
153
+ ```
154
+
155
+ ### 3. Installation Generator
156
+
157
+ Create a generator to help users set up your gem with different bundlers:
158
+
159
+ ```ruby
160
+ module YourGem
161
+ module Generators
162
+ class InstallGenerator < Rails::Generators::Base
163
+ class_option :bundler,
164
+ type: :string,
165
+ default: 'esbuild',
166
+ enum: %w[esbuild importmap webpack]
167
+
168
+ def setup_javascript
169
+ case options[:bundler]
170
+ when 'esbuild'
171
+ setup_esbuild
172
+ when 'importmap'
173
+ setup_importmap
174
+ when 'webpack'
175
+ setup_webpack
176
+ end
177
+ end
178
+
179
+ private
180
+
181
+ def setup_esbuild
182
+ # Add imports to app/javascript/active_admin.js
183
+ append_to_file 'app/javascript/active_admin.js' do
184
+ <<~JS
185
+ import $ from 'jquery';
186
+ import yourPlugin from 'your-plugin';
187
+
188
+ // Initialize plugin on jQuery
189
+ yourPlugin($);
190
+ window.$ = window.jQuery = $;
191
+
192
+ import '@your-scope/your-gem';
193
+ JS
194
+ end
195
+ end
196
+
197
+ def setup_importmap
198
+ # Add pins to config/importmap.rb
199
+ append_to_file 'config/importmap.rb' do
200
+ <<~RUBY
201
+ pin "jquery", to: "https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"
202
+ pin "your-plugin", to: "https://cdn.jsdelivr.net/npm/your-plugin/dist/plugin.min.js"
203
+ pin "your-gem", to: "your-gem.js"
204
+ RUBY
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
210
+ ```
211
+
212
+ ### 4. NPM Package Publishing
213
+
214
+ If your gem includes JavaScript, consider publishing an NPM package:
215
+
216
+ #### Package.json Configuration
217
+ ```json
218
+ {
219
+ "name": "@activeadmin/your-gem",
220
+ "version": "1.0.0",
221
+ "description": "Your gem description for ActiveAdmin",
222
+ "main": "src/index.js",
223
+ "module": "src/index.js",
224
+ "exports": {
225
+ ".": {
226
+ "import": "./src/index.js",
227
+ "require": "./src/index.js",
228
+ "default": "./src/index.js"
229
+ },
230
+ "./css": "./src/styles.scss"
231
+ },
232
+ "peerDependencies": {
233
+ "jquery": ">= 3.0, < 5",
234
+ "select2": "^4.0.13" // Add your dependencies here
235
+ },
236
+ "files": [
237
+ "src/**/*",
238
+ "app/assets/**/*",
239
+ "vendor/assets/**/*"
240
+ ],
241
+ "scripts": {
242
+ "prepare_sources": "mkdir -p src && cp -r app/assets/javascripts/active_admin/* src/ && cp -r app/assets/stylesheets/active_admin/* src/",
243
+ "prepublishOnly": "npm run prepare_sources"
244
+ },
245
+ "repository": {
246
+ "type": "git",
247
+ "url": "https://github.com/your-org/your-gem.git"
248
+ },
249
+ "keywords": ["activeadmin", "rails", "your-feature"],
250
+ "author": "Your Name",
251
+ "license": "MIT"
252
+ }
253
+ ```
254
+
255
+ #### Preparing JavaScript Assets for NPM
256
+
257
+ Create a script to copy your assets to the NPM package structure:
258
+
259
+ ```bash
260
+ #!/bin/bash
261
+ # scripts/prepare_npm_package.sh
262
+
263
+ # Create src directory for NPM
264
+ mkdir -p src
265
+
266
+ # Copy JavaScript files
267
+ cp -r app/assets/javascripts/active_admin/* src/
268
+
269
+ # Copy SCSS files if needed
270
+ cp -r app/assets/stylesheets/active_admin/* src/
271
+
272
+ # Ensure ESM module is included
273
+ cp app/assets/javascripts/active_admin/your_gem.esm.js src/index.js
274
+ ```
275
+
276
+ ### 5. CSS Selector Updates
277
+
278
+ ActiveAdmin 4 introduced several CSS class changes:
279
+
280
+ | ActiveAdmin 3.x | ActiveAdmin 4.x |
281
+ |----------------|-----------------|
282
+ | `.filter_form` | `.filters-form` |
283
+ | `.tabs` component | Removed - use divs with Tailwind |
284
+ | `.columns` component | Replaced with Tailwind grid |
285
+
286
+ Update your JavaScript and CSS accordingly:
287
+ ```javascript
288
+ // Old
289
+ $('.filter_form select').select2();
290
+
291
+ // New
292
+ $('.filters-form select').select2();
293
+ ```
294
+
295
+ ### 6. Testing Updates with Combustion
296
+
297
+ #### Complete Combustion Workflow for ActiveAdmin 4 Gems
298
+
299
+ **CRITICAL**: This workflow is specifically for testing ActiveAdmin extension gems with Combustion.
300
+
301
+ ##### Step 1: Add Dependencies to Gemfile
302
+
303
+ ```ruby
304
+ # Gemfile (for development/testing)
305
+ gem 'combustion'
306
+ gem 'importmap-rails', '~> 2.0' # Required for ActiveAdmin 4
307
+ ```
308
+
309
+ ##### Step 2: Run Combustion Generator (MANDATORY!)
310
+
311
+ ```bash
312
+ # NEVER manually create spec/internal structure!
313
+ bundle exec combust
314
+ ```
315
+
316
+ This creates:
317
+ - `spec/internal/` - minimal Rails app structure
318
+ - `config.ru` - in gem root for `bundle exec rackup`
319
+ - Basic Rails directories and config files
320
+
321
+ ##### Step 3: Set Up Test App Structure
322
+
323
+ After generator, create these files:
324
+
325
+ ```bash
326
+ # Create necessary directories
327
+ mkdir -p spec/internal/app/models
328
+ mkdir -p spec/internal/app/admin
329
+ mkdir -p spec/internal/app/assets/stylesheets
330
+ mkdir -p spec/internal/app/javascript
331
+ mkdir -p spec/internal/config/initializers
332
+ ```
333
+
334
+ ##### Basic Combustion Configuration
335
+ ```ruby
336
+ # spec/rails_helper.rb
337
+ ENV['RAILS_ENV'] ||= 'test'
338
+
339
+ require 'combustion'
340
+
341
+ # Initialize Combustion with only needed components
342
+ Combustion.path = 'spec/internal'
343
+ Combustion.initialize!(:active_record, :action_controller, :action_view) do
344
+ config.load_defaults Rails::VERSION::STRING.to_f if Rails::VERSION::MAJOR >= 7
345
+ end
346
+
347
+ require 'rspec/rails'
348
+ require 'capybara/rails'
349
+ ```
350
+
351
+ ##### Step 4: Configure config.ru (CRITICAL Loading Order!)
352
+
353
+ ```ruby
354
+ # config.ru - MUST control loading order for ActiveAdmin!
355
+ require "rubygems"
356
+ require "bundler"
357
+
358
+ # DON'T use Bundler.require - it loads gems too early!
359
+ Bundler.setup(:default, :development)
360
+
361
+ # Load Rails and combustion first
362
+ require 'combustion'
363
+
364
+ # Initialize Combustion with Rails components
365
+ Combustion.initialize! :active_record, :action_controller, :action_view do
366
+ config.load_defaults Rails::VERSION::STRING.to_f if Rails::VERSION::MAJOR >= 7
367
+ end
368
+
369
+ # NOW we can load ActiveAdmin and its dependencies after Rails is initialized
370
+ require 'importmap-rails'
371
+ require 'active_admin'
372
+ require 'your_activeadmin_gem'
373
+
374
+ run Combustion::Application
375
+ ```
376
+
377
+ ##### Step 5: Configure ActiveAdmin Assets
378
+
379
+ ```ruby
380
+ # spec/internal/app/assets/stylesheets/active_admin.css
381
+ @tailwind base;
382
+ @tailwind components;
383
+ @tailwind utilities;
384
+ ```
385
+
386
+ ```ruby
387
+ # spec/internal/config/importmap.rb
388
+ pin "@activeadmin/activeadmin", to: "active_admin.js", preload: true
389
+ ```
390
+
391
+ ```javascript
392
+ // spec/internal/app/javascript/active_admin.js
393
+ // Placeholder for ActiveAdmin JS
394
+ console.log("ActiveAdmin loaded");
395
+ ```
396
+
397
+ ##### Step 6: Set Up Test Models and Admin Resources
398
+
399
+ ```ruby
400
+ # spec/internal/db/schema.rb
401
+ ActiveRecord::Schema.define do
402
+ create_table :active_admin_comments, force: true do |t|
403
+ t.string :namespace
404
+ t.text :body
405
+ t.references :resource, polymorphic: true
406
+ t.references :author, polymorphic: true
407
+ t.timestamps
408
+ end
409
+
410
+ create_table :posts, force: true do |t|
411
+ t.string :title
412
+ t.text :body
413
+ t.text :description
414
+ t.timestamps
415
+ end
416
+ end
417
+ ```
418
+
419
+ ```ruby
420
+ # spec/internal/config/routes.rb
421
+ Rails.application.routes.draw do
422
+ ActiveAdmin.routes(self)
423
+ root to: 'admin/dashboard#index'
424
+ end
425
+ ```
426
+
427
+ ```ruby
428
+ # spec/internal/config/initializers/active_admin.rb
429
+ ActiveAdmin.setup do |config|
430
+ config.site_title = "Test App"
431
+ config.authentication_method = false
432
+ config.current_user_method = false
433
+ config.batch_actions = true
434
+ end
435
+ ```
436
+
437
+ ##### Step 7: Configure rails_helper.rb
438
+
439
+ ```ruby
440
+ # spec/rails_helper.rb
441
+ ENV['RAILS_ENV'] ||= 'test'
442
+
443
+ require 'combustion'
444
+
445
+ Combustion.path = 'spec/internal'
446
+ Combustion.initialize!(:active_record, :action_controller, :action_view) do
447
+ config.load_defaults Rails::VERSION::STRING.to_f if Rails::VERSION::MAJOR >= 7
448
+ end
449
+
450
+ require 'rspec/rails'
451
+ require 'capybara/rails'
452
+ ```
453
+
454
+ ##### Step 8: Running the Test App
455
+
456
+ ```bash
457
+ # Start the test app server
458
+ bundle exec rackup
459
+ # Visit http://localhost:9292/admin
460
+ ```
461
+
462
+ #### Critical Testing Pitfall: Model Registration Conflicts
463
+
464
+ **Problem**: Dynamic ActiveAdmin registrations in tests conflict with static admin files.
465
+
466
+ **Solution**: Choose ONE approach per model:
467
+
468
+ 1. **Static Registration** (for consistent configs):
469
+ ```ruby
470
+ # spec/internal/app/admin/users.rb
471
+ ActiveAdmin.register User do
472
+ permit_params :name, :email
473
+ # Fixed configuration
474
+ end
475
+ ```
476
+
477
+ 2. **Dynamic Registration** (for varying configs):
478
+ ```ruby
479
+ # spec/support/active_admin_helpers.rb
480
+ module ActiveAdminHelpers
481
+ module_function
482
+
483
+ def setup
484
+ ActiveAdmin.application = nil
485
+ yield # Dynamic registration block
486
+ reload_routes!
487
+ end
488
+
489
+ def reload_routes!
490
+ Rails.application.reload_routes!
491
+ end
492
+ end
493
+
494
+ # In test - NO static admin file for Post model
495
+ ActiveAdminHelpers.setup do
496
+ ActiveAdmin.register(Post) do
497
+ # Test-specific configuration
498
+ end
499
+ end
500
+ ```
501
+
502
+ **Important**: Never mix static and dynamic registration for the same model!
503
+
504
+ #### Capybara Configuration with Playwright
505
+ ```ruby
506
+ # spec/support/capybara.rb
507
+ require 'capybara-playwright-driver'
508
+
509
+ Capybara.register_driver :playwright do |app|
510
+ Capybara::Playwright::Driver.new(
511
+ app,
512
+ browser_type: :chromium,
513
+ headless: true,
514
+ viewport: { width: 1920, height: 1080 }
515
+ )
516
+ end
517
+
518
+ Capybara.default_driver = :rack_test
519
+ Capybara.javascript_driver = :playwright
520
+
521
+ # Important: Set server for JS tests
522
+ Capybara.server = :puma, { Silent: true }
523
+ ```
524
+
525
+ #### Waiting for JavaScript/AJAX in Tests
526
+ ```ruby
527
+ # spec/support/wait_helpers.rb
528
+ module WaitHelpers
529
+ def wait_for_ajax
530
+ Timeout.timeout(Capybara.default_max_wait_time) do
531
+ sleep 0.1
532
+ loop until finished_all_ajax_requests?
533
+ end
534
+ end
535
+
536
+ def finished_all_ajax_requests?
537
+ page.evaluate_script('jQuery.active').zero?
538
+ end
539
+
540
+ # For Select2 or similar plugins
541
+ def wait_for_select2
542
+ expect(page).to have_css('.select2-container', wait: 5)
543
+ end
544
+ end
545
+
546
+ RSpec.configure do |config|
547
+ config.include WaitHelpers, type: :feature
548
+ end
549
+ ```
550
+
551
+ ### 7. Production Build Issues
552
+
553
+ Common production issues and solutions:
554
+
555
+ #### Issue: JavaScript plugin not initialized
556
+ **Solution**: Explicitly initialize jQuery plugins
557
+ ```javascript
558
+ import select2 from 'select2';
559
+ import $ from 'jquery';
560
+
561
+ // This is critical for production builds
562
+ select2($);
563
+ ```
564
+
565
+ #### Issue: jQuery not globally available
566
+ **Solution**: Ensure global assignment
567
+ ```javascript
568
+ window.$ = window.jQuery = $;
569
+ ```
570
+
571
+ #### Issue: Assets not loading in production
572
+ **Solution**: Use CDN fallbacks or vendor assets
573
+ ```ruby
574
+ # In your gem's engine.rb
575
+ class Engine < ::Rails::Engine
576
+ initializer 'your_gem.assets' do |app|
577
+ if Rails.env.production?
578
+ # Add fallback assets
579
+ end
580
+ end
581
+ end
582
+ ```
583
+
584
+ ### 8. CI/CD Updates
585
+
586
+ Update your GitHub Actions workflow:
587
+
588
+ ```yaml
589
+ name: CI
590
+ on: [push, pull_request]
591
+
592
+ jobs:
593
+ test:
594
+ runs-on: ubuntu-latest
595
+ strategy:
596
+ matrix:
597
+ ruby: ['3.2', '3.3']
598
+ rails: ['7.0', '7.1', '7.2', '8.0']
599
+ activeadmin: ['4.0.0.beta16']
600
+
601
+ steps:
602
+ - uses: actions/checkout@v4
603
+
604
+ - name: Set up Ruby
605
+ uses: ruby/setup-ruby@v1
606
+ with:
607
+ ruby-version: ${{ matrix.ruby }}
608
+ bundler-cache: true
609
+
610
+ - name: Install Node.js
611
+ uses: actions/setup-node@v4
612
+ with:
613
+ node-version: '20'
614
+
615
+ - name: Install npm dependencies
616
+ run: npm install
617
+
618
+ - name: Install Playwright browsers
619
+ run: npx playwright install chromium
620
+
621
+ - name: Run tests
622
+ run: bundle exec rspec
623
+ ```
624
+
625
+ ### 9. Appraisals Configuration
626
+
627
+ Use Appraisal gem to test against multiple versions:
628
+
629
+ ```ruby
630
+ # Appraisals file
631
+ appraise 'rails-7.x-active-admin-4.x' do
632
+ gem 'rails', '~> 7.0'
633
+ gem 'activeadmin', '~> 4.0.0.beta16'
634
+ gem 'propshaft' # Required - Sprockets not supported
635
+ end
636
+
637
+ appraise 'rails-8.x-active-admin-4.x' do
638
+ gem 'rails', '~> 8.0'
639
+ gem 'activeadmin', '~> 4.0.0.beta16'
640
+ # Rails 8 includes Propshaft by default
641
+ end
642
+ ```
643
+
644
+ ### 10. Common Pitfalls and Solutions
645
+
646
+ #### Pitfall 1: Select2 or similar jQuery plugins not working
647
+ **Root Cause**: Plugin not attached to jQuery object in production
648
+ **Solution**: Explicitly call `plugin($)` after importing
649
+ ```javascript
650
+ import select2 from 'select2';
651
+ import $ from 'jquery';
652
+ select2($); // Critical - attaches plugin to jQuery
653
+ ```
654
+
655
+ #### Pitfall 2: CSS classes not found
656
+ **Root Cause**: ActiveAdmin 4 changed many CSS selectors
657
+ **Solution**: Search and replace old selectors with new ones
658
+ - `.filter_form` → `.filters-form`
659
+ - `.select2-container` needs explicit initialization in tests
660
+
661
+ #### Pitfall 3: Tests passing locally but failing in CI
662
+ **Root Cause**: Missing JavaScript dependencies or browser drivers
663
+ **Solution**:
664
+ ```yaml
665
+ # .github/workflows/ci.yml
666
+ - name: Install Playwright browsers
667
+ run: npx playwright install chromium
668
+ ```
669
+
670
+ #### Pitfall 4: Assets not compiling in production
671
+ **Root Cause**: Missing bundler configuration
672
+ **Solution**: Provide clear setup instructions for each bundler type in your README
673
+
674
+ #### Pitfall 5: Model registration conflicts in tests
675
+ **Root Cause**: Static admin files override dynamic test registrations
676
+ **Solution**:
677
+ - Delete static admin files for models that need dynamic config
678
+ - Keep static files only for models with consistent config
679
+ - Never mix both approaches for the same model
680
+
681
+ #### Pitfall 6: Input HTML options not passing through
682
+ **Root Cause**: Options can be lost during form DSL processing
683
+ **Solution**: Test with clean models not affected by other registrations
684
+ ```ruby
685
+ # Test with a model that has no static admin file
686
+ ActiveAdmin.register(TestModel) do
687
+ form do |f|
688
+ f.input :field, as: :searchable_select,
689
+ input_html: { class: 'custom-class' }
690
+ end
691
+ end
692
+ ```
693
+
694
+ #### Pitfall 7: Flaky JavaScript tests
695
+ **Root Cause**: Not waiting for AJAX/DOM updates
696
+ **Solution**: Add proper wait helpers
697
+ ```ruby
698
+ def wait_for_select2
699
+ expect(page).to have_css('.select2-container', wait: 5)
700
+ end
701
+ ```
702
+
703
+ #### Pitfall 8: Rails 8 compatibility issues
704
+ **Root Cause**: Formtastic 5.0 changes, Ransack updates
705
+ **Solution**:
706
+ - Test against multiple Rails versions using Appraisal
707
+ - Ensure Ransack methods are defined in models
708
+ ```ruby
709
+ def self.ransackable_attributes(_auth_object = nil)
710
+ %w[name title]
711
+ end
712
+ ```
713
+
714
+ #### Pitfall 9: Combustion and ActiveAdmin Loading Order Issues
715
+ **Root Cause**: ActiveAdmin requires Rails components at load time, conflicts with Combustion's initialization
716
+ **Critical Issue**: ActiveAdmin's `Bundler.require` loads before Rails is initialized by Combustion
717
+ **Symptoms**:
718
+ - `uninitialized constant Formtastic::ActionView`
719
+ - `uninitialized constant ActiveSupport::Autoload`
720
+ - `uninitialized constant #<Class:ActiveAdmin>::Importmap`
721
+ - Rackup fails with various Rails component loading errors
722
+ **Solution**:
723
+ - Use `Bundler.setup` instead of `Bundler.require` in config.ru
724
+ - Load ActiveAdmin AFTER Combustion initializes Rails
725
+ - Include importmap-rails for ActiveAdmin 4
726
+ - Don't require ActiveAdmin components in gem's main file
727
+ ```ruby
728
+ # Bad: In lib/your_gem.rb
729
+ require 'active_admin' # This loads too early!
730
+ require 'formtastic/inputs/your_input'
731
+
732
+ # Good: In engine.rb
733
+ initializer 'your_gem.setup', after: :load_config_initializers do
734
+ require 'active_admin' if defined?(Rails.application)
735
+ ActiveSupport.on_load(:active_admin) do
736
+ require 'formtastic/inputs/your_input'
737
+ end
738
+ end
739
+ ```
740
+
741
+ #### Pitfall 10: ActiveAdmin 4 Asset Pipeline Requirements (CRITICAL FOR COMBUSTION GEMS)
742
+ **Root Cause**: ActiveAdmin 4 uses Tailwind CSS v3 with custom plugin, requires compilation
743
+ **Critical Issue**: CSS must be compiled through Tailwind with ActiveAdmin plugin
744
+ **Symptoms**:
745
+ - Unstyled admin pages (no proper layout, just basic HTML)
746
+ - CSS file exists but has 0 bytes or wrong content
747
+ - `The asset "active_admin.css" is not present in the asset pipeline`
748
+
749
+ **Complete Solution for Combustion-based Gems**:
750
+
751
+ 1. **Add Dependencies** (Gemfile):
752
+ ```ruby
753
+ gem 'importmap-rails', '~> 2.0'
754
+ gem 'tailwindcss-rails' # For bundled tailwindcss executable
755
+ ```
756
+
757
+ 2. **Install NPM packages** (in spec/internal):
758
+ ```bash
759
+ cd spec/internal
760
+ npm init -y
761
+ npm install --save-dev tailwindcss@^3 # Use v3, not v4!
762
+ npm install --save-dev @activeadmin/activeadmin # For plugin (optional)
763
+ ```
764
+
765
+ 3. **Copy ActiveAdmin Plugin** (from Ruby gem):
766
+ ```bash
767
+ cp $(bundle show activeadmin)/plugin.js spec/internal/activeadmin-plugin.js
768
+ ```
769
+
770
+ 4. **Create Tailwind Config** (spec/internal/tailwind.config.mjs):
771
+ ```javascript
772
+ import activeAdminPlugin from './activeadmin-plugin.js';
773
+ import { execSync } from 'child_process';
774
+
775
+ const activeAdminPath = execSync('bundle show activeadmin', { encoding: 'utf-8' }).trim();
776
+
777
+ export default {
778
+ content: [
779
+ `${activeAdminPath}/app/views/**/*.{arb,erb,html,rb}`,
780
+ './app/admin/**/*.{arb,erb,html,rb}',
781
+ './app/views/**/*.{arb,erb,html}',
782
+ './app/javascript/**/*.js'
783
+ ],
784
+ darkMode: 'selector',
785
+ plugins: [activeAdminPlugin]
786
+ }
787
+ ```
788
+
789
+ 5. **Create Source CSS** (spec/internal/app/assets/stylesheets/active_admin_source.css):
790
+ ```css
791
+ @tailwind base;
792
+ @tailwind components;
793
+ @tailwind utilities;
794
+ ```
795
+
796
+ 6. **Build CSS**:
797
+ ```bash
798
+ cd spec/internal
799
+ npx tailwindcss -c tailwind.config.mjs \
800
+ -i app/assets/stylesheets/active_admin_source.css \
801
+ -o app/assets/stylesheets/active_admin_compiled.css \
802
+ --minify
803
+ ```
804
+
805
+ 7. **Configure Sprockets** (spec/internal/app/assets/stylesheets/active_admin.css):
806
+ ```css
807
+ /*
808
+ * This imports the compiled Tailwind CSS with ActiveAdmin styles
809
+ *= require ./active_admin_compiled
810
+ */
811
+ ```
812
+
813
+ 8. **Update Manifest** (spec/internal/app/assets/config/manifest.js):
814
+ ```javascript
815
+ //= link_tree ../builds
816
+ //= link active_admin.css
817
+ ```
818
+
819
+ 9. **Create Build Task** (lib/tasks/active_admin.rake):
820
+ ```ruby
821
+ namespace :active_admin do
822
+ desc 'Build Active Admin Tailwind stylesheets'
823
+ task :build do
824
+ require 'fileutils'
825
+
826
+ input = File.expand_path('../../spec/internal/app/assets/stylesheets/active_admin_source.css', __dir__)
827
+ output = File.expand_path('../../spec/internal/app/assets/stylesheets/active_admin_compiled.css', __dir__)
828
+ config = File.expand_path('../../spec/internal/tailwind.config.mjs', __dir__)
829
+
830
+ FileUtils.mkdir_p(File.dirname(output))
831
+
832
+ command = ['npx', 'tailwindcss', '-c', config, '-i', input, '-o', output, '--minify']
833
+ puts "Building Tailwind CSS: #{command.join(' ')}"
834
+ system(*command, exception: true)
835
+ puts "Tailwind CSS build complete: #{output}"
836
+ end
837
+ end
838
+ ```
839
+
840
+ #### Pitfall 11: Formtastic Custom Inputs Not Loading in Combustion
841
+ **Root Cause**: Loading order issues with ActiveAdmin, Formtastic, and custom inputs
842
+ **Symptoms**:
843
+ - `Formtastic::UnknownInputError: Unable to find input class YourInput`
844
+ - Input works in production but not in Combustion test environment
845
+
846
+ **Solutions**:
847
+
848
+ 1. **Immediate Fix in config.ru** (for Combustion):
849
+ ```ruby
850
+ # config.ru
851
+ require 'combustion'
852
+ Combustion.initialize! :active_record, :action_controller, :action_view
853
+
854
+ require 'importmap-rails'
855
+ require 'active_admin'
856
+ require 'your_gem'
857
+
858
+ # Critical: Explicitly require custom inputs after everything else
859
+ require 'formtastic/inputs/your_input'
860
+
861
+ run Combustion::Application
862
+ ```
863
+
864
+ 2. **Engine Initialization Fix**:
865
+ ```ruby
866
+ # lib/your_gem/engine.rb
867
+ initializer 'your_gem.setup', after: :load_config_initializers do
868
+ require 'active_admin' if defined?(Rails.application)
869
+
870
+ # Load immediately AND hook into ActiveAdmin
871
+ require 'formtastic/inputs/your_input'
872
+
873
+ ActiveSupport.on_load(:active_admin) do
874
+ require 'formtastic/inputs/your_input'
875
+ end
876
+ end
877
+ ```
878
+
879
+ 3. **Workaround Using Standard Inputs**:
880
+ ```ruby
881
+ # If custom input isn't loading, use standard input with same attributes
882
+ f.input :field, as: :text, input_html: {
883
+ class: 'your-input-class',
884
+ 'data-your-attribute': true
885
+ }
886
+ ```
887
+
888
+ **Note**: After making these changes, restart the server for them to take effect.
889
+
890
+ ## Migration Checklist
891
+
892
+ - [ ] Update Ruby version requirement to >= 3.2
893
+ - [ ] Update Rails version requirement to >= 7.0
894
+ - [ ] Update ActiveAdmin dependency to support 4.x
895
+ - [ ] Create ESM JavaScript modules
896
+ - [ ] Add installation generator for different bundlers
897
+ - [ ] Publish NPM package (if applicable)
898
+ - [ ] Update CSS selectors (`.filter_form` → `.filters-form`)
899
+ - [ ] Fix jQuery plugin initialization for production
900
+ - [ ] Update test suite for new asset handling
901
+ - [ ] Configure CI for multiple version testing
902
+ - [ ] Update documentation with setup instructions
903
+ - [ ] Test with esbuild, webpack, and importmap
904
+ - [ ] Add CDN fallbacks for JavaScript dependencies
905
+ - [ ] Handle both Sprockets and Propshaft
906
+
907
+ ## Example Implementation
908
+
909
+ See the full implementation in the `activeadmin-searchable_select` gem:
910
+ - [Installation Generator](../lib/generators/active_admin/searchable_select/install/install_generator.rb)
911
+ - [ESM Module](../app/assets/javascripts/active_admin/searchable_select.esm.js)
912
+ - [Package.json](../package.json)
913
+ - [CI Configuration](../.github/workflows/ci.yml)
914
+
915
+ ## Resources
916
+
917
+ - [ActiveAdmin 4.0 Breaking Changes](./activeadmin-4-changes.md)
918
+ - [ActiveAdmin 4.0 Release Notes](https://github.com/activeadmin/activeadmin/releases)
919
+ - [Rails 7+ Asset Pipeline Guide](https://guides.rubyonrails.org/asset_pipeline.html)
920
+ - [esbuild Rails Documentation](https://github.com/rails/jsbundling-rails)
921
+ - [Importmap Rails Documentation](https://github.com/rails/importmap-rails)
922
+
923
+ ## Conclusion
924
+
925
+ Migrating a gem to support ActiveAdmin 4 requires careful attention to:
926
+ 1. Modern JavaScript module systems
927
+ 2. Flexible asset pipeline support
928
+ 3. Updated CSS selectors and components
929
+ 4. Proper jQuery plugin initialization
930
+ 5. Comprehensive testing across different setups
931
+
932
+ The key to success is providing multiple paths for users with different asset pipeline configurations while maintaining backward compatibility where possible.