简体   繁体   中英

Keystone.js / mongoose virtual fields lean record

I'm trying to produce a lean record for a REST API that include virtual fields.

The official documentation for how to implement virtual fields for Mongoose:
http://mongoosejs.com/docs/guide.html

My model:

var keystone = require('keystone')
    , Types = keystone.Field.Types
    , list = new keystone.List('Vendors');

list.add({
    name : {
          first: {type : Types.Text}
        , last: {type : Types.Text}
    }
});

list.schema.virtual('name.full').get(function() {
    return this.name.first + ' ' + this.name.last;
});

list.register();

Now, let's query the model:

var keystone = require('keystone'),
    vendors = keystone.list('Vendors');

vendors.model.find()
    .exec(function(err, doc){
        console.log(doc)
    });

Virtual field name.full is not here:

[ { _id: 563acf280f2b2dfd4f59bcf3,
    __v: 0,
    name: { first: 'Walter', last: 'White' } }]

But if we do this:

vendors.model.find()
    .exec(function(err, doc){
        console.log(doc.name.full); // "Walter White"
    });

Then the virtual shows.

I guess the reason is that when I do a console.log(doc) the Mongoose document.toString() method is invoked which does not include virtuals by default. Fair enough. That's understandable.

To include the virtuals in any of the conversion methods you have to go:

doc.toString({virtuals: true}) 
doc.toObject({virtuals: true}) 
doc.toJSON({virtuals: true}) 

However, this includes keys I don't want for my REST API to pump out to my users:

{ _id: 563acf280f2b2dfd4f59bcf3,
  __v: 0,
  name: { first: 'Walter', last: 'White', full: 'Walter White' },
  _: { name: { last: [Object], first: [Object] } },
  list: 
   List {
     options: 
      { schema: [Object],
        noedit: false,
        nocreate: false,
        nodelete: false,
        autocreate: false,
        sortable: false,
        hidden: false,
        track: false,
        inherits: false,
        searchFields: '__name__',
        defaultSort: '__default__',
        defaultColumns: '__name__',
        label: 'Vendors' },
     key: 'Vendors',
     path: 'vendors',
     schema: 
      Schema {
        paths: [Object],
        subpaths: {},
        virtuals: [Object],
        nested: [Object],
        inherits: {},
        callQueue: [],
        _indexes: [],
        methods: [Object],
        statics: {},
        tree: [Object],
        _requiredpaths: [],
        discriminatorMapping: undefined,
        _indexedpaths: undefined,
        options: [Object] },
     schemaFields: [ [Object] ],
     uiElements: [ [Object], [Object] ],
     underscoreMethods: { name: [Object] },
     fields: { 'name.first': [Object], 'name.last': [Object] },
     fieldTypes: { text: true },
     relationships: {},
     mappings: 
      { name: null,
        createdBy: null,
        createdOn: null,
        modifiedBy: null,
        modifiedOn: null },
     model: 
      { [Function: model]
        base: [Object],
        modelName: 'Vendors',
        model: [Function: model],
        db: [Object],
        discriminators: undefined,
        schema: [Object],
        options: undefined,
        collection: [Object] } },
  id: '563acf280f2b2dfd4f59bcf3' }

I can always of course just delete the unwanted keys, but this doesn't seem quite right:

vendors.model.findOne()
    .exec(function(err, doc){
        var c = doc.toObject({virtuals: true});
        delete c.list;
        delete c._;
        console.log(c)
    });

This produces what I need:

{ _id: 563acf280f2b2dfd4f59bcf3,
  __v: 0,
  name: { first: 'Walter', last: 'White', full: 'Walter White' },
  id: '563acf280f2b2dfd4f59bcf3' }

Is there not a better way of getting a lean record?

I think you want the select method.. something like this:

vendors.model.findOne()
.select('_id __v name').
.exec(function(err, doc){
    console.log(c)
});

Also personally I prefer setting virtuals: true on the schema rather than the document, but depends on use case I guess.

One solution would be to use a module like Lodash (or Underscore) which allows you pick a whitelist of property names:

vendors.model.findOne()
    .exec(function(err, doc){
        var c = _.pick(doc, ['id', 'name.first', 'name.last', 'name.full']);
        console.log(c)
    });

Given your use-case of serving this data via REST API, I think explicitly defining a whitelist of property names is safer. You could even define a virtual property on your schema which returns the predefined whitelist:

list.schema.virtual('whitelist').get(function() {
    return ['id', 'name.first', 'name.last', 'name.full'];
});

and use it in multiple places, or have different versions of your whitelist, all managed at the model layer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM