When listing a bunch of entities, I would like to return a subset of data to each entity associated with a belongsTo()
relationship.
For example, let’s say I have a Cart
entity, and each cart item is associated with a Product
via belongsTo()
. When retrieving a list of cart entities, I only want the Product.name
and Product.price
fields, without the rest of the model.
The way I am currently doing it is by eager loading the relationship and then excluding fields I don’t want in the memento definition like this:
// Handler method index()
getInstance( "Cart" )
.with( [ "Product" ] )
.asMemento( "product" )
.get();
// Cart.cfc
function instanceReady() {
this.memento.defaultExcludes = [
"product.description",
"product.limit",
"product.expireDate",
// etc...
];
}
The end result looks like this:
[
{
"id": 1,
"userId": 202,
"productId": 334,
"product": {
"name": "My Product",
"price": 100.00
}
}
]
Excluding the fields is a bit of a pain, because whenever the Product
entity changes, I have to remember to add any new properties to the exclude list. I also feel like there must be a more efficient way to accomplish the same result without having to query the entire Product entity when I just need a few fields.
I experimented with a few different ideas already:
Idea 1: Subselects
From what I can tell from the docs, adding a subselect will append the virtual attributes at the root of the entity, and I want to maintain the nested object structure.
I was thinking something like this would work, but it does not:
function scopeAddProductBasic( qb ) {
return qb.addSubselect( "productName,productPrice", newEntity( "Product" )
.select( "name" )
.whereColumn( "id", "cart.productId" )
).with( "productName" );
}
The above code modifies the original query by adding a join, but doesn’t allow for multiple columns, and nothing gets returned in the actual SELECT statement.
Idea 2: Subquery
Perhaps a manual subquery is a way to go:
function scopeAddProductBasic( query ) {
var sub = query.newQuery()
.select( [ "product.name", "product.price" ] )
.from( "product" )
;
query.leftJoinSub( "product", sub, "product.id", "=", "cart.productId" );
}
Again, the above code modifies the original query by adding a join, but there’s no way that I can see to define the columns as a nested object. The columns get appended to the original query. I would have to write a complex memento.mapper
function combined with virtualAttributes to accomplish the task.
Idea 3: New Entity
Maybe this is a circumstance where I need to create a new entity with just the properties I need. For example:
// Cart.cfc
// the full relationship used for Cart behavior/calculations
function product() {
return BelongsTo( "Product", "productId" );
}
// when you need to return just the basics
function ProductBasic() {
return BelongsTo( "ProductBasic", "productId" );
}
The above approach works with one caveat. The resulting memento sub-object will be called productBasic
. This could probably be fixed with a memento.mapper
though.
Aside from the above options, is there anything obvious I am missing for the best way to handle this type of thing? I am leaning towards creating the New Entity approach and a custom memento.mapper
, but would love to hear other ideas.