Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions lib/activeadmin_table_footer/index_as_table_extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ module ActiveadminTableFooter
module IndexAsTableExtension
def build(page_presenter, collection)
footer_proc = page_presenter[:footer_data]
return super unless footer_proc && page_presenter.block

if footer_proc && page_presenter.block
original_block = page_presenter.block
wrapped = lambda do |table|
table.instance_variable_set(:@footer_data_proc, footer_proc)
instance_exec(table, &original_block)
end
page_presenter.instance_variable_set(:@block, wrapped)
original_block = page_presenter.block
wrapped = lambda do |table|
table.instance_variable_set(:@footer_data_proc, footer_proc)
instance_exec(table, &original_block)
end

super
# Render against a copy. ActiveAdmin builds one PagePresenter per
# `index do ... end` when it loads the resource and hands that same
# object to every render, so writing the wrapper back into the presenter
# would wrap the previous wrapper once per request — growing retained
# closures and block call depth until a render raises SystemStackError.
super(ActiveAdmin::PagePresenter.new(page_presenter.options, &wrapped), collection)
end
end
end
47 changes: 47 additions & 0 deletions spec/features/index_presenter_mutation_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require "rails_helper"

# Regression guard for a shared-state bug in IndexAsTableExtension.
#
# ActiveAdmin builds one PagePresenter per `index do ... end` when it loads the
# resource, and `get_page_presenter` hands that same object to every render
# (`ActiveAdmin::Views::Pages::Index#main_content`). Writing the footer wrapper
# back into that presenter therefore wrapped the previous wrapper on each
# render: retained closures and block call depth grew by one per request, until
# rendering an index raised SystemStackError in a long-lived process (~6k
# renders). Keep the presenter untouched — render against a copy.
RSpec.describe "Index page presenter across repeated renders", type: :feature do
let!(:plan) { Plan.create!(name: "Basic", monthly_price: 10) }
let!(:customer) { Customer.create!(name: "Acme") }
let!(:subscription) { Subscription.create!(customer: customer, plan: plan, seats: 4) }

def index_page_presenter
ActiveAdmin.application.load!
ActiveAdmin.application.namespaces[:admin]
.resources
.find { |resource| resource.resource_name.to_s == "Subscription" }
.get_page_presenter(:index)
end

it "leaves PagePresenter#block the very same object after repeated renders" do
presenter = index_page_presenter
original_block = presenter.block

5.times { visit "/admin/subscriptions" }

expect(presenter.block).to equal(original_block)
end

it "still renders the footer on every render" do
3.times do
visit "/admin/subscriptions"

within_table_for("subscriptions") do
within_table_footer do
expect(page).to have_table_cell(column: "Seats", exact_text: "4")
end
end
end
end
end
Loading