Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing MongoDB 2.4's full text search in a Meteor app

I'm looking into adding full text search to a Meteor app. I know MongoDB now supports this feature, but I have a few questions about the implementation:

  • What's the best way to enable the text search feature (textSearchEnabled=true) in a Meteor app?
  • Is there a way to add an index (db.collection.ensureIndex()) from within your app?
  • How can you run a Mongo command (i.e. db.quotes.runCommand( "text", { search: "TOMORROW" } )) from within a Meteor app?

Since my goal is to add search to Telescope, I'm searching for a "plug-and-play" implementation that requires minimal command line magic and could even work on Heroku or *.meteor.com.

like image 559
Sacha Avatar asked Jun 18 '13 02:06

Sacha


2 Answers

The simplest way without editing any Meteor code is to use your own mongodb. Your mongodb.conf should look something like this (on Arch Linux it is found at /etc/mongodb.conf)

bind_ip = 127.0.0.1
quiet = true
dbpath = /var/lib/mongodb
logpath = /var/log/mongodb/mongod.log
logappend = true
setParameter = textSearchEnabled=true

The key line is setParameter = textSearchEnabled=true, which, as it states, enables text search.

Start mongod up

Tell meteor to use your mongod not its own by specifying the MONGO_URL environmental variable.

MONGO_URL="mongodb://localhost:27017/meteor" meteor

Now say you have collection called Dinosaurs declared say in collections/dinosaurs.js

Dinosaurs = new Meteor.Collection('dinosaurs');

To create an text index for the collection create a file server/indexes.js

Meteor.startUp(function () {
    search_index_name = 'whatever_you_want_to_call_it_less_than_128_characters'

    // Remove old indexes as you can only have one text index and if you add 
    // more fields to your index then you will need to recreate it.
    Dinosaurs._dropIndex(search_index_name);

    Dinosaurs._ensureIndex({
        species: 'text',
        favouriteFood: 'text'
    }, {
        name: search_index_name
    });
});

Then you can expose the search through a Meteor.method, for example in the file server/lib/search_dinosaurs.js.

// Actual text search function
_searchDinosaurs = function (searchText) {
    var Future = Npm.require('fibers/future');
    var future = new Future();
    Meteor._RemoteCollectionDriver.mongo.db.executeDbCommand({
        text: 'dinosaurs',
        search: searchText,
        project: {
          id: 1 // Only take the ids
        }
     }
     , function(error, results) {
        if (results && results.documents[0].ok === 1) {
            future.ret(results.documents[0].results);
        }
        else {
            future.ret('');
        }
    });
    return future.wait();
};

// Helper that extracts the ids from the search results
searchDinosaurs = function (searchText) {
    if (searchText && searchText !== '') {
        var searchResults = _searchEnquiries(searchText);
        var ids = [];
        for (var i = 0; i < searchResults.length; i++) {
            ids.push(searchResults[i].obj._id);
        }
        return ids;
    }
};

Then you can publish only documents that have been searched for in 'server/publications.js'

Meteor.publish('dinosaurs', function(searchText) {
    var doc = {};
    var dinosaurIds = searchDinosaurs(searchText);
    if (dinosaurIds) {
        doc._id = {
            $in: dinosaurIds
        };
    }
    return Dinosaurs.find(doc);
});

And the client side subscription would look something like this in client/main.js

Meteor.subscribe('dinosaurs', Session.get('searchQuery'));

Props to Timo Brinkmann whose musiccrawler project was the source of most this knowledge.

like image 60
ElDog Avatar answered Nov 20 '22 01:11

ElDog


To create a text index and try to add like this I hope so it will be useful if there is still problem comment

From docs.mongodb.org:


Append scalar index fields to a text index, as in the following example which specifies an ascending index key on username:

db.collection.ensureIndex( { comments: "text",
                             username: 1 } )

Warning You cannot include multi-key index field or geospatial index field.

Use the project option in the text to return only the fields in the index, as in the following:

db.quotes.runCommand( "text", { search: "tomorrow",
                                project: { username: 1,
                                           _id: 0
                                         }
                              }
                    )

Note: By default, the _id field is included in the result set. Since the example index did not include the _id field, you must explicitly exclude the field in the project document.

like image 30
Sheel Avatar answered Nov 20 '22 02:11

Sheel