6

I have the following schema.

var UserSchema = new mongoose.Schema({
  username: {
    type: String,
    unique: true,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  test: {
    type: String, 
    default: 'hello world'
  }
});

UserSchema.pre('save', function(callback) {
  var user = this;
  this.test = undefined; // here unset test field which prevent to add in db 
});

module.exports = mongoose.model('User', UserSchema);

But when I find data for example

User.find(function(err, users) {
    if (err)
      res.send(err);

    res.json(users);
  });

it always returns

[
    {
        _id: "56fa6c15a9383e7c0f2e4477",
        username: "abca",
        password: "$2a$05$GkssfHjoZnX8na/QMe79LOwutun1bv2o76gTQsIThnjOTW.sobD/2",
        __v: 0,
        test: "hello world"
    }
]

How can I modify or add any special param to get data without test field and without any change in query, let's say

User.find({}, '-test', function (err, users){

});

also, I have set the default value in model: test: "hello world" but I do not want this value to appear in the response. I have also set this.test = undefined; and this should mean that it is preventing this default value to be added into database, however I am still getting this in response.

Blakes Seven
  • 49,422
  • 14
  • 129
  • 135
waqas jamil
  • 423
  • 3
  • 9

3 Answers3

2
  1. You want the test property persisted in database and just don't want it selected when querying:

You can use select in a pre find hook:

UserSchema.pre('find', function (next) {
    this.select({ test: false });
    next();
});

In a query hook (as opposed to a save hook for example), this refers to your query object. In a save hook it refers to the current document being saved.

This hook will only execute for find queries, not findById or findOne queries.

OR

(see Hank Chiu's answer)

set the select flag to false in the schema :

test: {
      type: String, 
      default: 'hello world',
      select: false,
}
  1. You don't want the testproperty persisted in database :

Remove the test property from schema and add a test virtual :

schema.virtual('test').get(function () {
    return 'hello world';
});

user.test will return hello world.

  1. You want the test property persisted in database but return something different :

Add a getter your test definition :

test: {
    type: String, 
    default: 'hello world',
    get: function () {
        return 'hello guys';
    }
}

user.test will return hello guys but its true value will be persisted in database.

Old erroneous answer :

You can use select which takes an object of Model properties as keys and booleans as values :

User
    .find({})
    .select({
        test: false;
    })
    .exec(function (err, users) {
        // users won't have the test property
    });

Komo
  • 2,043
  • 1
  • 22
  • 35
  • 2
    That is exactly the same as what `'-test'` does. So that's not what the OP is asking. They want the field exluded "automatically" so that **every** `.find()` call already excludes the field without having to do `'-test'` or `.select({ "test": false })` or `.select({ "test": -1 })` ( which is again the same thing ) – Blakes Seven Mar 29 '16 at 12:39
  • Oh you're right. I missed the "without any change in query" part, my bad. I updated my answer – Komo Mar 29 '16 at 13:06
  • 1
    Probably should note that [middleware](http://mongoosejs.com/docs/middleware.html) like this is pretty specific. So you would need `findOne` as well or else a `.findOne()` or `.findById()` would "still" return the field. I also wonder if considering the OP does not want to "store" the data if "virtuals" would be a better choice, and then excluding them from serialization. That can be done as well. – Blakes Seven Mar 29 '16 at 13:20
  • 1
    I agree, there's many ways to exclude a field from query results, but it all comes down to what the OP needs. I think that a find middleware makes sense if he wants to exclude fields from a generic query to make the results lighter. If he's querying a specific user, I think he's more likely to want to include all fields. I edited my answer based on your comment. – Komo Mar 29 '16 at 13:36
  • Thanks both of you for your quick response and very informative answer :) , actually I have multiple user roles "instructor", "client" , " admin" and "studio" . Each role has similar and different fields . So, I want to add only desired field associated to particular role and exclude remaining ones. – waqas jamil Mar 29 '16 at 16:11
  • 1
    You might want to lookup discriminators for that : http://mongoosejs.com/docs/discriminators.html. It will allow you to define models that inherits from another schema. This way you can define an admin schema that inherits from user schema. – Komo Mar 29 '16 at 16:22
  • I look into link provided by you . Here I face another issue . As, discrminator schema was based which inherited by child schema . Events -> ClickedLink. If we create 5 collections like 1)user -> instructor 2)user-> student 3)user-> studio 4)user-> admin 5) user it self It better to create 4 different collections with full fields rather than inherit 1) student 2) instructor 3) studio 4) admin – waqas jamil Mar 29 '16 at 17:23
  • Please open a new question, as it is a different matter :) – Komo Mar 29 '16 at 18:50
2

Set your schema as

test: {
    type: String, 
    default: 'hello world',
    select: false
}

Check the SchemaType#select in the document.

hankchiutw
  • 1,546
  • 1
  • 12
  • 15
  • 1
    Thank you for answer, Can I define "select" on conditional basis ? – waqas jamil Mar 29 '16 at 18:18
  • What do you mean "on conditional basis"? Once `select:false` defined, you can retrieve the attribute by using `select('+test')` when query. – hankchiutw Mar 29 '16 at 23:35
  • I have multiple roles like student, instructor, studio . So, what can I do to make "select" true for student and false for instructor? – waqas jamil Mar 30 '16 at 08:25
  • You can use function in `default` attribute, ex. `default: function(){ return (this.role=='student') ? 'hello world':'';}`. But this will not work if you are going to store different values in the field `test`. – hankchiutw Mar 31 '16 at 01:24
  • 1
    Chui , I understand your logic but problem is if role is "student" then default value become 'hello world' when role become "instructor" or else it's become '' (empty string ) still test field save in db . When I find any roles lets say "Instructor " field become visible for example "test: ' ' " – waqas jamil Mar 31 '16 at 08:19
0

Use select function as your example it would be like

User.find(/* condition */).select('username -_id -__v')

The above code will return the username field only

TL: DR

Fields are written as a single string with whitespace separator, to exclude field add - prefix like -_id or -username.

Ahmed Kamal
  • 2,660
  • 3
  • 21
  • 36