參考:博客 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測試。