Adding 'virtual' variables to a mongoose schema?
Asked Answered
M

5

24

I have the following document schema:

var pageSchema = new Schema({
      name: String
    , desc: String
    , url: String
})

Now, in my application I would like to also have the html source of the page inside the object, but I do not want to store it in the db.

Should I create a "local" enhanced object which has a reference to the db document?

function Page (docModel, html) {
    this._docModel = docModel
    this._html = html
}

Is there a way to use the document model directly by adding a "virtual" field?

Mel answered 14/8, 2013 at 18:21 Comment(5)
Why do you want to have it in the object but not store it?Trotyl
Because I do not need it to be persistent: if I reboot my server and reload the objects from database, that html would need to be updated too (it comes from an external process). I could store it, but it would be wasted space. Since in Mongoose there are virtual methods, it would have been nice to also have virtual variables..Mel
You can simply set [damn enter] a property on the object like document.prop = html. I don't think that method or virtuals will actually let you fetch "local" data if you fetch the object from the db again though, even if not restarting.Trotyl
Oh, right. I can simply add a new property. What confused me was that if I do page.newProperty = "something"; console.log(page) it does not show the newProperty in the output.. but if I do console.log(page.newProperty) I see the value :|Mel
Don't do this. Check my answer, virtuals are supported by mongoose.Azeria
A
37

This is perfectly possible in mongoose.
Check this example, taken from their documentation:

var personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
});

personSchema.virtual('name.full').get(function () {
  return this.name.first + ' ' + this.name.last;
});
console.log('%s is insane', bad.name.full); // Walter White is insane

In the above example, the property would not have a setter. To have a setter for this virtual, do this:

personSchema.virtual('name.full').get(function () {
  return this.name.full;
}).set(function(name) {
  var split = name.split(' ');
  this.name.first = split[0];
  this.name.last = split[1];
});

Documentation

Azeria answered 15/8, 2013 at 14:50 Comment(5)
Yes but they are not adding new fields.. They are using existing ones.Mel
And what's the problem? You can use setters also.Azeria
oh, I see. you mean I should this.newfield = inside the virtual set.Mel
Not sure it makes any sense though :lMel
I had to activate virtuals for the "toObject" and "toJSON" in schema options. Otherwise it did not work for me . Here: mongoosejs.com/docs/2.7.x/docs/virtuals.htmlMedan
C
16

Document properties that start with __ are not persisted to the db, so you could create a virtual property and have the getter and setter use this.__html

pageSchema.virtual('html').get(function () {
  return this.__html;
}).set(function (html) {
  this.__html = html;
});

but this is a bit of a hack with a caveat: this functionality is not documented so there is no list of internal properties that start with __ so there is a possibility, albeit unlikely, that in the future the internal implementation could start using a var called __html

https://github.com/Automattic/mongoose/issues/2642

Curzon answered 23/8, 2015 at 11:42 Comment(0)
S
13

I have not actually tested this but the idea seems worthy:

//model
var pageSchema = new Schema({
      name: String
    , desc: String
    , url: String
})

pageSchema.virtual('html')
  .get(function(){
    var url = this.url

    function get(url) {
      return new (require('httpclient').HttpClient)({
        method: 'GET',
          url: url
        }).finish().body.read().decodeToString();
    }

    return get(url);
  });


  //controller
  var page = new Page({
    name: "Google"
    , desc: "Search engine"
    , url: "http://google.com"
  });

  var html = page.html;

Basically set a virtual attribute called html which requests the body for the url attribute and returns it.

Be sure to enable outputting of virtual attributes for toJSON if you are using express and res.send(page).

pageSchema.set('toJSON', {
    virtuals: true
});
Stradivarius answered 18/5, 2014 at 6:21 Comment(0)
M
1

In 2020

You can set a virtual object by adding below objects fields in model.

toJSON: { virtuals: true },
toObject: { virtuals: true }

above example and below example are equivalent, but above is short and simple.

itemSchema.set('toJSON', {
    virtuals: true
});

Here is the Example

Model

const itemsSchema = new mongoose.Schema({
    image: {
        type: String,
        trim: true,
        required: [true, 'Please provide item image']
    },
    color: {
        type: String,
        trim: true
    },
    size: {
        type: String,
        trim: true
    },
    price: {
        type: Number,
        required: [true, 'Please provide item price']
    },
    shipping: {
        type: Number
    },
    discount: {
        type: Number
    },
    details: {
        type: String,
        trim: true
    }
}, {
    toJSON: { virtuals: true },
    toObject: { virtuals: true }
});

Set VirtualSchema

itemsSchema.virtual('totalPrice').get(function() {
    const sub_total = this.price + this.shipping;
    return (sub_total - ( ( sub_total / 100 ) * this.discount )).toFixed(2)
});
Magniloquent answered 8/6, 2020 at 15:21 Comment(0)
H
1

Here is a solution that works on my end:

const studentSchema = new Schema({
    name: {
        firstName: String,
        lastName: String
    }
});

// Add virtual variables here"
studentSchema.virtual('fullName').get(function () {
    return `${this.name.firstName} ${this.name.lastName}`
});
Holystone answered 7/3 at 3:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.