lehtu
lehtu

Reputation: 907

How to sort collection by array of values in publication? (array not in the collection items)

I have a collection Cats:

{
    "_id" : "rRq76LxsnPfmuh9DD",
    "register_id" : "20gnr3g",
    "name" : "Meow",
    "created_at": ...,
    ...
}

Every item in this collection has unique _id and unique register_id.

I also have RatingLog for the cats and now I want to sort cats by that rating. From the RatingLog we can get array of cat register_ids ordered by rating:

[
    "20gnr3g",
    "3412r23",
    "221n415",
    "Q0g4rEg",
    ...
]

Now I want to use that array for sorting the Cats collection. Note that all entries in Cats may not have been rated, but all entries should be returned: First in RatingLog order and rest items by created_at field.

My publication code atm:

Meteor.publish('allCats', function(sortType) {
    if (sortType == 'rating') {
        return Cats.find({}, {sort: { 
            // How to sort by registerIds array???
        }});
    }

    return Cats.find({}, {sort: { created_at: 1 }});
});

So the question is: How to sort Cats collection by register_ids array and created_at field in publication?

Upvotes: 1

Views: 60

Answers (1)

Ashley Reid
Ashley Reid

Reputation: 488

If you want to do it reactively, you can either store the ratings with the cats or map them later in Javascript. A third, non-reactive option would probably be to use the MongoDB aggregation pipeline with join's (added in 3.2), but I'm not familiar enough with that to provide an example at this time.

If you store the ratings with the cats, it becomes very simple:

Meteor.publish('allCats', function (sortType) {
  /**
   * Assuming that Cats contains contents of the form:
   *  {
   *    "_id" : "rRq76LxsnPfmuh9DD",
   *    "register_id" : "20gnr3g",
   *    "name" : "Meow",
   *    "created_at": ...,
   *    "rating": 123,
   *    ...
   *  }
   **/
  if (sortType == 'rating') {
    return Cats.find({}, { sort: { rating: 1 } });
  }

  return Cats.find({}, { sort: { created_at: 1 } });
});

If you choose to keep RatingLog separate and want the results to be reactive, you will need to perform your sorting on the client-side; publish both allCats and allCatRatings:

Meteor.publish('allCats', function() {
    return Cats.find({}, {sort: { created_at: 1 }});
});

Meteor.publish('allCatRatings', function() {
    return RatingLog.find();
});

After subscribing to both of those, retrieve the ratings and perform the sort:

Template.myTemplate.helpers({
  cats() {
    const sortType = Template.instance().sortType;
    if (sortType === 'rating') {
      return getCatsByRating();
    }
    return getCatsByCreationDate();
  }
});

function getCatsByCreationDate() {
  return Cats.find({}, {sort: { created_at: 1 }});
}

function getCatsByRating() {
  /**
   * Assuming that RatingLog contains contents of the form { _id:  catId, rating: 123 }
   **/
  const ratings = {};
  RatingLog.find().forEach(entry=>ratings[entry._id] = entry.rating);

  const defaultRating = 0;
  return Cats.find().fetch().sort(function (catA, catB) {
    const catARating = ratings[catA._id] || defaultRating;
    const catBRating = ratings[catB._id] || defaultRating;
    return catARating - catBRating;
  });
}

Upvotes: 1

Related Questions