Search code examples
ruby-on-railselasticsearchelasticsearch-model

rails elasticsearch searching a nested json field


I'm using the elasticsearch-model gem for a model that has a has_many relationship. To match the documentation, let's say the model is Article and the relationship is has_many categories. So I wrote the customer serializer as follows (directly from the documentation):

def as_indexed_json(options={})
  self.as_json(
    include: { categories: { only: :title},
             })
end

The serialization seems to work. The result of an example Article's as_indexed_json contains a "categories" => {"title"=> "one", "title"=> "two", "title"=> "three"} block.

What I'm struggling with, and haven't been able to find in the documentation, is how to search this nested field.

Here's what I've tried:

From the elasticsearch documentation on nested query I figured it ought to look like this:

r = Article.search query: {
    nested: {
        path: "categories",
        query: {match: {title: "one"}}
    }
}

but when I do r.results.first I get an error: nested object under path [categories] is not of nested type]...

I've tried adding changing the one line in the serializer to be: include: { categories: { type: "nested", only: :title} but that doesn't change anything, it still says that categories is not of a nested type.

Of course, I tried just querying the field without any nesting too like this:

r = Article.search query: {match: {categories: 'one'}}

But that just doesn't return any results.

Full text search like this:

r = Article.search query: {match: {_all: 'one'}}

Returns results, but of course I only want to search for 'one' in the categories field.

Any help would be much appreciated!


Solution

  • Rails don't create nesting mapping in elasticsearch. Elasticsearch is making the categories as object rather than nested child using dynamic mapping ("When Elasticsearch encounters a previously unknown field in a document, it uses dynamic mapping to determine the datatype for the field and automatically adds the new field to the type mapping as an object without nesting them"). For making them nested object you need to create mapping again in elasticsearch with categories as nested type, notice type as nested of category.

     PUT /my_index 
    {
      "mappings": {
        "article": {
          "properties": {
            "categories": {
              "type": "nested", 
              "properties": {
                "name":    { "type": "string"  },
                "comment": { "type": "string"  },
                "age":     { "type": "short"   },
                "stars":   { "type": "short"   },
                "date":    { "type": "date"    }
              }
            }
          }
        }
      }
    }
    

    After this you can either reindex data from client or if you want zero downtime read here.

    P.S: You have to delete the old mapping for particular index before creating a new one.