Ruby elasticsearch-model - configuring multiple mappings -
module spree product.class_eval include elasticsearch::model index_name spree::elasticsearchsettings.index document_type 'spree_product' mapping _all: {"index_analyzer" => "ngram_analyzer", "search_analyzer" => "whitespace_analyzer"} indexes :name, type: 'multi_field' indexes :name, type: 'string', analyzer:'ngram_analyzer', boost: 100 indexes :untouched, type: 'string', include_in_all: false, index: 'not_analyzed' end indexes :name_whole, type: 'multi_field' indexes :name, type: 'string', index_analyzer:'simple' end indexes :name_completion, type: 'multi_field' indexes :untouched, type: 'string', include_in_all: false, index: 'not_analyzed' end indexes :taxon_ids, type: 'multi_field' indexes :taxon_ids, type:'string', analyzer: 'simple' indexes :taxon_ids_ngram, type:'string', analyzer:'ngram_analyzer' end indexes :description, analyzer: 'snowball', include_in_all:false indexes :available_on, type: 'date', format: 'dateoptionaltime', include_in_all: false indexes :price, type: 'double', include_in_all:false indexes :sku, type: 'string', index: 'not_analyzed', include_in_all:false indexes :properties, type: 'string', index: 'not_analyzed', include_in_all:false end mapping _suggest: {"index_analyzer" => "simple", "search_analyzer" => "whitespace_analyzer"} indexes :name_completion, type: 'multi_field' indexes :name, type: 'completion', index_analyzer:'simple', search_analyzer: 'simple', payloads: true end indexes :taxon_ids, type: 'multi_field' indexes :taxon_ids, type:'string', analyzer: 'simple' indexes :taxon_ids_ngram, type:'string', analyzer:'ngram_analyzer' end indexes :description, analyzer: 'snowball', include_in_all:false indexes :available_on, type: 'date', format: 'dateoptionaltime', include_in_all: false indexes :price, type: 'double', include_in_all:false indexes :sku, type: 'string', index: 'not_analyzed', include_in_all:false indexes :properties, type: 'string', index: 'not_analyzed', include_in_all:false end def as_indexed_json(options={}) result = as_json({ methods: [:price, :sku], only: [:available_on, :description, :name], include: { variants: { only: [:sku], include: { option_values: { only: [:name, :presentation] } } } } }) result[:properties] = property_list unless property_list.empty? result[:taxon_ids] = taxons.map(&:self_and_ancestors).flatten.uniq.map(&:id) unless taxons.empty? result end # inner class used query elasticsearch. idea query dynamically build based on parameters. class product::elasticsearchquery include ::virtus.model attribute :from, integer, default: 0 attribute :price_min, float attribute :price_max, float attribute :properties, hash attribute :query, string attribute :taxons, array attribute :browse_mode, boolean attribute :sorting, string # when browse_mode enabled, taxon filter placed @ top level. causes results limited, facetting done on complete dataset. # when browse_mode disabled, taxon filter placed inside filtered query. causes facets limited resulting set. # method creates actual query based on current attributes. # idea use following schema , fill in blanks. # { # query: { # filtered: { # query: { # query_string: { query: , fields: [] } # } # filter: { # and: [ # { terms: { taxons: [] } }, # { terms: { properties: [] } } # ] # } # } # } # filter: { range: { price: { lte: , gte: } } }, # sort: [], # from: , # facets: # } def to_hash q = { match_all: {} } unless query.blank? # nil or empty q = { query_string: { query: query, fields: ['name^5','description','sku'], default_operator: 'and', use_dis_max: true } } end query = q and_filter = [] unless @properties.nil? || @properties.empty? # transform properties [{"key1" => ["value_a","value_b"]},{"key2" => ["value_a"]} # { terms: { properties: ["key1||value_a","key1||value_b"] } # { terms: { properties: ["key2||value_a"] } # enforces "and" relation between different property values , "or" relation between same property values properties = @properties.map {|k,v| [k].product(v)}.map |pair| and_filter << { terms: { properties: pair.map {|prop| prop.join("||")} } } end end sorting = case @sorting when "name_asc" [ {"name.untouched" => { order: "asc" }}, {"price" => { order: "asc" }}, "_score" ] when "name_desc" [ {"name.untouched" => { order: "desc" }}, {"price" => { order: "asc" }}, "_score" ] when "price_asc" [ {"price" => { order: "asc" }}, {"name.untouched" => { order: "asc" }}, "_score" ] when "price_desc" [ {"price" => { order: "desc" }}, {"name.untouched" => { order: "asc" }}, "_score" ] when "score" [ "_score", {"name.untouched" => { order: "asc" }}, {"price" => { order: "asc" }} ] else [ {"name.untouched" => { order: "asc" }}, {"price" => { order: "asc" }}, "_score" ] end # facets facets = { price: { statistical: { field: "price" } }, properties: { terms: { field: "properties", order: "count", size: 1000000 } }, taxon_ids: { terms: { field: "taxon_ids", size: 1000000 } } } # basic skeleton result = { min_score: 0.1, query: { filtered: {} }, sort: sorting, from: from, facets: facets } # add query , filters filtered result[:query][:filtered][:query] = query # taxon , property filters have effect on facets and_filter << { terms: { taxon_ids: taxons } } unless taxons.empty? # return products available #and_filter << { range: { available_on: { lte: "now" } } } result[:query][:filtered][:filter] = { "and" => and_filter } unless and_filter.empty? # add price filter outside query because should have no effect on facets if price_min && price_max && (price_min < price_max) result[:filter] = { range: { price: { gte: price_min, lte: price_max } } } end result end end private def property_list product_properties.map{|pp| "#{pp.property.name}||#{pp.value}"} end end end
i have following model 2 mappings, 1 conventional search other search suggestions. latter mapping required according elasticsearch docs enable completion suggestor - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html.
when indexing above model following error: [400] {"error":"mapperparsingexception[mapping [spree_product]]; nested: mapperparsingexception[root type mapping not empty after parsing! remaining fields: [_suggest : {search_analyzer=whitespace_analyzer, index_analyzer=simple}]]; ","status":400} /usr/local/bundle/gems/elasticsearch-transport-1.0.12/lib/elasticsearch/transport/transport/base.rb:135:in __raise_transport_error' /usr/local/bundle/gems/elasticsearch-transport-1.0.12/lib/elasticsearch/transport/transport/base.rb:227:in
perform_request' /usr/local/bundle/gems/elasticsearch-transport-1.0.12/lib/elasticsearch/transport/transport/http/faraday.rb:20:in perform_request' /usr/local/bundle/gems/elasticsearch-transport-1.0.12/lib/elasticsearch/transport/client.rb:119:in
perform_request' /usr/local/bundle/gems/elasticsearch-api-1.0.12/lib/elasticsearch/api/namespace/common.rb:21:in perform_request' /usr/local/bundle/gems/elasticsearch-api-1.0.12/lib/elasticsearch/api/actions/indices/create.rb:77:in
create' /tmp/spree_elasticsearch/lib/tasks/load_products.rake:5:in `block (2 levels) in ' tasks: top => spree_elasticsearch:load_products
found issue. not idea nest field completion
set mapping: mapping indexes :name_completion, type: 'completion', payloads: true end
ensure correct json returned indexing:
[:name_completion] = { input: name, output: name, payload: "" }
referenced example: https://github.com/elastic/elasticsearch-rails/commit/ded20356920802c35d258756113acfd95b25ade6
Comments
Post a Comment