Skip to content

fix: stop mutating the shared PagePresenter when wrapping the index block - #3

Merged
Fivell merged 1 commit into
masterfrom
fix/presenter-block-accumulation
Oct 20, 2026
Merged

Fivell merged 1 commit into
masterfrom
fix/presenter-block-accumulation

Conversation

@Fivell

@Fivell Fivell commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Problem

IndexAsTableExtension#build wrote its footer wrapper back into the PagePresenter it was handed:

page_presenter.instance_variable_set(:@block, wrapped)

ActiveAdmin builds one PagePresenter per index do ... end when it loads the resource, memoized in Resource::PagePresenters#page_presenters, and get_page_presenter returns that same object on every render. So each render wrapped the previous wrapper: retained closures and block call depth grew by one per request, until rendering an index raised SystemStackError (~6k renders in a long-lived process). The footer_proc && page_presenter.block guard did not stop it, because an already-wrapped block is still truthy.

Fix

Build the wrapper as before, but render against a fresh PagePresenter that shares the original options hash, leaving the stored presenter untouched. ActiveAdmin reads the presenter only through [] and #block on both 3.5 and 4.0, so rendering is unchanged.

Prepending IndexAsTable#table_for to inject the option instead was rejected: ActiveAdmin 4 has no such method — its build calls insert_tag directly — and this gem supports both majors.

Regression cover

spec/features/index_presenter_mutation_spec.rb renders the same index repeatedly and asserts PagePresenter#block is never replaced.

Before the fix it fails with the user block swapped for the lambda this gem installs:

expected #<Proc .../subscriptions.rb:18>
     got #<Proc .../index_as_table_extension.rb:13 (lambda)>

After the fix: 25 examples, 0 failures on both the default bundle (activeadmin 3.5.1) and gemfiles/activeadmin_4.0.gemfile (activeadmin 4.0.0.beta23).

No version bump here — release separately, following the existing fix/* then release/* flow.

…lock

IndexAsTableExtension#build wrote its footer wrapper back into the
PagePresenter it was handed. ActiveAdmin builds one PagePresenter per
`index do ... end` when it loads the resource, and get_page_presenter
returns that same object on every render, so each render wrapped the
previous wrapper: retained closures and block call depth grew by one per
request until rendering an index raised SystemStackError (~6k renders in
a long-lived process). The `footer_proc && page_presenter.block` guard
did not stop it, because an already-wrapped block is still truthy.

Build the wrapper as before, but render against a fresh PagePresenter
sharing the original options hash. ActiveAdmin reads the presenter only
through `[]` and `#block` on both 3.5 and 4.0, so rendering is unchanged.

Prepending IndexAsTable#table_for to inject the option instead was
rejected: ActiveAdmin 4 has no such method — its build calls insert_tag
directly — and this gem supports both majors.

The new spec renders the same index repeatedly and asserts
PagePresenter#block is never replaced. Verified failing before this
change and passing after, against activeadmin 3.5.1 and 4.0.0.beta23.
@Fivell
Fivell requested a balanced review from Copilot October 19, 2026 18:02
@Fivell Fivell changed the title fix: stop mutating the shared PagePresenter when wrapping the index b… fix: stop mutating the shared PagePresenter when wrapping the index block Oct 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused fix preserves rendering behavior, adds targeted regression coverage, and has no identified blocking issues.

No open issues 🎉

What changed in this PR

Prevents repeated index renders from mutating ActiveAdmin’s shared page presenter and accumulating wrappers that can cause stack overflow.

Changes:

  • Renders with a fresh presenter while preserving options and footer behavior.
  • Adds regression coverage for presenter block identity and repeated footer rendering.
File Description
spec/​features/​index_presenter_mutation_spec.rb Tests presenter stability and footer rendering across repeated requests.
lib/​activeadmin_table_footer/​index_as_table_extension.rb Wraps the index block in a fresh presenter instead of mutating shared state.

Review effort: Balanced

@Fivell
Fivell merged commit 908ded4 into master Oct 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants