Mongoose: Вызов заполнения встроенного документа встроенного документа

Созданный на 8 нояб. 2011  ·  93Комментарии  ·  Источник: Automattic/mongoose

Привет,
У меня есть следующие схемы (упрощенные для выпуска)

Пользователь = новая схема
Логин: Строка

A = новая схема
наб: [B]
комментарии: [Комментарии]

//и следующие встроенные схемы

B = новая схема
комментарии: [Комментарии]

Комментарии = новая схема
создатель:
тип: идентификатор объекта
ссылка: «Пользователь»
сообщение: Строка

Теперь, пока заполняется работа над A
например A.find().populate('comments.creator')

он не работает с двойным вложенным встроенным документом
например A.find().populate('emb.comments.creator')

Есть идеи?

new feature

Самый полезный комментарий

Я знаю, что это старый поток, но я только что создал плагин, который позволяет очень легко заполнять модели на любом уровне глубины. Я публикую здесь, если кому-то интересно: https://github.com/buunguyen/mongoose-deep-populate.

Использование очень простое, например:

post.deepPopulate('votes.user, comments.user.followers, ...', cb);
Post.deepPopulate(posts, 'votes.user, comments.user.followers', cb);

Пожалуйста, ознакомьтесь с репозиторием плагинов для получения дополнительной информации.

Все 93 Комментарий

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

Привет энеко,

Как ты это делаешь?

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

Некоторый код был бы потрясающим.

Спасибо,
Павел

@eneko неважно , просто нужен .toObject() ;)

На самом деле Пол, я понял, что populate() работает с запросами, которые возвращают несколько элементов, поэтому нет необходимости перебирать дочерние объекты. Но да, вы правы, вам нужно вызвать .toObject, если вы хотите, чтобы свойства сохранялись.

Вот пример:

// Ideally should be Parent.findOne().populate('children').populate('children.grandchildren').run();

function loadParentWithChildrenAndGrandChildren(parentId) {

  // Load parent without children references (children is array of ObjectId)
  Parent.findOne({ id: parentId }, { children: 0 }, function(err, parent) {
    if (err || !parent) return next(new Error("Parent not found: " + parentId));

    // Load children for this parent, populating grandchildren (no need to load parent reference)
    Children.find({ parent: parent._id }, { parent: 0 })
      .populate('grandchildren', [], { })
      .run(function(err, children) {
        if (err) return next(new Error("Could not load children: " + parentId));

        var result = parent.toObject();
        result.children = children;
        next(null, result);
      });
  });

}

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

Я заканчиваю тем, что делаю что-то вроде этого.

var viewWithId_forDisplay = function(id, callback) {
if ( !id || typeof id === 'undefined' || id.toString().match(app_utils.emptyReg) ) {
    throw new Error('Bad user ID.');
}

View.findById(id)
.run(function(err, obj) {
    if ( err || !obj || !obj.subviews || !obj.subviews.length ) return callback(err, obj);

    obj = obj.toObject();

    // recursive subview fetch
    async.map( obj.subviews, viewWithId_forDisplay, function(err, results) {
        obj.subviews = results;

        return callback(err, obj);
    } );

})
}

Теперь это работает в последней версии мангуста?

Поэтому я думаю, что это проблема, с которой я столкнулся. Каждый объект Comment имеет встроенный объект User. Комментарии встраиваются в виде массива в Activity. Запрос действий (как показано ниже) не заполняет объект пользователя для каждого комментария.

var get_activities = function (callback) {
  var _this = this
  Activity.find({}, [], {sort:{ _id: -1 }})
  .populate('user')
  .populate('comments')
  .run(function (error, activities) {
    callback(error, activities)
  })
}

var User = new Schema({
    id: { type: String, required: true, lowercase: true, index: { unique: true } }
  , email_address: { type: String, required: true, index: true }
  , name: { type: String, required: true }
  , first_name: { type: String,  required: true } 
  , last_name: { type: String, required: true }
  , user_name : { type: String, required: true, lowercase: true, index: { unique: true } }
  , avatar_url: { type: String, required: true }
  , bio: String
  , following: [{ type: Schema.ObjectId, ref: 'User', index: { unique: true } }]
  , followers: [{ type: Schema.ObjectId, ref: 'User', index: { unique: true } }]
})

var Activity = new Schema({
    id: { type: Number, required: true, index: { unique: true } }
  , user: { type: Schema.ObjectId, ref: 'User', required: true }
  , recipients: [{ type: String, index: { unique: true } }]
  , type: String  
  , body: { type: String, required: true }
  , timestamp: { type: Date, required: true }
  , likes: [{ type: Schema.ObjectId, ref: 'User', index: { unique: true } }]
  , comments: [{ type: Schema.ObjectId, ref: 'Comment' }]
})

var Comment = new Schema({
    id: { type: String, required: true, index: { unique: true } }
  , timestamp: { type: Date, required: true }
  , body: { type: String, required: true }
  , user: { type: Schema.ObjectId, ref: 'User', required: true } 
}) 

Есть ли какой-либо план по заполнению для поддержки вложенных путей для обработки подобных случаев?

да, мы хотели бы в какой-то момент. это может быстро стать проблемой производительности, но да, мы должны это поддерживать.

Для вложенных объектов мне нужно перебирать их вручную, загружая их, что больше похоже на проблему с производительностью и загромождает код моего сервера. Так что я +1 за облегчение моей работы ;-)

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

Я был бы за что-то вроде этого;

Parent.findById(1).populate('child').run(function (err, parent) {
  parent.child.populate('grandchild', function (err) {
    console.log(parent.child.grandchild)
  })
})

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

@Qard , ты читаешь мои мысли.

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

Было ли уже реализовано что-то вроде https://github.com/LearnBoost/mongoose/issues/601#issuecomment -3258317?

Мне интересно то же самое. Я пытаюсь выяснить, как заполнить встроенные ссылки на дочерние документы. @dbounds У меня есть аналогичные модели. Вы решили эту проблему: https://github.com/LearnBoost/mongoose/issues/601#issuecomment -3088564. Я не могу найти отношения мангуста

Информация в этом потоке была очень полезна для понимания текущего состояния .populate(), текущего ограничения, связанного с вложенной популяцией внуков, и того, как .toObject() в сочетании с ручным поиском может быть вариантом обхода этого.

Мне интересно, есть ли метод, который мы могли бы использовать, который не требует .toObject(). Я спрашиваю b\c после того, как мы заполним нашу модель и внуков, мы все еще хотели бы иметь документ и правильные типы, чтобы мы могли использовать такие функции, как parent.childarray.id(x), или изменять значения и вызывать .save().

Любая помощь здесь будет принята с благодарностью.

примечание: мы пытались использовать parent.set('child.grandchild', value), однако это, по-видимому, вызывает некоторые проблемы с целостностью документа, и мы больше не можем считывать значения из этого пути (ошибка - Invalid ObjectId при попытке прочитать parent.child или parent.child.grandchild).

@aeckmann Есть идеи по этому поводу?

был занят. хочу в финале 3.0

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

Великая прелесть! Я воспользуюсь этой функцией, как только она станет доступна.

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

Определенно заинтересован! Я не уверен, что я тот человек, который должен делать исправления, но любые идеи о том, как обойти эту проблему прямо сейчас, очень приветствуются!

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

Будет ли это добавлено в версии 3.0?

@hackfrag да

Это в 3.0 или еще добавляют? Если это возможно, у нас есть пример, пожалуйста :)

его нет в 3.0. он будет добавлен в предстоящем второстепенном выпуске.

Мне невероятно трудно взломать эту функцию. Есть ли у вас какие-либо оценки относительно того, когда этот релиз может быть доступен? Я брошу свой уродливый код и буду работать над другими областями своего приложения, если в ближайшем будущем появится настоящая поддержка :). И могу ли я добавить, спасибо за потрясающую библиотеку.

Стаунс, мой хак работает хорошо, и теперь я чувствую себя достаточно хорошо, чтобы выпустить его. Я свяжусь с вами в течение 24 часов — сначала нужно протестировать Mongoose 3

-- Джошуа Гросс
Кристиан / Консультант по веб-разработке / Кандидат компьютерных наук, UW-Madison 2013
414-377-1041 / http://www.joshisgross.com

13 августа 2012 г., в 18:56, [email protected] написал:

Мне невероятно трудно взломать эту функцию. Есть ли у вас какие-либо оценки относительно того, когда этот релиз может быть доступен? Я откажусь от своего уродливого кода и буду работать над другими областями своего приложения, если настоящая поддержка появится в ближайшем будущем :)


Ответьте на это письмо напрямую или просмотрите его на GitHub.

@JoshuaGross, эй, можешь вкратце изложить свой хак? мой код становится слишком грязным и уродливым без этой функции...

Привет @madhums , @stowns , @aheckmann , @farhanpatel , @hackfrag , @jsalonen и т. д. Мой хак (протестирован только на Mongoose 2.7) находится здесь: https://github.com/JoshuaGross/mongoose-subpopulate

Надеюсь, это полезно.

ВАУ Джошуа! Бесконечно благодарен!

Я обязательно посмотрю это прямо сейчас!

спасибо @JoshuaGross , проверю сегодня позже

Что должно было случиться, если ни в одном из документов нет поля для «заполнения» запроса на поиск? Были бы ошибки? Технически ошибки не будет, но будет ли показан этот документ(ы)?

Работают ли уже вложенные вызовы populate()? Похоже, была какая-то активность, но я не смог определить, как это сделать.

У меня есть:

List.findById(req.params.id).populate('items').populate('items.user').exec(fn);

пользователь глубоко вложен... можно ли заполнить?

@aheckmann , вы можете это прокомментировать? Я уже давно использую mongoose-subpopulate в производстве для создания подзаселений.

mongoose-subpopulate не работает с экспресс 3.

У меня есть филиал начал, но был довольно занят. Находится в пути.

В среду, 3 октября 2012 г., 23:37, Энтони Эттингер
уведомления@github.com написал :

mongoose-subpopulate не работает с экспресс 3.


Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps://github.com/LearnBoost/mongoose/issues/601#issuecomment -9131991.

Аарон
@ааронхекманн https://twitter.com/#!/ааронхекманн

@chovy ты имеешь в виду мангуста 3? Я использую mongoose-subpopulate с экспрессом 3.

Да, мангуст 3 я имел в виду.

@aheckmann - С нетерпением жду вашей ветки!

Я не мог понять, как использовать mongoose-subpopulate

+1 за эту функцию

+1 за это

Есть ли способ заполнить дочерний массив массива?

var notifications = new Schema({
    _id             : { type : ObjectId }
    , from          : { type : ObjectId, ref: 'user' }
    , status                : { type : Number, default : 1 }
    , created_at            : { type : Date }
    , updated_at            : { type : Date }
});

var applications = new Schema({
    _id                     : { type : ObjectId, required : true, ref : 'application' }
    , notifications                 : [notifications]
});

var schema = new Schema({
    name                        : { type : String, required : true }
    , email                     : { type : String }
    , applications                      : [applications]
    , created_at                        : { type : Date }
    , updated_at                        : { type : Date }
});

var User = module.exports = mongoose.model('user', schema);

Что я хочу сделать здесь, так это получить имя отправителя уведомления

// works like a charm
User.findOne({_id:'me'}).populate('applications');

// doesn't work
User.findOne({_id:'me'}).populate('applications.notifications.from');

Или, если есть другой способ сделать другой запрос, чтобы получить все имена пользователей вручную?

вам нужно будет сделать "из" массива, как вы делаете с уведомлениями. Лучше всего получить пользователя для каждого уведомления в вашем обратном вызове.

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

