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
Related
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
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 }
I want to use ElasticSearch to search with multiple parameters (name, sex, age at a time).
what I've done so far is included elastic search in my model and added a as_indexed_json method for indexing and included relationship.
require 'elasticsearch/model'
class User < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
belongs_to :product
belongs_to :item
validates :product_id, :item_id, :weight, presence: true
validates :product_id, uniqueness: {scope: [:item_id] }
def as_indexed_json(options = {})
self.as_json({
only: [:id],
include: {
product: { only: [:name, :price] },
item: { only: :name },
}
})
end
def self.search(query)
# i'm sure this method is wrong I just don't know how to call them from their respective id's
__elasticsearch__.search(
query: {
filtered: {
filter: {
bool: {
must: [
{
match: {
"product.name" => query
}
}
],
must: [
{
match: {
"item.name" => query
}
}
]
}
}
}
}
)
end
end
User.import force: true
And In controller
def index
#category = Category.find(params[:category_id])
if params[:search].present? and params[:product_name].present?
#users = User.search(params[:product_name]).records
end
if params[:search].present? and params[:product_price].present?
#users = User.search(params[:product_price]).records
end
if params[:search].present? and params[:item].present?
if #users.present?
#users.search(item: params[:item], product: params[:product_name]).records
else
#users = User.search(params[:item]).records
end
end
end
There are basically 3 inputs for searching with product name , product price and item name, This is what i'm trying to do like if in search field only product name is present then
#users = User.search(params[:product_name]).records
this will give me records but If user inputs another filter say product price or item name in another search bar then it's not working. any ideas or where I'm doing wrong :/ stucked from last 3 days
Basically I got 3 models(Book,Chapter,Author), and I want to include some of the books and author attributes when indexing chapter.
here is my Chapter.rb
class Chapter < ActiveRecord::Base
belongs_to :book, :counter_cache => true
include Elasticsearch::Model
index_name [Rails.env, model_name.collection.gsub(/\//, '-')].join('_')
mappings do
indexes :id, type: :integer
indexes :title, type: :string
indexes :description, type: :string
indexes :content, type: :string
indexes :updated_at, type: :date # Date example
indexes :book_title
indexes :book_type
indexes :author_name
indexes :book_id
end
def book_title
book.title
end
def book_type
book.book_type
end
def author_name
" #{book.author.firstname} #{book.author.lastname} "
end
def to_indexed_json
to_json methods: [:book_title, :book_type, :author_name]
end
end
http://localhost:9200/development_chapters/_mapping?pretty shows correct mapping
{
"development_chapters" : {
"mappings" : {
"chapter" : {
"properties" : {
"author_name" : {
"type" : "string"
},
"book_title" : {
"type" : "string"
},....
}
}
}
}
}
Then why do I not get author_name, book_title etc... in the search results
<Elasticsearch::Model::Response::Result:0x00000105e393a0 #result=#<Hashie::Mash _id="415" _index="development_chapters" _score=1.0 _source=#<Hashie::Mash book_id=153 content="[\"Explicabo accusantium odit .\"]" created_at="2015-04-22T18:43:58.586Z" description="You can't generate the application without quantifying the cross-platform SDD bandwidth!" id=415 title="Future Communications Orchestrator" updated_at="2015-04-22T18:43:58.586Z"> _type="chapter">>
You are defining wrong serialization method. Elasticsearch::Model searches for method as_indexed_json and you are defining to_indexed_json. In elasticesearch-model gem you can find examples https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-model/examples/activerecord_associations.rb#L82
It should look something like this:
def as_indexed_json(options = {})
as_json methods: [:book_title, :book_type, :author_name]
end
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