Search code examples
node.jsloopbackjs

Extend model with count of related model


I'm trying to build a simple blog with loopback. I want to extend get Posts with the amount of comments.

I have two possible ways in my mind. 1) Extend the response of the get-posts by a count of the comments, this would be my favorite way, but I have no idea how to extend the reposne. 2) I have tried to observe the comment saving and to get the posts-model, but I can't change it.

post.json

{
  "name": "post",
  "base": "PersistedModel",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "title": {
      "type": "string",
      "required": true
    },
    "content": {
      "type": "string",
      "required": true
    }
    "published": {
      "type": "boolean",
      "required": true,
      "default": false
    }
    "commentCount": {
      "type": "number",
      "default": 0
    }
  },
  "validations": [],
  "relations": {
    "user": {
      "type": "belongsTo",
      "model": "user",
      "foreignKey": ""
    },
    "comments": {
      "type": "hasMany",
      "model": "comment",
      "foreignKey": ""
    }
  },
  "acls": [
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "DENY"
    },
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "admin",
      "permission": "ALLOW"
    },
    {
      "accessType": "EXECUTE",
      "principalType": "ROLE",
      "principalId": "admin",
      "permission": "ALLOW",
      "property": "find"
    },
    {
      "accessType": "EXECUTE",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW",
      "property": "create"
    },
    {
      "accessType": "READ",
      "principalType": "ROLE",
      "principalId": "$owner",
      "permission": "ALLOW"
    },
    {
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW",
      "property": [
        "__create__comments",
        "__get__comments"
      ]
    },
    {
      "principalType": "ROLE",
      "principalId": "$owner",
      "permission": "ALLOW",
      "property": "__delete__comments"
    }
  ],
  "methods": {}
}

comment.json

{
  "name": "comment",
  "base": "PersistedModel",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "content": {
      "type": "string",
      "required": true
    }
  },
  "validations": [],
  "relations": {
    "user": {
      "type": "belongsTo",
      "model": "user",
      "foreignKey": ""
    },
    "idea": {
      "type": "belongsTo",
      "model": "post",
      "foreignKey": ""
    }
  },
  "acls": [
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$everyone",
      "permission": "DENY"
    },
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "admin",
      "permission": "ALLOW"
    },
    {
      "accessType": "READ",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW"
    },
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$owner",
      "permission": "ALLOW"
    }
  ],
  "methods": {}
}

comment.js ##

var loopback = require('loopback');

module.exports = function(Comment) {
    Comment.observe('after save', function(ctx, userInstance, next) {
      var postId = ctx.instance.postId;

// loopback.getModel('post').definition.rawProperties.commentCount... something... something... 

    });
};

I'm still very new to loopback and I don't know what is the best way to achieve the solution. Maybe you have a third, better way? Or maybe anyone can help me to complete the comment.js.


Solution

  • Fisrt, in your comment.json, you've written idea instead of post:

     "post": {  //change here
          "type": "belongsTo",
          "model": "post",
          "foreignKey": ""
        }
    

    Secondly, you simply add one commentCount in the post linked to your comment in your after save method and then update the attributes of your post:

    'use strict';
    
    var app = require('../../server/server');
    var models = app.models;
    var Post;
    // pattern to get your models on start event
    app.on('started', function () {
        Post = models.post;
    });
    
    module.exports = function(Comment) {
       Comment.observe('after save', function(ctx, next) {
          // only add a commentCount if it's a new instance
          if (ctx.instance && ctx.isNewInstance && ctx.instance.postId) {
              Post.findOne({where: {id: ctx.instance.postId}}, function (err, post) {
                  if (!err) {
                     post.updateAttributes({commentCount: post.commentCount++});
                  }
              });
          }
          next();
       });
    };
    

    Another solution would be to create a customGet endpoint in your post.js file:

    'use strict';
     module.exports = function(Post) {
            Post.customGet = function (postId, cb) {
                Post.findOne({where: {id: postId}, include: 'comments'}, function (err, post) {
                    if(err){
                        return cb(err, {});
                    }
                    post.commentCount = post.comments.length;
                    return cb(err, post);
                });
            }
    
            Post.remoteMethod('customGet', {
                description: 'New endpoint with the commentCount',
                accepts: {arg: 'postId', type: 'string'},
                returns: {arg: 'post', type: 'object'},
                http: {verb: 'get'}
            });
      };
    

    You can improve this method a bit but you get the idea.