Spec-Zone.ru › Mongoose

Схемы

Если вы ещё этого не сделали, пожалуйста, потратьте минуту на чтение быстрого старта, чтобы понять, как работает Mongoose. Если вы мигрируете с версии 6.x на 7.x, пожалуйста, потратьте время на чтение руководства по миграции.

Определение вашей схемы

Всё в Mongoose начинается со схемы. Каждая схема отображается на коллекцию MongoDB и определяет форму документов в этой коллекции.

import mongoose from 'mongoose';
const { Schema } = mongoose;

const blogSchema = new Schema({
  title: String, // String is shorthand for {type: String}
  author: String,
  body: String,
  comments: [{ body: String, date: Date }],
  date: { type: Date, default: Date.now },
  hidden: Boolean,
  meta: {
    votes: Number,
    favs: Number
  }
});

Если вы хотите добавить дополнительные ключи позже, используйте метод Schema#add.

Каждый ключ в нашем коде blogSchema определяет свойство в наших документах, которое будет преобразовано в его соответствующий тип SchemaType. Например, мы определили свойство title , которое будет преобразовано в тип String SchemaType, и свойство date , которое будет преобразовано в тип Date SchemaType.

Обратите внимание, что если свойству требуется только тип, его можно указать с помощью сокращённой записи (сравните свойство title выше со свойством date).

Ключи также могут быть назначены вложенным объектам, содержащим дальнейшие определения ключей/типов, как свойство meta выше. Это произойдёт всякий раз, когда значение ключа является POJO, у которого нет свойства type.

В этих случаях Mongoose создаёт фактические пути схемы только для листов в дереве (например, meta.votes и meta.favs выше), а ветви не имеют фактических путей. Побочным эффектом этого является то, что meta выше не может иметь собственной валидации. Если валидация необходима выше по дереву, путь должен быть создан выше по дереву — см. раздел Поддокументы для получения дополнительной информации о том, как это сделать. Также прочитайте подраздёл «Mixed» в руководстве по SchemaTypes для ознакомления с нюансами.

Разрешенные SchemaTypes:

  • Строка
  • Число
  • Дата
  • Буфер
  • Логическое значение
  • Смешанный
  • ObjectId
  • Массив
  • Decimal128
  • Карта
  • UUID

Подробнее о SchemaTypes здесь.

Схемы не только определяют структуру вашего документа и преобразование свойств, но также определяют методы экземпляра документа методы экземпляра, статические методы модели статические методы модели, составные индексы составные индексы и циклы жизни документа, называемые средствами разработки.

Создание модели

Чтобы использовать наше определение схемы, нам нужно преобразовать наше blogSchema в модель, с которой мы можем работать. Для этого мы передаём её в mongoose.model(modelName, schema):

const Blog = mongoose.model('Blog', blogSchema);
// ready to go!

Идентификаторы

По умолчанию Mongoose добавляет свойство _id к вашим схемам.

const schema = new Schema();

schema.path('_id'); // ObjectId { ... }

Когда вы создаёте новый документ с автоматически добавленным свойством _id , Mongoose создаёт новый _id типа ObjectId для вашего документа.

const Model = mongoose.model('Test', schema);

const doc = new Model();
doc._id instanceof mongoose.Types.ObjectId; // true

Вы также можете перезаписать значение по умолчанию Mongoose _id своим собственным _id. Просто будьте осторожны: Mongoose откажется сохранить документ, у которого нет _id, поэтому вы сами должны установить _id , если вы определите свой собственный путь _id.

const schema = new Schema({ _id: Number });
const Model = mongoose.model('Test', schema);

const doc = new Model();
await doc.save(); // Throws "document must have an _id before saving"

doc._id = 1;
await doc.save(); // works

Методы экземпляра

Экземпляры Models являются документами. Документы имеют множество своих встроенных методов экземпляра встроенные методы экземпляра. Мы также можем определить наши собственные пользовательские методы экземпляра документа.

// define a schema
const animalSchema = new Schema({ name: String, type: String },
  {
  // Assign a function to the "methods" object of our animalSchema through schema options.
  // By following this approach, there is no need to create a separate TS type to define the type of the instance functions.
    methods: {
      findSimilarTypes(cb) {
        return mongoose.model('Animal').find({ type: this.type }, cb);
      }
    }
  });

// Or, assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function(cb) {
  return mongoose.model('Animal').find({ type: this.type }, cb);
};

Теперь все наши экземпляры animal имеют доступный метод findSimilarTypes.

const Animal = mongoose.model('Animal', animalSchema);
const dog = new Animal({ type: 'dog' });

dog.findSimilarTypes((err, dogs) => {
  console.log(dogs); // woof
});
  • Переопределение встроенного метода mongoose может привести к непредсказуемым результатам. Подробнее см. здесь.
  • В примере выше используется объект Schema.methods напрямую для сохранения метода экземпляра. Вы также можете использовать вспомогательный метод Schema.method() , как описано здесь.
  • Не объявляйте методы с помощью стрелочных функций ES6 (=>). Стрелочные функции явным образом запрещают привязку this, поэтому ваш метод не будет иметь доступа к документу, и примеры выше не будут работать.

Статические методы

Вы также можете добавить статические функции к своей модели. Существует три эквивалентных способа добавления статического метода:

  • Добавление свойства функции ко второму аргументу конструктора схемы (statics)
  • Добавление свойства функции к schema.statics
  • Вызов функции Schema#static()
