halacious
halacious copied to clipboard
Embedding one entity nested in another doesn't work correctly when using toHal()
Embedding an entity that defines a toHal method on it inside another method with a toHal method on it works perfectly well, iff the embedding is done by use of configuration. If it is done using code in the toHal method of the outer entity then it does not work as expected.
The following works as expected:
function InnerModel(id, name, owner) {
this.id = id;
this.name = name;
this.owner = owner;
}
InnerModel.prototype.toHal = function(rep, next) {
rep.link('owner', '/users/' + this.owner.id);
next();
}
function PageModel(items, totalCount) {
this.items = items;
this.totalCount = totalCount;
}
PageModel.prototype.toHal = function(rep, next) {
rep.link('first', '?index=0'); // To prove that the toHal method runs
next();
}
server.route({
method: 'GET',
path: '/items',
config: {
handler: (req, reply) => {
reply(new PageModel([new InnerModel(1, 'Name', {id: 1001, name: 'Graham'})], 10));
},
tags: ['api'],
plugins: {
hal: {
embedded: {
item: {
path: 'items',
href: './{item.id}'
}
}
}
}
}
});
And this produces the following output:
{
"_links": {
"self": {
"href": "/items"
},
"first": {
"href": "?index=0"
}
},
"totalCount": 10,
"_embedded": {
"item": [
{
"_links": {
"self": {
"href": "/items/1"
},
"owner": {
"href": "/users/1001"
}
},
"id": 1,
"name": "Name",
"owner": {
"id": 1001,
"name": "Graham"
}
}
]
}
}
However, if I try and do this with code instead, as follows:
function InnerModel(id, name, owner) {
this.id = id;
this.name = name;
this.owner = owner;
}
InnerModel.prototype.toHal = function(rep, next) {
rep.link('owner', '/users/' + this.owner.id);
next();
}
function PageModel(items, totalCount) {
this.items = items;
this.totalCount = totalCount;
}
PageModel.prototype.toHal = function(rep, next) {
this.items.forEach(function(i) {
rep.embed('items', './' + i.id, i);
});
rep.ignore('items');
rep.link('first', '?index=0'); // To prove that the toHal method runs
next();
}
server.route({
method: 'GET',
path: '/items',
config: {
handler: (req, reply) => {
reply(new PageModel([new InnerModel(1, 'Name', {id: 1001, name: 'Graham'})], 10));
},
tags: ['api'],
plugins: {
hal: {
}
}
}
});
And this produces the following output:
{
"_links": {
"self": {
"href": "/items"
},
"first": {
"href": "?index=0"
}
},
"totalCount": 10,
"_embedded": {
"item": [
{
"_links": {
"self": {
"href": "/items/1"
}
},
"id": 1,
"name": "Name",
"owner": {
"id": 1001,
"name": "Graham"
}
}
]
}
}
Note in this case the inner Item does not have a link of "owner" even though the actual InnerModel class is exactly the same in both cases. The only difference is the one embeds the items using the "embedded" configuration and the other does it using "rep.embed" in the toHal method.
Note that I wanted to do it this way so that I can have a single PageModel class that is used everywhere instead of repeating the configuration everywhere that I want to do this.
Unless I have missed something this is still an issue in ^5.0.1.
Does anyone know of a workaround?
Thanks
Hey there,
I need to go through and clean up these old tickets. The best way to implement toHal()
now is to make use of the entity's configure()
method. its simpler and it works just like configuring the route:
PageModel.prototype.toHal = function (rep, next) {
rep.configure({
embedded: {
item: {
path: 'items',
href: './{item.id}'
}
}
}, next);
};
Hi @bleupen,
Thanks a lot for the heads-up, that's really helped simplify a lot of my models toHal() methods and HAPI plugin config.
Cheers
Alex