Метеор, отношение один ко многим и добавить поле только в коллекцию на стороне клиента в публикации?

Может кто-нибудь увидеть, что может быть не так в этом коде, в основном я хочу проверить, была ли публикация опубликована текущим вошедшим пользователем И добавить временное поле в коллекцию на стороне клиента: isCurrentUserShared.

Это работает 1-й раз при загрузке новой страницы и заполнении из существующих общих ресурсов или при добавлении или удалении записи в коллекцию общих ресурсов ТОЛЬКО в самый первый раз после загрузки страницы.

1) isSharedByMe только 1 изменяет состояние, затем обратные вызовы по-прежнему вызываются в соответствии с console.log, но isSharedByMe не обновляется в коллекции сообщений после первого добавления или удаления записи. Работает с первого раза.

2) Почему обратные вызовы вызываются дважды подряд, то есть добавление 1 записи в Shares collection запускает 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 ответа

Лично я бы пошел по-другому, используя оператор $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) Вы спрашиваете о коллекции фраз, но функция публикации никогда не будет публиковать что-либо в этой коллекции, как все added звонки отправляются в коллекцию minimongo под названием "posts".

2) Вы спрашиваете о коллекции "Reposts", но ни один из кодов не использует это имя, поэтому неясно, на что вы ссылаетесь. Каждый элемент, добавленный в коллекцию "Posts", создаст нового наблюдателя в коллекции "Shares", так как он вызывает checkSharedId(), Каждый наблюдатель будет пытаться добавлять и изменять документы в коллекции сообщений клиента.

3) Относится к пункту 2, mySharedHandle.stop() остановит только последний наблюдатель, созданный checkSharedId() потому что ручка перезаписывается каждый раз checkSharedId() это запустить.

4) Если ваш обозреватель 'Shares' находит документ с IN_postId, он пытается отправить документ с этим _id в коллекцию 'minimongo' posts '. IN_postId передается из вашей находки в коллекции "Posts", а наблюдатель также пытается отправить другой документ в коллекцию "posts" клиента. Какой документ вы хотите на клиенте с этим _id? Некоторые ошибки, которые вы видите, могут быть вызваны попытками Meteor игнорировать дубликаты добавленных запросов.

Из всего этого я думаю, что вам лучше разбить это на две функции публикации, одну для "Сообщений" и одну для "Акций", чтобы воспользоваться преимуществами курсоров публикации поведения по умолчанию для метеоров. При необходимости любое соединение может быть выполнено на клиенте. Например:

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

Альтернативный метод присоединяется в publish с наблюдаемыми изменениями. Непроверенный код, и мне не ясно, что он имеет много преимуществ по сравнению с более простым способом, описанным выше. Поэтому, пока вышеперечисленное не сломается или не станет узким местом в производительности, я буду делать это, как указано выше.

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

myPosts это курсор, поэтому при вызове forEach на нем он циклически перебирает результаты, добавляя поле, которое вы хотите, но заканчивая в конце списка результатов. Таким образом, когда вы вернетесь myPostsнет ничего, чтобы перебрать, так что fetch() даст пустой массив.

Вы должны быть в состоянии исправить это, просто добавив myPosts.cursor_pos = 0; прежде чем вернуться, тем самым вернув курсор в начало результатов.

Другие вопросы по тегам