Você tem duas maneiras possíveis de um usuário seguir outro usuário; direta ou indiretamente por meio de um grupo, caso em que o usuário diretamente segue o grupo. Vamos começar armazenando esses diretos relações entre usuários e grupos:
{
_id: "userA",
followingUsers: [ "userB", "userC" ],
followingGroups: [ "groupX", "groupY" ]
}
Agora, você poderá rapidamente descobrir quais usuários o usuário A está seguindo, direta ou indiretamente. Para conseguir isso, você pode desnormalizar os grupos que o usuário A está seguindo. Digamos que os grupos X e Y sejam definidos da seguinte forma:
{
_id: "groupX",
members: [ "userC", "userD" ]
},
{
_id: "groupY",
members: [ "userD", "userE" ]
}
Com base nesses grupos e nas relações diretas que o usuário A tem, você pode gerar assinaturas entre usuários. As origens de uma assinatura são armazenadas com cada assinatura. Para os dados de exemplo, as assinaturas ficariam assim:
// abusing exclamation mark to indicate a direct relation
{ ownerId: "userA", userId: "userB", origins: [ "!" ] },
{ ownerId: "userA", userId: "userC", origins: [ "!", "groupX" ] },
{ ownerId: "userA", userId: "userD", origins: [ "groupX", "groupY" ] },
{ ownerId: "userA", userId: "userE", origins: [ "groupY" ] }
Você pode gerar essas assinaturas com bastante facilidade, usando uma chamada map-reduce-finalize para um usuário individual. Se um grupo for atualizado, você só precisará executar novamente o map-reduce para todos os usuários que estão seguindo o grupo e as assinaturas serão atualizadas novamente.
Redução de mapa
As seguintes funções de redução de mapa gerarão as assinaturas para um único usuário.
map = function () {
ownerId = this._id;
this.followingUsers.forEach(function (userId) {
emit({ ownerId: ownerId, userId: userId } , { origins: [ "!" ] });
});
this.followingGroups.forEach(function (groupId) {
group = db.groups.findOne({ _id: groupId });
group.members.forEach(function (userId) {
emit({ ownerId: ownerId, userId: userId } , { origins: [ group._id ] });
});
});
}
reduce = function (key, values) {
origins = [];
values.forEach(function (value) {
origins = origins.concat(value.origins);
});
return { origins: origins };
}
finalize = function (key, value) {
db.subscriptions.update(key, { $set: { origins: value.origins }}, true);
}
Você pode então executar o map-reduce para um único usuário, especificando uma consulta, neste caso para
userA
. db.users.mapReduce(map, reduce, { finalize: finalize, query: { _id: "userA" }})
Algumas notas:
- Você deve excluir as assinaturas anteriores de um usuário antes de executar o map-reduce para esse usuário.
- Se você atualizar um grupo, deverá executar map-reduce para todos os usuários que seguem o grupo.
Devo observar que essas funções de redução de mapa acabaram sendo mais complexas do que eu tinha em mente , porque o MongoDB não suporta arrays como valores de retorno de funções de redução. Em teoria, as funções poderiam seria muito mais simples, mas não seria compatível com o MongoDB. No entanto, esta solução mais complexa pode ser usada para mapear-reduzir todos os
users
coleta em uma única chamada, se você precisar.