Meteor / Mongo根据子字段查找记录并修剪子ID的子阵列?

时间:2015-07-20 18:21:05

标签: mongodb meteor mongodb-query meteor-publications

我在Meteor中有这样的集合:

TagsToArticles = {
  tag: "Tag1",
  articles: [ article1Id, article2Id, article3Id ]
}

文章集合具有以下架构

Articles = {
  permission: "private"
  ...
}

标签基本上是索引搜索器。

每篇文章的权限都设置为“私人”,“群组”或“公开”。

现在,我正在发布这样的标签:

Meteor.publish("allTags", function() {
  return TagsToARticles.find({});
}

然后在客户端,我正在过滤文章列表,只显示那些公开的或那些私有但由当前用户创建的文章。

但是,理想情况下,出于安全考虑,我希望在发布函数本身内对服务器端进行过滤,以防止客户端访问私有文章的文章ID。我确实阻止访问实际的文章对象,除非客户端具有适当的权限,但我想更进一步,从结果中完全删除ID。

所以我正在寻找的本质上是一个允许我使用以下伪代码的查询:

TagsToArticles.find({ articles.foreach(articleId) {
  if (Articles.findOne(articleId).permission == 'public') ||
     (Articles.findOne(articleId).ownerId == Meteor.userId())
     include articleId
  }

我最初想的是使用与上面完全相同的函数来执行此操作(基本上获取所有记录然后遍历每个记录并手动修剪数组,然后返回更新的记录集),但我的理解是我会那么如果基础数据发生变化,则会失去Meteor的反应性并且记录集不会更新。

如果没有单个find()查询来完成这项工作,如果有办法对函数进行额外的传递并仍然返回一个反应数据集,我也可以使用该解决方案。

由于这是一个非规范化的集合(标签也位于文章文档中),我想我还可以进一步非规范化,不仅包括文章ID,还包括ownerId和权限。但我仍然不确定如何测试单个数组元素,如果可能的话,我想尽量减少我需要做的非规范化的数量......

3 个答案:

答案 0 :(得分:1)

您应该考虑使用composite-publish包。

如果我正确理解您的架构,发布代码将如下所示:

Meteor.publishComposite('articles', function() {
  return {
    find: function() {
      return Articles.find({ $or: [ { permission: 'public' }, { ownerId: this.userId } ] });
    },
    children: [{
      find: function(article) {
        return TagsToArticles.find( { articles: article.articleId } );
      }
    }]
  }
});

该软件包进行了响应式连接,实际上非常昂贵。所以请记住这一点,看看这对你来说仍然是一个不错的选择。

P.S。我记得你不能在发布中使用Meteor.userId(),这就是我使用this.userId的原因

答案 1 :(得分:1)

关于您的问题(带有标签的文章)的好处是所有用户的关系都是相同的。所以它是数据的属性而不是数据请求的属性。所以你可以使用非规范化来帮助你。在SQL世界中,您希望对所有数据进行规范化,然后使用连接在读取时将它们组合在一起。在MongoDB中,更好的方法是使用文档以真实的方式存储相关数据的子文档(因此您将原始文档放在另一个集合中,并将其副本作为主文档中的子文档)。因为这些关系是数据的属性,所以您可以简单地嵌入这些子文档并将它们存储到主文档中。

非规范化数据的问题是如何使这些副本保持同步。如何确保一旦更改主文档,子文档中的副本也会更新?要解决此问题,您可以使用PeerDB Meteor包(我是作者之一),您可以在其中声明一次关系,PeerDB将确保在文档更新后最终将事物同步。

但是你的问题是你要隐藏articles数组本身中的条目,以防止那些用户不应该看到的条目。这应该分别针对不同的用户。为此,您可以使用middleware包(我也是作者之一),它允许您撰写发布函数的这种转换。

所以这个想法是:

  • 将您的articles数组中的ID以及进行权限检查所需的信息非常规化
  • 然后为每个发布删除那些用户无权访问的ID,并删除那些用于进行权限检查的额外字段

在CoffeeScript中:

class TagsToArticles extends Document
  @Meta
    name: 'TagsToArticles'
    fields: =>
      articles: [@ReferenceField Article, ['permission', 'ownerId']]

allTags = new PublishEndpoint 'allTags', ->
  TagsToArticles.documents.find {}

class CleanArticlesMiddleware
  processArticles: (fields, userId) ->    
    fields.articles = (_id for {_id, permission, ownerId} in fields.articles when permission is 'public' or ownerId is userId) if fields.articles

  added: (publish, collection, id, fields) ->
    @processArticles fields, publish.userId
    publish.added collection, id, fields

  changed: (publish, collection, id, fields) ->
    @processArticles fields, publish.userId
    publish.changed collection, id, fields

allTags.use new CleanArticlesMiddleware()

答案 2 :(得分:0)

甚至不可能在Mongo中进行连接,而且根据Mongo数据库中的连接制作反应数据集看起来更不现实。

但你真的需要反应数据集吗?您可以在Articles数据集中订阅public和userId记录,在TagsToArticles中订阅所需的标签,并为枚举文章和标签并返回记录集的模板定义帮助器。当Article或TagsToArticles中的项目发生变化时,Meteor会自动更新您的HTML。

  

但我仍然不确定如何测试单个数组元素

如果您在文章中有标签,则可以仅浏览文章集合并从公共和用户文章中收集唯一标签