Mongoose: Chamando popular no documento incorporado do documento incorporado

Criado em 8 nov. 2011  ·  93Comentários  ·  Fonte: Automattic/mongoose

Ei,
Eu tenho os seguintes esquemas (simplificados para emissão)

Usuário = novo esquema
login:String

A = novo esquema
emb: [B]
comentários: [Comentários]

//e seguindo esquemas embutidos

B = novo esquema
comentários: [Comentários]

Comentários = novo esquema
O Criador:
tipo: ObjectId
ref: 'Usuário'
mensagem: cadeia

Agora, enquanto populate funciona em A
por exemplo A.find().populate('comments.creator')

não funciona no documento incorporado aninhado duplo
por exemplo A.find().populate('emb.comments.creator')

Alguma ideia?

new feature

Comentários muito úteis

Eu sei que este é um tópico antigo, mas acabei de criar um plugin que facilita muito o preenchimento de modelos em qualquer nível de profundidade. Estou postando aqui caso alguém se interesse: https://github.com/buunguyen/mongoose-deep-populate.

O uso é muito simples, por exemplo:

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

Por favor, verifique o repositório de plugins para mais informações.

Todos 93 comentários

A maneira como estou fazendo isso atualmente é preencher os itens aninhados e, em seguida, iterar por meio deles para preencher os filhos. Eu estava planejando implementar uma solução para preencher vários níveis aninhados, mas ficou muito complicado.

Olá eneko,

Como você está fazendo isso?

Quando você faz seu primeiro preenchimento, você retorna os itens aninhados, mas quando você os percorre para preencher seus filhos, você consegue salvar os filhos preenchidos? Nunca gruda quando eu tento fazer isso.

Algum código seria incrível.

Obrigado,
Paulo

@eneko deixa pra lá, só precisava de .toObject() ;)

Na verdade, Paul, imaginei que populate() funciona em consultas que retornam vários elementos, portanto, não há necessidade de iterar pelos objetos filhos. Mas sim, você está certo, você precisa chamar .toObject se quiser que as propriedades permaneçam.

Aqui está um exemplo:

// 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);
      });
  });

}

Ah, obrigado pela sua resposta detalhada. Suas chamadas de mangusto são mais específicas do que as minhas e eu poderia aprender com sua abordagem. Por acaso, meu aplicativo específico exigia um preenchimento recursivo, então, por enquanto, posso estar preso fazendo isso manualmente.

Eu acabo fazendo algo assim.

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);
    } );

})
}

Isso agora funciona na versão mais recente do mangusto?

Então acho que esse é o problema que estou enfrentando. Cada objeto Comment tem um objeto User incorporado. Os comentários são incorporados como um array em Activity. Uma consulta de atividades (como abaixo) não preenche o objeto Usuário em cada comentário.

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 } 
}) 

Existe algum plano para popular para dar suporte a caminhos aninhados para lidar com casos como esse?

sim, gostaríamos de em algum momento. isso pode rapidamente se tornar um problema de desempenho, mas sim, devemos apoiá-lo.

Para objetos aninhados, preciso iterar sobre eles manualmente, carregando-os, o que parece mais um problema de desempenho e desordena meu código do servidor. Então eu sou +1 por facilitar meu trabalho ;-)

Enquanto isso, continuarei e também o abordarei dividindo as seções da página em objetos carregáveis ​​separadamente que não exigem dados tão profundamente aninhados em cada chamada.

Eu seria a favor de algo assim;

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

Na verdade, seria muito útil ter o preenchimento disponível no documento assim. Você pode continuar mergulhando até onde precisar.

@Qard você leu minha mente.

@Qard é assim que os relacionamentos de mangusto funcionam. Eu usei esse módulo por um tempo no início, mas acabei fazendo as coisas manualmente. Talvez o Mongoose deva integrá-lo ao núcleo, ou pelo menos partes dele, como chamar populate em arrays.

Algo como https://github.com/LearnBoost/mongoose/issues/601#issuecomment -3258317 já foi implementado?

Estou me perguntando a mesma coisa. Estou tentando descobrir como preencher referências filho de documentos incorporados. @dbounds eu tenho modelos análogos. Você resolveu este problema: https://github.com/LearnBoost/mongoose/issues/601#issuecomment -3088564. Não consigo encontrar relações de mangusto

As informações neste tópico foram muito úteis para entender o estado atual de .populate(), a limitação atual relacionada à população aninhada de netos e como .toObject() em combinação com a pesquisa manual pode ser uma opção para contornar isso.

Eu estou querendo saber se existe uma técnica que poderíamos usar que não requer .toObject(). Eu pergunto b\c depois de preenchermos nosso modelo e os netos que ainda gostaríamos de ter um Documento e tipos apropriados para que possamos usar funções como parent.childarray.id(x) ou modificar os valores e chamar .save().

Qualquer ajuda aqui seria muito apreciada.

nota: Tentamos usar parent.set('child.grandchild', value), mas isso parece causar alguns problemas com a integridade do documento e não conseguimos mais ler valores desse caminho (o erro é Invalid ObjectId ao tentar ler pai.filho ou pai.filho.neto).

@aheckmann Alguma ideia sobre isso ainda?

esteve ocupado. eu quero no 3.0 final

@aheckmann Incrível mal posso esperar. Será realmente útil em aplicações reais. Mas eu quero parabenizá-lo pelo excelente trabalho que você realizou até agora.

Grande maravilha! Vou usar esse recurso assim que estiver disponível.

Ei pessoal, eu escrevi minha própria camada em torno do Mongoose que suporta subpopulação. Não é arquitetonicamente ideal, pois é um wrapper em torno de um wrapper, mas suporta subpopular da maneira que queremos que funcione. Alguém está interessado em vê-lo se eu limpá-lo e liberá-lo?

Definitivamente interessado! Não tenho certeza se sou a pessoa certa para fazer o patch, mas quaisquer idéias sobre como contornar esse problema agora são muito bem-vindas!

Agora é um hack, embora um hack agradável da minha perspectiva :) Espero que possa ser usado para corrigir o Mongoose, mas é um patch de macaco no momento. Escreverei mais alguns testes, documentarei meu código e voltarei aqui na próxima semana.

Isso será adicionado na versão 3.0?

@hackfrag sim

Isso está em 3.0 ou ainda está sendo adicionado. Se for poderia ter um exemplo por favor :)

não está em 3.0. ele será adicionado em uma próxima versão secundária.

Estou tendo um tempo incrivelmente difícil tentando hackear essa funcionalidade. Você tem alguma estimativa de quando esta versão pode estar disponível? Vou abandonar meu código feio e trabalhar em outras áreas do meu aplicativo se o suporte verdadeiro estiver em um futuro próximo :) . E devo acrescentar, obrigado pela biblioteca incrível.

Stowns, meu hack está funcionando bem e agora me sinto bem em lançá-lo. Entrarei em contato com você dentro de 24 horas - preciso testar contra o Mongoose 3 primeiro

-- Josué Gross
Christian / Consultor de Desenvolvimento Web / Candidato BA em Ciência da Computação, UW-Madison 2013
414-377-1041 / http://www.joshisgross.com

Em 13 de agosto de 2012, às 18h56, stowns [email protected] escreveu:

Estou tendo um tempo incrivelmente difícil tentando hackear essa funcionalidade. Você tem alguma estimativa de quando esta versão pode estar disponível? Vou abandonar meu código feio e trabalhar em outras áreas do meu aplicativo se o suporte verdadeiro estiver em um futuro próximo :)


Responda a este e-mail diretamente ou visualize-o no GitHub.

@JoshuaGross ei, você pode colocar seu hack em uma essência? meu código está ficando muito confuso e feio sem esse recurso ...

Ei @madhums , @stowns , @aheckmann , @farhanpatel , @hackfrag , @jsalonen , etc. Meu hack (só testado no Mongoose 2.7) está aqui: https://github.com/JoshuaGross/mongoose-subpopulate

Espero que seja útil.

