Rails: elasticsearch query block error - ruby-on-rails

I'm using elasticsearch on my rails app and every time I try to add a block to the def self.search(query) I get an error:
[400] {"error":{"root_cause":[{"type":"parse_exception","reason":"illegal latitude value [269.99999983236194] for [GeoDistanceSort] for field [distance_type]."}],"type":"search_phase_execution_exception","reason":"all shards failed","phase":"query","grouped":true,"failed_shards":[{"shard":0,"index":"prices","node":"AfyW4Pa4S-qKhua-3lY4rg","reason":{"type":"parse_exception","reason":"illegal latitude value [269.99999983236194] for [GeoDistanceSort] for field [distance_type]."}}]},"status":400}
Up to this point, it doesn't return an error:
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
fields: ['title^5', 'description']
}
},
highlight: {
pre_tags: ['<em>'],
post_tags: ['</em>'],
fields: {
title: {},
description: {},
}
}
}
)
end
if a try to add the sort block query inside the model, it returns the above error:
require 'elasticsearch/model'
require 'elasticsearch/model'
class Item < ApplicationRecord
mount_uploader :image, ImageUploader
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
index_name Rails.application.class.parent_name.underscore
document_type self.name.downcase
settings index: { number_of_shards: 1 } do
mappings dynamic: 'false' do
indexes :title, analyzer: 'english', index_options: 'offsets'
indexes :description, analyzer: 'english'
indexes :location, type: 'geo_point'
end
end
def location
[longitude.to_f, latitude.to_f]
end
def current_location
location = request.location
end
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
fields: ['title^5', 'description']
}
},
sort: [
{
_geo_distance: {
"pin.location": ["current_location"],
distance: ["radius"],
unit: ["km"],
mode: ["min"],
order: ["asc"],
distance_type: ["arc"],
}
}
],
highlight: {
pre_tags: ['<em>'],
post_tags: ['</em>'],
fields: {
title: {},
description: {},
}
}
}
)
end
end
Item.__elasticsearch__.client.indices.delete index: Item.index_name rescue nil
Item.__elasticsearch__.client.indices.create \
index: Item.index_name,
body: { settings: Item.settings.to_hash, mappings: Item.mappings.to_hash }
Item.import(force: true)
Update 1
I have installed the geocoder gem to get the location of each address added. So when I create the account with address I get the longitude and latitude coordinates with the following code. After that I added the longitude and langitude columns to the items table, and then when the user uploads an item the coordinates are updated on the items.rb as well.
Here is the account.rb setup:
geocoded_by :full_address
after_validation :geocode, if: ->(obj){ obj.full_address.present? }
def full_address
[street, city, state, zip_code, country].join(",")
end

Related

elastic search not found error in ruby on rails

