3

Using Tire with Mongoid, I'm having trouble figuring out how to structure a query for finding events with ElasticSearch. In particular, I'm trying to find events that users are watching in addition to events with performers the user follows:

# app/models/event.rb
class Event
  include Mongoid::Document
  include Tire::Model::Search
  include Tire::Model::Callbacks

  field :name, type: String

  has_and_belongs_to_many :performers
  has_many :watchers, class_name: 'User'

  mapping do
    indexes :name
    indexes :watcher_ids, type: 'string', index: :not_analyzed
    indexes :performer_ids, type: 'string', index: :not_analyzed
  end
end

The following query works only for either watchers or performers.

Tire.search 'events' do
  query do
    string params[:query]
    # Only one of these will work at a time:
    terms :performer_ids, current_user.followed_performers.collect(&:id).map(&:to_s)
    terms :watcher_ids, [current_user.id.to_s]
  end
end
  • small edit because I typed my example wrong.

Here's a solution that seems to be "working"... but feels wrong

Tire.search('events') do
  query do
    boolean do
      should { string params[:query] }
      should { string "performer_ids:#{current_user.followed_performers.collect(&:id).map(&:to_s).join(',')}" }
      should { string "watcher_ids:#{current_user.id.to_s}" }
    end
  end
end
jeremywoertink
  • 2,281
  • 1
  • 23
  • 29

2 Answers2

4

You're on a right path, but as advised by Russ Smith, you need to use a filter DSL.

Now, if you just repeatedly call filter, you'll perform a union: AND. If you want to return either events user is watching or by performers the user follows, you have to use a or filter.

Also, for best performance, use the filtered query, as opposed to the top level filter -- in the former case, filters run first, slicing your corpus and perform queries only on this subset.

The code should look like this:

Tire.search 'events' do
  query do
    filtered do
      query do
        string params[:query]
      end
      filter :or, terms: { organization_ids: current_user.followed_performers.collect(&:id).map(&:to_s) },
                  terms: { watcher_ids:      [current_user.id.to_s] }
    end
  end
end

See the integration tests for more examples:

karmi
  • 14,059
  • 3
  • 33
  • 41
  • Thanks guys, I've tried both examples, and my tests fail. @karmi when trying your example, it returns the watcher_ids but not the other terms... "filter":{"and":[{"or":{"terms":{"watcher_ids":[.... – jeremywoertink Dec 22 '12 at 18:38
  • ok, I've taken some time to work this out, but I'm still not getting the results I'm looking for. I'm not using filters since the boolean doesn't work how I need it. Here's what I have.. https://gist.github.com/4383340 Do you see what I'm missing? – jeremywoertink Dec 26 '12 at 23:32
  • 1. I don't understand what "it returns the watcher_ids but not the other terms" means. 2. I don't understand what "the boolean doesn't work how I need it" means. 3. The code posted by Russ and /me should definitely work for the outlined case -- maybe try to isolate the behaviour into plain Ruby? 4. I have trouble reading the intent of the code in the linked gist: why use `multi_search` for single search, why wrap everything in the `relevant_events` method? – karmi Dec 27 '12 at 07:35
  • Sorry, communicating through comments makes things more difficult. The reason for the multi_search is because about 70% of the code I'm using was ripped out so only the code relevant to my issue was shown. My issue is that I have an array of object IDs that I want to search on. Using the filter with an or, I get no results. Using the boolean, I do get results. The issue with the boolean is that each "should" is required. So if performer_ids is empty, then the query fails. I'm sure it's an easy fix, but I've been stuck on this for a while. Is there a better place to post questions for Tire? – jeremywoertink Dec 27 '12 at 21:28
  • Yes, Github issues work fairly well... You should be able to achieve with `or` filters the same thing as with the boolean `string` queries... – karmi Dec 27 '12 at 21:49
2

I think what you are looking for is a filter. This is not fully tested code, but it might lead you in the right direction.

class Event
  include Mongoid::Document
  include Tire::Model::Search
  include Tire::Model::Callbacks

  field :name, type: String

  has_and_belongs_to_many :performers
  has_many :watchers, class_name: 'User'

  mapping do
    indexes :name
    indexes :watcher_ids, type: 'integer', index: :not_analyzed
    indexes :performer_ids, type: 'integer', index: :not_analyzed
  end
end

Tire.search('events') do
  query do
    string 'my event'
  end

  filter :in, :organization_ids, [1,2,3]
  filter :in, :watcher_ids, [1]
end
Russ Smith
  • 56
  • 4