流星,一对多关系&在发布中仅向客户端集合添加字段?

时间:2013-12-19 18:09:02

标签: javascript mongodb meteor publish-subscribe

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

这是第一次加载新页面并从现有共享填充时,或者在加载页面时第一次添加或删除记录到Shares集合时。

1)isSharedByMe只更改状态1次,然后仍按照console.log调用回调,但是在第一次添加或删除记录后,isSharedByMe在Posts集合中没有更新。它第一次工作。

2)为什么连续两次调用回调,即将一条记录添加到Sharescollection触发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();
});

3 个答案:

答案 0 :(得分:2)

就个人而言,我会通过使用$ 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 });
});

答案 1 :(得分:1)

我不确定这会让您在多大程度上解决您的实际问题,但我会从代码中的一些奇怪事项开始,以及您提出的问题。

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

2)你询问“Reposts”集合,但是没有一个代码使用该名称,所以不清楚你指的是什么。添加到“帖子”集合中的每个元素虽然会在“共享”集合上创建一个新观察者,因为它调用checkSharedId()。每个观察者都会尝试在客户的“帖子”集合中添加和更改文档。

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

4)如果您的“分享”观察者发现了一个带有IN_postId的文档,它会尝试将带有该_id的文档发送到minimongo'posts'集合。 IN_postId从您在“帖子”集合中的查找传递,其观察者也尝试向客户端的“帖子”集合发送不同的文档。你想在客户端使用哪个doc _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();
  });
});

答案 2 :(得分:0)

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

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