holderdeord/hdo-site

View on GitHub
lib/hdo/search/searcher.rb

Summary

Maintainability
A
1 hr
Test Coverage
module Hdo
  module Search
    class Searcher
      BOOST = {
        Issue.index_name           => 0,
        Party.index_name           => 3.5,
        Representative.index_name  => 2,
        Promise.index_name         => 1,
        Proposition.index_name     => 1,
        ParliamentIssue.index_name => 1
      }

      def initialize(query, size = nil)
        @query  = query.blank? ? '*' : query.strip
        @size   = size || 100
      end

      def all
        q = {
          query: {
            query_string: {
              query: @query,
              default_operator: 'AND',
            }
          },
          indices_boost: BOOST,
          sort: ['_score']
        }

        indices = (SearchSettings.models - [Issue, Vote]).map(&:index_name)

        opts = {
          index: indices,
          type: nil,
          size: @size,
        }

        response_from { Issue.search(q, opts) }
      end

      def promises
        q = {
          query: {
            query_string: {query: @query, default_operator: 'AND'}
          },
          filter: {term: {parliament_period_name: '2013-2017' } },
          sort: ['_score']
        }

        opts = {
          size: @size,
        }

        response_from { Promise.search(q, opts) }
      end

      def autocomplete
        q = {
          query: {
            query_string: {query: "#{@query}* #{@query}", default_operator: 'OR'}
          },
          sort: ['_score']
        }

        opts = {
          index: [Representative].map(&:index_name),
          type: nil,
          size: 25,
        }

        response_from { Representative.search(q, opts) }
      end

      def propositions(params = {})
        q = {
          query: {
            query_string: {query: @query}
          },
          sort: [{vote_time: 'desc'}]
        }

        q[:filter] = {term: {status: params[:status]}} if params[:status].present?

        opts = {
          size: @size
        }

        response_from { Proposition.search(q, opts) }
      end

      def proposition_histogram(opts = {})
        start_time = opts[:start] || 6.months.ago

        q = {
          facets: {
            counts: {
              date_histogram: { field: 'vote_time', interval: '1w' },
              global: true,
              facet_filter: {
                fquery: {
                  query: {
                    filtered: {
                      query: {query_string: {query: @query }}
                    },
                    filter: {
                      bool: {
                        must: [
                          {match_all: {}},
                          {terms: {_type: ['proposition']}},
                          {
                            range: {
                              vote_time: { from: (start_time.to_f * 1000).to_i }
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }

        response_from { Proposition.search(q, size: 0) }
      end

      private

      SEARCH_ERRORS = [
        Elasticsearch::Transport::Transport::Errors::InternalServerError,
        Errno::ECONNREFUSED
      ]

      def response_from(&blk)
        Response.new(yield)
      rescue *SEARCH_ERRORS => ex
        Rails.logger.error "search failed, #{ex.class} #{ex.message}"
        Response.new(nil, ex)
      end

      class Response
        attr_reader :exception, :response

        def initialize(response, exception = nil)
          @response = response
          @exception = exception
        end

        def facets
          if @response
            Hashie::Mash.new @response.response['facets']
          else
            {}
          end
        end

        def results
          if @response
            @response.results
          else
            []
          end
        end

        def records
          if @response
            @response.records
          else
            []
          end
        end

        def success?
          !@exception
        end

        def down?
          @exception.kind_of? Errno::ECONNREFUSED
        end

        def error_message
          @exception && @exception.message
        end
      end # Response

    end # Searcher
  end
end