Rails 4 - Elasticsearch and searchkick NOT working as expected - ruby-on-rails

I have a Rails4 app using elasticsearch and searchkick for a sitewide search page.
I have configured the models and its associations using searchkick search_data, but its not working as per my requirement where user can search all venues by location, name, capacity, event(marriage, engagements etc.) and food_type(veg/non-veg). So precisely when i search for venues with required params, i still didnt get the desired result.
Below is the elasticsearch query in the server...
[SITE WIDE SEARCH] **************SEARCH QUERY***["new", "Mumbai, Maharashtra", "3000", "VEG", "BUSINESS MEETING"]******************
Venue Search (7.2ms) curl http://localhost:9200/venues_development/_search?pretty -d '{"query":{"bool":{"must":{"dis_max":{"queries":[{"match":{"_all":{"query":"Mumbai, Maharashtra","boost":10,"operator":"and","analyzer":"searchkick_search"}}},{"match":{"_all":{"query":"Mumbai, Maharashtra","boost":10,"operator":"and","analyzer":"searchkick_search2"}}},{"match":{"_all":{"query":"Mumbai, Maharashtra","boost":1,"operator":"and","analyzer":"searchkick_search","fuzziness":1,"prefix_length":0,"max_expansions":3,"fuzzy_transpositions":true}}},{"match":{"_all":{"query":"Mumbai, Maharashtra","boost":1,"operator":"and","analyzer":"searchkick_search2","fuzziness":1,"prefix_length":0,"max_expansions":3,"fuzzy_transpositions":true}}}]}},"filter":[{"range":{"capacity_in_persons":{"to":"3000","include_upper":true}}},{"term":{"food_type":"VEG"}},{"term":{"event_type_name":"BUSINESS MEETING"}}]}},"size":1000,"from":0,"timeout":"11s","_source":false}'
models/venue.rb:
##columns of venue.rb
##=> Venue(id: integer, name: string, description: text,active: boolean, announcements_count: integer, comments_count: integer, pictures_count: integer, videos_count: integer, created_at: datetime, updated_at: datetime, capacity_in_persons: string, workflow_state: string)
### associations
has_many :event_categories
has_many :event_types, through: :event_categories
has_one :address, :as=> :addressable, :dependent => :destroy
###elasticsearch config starts here
searchkick word_middle:["name^10", :slug, :capacity_in_persons], locations: ["location"]
def search_data
{
name: name, analyzer: 'english', #: :word_start, misspellings: false},
capacity_in_persons: capacity_in_persons,
food_type: food_type,
slug: slug,
##has many event types
event_type_name: event_types.map(&:name),
ratings: ratings.map(&:stars),
##location: [self.address.latitude, self.address.longitude],
location: [self.address.latitude, self.address.longitude],
picture_url: pictures.select{|p| p == pictures.last}.map do |i|{
original: i.original_url
}
end
}
end
##to eager load other associations
scope :search_import, -> { includes(:address, :event_types, :pricing_details, :ratings) }
after_save :reindex if try(:address).present?
###### controller action ##########
#query = []
#query << params[:venue_name] if params[:venue_name].present?
#query << params[:address] if params[:address].present?
#query << params[:venue_capacity_in_persons] if params[:venue_capacity_in_persons].present?
#query << params[:food_type] if params[:food_type].present?
#query << params[:event_name] if params[:event_name].present?
#query = #query.flatten.compact
logger.tagged("SITE WIDE SEARCH"){ logger.info "**************SEARCH QUERY***#{#query}******************" }
##TODO-add constraints to handle range for capacity in persons
####halls = Hall.get_completed_halls_only.paginate(:page => params[:page]).search(#query).results
###halls = Hall.get_completed_halls_only.search(#query)
#halls = Hall.get_completed_halls_only.search params[:address],
where: {
capacity_in_persons: {lte: params[:venue_capacity_in_persons]},
food_type: params[:food_type],
event_type_name: params[:event_name]
}
I have added changes to my address.rb to find venue nearby and i think it working fine but any suggestions are welcome.address.rb is polymorphic and has address_1 attribute to store address coming from google dropdown along with geocoding from geocoder gem.
models/address.rb
###address belongs_to venue
searchkick locations: [:address_1]
## call Address.reindex if this method is changed
def search_data
attributes.except("id").merge(
address_1: {lat: latitude, lon: longitude},
city: city,
state: state,
zipcode: zipcode ##unless addressable_type == "Hall"
)
end

Related

How can I call related tables with include option by as_json