// define a schema
const animalSchema = new Schema({ name: String, type: String },
  {
  // Assign a function to the "statics" object of our animalSchema through schema options.
  // By following this approach, there is no need to create a separate TS type to define the type of the statics functions.
    statics: {
      findByName(name) {
        return this.find({ name: new RegExp(name, 'i') });
      }
    }
  });

// Or, Assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function(name) {
  return this.find({ name: new RegExp(name, 'i') });
};
// Or, equivalently, you can call `animalSchema.static()`.
animalSchema.static('findByBreed', function(breed) { return this.find({ breed }); });

const Animal = mongoose.model('Animal', animalSchema);
let animals = await Animal.findByName('fido');
animals = animals.concat(await Animal.findByBreed('Poodle'));

Не объявляйте статические методы с помощью стрелочных функций ES6 (=>). Стрелочные функции явным образом запрещают привязку this, поэтому примеры выше не будут работать из-за значения this.

Вспомогательные средства запросов

Вы также можете добавить вспомогательные функции запросов, которые похожи на методы экземпляра, но для запросов mongoose. Вспомогательные средства запросов позволяют расширить цепочечный API-интерфейс билдера запросов mongoose API-интерфейс билдера запросов.

// define a schema
const animalSchema = new Schema({ name: String, type: String },
  {
  // Assign a function to the "query" object of our animalSchema through schema options.
  // By following this approach, there is no need to create a separate TS type to define the type of the query functions.
    query: {
      byName(name) {
        return this.where({ name: new RegExp(name, 'i') });
      }
    }
  });

// Or, Assign a function to the "query" object of our animalSchema
animalSchema.query.byName = function(name) {
  return this.where({ name: new RegExp(name, 'i') });
};

const Animal = mongoose.model('Animal', animalSchema);

Animal.find().byName('fido').exec((err, animals) => {
  console.log(animals);
});

Animal.findOne().byName('fido').exec((err, animal) => {
  console.log(animal);
});

Индексы

MongoDB поддерживает вторичные индексы. В mongoose мы определяем эти индексы в нашей Schema на уровне пути уровне или на уровне schema. Определение индексов на уровне схемы необходимо при создании составных индексов.

const animalSchema = new Schema({
  name: String,
  type: String,
  tags: { type: [String], index: true } // path level
});

animalSchema.index({ name: 1, type: -1 }); // schema level

См. SchemaType#index() для других параметров индексов.

При запуске приложения Mongoose автоматически вызывает createIndex для каждого определённого индекса в вашей схеме. Mongoose будет вызывать createIndex для каждого индекса последовательно и издавать событие «index» в модели, когда все вызовы createIndex завершатся успешно или когда произошла ошибка. Хотя это удобно для разработки, рекомендуется отключить это поведение в рабочей среде, так как создание индексов может существенно повлиять на производительность производительность базы данных. Отключить это поведение можно, установив параметр autoIndex вашей схемы в значение false, или глобально для подключения, установив параметр autoIndex в значение false.

mongoose.connect('mongodb://user:pass@127.0.0.1:port/database', { autoIndex: false });
// or
mongoose.createConnection('mongodb://user:pass@127.0.0.1:port/database', { autoIndex: false });
// or
mongoose.set('autoIndex', false);
// or
animalSchema.set('autoIndex', false);
// or
new Schema({ /* ... */ }, { autoIndex: false });

Mongoose издать событие index в модели, когда индексы закончат построение или произошла ошибка.

// Will cause an error because mongodb has an _id index by default that
// is not sparse
animalSchema.index({ _id: 1 }, { sparse: true });
const Animal = mongoose.model('Animal', animalSchema);

Animal.on('index', error => {
  // "_id index cannot be sparse"
  console.log(error.message);
});

Также см. метод Model#ensureIndexes.

Виртуальные свойства

Виртуальные свойства — это свойства документа, которые вы можете получить и установить, но которые не сохраняются в MongoDB. Геттеры полезны для форматирования или объединения полей, а сеттеры полезны для декомпозиции одного значения на несколько значений для хранения.

// define a schema
const personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
});

// compile our model
const Person = mongoose.model('Person', personSchema);

// create a document
const axl = new Person({
  name: { first: 'Axl', last: 'Rose' }
});

Предположим, вы хотите вывести полное имя человека. Вы можете сделать это самостоятельно:

console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose

Но конкатенация имени и фамилии каждый раз может стать обременительной. А что, если вам нужно выполнить дополнительную обработку имени, например, удаление диакритических знаков? Геттер виртуального свойства позволяет определить свойство fullName , которое не будет сохранено в MongoDB.

// That can be done either by adding it to schema options:
const personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
}, {
  virtuals: {
    fullName: {
      get() {
        return this.name.first + ' ' + this.name.last;
      }
    }
  }
});

// Or by using the virtual method as following:
personSchema.virtual('fullName').get(function() {
  return this.name.first + ' ' + this.name.last;
});

Теперь mongoose будет вызывать вашу функцию-геттер каждый раз, когда вы обращаетесь к свойству fullName:

console.log(axl.fullName); // Axl Rose

Если вы используете toJSON() или toObject() Mongoose не будет включать виртуальные свойства по умолчанию. Передайте { virtuals: true } в toJSON() или toObject() для включения виртуальных свойств.

// Convert `doc` to a POJO, with virtuals attached
doc.toObject({ virtuals: true });

// Equivalent:
doc.toJSON({ virtuals: true });

Вышеуказанное замечание относительно toJSON() также относится к результатам вызова JSON.stringify() для документа Mongoose, потому что JSON.stringify() вызывает toJSON(). Чтобы включить виртуальные свойства в JSON.stringify() вывод, вы можете либо вызвать toObject({ virtuals: true }) на документе перед вызовом JSON.stringify(), либо задать опцию toJSON: { virtuals: true } в вашей схеме.