+1, надеялся, что докопаюсь до сути и увижу, что это было, и я просто делал это неправильно. :(

+1, какие-нибудь оценки времени для этой функции? Это было бы безумно полезно.

@winduptoy выглядит как 3.6

+1 и здесь.

Что еще более важно, мы должны, по крайней мере, иметь возможность самостоятельно заполнять дочерние элементы без необходимости сначала вызывать toObject() для родительского объекта. Я могу понять желание сохранить согласованные типы полей после вызова set, но мангуст уже нарушает это правило с помощью функции заполнения для начала. Мы должны иметь возможность установить ссылочное поле на полный объект без того, чтобы мангуст менял его на просто идентификатор монго.

Помимо этой путаницы, люблю мангуста.

Я так жду эту функцию.

+1 Мне тоже нужна эта функция...

+1 на это будущее

+1 - с нетерпением жду этого

+1 Это было бы круто

+1 Вот дошли до программных проблем, но если поставить другой символ помогло бы, например

User.findOne({_id:'me'}).populate('applications$notifications.from');
// Arrays $
// Simple document point "."

Так что проблем не избежать... Нет?

+2

+1 Это было бы полезно.

+1 Это было бы очень хорошо с твоей стороны.

Пожалуйста, выпустите эту функцию в 2013 году.

+1 Мне это нужно прямо сейчас, это будет очень полезно.

Для тех из вас, кому это нужно прямо сейчас, вы можете проверить mongoose-subpopulate. Я использую его несколько месяцев: https://github.com/JoshuaGross/mongoose-subpopulate (я сопровождаю)

Вот план заполнения в 3.6: https://github.com/LearnBoost/mongoose/pull/1292 .

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

Думаю всенародным голосованием победило это предложение!

@aheckmann Большое спасибо!!

Также люблю lean() , так что пока это кажется действительно превосходным!

В восторге от 3.6, это хорошее исправление, спасибо!

Спасибо, мистер!

5 марта 2013 г., в 17:26, Энди Бёрк, [email protected] , написал:

В восторге от 3.6, это хорошее исправление, спасибо!


Ответьте на это письмо напрямую или просмотрите его на GitHub.

@aheckmann Отлично, спасибо!

Спасибо!!! Прекрасная работа!!!

Это потрясающе! Я считаю, что это может помочь в моем случае. Как мне поступить в этом сценарии:

var UserSchema, WineRating, WineSchema, mongoose;

UserSchema = new mongoose.Schema({
  wine_ratings: {
    type: [
      {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'WineRating'
      }
    ]
  }
});
mongoose.model("User", UserSchema);

WineRating = new mongoose.Schema({
  wine: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Wine'
  }
});
mongoose.model("WineRating", WineRating, 'wine_ratings');


WineSchema = new mongoose.Schema({
  name: String
});
mongoose.model("Wine", WineSchema);

mongoose.model("User").findById(user._id).populate('wine_ratings.wine').exec(function(err, user) {});
/*
gets exception: 
TypeError: Cannot call method 'path' of undefined
    at search (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1830:28)
    at search (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1849:22)
    at Function._getSchema (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1856:5)
    at populate (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1594:22)
    at Function.Model.populate (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/model.js:1573:5)
    at Query.findOne (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/query.js:1633:11)
    at exports.tick (/Users/flockonus/workspace/az/api/node_modules/mongoose/lib/utils.js:393:16)
*/

Здесь что-то не так? Я на "3.6.0rc0"

У меня такая же проблема: https://github.com/LearnBoost/mongoose/issues/1377

@flockonus @vovan22 Столкнулся с той же проблемой. Вот как я это решил.

Я пытался заставить post.populate("comments comments._creator") работать в нашем проекте и добился небольшого успеха со следующими настройками.

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

https://gist.github.com/joeytwiddle/6129653

Поскольку мой патч отстой, я попытался внести свой вклад, написав вместо этого тестовый пример! https://github.com/LearnBoost/мангуст/тянуть/1603

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

Да, печально известный 601. Это так задумано, по крайней мере, на данный момент. Последний выпуск Mongoose поддерживает глубокое заполнение, но _только в пределах одной схемы_.

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

https://gist.github.com/joeytwiddle/6129676

Это позволяет вам заполнять потомки одного документа любой глубины! По-прежнему требуется один дополнительный обратный вызов после того, как вы получили документ, но только один. Например:

deepPopulate(blogPost, "comments comments._creator comments._creator.blogposts", {sort:{title:-1}}, callback);

Я надеюсь, что использование doc.constructor для получения модели не вызовет проблем в будущем!

_(Спасибо Sunride и правительству Германии!)_

@joeytwiddle - большое спасибо! Оно работало завораживающе. Теперь я попытаюсь выяснить, как сделать так, чтобы он заполнялся n-глубоко.

Есть новости по этому поводу?