current relation is below.
class Hoge < ApplicationRecord
belongs_to :hoge_category
class HogeCategory < ApplicationRecord
belongs_to :big_hoge_category
has_many :hoges
class BigHogeCategory < ApplicationRecord
has_many :hoge_categories
has_many :hoges, through: :hoge_categories
I want to extract Hoge's data which is related HogeCategory table and BigHogeCategory table.
Like this.
HogesController
hoges = Hoge.index( params[:keyword], nil, params[:hoge_category_id], params[:offset].to_i, 10 ).as_json(include: :hoge_category)
render status: 200, json: { hoges: hoges } #send API
Hoge.rb
class Hoge < ApplicationRecord
searchkick callbacks: :async, language: "japanese", word_middle: [:title, :message]
def self.index(keyword, connect_id, hoge_category_id, offset, limit) #execute search
where = {}
if keyword.present?
hoges = Hoge.search(keyword, fields: [:title, :message],misspellings: false, where: where, order: { created_at: :desc }, limit: limit, offset: offset).results
else
hoges = Hoge.search(fields: [:title, :message],misspellings: false, where: where, order: { created_at: :desc }, limit: limit, offset: offset).results
end
return hoges
end
def search_data
{
title: title,
message: message,
created_at: created_at,
hoge_category_id: hoge_category&.id
}
end
end
From my search, it's better to use as_json with include option.
But I don't know how to write when related tables are multiple.
How can I write that?
Or if you have a better idea, let me know please..

Rails to_json belongs_to object

I have two models: Cabinet and Workplace.
class Cabinet < ActiveRecord::Base
def as_json(options={})
options.merge!({except: [:created_at, :updated_at]})
super(options)
end
end
class Workplace < ActiveRecord::Base
belongs_to :cabinet
def as_json(options = {})
options.merge!(:except => [:created_at, :updated_at, :cabinet_id], include: :cabinet)
super(options)
end
end
When I called Cabinet.first.to_json I get
{
id: 1,
cabinet: "100"
}
but when I called Workplace.first.to_json id get
{
name: "first workplace",
Cabinet: {
id: 1,
cabinet: "100",
created_at: "#created_at",
updated_at: "#updated_at"
}
}
Why this? Thanks and sorry for my english :)
Not sure if I am following you, but do you want to get just attributes from Workplace model, and not Cabinet data when you do Workplace.first.to_json?
I think it is because you include cabinet in as_json method configuration as explained here.
You should either remove it or do this:
Workplace.first.attributes.to_json
Let me know if I am missing something from your question.
Let's assume that your model Cabinet has :id, :cabinet, :created_at, :updated_at attributes and Workplace has :id, :name, :cabinet_id, .....
Now, if you try to fire Cabinet.first.to_json, ofcourse it will render the following:
{
id: 1,
cabinet: "100"
}
becuase that is the attributes belongs to Cabinet model. Then you also added these line of code options.merge!({except: [:created_at, :updated_at]}) that's why it only renders :id and :name attributes. And if you try to fire Workplace.first.to_json then it will render:
{
name: "first workplace",
Cabinet: {
id: 1,
cabinet: "100",
created_at: "#created_at",
updated_at: "#updated_at"
}
}
because, of these options.merge!(:except => [:created_at, :updated_at, :cabinet_id], include: :cabinet). You include the model Cabinet so it will automatically added to your json.

searchkick advanced search not working

class Product < ActiveRecord::Base
belongs_to :city
has_and_belongs_to_many :categories
before_destroy { categories.clear }
searchkick locations: ["location"]
def search_data
{
istatus: i_status,
name: name,
price: price,
city_id: city_id,
value: value,
discount: discount,
expiry_date: expiry_date,
created_at: created_at,
products_sold: products_sold,
city: city.name,
deal_type: deal_type,
country: city.country.name,
category_id: categories.map(&:id),
location: [latitude, longitude]
}
end
def self.apply_filters(request)
# #product = Product.search "Tex-Mex", limit:10 #=>this works
#product = Product.search body: {match: {name: "Tex-Mex"}},limit: 10 #=>does not work, the limit part work
end
end
when i use advanced search using body.. it does not return the desired results although the limit:10 part us working as it does return 10 results only
I believe there is some missing information in the documentation.
Here's a reference to a body query that works based on the tests written in SearchKick:
https://github.com/ankane/searchkick/blob/c8f8dc65df2e85b97ea508e14ded299bb8111942/test/index_test.rb#L47
For advanced search to work, the way it should be written is:
#product = Product.search body: { query: { match: {name: "Tex-Mex"}}},limit: 10
You need a query key following the body.
// conditions = {}
query = Product.search params[:query], execute: false, where : conditions
query.body[:query] = { match: {name: "Tex-Mex"} }
query.body[:size] = 10
query.execute
You would need to build your query using the Elasticsearch DSL. Specifically, using size and match.
Product.search body: { query: { match: {name: "Tex-Mex"} }, size: 10 }
When using Advanced search, Searchkick ignores parameters outside the body hash. While the body hash allows you to use the full ES DSL.