// Explicitly add virtuals to `JSON.stringify()` output
JSON.stringify(doc.toObject({ virtuals: true }));

// Or, to automatically attach virtuals to `JSON.stringify()` output:
const personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
}, {
  toJSON: { virtuals: true } // <-- include virtuals in `JSON.stringify()`
});

Вы также можете добавить пользовательский сеттер к своему виртуальному свойству, который позволит вам установить имя и фамилию через виртуальное свойство fullName.

// Again that can be done either by adding it to schema options:
const personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
}, {
  virtuals: {
    fullName: {
      get() {
        return this.name.first + ' ' + this.name.last;
      },
      set(v) {
        this.name.first = v.substr(0, v.indexOf(' '));
        this.name.last = v.substr(v.indexOf(' ') + 1);
      }
    }
  }
});

// Or by using the virtual method as following:
personSchema.virtual('fullName').
  get(function() {
    return this.name.first + ' ' + this.name.last;
  }).
  set(function(v) {
    this.name.first = v.substr(0, v.indexOf(' '));
    this.name.last = v.substr(v.indexOf(' ') + 1);
  });

axl.fullName = 'William Rose'; // Now `axl.name.first` is "William"

Сеттеры виртуальных свойств применяются до других проверок. Таким образом, пример выше по-прежнему будет работать, даже если поля имени first и last являются обязательными.

Только невиртуальные свойства работают как часть запросов и для выбора полей. Поскольку виртуальные свойства не хранятся в MongoDB, вы не можете запрашивать их.

Вы можете подробнее узнать о виртуальных свойствах здесь.

Псевдонимы

Псевдонимы — это особый тип виртуальных свойств, где геттер и сеттер беспрепятственно получают и устанавливают другое свойство. Это удобно для экономии пропускной способности сети, поэтому вы можете преобразовать короткое имя свойства, хранящееся в базе данных, в более длинное имя для повышения читаемости кода.

const personSchema = new Schema({
  n: {
    type: String,
    // Now accessing `name` will get you the value of `n`, and setting `name` will set the value of `n`
    alias: 'name'
  }
});

// Setting `name` will propagate to `n`
const person = new Person({ name: 'Val' });
console.log(person); // { n: 'Val' }
console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' }
console.log(person.name); // "Val"

person.name = 'Not Val';
console.log(person); // { n: 'Not Val' }

Вы также можете объявлять псевдонимы для вложенных путей. Гораздо проще использовать вложенные схемы и поддокументы, но вы также можете объявлять псевдонимы вложенных путей непосредственно, если используете полное вложенное имя пути nested.myProp в качестве псевдонима.

const childSchema = new Schema({
  n: {
    type: String,
    alias: 'name'
  }
}, { _id: false });

const parentSchema = new Schema({
  // If in a child schema, alias doesn't need to include the full nested path
  c: childSchema,
  name: {
    f: {
      type: String,
      // Alias needs to include the full nested path if declared inline
      alias: 'name.first'
    }
  }
});

Параметры

Схемы имеют несколько настраиваемых параметров, которые можно передать в конструктор или в метод set:

new Schema({ /* ... */ }, options);

// or

const schema = new Schema({ /* ... */ });
schema.set(option, value);

Допустимые параметры:

  • autoIndex
  • autoCreate
  • bufferCommands
  • bufferTimeoutMS
  • capped
  • collection
  • discriminatorKey
  • excludeIndexes
  • id
  • _id
  • minimize
  • read
  • writeConcern
  • shardKey
  • statics
  • strict
  • strictQuery
  • toJSON
  • toObject
  • typeKey
  • validateBeforeSave
  • versionKey
  • optimisticConcurrency
  • collation
  • timeseries
  • selectPopulatedPaths
  • skipVersioning
  • timestamps
  • storeSubdocValidationError
  • collectionOptions
  • methods
  • query

Параметр: autoIndex

По умолчанию, функция Mongoose's init() создаёт все индексы, определённые в схеме вашей модели, вызывая Model.createIndexes() после успешного подключения к MongoDB. Автоматическое создание индексов отлично подходит для сред разработки и тестирования. Но построение индексов также может создать значительную нагрузку на вашу базу данных в рабочей среде. Если вы хотите тщательно управлять индексами в рабочей среде, вы можете установить autoIndex в false.

const schema = new Schema({ /* ... */ }, { autoIndex: false });
const Clock = mongoose.model('Clock', schema);
Clock.ensureIndexes(callback);

Параметр autoIndex установлен по умолчанию в true. Вы можете изменить это значение по умолчанию, установив mongoose.set('autoIndex', false);

Параметр: autoCreate

Прежде чем Mongoose создаст индексы, по умолчанию он вызывает Model.createCollection() для создания базовой коллекции в MongoDB. Вызов createCollection() устанавливает значение по умолчанию для сортировки коллекции на основе параметра collation и определяет коллекцию как ограниченную, если вы установите параметр схемы capped.

Вы можете отключить это поведение, установив autoCreate в false с помощью mongoose.set('autoCreate', false). Как и autoIndex, autoCreate полезно для сред разработки и тестирования, но вы можете отключить его для рабочей среды, чтобы избежать ненужных обращений к базе данных.

К сожалению, createCollection() не может изменить существующую коллекцию. Например, если вы добавите capped: { size: 1024 } в свою схему, а существующая коллекция не ограничена, createCollection() не перезапишет существующую коллекцию. Это происходит потому, что сервер MongoDB не позволяет изменять параметры коллекции без предварительного удаления коллекции.