I'm going to do elastic search on DoctorProfile and Subspeciality table. The error that I'm dealing with is that it gives the not found result. It takes a list of ids from doctor table but it doesn't gives desire result which is the doctor and with subspeciality.
this is what everything that i did:
I used these gems:
gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'
gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'
gem 'elasticsearch-extensions', git: 'git://github.com/elasticsearch/elasticsearch-ruby.git'
my search method:
def search
query = params[:query]
#query.encode("UTF-8")
if query.nil?
render json: { message: "no query is provided" }, status: :unprocessable_entity
return
end
profiles = DoctorProfile.search(query).results.map { |r| r._source.id }
subspecialties = Subspecialty.search(query).results.map { |r| r._source.title }
subspecialties.uniq!
profiles = profiles + DoctorProfile.where("subspeciality in (?)", subspecialties).ids
profiles.uniq!
logger.info "################ The profile is #{profiles} ########################"
#doctors = DoctorProfile.find(profiles)
#cleaned_doctors = #doctors.select { |u| !u.user.nil? }
render json: #cleaned_doctors
end
in the doctor model:
after_commit on: [:create] do
__elasticsearch__.index_document if self.enabled?
end
after_commit on: [:update] do
__elasticsearch__.update_document if self.enabled?
end
after_commit on: [:destroy] do
__elasticsearch__.delete_document
end
settings index: {
number_of_shards: 1,
number_of_replicas: 0,
analysis: {
filter: {
autocomplete_filter: {
type: "edge_ngram",
min_gram: 1,
max_gram: 20
}
},
analyzer: {
autocomplete: {
type: "custom",
tokenizer: "standard",
filter: [
"lowercase",
"autocomplete_filter"
]
}
}
}
}
mappings dynamic: 'false' do
indexes :first_name, type: "string"
indexes :last_name, type: "string"
indexes :medical_code, type: "string"
indexes :expertise, type: "string", analyzer: "autocomplete", search_analyzer: "standard"
indexes :subspeciality, type: "string", analyzer: "autocomplete", search_analyzer: "standard"
end
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
fields: ['medical_code^10', 'subspeciality^5', 'expertise^2', 'first_name', 'last_name']
}
}
}
)
and in subspeciality model:
after_commit on: [:create] do
self.services = self.services.each {|str| str.force_encoding("UTF-8")}
__elasticsearch__.index_document if self.enabled?
end
after_commit on: [:update] do
self.services = self.services.each {|str| str.force_encoding("UTF-8")}
__elasticsearch__.update_document if self.enabled?
end
after_commit on: [:destroy] do
__elasticsearch__.delete_document
end
settings index: {
number_of_shards: 1,
number_of_replicas: 0,
analysis: {
filter: {
autocomplete_filter: {
type: "edge_ngram",
min_gram: 1,
max_gram: 20
}
},
analyzer: {
autocomplete: {
type: "custom",
tokenizer: "standard",
filter: [
"lowercase",
"autocomplete_filter"
]
}
}
}
}
mappings dynamic: 'false' do
indexes :description, type: "string", analyzer: "autocomplete", search_analyzer: "standard"
indexes :services, type: "string"
end
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
fields: ['description', 'services']
}
}
}
)
end
and this is my error in log :
Couldn't find all DoctorProfiles with 'id': (031addd8-9df8-4a53-974d-da0067302ad0, ff890720-4bfb-47d8-bdb8-3dc712b27f29, 869b28e1-cdd7-4bb6-b1d0-c7296e4b0637, 6dd6a784-c54b-4bb7-a0e1-337474ec4114, 234ccc87-f0c7-42f7-b96f-cf8d85487929, 543b621d-87aa-4a34-b6d6-62144c6a387e, 77e35144-9b93-48a0-a5bb-7b3addb99dff, d368f1df-3d1a-49ce-b6f5-f791df3294b1, d3dca8de-3143-4b03-90ec-e73a27c88960, 24abb0b3-2d11-457b-b95d-972462c4a37f) (found 2 results, but was looking for 10
i changed this line of code
#doctors = DoctorProfile.find(profiles)
to
#doctors = DoctorProfile.where("id in (?)",profiles)
and remove this line:
#cleaned_doctors = #doctors.select { |u| !u.user.nil? }
now i want to know what does this method do eactly.
#cleaned_doctors = #doctors.select { |u| !u.user.nil? }
to be mention, i have a table named user that doctorProfile has reference to it
Your code doesn't say much, but it seems that you have a boolean enabled on your model, which tells whether the record is to be indexed or not.
The issue is with the update callback, because if you change your model from enabled to not enabled, instead of removing it from the index, it just don't update existing information.
The correct callback would be
after_commit on: [:update] do
if enabled?
if previous_changes['enabled'] &&
!previous_changes['enabled'].first
# previously not enabled, we need to index it
__elasticsearch__.index_document
else
# previously enabled, we need to update it
__elasticsearch__.update_document
end
else
# not enabled
if previous_changes['enabled'] &&
previous_changes['enabled'].first
# previously enabled, delete
__elasticsearch__.delete_document
end
# if it wasn't enabled before, it's not in the index anyway.
# do nothing
end
end
The previous_changes hash stores the attributes that did change when saving the model, so you can check the previous value of the enabled attribute. See http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes
Once you have the new callback in place, rebuild the indexes to remove the bogus data in production if needed:
DoctorProfile.where(enabled: true).find_each { |dp| dp.__elasticsearch__.index_document }
Subspecialty.where(enabled: true).find_each { |dp| dp.__elasticsearch__.index_document }

