1

I'm having an issue with ActiveAdmin 3.3.0 in my Rails 8.0.2 application where DELETE actions are not working. When an admin user clicks the "Delete" link, the application performs a GET request to view the resource instead of a DELETE request to remove it. This happens across all ActiveAdmin resources.

  • Create (POST), Read (GET), and Update (PATCH) actions all work properly
  • When clicking the Delete link, the browser sends a GET request instead of DELETE
  • The issue appears in all admin resources, not just one specific model

Environment

  • Ruby on Rails 8.0.2
  • ActiveAdmin 3.3.0

I'm a total noob so I have some gems but tbh I'm not sure which are even being used anymore pertaining to this issue:

  • Propshaft (for asset pipeline)
  • ImportMap (for JavaScript module handling)
  • Turbo Rails (for page transitions)

What I've Tried

In my ActiveAdmin resource, I've attempted to explicitly set turbo_method: :delete for the Delete action:

# app/admin/service_templates.rb
ActiveAdmin.register ServiceTemplate do
  # ...
  index do
    # ...
    actions defaults: false do |template|
      item "View", resource_path(template)
      item "Edit", edit_resource_path(template)
      item "Delete", resource_path(template), turbo_method: :delete, data: { confirm: "Are you sure?" }
    end
  end
  # ...
end

...

 def destroy
   resource = ServiceTemplate.find(params[:id])
   resource.destroy
   flash[:notice] = "Service template was successfully destroyed."
   redirect_to collection_path
 end

However, this doesn't work - clicking the Delete link still sends a GET request.

Regular Devise sign-out links with data: { turbo_method: :delete } work correctly in my application although that might be overdid by Active_admin as well:

<%= link_to "Sign out", destroy_user_session_path, data: { turbo_method: :delete } %>
 # Default:
  config.logout_link_path = :destroy_admin_user_session_path

The list goes on...

  • Added Rails UJS to the application
  • Added Rails UJS via Yarn (yarn add @rails/ujs)
  • Updated active_admin.js to include Rails UJS and initialize it
  • Modified JavaScript imports and configuration
  • Tried adding Rails UJS to importmap
  • Attempted to add JavaScript import to ActiveAdmin head via config.head
  • Changed how DELETE links are implemented
  • Tried using form_tag with method: instead of links
  • Added unique classes and IDs to forms to avoid batch action conflicts
  • Used explicit absolute paths instead of resource path helpers
  • Added data attributes to prevent batch action handling
  • Attempted custom controller actions
  • Tried creating a custom GET-based delete action as an alternative to DELETE requests
  • Attempted to implement a custom controller hook to handle deletion
  • Explored global modifications
    • Considered monkey-patching ActiveAdmin classes to change how delete actions work
    • Looked at modifying the ResourceController to handle alternative deletion approaches

The core issue remains that in Rails 8 with ActiveAdmin 3.3.0, the JavaScript handling for DELETE operations isn't working correctly, causing delete buttons to either not function or be incorrectly routed to batch actions.

Relevant Configuration Files

[config/initializers/active_admin.rb]