const schema = new Schema({ name: String }, {
  autoCreate: false,
  capped: { size: 1024 }
});
const Test = mongoose.model('Test', schema);

// No-op if collection already exists, even if the collection is not capped.
// This means that `capped` won't be applied if the 'tests' collection already exists.
await Test.createCollection();

Параметр: bufferCommands

По умолчанию Mongoose буферизует команды при отключении соединения до тех пор, пока драйвер не сможет повторно подключиться. Для отключения буферизации установите bufferCommands в false.

const schema = new Schema({ /* ... */ }, { bufferCommands: false });

Параметр схемы bufferCommands переопределяет глобальный параметр bufferCommands.

mongoose.set('bufferCommands', true);
// Schema option below overrides the above, if the schema option is set.
const schema = new Schema({ /* ... */ }, { bufferCommands: false });

Параметр: bufferTimeoutMS

Если bufferCommands включен, этот параметр задаёт максимальное время ожидания буферизации Mongoose перед выводом ошибки. Если не указано, Mongoose будет использовать 10000 (10 секунд).

// If an operation is buffered for more than 1 second, throw an error.
const schema = new Schema({ /* ... */ }, { bufferTimeoutMS: 1000 });

Параметр: capped

Mongoose поддерживает ограниченные коллекции MongoDB. Чтобы указать, что базовая коллекция MongoDB должна быть capped, установите параметр capped в максимальный размер коллекции в байтах.

new Schema({ /* ... */ }, { capped: 1024 });

Параметр capped также можно установить в объект, если вы хотите передать дополнительные параметры, такие как max. В этом случае вы должны явно указать параметр size, который является обязательным.

new Schema({ /* ... */ }, { capped: { size: 1024, max: 1000, autoIndexId: true } });

Параметр: collection

По умолчанию Mongoose создаёт имя коллекции, передавая имя модели в метод utils.toCollectionName. Этот метод делает имя множественным. Установите этот параметр, если вам нужно другое имя для вашей коллекции.

const dataSchema = new Schema({ /* ... */ }, { collection: 'data' });

Параметр: discriminatorKey

Когда вы определяете дискриминатор, Mongoose добавляет путь в вашу схему, который хранит, к какому дискриминатору относится документ. По умолчанию Mongoose добавляет путь __t, но вы можете установить discriminatorKey для переопределения этого значения по умолчанию.

const baseSchema = new Schema({}, { discriminatorKey: 'type' });
const BaseModel = mongoose.model('Test', baseSchema);

const personSchema = new Schema({ name: String });
const PersonModel = BaseModel.discriminator('Person', personSchema);

const doc = new PersonModel({ name: 'James T. Kirk' });
// Without `discriminatorKey`, Mongoose would store the discriminator
// key in `__t` instead of `type`
doc.type; // 'Person'

Параметр: excludeIndexes

Когда excludeIndexes равен true, Mongoose не будет создавать индексы из заданной схемы поддокумента. Этот параметр работает только тогда, когда схема используется в пути поддокумента или пути массива документов. Mongoose игнорирует этот параметр, если он установлен в схеме верхнего уровня для модели. По умолчанию false.

const childSchema1 = Schema({
  name: { type: String, index: true }
});

const childSchema2 = Schema({
  name: { type: String, index: true }
}, { excludeIndexes: true });

// Mongoose will create an index on `child1.name`, but **not** `child2.name`, because `excludeIndexes`
// is true on `childSchema2`
const User = new Schema({
  name: { type: String, index: true },
  child1: childSchema1,
  child2: childSchema2
});

Параметр: id

Mongoose по умолчанию назначает каждой вашей схеме виртуальный getter id, который возвращает поле _id документа, преобразованное в строку, или, в случае ObjectIds, его шестнадцатеричную строку. Если вы не хотите, чтобы в вашу схему добавлялся getter id, вы можете отключить его, передав этот параметр при создании схемы.

// default behavior
const schema = new Schema({ name: String });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'

// disabled id
const schema = new Schema({ name: String }, { id: false });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p.id); // undefined

Параметр: _id

Mongoose по умолчанию назначает каждому из ваших схем поле _id при условии, что оно не было передано в конструктор Schema. Присвоенный тип — ObjectId, чтобы соответствовать поведению по умолчанию MongoDB. Если вы совсем не хотите, чтобы _id добавлялся в вашу схему, вы можете отключить его, используя этот параметр.

Вы можете только использовать этот параметр для поддокументов. Mongoose не может сохранить документ без его id, поэтому вы получите ошибку, если попытаетесь сохранить документ без _id.

// default behavior
const schema = new Schema({ name: String });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' }

// disabled _id
const childSchema = new Schema({ name: String }, { _id: false });
const parentSchema = new Schema({ children: [childSchema] });

const Model = mongoose.model('Model', parentSchema);

Model.create({ children: [{ name: 'Luke' }] }, (error, doc) => {
  // doc.children[0]._id will be undefined
});

Параметр: minimize

Mongoose по умолчанию «минимизирует» схемы, удаляя пустые объекты.

const schema = new Schema({ name: String, inventory: {} });
const Character = mongoose.model('Character', schema);

// will store `inventory` field if it is not empty
const frodo = new Character({ name: 'Frodo', inventory: { ringOfPower: 1 } });
await frodo.save();
let doc = await Character.findOne({ name: 'Frodo' }).lean();
doc.inventory; // { ringOfPower: 1 }

// will not store `inventory` field if it is empty
const sam = new Character({ name: 'Sam', inventory: {} });
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // undefined

