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

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

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

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

2) Почему обратные вызовы вызываются дважды подряд, т. е. добавление 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();
});

person Giant Elk    schedule 19.12.2013    source источник
comment
Вам не хватает скобки после {isCurrentUserShared: 1}, но я предполагаю, что проблема не в этом. Какое поведение вы наблюдаете?   -  person Christian Fritz    schedule 19.12.2013
comment
Спасибо, я исправил опечатку, но это не главная проблема.   -  person Giant Elk    schedule 20.12.2013
comment
Вот еще один способ решить эту проблему, используя массив общих ресурсов в коллекции сообщений. Я не уверен, что более эффективно, но этот способ намного больше кода: stackoverflow.com/questions/20689113/   -  person Giant Elk    schedule 20.12.2013
comment
Пакет github.com/erundook/meteor-publish-with-relations помочь вам вообще?   -  person Andrew Mao    schedule 23.01.2014
comment
Привет, Эндрю, этот пакет не отвечает на мой вопрос о том, как заставить работать приведенный выше код. Также публикация с отношениями имеет проблему с утечкой памяти https://github.com/erundook/meteor-publish-with-relations/issues/12, а также слабая документация по вариантам использования.   -  person Giant Elk    schedule 24.01.2014


Ответы (3)


Лично я бы сделал это совершенно по-другому, используя оператор $in и сохраняя в записях массив идентификаторов postId или shareId.

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 });
});
person AbigailW    schedule 15.01.2014
comment
Использование массива быстро выйдет из-под контроля и потребует дополнительной работы, чтобы позже перейти на «ссылку». Представьте себе не только репост, но и лайки, комментарии и т. д., он быстро превысит лимит 16Meg Mongo doc для большого приложения. В настоящее время я собираюсь сделать это, как в Telescope, но это создает нагрузку на клиента, представьте себе все дополнительные поездки из-за того, что клиенту необходимо обновить параметр в обратном вызове публикации VS, выполняющем соединение на сервере. github.com/SachaG/Telescope/blob/master/server/publications. js - person Giant Elk; 24.01.2014
comment
Столкнувшись с чем-то подобным в проекте, теперь это кажется лучшим подходом. - person user728291; 02.02.2014

Я не уверен, насколько это продвинет вас к решению ваших реальных проблем, но я начну с нескольких странностей в вашем коде и вопросов, которые вы задаете.

1) Вы спрашиваете о коллекции Phrases, но функция публикации никогда ничего не публикует в этой коллекции, поскольку все вызовы added отправляются в коллекцию minimongo с именем «posts».

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

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

4) Если ваш наблюдатель «Shares» находит документ с IN_postId, он пытается отправить документ с этим _id в коллекцию «posts» minimongo. IN_postId передается из вашей находки в коллекции «Сообщения», а его наблюдатель также пытается отправить другой документ в коллекцию «сообщений» клиента. Какой документ вы хотите на клиенте с этим _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;
};

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

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();
  });
});
person user728291    schedule 11.01.2014
comment
Является ли объединение общих ресурсов и сообщений основной сутью того, что вы хотите получить на клиенте? Если вы хотите ограничить то, что публикуется «сообщениями» или «поделиться», вы можете использовать критерии передачи клиента для функция публикации. - person user728291; 12.01.2014
comment
Да, но тогда подписка должна будет проверять каждую публикацию, которая показывается клиенту, чтобы узнать, была ли она опубликована. Другими словами, этот способ будет намного медленнее, представьте себе список из 100 сообщений, прежде чем каждое сообщение сможет правильно отображаться в браузере, клиенту придется ждать 100 вызовов Shares.find(postId). Это не настоящее соединение с БД, или, по крайней мере, оно отправляет логику клиенту, когда она должна быть на сервере, и уродливо использует переменные сеанса для каждого соединения с БД в моем приложении. Meteor рекомендует использоватьObservChanges, я просто не могу заставить свой код работать, вероятно, это что-то простое. - person Giant Elk; 12.01.2014
comment
Я отредактировал свой ответ, чтобы использовать наблюдения и управлять соединением исключительно на сервере. Я не думаю, что Meteor рекомендует это где-либо, хотя в некоторых случаях это может иметь преимущества в производительности, например, когда публикуемый набор велик и клиентов мало. Мне любопытны различия в производительности, которые вы наблюдаете при использовании двух разных подходов в дикой природе. - person user728291; 13.01.2014
comment
Спасибо за ваше обновление, но должен быть лучший способ, это беспорядок в коде. Кстати, у вас есть незначительная ошибка в Shares.findOne — отсутствует идентификатор пользователя, а в Shares.find — идентификатор пользователя отсутствует в нотации объекта. Я до сих пор не понимаю, почему мой пример работает при инициализации, а затем останавливается &&, почему операторы console.log вызываются дважды подряд после первоначальной публикации, т.е. При добавлении записи в коллекцию Shares. - person Giant Elk; 15.01.2014
comment
Используйте разбиение на страницы: › Я не думаю, что Meteor рекомендует это где-либо, хотя это может иметь преимущества в производительности в некоторых случаях, например, когда публикуемый набор велик и клиентов мало. - person Giant Elk; 26.01.2014
comment
Не могли бы вы подробнее рассказать о своем последнем комментарии? Трудно что-то понять из фрагмента из двух слов, за которым следует цитата. Если у вас есть второй вопрос о том, как разбить на страницы, я бы перешел по ссылке, которую я дал в моем первый комментарий. Оба метода, которые я показал, будут выполнять соединение только для любых опубликованных «сообщений». Я возвращаю все «сообщения» в примерах, потому что это было указано в вашем вопросе. - person user728291; 27.01.2014
comment
Думаю, я неправильно понял, что вы имели в виду, говоря, что Meteor не рекомендует, я просто говорю, что нумерация страниц предотвратит проблемы с производительностью. В настоящее время у Meteor нет элегантного решения для соединений, кроме действительно простых публикаций, где вы передаете идентификатор документа. Есть люди, работающие над этим, но я не думаю, что для них это так же важно, как укрепление других основных функций. Вот почему я пытаюсь сам что-нибудь смастерить. - person Giant Elk; 27.01.2014

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

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

person richsilv    schedule 19.12.2013
comment
Я обновил вопрос/код, поэтому этот ответ не применяется, проверьте последнее редактирование, посмотрите, сможете ли вы понять это. Спасибо. - person Giant Elk; 11.01.2014