# frozen_string_literal: true
ActiveAdmin.setup do |config|
  # == Site Title
  #
  # Set the title that is displayed on the main layout
  # for each of the active admin pages.
  #
  config.site_title = "BizBlasts Admin"

  # Set the link url for the title. For example, to take
  # users to your main site. Defaults to no link.
  #
  config.site_title_link = "/"

  # Set an optional image to be displayed for the header
  # instead of a string (overrides :site_title)
  #
  # Note: Aim for an image that's 21px high so it fits in the header.
  #
  # config.site_title_image = "logo.png"

  # == Load Paths
  #
  # By default Active Admin files go inside app/admin/.
  # You can change this directory.
  #
  # eg:
  #   config.load_paths = [File.join(Rails.root, 'app', 'ui')]
  #
  # Or, you can also load more directories.
  # Useful when setting namespaces with users that are not your main AdminUser entity.
  #
  # eg:
  #   config.load_paths = [
  #     File.join(Rails.root, 'app', 'admin'),
  #     File.join(Rails.root, 'app', 'cashier')
  #   ]

  # == Default Namespace
  #
  # Set the default namespace each administration resource
  # will be added to.
  #
  # eg:
  #   config.default_namespace = :hello_world
  #
  # This will create resources in the HelloWorld module and
  # will namespace routes to /hello_world/*
  #
  # To set no namespace by default, use:
  #   config.default_namespace = false
  #
  # Default:
  # config.default_namespace = :admin
  #
  # You can customize the settings for each namespace by using
  # a namespace block. For example, to change the site title
  # for a specific namespace:
  #
  #   config.namespace :admin do |admin|
  #     admin.site_title = "Custom Admin Title"
  #   end
  #
  # This will ONLY change the title for the admin section. Other
  # namespaces will continue to use the main "site_title" configuration.

  # == User Authentication
  #
  # Active Admin will automatically call an authentication
  # method in a before filter of all controller actions to
  # ensure that there is a currently logged in admin user.
  #
  # This setting changes the method which Active Admin calls
  # within the application controller.
  config.authentication_method = :authenticate_admin_user!

  # == User Authorization
  #
  # Active Admin will automatically call an authorization
  # method in a before filter of all controller actions to
  # ensure that there is a user with proper rights. You can use
  # CanCanAdapter or make your own. Please refer to documentation.
  config.authorization_adapter = ActiveAdmin::PunditAdapter
  config.pundit_policy_namespace = :admin

  # In case you prefer Pundit over other solutions you can here pass
  # the name of default policy class. This policy will be used in every
  # case when Pundit is unable to find suitable policy.
  config.pundit_default_policy = "Admin::DefaultPolicy"

  # You can customize your CanCan Ability class name here.
  # config.cancan_ability_class = "Ability"

  # You can specify a method to be called on unauthorized access.
  # This is necessary in order to prevent a redirect loop which happens
  # because, by default, user gets redirected to Dashboard. If user
  # doesn't have access to Dashboard, he'll end up in a redirect loop.
  # Method provided here should be defined in application_controller.rb.
  # config.on_unauthorized_access = :access_denied

  # == Current User
  #
  # Active Admin will associate actions with the current
  # user performing them.
  #
  # This setting changes the method which Active Admin calls
  # (within the application controller) to return the currently logged in user.
  config.current_user_method = :current_admin_user

  # == Logging Out
  #
  # Active Admin displays a logout link on each screen. These
  # settings configure the location and method used for the link.
  #
  # This setting changes the path where the link points to. If it's
  # a string, the strings is used as the path. If it's a Symbol, we
  # will call the method to return the path.
  #
  # Default:
  config.logout_link_path = :destroy_admin_user_session_path

  # This setting changes the http method used when rendering the
  # link. For example :get, :delete, :put, etc..
  #
  # Default:
  # config.logout_link_method = :get

  # == Root
  #
  # Set the action to call for the root path. You can set different
  # roots for each namespace.
  #
  # Default:
  # config.root_to = 'dashboard#index'

  # == Admin Comments
  #
  # This allows your users to comment on any resource registered with Active Admin.
  #
  # You can completely disable comments:
  config.comments = false
  #
  # You can change the name under which comments are registered:
  # config.comments_registration_name = 'AdminComment'
  #
  # You can change the order for the comments and you can change the column
  # to be used for ordering:
  # config.comments_order = 'created_at ASC'
  #
  # You can disable the menu item for the comments index page:
  # config.comments_menu = false
  #
  # You can customize the comment menu:
  # config.comments_menu = { parent: 'Admin', priority: 1 }

  # == Batch Actions
  #
  # Enable and disable Batch Actions
  #
  config.batch_actions = true

  # == Controller Filters
  #
  # You can add before, after and around filters to all of your
  # Active Admin resources and pages from here.
  #
  # Tenant handling for multi-tenancy - unscope tenancy for admin users
  config.before_action do
    ActsAsTenant.current_tenant = nil if request.path.include?('/admin')
  end

  # == Attribute Filters
  #
  # You can exclude possibly sensitive model attributes from being displayed,
  # added to forms, or exported by default by ActiveAdmin
  #
  config.filter_attributes = [:encrypted_password, :password, :password_confirmation]

  # == Localize Date/Time Format
  #
  # Set the localize format to display dates and times.
  # To understand how to localize your app with I18n, read more at
  # https://guides.rubyonrails.org/i18n.html
  #
  # You can run `bin/rails runner 'puts I18n.t("date.formats")'` to see the
  # available formats in your application.
  #
  config.localize_format = :long

  # == Setting a Favicon
  #
  # config.favicon = 'favicon.ico'

  # == Meta Tags
  #
  # Add additional meta tags to the head element of active admin pages.
  #
  # Add tags to all pages logged in users see:
  #   config.meta_tags = { author: 'My Business' }

  # By default, sign up/sign in/recover password pages are excluded
  # from showing up in search engine results by adding a robots meta
  # tag. You can reset the hash of meta tags included in logged out
  # pages:
  #   config.meta_tags_for_logged_out_pages = {}

  # == Removing Breadcrumbs
  #
  # Breadcrumbs are enabled by default. You can customize them for individual
  # resources or you can disable them globally from here.
  #
  # config.breadcrumb = false

  # == Create Another Checkbox
  #
  # Create another checkbox is disabled by default. You can customize it for individual
  # resources or you can enable them globally from here.
  #
  # config.create_another = true

  # == Register Stylesheets & Javascripts
  #
  # We recommend using the built in Active Admin layout and loading
  # up your own stylesheets / javascripts to customize the look
  # and feel.
  #
  # To load a stylesheet:
  #   config.register_stylesheet 'my_stylesheet.css'
  #
  # You can provide an options hash for more control, which is passed along to stylesheet_link_tag():
  #   config.register_stylesheet 'my_print_stylesheet.css', media: :print
  #
  # To load a javascript file:
  #   config.register_javascript 'my_javascript.js'

  # == CSV options
  #
  # Set the CSV builder separator
  # config.csv_options = { col_sep: ';' }
  #
  # Force the use of quotes
  # config.csv_options = { force_quotes: true }

  # == Menu System
  #
  # You can add a navigation menu to be used in your application, or configure a provided menu
  #
  # To change the default utility navigation to show a link to your website & a logout btn
  #
  #   config.namespace :admin do |admin|
  #     admin.build_menu :utility_navigation do |menu|
  #       menu.add label: "My Great Website", url: "http://www.mygreatwebsite.com", html_options: { target: :blank }
  #       admin.add_logout_button_to_menu menu
  #     end
  #   end
  #
  # If you wanted to add a static menu item to the default menu provided:
  #
  config.namespace :admin do |admin|
    admin.build_menu :default do |menu|
      menu.add label: "Dashboard", url: "/admin/dashboard", priority: 1
      menu.add label: "Client Websites", url: "/admin/client_websites", priority: 2
    end
  end

  # == Download Links
  #
  # You can disable download links on resource listing pages,
  # or customize the formats shown per namespace/globally
  #
  # To disable/customize for the :admin namespace:
  #
  #   config.namespace :admin do |admin|
  #
  #     # Disable the links entirely
  #     admin.download_links = false
  #
  #     # Only show XML & PDF options
  #     admin.download_links = [:xml, :pdf]
  #
  #     # Enable/disable the links based on block
  #     #
  #     # admin.download_links = proc { |admin| user.admin? }
  #
  #   end

  # == Pagination
  #
  # Pagination is enabled by default for all resources.
  # You can control the default per page count for all resources here.
  #
  # config.default_per_page = 30
  #
  # You can control the max per page count too.
  #
  # config.max_per_page = 10_000

  # == Filters
  #
  # By default the index screen includes a "Filters" sidebar on the right
  # hand side with a filter for each attribute of the registered model.
  # You can enable or disable them for all resources here.
  #
  # config.filters = true
  #
  # By default the filters include associations in a select, which means
  # that every record will be loaded for each association (up
  # to the value of config.maximum_association_filter_arity).
  # You can enabled or disable the inclusion
  # of those filters by default here.
  #
  # config.include_default_association_filters = true

  # config.maximum_association_filter_arity = 256 # default value of :unlimited will change to 256 in a future version
  # config.filter_columns_for_large_association = [
  #    :display_name,
  #    :full_name,
  #    :name,
  #    :username,
  #    :login,
  #    :title,
  #    :email,
  #  ]
  # config.filter_method_for_large_association = '_starts_with'

  # == Head
  #
  # You can add your own content to the site head like analytics. Make sure
  # you only pass content you trust.
  #
  # config.head = ''.html_safe

  # == Footer
  #
  # By default, the footer shows the current Active Admin version. You can
  # override the content of the footer here.
  #
  config.footer = 'BizBlasts Admin'

  # == Sorting
  #
  # By default ActiveAdmin::OrderClause is used for sorting logic
  # You can inherit it with own class and inject it for all resources
  #
  # config.order_clause = MyOrderClause

  # == Webpacker
  #
  # By default, Active Admin uses Propshaft for bootstrapping
  # and other assets. If you would like to use Webpacker instead
  # (i.e. you're using React, Vue, or other JS framework for
  # your JS), you can set this flag to true.
  #
  # config.use_webpacker = true