NoMethodError (undefined method `highlight' for #<Elasticsearch::Model::Response::Result>

I'm going to use elastic search for my ruby on rails project. I get this error when I search some of the word that It's used in my article too much.
NoMethodError (undefined method `highlight' for #<Elasticsearch::Model::Response::Result:0x007f062ed26708>)
i got this in the log production. this is what everything that i did:
in controller:
# POST /search/article
def search
render json: Article.search(params[:query]), each_serializer: ElasticsearchResultsSerializer
end
this is my article.rb model
#default_scope { order('created_at DESC') }
scope :visible, -> { where(enabled: true) }
after_commit on: [:create] do
self.keywords = self.keywords.each {|str| str.force_encoding("UTF-8")}
__elasticsearch__.index_document if self.enabled?
end
after_commit on: [:update] do
self.keywords = self.keywords.each {|str| str.force_encoding("UTF-8")}
__elasticsearch__.update_document if self.enabled?
end
after_commit on: [:destroy] do
__elasticsearch__.delete_document
end
settings index: { number_of_shards: 1, number_of_replicas: 0 }
mappings dynamic: 'false' do
indexes :content, type: "string", index_options: 'offsets'
indexes :title, type: "string"
indexes :description, type: "string"
indexes :category, type: "string"
indexes :created_at, type: "date"
indexes :keywords, type: "string"
end
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
fields: ['title^10', 'content^5', 'description^2', 'keywords', 'category']
}
},
highlight: {
pre_tags: ['<em>'],
post_tags: ['</em>'],
fields: { title: {}, content: {} }
}
}
)
end
def as_indexed_json(options={})
as_json(
only: [:content, :title, :id, :category, :keywords, :description]
)
end
and also i used serializer
class ElasticsearchResultsSerializer < ActiveModel::Serializer
attributes :_id, :highlight, :_score, :_source
def _source
#article = object._index.singularize.capitalize.constantize.find(object._id)
#serializer = "#{object._index.singularize.capitalize}Serializer".constantize
#serializer.new(#article)
end
end
Maybe is a silly observation, but did you tried to change the value
:highlight to :_highlight ?
class ElasticsearchResultsSerializer < ActiveModel::Serializer
attributes :_id, :_highlight, :_score, :_source
def _source
#article = object._index.singularize.capitalize.constantize.find(object._id)
#serializer = "#{object._index.singularize.capitalize}Serializer".constantize
#serializer.new(#article)
end
end
I would try switching fields to:
fields: {
:"*" => {}
}
Just to see if that gets rid of the error. See the following: https://github.com/elastic/elasticsearch-rails/issues/446

Trouble highlighting in elasticsearch using rails

After reading several sites (including elasticsearch's documentation) and experimenting around a lot, I'm having trouble getting highlights. I can do the basic keyword search, but it's clear I'm not grasping something. Here's my code.
Gems:
gem 'elasticsearch-model'
gem 'elasticsearch-rails'
Controller:
class TermsController < ApplicationController
def search
#terms = Term.search(params[:query]).results
end
end
Model:
require 'elasticsearch/model'
class Term < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
settings index: { number_of_shards: 1, number_of_replicas: 0 } do
mappings dynamic: 'false' do
indexes :id, index: :not_analyzed
indexes :name, analyzer: 'spanish'
indexes :gender, index: :not_analyzed
indexes :part_of_speech, index: :not_analyzed
indexes :definition
indexes :etymology1
indexes :etymology2
indexes :uses
indexes :romance_cognates
indexes :notes1
indexes :notes2
indexes :quote1, analyzer: 'spanish'
indexes :quote2, analyzer: 'spanish'
end
end
def as_indexed_json(options = {})
as_json(
only: [:name, :gender, :part_of_speech, :definition, :etymology1, :etymology2, :uses, :romance_cognates, :notes1, :notes2, :quote1, :quote2]
)
end
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
fields: ['name', 'definition', 'etymology1', 'etymology2', 'uses', 'romance_cognates', 'notes1', 'notes2', 'quote1', 'quote2']
}
},
highlight: {
tags_schema: 'styled',
fields: {
:'*' => {}
}
}
}
)
end
end
# Delete the previous terms index in Elasticsearch
Term.__elasticsearch__.client.indices.delete index: Term.index_name rescue nil
# Create the new index with the new mapping
Term.__elasticsearch__.client.indices.create \
index: Term.index_name,
body: { settings: Term.settings.to_hash, mappings: Term.mappings.to_hash }
# Index all term records from the db to Elasticsearch
Term.import(force: true)
I also tried:
{
query: {
multi_match: {
query: query,
fields: ['name', 'definition', 'etymology1', 'etymology2', 'uses', 'romance_cognates', 'notes1', 'notes2', 'quote1', 'quote2']
}
},
highlight: {
fields: {
content: {'force_source': true}
}
}
}
and
{
query: {
multi_match: {
query: query,
fields: ['name', 'definition', 'etymology1', 'etymology2', 'uses', 'romance_cognates', 'notes1^5', 'notes2', 'quote1', 'quote2']
}
},
highlight: {
fields: {
content: {type: 'plain'}
}
}
}
and
{
query: {
multi_match: {
query: query,
fields: ['name', 'definition', 'etymology1', 'etymology2', 'uses', 'romance_cognates', 'notes1^5', 'notes2', 'quote1', 'quote2']
}
},
highlight: {
pre_tags: ['<tag1>']
post_tags: ['</tag1>']
fields: {
_all: {}
}
}
}
...Along with many other attempts I can't remember
It appears the key that I was missing as illustrated here is that I needed the try() method in my view template. I'm sure there's a more concise way of writing this, but a sample of my view syntax looks like this:
<%= term.try(:highlight).try(:definition) ? term.highlight.definition[0].html_safe : term.definition.html_safe %>
<%= term.try(:highlight).try(:etymology1) ? term.highlight.etymology1[0].html_safe : term.etymology1.html_safe %>

Elasticsearch rails/ Elasticsearch model search model association

