Look up an entity by the object's own class before its element's - #2844
Open
ericproulx wants to merge 1 commit into
Open
Look up an entity by the object's own class before its element's#2844ericproulx wants to merge 1 commit into
ericproulx wants to merge 1 commit into
Conversation
Grape::DSL::Entity#object_class decided which class to look an entity up for
by duck-typing:
return object.klass if object.respond_to?(:klass)
return object.first.class if object.respond_to?(:first)
object.class
Both tests are answered by plenty of single objects, and the object's own
class was consulted only last. A Struct is Enumerable, so it responds to
#first; so does any model that includes Enumerable; and #klass is hardly
exclusive to ActiveRecord::Relation. For all of those, `represent Model,
with: Entity` was silently ignored and the entity for whatever #first
returned was looked up instead -- usually nothing, so the raw object was
serialized.
Try the object's own class first and fall back to the collection or wrapped
class only when that comes up empty. An Array still resolves through its
element class, since Array itself has no entity, and a relation still
resolves through #klass.
Deferring the fallback also stops #first from being called at all when the
object resolves on its own class, which matters for anything where taking
the first element is expensive or has side effects.
Longstanding: the duck-typing dates to 2014 (v0.10.0) and behaves the same
way in 3.3.4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ericproulx
force-pushed
the
fix/entity-lookup-own-class-first
branch
from
August 1, 2026 11:26
794a2da to
e70e858
Compare
Danger ReportNo issues found. |
4 tasks
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.
Summary
Grape::DSL::Entity#object_classdecided which class to look an entity up for by duck-typing, and considered the object's own class only last:Both tests are answered by plenty of single objects. A
StructisEnumerable, so it responds to#first; so does any model that includesEnumerable; and#klassis hardly exclusive toActiveRecord::Relation. For all of those,represent Model, with: Entitywas silently ignored — Grape looked up the entity for whatever#firstreturned, usually found nothing, and serialized the raw object.Given
represent Model, with: Entityandpresent <one instance>:StructinstanceEnumerable#klass[][](unchanged)Approach
Try the object's own class first, falling back to the collection or wrapped class only when that comes up empty. An
Arraystill resolves through its element class —Arrayitself has no registered entity — and a relation still resolves through#klass.Deferring the fallback also stops
#firstfrom being called at all when the object resolves on its own class, which matters for anything where taking the first element is expensive or has side effects. There is a spec pinning that: a relation whose#firstraises still renders.Backward compatibility
No UPGRADING entry. The change only makes a registered entity apply where it previously did not, and the objects affected are ones whose
representregistration was being ignored. The one theoretical shift is an object whose own class has an entity and whose elements have a different one — the object's own now wins, which is the more specific reading.Longstanding rather than a regression: the duck-typing dates to 2014 (v0.10.0,
28df2d32) and behaves identically in 3.3.4.Test plan
api_spec.rbunder.represent: Struct, Enumerable model,#klassobject, array-still-resolves-by-element, and the#first-not-called guard. Verified 3 of them fail without thelib/change (the other two are invariant guards).🤖 Generated with Claude Code