Search code examples
ruby-on-railselasticsearchelasticsearch-model

Elasticsearch rails/ Elasticsearch model search model association


I have a model named Movie that looks like this:

class Movie < ActiveRecord::Base
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks

  has_many :actors, after_add: [ lambda {|a,c| a.__elasticsearch__.index_document}],
                    after_remove: [ lambda {|a,c| a.__elasticsearch__.index_document}]

  settings index: {number_of_shards: 1} do
    mappings dynamic: 'false' do
      indexes :title, analyzer: 'snowball', boost: 100
      indexes :actors
    end
  end

   def as_indexed_json(options={})
    self.as_json(
      include: {
          actors: { only: :name}
      }
    )
  end
end

When i do Movie.first.as_indexed_json , I get:

{"id"=>6, "title"=>"Back to the Future ", 
"created_at"=>Wed, 03 Dec 2014 22:21:24 UTC +00:00, 
"updated_at"=>Fri, 12 Dec 2014 23:40:03 UTC +00:00, 
"actors"=>[{"name"=>"Michael J Fox"}, {"name"=>"Christopher Lloyd"}, 
{"name"=>"Lea Thompson"}]}

but when i do Movie.search("Christopher Lloyd").records.first i get: => nil .

What changes can i make to the index to search movies associated with the searched actor?


Solution

  • I used filtering query to solve this, first I created an ActiveSupport::Concern called searchable.rb, the concern looks like this:

    module Searchable
      extend ActiveSupport::Concern
      included do
        include Elasticsearch::Model
        include Elasticsearch::Model::Callbacks
    
        index_name [Rails.application.engine_name, Rails.env].join('_')
    
        settings index: { number_of_shards: 3, number_of_replicas: 0} do
        mapping do
          indexes :title, type: 'multi_field' do
            indexes :title, analyzer: 'snowball'
            indexes :tokenized, analyzer: 'simple'
          end
          indexes :actors, analyzer: 'keyword'
        end
        def as_indexed_json(options={})
          hash = self.as_json()
          hash['actors'] = self.actors.map(&:name)
          hash
        end
    
        def self.search(query, options={})
          __set_filters = lambda do |key, f|
            @search_definition[:post_filter][:and] ||= []
            @search_definition[:post_filter][:and]  |= [f]
          end
    
          @search_definition = {
            query: {},
    
            highlight: {
              pre_tags: ['<em class="label label-highlight">'],
              post_tags: ['</em>'],
              fields: {
                title: {number_of_fragments: 0}
              }
            },
            post_filter: {},
    
            aggregations: {
              actors: {
                filter: {bool: {must: [match_all: {}]}},
                aggregations: {actors: {terms: {field: 'actors'}}}
              }
            }
          }
    
            unless query.blank?
            @search_definition[:query] = {
              bool: {
                should: [
                  {
                    multi_match: {
                      query: query,
                      fields: ['title^10'],
                      operator: 'and'
                    }
                  }
                ]
              }
            }
          else
            @search_definition[:query] = { match_all: {} }
            @search_definition[:sort] = {created_at: 'desc'}
           end
    
           if options[:actor]
            f = {term: { actors: options[:taxon]}}
           end
    
           if options[:sort]
            @search_definition[:sort] = { options[:sort] => 'desc'}
            @search_definition[:track_scores] = true
           end
           __elasticsearch__.search(@search_definition)
        end
      end
    end
    

    I have the above concern in the models/concerns directory. In movies.rb I have:

    class Movie < ActiveRecord::Base
      include Searchable
    end
    

    In movies_controller.rb I am doing searching on the index action and the action looks like this:

    def index 
      options = {
      actor: params[:taxon],
        sort: params[:sort]
      }
      @movies = Movie.search(params[q], options).records
    end 
    

    Now when i go to http://localhost:3000/movies?q=future&actor=Christopher I get all records which have the word future on their title and has an actor with a name Christopher. You can have more than one filter as shown by the expert template of the example application templates found here .