天天看点

Mongoose使用小结Populate - DBRef-like behavior (Foreign Key)

Before introducing to Mongoose, we talk something about MongoDB first.

Major difference from RDBMS

  • Unlike an RDBMS record which is "flat" (a fixed number of simple data type), the basic unit of MongoDb is "document" which is "nested" and can contain multi-value fields (arrays, hash).
  • Unlike RDBMS where all records stored in a table must be confined to the table schema, documents of any structure can be stored in the same collection.
  • There is no "join" operation in the query. Overall, data is encouraged to be organized in a more denormalized manner and the more burden of ensuring data consistency is pushed to the application developers
  • There is no concept of "transaction" in MongoDb. "Atomicity" is guaranteed only at the document level (no partial update of a document will occurred).
  • There is no concept of "isolation", any data read by one client may have its value modified by another concurrent client.

Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment.

Model Definition

var mongoose = require('mongoose')
  , Schema = mongoose.Schema;

var mySchema = new Schema({
    // my props
});
…
Mongoose.connect(‘mongodb://host/db’);


           

Schema

var ObjectId = Schema.ObjectId;
var safe = true;    //or safe = {j:1, w:2, wtimeout:10000}
function toLower(v){
         return v.toLowerCase();
}
 
var PostSchema = new Schema({
    owner   : ObjectId  // {“_id”: 125687847}
  , title   : String // {type: String, required: true} -Schema Type: String, Number, Date
                                                    Boolean, Buffer, ObjectId, Mixed
  , any     : {} // or Schema.Types.Mixed
  , topic   : {type: String, validate: /[a-z]/, trim: true, lowercase: true}-> validation
  , type    : {type: String, enum: [‘page’, ‘post’]} -> validation
  , date    : {type: Date, default: Date.now}
  , name    : {type: String, index: true} // Regular index -> index = true
              {type: String, index: {unique: true}}// spare index -> spare = true
              {type: String, index: {spare: true}}// unique index -> unique = true
              {type: String, unique: true, spare: true}
                                          // PostSchema.index({},{unique: true});
  , email   : {type: String, set: toLower}//Setting && Getting
                                          //post=new PostSchema({email:[email protected]})
, meta    : {
         Likes : [String]
       , age  :  {type: Number, min: 7, max: 23}
   }
}, 
  {safe: safe} // Schema options: safe, strict, shardkey.
);
 
PostsSchema.path(‘meta.age’).set(function(v){
         // setter…
})
 
PostsSchema.path(‘title’).validate(function (v){
         return v.length < 23;  //could be RegExp.
})
           

Schema Type: http://mongoosejs.com/docs/schematypes.html

Schema options:

safe

By default this is set to true for all schemas which guarantees that any error that occurs will get reported back to our method callback.

By setting safe to something else like { j: 1, w: 2, wtimeout: 10000 }we can guarantee the write was committed to the journal (j: 1), at least 2 replicas (w: 2), and that the write will timeout if it takes longer than 10 seconds (wtimeout: 10000). Errors will still be reported back to our callback.

strict

The strict option makes it possible to ensure that values added to our model instance that were not specified in our schema do not get saved to the db.

shardkey (new in v2.5.3)

The 

shardkey

 option is used when we have a sharded MongoDB architecture. Each sharded collection is given a shard key which must be present in all insert/update operations. We just need to set this schema option to the same shard key and we’ll be all set.

In this example, Mongoose will include the required 

tag

 and 

name

 in all

doc.save()

where

 clauses for us.

Virtual attribute – like View

Mongoose allows you to do this, too, via virtualattribute setters. You can define a virtual attribute setter thusly:

PersonSchema
.virtual('name.full')
.get(function () {
  return this.name.first + ' ' + this.name.last;
})
.set(function (setFullNameTo) {
  var split = setFullNameTo.split(' ')
    , firstName = split[0]
   , lastName = split[1];
  this.set('name.first', firstName);
  this.set('name.last', lastName);
});

           

Schema events

When a schema is passed to 

mongoose.model()

 the 

init

 event will be emitted on the schema, passing in the model. This is helpful for some plugins that need to hook directly into the model.

var schema = new Schema({ name: String });
 
schema.on('init', function (model) {
  // do stuff with the model
});
 
mongoose.model('MyModel', schema);
           

Beyond Keys: Middleware

Middleware are special user-defined functions that are called transparently when certain native methods are called on 

Document

instances (

init

save

 and 

remove

).

BlogPost.pre('save', function (next) {
  email(this.email, 'Your record has changed');
  next();
});

 
           

Embedded Documents - Define documents within documents.

var Comments = new Schema({
    title     : String
  , body      : String
  , date      : Date
});
 
var BlogPost = new Schema({
    author    : ObjectId
  , title     : String
  , body      : String
  , date      : Date
  , comments  : [Comments]
  , meta      : {
        votes : Number
      , favs  : Number
    }
});
 
 
//Methods and Statics
BlogPost.methods.findSimilarTitle = function findSimilarTitle(ti) {
         return this.find({title: this.title}, ti);
} 
// blogPost.findSimilarType()
//         .where('name': /rover/i)

 
BlogPost.statics.search = function search(name, cb) {
         return this.where('name', new RegExp(name, 'i')).run(cb);

}
 
mongoose.model('BlogPost', BlogPost);
 
 
// retrieve my model
var BlogPost = mongoose.model('BlogPost');
 
// create a blog post
var post = new BlogPost();
 
// create a comment
post.comments.push({ title: 'My comment' });
 
post.save(function (err) {
  if (!err) console.log('Success!');
});
 
//Removing an embedded document.
BlogPost.findById(myId, function (err, post) {
  if (!err) {
    post.comments[0].remove();
    post.save(function (err) {
      // do something
    });
  }
});
           

Query http://mongoosejs.com/docs/finding-documents.html

Some query functions as bellow:

Model.find –

findOne –

findById – by ‘_id’

count –

remove –

distinct –

where –

Model

.where('age').gte(25)
.where('tags').in(['movie', 'music', 'art'])
.select('name', 'age', 'tags')
.skip(20)
.limit(10)
.asc('age')
.slaveOk()
.hint({ age: 1, name: 1 })
.run(callback);

           

$where –

Sometimes you need to query for things in mongodb using aJavaScript expression. You can do so via find({$where: javascript}), or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.

Model.$where('this.firstname === this.lastname').exec(callback)

query
.where('name', 'Space Ghost')
.where('age').gte(21).lte(65)
.run(callback)

 
           

In this case, gte() and lte() operate on the previous path if not explicitly passed. The above query results in the following query expression:

{ name: 'Space Ghost', age: { $gte: 21, $lte: 65 }}

Update -

var conditions = { name: 'borne' }
  , update = { $inc: { visits: 1 }}
  , options = { multi: true };

Model.update(conditions, update, options, callback);

function callback (err, numAffected) {
 // numAffected is the number of updated documents
})


           

