【问题标题】:Meteor, One to Many Relationship & add field only to client side collection in Publish?Meteor,一对多关系和仅将字段添加到发布中的客户端集合?
【发布时间】:2013-12-19 18:09:02
【问题描述】:

任何人都可以看到这段代码中可能有什么问题,基本上我想检查一个帖子是否已被当前登录的用户共享并向客户端集合添加一个临时字段:isCurrentUserShared。

这在加载新页面并从现有共享中填充时第一次起作用,或者在页面加载后的第一次向共享集合中添加或删除记录时。

1) isSharedByMe 仅更改状态 1 次,然后仍会根据 console.log 调用回调,但在我第一次添加或删除记录后,isSharedByMe 不会在 Posts 集合中更新。它第一次工作。

2) 为什么回调会连续调用两次,即向 Sharecollection 添加 1 条记录会触发 2 次调用,如 console.log 所示。

Meteor.publish('posts', function() {

    var self = this;
    var mySharedHandle;


    function checkSharedBy(IN_postId) {
        mySharedHandle = Shares.find( { postId: IN_postId, userId: self.userId }).observeChanges({

            added: function(id) {
                console.log("   ...INSIDE checkSharedBy(); ADDED: IN_postId = " + IN_postId );
                self.added('posts', IN_postId, { isSharedByMe: true });
            },

            removed: function(id) {
                console.log("   ...INSIDE checkSharedBy(); REMOVED: IN_postId = " + IN_postId );
                self.changed('posts', IN_postId, { isSharedByMe: false });
            }
        });
    }


    var handle = Posts.find().observeChanges({

        added: function(id, fields) {
            checkSharedBy(id);
            self.added('posts', id, fields);
        },

        // This callback never gets run, even when checkSharedBy() changes field isSharedByMe.
        changed: function(id, fields) {
            self.changed('posts', id, fields);
        },

        removed: function(id) {
            self.removed('posts', id);
        }
    });

    // Stop observing cursor when client unsubscribes
    self.onStop(function() {
        handle.stop();
        mySharedHandle.stop();
    });

    self.ready();
});

【问题讨论】:

  • 您在 {isCurrentUserShared: 1} 之后缺少一个括号,但我认为这不是问题所在。您看到的行为是什么?
  • 谢谢我修正了错字,但这不是主要问题。
  • 这是另一种使用帖子集合中的共享数组来解决此问题的方法。我不确定哪个更有效,但这种方式代码更多:stackoverflow.com/questions/20689113/…
  • 从阅读有关最大文档大小 16Meg 的 Mongo 文档,并重新考虑如果发生数百万份共享会发生什么,我将远离这种模式(下面的链接),除非嵌入式数组不会太大,即少于几千个项目:stackoverflow.com/questions/20689113/…

标签: javascript mongodb meteor publish-subscribe


【解决方案1】:

就我个人而言,我会以一种非常不同的方式来解决这个问题,即使用 $in 运算符,并在记录中保留一组 postIds 或 shareIds。

http://docs.mongodb.org/manual/reference/operator/query/in/

我发现发布函数在保持简单时效果最好,如下所示。

Meteor.publish('posts', function() {
    return Posts.find();
});
Meteor.publish('sharedPosts', function(postId) {
    var postRecord = Posts.findOne({_id: postId});
    return Shares.find{{_id: $in: postRecord.shares_array });
});

【讨论】:

  • 使用数组会很快失控,并导致以后需要更多工作来更改为“参考”。想象一下,不仅是分享,还有点赞、cmets 等,它将迅速增长到超过 16Meg Mongo doc 的大型应用程序限制。暂时我会像在 Telescope 中那样做,但这会将负载放在客户端上,想象一下由于客户端需要更新发布回调中的参数 VS 在服务器上进行连接而导致的所有额外行程。 github.com/SachaG/Telescope/blob/master/server/publications.js
  • 在项目中遇到过类似的情况,现在这似乎是最好的方法。
【解决方案2】:

我不确定这对您解决实际问题有多大帮助,但我将从您的代码中的一些奇怪之处和您提出的问题开始。

1) 您询问有关 Phrases 集合的问题,但发布函数永远不会向该集合发布任何内容,因为所有 added 调用都会发送到名为“posts”的 minimongo 集合。

2) 您询问了一个“转发”集合,但没有任何代码使用该名称,因此不清楚您指的是什么。添加到“Posts”集合的每个元素都会在“Shares”集合上创建一个新的观察者,因为它调用checkSharedId()。每个观察者都将尝试在客户的“帖子”集合中添加和更改文档。

3) 与第 2 点相关,mySharedHandle.stop() 只会停止由 checkSharedId() 创建的最后一个观察者,因为每次运行 checkSharedId() 时都会覆盖句柄。

4) 如果您的“Shares”观察者发现一个带有 IN_postId 的文档,它会尝试将带有该 _id 的文档发送到 minimongo 的“posts”集合。 IN_postId 是从您在“Posts”集合中找到的,其观察者也试图将不同的文档发送到客户端的“posts”集合。您希望使用该 _id 的客户端上的哪个文档?您看到的一些错误可能是由Meteor's attempts to ignore duplicate added requests 引起的。

从这一切来看,我认为您最好将其分解为两个发布功能,一个用于“帖子”,一个用于“分享”,以利用流星默认行为发布游标。然后可以在必要时在客户端上完成任何连接。例如:

//on server
Meteor.publish('posts', function(){
  return Posts.find();
});