end

[app/views/layouts/application.html.erb]

<!DOCTYPE html>
<html>
  <head>
    <title><%= content_for(:title) || "BizBlasts" %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="mobile-web-app-capable" content="yes">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= yield :head %>

    <link rel="icon" href="/icon.png" type="image/png">
    <link rel="icon" href="/icon.svg" type="image/svg+xml">
    <link rel="apple-touch-icon" href="/icon.png">

    <%# Use both Rails asset helper and direct link as fallback %>
    <% begin %>
      <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <% rescue => e %>
      <% Rails.logger.error "Failed to load stylesheet with helper: #{e.message}" %>
      <link rel="stylesheet" href="/assets/application.css" data-turbo-track="reload">
    <% end %>
    
    <% begin %>
      <%= javascript_importmap_tags %>
    <% rescue => e %>
      <% Rails.logger.error "Failed to load JavaScript with helper: #{e.message}" %>
      <script src="/assets/application.js" data-turbo-track="reload"></script>
    <% end %>
  </head>

  <body>
    <%# Render flash messages %>
    <% flash.each do |type, msg| %>
      <div class="flash-message flash-<%= type %>">
        <%= msg %>
      </div>
    <% end %>

    <%= yield %>

    <% if user_signed_in? %>
      <%# Sign out link for regular Users (manager, staff, client) %>
      <%= link_to "Sign out", destroy_user_session_path, data: { "turbo-method": :delete } %>
    <% end %>
  </body>
