Best practice for using hashtags

I was wondering how a best practice for using hashtags would look like.

Situation:

  • A post item to which you can add an variable amount of hashtags.
  • Ability to find all posts with a certain hashtag.

Storing the hashtags in an array would be inefficient when using a search on the whole collection.

How best to approach this?

cheers,
godo

You have more than few options for that. In order of increasing scalability/performance:

  • Don’t do anything special, store the hashtags within the post body and do either one of regex or text search on the post body
  • Store the hashtags within the post body, and also use one of the search packages that makes use of elastic search to search for posts containing a given hashtag
  • Store the hashtags in an array along with the post document, and just do a standard find on the hashtag
  • Store a separate collection of (unique) hashtags and their related post id’s and insert/update within afterinsert/afterupdate/afterremove hooks on the post collection (or if you don’t want realtime, you could create a job that updates the hashtag colllection very N mins/hrs)
  • Use one of the redis packages to store key value pairs of hashtag/postid and query on those

I’m sure you can refine these further, even come up with other strategies.

4 Likes

So say I go with this easy search package (https://atmospherejs.com/matteodem/easy-search)

My post document:

{
    "_id" : "wci7ZZWYc2ZNCtRr6",
    "title" : "Tioning nyo iper sidethe toino iesto tioningsre.",
    "text" : "Tii getsi retem o lytrii getanan...",
    "tags" : [ 
        "Olyly", 
        "Ryvi", 
        "Touningan", 
        "Fuliof", 
        "Berout", 
        "Sometionprothe", 
        "Encon", 
        "Eso"
    ],
    "comments" : 0
}

initiate easy search:

Articles = new Meteor.Collection('articles');
// name is the field of the documents to search over
Articles.initEasySearch('tags');

Is this how a normal query without the easy search package would look like?

var tag = "Encon"
Articles.find({tags: {$in: tag} };

I have not used that package so it is for you to find out but there is a link to the documentation on the readme which points to http://matteodem.github.io/meteor-easy-search/getting-started/

Did you take a look at that?

My solution:

(with [easy search package][1])

  1. create a collection for tags

Tags = new Mongo.Collection('tags');

  1. create tags
Meteor.methods({
  newTag: function(arg) {
    arg = arg.replace(/\#/g, ''); // remove all #
    arg = arg.replace(/\s+/g, ''); // remove spaces
    var exists = Tags.findOne({name: arg});
    if (!exists) {
      Tags.insert({name: arg, createdAt: Date.now(), uses: 0, clicks: 0});
    } else {throw new Meteor.Error('Tag already exists.');}
  }
});
  1. add tags to articles
Meteor.methods({

  addTagToArticle: function(arg) {
    var tag = Tags.findOne(arg.tag_id);
    if (tag) {
      var article = Articles.findOne(arg.article);
      if (article) {
        article = article.tags;
        var hasTag = article.indexOf(tag._id); // returns index or if not present, -1
        if (hasTag < 0) {
          Articles.update(arg.article, {$push: {tags: tag._id}}, function(err, res) {
            if (err)  {
              throw new Meteor.Error('Something went wrong. ' + err);
            } else {
              Articles.update(article._id {$push: {tagsFull: tag.name}}); // used for EasySearch
              Tags.update(tag._id, {$inc: {uses: 1}});
            }
          });
        } else {throw new Meteor.Error('Keyword already belongs to this article.');}
      } else {throw new Meteor.Error('Article does not exist');}
    } else {throw new Meteor.Error('Keyword does not exist');}
  }
  1. setup EasySearch
Articles.initEasySearch('articles', {
  "use": "mongo-db"
});

EasySearch.createSearchIndex('articles', {
    'collection': Articles, // instanceof Meteor.Collection
    'field': ['title', 'subtitle', 'tagsFull'], // array of fields to be searchable
    'limit': 10,
    'use': 'mongo-db',
    // 'convertNumbers': true,
    'sort': function() {
        return { 'createdAt': -1 };
    }
});
  1. create template
<li class="border-right"><i class="fa fa-search"></i>{{> esInput index="articles" placeholder="search"}}
  <div id="search-dropdown" class="dropdown-menu" role="menu" data-toggle="dropdown-menu">
    <div class="all-search-list-items">
      <ul>
        {{#esEach index="articles"}}
          {{> searchListItem}}
        {{/esEach}}
      </ul>
      {{#ifEsHasNoResults index="articles"}}
      <div class="message">No results...</div>
      {{/ifEsHasNoResults}}

      {{#ifEsIsSearching index="articles"}}
      <div class="spinner">{{> spinner}}</div>
      {{/ifEsIsSearching}}
    </div>
  </div>
</li>

Maybe this will help someone :wink:
[1]: https://atmospherejs.com/matteodem/easy-search

2 Likes

@godo15 Thank you very much for sharing!

Could you please show how you set up the field ‘tagsFull’ in step 4?

I don’t understand your question.
You just need the field if you are using EasySearch (denormalize data).
Otherwise you could not search for tags.
I’m pushing the full tag name into the array field in step 3)

Sorry. It’s my bad. I didn’t see it in step 3 the first time. I get it now. Thank you very much!!