Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use mongoosastic for an autocomplete

I'm trying to create an autocomplete using mongoosastic and Elastic Search, and so far, I have been able to create it using sense but I'm having trouble porting it to mongoosastic.

I followed this tutorial from ElasticSearch docs, and I was able to achieve what I wanted using "sense" with a mapping that looks like this:

PUT storys/story/_mapping
{
    "story" : { 
        "properties": {
            "description": {
                "type": "string"
            },
            "title": {
                "type" : "completion",
                "index_analyzer": "simple",
                "search_analyzer": "simple"
            }
        }  
    }
}

and a query like this:

GET storys/_suggest
{
    "story-suggest": {
        "text": "bow",
        "completion": {
            "field": "title"
        }
    }
}

However, I'm having trouble porting this to mongoosastic. I tried the following approach:

    var StorySchema = new Schema({
        title:{
            type: String, es_type:'completion', es_index_analyzer: 'simple', es_search_analyzer: 'simple', es_payloads: true
        },
        description: { 
            type: String
        }
    });

StorySchema.plugin(mongoosastic);

And when querying from the server controller:

Story.search({ 
    query: {
        "match": { title : req.query.q }
    },
    suggest: {
            "my-title-suggestions-1" :{
                text: req.query.q,
                completion: {
                    field: 'title'
                }
            }
        }
});

I understand that when I use "sense", I'm using the _suggest endpoint, and that's why the "story-suggest" query works. However, when using mongoosastic, I'm limited to use the .search({}) for querying which acts like _search I suppose. However, I cannot seem to find a way to accomplish the _suggest behavior I'm seeking for an autocomplete, and I keep getting parsing errors in ElasticSearch when I try to do a query with a suggest.

Is there a way to accomplish what I'm trying to do either with mongoosastic or elastic search?

I have tried doing this using "sense" but even though I get the suggestions for "autocomplete" I also get a bunch of SearchParseExceptions:

GET _search
{
    "query": {
       "match": { title : "bow" }
    },
    "suggest": {
        "story-suggest": {
            "text": "bow",
            "completion": {
                "field": "title"
            }
        }
    }
}
like image 311
cyber ice Avatar asked Sep 08 '15 15:09

cyber ice


2 Answers

Here is a complete solution for a basic autocomplete:

  1. Add the necessary parameters to your schema:

    var TagSchema = new Schema({
        name: {
            type: String,
            unique: true,
            required: true,
            es_type: 'completion',
            es_index_analyzer: 'simple',
            es_search_analyzer: 'simple',
            es_payloads: true
        }
    });
    
  2. Add the plugin to your schema and create the model:

    TagSchema.plugin(mongoosastic);
    var Tag = mongoose.model('Tag', TagSchema);
    
  3. Use the create mapping method to register the mapping with ES. If you don't do this step your index will get registered with defaults, when the first document is created and indexed:

    Tag.createMapping(function(err, mapping) {
        if (err) {
            console.log('error creating mapping (you can safely ignore this)');
            console.log(err);
        } else {
            console.log('mapping created!');
            console.log(mapping);
        }
    });
    
  4. *Optional - Index existing documents in your database:

    var stream = Tag.synchronize(),
        count = 0;
    
    stream.on('data', function(err, doc) {
        count++;
    });
    stream.on('close', function() {
        console.log('indexed ' + count + ' documents!');
    });
    stream.on('error', function(err) {
        console.log(err);
    });
    
  5. Search with an empty query body and supply two options - 1) Use the ES suggest query which uses the "es_completion" field we created in our schema; 2) size = 0 so that no tags are returned by the empty body query.

    Tag.search(null, {
        suggest: {
            "tag-suggest": {
                "text": "aTermToAutocomplete",
                "completion": {
                    "field": "name"
                }
            }
        },
        "size" : 0
    },
    function(err, results) {
        if (err) {
            return console.log(JSON.stringify(err, null, 4));
        }
        return console.log(JSON.stringify(results, null, 4));
    });
    
like image 58
Huston Hedinger Avatar answered Nov 20 '22 02:11

Huston Hedinger


I have found a way. With the latest update to mongoosastic library you could just do something like this:

GET _search
{
    "query": {
       "match_all": {}
    },
    "suggest": {
        "story-suggest": {
            "text": "bow",
            "completion": {
                "field": "title"
            }
        }
    }
}

This is because the new mongoosastic version supports suggesters. Just be aware of what you are doing, specially in the query part, as a "match_all" would match all the documents and give you an unnecessarily big response; but whatever you place in the "text" part of your suggester would return you the words to autocomplete.

Also be aware that in order for this to work, you need to use the createMapping method in your model definition, like this:

var StorySchema = new Schema({
    title:{
        type: String, es_type:'completion', es_index_analyzer: 'simple', es_search_analyzer: 'simple', es_payloads: true
    },
    description: { 
        type: String
    }
});

StorySchema.plugin(mongoosastic);
var Story = mongoose.model('Story', StorySchema);

Story.createMapping({}, function(err, mapping){
    // ...
});
like image 43
cyber ice Avatar answered Nov 20 '22 02:11

cyber ice