Это поведение можно переопределить, установив параметр minimize в false. В таком случае будут храниться пустые объекты.

const schema = new Schema({ name: String, inventory: {} }, { minimize: false });
const Character = mongoose.model('Character', schema);

// will store `inventory` if empty
const sam = new Character({ name: 'Sam', inventory: {} });
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // {}

Для проверки того, пуст ли объект, можно использовать вспомогательную функцию $isEmpty():

const sam = new Character({ name: 'Sam', inventory: {} });
sam.$isEmpty('inventory'); // true

sam.inventory.barrowBlade = 1;
sam.$isEmpty('inventory'); // false

Параметр: read

Позволяет устанавливать параметры query#read на уровне схемы, предоставляя нам способ применения значений по умолчанию для ReadPreferences ко всем запросам, полученным от модели.

const schema = new Schema({ /* ... */ }, { read: 'primary' });            // also aliased as 'p'
const schema = new Schema({ /* ... */ }, { read: 'primaryPreferred' });   // aliased as 'pp'
const schema = new Schema({ /* ... */ }, { read: 'secondary' });          // aliased as 's'
const schema = new Schema({ /* ... */ }, { read: 'secondaryPreferred' }); // aliased as 'sp'
const schema = new Schema({ /* ... */ }, { read: 'nearest' });            // aliased as 'n'

Также допускаются псевдонимы каждого предпочтения, так что вместо необходимости вводить «secondaryPreferred» и опасаться ошибок в написании, мы можем просто передать «sp».