Keep in mind that that the  safe  option specified in your schema is used by default when not specified here.

Populate - DBRef-like behavior (Foreign Key)

ObjectIds can now refer to another document in a collection within our database and be populate()d when querying. An example is helpful:

var mongoose = require('mongoose')
  , Schema = mongoose.Schema

var PersonSchema = new Schema({
    name    : String
  , age     : Number
  , stories : [{ type: Schema.ObjectId, ref: 'Story' }]
});

var StorySchema = new Schema({
    _creator : { type: Schema.ObjectId, ref: 'Person' }
  , title    : String
  , fans     : [{ type: Schema.ObjectId, ref: 'Person' }]
});
var Story  = mongoose.model('Story', StorySchema);
var Person = mongoose.model('Person', PersonSchema);

           

save a ref

var aaron = new Person({ name: 'Aaron', age: 100 });
 
aaron.save(function (err) {
  if (err) ...
 
  var story1 = new Story({
      title: "A man who cooked Nintendo"
    , _creator: aaron._id
  });
 
  story1.save(function (err) {
    if (err) ...
  });
})
           

Populating the refs

Story
.findOne({ title: /Nintendo/i })
.populate('_creator', [‘name’]) // .populate(‘fans’, null, {age: {$gte:21}}, {limit:5})
.run(function (err, story) {
  if (err) ..
  console.log('The creator is %s', story._creator.name);
  // prints "The creator is Aaron"
})
           

Updating

Now that we have a story we realized that the _creator was incorrect. We can update ObjectId refs the same as any other property through the magic of Mongooses internal casting:

var guille = new Person({ name: 'Guillermo' });

guille.save(function (err) {
  if (err) ..

  story._creator = guille; // or guille._id
  story.save(function (err) {

    if (err) ..

    Story
    .findOne({ title: /Nintendo/i })
    .populate('_creator', ['name'])
    .run(function (err, story) {

      if (err) ..

       console.log('The creator is %s', story._creator.name)
      // prints "The creator is Guillermo"
    })
  })