Stop entity autodetection from picking up a top-level ::Entity constant#2825
Merged
Conversation
ericproulx
force-pushed
the
fix-top-level-entity-constant
branch
from
July 26, 2026 19:33
7871ad4 to
20fa148
Compare
Danger ReportNo issues found. |
dblock
approved these changes
Jul 27, 2026
entity_class_for_obj used klass.const_defined?(:Entity), which walks the ancestor chain up to Object — where top-level constants live. Any app defining a class named Entity had it silently used as the presenter for every presented object without its own nested Entity (also reachable for empty collections, whose lookup class resolves to NilClass). Walk the ancestors explicitly with const_defined?(:Entity, false), skipping Object, so only a genuine Klass::Entity (on the class or a real ancestor) is auto-detected. Same family as the ::Options fix in #2773. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ericproulx
force-pushed
the
fix-top-level-entity-constant
branch
from
July 27, 2026 05:06
20fa148 to
a3f6b79
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Grape::DSL::Entity#entity_class_for_objauto-detects a presenter with:const_defined?with the defaultinherit: truewalks the full ancestor chain — includingObject, which is where Ruby stores top-level constants. So if an application defines any top-level class namedEntity(a natural name in apps using grape-entity or similar), it is silently treated asKlass::Entityfor everypresent-ed object that doesn't have its own nestedEntity:The same path is reachable for empty collections:
object_class([])resolves toNilClass([].first.class), whose ancestor chain also reachesObject.This is the same failure family as #2773, where a top-level
::Optionsconstant broke middleware building via an over-eager constant lookup.Fix
Walk the ancestors explicitly and ask each one for its own
Entityconstant (const_defined?(:Entity, false)), skippingObject:Documented behavior is preserved:
Klass::Entityon the object's class or any genuine ancestor (including module ancestors) is still found, nearest-ancestor first — which is exactly what the previous inherited lookup resolved. Only the accidental fallback to the global namespace is gone.The regression spec stubs a top-level
::Entity(aGrape::Entitysubclass, so it passes therespond_to?(:represent)check) and asserts it is not used for an object with no nested entity.🤖 Generated with Claude Code