Параметр read также позволяет указать *наборы тегов*. Это позволяет *драйверу* (https://github.com/mongodb/node-mongodb-native/) определять, с какими узлами репликации он должен пытаться читать. Дополнительную информацию о наборах тегов см. здесь и здесь.

ПРИМЕЧАНИЕ: вы также можете указать параметр стратегии предпочтения чтения драйвера (стратегия) при подключении:

// pings the replset members periodically to track network latency
const options = { replset: { strategy: 'ping' } };
mongoose.connect(uri, options);

const schema = new Schema({ /* ... */ }, { read: ['nearest', { disk: 'ssd' }] });
mongoose.model('JellyBean', schema);

Параметр: writeConcern

Позволяет установить write concern на уровне схемы.

const schema = new Schema({ name: String }, {
  writeConcern: {
    w: 'majority',
    j: true,
    wtimeout: 1000
  }
});

Параметр: shardKey

Параметр shardKey используется, когда у нас есть фрагментированная архитектура MongoDB. Каждой фрагментированной коллекции присваивается ключ фрагментации, который должен быть присутствовать во всех операциях вставки/обновления. Нам просто нужно установить этот параметр схемы на тот же ключ фрагментации, и всё будет готово.

new Schema({ /* ... */ }, { shardKey: { tag: 1, name: 1 } });

Обратите внимание, что Mongoose не отправляет команду shardcollection за вас. Вам нужно настроить свои фрагменты самостоятельно.

Параметр: strict

Параметр strict (включён по умолчанию) гарантирует, что значения, переданные в конструктор нашей модели, которые не были указаны в нашей схеме, не будут сохранены в базе данных.

const thingSchema = new Schema({ /* ... */ })
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is not saved to the db

// set to false..
const thingSchema = new Schema({ /* ... */ }, { strict: false });
const thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is now saved to the db!!

Это также влияет на использование doc.set() для установки значения свойства.

const thingSchema = new Schema({ /* ... */ });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing;
thing.set('iAmNotInTheSchema', true);
thing.save(); // iAmNotInTheSchema is not saved to the db

Это значение можно переопределить на уровне экземпляра модели, передав второй булевый аргумент:

const Thing = mongoose.model('Thing');
const thing = new Thing(doc, true);  // enables strict mode
const thing = new Thing(doc, false); // disables strict mode

Параметр strict также может быть установлен в "throw", что приведет к генерации ошибок вместо удаления неверных данных.

ПРИМЕЧАНИЕ: Любое свойство/значение, заданное на экземпляре, которое не существует в вашей схеме, всегда игнорируется, независимо от параметра схемы.

const thingSchema = new Schema({ /* ... */ });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing;
thing.iAmNotInTheSchema = true;
thing.save(); // iAmNotInTheSchema is never saved to the db

Параметр: strictQuery

Mongoose поддерживает отдельный параметр strictQuery для предотвращения строгого режима для фильтров запросов. Это связано с тем, что пустые фильтры запросов приводят к тому, что Mongoose возвращает все документы в модели, что может вызвать проблемы.

const mySchema = new Schema({ field: Number }, { strict: true });
const MyModel = mongoose.model('Test', mySchema);
// Mongoose will filter out `notInSchema: 1` because `strict: true`, meaning this query will return
// _all_ documents in the 'tests' collection
MyModel.find({ notInSchema: 1 });

Параметр strict применяется и к обновлениям. Параметр strictQuery — только для фильтров запросов.

// Mongoose will strip out `notInSchema` from the update if `strict` is
// not `false`
MyModel.updateMany({}, { $set: { notInSchema: 1 } });

У Mongoose есть отдельный параметр strictQuery для включения/отключения строгого режима для параметра filter в запросах.

const mySchema = new Schema({ field: Number }, {
  strict: true,
  strictQuery: false // Turn off strict mode for query filters
});
const MyModel = mongoose.model('Test', mySchema);
// Mongoose will not strip out `notInSchema: 1` because `strictQuery` is false
MyModel.find({ notInSchema: 1 });

В целом, мы не рекомендуем передавать пользовательские объекты в качестве фильтров запросов:

// Don't do this!
const docs = await MyModel.find(req.query);

// Do this instead:
const docs = await MyModel.find({ name: req.query.name, age: req.query.age }).setOptions({ sanitizeFilter: true });

В Mongoose 7, strictQuery установлено по умолчанию в false. Однако вы можете переопределить это поведение глобально:

// Set `strictQuery` to `true` to omit unknown fields in queries.
mongoose.set('strictQuery', true);

Параметр: toJSON

Абсолютно такой же, как параметр toObject, но применяется только при вызове метода toJSON документа.

const schema = new Schema({ name: String });
schema.path('name').get(function(v) {
  return v + ' is my name';
});
schema.set('toJSON', { getters: true, virtuals: false });
const M = mongoose.model('Person', schema);
const m = new M({ name: 'Max Headroom' });
console.log(m.toObject()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom' }
console.log(m.toJSON()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
// since we know toJSON is called whenever a js object is stringified:
console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }

Чтобы ознакомиться со всеми доступными параметрами toJSON/toObject, прочитайте эту страницу.

Параметр: toObject

Документы имеют метод toObject, который преобразует документ mongoose в обычный JavaScript-объект. Этот метод принимает несколько опций. Вместо того, чтобы применять эти опции к каждому документу, мы можем объявить опции на уровне схемы и сделать их применением ко всем документам схемы по умолчанию.

Чтобы все виртуальные поля отображались в вашем console.log выводе, установите опцию toObject в значение { getters: true }.

const schema = new Schema({ name: String });
schema.path('name').get(function(v) {
  return v + ' is my name';
});
schema.set('toObject', { getters: true });
const M = mongoose.model('Person', schema);
const m = new M({ name: 'Max Headroom' });
console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }

Чтобы увидеть все доступные toObject опции, прочтите эту страницу.

опция: typeKey

По умолчанию, если у вас есть объект с ключом 'type' в вашей схеме, mongoose будет интерпретировать его как объявление типа.

// Mongoose interprets this as 'loc is a String'
const schema = new Schema({ loc: { type: String, coordinates: [Number] } });

Однако, для приложений, таких как geoJSON, свойство 'type' важно. Если вы хотите управлять тем, какой ключ mongoose использует для поиска объявлений типов, установите опцию схемы 'typeKey'.

const schema = new Schema({
  // Mongoose interprets this as 'loc is an object with 2 keys, type and coordinates'
  loc: { type: String, coordinates: [Number] },
  // Mongoose interprets this as 'name is a String'
  name: { $type: String }
}, { typeKey: '$type' }); // A '$type' key means this object is a type declaration

опция: validateBeforeSave

По умолчанию документы автоматически проверяются перед сохранением в базе данных. Это предотвращает сохранение невалидного документа. Если вы хотите вручную обрабатывать валидацию и иметь возможность сохранять объекты, которые не проходят валидацию, вы можете установить validateBeforeSave в значение false.

const schema = new Schema({ name: String });
schema.set('validateBeforeSave', false);
schema.path('name').validate(function(value) {
  return value != null;
});
const M = mongoose.model('Person', schema);
const m = new M({ name: null });
m.validate(function(err) {
  console.log(err); // Will tell you that null is not allowed.
});
m.save(); // Succeeds despite being invalid

опция: versionKey

Ключ versionKey устанавливается для каждого документа при его первом создании Mongoose. Значение этого ключа содержит внутреннюю ревизию документа. Опция versionKey — это строка, представляющая путь для использования при версии. По умолчанию используется __v. Если это конфликтует с вашим приложением, вы можете настроить это следующим образом:

const schema = new Schema({ name: 'string' });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
await thing.save(); // { __v: 0, name: 'mongoose v3' }

// customized versionKey
new Schema({ /* ... */ }, { versionKey: '_somethingElse' })
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
thing.save(); // { _somethingElse: 0, name: 'mongoose v3' }

Обратите внимание, что по умолчанию версия Mongoose не является полным решением оптимистической блокировки. По умолчанию версия Mongoose работает только с массивами, как показано ниже.

// 2 copies of the same document
const doc1 = await Model.findOne({ _id });
const doc2 = await Model.findOne({ _id });

// Delete first 3 comments from `doc1`
doc1.comments.splice(0, 3);
await doc1.save();

// The below `save()` will throw a VersionError, because you're trying to
// modify the comment at index 1, and the above `splice()` removed that
// comment.
doc2.set('comments.1.body', 'new comment');
await doc2.save();

Если вам нужна поддержка оптимистической блокировки для save(), вы можете установить опцию optimisticConcurrency

Версионирование документов также можно отключить, установив versionKey в значение false. НЕ отключайте версионирование, если вы не точно не знаете, что делаете.

new Schema({ /* ... */ }, { versionKey: false });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'no versioning please' });
thing.save(); // { name: 'no versioning please' }

Mongoose только обновляет ключ версии, когда вы используете save(). Если вы используете update(), findOneAndUpdate(), и т.д., Mongoose не будет обновлять ключ версии. В качестве обходного решения вы можете использовать приведенный ниже middleware.

schema.pre('findOneAndUpdate', function() {
  const update = this.getUpdate();
  if (update.__v != null) {
    delete update.__v;
  }
  const keys = ['$set', '$setOnInsert'];
  for (const key of keys) {
    if (update[key] != null && update[key].__v != null) {
      delete update[key].__v;
      if (Object.keys(update[key]).length === 0) {
        delete update[key];
      }
    }
  }
  update.$inc = update.$inc || {};
  update.$inc.__v = 1;
});

опция: optimisticConcurrency

