0

I'm having some difficulty with Elastic Search and Tire not returning any results. I'm using Ruby 1.9.3 and Rails 3.2.11.

In my controller I'm calling:

@location_id = 1
@listings = Listing.search(params.merge!(location_id: @location_id))

In my listing model I have

mapping do
    indexes :id, type: 'integer'
    ...
    indexes :author do
      indexes :location_id,     :type => 'integer', :index => :not_analyzed
      ...
end

def self.search(params={})
      tire.search(load: true, page: params[:page], per_page: 20) do |search|

      search.query  { string params[:query], :default_operator => "AND" } if params[:query].present?
      search.filter :range, posted_at: {lte: DateTime.now}

      search.filter :term, "author.location_id"       => params[:location_id]
end

I have 300 results which all have the location_id of 1 in the database so I can't seem to figure out why it's returning a nil set? If I comment out the author.location_id search filter line it returns all other results as expected?

simonmorley
  • 2,810
  • 4
  • 30
  • 61
Ryan
  • 23
  • 3

1 Answers1

1

There are several things which needs to be adressed in a situation like yours. Let's start with a fully working code:

require 'active_record'
require 'tire'
require 'logger'

# Tire.configure { logger STDERR }
# ActiveRecord::Base.logger = Logger.new(STDERR)

Tire.index('articles').delete

ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:" )
ActiveRecord::Schema.define(version: 1) do
  create_table :articles do |t|
    t.string :title
    t.integer :author_id
    t.date    :posted_at
    t.timestamps
  end
  create_table :authors do |t|
    t.string  :name
    t.integer :number, :location_id
    t.timestamps
  end
  add_index(:articles, :author_id)
  add_index(:authors, :location_id)
end

class Article < ActiveRecord::Base
  belongs_to :author, touch: true
  self.include_root_in_json = false

  include Tire::Model::Search
  include Tire::Model::Callbacks

  mapping do
    indexes :title

    indexes :author do
      indexes :location_id, type: 'integer'
    end
  end

  def self.search(params={})
    tire.search load: {include: 'author'} do |search|
      search.query do |query|
        query.filtered do |f|
          f.query { params[:query].present? ? match([:title], params[:query], operator: 'and') : match_all }
          f.filter :range, 'posted_at' => { lte: DateTime.now }
          f.filter :term,  'author.location_id' => params[:location_id]
        end
      end
    end
  end

  def to_indexed_json
    to_json( only: ['title', 'posted_at'], include: { author: { only: [:location_id] } } )
  end
end

class Author < ActiveRecord::Base
  has_many :articles

  after_touch do
    articles.each { |a| a.tire.update_index }
  end
end

# -----

Author.create id: 1, name: 'John', location_id: 1
Author.create id: 2, name: 'Mary', location_id: 1
Author.create id: 3, name: 'Abby', location_id: 2

Article.create title: 'Test A', author: Author.find(1), posted_at: 2.days.ago
Article.create title: 'Test B', author: Author.find(2), posted_at: 1.day.ago
Article.create title: 'Test C', author: Author.find(3), posted_at: 1.day.ago
Article.create title: 'Test D', author: Author.find(3), posted_at: 1.day.from_now

Article.index.refresh

# -----

articles = Article.search query: 'test', location_id: 1
puts "", "Documents with location:1", '-'*80
articles.results.each { |a| puts "* TITLE: #{a.title}, LOCATION: #{a.author.location_id}, DATE: #{a.posted_at}" }

articles = Article.search query: 'test', location_id: 2
puts "", "Documents with location:2", '-'*80
articles.results.each { |a| puts "* TITLE: #{a.title}, LOCATION: #{a.author.location_id}, DATE: #{a.posted_at}" }
puts "(NOTE: 'D' is missing, because is not yet posted)"

articles = Article.search query: 'test b', location_id: 1
puts "", "Documents with query:B and location:1", '-'*80
articles.results.each { |a| puts "* TITLE: #{a.title}, LOCATION: #{a.author.location_id}, DATE: #{a.posted_at}" }

First, it's usually a good idea to create an isolated, extracted case like this.

In your example code, I assume you have a relationship Listing belongs_to :author. You need to properly define the mapping and serialization, which I again assume you did.

As for the query itself:

  • Unless you're using faceted navigation, use the filtered query, not top level filters, as in my example code.

  • Do not use the string query, unless you really want to expose all the power (and fragility!) of the Lucene query string query to your users.

  • Use the match query, as your "generic purpose" query -- Tire sprinkles some sugar on top of it, allowing to easily create multi_match queries, etc

  • The filter syntax in your example is correct. When the filter method is called multiple times in Tire, it creates and and filter.

Uncomment the Tire logging configuration (and possibly also the ActiveRecord logging), to see what the code is doing.

karmi
  • 14,059
  • 3
  • 33
  • 41