I have a model named Movie that looks like this:
class Movie < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
has_many :actors, after_add: [ lambda {|a,c| a.__elasticsearch__.index_document}],
after_remove: [ lambda {|a,c| a.__elasticsearch__.index_document}]
settings index: {number_of_shards: 1} do
mappings dynamic: 'false' do
indexes :title, analyzer: 'snowball', boost: 100
indexes :actors
end
end
def as_indexed_json(options={})
self.as_json(
include: {
actors: { only: :name}
}
)
end
end
When i do Movie.first.as_indexed_json , I get:
{"id"=>6, "title"=>"Back to the Future ",
"created_at"=>Wed, 03 Dec 2014 22:21:24 UTC +00:00,
"updated_at"=>Fri, 12 Dec 2014 23:40:03 UTC +00:00,
"actors"=>[{"name"=>"Michael J Fox"}, {"name"=>"Christopher Lloyd"},
{"name"=>"Lea Thompson"}]}
but when i do Movie.search("Christopher Lloyd").records.first i get: => nil .
What changes can i make to the index to search movies associated with the searched actor?
I used filtering query to solve this, first I created an ActiveSupport::Concern called searchable.rb, the concern looks like this:
module Searchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
index_name [Rails.application.engine_name, Rails.env].join('_')
settings index: { number_of_shards: 3, number_of_replicas: 0} do
mapping do
indexes :title, type: 'multi_field' do
indexes :title, analyzer: 'snowball'
indexes :tokenized, analyzer: 'simple'
end
indexes :actors, analyzer: 'keyword'
end
def as_indexed_json(options={})
hash = self.as_json()
hash['actors'] = self.actors.map(&:name)
hash
end
def self.search(query, options={})
__set_filters = lambda do |key, f|
#search_definition[:post_filter][:and] ||= []
#search_definition[:post_filter][:and] |= [f]
end
#search_definition = {
query: {},
highlight: {
pre_tags: ['<em class="label label-highlight">'],
post_tags: ['</em>'],
fields: {
title: {number_of_fragments: 0}
}
},
post_filter: {},
aggregations: {
actors: {
filter: {bool: {must: [match_all: {}]}},
aggregations: {actors: {terms: {field: 'actors'}}}
}
}
}
unless query.blank?
#search_definition[:query] = {
bool: {
should: [
{
multi_match: {
query: query,
fields: ['title^10'],
operator: 'and'
}
}
]
}
}
else
#search_definition[:query] = { match_all: {} }
#search_definition[:sort] = {created_at: 'desc'}
end
if options[:actor]
f = {term: { actors: options[:taxon]}}
end
if options[:sort]
#search_definition[:sort] = { options[:sort] => 'desc'}
#search_definition[:track_scores] = true
end
__elasticsearch__.search(#search_definition)
end
end
end
I have the above concern in the models/concerns directory.
In movies.rb I have:
class Movie < ActiveRecord::Base
include Searchable
end
In movies_controller.rb I am doing searching on the index action and the action looks like this:
def index
options = {
actor: params[:taxon],
sort: params[:sort]
}
#movies = Movie.search(params[q], options).records
end
Now when i go to http://localhost:3000/movies?q=future&actor=Christopher I get all records which have the word future on their title and has an actor with a name Christopher. You can have more than one filter as shown by the expert template of the example application templates found here .
You can try add method search to your model like this:
class Movie < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
# ...
def self.search(query, options = {})
es_options =
{
query: {
query_string: {
query: query,
default_operator: 'AND',
}
},
sort: '_score',
}.merge!(options)
__elasticsearch__.search(es_options)
end
# ...
end
Here is some examples of method search: http://www.sitepoint.com/full-text-search-rails-elasticsearch/
And now you can search in all your indexed fields.
You need to specify the fields in the the search method, like:
def self.search query
__elasticsearch__.search(
query: {
multi_match: {
query: query,
fields: %w[title actor.name]
}
}
)
end
Try this
indexes :actors do
indexes :name, type: "string"
end

Mapping of a geo_point field in ElasticSearch with Tire

I'm trying to index a geo_point field in Elasticsearch with the Tire gem. Here is my Tire mapping for my ActiveRecord model :
class Availability < ActiveRecord::Base
belongs_to :user
attr_accessible :date, :latitude, :longitude
include Tire::Model::Search
include Tire::Model::Callbacks
tire do
mapping do
indexes :id, type: 'integer', index: 'not_analysed'
indexes :user_id, type: 'integer', index: 'not_analysed'
indexes :user_firstname, type: 'string', as: 'user_firstname'
indexes :user_lastname, type: 'string', as: 'user_lastname'
indexes :user_level, type: 'integer', as: 'user_level'
indexes :date, type: 'date'
indexes :location, type: 'geo_type', as: 'location'
end
end
# def location
# "#{latitude},#{longitude}"
# end
def location
[longitude.to_f, latitude.to_f]
end
def user_firstname
user.firstname
end
def user_lastname
user.lastname
end
def user_level
user.level
end
end
When I create the mapping (bundle exec rake environment tire:import CLASS=Availability FORCE=true), Elasticsearch seems to ignore the geo_point type for the location field.
Here is the result of the http://localhost:9200/availabilities/_mapping call :
{
availabilities: {
availability: {
properties: {
date: {...},
id: {...},
location: {
type: "double"
},
user_firstname: {...},
user_id: {...},
user_lastname: {...},
user_level: {...}
}
}
}
}
The location field is indexed as an array of double on the documents (results of http://localhost:9200/availabilities/_search) :
{
id: 8,
...
location: [
2.301643,
48.780651
]
}
When I change the location method to :
def location
"#{latitude},#{longitude}"
end
Which is another solution to index a geo_point field according to the documentation (http://www.elasticsearch.org/guide/reference/mapping/geo-point-type.html), the result for the location mapping is :
location: {
type: "string"
},
And of course the location field is indexed as a string :
{
id: 4,
...
location: "48.780651,2.301643"
}
Any idea why the geo_point is ignored in my mapping ?
Thanks !
The location index type was mistyped.
You used geo_type instead of geo_point:
indexes :location, type: 'geo_point', as: 'location'

Resources