</html>

[app/assets/javascripts/active_admin.js]

//= require active_admin/base

[config/initializers/propshaft_config.rb]

# frozen_string_literal: true

# This initializer configures Propshaft to properly handle ActiveAdmin assets
# and ensures assets are served correctly in production

# Make sure paths are set up before the app loads
if defined?(Propshaft)
  # Register ActiveAdmin asset paths with Propshaft immediately, rather than waiting for after_initialize
  Rails.application.config.assets.paths ||= []
  puts "Setting up Propshaft asset paths for ActiveAdmin..."
  
  # Add app/assets/builds directory to load path
  Rails.application.config.assets.paths << Rails.root.join('app', 'assets', 'builds').to_s
  
  # Add public/assets directory to load path
  Rails.application.config.assets.paths << Rails.root.join('public', 'assets').to_s
  
  # Add the ActiveAdmin gem's asset paths
  if defined?(ActiveAdmin) || Gem.loaded_specs.key?('activeadmin')
    begin
      activeadmin_path = Gem.loaded_specs['activeadmin'].full_gem_path
      Rails.application.config.assets.paths << File.join(activeadmin_path, 'app', 'assets', 'stylesheets')
      Rails.application.config.assets.paths << File.join(activeadmin_path, 'app', 'assets', 'javascripts')
      puts "Added ActiveAdmin asset paths: #{activeadmin_path}"
    rescue => e
      puts "Failed to add ActiveAdmin asset paths: #{e.message}"
    end
  end
  
  # Disable fingerprinting in production - we'll handle asset versioning manually
  if Rails.env.production?
    begin
      if Rails.application.config.respond_to?(:assets)
        Rails.application.config.assets.digest = false
        puts "Disabled asset fingerprinting in production to avoid issues with ActiveAdmin"
      end
    rescue => e
      puts "Failed to disable asset fingerprinting: #{e.message}"
    end
  end
  
  # Register specific paths for ActiveAdmin CSS files
  # This ensures they're findable regardless of precompilation
  Rails.application.config.after_initialize do
    # Print asset paths to logs for debugging
    Rails.logger.info "Propshaft asset paths: #{Rails.application.config.assets.paths.inspect}"
    
    # Special handling for ActiveAdmin assets in production
    if Rails.env.production?
      # Make a direct mapping for active_admin.css to active_admin.css in public/assets
      # to bypass Propshaft lookups which might be failing
      if Rails.application.respond_to?(:assets) && Rails.application.assets.respond_to?(:define_singleton_method)
        begin
          original_compute_path = Rails.application.assets.method(:compute_path)
          Rails.application.assets.define_singleton_method(:compute_path) do |path, **options|
            # Special case for active_admin.css
            if path == "active_admin.css"
              Rails.root.join('public', 'assets', 'active_admin.css').to_s
            elsif path == "application.css"
              Rails.root.join('public', 'assets', 'application.css').to_s
            elsif path == "application.js"
              Rails.root.join('public', 'assets', 'application.js').to_s
            else
              original_compute_path.call(path, **options)
            end
          end
          Rails.logger.info "Added special handling for asset path computation"
        rescue => e
          Rails.logger.error "Failed to add special handling for assets: #{e.message}"
        end
      end
    end
  end
end 

[config/importmap.rb]

# Pin npm packages by running ./bin/importmap

pin "application"

[Gemfile]

# frozen_string_literal: true

source "https://rubygems.org"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 8.0.2"
# The modern asset pipeline for Rails [https://github.com/rails/propshaft]
gem "propshaft"
# Use postgresql as the database for Active Record
gem "pg", "~> 1.1"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[windows jruby]

# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable
gem "solid_cache"
gem "solid_queue"


# Authentication
gem "devise"
gem "devise-jwt"

# Authorization
gem "pundit"

# Admin interface
gem "activeadmin"
# gem "sassc-rails" # REMOVED - Conflicts with Propshaft/Importmap

# Multitenancy
gem "acts_as_tenant"

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false

# Deploy this application anywhere as a Docker container [https://kamal-deploy.org]
# gem "kamal", require: false # REMOVED

# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/]
gem "thruster", require: false

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

# Use node based css bundling
gem "cssbundling-rails"

group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
  gem "dotenv-rails"
  gem "factory_bot_rails"
  gem "faker"
  gem "rspec-rails"
  gem "capybara"
  gem "selenium-webdriver"
  gem "cuprite"
  gem "webdrivers"

  # Static analysis for security vulnerabilities [https://brakemanscanner.org/]
  gem "brakeman", require: false

  # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
  gem "rubocop-rails-omakase", require: false

  # Testing framework
  gem "shoulda-matchers", "~> 6.0"
  gem "database_cleaner" # Main database_cleaner gem
  gem "database_cleaner-active_record" # For cleaning the database between tests
  
  # For test performance metrics
  gem "simplecov", require: false

  # For checking rendered templates in controller/request specs
  gem "rails-controller-testing"
end

group :development do
  # Use console on exceptions pages [https://github.com/rails/web-console]
  gem "web-console"
  # Annotate models
  gem "annotate"
end
# For time-of-day calculations in availability logic
gem 'tod'
gem 'parallel_tests', group: [:development, :test]

Tests

I've created system tests that verify this issue:

# spec/system/admin/templates_management_spec.rb
context "Deleting a template" do
  let!(:template) { create(:service_template, name: "Template 1") }

  it "allows admin to delete a template" do
    visit admin_service_templates_path
    
    # Find the delete link in the actions column and click it
    within("tr", text: template.name) do
      # Use standard Capybara block for confirmation
      accept_confirm do
        click_link "Delete"
      end
    end

    expect(page).to have_content("Service template was successfully destroyed.")
    expect(page).not_to have_selector("tr", text: template.name)
  end
end

This test fails because the DELETE request never happens.

How can I fix ActiveAdmin's DELETE functionality with Rails 8.0.2, and my mix of other components? Is there a compatibility issue between ActiveAdmin 3.3.0 and Rails 8.0.2 with Turbo? Do I need to configure something specific for UJS/Turbo to work with ActiveAdmin's link generation? I've tried to use buttons and they do not solve the problem either because I have batch actions. users/544825/max has mentioned this a common problem. I feel geniuely stuck after spending like a week on this one. And I don't think I can restart or create a small version of this due to my project already being half a million lines of code. Please let me know if you have any questions!

8
  • The only way I could get any delete through ActiveAdmin was with in line javascript. Creating a html POST form with method input as delete. Commented Apr 18 at 5:37
  • data: {turbo_method: :delete, turbo_confirm: "Are you sure?"}. Commented Apr 18 at 15:01
  • Alex, thanks for the tip. I added "item "Delete", resource_path(template), data: {turbo_method: :delete, turbo_confirm: "Are you sure?"}" add this did not solve the issue Commented Apr 18 at 15:27
  • doesn't look like you have Turbo. do you need to configure Turbo for activeadmin separately? Commented Apr 18 at 15:38
  • I just ran: console.log("Turbo defined:", typeof Turbo !== "undefined"); console.log("Turbo enabled:", typeof Turbo !== "undefined" && Turbo.session.enabled); console.log("Has visit manager:", typeof Turbo !== "undefined" && !!Turbo.navigator); console.log("Active observers:", typeof Turbo !== "undefined" && !!document.querySelector('body[data-turbo]')); Turbo defined: false Turbo enabled: false Has visit manager: false Active observers: false So looks like turbo is not actively being used despite the gem being in place. I am not sure about the special configuration. Commented Apr 18 at 15:55

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.