Оптимистическая блокировка — это стратегия, гарантирующая, что документ, который вы обновляете, не изменился между его загрузкой с помощью find() или findOne() и его обновлением с помощью save().

Например, предположим, что у вас есть модель House, которая содержит список photos, и поле status, которое представляет собой то, отображается ли этот дом в результатах поиска. Допустим, дом со статусом 'APPROVED' должен иметь как минимум два photos. Вы можете реализовать логику утверждения документа дома, как показано ниже:

async function markApproved(id) {
  const house = await House.findOne({ _id });
  if (house.photos.length < 2) {
    throw new Error('House must have at least two photos!');
  }

  house.status = 'APPROVED';
  await house.save();
}

Функция markApproved() выглядит правильно изолированно, но может быть проблема: а что, если другая функция удалит фотографии дома между вызовом findOne() и вызовом save()? Например, приведенный ниже код будет успешен:

const house = await House.findOne({ _id });
if (house.photos.length < 2) {
  throw new Error('House must have at least two photos!');
}

const house2 = await House.findOne({ _id });
house2.photos = [];
await house2.save();

// Marks the house as 'APPROVED' even though it has 0 photos!
house.status = 'APPROVED';
await house.save();

Если вы установите опцию optimisticConcurrency в схеме модели House, вышеупомянутый сценарий выдаст ошибку.

const House = mongoose.model('House', Schema({
  status: String,
  photos: [String]
}, { optimisticConcurrency: true }));

const house = await House.findOne({ _id });
if (house.photos.length < 2) {
  throw new Error('House must have at least two photos!');
}

const house2 = await House.findOne({ _id });
house2.photos = [];
await house2.save();

// Throws 'VersionError: No matching document found for id "..." version 0'
house.status = 'APPROVED';
await house.save();

опция: collation

Устанавливает по умолчанию сортировку для каждого запроса и агрегации. Вот краткий обзор сортировок для начинающих.

const schema = new Schema({
  name: String
}, { collation: { locale: 'en_US', strength: 1 } });

const MyModel = db.model('MyModel', schema);

MyModel.create([{ name: 'val' }, { name: 'Val' }]).
  then(() => {
    return MyModel.find({ name: 'val' });
  }).
  then((docs) => {
    // `docs` will contain both docs, because `strength: 1` means
    // MongoDB will ignore case when matching.
  });

опция: timeseries

Если вы установите опцию timeseries в схеме, Mongoose создаст коллекцию временных рядов для любой модели, созданной на основе этой схемы.

const schema = Schema({ name: String, timestamp: Date, metadata: Object }, {
  timeseries: {
    timeField: 'timestamp',
    metaField: 'metadata',
    granularity: 'hours'
  },
  autoCreate: false,
  expireAfterSeconds: 86400
});

// `Test` collection will be a timeseries collection
const Test = db.model('Test', schema);

опция: skipVersioning

skipVersioning позволяет исключить пути из версионирования (т.е. внутренняя ревизия не будет инкрементирована, даже если эти пути обновлены). Не делайте этого, если вы не знаете, что делаете. Для поддокументов включите это в родительский документ с полным путем.

new Schema({ /* ... */ }, { skipVersioning: { dontVersionMe: true } });
thing.dontVersionMe.push('hey');
thing.save(); // version is not incremented

опция: timestamps

Опция timestamps сообщает Mongoose назначить поля createdAt и updatedAt вашей схеме. Присвоенный тип — Дата.

По умолчанию имена полей — createdAt и updatedAt. Настройте имена полей, установив timestamps.createdAt и timestamps.updatedAt.

Вот как работает timestamps внутри:

  • Если вы создаёте новый документ, mongoose просто устанавливает createdAt, и updatedAt в момент создания.
  • Если вы обновляете документ, mongoose добавит updatedAt в объект $set.
  • Если вы установите upsert: true в операции обновления, mongoose воспользуется оператором $setOnInsert, чтобы добавить createdAt в документ в случае, если операция upsert привела к новому вставленному документу.
const thingSchema = new Schema({ /* ... */ }, { timestamps: { createdAt: 'created_at' } });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing();
await thing.save(); // `created_at` & `updatedAt` will be included

// With updates, Mongoose will add `updatedAt` to `$set`
await Thing.updateOne({}, { $set: { name: 'Test' } });

// If you set upsert: true, Mongoose will add `created_at` to `$setOnInsert` as well
await Thing.findOneAndUpdate({}, { $set: { name: 'Test2' } });

// Mongoose also adds timestamps to bulkWrite() operations
// See https://mongoosejs.com/docs/api/model.html#model_Model-bulkWrite
await Thing.bulkWrite([
  {
    insertOne: {
      document: {
        name: 'Jean-Luc Picard',
        ship: 'USS Stargazer'
      // Mongoose will add `created_at` and `updatedAt`
      }
    }
  },
  {
    updateOne: {
      filter: { name: 'Jean-Luc Picard' },
      update: {
        $set: {
          ship: 'USS Enterprise'
        // Mongoose will add `updatedAt`
        }
      }
    }
  }
]);

По умолчанию Mongoose использует new Date() для получения текущего времени. Если вы хотите перезаписать функцию, которую Mongoose использует для получения текущего времени, вы можете установить опцию timestamps.currentTime. Mongoose будет вызывать функцию timestamps.currentTime всякий раз, когда ему нужно получить текущее время.

const schema = Schema({
  createdAt: Number,
  updatedAt: Number,
  name: String
}, {
  // Make Mongoose use Unix time (seconds since Jan 1, 1970)
  timestamps: { currentTime: () => Math.floor(Date.now() / 1000) }
});

