Skip to content

Embedding one entity nested in another doesn't work correctly when using toHal() #80

@sazzer

Description

@sazzer

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions