模式¶
SchemaTypes handle definition of path defaults, validation, getters, setters, field selection defaults for queries,and other general characteristics for Mongoose document properties.
什么是 SchemaType?¶
You can think of a Mongoose schema as the configuration object for a Mongoose model. A SchemaType is then a configuration object for an individual property. A SchemaType says what type a given path should have, whether it has any getters/setters, and what values are valid for that path.
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'
A SchemaType is different from a type. In other words, mongoose.ObjectId !== mongoose.Types.ObjectId
.
A SchemaType is just a configuration object for Mongoose. An instance of
the mongoose.ObjectId
SchemaType doesn't actually create MongoDB ObjectIds,
it is just a configuration for a path in a schema.
The following are all the valid SchemaTypes in Mongoose. Mongoose plugins can also add custom SchemaTypes like int32. Check out Mongoose's plugins search to find plugins.
示例¶
const schema = new 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,
decimal: Schema.Types.Decimal128,
array: [],
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,
},
});
// example use
const Thing = mongoose.model("Thing", schema);
const m = new Thing();
m.name = "Statue of Liberty";
m.age = 125;
m.updated = new Date();
m.binary = Buffer.alloc(0);
m.living = false;
m.mixed = { any: { thing: "i want" } };
m.markModified("mixed");
m._someId = new mongoose.Types.ObjectId();
m.array.push(1);
m.ofString.push("strings!");
m.ofNumber.unshift(1, 2, 3, 4);
m.ofDates.addToSet(new Date());
m.ofBuffer.pop();
m.ofMixed = [1, [], "three", { four: 5 }];
m.nested.stuff = "good";
m.map = new Map([["key", "value"]]);
m.save(callback);
type
键¶
type
is a special property in Mongoose schemas. When Mongoose finds
a nested property named type
in your schema, Mongoose assumes that
it needs to define a SchemaType with the given type.
// 3 string SchemaTypes: 'name', 'nested.firstName', 'nested.lastName'
const schema = new Schema({
name: { type: String },
nested: {
firstName: { type: String },
lastName: { type: String },
},
});
As a consequence, you need a little extra work to define a property named type
in your schema.
For example, suppose you're building a stock portfolio app, and you
want to store the asset's type
(stock, bond, ETF, etc.). Naively,
you might define your schema as shown below:
const holdingSchema = new Schema({
// You might expect `asset` to be an object that has 2 properties,
// but unfortunately `type` is special in Mongoose so mongoose
// interprets this schema to mean that `asset` is a string
asset: {
type: String,
ticker: String,
},
});
However, when Mongoose sees type: String
, it assumes that you mean
asset
should be a string, not an object with a property type
.
The correct way to define an object with a property type
is shown
below.
const holdingSchema = new Schema({
asset: {
// Workaround to make sure Mongoose knows `asset` is an object
// and `asset.type` is a string, rather than thinking `asset`
// is a string.
type: { type: String },
ticker: String,
},
});
SchemaType 选项¶
You can declare a schema type using the type directly, or an object with
a type
property.
const schema1 = new Schema({
test: String, // `test` is a path of type String
});
const schema2 = new Schema({
// The `test` object contains the "SchemaType options"
test: { type: String }, // `test` is a path of type string
});
In addition to the type property, you can specify additional properties for a path. For example, if you want to lowercase a string before saving:
const schema2 = new Schema({
test: {
type: String,
lowercase: true, // Always convert `test` to lowercase
},
});
You can add any property you want to your SchemaType options. Many plugins
rely on custom SchemaType options. For example, the mongoose-autopopulate
plugin automatically populates paths if you set autopopulate: true
in your
SchemaType options. Mongoose comes with support for several built-in
SchemaType options, like lowercase
in the above example.
The lowercase
option only works for strings. There are certain options
which apply for all schema types, and some that apply for specific schema
types.
模式选项¶
required
: boolean 或函数,如果为 true,则为该属性添加需要验证器default
: 任意或函数,为路径设置默认值。如果该值是一个函数,则默认使用该函数的返回值。select
: boolean, 为查询指定默认的投影validate
: 函数,为这个属性添加一个验证器函数get
: 函数,使用Object.defineProperty()
为该属性定义一个自定义 getter.set
: 函数,使用Object.defineProperty()
为该属性定义自定义 setter.alias
: 字符串,仅mongoose >= 4.10.0
。用给定的名称定义一个virtual来获取/设置这个路径。immutable
: 布尔值,定义路径为不可变的。Mongoose 阻止你改变不可变的路径,除非父文档有isNew: true
。transform
: 当你调用Document#toJSON()
函数时,包括当你JSON.stringify()
一个文档时,Mongoose 调用这个函数。
const numberSchema = new Schema({
integerOnly: {
type: Number,
get: (v) => Math.round(v),
set: (v) => Math.round(v),
alias: "i",
},
});
const Number = mongoose.model("Number", numberSchema);
const doc = new Number();
doc.integerOnly = 2.001;
doc.integerOnly; // 2
doc.i; // 2
doc.i = 3.001;
doc.integerOnly; // 3
doc.i; // 3
索引 Indexes¶
You can also define MongoDB indexes using schema type options.
index
: boolean, whether to define an index on this property.unique
: boolean, whether to define a unique index on this property.sparse
: boolean, whether to define a sparse index on this property.
const 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`
},
});
字符 String¶
lowercase
: boolean, whether to always call.toLowerCase()
on the valueuppercase
: boolean, whether to always call.toUpperCase()
on the valuetrim
: boolean, whether to always call.trim()
on the valuematch
: RegExp, creates a validator that checks if the value matches the given regular expressionenum
: Array, creates a validator that checks if the value is in the given array.minLength
: Number, creates a validator that checks if the value length is not less than the given numbermaxLength
: Number, creates a validator that checks if the value length is not greater than the given numberpopulate
: Object, sets default populate options
数字 Number¶
min
: Number, creates a validator that checks if the value is greater than or equal to the given minimum.max
: Number, creates a validator that checks if the value is less than or equal to the given maximum.enum
: Array, creates a validator that checks if the value is strictly equal to one of the values in the given array.populate
: Object, sets default populate options
日期 Date¶
min
: Datemax
: Date
对象 Id ObjectId¶
populate
: Object, sets default populate options
使用笔记¶
String¶
To declare a path as a string, you may use either the String
global
constructor or the string 'String'
.
const schema1 = new Schema({ name: String }); // name will be cast to string
const schema2 = new Schema({ name: "String" }); // Equivalent
const Person = mongoose.model("Person", schema2);
If you pass an element that has a toString()
function, Mongoose will call it,
unless the element is an array or the toString()
function is strictly equal to
Object.prototype.toString()
.
new Person({ name: 42 }).name; // "42" as a string
new Person({ name: { toString: () => 42 } }).name; // "42" as a string
// "undefined", will get a cast error if you `save()` this document
new Person({ name: { foo: 42 } }).name;
Number¶
To declare a path as a number, you may use either the Number
global
constructor or the string 'Number'
.
const schema1 = new Schema({ age: Number }); // age will be cast to a Number
const schema2 = new Schema({ age: "Number" }); // Equivalent
const Car = mongoose.model("Car", schema2);
There are several types of values that will be successfully cast to a Number.
new Car({ age: "15" }).age; // 15 as a Number
new Car({ age: true }).age; // 1 as a Number
new Car({ age: false }).age; // 0 as a Number
new Car({ age: { valueOf: () => 83 } }).age; // 83 as a Number
If you pass an object with a valueOf()
function that returns a Number, Mongoose will
call it and assign the returned value to the path.
The values null
and undefined
are not cast.
NaN, strings that cast to NaN, arrays, and objects that don't have a valueOf()
function
will all result in a CastError once validated, meaning that it will not throw on initialization, only when validated.
Dates¶
Built-in Date
methods are not hooked into the mongoose change tracking logic which in English means that if you use a Date
in your document and modify it with a method like setMonth()
, mongoose will be unaware of this change and doc.save()
will not persist this modification. If you must modify Date
types using built-in methods, tell mongoose about the change with doc.markModified('pathToYourDate')
before saving.
const Assignment = mongoose.model("Assignment", { dueDate: Date });
Assignment.findOne(function (err, doc) {
doc.dueDate.setMonth(3);
doc.save(callback); // THIS DOES NOT SAVE YOUR CHANGE
doc.markModified("dueDate");
doc.save(callback); // works
});
Buffer¶
To declare a path as a Buffer, you may use either the Buffer
global
constructor or the string 'Buffer'
.
const schema1 = new Schema({ binData: Buffer }); // binData will be cast to a Buffer
const schema2 = new Schema({ binData: "Buffer" }); // Equivalent
const Data = mongoose.model("Data", schema2);
Mongoose will successfully cast the below values to buffers.
const file1 = new Data({ binData: 'test'}); // {"type":"Buffer","data":[116,101,115,116]}
const file2 = new Data({ binData: 72987 }); // {"type":"Buffer","data":[27]}
const file4 = new Data({ binData: { type: 'Buffer', data: [1, 2, 3]}}); // {"type":"Buffer","data":[1,2,3]}
Mixed¶
An "anything goes" SchemaType. Mongoose will not do any casting on mixed paths.
You can define a mixed path using Schema.Types.Mixed
or by passing an empty
object literal. The following are equivalent.
const Any = new Schema({ any: {} });
const Any = new Schema({ any: Object });
const Any = new Schema({ any: Schema.Types.Mixed });
const Any = new Schema({ any: mongoose.Mixed });
Since Mixed is a schema-less type, you can change the value to anything else you
like, but Mongoose loses the ability to auto detect and save those changes.
To tell Mongoose that the value of a Mixed type has changed, you need to
call doc.markModified(path)
, passing the path to the Mixed type you just changed.
To avoid these side-effects, a Subdocument path may be used instead.
person.anything = { x: [3, 4, { y: "changed" }] };
person.markModified("anything");
person.save(); // Mongoose will save changes to `anything`.
ObjectIds¶
An ObjectId
is a special type typically used for unique identifiers. Here's how
you declare a schema with a path driver
that is an ObjectId:
const mongoose = require("mongoose");
const carSchema = new mongoose.Schema({ driver: mongoose.ObjectId });
ObjectId
is a class, and ObjectIds are objects. However, they are
often represented as strings. When you convert an ObjectId to a string
using toString()
, you get a 24-character hexadecimal string:
const Car = mongoose.model("Car", carSchema);
const car = new Car();
car.driver = new mongoose.Types.ObjectId();
typeof car.driver; // 'object'
car.driver instanceof mongoose.Types.ObjectId; // true
car.driver.toString(); // Something like "5e1a0651741b255ddda996c4"
Boolean¶
Booleans in Mongoose are plain JavaScript booleans.
By default, Mongoose casts the below values to true
:
true
'true'
1
'1'
'yes'
Mongoose casts the below values to false
:
false
'false'
0
'0'
'no'
Any other value causes a CastError.
You can modify what values Mongoose converts to true or false using the
convertToTrue
and convertToFalse
properties, which are JavaScript sets.
const M = mongoose.model("Test", new Schema({ b: Boolean }));
console.log(new M({ b: "nay" }).b); // undefined
// Set { false, 'false', 0, '0', 'no' }
console.log(mongoose.Schema.Types.Boolean.convertToFalse);
mongoose.Schema.Types.Boolean.convertToFalse.add("nay");
console.log(new M({ b: "nay" }).b); // false
Arrays¶
Mongoose supports arrays of SchemaTypes and arrays of subdocuments. Arrays of SchemaTypes are also called primitive arrays, and arrays of subdocuments are also called document arrays.
const ToySchema = new Schema({ name: String });
const ToyBoxSchema = new Schema({
toys: [ToySchema],
buffers: [Buffer],
strings: [String],
numbers: [Number],
// ... etc
});
Arrays are special because they implicitly have a default value of []
(empty array).
const ToyBox = mongoose.model("ToyBox", ToyBoxSchema);
console.log(new ToyBox().toys); // []
To overwrite this default, you need to set the default value to undefined
Note: specifying an empty array is equivalent to Mixed
. The following all create arrays of
Mixed
:
const Empty1 = new Schema({ any: [] });
const Empty2 = new Schema({ any: Array });
const Empty3 = new Schema({ any: [Schema.Types.Mixed] });
const Empty4 = new Schema({ any: [{}] });
Maps¶
在 Mongoose 5.1.0 中新增
MongooseMap 是JavaScript Map
类的子类.
在这些文档中,我们将交替使用术语map
和MongooseMap
。
在 Mongoose 中,映射是使用任意键创建嵌套文档的方式。
Note
在 Mongoose Maps 中,键必须是字符串,以便在 MongoDB 中存储文档。
const userSchema = new Schema({
// `socialMediaHandles` is a map whose values are strings. A map's
// keys are always strings. You specify the type of values using `of`.
socialMediaHandles: {
type: Map,
of: String,
},
});
const User = mongoose.model("User", userSchema);
// Map { 'github' => 'vkarpov15', 'twitter' => '@code_barbarian' }
console.log(
new User({
socialMediaHandles: {
github: "vkarpov15",
twitter: "@code_barbarian",
},
}).socialMediaHandles
);
上面的例子并没有明确地将github
or twitter
声明为路径,
但是,由于socialMediaHandles
是一个映射,你可以存储任意键/值对。
然而,由于socialMediaHandles
是一个映射,你必须使用.get()
来获取键的值,而.set()
来设置键的值。
const user = new User({ socialMediaHandles: {} });
// Good
user.socialMediaHandles.set("github", "vkarpov15");
// Works too
user.set("socialMediaHandles.twitter", "@code_barbarian");
// Bad, the `myspace` property will **not** get saved
user.socialMediaHandles.myspace = "fail";
// 'vkarpov15'
console.log(user.socialMediaHandles.get("github"));
// '@code_barbarian'
console.log(user.get("socialMediaHandles.twitter"));
// undefined
user.socialMediaHandles.github;
// Will only save the 'github' and 'twitter' properties
user.save();
映射类型存储为BSON 对象在 MongoDB. BSON 对象中的键是有序的,所以这意味着映射的插入顺序属性得到了维护。
Mongoose 支持一个特殊的$*
语法来填充 映射中的所有元素。
例如,假设你的socialMediaHandles
地图包含一个ref
:
const userSchema = new Schema({
socialMediaHandles: {
type: Map,
of: new Schema({
handle: String,
oauth: { type: ObjectId, ref: "OAuth" },
}),
},
});
const User = mongoose.model("User", userSchema);
为了填充每个socialMediaHandles
条目的oauth
属性,你应该在socialMediaHandles.$*.oauth
上填充:
Getters¶
Getters are like virtuals for paths defined in your schema. For example,
let's say you wanted to store user profile pictures as relative paths and
then add the hostname in your application. Below is how you would structure
your userSchema
:
const root = "https://s3.amazonaws.com/mybucket";
const userSchema = new Schema({
name: String,
picture: {
type: String,
get: (v) => `${root}${v}`,
},
});
const User = mongoose.model("User", userSchema);
const doc = new User({ name: "Val", picture: "/123.png" });
doc.picture; // 'https://s3.amazonaws.com/mybucket/123.png'
doc.toObject({ getters: false }).picture; // '/123.png'
Generally, you only use getters on primitive paths as opposed to arrays or subdocuments. Because getters override what accessing a Mongoose path returns, declaring a getter on an object may remove Mongoose change tracking for that path.
const schema = new Schema({
arr: [{ url: String }],
});
const root = "https://s3.amazonaws.com/mybucket";
// Bad, don't do this!
schema.path("arr").get((v) => {
return v.map((el) => Object.assign(el, { url: root + el.url }));
});
// Later
doc.arr.push({ key: String });
doc.arr[0]; // 'undefined' because every `doc.arr` creates a new array!
Instead of declaring a getter on the array as shown above, you should
declare a getter on the url
string as shown below. If you need to declare
a getter on a nested document or array, be very careful!
const schema = new Schema({
arr: [{ url: String }],
});
const root = "https://s3.amazonaws.com/mybucket";
// Good, do this instead of declaring a getter on `arr`
schema.path("arr.0.url").get((v) => `${root}${v}`);
Schemas¶
To declare a path as another schema,
set type
to the sub-schema's instance.
To set a default value based on the sub-schema's shape, simply set a default value, and the value will be cast based on the sub-schema's definition before being set during document creation.
const subSchema = new mongoose.Schema({
// some schema definition here
});
const schema = new mongoose.Schema({
data: {
type: subSchema
default: {}
}
});
创建自定义类型¶
Mongoose can also be extended with custom SchemaTypes. Search the plugins site for compatible types like mongoose-long, mongoose-int32, and other types.
Read more about creating custom SchemaTypes here.
schema.path()
函数¶
The schema.path()
function returns the instantiated schema type for a
given path.
const sampleSchema = new Schema({ name: { type: String, required: true } });
console.log(sampleSchema.path("name"));
// Output looks like:
/**
* SchemaString {
* enumValues: [],
* regExp: null,
* path: 'name',
* instance: 'String',
* validators: ...
*/
You can use this function to inspect the schema type for a given path, including what validators it has and what the type is.