参考:博客 https://www.cnblogs.com/chentianwei/p/10268346.htmljavascript
参考: mongoose官网(https://mongoosejs.com/docs/models.html)html
参考: 英文:Boosting Node.js和MongoDB with Mongoose前端
Mongoose is a fully developed object document mapping (ODM) library for Node.js and MongoDB. java
ODM的概念对应sql的ORM,就是ruby on rails中的activerecord那因层。node
activerecord包括migrations, Validations, associations, Query interface, 对应mvc框架中的Models。git
ORM, Object-Relational Mappiing。程序员
ODM的做用,定义数据库的数据格式schema, 而后经过它取数据,把数据库中的document映射成程序中的一个对象。这个对象有save, update的系列方法,有tilte, author等系列属性。github
在调用这些方法时,odm会根据你调用时使用的条件,转化成mongoDb Shell语言,帮你发送出去。web
天然,在程序内使用链式调用,比手写数据库语句更灵活也方便。正则表达式
例子:
//先安装好MongoDb和Node.js $ npm install mongoose // getting-started.js var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/test'); db.on('error', console.error.bind(console, "connection error")) db.once('open', function() { //当链接成功后,写Schema, model, 写实例并保存到数据库。 })
在db.once内的例子1
var userSchema = new mongoose.Schema({ user: { username: String, password: String } }) var User = mongoose.model('user', userSchema) var frank = new User({ user: { username: 'Frank', password: '123456' } }) frank.save((err, frank) => { console.log('save success!') console.log(frank.user) })
在db.once()的例子2
//构建一个Schema var kittySchema = new mongoose.Schema({ name: String }); // 写一个方法 kittySchema.methods.speak = function () { var greeting = this.name ? "Meow name is " + this.name : "I don't have a name"; console.log(greeting); } // 生成一个model var Kitten = mongoose.model('Kitten', kittySchema); // 实例化一个对象 var fluffy = new Kitten({ name: 'fluffy' }); // 经过mongoose写入数据库 fluffy.save((err, fluffy) => { if (err) { return console.error(err) } fluffy.speak() })
⚠️:此时已经将fluffy对象保存到mongodb://localhost:27017/test的Kitten model内。
即将一个document,保存到test数据库的kittens collection中。
model自动建立了kittens这个collection。(自动添加了s)
⚠️注意:此时mongoDb尚未建立kittens
在建立一个实例并执行save方法,test数据库才会建立了kittens collections和documents。
能够对比使用node.js mongodb driver的代码。
var MongoClient = require('mongodb').MongoClient, assert=require('assert'); var url = 'mongodb://localhost:27017/myproject'; MongoClient.connect(url,function(err,db){ assert.equal(null,err); console.log("成功链接到服务器"); insertDocuments(db,function(){ db.close(); }); // db.close(); }); var insertDocuments = function(db,callback){ var collection = db.collection('documents'); collection.insertMany([ {a:1}, {a:2}, {a:3} ],function(err,result){ assert.equal(err,null); assert.equal(3,result.result.n); assert.equal(3,result.ops.length); console.log("成功插入3个文档到集合!"); callback(result);
});
}
上面代码是专为Node.js提供的驱动程序代码和mongDB shell语言相似。
而,用mongoose定位于使用关系型的数据结构schema,来构造你的app data。
它包括内置的类型构件, 验证, 查询,业务逻辑勾子和更多的功能,开箱即用out of the box!
mongoose把你使用Node.js驱动代码本身写复杂的验证,和逻辑业务的麻烦,简单化了。
mongoose创建在MongoDB driver之上,让程序员能够model 化数据。
mongoose须要一段时间的学习和理解。在处理某些特别复杂的schema时,会遇到一些限制。
但直接使用Node.js的驱动代码,在你进行数据验证时会写大量的代码,并且会忽视一些安全问题。
不喜欢使用mongoose进行复杂的query,而是使用native driver。
Mongoose的缺点是某些查询的速度较慢。
固然Mongoose的优势不少。由于ODM(object document mapping)是现代软件编程的重要部分!
特别是企业级的engineering。
主要优点,就是从database中,提取每件事:程序代码只和object和它们的methods交互。
ODM容许指定:不一样类型的对象和把业务逻辑放在类内(和那些对象相关)之间的关系relationships.
另外,内建的验证和类型type casting能够扩展和客制。
当Mongoose和Express.js一块儿使用时, Mongoose让stack真正地拥护MVC理念。
Mongoose 使用相似Mongo shell, native MongoDB driver的交互方式。
Buckle up!本章将要讨论:
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true}); //一个mongoose链接实例 var db = mongoose.connection; db.once('open', () => { //... })
和native driver不同,咱们无需等待established connection, 只须要把全部的代码放入open()回调内。
不放入open()也能够,默认使用buffer。使用open(),确保链接了服务器。
Mongoose lets you start using your models immediately, without waiting for mongoose to establish a connection to MongoDB.
不管是否链接上服务器的MongoDB数据库,均可以立刻使用model。
mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true});
var Schema = mongoose.Schema var MyModel = mongoose.model('Test', new Schema({ name: String })); // Works MyModel.findOne(function(error, result) { /* ... */ });
That's because mongoose buffers model function calls internally. This buffering is convenient, but also a common source of confusion. Mongoose will not throw any errors by default if you use a model without connecting.
这是由于mongoose内部地缓冲了模型函数调用。这个缓冲很是的方便,但也是一个常见的source困惑。
由于若是在没有链接的状况下,你使用model,Mongoose默认不会抛出❌,
//一个脚本 const mongoose = require('mongoose') var MyModel = mongoose.model('Test', new Schema({ name: String})); //查询的代码会挂起来,指定mongoose成功的链接上。 MyModel.findOne(function(error, result) { /*...*/}); setTimeout(function() { mongoose.connect('mongodb://localhost:27017/myapp', {useNewUrlParser: true}) }, 6000)
mongodb://username:password@host:port/database_name
默承认以以下使用,host是localhost, port是27017, 数据库名字是test, 不设置username和password:
mongoose.connect('mongodb://localhost:27017/test', {useMongoClient: true})
mongoose.Promise = global.Promise
Promise这行让mongoose可使用native ES6 promise 。也可使用其余的promise implementation 。
Mongoose.prototype.Promise //The Mongoose Promise constructor。
connect(url, options)。 options是一个对象,里面是关于链接的属性设置。具体见官方文档。彻底支持原生Node.js driver。
下一步: 一个重要的差异(不一样于Mongoskin和其余轻量型MongoDB库):
建立一个model, 使用model()函数并传递一个string和一个schema
const Book = mongoose.model("Book", {name: String})
⚠️这里没有使用new mongoose.Schema()
如今配置语句结束,咱们建立a document表明Book model 的实例:
const oneBook = new Book({name: 'Practical Node.js'})
Mongoose documents有很是方便的内置方法:validate, isNew, update
(https://mongoosejs.com/docs/api.html#Document)
⚠️留心这些方法只能用在document上,不能用在collection或model上。
docuement是a model的实例, 而a model有点抽象,相似real MongoDB collection。
可是, 它由一个schema支持, 而且做为一个Node.js class(及额外的方法和属性)存在。
Models are fancy constructors compiled from Schema
definitions.
一般,咱们不直接地使用Mongoose collections, 咱们只经过models操做数据。
一些主要的model方法和native MongDB driver相似: find(), insert(), save()等等。
为了把一个docuemnt存入数据库,使用document.save()
这个方法是异步的asynchronous。所以添加一个callback或者promise或者async/await函数。
执行下面的脚本代码⚠️先打开MongoDB,server。
const mongoose = require('mongoose') mongoose.connect('mongodb://localhost:27017/test') mongoose.Promise = global.Promise const Book = mongoose.model("Book", {name: String}) const oneBook = new Book({name: "Hello world!"}) oneBook.save((err, result) => { if (err) { console.err(err) process.exit(1) } else { console.log("Saved:", result) process.exit(0) } })
Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB collection and defines the shape of the documents within that collection.
Mongoose开始于一个schema. 每一个scheme映射到一个MongoDB collection并定义这个collection中的document的外形。
var mongoose = require('mongoose'); var blogSchema = new mongoose.Schema({ title: String, comments: [{body: String, date: Date}], date: { type: Date, default: Date.now}, hidden: Boolean }) //add()方法,用于添加属性,参数是一个key/value对象, 或者是另外一个Schema. //add()能够链式调用。 blogSchema.add({author: String})
每一个key在咱们的documents内定义了一个属性并给予一个相关的SchemaType。
key也能够是嵌套的对象。
Schema不仅定义document的结构和属性,也定义document的实例方法,静态Model方法, 混合的compond indexes, 文档hooks 调用middleware。
为了使用咱们的schema定义,须要转化blogSchema进入一个Model:
var Blog = mongoose.model('Blog', blogSchema)
Models的实例是documents。Documents有内建的实例方法。
Instance methods
经过schema定义客制化的实例方法:
var animalSchema = new Schema({ name: String, type: String }) // 分配一个函数给methods对象 animalSchema.methods.findSimilarTypes = function(callback) { return this.model("Animal").find({ type: this.type }, callback) }
var Animal = mongoose.model('Animal', animalSchema) var dog = new Animal({type: 'dog'}) // 存入数据库
dog.save((err, dog) => { console.log("save success!") }) // dog document使用自定义的方法 dog.findSimilarTypes(function(err, dogs) { console.log("yes", dogs); // yes [ { _id: 5c45ba13aaa2f74d3b624619, type: 'dog', __v: 0 } ] });
Statics
给一个Model增长一个静态方法。
把一个函数分配给animalSchema的statics对象。
若是把一个Model当作一个类,那么静态方法就是这个类的类方法。
animalSchema.statics.findByName = function(name, callback) { return this.find({name: new RegExp(name, "i") }, callback) } var Animal = mongoose.model("Aniaml", animalSchema) Animal.findByName("fido", function(err, animals) { console.log("result: ", animals) })
⚠️,声明statics,不能使用箭头函数。由于箭头函数明确地防止绑定this。
也可使用Schema.static(name, funtion)方法
var schema = new mongoose.Schema(..); schema.static('findByName', function(name, callback) => { return this.find({name: name}, callback) })
使用{name: fn, name:fun, ...}做为惟一参数:
若是把一个hash(内含多个name/fn 对儿),做为惟一的参数传递给static(), 那么每一个name/fn对儿将会被增长为statics静态方法。
bookSchema.static({ // Static methods for generic, not instance/document specific logic getZeroInventoryReport: function(callback) { // Run a query on all books and get the ones with zero inventory // Document/instance methods would not work on "this" return callback(books) }, getCountOfBooksById: function(bookId, callback){ // Run a query and get the number of books left for a given book // Document/instance methods would not work on "this" return callback(count) } })
Query Helpers
能够增长query helper functions, 相似实例方法(❌?这句不是很明白,哪里相似了?),
可是for mongoose queries。
Query helper methods 让你扩展mongoose的链式查询builder API。chainable query builder API.
animalSchema.query.byName = function(name) { return this.where({ name: new RegExp(name, 'i') }); }; var Animal = mongoose.model('Animal', animalSchema); Animal.find().byName('fido').exec(function(err, animals) { console.log(animals); });
⚠️由上可见query helper方法是Model调用的。因此原文 like instance methods 这句不明白。
MongDB支持第二个indexes.
使用mongoose,定义indexes的方法有2个:
var animalSchema = new mongoose.Schema({ name: String, type: String, tags: { type: [String], index: true} }) animalSchema.index({ name: 1, type: -1})
Virtuals
document的一个属性。
Schemas有一些选项配置,能够用构建起或者用set()
new mongoose.Schema({..}, options) // or var schema = new mongoose.Schema({..}) schema.set(option, value)
Mongoose schemas是插件方式的, 便可以经过其余程序的schemas进行扩展。
(具体使用点击链接)
假如:在有大量关联的对象的复杂应用内,咱们想要在保存一个对象前,执行一段逻辑。
使用hook,来储存这段逻辑代码是一个好方法。例如,咱们想要在保存一个book document前上传一个PDF到web site:
//在一个schema上使用pre()钩子:
booSchema.pre('save', (next) => { // Prepare for saving // Upload PFD return next() })
pre(method, [options], callback)
第一个参数是method的名字
⚠️:钩子和方法都必须添加到schemas上,在编译他们到models 以前。也就是说,在调用mongoose.model()以前。
SchemaTypes处理definition of path defaults , 验证, getters, setters, 查询的默认field selection, 和Mongoose document属性的其余一些广泛特征。
你能够把一个Mongoose Schema看做是Mongoose model的配置对象。
因而,一个SchemaType是一个配置对象,做为一个独立的属性。
const schema = new Schema({ name: String }); schema.path('name') instanceof mongoose.SchemaType; // true schema.path('name') instanceof mongoose.Schema.Types.String; // true schema.path('name').instance; // 'String'
// 一个userSchema的userSchema.path("name"): SchemaString { enumValues: [], regExp: null, path: 'name', instance: 'String', validators: [], getters: [], setters: [], options: { type: [Function: String] }, _index: null }
我以为:一个path相似关系型数据库中的table中的一个field定义。
因此一个SchemaType,表达了一个path的数据类型, 它是不是getters/setters的模式。
一个SchemaType不等于一个Type。它只是Mongoose的一个配置对象。
mongoose.ObjectId !== mongoose.Types.ObjectId
它只是在一个schema内,对一个path的配置。
经常使用的SchemaTyps:
var schema = new mongoose.Schema({ name: String, binary: Buffer, living: Boolean, updated: { type: Date, default: Date.now}, age: { type: Number, min: 18, max: 65}, mixed: Schema.Types.Mixed, _someId: Schema.Types.ObjectId, array: [] })
数组的SchemaTypes:
var schema = new Schema({ ofString: [String], ofNumber: [Number], ofDates: [Date], ofBuffer: [Buffer], ofBoolean: [Boolean], ofMixed: [Schema.Types.Mixed], ofObjectId: [Schema.Types.ObjectId], ofArrays: [[]], ofArrayOfNumbers: [[Number]],
//嵌套对象 nested: { stuff: { type: String, lowercase: true, trim: true} }, map: Map, mapOfString: { type: Map, of: String } })
var schema1 = new Schema({ test: String // `test` is a path of type String }); var schema2 = new Schema({ // The `test` object contains the "SchemaType options" test: { type: String, lowercase: true } // `test` is a path of type string });
你能够增长任何属性(你想要给你的SchemaType options)。 有许多插件客制化SchemaType options。
Mongoose有几个内置的SchemaType options(具体见https://mongoosejs.com/docs/schematypes.html)
能够用schema type options定义MongoDB indexes:
var schema2 = new Schema({ test: { type: String, index: true, unique: true // Unique index. If you specify `unique: true` // specifying `index: true` is optional if you do `unique: true` } });
不一样的SchemaType有不一样的options,具体见官方guide。
正如许多ORMs/ODMs, 在mongoose中,cornerstone object is a model。对象的基石是模块。
把一个schema编译进入一个model, 使用:
mongoose.model(name, schema)
第一个参数name,是一个字符串,大写字母开头,一般这个string和对象字面量(声明的变量名)同样。
默认,Mongoose会使用这个model name的复数形式去绑定到一个collection name。
Models用于建立documents(实际的data)。使用构建器:
new ModelName(data)
Models又内建的静态类方法相似native MongoDB方法,如find(), findOne(), update(), insertMany()
一些经常使用的model 方法:
注意⚠️,一部分model方法不会激活hooks, 好比deleteOne(),remove()。他们会直接地执行。
最经常使用的实例方法:
大多数时候,你须要从你的document获得数据。
使用res.send()把数据发送到一个客户端。
document对象须要使用toObject()和toJSON()转化格式,而后再发送。
具体见:querying一章。
可使用findById(), 而后在回调函数内修改查询到的实例的属性值。
Tank.findById(id, function (err, tank) { if (err) return handleError(err); tank.size = 'large'; //或者使用tank.set({ size: 'large' }) tank.save(function (err, updatedTank) { if (err) return handleError(err); res.send(updatedTank); }); });
若是只是想要把新的数据更新到数据库,不返回,则可使用Model#updateOne()
Tank.update({_id: id}, { $set: {size: 'large'}}, callback)
若是如findById加上save(),返回新的数据,有更方便的方法: findByIdAndupdate()
配合使用res.send()
Tank.findByIdAndUpdate(id, { $set: { size: 'large' }}, { new: true }, function (err, tank) { if (err) return handleError(err); res.send(tank); });
⚠️,findByIdAndUpdate不会执行hooks或者验证,因此若是须要hooks和full documente validation,用第一种query而后save() it。
Documents在被保存前须要验证,具体见validation
.set(doc)方法,参数是另外一document的话,至关于重写。
使用Model.populate()或者 Query.populate()
虽然,Node开发者不能查询Mongo DB(on complex relationships), 可是经过Mongoose的帮助,开发者能够在application layer作到这点。
在大型的程序中,documents之间又复杂的关系,使用mongoose就变得很方便了。
例如,在一个电子商务网站,一个订单经过产品id,关联产品。为了获得更多的产品信息,开发者须要写2个查询: 一个取order,另外一个取订单的产品。
使用一个Mongoose query就能作到上面的2个查询的功能。
Mongoose经过链接订单和产品让2者的关系变得简单:Mongoose提供的一个功能,population。
这里population涉及的意思相似related,即相关的,有联系的。
populations是关于增长更多的data到你的查询,经过使用relationships。
它容许咱们从一个不一样的collection取数据来fill填document的一部分。
好比咱们有posts和users,2个documents。Users能够写posts。这里有2类方法实现这个写功能:
因而Mongoose提供了population,在这里有用武之地了。
在user schema内引用posts。以后populate这些posts。为了使用populate(), 咱们必须定义ref和model的名字:
const mongoose = require('mongoose') const Schema = mongoose.Schema const userSchema = new Schema({ _id: Number, name: String, posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }] })
⚠️,Schema.Types.ObjectId是一种SchemaType。
实际的postSchema只加了一行代码:
const postSchema = Schema({ _creator: { type: Number, ref: 'User'}, title: String, text: String })
下面的几行代码是咱们建立models, 而后yes!!! 只用一个findOne()类方法便可获得所有的posts的数据。
执行exec()来run:
const Post = mongoose.model("Post", postSchema) const User = mongoose.model('User', userSchema) //添加一些数据,并存入MongoDB数据库
User.findOne({name: /azat/i}) .populate('posts') .exec((err, user) => { if (err) return handleError(err) console.log('The user has % post(s)', user.posts.length) })
⚠️ ObjectId
, Number
, String
, and Buffer
are valid data types to use as references,
meaning they will work as foreign keys in the relational DB terminology.
也能够只返回一部分填入的结果。例如,咱们可以限制posts的数量为10个:
⚠️在mongoose, path指 定义一个Schema中的type类型的名字
.populate({ path: 'posts', options: { limit: 10, sort: 'title'} })
有时候,只会返回指定的fileds,而不是整个document,使用select:
.populate({ path: 'posts', select: 'title', options: { limit: 10, sort: 'title' } })
另外,经过一个query来过滤填入的结果!
.populate({ path: 'posts', select: '_id title text', match: {text: /node\.js/i}, options: { limit: 10, sort: '_id'} })
查询选择的属性使用select, 值是一个字符串,用空格分开每一个field name。
建议只查询和填入须要的fields,由于这样能够防止敏感信息的泄漏leakage,下降系统风险。
populate方法能够find()链接使用,即多个document的查询。
1. user.posts.length,这是user.posts是一个数组吗?因此可使用length方法。
答:是的,在定义userSchema时,posts field的数据类型是数组。
2.exec()的使用:
Model.find()返回<Query>, 而后使用Query.populate()并返回<Query>this, 而后使用Query.exec()返回Promise
3 type和ref
type表明SchemType。ref属性是SchemaType Options的一种。和type属性配合使用。
4.上面的案例,如何保存有关联的数据?
var user = new User({name: "John", _id: 2}) var post = new Post({title: "New land", text: "Hello World!"}) user.posts = post._id post._creator = user._id user.save() post.save() User.findOne({_id: 2}).populate("posts") .exec((error, user) => { console.log(user.posts.length) })
还须要看官网的Populate一章。讲真,这本书讲的都很浅显,有的没有说清楚。
理解:User和Post各自有一个含有选项ref的path。所以双方创建了关联。
Population是指: 在一个document内,用来自其余collection(s)的document,自动地取代指定paths的值。
咱们能够populate一个单独的document,多个documents, 普通的object,多个普通的objects, 或者从一个query返回的全部objects。
基础
const mongoose = require('mongoose') const Schema = mongoose.Schema mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true}) // var db = mongoose.connection const personScheam = Schema({ _id: Schema.Types.ObjectId, name: String age: Number, stories: [{ type: Schema.Types.ObjectId, ref: "Story"}] }) const storySchema = Schema({ author: { type: Schema.Types.ObjectId, ref: "Person"}, title: String, fans: [{ type: Schema.Types.ObjectId, ref: "Person"}] }) const Story = mongoose.model("Story", storySchema) const Person = mongoose.model("Person", personScheam)
注意⚠️
保存refs到其余documents和你保存属性的方式同样,指须要分配_id值:
const author = new Person({ _id: new mongoose.Types.ObjectId, name: "Ian Fleming", age: 50 }) author.save((err) => { if (err) return handleError(err) const story1 = new Story({ title: "Casino Royale", author: author._id }) story1.save((err, story1) => { if (err) return handleError(err) console.log("Success stores", story1.title) }) })
上面的代码,由于story1有外键author(即经过_id创建了两个documents的关联), 因此story1能直接populate author的数据。
如今填入story的author,使用query builder:
Story.findOne({ title: "Casino Royale"}) .populate('author') .exec((err, story) => { if (err) return handleError(err) console.log("The author is %s", story.author.name) })
经过在返回结果前运行一个独立的query,(findOne()方法返回的是一个Query对象)
填入的paths再也不是它们的原始的_id, 它们的值被替换为从数据库返回的document。
Arrays of refs和 非Arrays of refs的工做方式同样。都是在query对象上调用populate方法,并返回一个array of documents来替代原始的_ids。
也能够手动填入一个对象,来替换_id。把一个document对象赋值给author属性。
这个对象必须是你的ref选项所涉及的model的一个实例:
//假设以前已经向数据库存入了一个person和一个story, story有person的外键: Story.findOne({ title: "Casino Royale"}, (error, story) => { if (error) { return handleError(error) } Person.findOne({name: "Ian Fleming"}).exec((err, person) => { story.author = person
console.log(story.author.name) }) })
//控制台会输出author的名字
这是不使用populate的方法。和使用populate的效果同样,都是替换掉了_id。
Person.deleteMany({ name: "Ian Fleming" }, (err, result) => { if (err) { console.log("err: ",err) } else { console.log("res: ", result) } }); //由于没有了Person中的document, story.author.name是null。 Story.findOne({ title: "Casino Royale"}) .populate('author') .exec((err, story) => { if (err) return handleError(err) console.log("The author is %s", story.author.name) })
若是storySchema的authors path是数组形式的, 则populate()会返回一个空的array
若是只想从返回的populated documents获得指定的fields, 能够向populate()传入第二个参数: field name\
populate(path, [select])
Story.findOne({ title: "Casino Royale"}) .populate('author', 'name') .exec((err, story) => { if (err) return handleError(err) console.log("The author is %s", story.author.name) //返回The authors age is undefined console.log('The authors age is %s', story.author.age) })
若是咱们想要同时填入多个paths, 把populate方法连起来:
Story. find(...). populate('fans'). populate('author'). exec();
若是咱们想要填入populate的fans数组基于他们的age, 同时只选择他们的名字,并返回最多5个fans, 怎么作?
Story.find(...) .populate({ path: 'fans', match: {age: { $gte: 21 }}, // 使用"-_id",明确表示不包括"_id"field。 select: "name -_id", options: { limit: 5} }) .exec()
本章Populate官网教程提供的案例,auhtor对象的stories field并无被设置外键。
所以不能使用author.stories获得stories的列表。
这里有2个观点:perspectives:
第一, 你想要author对象知道哪些stories 是他的。一般,你的schema应该解决one-to-many关系,经过在many端加一个父pointer指针。可是,若是你有好的缘由想要一个数组的child指针,你可使用push()方法,把documents推到这个数组上:
author.stories.push(story1)
author.save(callback)
这样,咱们就能够执行一个find和populate的联合
Person. findOne({ name: 'Ian Fleming' }). populate('stories'). // only works if we pushed refs to children exec(function (err, person) { if (err) return handleError(err); console.log(person); });
是否真的要设置2个方向的pointers是一个可争论的地方。
第二,做为代替, 咱们能够忽略populating,并直接使用find()方法,找到stories:
Story. find({ author: author._id }). exec(function (err, stories) { if (err) return handleError(err); console.log('The stories are an array: ', stories); });
若是咱们有一个正存在的mongoose document并想要填入一些它的paths,
可使用document#populate() , 返回Document this。
doc.populate(path|options, callback) // or doc.populate(options).execPopulate()
若是咱们有多个documents或者plain objects, 咱们想要填入他们,使用Model.populate()方法。
这和document#populate(), query#populate()方式相似。
populate(docs, options, [callback(err, doc)]) 返回Promise.
// populates an array of objects
// find()返回一个query,里面的result是一个array of documents, 所以opts也应该是一个array of document
User.find(match, function (err, users) { var opts = [{ path: 'company', match: { x: 1 }, select: 'name' }] var promise = User.populate(users, opts); promise.then(console.log).end(); })
填入一个object, 和上面填入一个array of objects, 和填入不少plain objects。具体见文档
一个model的内的实例能够互相关联。即Self Joins
(这在Rails中的例子也是自身model上加一个foreign_key)
一个user schema能够跟踪user的朋友:
⚠️,关键使用ref选项,引用"User"自身!!!
var userSchema = new Schema({ name: String, friends: [{ type: Scheam.Types.ObjectId, ref: 'User'}] })
Populate让你获得一个user的朋友的列表。
可是若是你也想要一个user的朋友的朋友哪?加一个populate选项的嵌套:
User. findOne({ name: 'Val' }). populate({ path: 'friends', // Get friends of friends - populate the 'friends' array for every friend populate: { path: 'friends' } });
一个完整的例子:
//populate.js const mongoose = require('mongoose') const Schema = mongoose.Schema mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true}) const userSchema = new Schema({ _id: Number, name: String, friends: [{ type: Number, ref: 'User' }] }) const User = mongoose.model("User", userSchema) //存入下面的数据 var user = new User({ name: "chen", _id: 3, friends: [4] }).save() var user2 = new User({ name: "haha", _id: 4, friends: [3, 5] }).save() var user3 = new User({ name: "ming", _id: 5, friends: [5] }).save()
执行查询,使用populate选项:
User.findOne({_id: 3}) .populate({ path: 'friends', populate: {path: 'friends'} }) .exec((err, result) => { console.log(result) }) //返回 { posts: [], friends: [ { posts: [], friends: [ { posts: [], friends: [ 4 ], _id: 3, name: 'chen', __v: 0 }, { posts: [], friends: [ 5 ], _id: 5, name: 'ming', __v: 0 } ], _id: 4, name: 'haha', __v: 0 } ], _id: 3, name: 'chen', __v: 0 }
使用model选项
以前的练习:
//引进mongoose const mongoose = require('mongoose') //获得Schema构建器 const Schema = mongoose.Schema //mongoose实例链接到本地端口27017的数据库test mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true}) //获得connection对象实例, 由于实际的缘由,一个Connection等于一个Db var db = mongoose.connection
// with mongodb:// URI, 建立一个Connection实例
// 这个connection对象用于建立和检索models。
// Models老是在一个单一的connection中使用(scoped)。
var db = mongoose.createConnection('mongodb://user:pass@localhost:port/database');
假如,events和conversations这2个collection储存在不一样的MongoDB instances内。
var eventSchema = new Schema({ name: String, // The id of the corresponding conversation // ⚠️没有使用ref conversation: Schema.Typs.ObjectId }); var conversationSchema = new Schema({ numMessages: Number });
var db1 = mongoose.createConnection('localhost:27000/db1'); var db2 = mongoose.createConnection('localhost:27001/db2'); //⚠️,个人电脑上不能同时开2个mongd,提示❌
exception in initAndListen: DBPathInUse: Unable to lock the lock file: /data/db/mongod.lock (Resource temporarily unavailable). Another mongod instance is already running on the /data/db directory, terminating
var Event = db1.model('Event', eventSchema); var Conversation = db2.model('Conversation', conversationSchema);
这种状况下,不能正常使用populate()来填入数据,须要告诉populate使用的是哪一个model:
Event. find(). populate({ path: 'conversation', model: Conversation }). exec(function(error, docs) { /* ... */ });
// Populating across Databases const mongoose = require('mongoose') const Schema = mongoose.Schema mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true }) var db2 = mongoose.createConnection('mongodb://localhost:27017/db2', { useNewUrlParser: true }) // 建立2个Schema。 var eventSchema = new Schema({ name: String, conversation: Schema.Types.ObjectId }); var conversationSchema = new Schema({ numMessages: Number });
// 在test 数据库上建立一个Event类的实例。 var Event = mongoose.model('Event', eventSchema) var event = new Event({name: "click"}).save() // 在db2 数据库上建立一个Conversation类的实例 var Conversation = db2.model('Conversation', conversationSchema); var conversation = new Conversation({numMessages: 50}).save()
// 我在mongDb shell中给event document增长了一个field(conversation: XX),值是conversation实例的_id
启动上面的脚本后,我修改脚本去掉建立实例的2行代码,而后添加一个find和populate, 而后重启脚本:
Event.find() .populate({ path: 'conversation', model: Conversation}) .exec((error, docs) => { console.log(docs) })
成功,填入conversation: (这个例子就是在不一样database的一对一关联)
[ { _id: 5c4ad1f2916c8325ae15a6ac, name: 'click', __v: 0, conversation: { _id: 5c4ad1f2916c8325ae15a6ad, numMessages: 50, __v: 0 } } ]
上面的练习,
上面的练习,把2个model放在同database下,能够正确运行的✅。
即eventSchema没有使用 ref, 但在find().populate()内使用了model: "Conversation", 能够填入对应的conversation实例。
由于官方文档:Query.prototype.populate()的参数[model]的解释是这样的:
«Model» The model you wish to use for population.
If not specified, populate will look up the model by the name in the Schema's ref field.
即,
若是populate方法内指定了model选项,则从这个model中找对应的document。
若是没有指定model,才会在eventSchema中找ref选项,由于ref的值就是一个model的名字。
结论就是,不管是model选项仍是 ref选项,它们都是把2个document链接起来的辅助。
refPath
上一章population。 这是一种传统的方法,来设计你的数据库。它minic模仿了关系型数据库设计并使用普通的forms和严格的数据原子化atomization。
The document storage model in NoSQL databases is well suited to use nested documents。
若是你指定最频繁运行的查询是什么,使用nested documents是更好的选择。
你能够优化你的数据库让它倾向某一个查询。
例如,大多数典型的使用案例是读用户数据。那么代替使用2个collections(posts and users),
咱们能够用单一的collections(users), 内部嵌套posts。
绝对使用哪一种方式更多的是建筑学的问题,它的答案由具体使用决定。
例如,
const userSchema = new mongoose.Schema({ name: String, posts: [mongoose.Schema.Types.Mixed] }) // Attach methods, hooks, etc. const User = mongoose.model('User', userSchema)
const postSchema = new mongoose.Schema({ title: String, text: String }) // Attach methods, hooks, etc., to post schema const userSchema = new mongoose.Schema({ name: String, posts: [postSchema] }) // Attach methods, hooks, etc., to user schema const User = mongoose.model('User', userSchema)
由于使用了数组,因此可使用push, unshift, 等方法(在JavaScript/Node.js)或者MongoDB$push操做符号来更新user document:
User.updateOne( {_id: userId}, {$push: {posts: newPost}}, (error, results) => { // 处理错误和检测结果 } )
也可使用save():
var childSchema = new Schema({name: String}) var parentSchema = new Schema({ children: [childSchema], name: String }) var Parent = mongoose.model('Parent', parentSchema) var parent = new Parent({ children: [{name: 'Matt'}, {name: 'Sarah'}] }) parent.children[0].name = 'Matthew'
parent.children.push({ name: 'Liesl'})
parent.save((error, result) => { if (error) return console.log(error) console.log(result) })
获得:
{ _id: 5c47d630d93ce656805231f8, children: [ { _id: 5c47d630d93ce656805231fa, name: 'Matthew' }, { _id: 5c47d630d93ce656805231f9, name: 'Sarah' } ,
{ _id: 5c47d9b07517b756fb125221, name: 'Liesl' } ], __v: 0 }
注意⚠️,新增了3个child, 和parent一块儿存在mongoDB的test数据库的parents collections内
每一个子document默认有一个_id。
Mongoose document arrays有一个特别的id方法用于搜索一个doucment array来找到一个有给定_id值的document。
var doc = parent.children.id(_id)
移除使用remove方法,至关于在子文档内使用.pull()
parent.children.pull(_id) //等同 parent.children.id(_id).remove()
//对于:a single nested subdocument: parent.child.remove() //等同 parent.child = null
Mongoose models提供用于CRUD操做的静态帮助函数。这些函数返回一个mongoose Query 对象。
一个Query对象可使用.then()函数。
当使用一个query并传入一个callback(), 你指定你的query做为一个JSON document。
这个JSON document的语法和MongoDB shell相同。
var Person = mongoose.model('Person', yourSchema); // find each person with a last name matching 'Ghost', selecting the `name` and `occupation` fields Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) { if (err) return handleError(err); console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation); });
⚠️在Mongoose内,全部的callback都使用这个模式callback(error, result)
findOne()的例子:
Adventure.findOne({ type: 'iphone' }, function (err, adventure) {}); // same as above Adventure.findOne({ type: 'iphone' }).exec(function (err, adventure) {});
// specify options, in this case lean Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }, callback); // same as above Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }).exec(callback); // chaining findOne queries (same as above) Adventure.findOne({ type: 'iphone' }).select('name').lean().exec(callback);
lean选项为true,从queries返回的documents是普通的javaScript 对象,而不是MongooseDocuments。
countDocuments()的例子
在一个collection中,计算符合filter的documents的数量.
一个Query 可让你使用chaining syntax,而不是specifying a JSON object
例子:
Person. find({ occupation: /host/, 'name.last': 'Ghost', age: { $gt: 17, $lt: 66}, likes: { $in: ['vaporizing', 'talking']} }). limit(10). sort({ occupation: -1 }). select({name: 1, occupation: 1}) exec(callback) //等同于使用query builder:
Person. find({ occupation: /host/ }). where('name.last').equals('Ghost'). where('age').gt(17).lt(66). where('likes').in(['vaporizing', 'talking']). limit(10). sort('-occupation'). select('name occupation'). exec(callback);
可使用.then函数, 可是调用query的then()可以执行这个query屡次。
const q = MyModel.updateMany({}, { isDeleted: true }, function() { console.log('Update 1'); }); q.then(() => console.log('Update 2')); q.then(() => console.log('Update 3'));
上个例子,执行了3次updateMany()。
注意⚠️不要在query混合使用回调函数和promises。
不存在于数据库,可是像regular field in a mongoose document。就是mock,fake。
Virtual fields的用途:
例子,一个personSchema,有firstName, lastName2个fields,和一个Virtual fields(fullName),这个Virtual fullName无需真实存在。
另外一个例子,兼容之前的database。每次有一个新的schema, 只需增长一个virtual来支持旧的documents。
例如, 咱们有上千个用户记录在数据库collection,咱们想要开始收集他们的位置。所以有2个方法:
1. 运行一个migration script,为全部old user documents增长一个location field, 值是none。
2. 使用virtual field 并在运行时,apply defaults。
再举一个例,加入有一个大的document,咱们须要获得这个document的部分数据,而不是全部的数据,就可使用virtual field来筛选要显示的数据:
//从Schema中筛选出一些fields,放入虚拟field"info" userSchema.virtual('info') .get(function() { return { service: this.service, username: this.username, date: this.date, // ... } })
const mongoose = require('mongoose') mongoose.connect('mongodb://localhost:27017/myproject', {useNewUrlParser: true}) var personSchema = new mongoose.Schema({ name: { first: String, last: String } })
//定义一个virtualType personSchema.virtual('fullName').get(function () { return this.name.first + ' ' + this.name.last; }); var Person = mongoose.model('Person', personSchema) // var axl = new Person({ // name: { // first: 'Axl', // last: 'Rose' // } // }).save((error, result) => { // if (error) return console.log(error) // console.log(result) // }) Person.findOne({"name.first": 'Axl'}, (error, result) => { console.log(result.fullName) })
上面的例子使用了Schema#virtual()方法。定义了一个虚拟field,并VirtualType#get()方法定义了一个getter。天然也能够定义一个setter,使用set()方法:(关于get,set见👇一章)
//为virtual field “fullName”添加了写入write的set()函数 personSchema.virtual('fullName').set(function(v) { var parts = v.split(" ") this.name.first = parts[0] this.name.last = parts[1] }) //把一个名字字符串存入到fullName filed。本质是存到了name.first和name.last Person.findOne({"name.first": 'Axl'}, (error, person) => { person.fullName = "chen hao" person.save() })
结论: get和set方法就是read/write的存取方法。
⚠️: 由于virtuals不能储存在MongoDB,因此不能查询他们query。
默认,这2个方法不会影响到虚拟fileds。能够经过传{virtuals: true}来让这2个方法对virtual fields生效。
例子:
由于使用选项{getters: true}, 全部getters都会使用,包括virtual getters和path getters。
path getters指对Schema对象中的path(其实就是fields的另类称呼)设置一个get(callback)函数:
const mongoose = require('mongoose') mongoose.connect('mongodb://localhost:27017/myproject', {useNewUrlParser: true}) var schema = new mongoose.Schema({ name: String }); schema.path('name').get(function (v) { return v + ' is my name'; }); schema.set('toObject', { getters: true }); var People = mongoose.model('People', schema); var m = new People({ name: 'Max Headroom' }); console.log(m)
把document转化为一个普通的javaScript object, 并准备储存到数据库。返回一个js object。
toObject()方法的本质就是对document的一些设定,根据这些设定,把数据转化为js object.
参数只有[options], 有7个选项:
toObject使用的地方:
1. Schema构建器的选项
model对象自己是schema对象的副本。
而Schema()构建器的options内包括toJson和toObject选项,默认状况下构建器不会使用这2个选项。
因此若是你在Schema构建器上使用toObject选项(如上面的例子),则生成的doc必然使用toObject选项设置的规则,其中minimize和versionKey是默认true。
2. 对某个document使用.
Schema不是静态的类型定义。
Mongoose可让开发者在Schema内,定义/写入 getters(get), setters(set)和defaults(default)
get是在一个field被读时引用。 set是当一个field被赋值时引用。
开发者经过他们能够修改实际的database document的值。
Mongoose有4个方法:set()
, get()
, default()
and validate()
利用上面的4个方法,咱们能够在Mongoose Schema的fields中定义(和type在同一层:)
postSchema = new mongoose.Schema({ slug: { type: String, set: function(slug) { return slug.toLowerCase() } }, numberOfLikes: { type: Number, get: function(value) { return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") } }, authorId: { type: ObjectId, default: function() { return new mongoose.Types.ObjectId() } }, email: { type: String, unique: true, validate: [ function(email) { return (email.match(/[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i) != null)}, 'Invalid email' ] } })
由于会根据需求动态的定义Schema,因此Mongoose提供了另外的方法来修补Schema behavior:
chain methods--这须要2步:
⚠️和定义virtual fields的方式相似。
SchemaType.get(fn), 返回<this>。 为这个schema type的全部实例附加一个getter。
例如单独的为numberOfPosts field建立一个getter方法:
userSchema .path('numberOfPosts') .get(function() { return this.posts.length })
提示:
什么是path?
path就是一个名字,特指Schema内的嵌套的field name和它的父对象。
例如, 咱们有ZIP代码(zip)做为contact.address的一个child,
好比user.contact.address.zip, 而后contact.address.zip就是一个path。
mongoose提供了validation功能,如下是几条验证的rules:
例子:
这个例子由于生成的cat实例的name没有赋值,在save时,没法经过validation。
const mongoose = require('mongoose') const Schema = mongoose.Schema const { expect } = require('chai') mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true}) var schema = new Schema({ name: { type: String, required: true } }) var Cat = mongoose.model("Cat", schema) var cat = new Cat() cat.save((err) => { console.log(err) expect(err.errors['name'].message).to.equal("Path `name` is required.") })
err是一个javascript object:
经过err.errors.name.message获得 'Path `name` is required.'
{ ValidationError: Cat validation failed: name: Path `name` is required. at ValidationError.inspect (/Users/chen /node_practice/driver/node_modules/mongoose/lib/error/validation.js:59:24) at formatValue (internal/util/inspect.js:523:31) //...一大坨路径 errors: { name: { ValidatorError: Path `name` is required. at new ValidatorError (/Users/chentianwei/node_practice/driver/node_modules/mongoose/lib/error/validator.js:29:11) at validate (/Users/chentianwei/node_practice/driver/node_modules/mongoose/lib/schematype.js:926:13) //...一大坨路径 message: 'Path `name` is required.', name: 'ValidatorError', properties: { validator: [Function], message: 'Path `name` is required.', type: 'required', path: 'name', value: undefined }, kind: 'required', path: 'name', value: undefined, reason: undefined, [Symbol(mongoose:validatorError)]: true } }, _message: 'Cat validation failed', name: 'ValidationError' }
参数
第一个参数:required能够是《Boolean| Function | Object》
第二个参数:[message],string, 提供错误信息。
SchemaType#validate()
客制化的验证器也能够是异步的。让validator 函数返回一个promise(或者使用async function), mongoose会等待promise去处理。
也可使用回调函数做为参数。
(具体案例见文档)
若是验证失败,会返回错误的信息,其中包括一个errors对象。这个errors对象内部包含ValidatorError对象,kind, path, value, message, reason属性。
若是你在验证器内使用throw new Error(//...), 若是产生❌,reason属性会包含这个❌的信息。
见上面的☝️代码块。
案例
const mongoose = require('mongoose') const Schema = mongoose.Schema const { expect } = require('chai') mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true}) var toySchema = new Schema({ color: String, name: String }) //给color设置验证函数,color的值指定在red, white, gold内选择。 var validator = function(value) { return /red|white|gold/i.test(value) } toySchema.path('color').validate(validator, 'Color `{VALUE}` not valid', 'Invalid color') // 给name设置验证函数,若是传入的值不能经过判断,就抛出一个Error对象。并提供错误的信息。 toySchema.path('name').validate(function(v) { if (v !== 'Turbo Man') { throw new Error('Need to get a Turbo Man for Christams') } return true }, 'Name `{VALUE}` is not valid') // 声明Toy类 var Toy = mongoose.model('Toy', toySchema) // 实例化toy var toy = new Toy({color: 'Green', name: 'Power Ranger'}) // 保存,回调函数内进行测试。 toy.save(function(err) { console.log(err.errors.color) expect(err.errors.color.message).to.equal('Color `Green` not valid') expect(err.errors.color.path).to.equal('color') // 若是验证器throw an error, "reason"属性会包括错误的信息及stack trace console.log(err.errors.name) expect(err.errors.name.message).to.equal('Need to get a Turbo Man for Christams') expect(err.errors.name.value).to.equal('Power Ranger') })
默认状况下,更新验证不会使用,须要指定runValidators选项。
在doc更新时,激活验证。如 updateOne(), findOneAndUpdate()等。
var opts = { runValidators: true }; Toy.updateOne({}, { color: 'bacon' }, opts, function (err) { assert.equal(err.errors.color.message, 'Invalid color'); });
Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions。
中间件被指定在schema层,写plugins时很是有用!
Mongoose 有4种中间件:document, model, aggregate, query。
document中间件被如下函数支持:
Query中间件被如下Model和Query函数支持:
Model中间件被如下model 函数支持:
Pre middleware functions are executed one after another, when each middleware calls next
.
当pre中间件调用next函数后,就会执行后续的pre中间件。
var schema = new Schema(..) schema.pre('save', function(next) { // do stuff next() })
代替使用next,可使用一个函数返回一个promise,也能够用async/await
关于return的使用
调用next()不会中止中间件函数内的后续代码的执行。除非使用return next()
var schema = new Schema(..); schema.pre('save', function(next) { if (foo()) { console.log('calling next!'); // `return next();` will make sure the rest of this function doesn't run next(); } // 若是用return next(),下面的代码就不会执行了。 console.log('after next'); });
中间件用于细化逻辑。下面是一些其余的点子:
若是pre hook发送❌,以后的中间件或者勾住的函数不会执行。做为代替,Mongoose会传递一个error给回调函数, and/or拒绝返回的promise。
schema.pre('save', function(next) { const err = new Error('something went wrong'); // If you call `next()` with an argument, that argument is assumed to be // an error. next(err); }); // later... // Changes will not be persisted to MongoDB because a pre hook errored out myDoc.save(function(err) { console.log(err.message); // something went wrong });
在能够hooked method和它的pre中间件完成后,post中间件被执行。
schema.post('init', function(doc) { console.log('%s has been initialized from the db', doc._id); });
若是你的post hook function接受至少2个参数, mongoose会假设第2个参数是一个next()函数,你调用它来激活下一个中间件。
schema.post('save', function(doc, next) { setTimeout(function() { console.log('post1') next(); //执行下一个post hook }, 10) }) // 只有第一个post中间件调用next(), 才会执行下一个post中间件 schema.post('save', function(doc, next) { console.log('post2'); next(); });
注意⚠️: mongoose有内建的pre('save')钩子会调用validate()函数。
一句话就是pre('validate')和post('validate')会在pre('save')钩子以前被调用。
为了不重建全部其余的和ODM不相关的部分,如template, routes等等,根据上一章的Blog进行重构。
使用Mongoose。
在MongoDB和请求handlers之间产生一个抽象层。
代码参考:https://github.com/azat-co/blog-express/tree/mongoose
$npm install mongoose
由于mongoose是模仿关系型数据库的一个关系型的数据模型,咱们须要在主程序文件夹创建一个models文件夹,用于储存数据结构和关系。
而后在app.js内加上
const mongoose = require('mongoose')
const models = require('./models')
而后
创建链接声明,由于Mongoose使用models,不会直接使用Mongodb的collections因此去掉:
const collections = { articles: db.collection('articles'), users: db.collection('users') }
加上链接:
const db = mongoose.connect(dbUrl, {useMongoClient: true})
修改代码:
经过req对象获得mongoose models, 而后就能在每一个Express.js route内操做MongoDb的数据.
app.use((req, res, next) => { if (!models.Article || !models.User) { // <--- ADD
return next(new Error('No models.')) // <--- UPDATE
} req.models = models // <--- ADD
return next() })
OK了,从mongoskin到Mongoose完成。
为了说明代码复用,咱们从routes/article.js抽出find方法到models/article.js。
全部的database methods都这么作。
//GET /api/articles API route exports.list = (req, res, next) => { req.collections.articles.find({}).toArray((error, articles) => { if (error) return next(error) res.send({articles: articles}) }) }
抽出find,变成:
articleSchema.static({ list: function(callback) {
//第2个参数null是表示不设置projection this.find({}, null, {sort: {_id: -1}}, callback) } })
而后编译schema and methods 进入a model。
module.exports = mongoose.model('Article', articleSchema)
完整的article.js代码:
const mongoose = require('mongoose') const articleSchema = new mongoose.Schema({ title: { type: String, required: true, //一个验证器,不能为空 // validate验证器,第2个参数是message validate: [function(value){ return value.length <= 120 }, 'Title is too long(120 max)'], default: 'New Post' //默认值 }, text: String, published: { type: Boolean, default: false }, slug: { type: String, // 对SchemaType的path(slug)的设置: set: function(value) { return value.toLowerCase().replace(' ', '-') } } }) articleSchema。static({ list: function(callback) { this.find({}, null, {sort: {_id: -1}}, callback) } }) module.exports = mongoose.model('Article', articleSchema)
而后,
一样增长user.js和index.js。
随着models下.js文件的增多,使用index.js来控制输出models文件夹下的全部脚本文件,就变得很方便了。
exports.Article = require('./article')
exports.User = require('./user')
一样,把routes/article.js 的代码中的Mongoskin collection改为Mongoose models。
exports.show = (req, res, next) => { if (!req.params.slug) return next(new Error('No article slug.')) // req.collections.articles.findOne({slug: req.params.slug}, (error, article) => { //使用Mongoose models: req.models.Article.findOne({slug: req.params.slug}, (error, article) => { if (error) return next(error) if (!article.published && !req.session.admin) return res.status(401).send() res.render('article', article) }) }
这个show函数,用于显示一个具体的article详细内容的页面。
// 进口routes文件夹内的全部脚本的输出。 const routes = require('./routes/index.js') app.get('/articles/:slug', routes.article.show)
⚠️提示:
这里的next()函数是。express实例app的方法use中的callback内的参数。
app.use([path], callback[, callback...])
回调callback能够屡次的调用。为了方便,使用next函数,表明完成这个回调,并进行下一个callback。
routes/article.js至关于Rails中的controller,
app.js中的app.get(url, callback)至关于Rails中的routes内的一条route.
express实例app是一根实例。
route->control方法->model(MongoDB)
以后还要改list方法, add, edit, del, postArticle, admin方法。具体见代码:
(完整代码:https://github.com/azat-co/blog-express/blob/mongoose/routes/article.js)
有方便的语法糖findByIdAndUpdate。可是有些hook不会被激活,须要肯定它的使用。
所以使用传统的findById()加save()方法,能够激活全部的hook, 更安全。
使用Model.deleteOne。
而后修改routes/index.js
exports.article = require('./article') exports.user = require('./user') exports.index = (req, res, next) => { req.models.Article.find({published: true}, null, {sort: {_id: -1}}, (error, articles) => { if (error) return next(error) res.render('index', {articles: articles}) }) }
⚠️: null这个参数的位置自己是用于指定筛选出的fields,
若是不筛选则使用null,表示不进行筛选fields。全部的fields的数据都会取出。
最后,routes/user.js
具体还要约定第6章。
exports.authenticate = (req, res, next) => { if (!req.body.email || !req.body.password) { return res.render('login', {error: 'Please enter your email and password.'}) } req.models.User.findOne({ email: req.body.email, password: req.body.password }, function (error, user) { if (error) return next(error) if (!user) return res.render('login', {error: 'Incorrect email&password combination.'}) req.session.user = user req.session.admin = user.admin res.redirect('/admin') }) }
本章探讨使用Mongoose, 如何安装,创建链接到数据库。如何使用mongose schemas和SchemaType, 使用models, 如何使用它的query语法。如何使用hooks。 如何使用populate或nested documente。如何使用virtual fields.
并重构了Blog。使用Mongoose和一个MVC结构。
下章,讲解创建RESTFUL APIs使用Express, Hapi(忽略)。 如今的趋势是大前端加轻量后端。
这个趋势让开发团队集中在最重要的方面:终端用户--用户的交互体验。同时对商业来讲,下降重复循环和更少的维护和开发费用。
测试驱动也很重要:使用Mocha, 它普遍用于Node.js测试。