2015-06-19 93 views
1
module Spree 
    Product.class_eval do 
     include Elasticsearch::Model 

index_name Spree::ElasticsearchSettings.index 
document_type 'spree_product' 

    mapping _all: {"index_analyzer" => "nGram_analyzer", "search_analyzer" => "whitespace_analyzer"} do 
    indexes :name, type: 'multi_field' do 
     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' do 
     indexes :name, type: 'string', index_analyzer:'simple' 
    end 

    indexes :name_completion, type: 'multi_field' do 
     indexes :untouched, type: 'string', include_in_all: false, index: 'not_analyzed' 
    end 

    indexes :taxon_ids, type: 'multi_field' do 
     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"} do 
    indexes :name_completion, type: 'multi_field' do 
     indexes :name, type: 'completion', index_analyzer:'simple', search_analyzer: 'simple', payloads: true 
    end 


    indexes :taxon_ids, type: 'multi_field' do 
     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 to query elasticsearch. The idea is that the query is dynamically build based on the 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 is enabled, the taxon filter is placed at top level. This causes the results to be limited, but facetting is done on the complete dataset. 
    # When browse_mode is disabled, the taxon filter is placed inside the filtered query. This causes the facets to be limited to the resulting set. 

    # Method that creates the actual query based on the current attributes. 
    # The idea is to always to use the following schema and fill in the 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 from [{"key1" => ["value_a","value_b"]},{"key2" => ["value_a"]} 
     # to { terms: { properties: ["key1||value_a","key1||value_b"] } 
     # { terms: { properties: ["key2||value_a"] } 
     # This enforces "and" relation between different property values and "or" relation between same property values 
     properties = @properties.map {|k,v| [k].product(v)}.map do |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 and filters to filtered 
    result[:query][:filtered][:query] = query 
    # taxon and property filters have an effect on the facets 
    and_filter << { terms: { taxon_ids: taxons } } unless taxons.empty? 
    # only return products that are available 
    #and_filter << { range: { available_on: { lte: "now" } } } 
    result[:query][:filtered][:filter] = { "and" => and_filter } unless and_filter.empty? 

    # add price filter outside the query because it 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 

我有以下模型與兩個映射,一個用於傳統搜索另一個用於搜索建議。根據ElasticSearch文檔,後者映射是必需的,以啓用完成建議者 - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.htmlRuby elasticsearch-model - 配置多個映射

當索引上述模型得到以下錯誤: [400] { 「錯誤」:「MapperParsingException [映射[spree_product]];嵌套:MapperParsingException [根類型映射不解析剩餘字段之後爲空:[_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:__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 cr '在 /tmp/spree_elasticsearch/lib/tasks/load_products.rake:5:in`塊(2級)' 任務eate:TOP => spree_elasticsearch:load_products

回答