UAU Josué! Obrigado um milhão!

Com certeza vou dar uma olhada nisso agora mesmo!

obrigado @JoshuaGross , vou verificar isso mais tarde hoje

Isso aconteceria se algum dos documentos não tiver o campo para "preencher" está solicitando fazer a busca? Teria erros? Tecnicamente não haveria erro, mas será mostrado este(s) documento(s)?

As chamadas populate() aninhadas ainda funcionam? Parece que houve alguma atividade, mas não consegui determinar como fazê-lo.

Eu tenho:

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

usuário é um aninhado profundo ... é possível preencher?

@aheckmann você pode comentar sobre isso? Eu tenho usado mangusto-subpopulate em produção por um bom tempo para fazer subpopulates.

mongoose-subpopulate não funciona com o express 3.

Tenho uma filial iniciada, mas tenho estado bastante ocupada. Está a caminho.

Quarta-feira, 3 de outubro de 2012 às 23h37, Anthony Ettinger
[email protected] escreveu :

mongoose-subpopulate não funciona com o express 3.


Responda a este e-mail diretamente ou visualize-o no Gi tHubhttps://github.com/LearnBoost/mongoose/issues/601#issuecomment -9131991.

Arão
@aaronheckmann https://twitter.com/#!/aaronheckmann

@chovy você quer dizer mangusto 3? Eu uso mangusto-subpopular com expresso 3.

Sim, mangusto 3 eu quis dizer.

@aheckmann - Ansioso para ver sua filial!

Eu não consegui descobrir como usar mangusto-subpopular

+1 para este recurso

+1 para este

Existe uma maneira de preencher um array filho de um array?

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);

O que eu quero realizar aqui é obter o nome do remetente da notificação

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

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

Ou se houver outra maneira de fazer outra consulta para obter todos os nomes de usuário manualmente?

você precisaria fazer "de" uma matriz como está fazendo com as notificações. A melhor coisa a fazer é buscar o usuário para cada notificação em seu retorno de chamada.

sim, estou apenas fazendo a população manualmente, bull será legal se populate funcionar com referências profundas também...

+1, estava esperando que eu chegasse ao fundo e visse isso e eu simplesmente não estava fazendo isso direito. :(

+1, alguma estimativa de cronograma para este recurso? Seria insanamente útil.

@winduptoy parecendo 3.6

+1 aqui também.

Mais importante, devemos pelo menos ser capazes de preencher os filhos nós mesmos sem ter que chamar toObject() no objeto pai primeiro. Eu posso entender o desejo de manter os tipos de campo consistentes após a chamada de set, mas o mangusto já quebra essa regra com a função populate para começar. Devemos ser capazes de definir um campo referenciado para o objeto completo sem que o mangusto o altere apenas para o id do mongo.

Fora essa confusão, amo mangusto.

Estou tão ansioso para este recurso.

+1 Eu também preciso desse recurso...

+1 para este futuro

+1 - ansioso por este

+1 Isso seria incrível

+1 Isso veio com problemas de software, mas se você colocar outro símbolo ajudaria, por exemplo

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

Então não evite problemas... Não?

+2

+1 Isso seria útil.

+1 Isso seria muito bom de sua parte.

Por favor, libere esse recurso em 2013

+1 Eu preciso disso agora, será muito útil.

Para aqueles de vocês que precisam disso agora, você pode querer verificar mangusto-subpopulate. Estou usando há vários meses: https://github.com/JoshuaGross/mongoose-subpopulate (sou o mantenedor)

Aqui está o plano para preencher em 3.6: https://github.com/LearnBoost/mongoose/pull/1292

+1 Precisamos disso. Espero que seja lançado em breve, (porque o problema já começou ano atrás)

Acho que pelo voto popular, ganhou essa proposta!

@aheckmann Muito obrigado!!

Também amo lean() , então isso parece realmente excelente até agora!

Animado para 3.6, esta é uma boa correção, obrigado!

Obrigado sr!

Em 5 de março de 2013, às 17h26, Andy Burke [email protected] escreveu:

Animado para 3.6, esta é uma boa correção, obrigado!


Responda a este e-mail diretamente ou visualize-o no GitHub.

@aheckmann Ótimo, obrigado!

Obrigado!!! Bom trabalho!!!

Fantástico! Acredito que possa ajudar no meu caso. Como devo proceder neste cenário:

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)
*/

