Searching Multiple Terms with ElasticSearch + Tire - ruby-on-rails

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

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:
https://github.com/karmi/tire/blob/master/test/integration/filters_test.rb
https://github.com/karmi/tire/blob/master/test/integration/filtered_queries_test.rb

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

Related

Rails Mongoid : Query embedded document and access to Mongoid criteria

I have an application where users can create many travels, and they can invite their facebook friends. In the travel document, there is a field "participants" that is an embedded document, Participant model embedded in Travel model.
Here are my models :
class Travel
include Mongoid::Document
include Mongoid::Timestamps
# relations
belongs_to :user
# fields
field :title, type: String
field :description, type: String
field :begin_date, type: Date
field :end_date, type: Date
field :budget, type: Integer
field :go_back, type: Boolean
field :title_namespace, type: String
# friends
embeds_many :participants
accepts_nested_attributes_for :participants
end
class Participant
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
field :user_id, type: String
# relations
embedded_in :travel, :inverse_of => :participants
end
When I try to display travel where users have been invited, with this request :
#travel_participations = Travel.where('participants.user_id' => #user.id)
I don't have any result, even if I have this line in byebug :
#<Mongoid::Criteria
selector: {"participants.user_id"=>BSON::ObjectId('592c8da58511989ec850921e')}
options: {}
class: Travel
embedded: false>
So when I put this on my view :
<% unless #participations.nil? %>
<% #travel_participations.each do |travel_participation| %>
<p> <%= travel_participation.title %> </p>
<% end %>
<% end %>
I tried with .all, .first, .to_a, .as_json, no results ... Some one know where is the problem ?
You have this in your embedded model:
field :user_id, type: String
but your query is using a BSON::ObjectId:
Travel.where('participants.user_id' => #user.id)
as shown in the raw query:
selector: {"participants.user_id"=>BSON::ObjectId('592c8da58511989ec850921e')}
Your embedded document probably has a string field like:
"user_id": "592c8da58511989ec850921e"
rather than the ObjectId you're looking for:
"user_id": ObjectId("592c8da58511989ec850921e")
so you won't find what you're looking for due to the type mismatch.
Either fix the embedded field's type:
field :user_id, type: BSON::ObjectId
or query it as the string it is:
Travel.where('participants.user_id' => #user.id.to_s)
Changing the type will involve fix up whatever data you already have, changing the query is ugly in a different way.
Sometimes Mongoid will convert between strings and ObjectIds for you, sometimes it won't. When I used Mongoid I patched to_bson_id methods into BSON::ObjectId, String, Mongoid::Document, ... so that I could say things like:
Model.where(:some_id => some_id.to_bson_id)
and not have to constantly worry about what type some_id was. I also made sure that all ID fields were always specified as BSON::ObjectId.

MongoId combined uniqueness index

I have three models
class Org
include Mongoid::Document
field :name, type: String
embeds_many :org_groups
end
class OrgGroup
include Mongoid::Document
field :name, type: String
embedded_in :org
has_and_belongs_to_many :humans
end
class Human
include Mongoid::Document
field :name, type: String
end
One Human can be in many Org, but only in one OrgGroup.
I need set uniqueness index for Human in Org.
How I can do this?
You can create a method that will be call by a callback.
See documentation for callbacks.
You can simply raise something from this method if your conditions are not respected.
Ask if you need a sample.
If you need a unique index in the mongodb, you can do like this:
class Person
include Mongoid::Document
field :first_name
field :last_name
index({ first_name: 1, last_name: 1 }, { unique: true })
end
And the docs are here:
https://docs.mongodb.com/ecosystem/tutorial/mongoid-indexes/
Hope this is helpful for you.

Rails Elastic Search with Tire not finding association records

I have simple Rails app with a Post model that belongs to a creator (which is a User object) and I want to find posts that match the creator's first_name.
class Post < ActiveRecord::Base
include Tire::Model::Search
include Tire::Model::Callbacks
belongs_to :creator, polymorphic: true
mapping do
indexes :id, type: 'integer'
indexes :title, boost: 10
indexes :creator do
indexes :first_name
indexes :service_type
end
end
end
When I run search, there are no results:
Post.tire.search(load: true, page: params[:page], per_page: params[:per_page]) do
query do
match 'creator.first_name', 'Matt'
end
end
Even when I've run rake environment tire:import:all FORCE=true and verified that a Post exists with a creator that has a first name of 'Matt' in the database.
Is there something I am missing? Thanks!
UPDATE:
Adding the following to Post.rb did the trick:
def to_indexed_json
as_json.merge(
creator: creator.as_indexed_json
).to_json
end
Apparently, you need to specify the association in the to_indexed_json to make it work.

Avoiding Mongoid N+1

I'd like to pull all companies that have at least one position title of "CEO".
I could hack it together with a query for each table and an intersect (I know... no joins http://mongoid.org/en/mongoid/docs/tips.html#relational_associations, and N+1 problem in mongoid, and I could just embed positions in company), but any way to do something like:
Company.includes(:positions).where("positions.title" => "CEO")?
Thanks:
class Position
include Mongoid::Document
field :title, type: String
field :profile_id, type: String
field :tenure, type: BigDecimal
belongs_to :company, index: true
class Company
include Mongoid::Document
field :name, type: String
field :linkedin_id, type: String
field :positions_count, type: Integer #Mongo Index
belongs_to :industry, index: true
has_many :positions
index({ positions_count: 1}, {background: true})
To avoid the the N+1 problem enable Mongoid identity_map feature
This will allow you to do the following query:
companies_with_ceo = Position.where(title: 'CEO').includes(:company).map(&:company)
Which should execute only 2 queries to the database.

Using geoNear with Rails / Mongoid

I'm having problems querying Geospatial indexes with MongoDB / Rails.
I'm using this gem - https://github.com/kristianmandrup/mongoid_geospatial
Here's my fairly basic model:
class Company
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Geospatial
field :name, type: String
field :location, type: Array, spatial: true
spatial_index :location
validates :location, location: true
end
Then, in my controller, I have this
##vendors = Vendor.where(:location.near => {:point => [-2.1294761000000335,57.0507625], :max => 5})
However, this isn't returning expected results (ie- it's returning things from all over the place, not just near that particular lon / lat)
Also, how would I go about doing a geoNear with this?
So that I can get back the distances from central point for each result?
Note
After writing this question, I've seen the gem has been updated, but I'm not sure if there's a better alternative..?
You don't need the mongoid_geospatial gem to do a geoNear query: mongoid already supports it (in version 3 at least).
Change your model to:
class Company
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
field :location, type: Array
index({location: "2d"})
validates :location, location: true
end
And run your query as:
#vendors = Vendor.geo_near([-2.1294761000000335,57.0507625]).max_distance(5)

Resources