mysociety/alaveteli

View on GitHub
lib/typeahead_search.rb

Summary

Maintainability
A
0 mins
Test Coverage
class TypeaheadSearch
  include ConfigHelper

  attr_accessor :query, :model, :page, :per_page, :wildcard, :run_search

  def initialize(query, opts = {})
    @query = query
    @model = opts.fetch(:model)
    @page = opts.fetch(:page, 1)
    @per_page = opts.fetch(:per_page, 25)
    @exclude_tags = opts.fetch(:exclude_tags, [])
    @wildcard = true
    @run_search = true
  end

  def xapian_search
    check_query
    return nil unless @run_search

    ActsAsXapian.readable_init
    old_default_op = ActsAsXapian.query_parser.default_op
    ActsAsXapian.query_parser.default_op = Xapian::Query::OP_OR
    begin
      xapian_search = run_query
    rescue ActsAsXapian::UnhandledRuntimeError => e
      # Wildcard expands to too many terms
      Rails.logger.warn "Wildcard query '#{query}' caused: #{e.message.force_encoding('UTF-8')}"
      @wildcard = false
      xapian_search = run_query
    end
    ActsAsXapian.query_parser.default_op = old_default_op
    xapian_search
  end

  def options
    {
      offset: (@page - 1) * @per_page,
      limit: @per_page,
      sort_by_prefix: nil,
      sort_by_ascending: true,
      collapse_by_prefix: collapse?,
      wildcard: @wildcard,
      model: @model
    }
  end

  private

  def check_query
    # Maximum length of a key is 252 bytes
    @query = @query.mb_chars.limit(252).strip
    # don't wildcard search a short end word
    query_words = @query.split
    if query_words.last && query_words.last.strip.length < 3
      query_words.pop
      @query = query_words.join
      @wildcard = false
    end

    # don't run a search if there's no query
    @run_search = false if @query.blank?
  end

  def run_query
    user_query = ActsAsXapian.query_parser.parse_query(prepared_query, flags)
    ActsAsXapian::Search.new([@model], @query, options, user_query)
  end

  def flags
    if @wildcard
      default_flags | Xapian::QueryParser::FLAG_WILDCARD
    else
      default_flags
    end
  end

  def default_flags
    Xapian::QueryParser::FLAG_LOVEHATE |
      Xapian::QueryParser::FLAG_SPELLING_CORRECTION
  end

  def prepared_query
    # Since acts_as_xapian doesn't support the Partial match flag, we work around it
    # by making the last word a wildcard, which is quite the same
    query = if @wildcard
      "#{@query.strip}*"
    else
      @query
    end
    if @exclude_tags
      tag_string = @exclude_tags.map { |tag| "-tag:#{tag}" }.join(" ")
      query = "#{query} #{tag_string}"
    end
    query
  end

  def collapse?
    if @model == PublicBody
      nil
    elsif @model == InfoRequestEvent
      'request_collapse'
    end
  end
end