Allow presentation of lists using heterogeneous entities (e.g. for STI data models)
Nobody has claimed this yet.
- Dominant language
- Ruby
- Stars
- 10k
- Forks
- 1.2k
- Avg merge
- 14h 38m
- Merged PRs (30d)
- 92
Description
Summary
Currently, when presenting multiple objects, Grape infers the entity it should use from the first member of the list (ref). This behavior is problematic for use cases that present heterogeneous lists of objects, i.e. those that differ with respect the entity intended for their presentation. A common such use case is family of models that implement single-table inheritance.
I propose constructing a variant of present that presents each member object using its own entity. I have monkey-patch solution currently running in production that I would like to implement within Grape itself.
Example of the Problem
Suppose you are modeling pets. All pets have names, but dogs have some attributes that cats lack, and visa versa. (I'll use Mongoid here, but any ORM that allows single table/collection inheritance could be used to illustrate this behavior.)
module Models
class Pet
include Mongoid::Document
store_in collection: 'pets'
field :name, type: String
end
class Dog < Pet
field :dog_field, type: String
end
class Cat < Pet
field :cat_field, type: String
end
end
Let's define Grape entities for these models:
module Entities
class Pet < Grape::Entity
expose :name
end
class Dog < Pet
Models::Dog::Entity = self
expose :dog_field
end
class Cat < Pet
Models::Cat::Entity = self
expose :cat_field
end
end
And finally define a /pets endpoint:
class Pets < Grape::API
namespace 'pets' do
present :data, Pet.all
end
end
Suppose we have one dog and one cat in our database. When we make a request to /pets, Grape will use the first object in our list to infer the entity it will use to represent all the objects. Let's suppose the cat is first, and so Grape will find the Entities::Cat entity. Grape will build a represenentation of the cat using the Cat entity. Then Grape will attempt to the Cat entity to represent the dog. In doing so, Grape will call cat_field on the dog, which will result in a NoMethodError.
Proposed Solution
I have implemented a solution to this problem as a monkey patch to the Grape gem. This file runs as part of the app's initialization: https://gist.github.com/hoffm/ed05817d28c261aa4dd078b30d61a7c9
This code changes the behavior of present when all of these conditions are met:
- The object responds to
#map(this is a proxy for its being list). - No entity is explicitly specified via the
:withoption. - The members of the list are instances of models that collectively specify more than one entity class.
When all three conditions are met, the new code creates a representation of each member of the list and combines these representations into the overall representation. This allows each member to be rendered using its own entity, and therefore to be represented using its type's specific shape.
This solution is currently running in a high-throughput production environment and seems to be functioning as intended. I propose implementing something similar within Grape, except that in order to avoid a breaking change, developers should have to explicitly opt into this behavior. This opt-in could be implemented via a new option passed to present.
Future Versions
In future versions of Grape, it may be desirable for the behavior described here become the default behavior for present.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in lib/grape/dsl/inside_route.rb around lines 359-364 and trace the present entry point that infers one entity from the first list member. Review the linked monkey-patch for the proposed per-member behavior, then define and verify an explicit present opt-in that renders heterogeneous entities without changing existing behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ruby
- Domain
- api
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100