Há algo de errado? Estou em "3.6.0rc0"

@flockonus @vovan22 Corri para o mesmo problema. Foi assim que resolvi.

Eu estava tentando fazer post.populate("comments comments._creator") trabalhar em nosso projeto e tive um pouco de sucesso com os seguintes ajustes.

Não parece funcionar para consultas mais profundas, e a primeira mudança quebra alguns dos testes existentes , mas espero que possa ser de interesse para quem trabalha nisso.

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

Como meu patch é meio ruim, tentei contribuir escrevendo um caso de teste! https://github.com/LearnBoost/mongoose/pull/1603

joeytwiddle, estou tendo exatamente o mesmo problema e espero que você o conserte - estou tentando preencher recursivamente, mas também recebo o erro de caminho não encontrado. Eu não sei se isso é por design ...

Sim, o infame 601. É por design, pelo menos por enquanto. A versão mais recente do Mongoose suporta o preenchimento profundo, mas _somente dentro de um esquema_.

Em nosso projeto, precisamos fazer uma população profunda em diferentes modelos com bastante frequência, então escrevemos uma função auxiliar:

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

Isso permite que você preencha os descendentes de um documento com qualquer profundidade que desejar! Ele ainda requer um retorno de chamada extra depois de buscar o documento, mas apenas um. Por exemplo:

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

Espero que o uso de doc.constructor para obter o modelo não seja problemático no futuro!

_(Graças à Sunride e ao governo alemão!)_

@joeytwiddle - muito obrigado! Funcionou como um encanto. Agora vou tentar descobrir uma maneira de fazê-lo preencher n-deep.

Alguma atualização sobre isso?

@aheckmann Eu entendo sua teoria sobre isso, mas discordo que seja um antipadrão.

Às vezes você precisa realizar operações caras, é inevitável em sistemas complexos e você precisa coletar documentos díspares juntos. Por exemplo, você pode querer fazer uma operação cara quando um documento MUITO, MUITO raramente muda e armazenar em cache os resultados para desempenho.

Por que você acredita que um nível de população é 'ok', mas mais do que isso é um sinal de um anti-padrão?

Eu sei que este é um tópico antigo, mas acabei de criar um plugin que facilita muito o preenchimento de modelos em qualquer nível de profundidade. Estou postando aqui caso alguém se interesse: https://github.com/buunguyen/mongoose-deep-populate.

O uso é muito simples, por exemplo:

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

Por favor, verifique o repositório de plugins para mais informações.

@buunguyen bom trabalho!

Não está funcionando para mim.

models/missionParticipation.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;

services/missionParticipationService.js

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

E eu recebo esse erro no console

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

@joaom182
Eu ainda não usei o deepPopulate, mas gravando nos documentos em http://npm.taobao.org/package/mongoose-deep-populate eu diria que a chamada correta deveria ser:

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 
  })
})

Cumprimentos

Encontrei outra forma, mas estou preocupado com o desempenho, tento fazer uma comparação.

A outra maneira usa o módulo assíncrono

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 você é um pouco rápido demais :). Embora eu tenha adicionado código para trazer deepPopulate para Query , eu adiei o envio de uma nova versão no NPM para que eu pudesse testar um pouco mais.

Acabei de empurrar a nova versão (0.0.7). Portanto, esta sintaxe que você usou deve funcionar depois de atualizar a dependência:

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

@buunguyen Incrível!

Vocês podem abrir problemas futuros no repositório do mangusto-deep-populate , por favor? Facilita a vida de todos :)

@buunguyen Incrível! A melhor coisa que aconteceu ao meu projeto e você fez o meu dia! Obrigado!

Esta página foi útil?
0 / 5 - 0 avaliações