опция: pluginTags

Mongoose поддерживает определение глобальных плагинов, плагинов, которые применяются ко всем схемам.

// Add a `meta` property to all schemas
mongoose.plugin(function myPlugin(schema) {
  schema.add({ meta: {} });
});

Иногда вам может потребоваться применить определенный плагин только к некоторым схемам. В этом случае вы можете добавить pluginTags к схеме:

const schema1 = new Schema({
  name: String
}, { pluginTags: ['useMetaPlugin'] });

const schema2 = new Schema({
  name: String
});

Если вы вызываете plugin() с опцией tags, Mongoose применит этот плагин только к схемам, имеющим соответствующую запись в pluginTags.

// Add a `meta` property to all schemas
mongoose.plugin(function myPlugin(schema) {
  schema.add({ meta: {} });
}, { tags: ['useMetaPlugin'] });

опция: selectPopulatedPaths

По умолчанию Mongoose автоматически select() все заполненные пути, если вы их явно не исключили.

const bookSchema = new Schema({
  title: 'String',
  author: { type: 'ObjectId', ref: 'Person' }
});
const Book = mongoose.model('Book', bookSchema);

// By default, Mongoose will add `author` to the below `select()`.
await Book.find().select('title').populate('author');

// In other words, the below query is equivalent to the above
await Book.find().select('title author').populate('author');

Чтобы отказаться от выбора заполненных полей по умолчанию, установите selectPopulatedPaths в значение false в вашей схеме.

const bookSchema = new Schema({
  title: 'String',
  author: { type: 'ObjectId', ref: 'Person' }
}, { selectPopulatedPaths: false });
const Book = mongoose.model('Book', bookSchema);

// Because `selectPopulatedPaths` is false, the below doc will **not**
// contain an `author` property.
const doc = await Book.findOne().select('title').populate('author');

опция: storeSubdocValidationError

По историческим причинам, когда возникает ошибка валидации в подпути одного вложенного схемы, Mongoose записывает, что была ошибка валидации в пути отдельного вложенного схемы. Например:

const childSchema = new Schema({ name: { type: String, required: true } });
const parentSchema = new Schema({ child: childSchema });

const Parent = mongoose.model('Parent', parentSchema);

// Will contain an error for both 'child.name' _and_ 'child'
new Parent({ child: {} }).validateSync().errors;

Установите storeSubdocValidationError в значение false в дочерней схеме, чтобы Mongoose сообщал только об ошибке родительского элемента.

const childSchema = new Schema({
  name: { type: String, required: true }
}, { storeSubdocValidationError: false }); // <-- set on the child schema
const parentSchema = new Schema({ child: childSchema });

const Parent = mongoose.model('Parent', parentSchema);

// Will only contain an error for 'child.name'
new Parent({ child: {} }).validateSync().errors;

опция: collectionOptions

Опции, такие как collation и capped, влияют на опции, которые Mongoose передает MongoDB при создании новой коллекции. Схемы Mongoose поддерживают большинство MongoDB createCollection() опций, но не все. Вы можете использовать опцию collectionOptions для установки любых опций createCollection(); Mongoose будет использовать collectionOptions в качестве значений по умолчанию при вызове createCollection() для вашей схемы.

const schema = new Schema({ name: String }, {
  autoCreate: false,
  collectionOptions: {
    capped: true,
    max: 1000
  }
});
const Test = mongoose.model('Test', schema);

// Equivalent to `createCollection({ capped: true, max: 1000 })`
await Test.createCollection();

С ES6 классами

Схемы имеют метод loadClass(), который вы можете использовать для создания схемы Mongoose из ES6 класса:

  • Методы ES6 класса становятся методами Mongoose
  • Статические методы ES6 класса становятся статиками Mongoose
  • Геттеры и сеттеры ES6 класса становятся виртуальными полями Mongoose

Вот пример использования loadClass() для создания схемы из ES6 класса:

class MyClass {
  myMethod() { return 42; }
  static myStatic() { return 42; }
  get myVirtual() { return 42; }
}

const schema = new mongoose.Schema();
schema.loadClass(MyClass);

console.log(schema.methods); // { myMethod: [Function: myMethod] }
console.log(schema.statics); // { myStatic: [Function: myStatic] }
console.log(schema.virtuals); // { myVirtual: VirtualType { ... } }

Подключаемые

Схемы также подключаемые, что позволяет нам упаковать переиспользуемые функции в плагины, которые можно обмениваться в сообществе или просто между вашими проектами.

Дополнительная информация

Вот альтернативное введение в схемы Mongoose.

Чтобы максимально эффективно использовать MongoDB, вам необходимо освоить основы проектирования схем MongoDB. Проектирование схем SQL (третья нормальная форма) было разработано, чтобы минимизировать затраты на хранение, в то время как проектирование схем MongoDB направлено на максимальную скорость выполнения распространённых запросов. Блог-сериал 6 правил проектирования схем MongoDB — отличный ресурс для изучения основных правил создания быстро работающих запросов.

Пользователи, желающие освоить проектирование схем MongoDB в Node.js, должны изучить Книгу о проектировании схем MongoDB Кристиана Квальхейма, первоначального автора драйвера MongoDB для Node.js. Эта книга показывает, как реализовать высокопроизводительные схемы для широкого круга задач, включая электронную коммерцию, вики-сайты и бронирование встреч.

Далее

Теперь, когда мы разобрались с Schemas, давайте посмотрим на SchemaTypes.

© 2010 LearnBoost
Licensed under the MIT License.
https://mongoosejs.com/docs/guide.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API