Meteor.publish('shares', function(){
  return Shares.find( {userId: this.userId }, {fields: {postId: 1}} );
});

//on client - uses _.pluck from underscore package
Meteor.subscribe( 'posts' );
Meteor.subscribe( 'shares');

Template.post.isSharedByMe = function(){  //create the field isSharedByMe for a template to use
  var share = Shares.findOne( {postId: this._id} );
  return share && true;
};

使用observeChanges 加入发布的替代方法。未经测试的代码,我不清楚它是否比上面更简单的方法有很多优势。因此,在上述情况中断或成为性能瓶颈之前,我会按照上述方式进行。

Meteor.publish("posts", function(){
  var self = this;
  var sharesHandle;
  var publishedPosts = [];
  var initialising = true;  //avoid starting and stopping Shares observer during initial publish

  //observer to watch published posts for changes in the Shares userId field
  var startSharesObserver = function(){
    var handle = Shares.find( {postId: {$in: publishedPosts}, userId === self.userId }).observeChanges({

      //other observer should have correctly set the initial value of isSharedByMe just before this observer starts.
      //removing this will send changes to all posts found every time a new posts is added or removed in the Posts collection 
      //underscore in the name means this is undocumented and likely to break or be removed at some point
      _suppress_initial: true,

      //other observer manages which posts are on client so this observer is only managing changes in the isSharedByMe field 
      added: function( id ){
        self.changed( "posts", id, {isSharedByMe: true} );
      },

      removed: function( id ){
        self.changed( "posts", id, {isSharedByMe: false} );
      }
    });
    return handle;
  };

  //observer to send initial data and always initiate new published post with the correct isSharedByMe field.
  //observer also maintains publishedPosts array so Shares observer is always watching the correct set of posts.  
  //Shares observer starts and stops each time the publishedPosts array changes
  var postsHandle = Posts.find({}).observeChanges({
    added: function(id, doc){
      if ( sharesHandle ) 
        sharesHandle.stop();
      var shared = Shares.findOne( {postId: id});
      doc.isSharedByMe = shared && shared.userId === self.userId;
      self.added( "posts", id, doc);
      publishedPosts.push( id );
      if (! initialising)
        sharesHandle = startSharesObserver();
    },
    removed: function(id){
      if ( sharesHandle ) 
        sharesHandle.stop();
      publishedPosts.splice( publishedPosts.indexOf( id ), 1);
      self.removed( "posts", id );
      if (! initialising)
        sharesHandle = startSharesObserver();
    },
    changed: function(id, doc){
      self.changed( "posts", id, doc);
    }
  });

  if ( initialising )
    sharesHandle = startSharesObserver();

  initialising = false;
  self.ready();

  self.onStop( function(){
    postsHandle.stop();
    sharesHandle.stop();
  });
});

【讨论】:

  • 编辑 Q 以保持集合名称一致。希望让加入变得更简单,这样它就不会发布我所有的用户共享,只有显示的共享,即我最终会为此添加分页,因此需要一个与每个已发布帖子更紧密相关的加入。关于您的第 3 点)您的意思是我不应该使用 mySharedHandle.stop(),还是将该行放在 checkSharedId() 中?您的第二个#3)或第 4 点我认为这是向 miniMongo 添加新字段的方法。我的想法来自:stackoverflow.com/questions/10565654/…
  • Shares 和 Posts 的加入是您希望最终在客户端得到什么的基本要点吗?如果您想限制“帖子”或“分享”发布的内容,您可以使用client pass criteria to the publish function
  • 是的,但是订阅者必须检查显示给客户的每个帖子,看看它是否已被共享。换句话说,这种方式会慢得多,想象一个有 100 个帖子的列表,在每个帖子可以在浏览器中正确显示之前,客户端必须等待 100 次 Shares.find(postId) 调用。这不是一个真正的数据库连接,或者至少它应该在服务器上时将逻辑推送到客户端,并且对于我的应用程序中的每个数据库连接都使用 Session vars。 Meteor 建议使用 observeChanges,我就是无法让我的代码工作,这可能很简单。
  • 我已编辑我的答案以仅在服务器上使用观察和管理加入。我不认为 Meteor 在任何地方都“推荐”这种方式,尽管它在某些情况下可能具有性能优势,例如当发布的集合很大并且客户端很少时。我很好奇您在实际使用这两种不同方法时观察到的性能差异。
  • 感谢您的更新,但必须有更好的方法,那是一团乱码。顺便说一句,您在 Shares.findOne 中有小错误 - 缺少用户 ID,并且 Shares.find - 用户 ID 不在对象表示法中。我仍然不知道为什么我的示例适用于初始化然后停止 && 为什么 console.log 语句在初始发布后连续调用两次,即将记录添加到共享集合时。
【解决方案3】:

myPosts 是一个游标,因此当您在其上调用 forEach 时,它会循环显示结果,添加您想要的字段,但最终会出现在结果列表的末尾。因此,当您返回 myPosts 时,没有任何东西可以循环,因此 fetch() 将产生一个空数组。

您应该能够通过在返回之前添加myPosts.cursor_pos = 0; 来更正此问题,从而将光标返回到结果的开头。

【讨论】:

  • 我更新了问题/代码,所以这个答案不适用,检查最新的编辑,看看你能不能解决这个问题。谢谢。
猜你喜欢
  • 1970-01-01
  • 2017-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-26
  • 2015-08-08
  • 2017-07-01
相关资源
最近更新 更多