@aheckmann Я понимаю вашу теорию по этому поводу, но я не согласен с тем, что это анти-шаблон.

Иногда вам нужно выполнять дорогостоящие операции, это неизбежно в сложных системах, и вам действительно нужно собирать разрозненные документы вместе. Например, вы можете захотеть выполнить одну дорогостоящую операцию, когда документ ОЧЕНЬ ОЧЕНЬ редко изменяется, и кэшировать результаты для повышения производительности.

Почему вы считаете, что один уровень населения «нормальный», но более того — это признак антипаттерна?

Я знаю, что это старый поток, но я только что создал плагин, который позволяет очень легко заполнять модели на любом уровне глубины. Я публикую здесь, если кому-то интересно: https://github.com/buunguyen/mongoose-deep-populate.

Использование очень простое, например:

post.deepPopulate('votes.user, comments.user.followers, ...', cb);
Post.deepPopulate(posts, 'votes.user, comments.user.followers', cb);

Пожалуйста, ознакомьтесь с репозиторием плагинов для получения дополнительной информации.

@buunguyen отличная работа!

Не работает для меня.

модели/миссияParticipation.js

var deepPopulate = require('mongoose-deep-populate');
var mongoose = require('mongoose');
var Types = mongoose.Schema.Types;

var missionParticipationSchema = new mongoose.Schema({
        user: {
            type: String,
            default: ''
        },
        mission: {
            type: Types.ObjectId,
            ref: 'Mission'
        },
        images: [{
            type: Types.ObjectId,
            ref: 'Image'
        }]
    }, {
        toJSON: {
            getters: true,
            virtuals: true
        },
        toObject: {
            getters: true,
            virtuals: true
        }
    });

    missionParticipationSchema.plugin(deepPopulate, {
        whitelist: [
            'images',
            'mission',
            'mission.images.poster',
            'mission.images.banner'
        ]
    });

var MissionParticipation = mongoose.model('MissionParticipation', missionParticipationSchema);

module.exports = MissionParticipation;

услуги/миссияParticipationService.js

MissionParticipation.find({user: userID}).deepPopulate('mission.images.poster mission.images.banner').exec(function (err, missionParticipationsDocs) {
    // do the magic.
});

И я получаю эту ошибку на консоли

TypeError: Object #<Query> has no method 'deepPopulate'

@joaom182
Я еще не использовал deepPopulate, но записывая документы на http://npm.taobao.org/package/mongoose-deep-populate , я бы предположил, что правильный вызов должен быть:

MissionParticipation.find({user: userID}, function (err, participations) {
  MissionParticipation.deepPopulate(participations, 'mission.images.poster mission.images.banner', function(err) {
    if (err) {
      //handle it
      return void 0;
    }
    //do your magic stuff. with participations, which are populated in place in the examples 
  })
})

С уважением

Я нашел другой способ, но меня беспокоит производительность, я пытаюсь сделать сравнение.

Другой способ использует асинхронный модуль

MissionParticipation.find({
   user: userID
}).populate('mission').exec(function (err, missionParticipationsDocs) {
   if (err)
      return; // handle error

   async.forEach(missionParticipationsDocs, function (mp, callback) {
      mp.mission.populate('images.poster', 'images.banner', 'prize', function (err, result) {
         callback();
      });
   }, function (err) {
      // forEach async completed
      if(err)
         return; // handle error
      resolve(missionParticipationsDocs);
   });
});

@ joaom182 ты слишком быстр :). Хотя я добавил код, чтобы преобразовать deepPopulate в Query , я отложил отправку новой версии в NPM, чтобы иметь возможность немного больше протестировать.

Я только что выложил новую версию (0.0.7). Таким образом, этот синтаксис, который вы использовали, должен работать после обновления зависимости:

MissionParticipation
  .find({user: userID})
  .deepPopulate('mission.images.poster mission.images.banner')
  .exec(cb);

@buunguyen Потрясающе!

Можете ли вы открыть будущие выпуски в репозитории mongoose-deep-populate , пожалуйста? Облегчает всем жизнь :)

@buunguyen Потрясающе! Лучшее, что случилось с моим проектом, и Вы сделали мой день! Спасибо!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги