ElasticSearch, Tire - refactor autocomplete method for multiple resources I am using ElasticSearch and Tire for search in my app across all models.


I would like to refactor autocomplete method because it looks to complex for me.
Am I using a good pattern for searching across model?


Model

class Link &lt; ActiveRecord::Base
  include Tire::Model::Search
  include Tire::Model::Callbacks

  tire.mapping do
    indexes :id, :type =&gt; 'string', :index =&gt; :not_analyzed
    indexes :title, analyzer: 'snowball', boost: 100
  end

  def to_indexed_json
    to_json(
      only: [:id, :title]
    )
  end
  ...
end


Controller

class SearchController &lt; ApplicationController
  def new
    search = SearchesCatalog.new(params[:term])

    render json: search.autocomplete.to_json
  end
end


Lib

class SearchesCatalog
  attr_reader :options

  def initialize(options = {})
    @options = HashWithIndifferentAccess.new(options)
    @resources = [ :questions, :answers, :links, :events, past_events, :reviews ]
  end

  def results
    term = options[:term]

    Tire.search @resources do
      query { string "#{term}" }
    end.results
  end

  def autocomplete
    array = []
    results.each do |result|
      case result.type
    when "past_event"
      array &lt;&lt; { title: result.content, label: "#{result.content} &lt;span class='search-type'&gt;PastEvent&lt;/span&gt;", value: "/past_events/#{result.id}" }
    when "event"
      array &lt;&lt; { title: result.title, label: "#{result.title} &lt;span class='search-type'&gt;Event&lt;/span&gt;", value: "/events/#{result.id}" }
    when "topic"
      array &lt;&lt; { title: result.name, label: "#{result.name} &lt;span class='search-type'&gt;Topic&lt;/span&gt;", value: "/#{result.app_id}/t/#{result.id}" }
    when "link"
      array &lt;&lt; { title: result.title, label: "#{result.title} &lt;span class='search-type'&gt;Link&lt;/span&gt;", value: "/links/#{result.id}" }
    when "question"
      array &lt;&lt; { title: result.content, label: "#{result.content[0..100]} &lt;span class='search-type'&gt;Question&lt;/span&gt;" }
    when "answer"
    array &lt;&lt; { title: result.content, label: "#{result.content[0..100]} &lt;span class='search-type'&gt;Answer&lt;/span&gt;" }
    end
    array
  end
end


Javascript

// set up the search bar
$("#question_search").autocomplete({
  source:$('#question_search').data('source'),
  html: true,
  minlength: 1,
  appendto: "#search_results",
  select: function( event, ui ) {
    window.location=ui.item.value;
    return false;
  },
  focus: function( event, ui ) {
    $("#question_search").val(ui.item.title);
    return false;
  },
  open: function( event, ui ) {
    $('#search_results')
    .find('a:first').addclass('first').end()
    .find('a:last').addclass('last');
  }
});
I'll take autocomplete and leave the rest for others. Some notes:


Never write the pattern "empty array" + each + push + return array. That's a map (more on functional programming here)
Is this "lib" thing really in lib/? it should go to app/. In lib you only have generic code that may be reused across applications. 
Don't write routes by hand, ever, that's terrible practice, use the helper router methods provided by Rails.
Don't write tags by hand, use content_tag.
Abstract and simplify by identifying repeated patterns in your code.


I'd write:

def autocomplete
  results.map do |result|
    title, caption, route = case result.type.to_sym
    when :past_event
      [result.content, "PastEvent", past_event_path(result)]
    ...
    end

    label = "%s %s" % [title, content_tag(:span, caption, class: 'search-type')]
    {title: title, label: label, value: route}
  end
end