How to create Parent Child mapping?

Im migrating from Tire to Flex
Basic search and index-sync works
I figured the flex.parent line in the model would auto-create the _parent mapping, but it crashes
I was not able to find any parent/child demo project.
Flex.yml:
settings:
number_of_shards: 5
number_of_replicas: 1
# analysis:
# analyzer:
# tokenizer:
mappings:
userprofile:
startdatef:
type: 'date'
format: 'dateOptionalTime'
fields:
index: 'not_analyzed'
untouched:
type: 'date'
index: 'not_analyzed'
orgunit:
org_name:
type: 'string'
index: 'analyzed'
search_analyzer: orgunit_name_search
index_analyzer: orgunit_name_index
untouched:
type: 'string'
index: 'not_analyzed'
Parent model:
class Userprofile < ActiveRecord::Base
include Flex::ModelIndexer
include Flex::Model
flex.sync self
has_many :assignments,
-> { order(startdate: :desc) }, dependent: :restrict_with_exception
module Search
include Flex::Scopes
flex.context = Userprofile
scope :alla, query([])
end
# rubocop:disable all
def flex_source
{
id: id,
fullname: fullname,
firstname: firstname,
lastname: lastname,
pnr: pnr,
gender: gender,
asscount: asscount,
created_at: created_at,
updated_at: updated_at,
user_id: user_id,
creator_id: creator_id,
}
end
# rubocop:enable all
end
Child model:
class Assignment < ActiveRecord::Base
include Flex::ModelIndexer
include Flex::Model
flex.parent :userprofile, 'userprofile' => 'assignment' # This makes indexing break
flex.sync self, :userprofile
belongs_to :userprofile, counter_cache: true, touch: true
module Search
include Flex::Scopes
flex.context = Assignment
scope :alla, query([])
end
def flex_source
{
# _parent_id: userprofile_id,
userprofile_id: userprofile_id,
created_at: created_at,
updated_at: updated_at
}
end
end
rake flex:import
Model Userprofile: Processing 37 documents in batches of 1000:
processing...: 100% ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| Time: 0:00:01
Processed 37. Successful 37. Skipped 0. Failed 0.
Model Assignment: Processing 36 documents in batches of 1000:
rake aborted!: 0% | | ETA: --:--:--
activerecord-4.0.2/lib/active_record/relation/batches.rb:75:in find_in_batches'
400: {"error":"ElasticSearchIllegalArgumentException[Can't specify parent if no parent field has been configured]","status":400}
flex-1.0.6/lib/flex/template.rb:54:indo_render'
...
Tasks: TOP => flex:import
In your mapping you have to specify the "parent" field. Only then ES can link the ID in the "_parent" field you want to index to the corresponding index-type. Try this as a template (substitute the variables apropriate to your index). It just adds the infos to the mapping, not removing existing ones:
curl -XPUT 'http://localhost:9200/$yourIndex/$yourChildTypeName/_mapping' -d '
{
"$yourChildTypeName" : {
"_parent" : {
"type" : "$yourParentTypeName"
}
} '
Then try indexing again.

Access to parent from embedded document on creation (Mongoid)

This trick works with "has_many" relation, but fails with "embeds_many". Any ideas?
class Country
include Mongoid::Document
field :name, type: String
embeds_many :cities
end
class City
include Mongoid::Document
field :name, type: String
field :full_name, type: String, default: ->{ "#{name}, #{country.name}" }
embedded_in :country
end
1.9.3p392 :025 > c = Country.find_or_create_by(name: 'foo')
=> #<Country _id: foo, name: "foo">
1.9.3p392 :026 > c.cities.find_or_create_by(name: 'bar')
NoMethodError: undefined method `city' for nil:NilClass
So, it fails on a line "field :full_name, type: String, default: ->{ "#{name}, #{country.name}" }" becouse country is undefined for that moment
You need to check for country first, then it will return country.name
field :full_name, type: String, default: ->{ "#{name}, " << country.name if country }
I could not get this to work with string interpolation, but append works (which concatenates country.name to str)

Resources