From c9f2af4dc38a2ec68332b74159a2b6c0278668b8 Mon Sep 17 00:00:00 2001 From: Andrew Kane Date: Tue, 13 Aug 2013 01:42:54 -0700 Subject: [PATCH] No more Tire DSL --- lib/searchkick/search.rb | 319 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------------------------------------------------------------------- test/boost_test.rb | 7 ++++--- 2 files changed, 181 insertions(+), 145 deletions(-) diff --git a/lib/searchkick/search.rb b/lib/searchkick/search.rb index 405084f..1a8ca13 100644 --- a/lib/searchkick/search.rb +++ b/lib/searchkick/search.rb @@ -35,165 +35,196 @@ module Searchkick all = term == "*" - # TODO lose Tire DSL for more flexibility - s = - Tire::Search::Search.new do - query do - custom_filters_score do - query do - if !all - boolean do - must do - if options[:autocomplete] - match fields, term, analyzer: "searchkick_autocomplete_search" - else - dis_max do - query do - match fields, term, use_dis_max: false, boost: 10, operator: operator, analyzer: "searchkick_search" - end - query do - match fields, term, use_dis_max: false, boost: 10, operator: operator, analyzer: "searchkick_search2" - end - query do - match fields, term, use_dis_max: false, fuzziness: 1, max_expansions: 3, operator: operator, analyzer: "searchkick_search" - end - query do - match fields, term, use_dis_max: false, fuzziness: 1, max_expansions: 3, operator: operator, analyzer: "searchkick_search2" - end - end - end - end - if conversions_field and options[:conversions] != false - should do - nested path: conversions_field, score_mode: "total" do - query do - custom_score script: "doc['count'].value" do - match "query", term - end - end - end - end - end - end - end - end - if options[:boost] - filter do - filter :exists, field: options[:boost] - script "log(doc['#{options[:boost]}'].value + 2.718281828)" - end + if all + payload = { + match_all: {} + } + else + if options[:autocomplete] + payload = { + multi_match: { + fields: fields, + query: term, + analyzer: "searchkick_autocomplete_search" + } + } + else + shared_options = { + fields: fields, + query: term, + use_dis_max: false, + operator: operator + } + payload = { + dis_max: { + queries: [ + {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search")}, + {multi_match: shared_options.merge(boost: 10, analyzer: "searchkick_search2")}, + {multi_match: shared_options.merge(fuzziness: 1, max_expansions: 3, analyzer: "searchkick_search")}, + {multi_match: shared_options.merge(fuzziness: 1, max_expansions: 3, analyzer: "searchkick_search2")}, + ] + } + } + end + + if conversions_field and options[:conversions] != false + # wrap payload in a bool query + payload = { + bool: { + must: payload, + should: { + nested: { + path: conversions_field, + score_mode: "total", + query: { + custom_score: { + query: { + match: { + query: term + } + }, + script: "doc['count'].value" + } + } + } + } + } + } + end + end + + custom_filters = [] + + if options[:boost] + custom_filters << { + filter: { + exists: { + field: options[:boost], + } + }, + script: "log(doc['#{options[:boost]}'].value + 2.718281828)" + } + end + + if options[:user_id] and personalize_field + custom_filters << { + filter: { + term: { + personalize_field => options[:user_id] + }, + }, + boost: 100 + } + end + + if custom_filters.any? + payload = { + custom_filters_score: { + query: payload, + filters: custom_filters, + score_mode: "total" + } + } + end + + payload = { + query: payload, + size: per_page + } + payload[:from] = offset if offset + payload[:explain] = options[:explain] if options[:explain] + + # order + if options[:order] + order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc} + payload[:sort] = order + end + + # where + # TODO expand or + where_filters = + proc do |where| + filters = [] + (where || {}).each do |field, value| + if field == :or + value.each do |or_clause| + filters << {or: or_clause.map{|or_statement| {term: or_statement} }} end - if options[:user_id] and personalize_field - filter do - filter :term, personalize_field => options[:user_id] - boost 100 - end + else + # expand ranges + if value.is_a?(Range) + value = {gte: value.first, (value.exclude_end? ? :lt : :lte) => value.last} end - score_mode "total" - end - end - size per_page - from offset if offset - explain options[:explain] if options[:explain] - - # order - if options[:order] - order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc} - sort do - order.each do |k, v| - by k, v - end - end - end - - # where - # TODO expand or - where_filters = - proc do |where| - filters = [] - (where || {}).each do |field, value| - if field == :or - value.each do |or_clause| - filters << {or: or_clause.map{|or_statement| {term: or_statement} }} - end - else - # expand ranges - if value.is_a?(Range) - value = {gte: value.first, (value.exclude_end? ? :lt : :lte) => value.last} - end - if value.is_a?(Array) # in query - filters << {terms: {field => value}} - elsif value.is_a?(Hash) - value.each do |op, op_value| - if op == :not # not equal - if op_value.is_a?(Array) - filters << {not: {terms: {field => op_value}}} - else - filters << {not: {term: {field => op_value}}} - end - else - range_query = - case op - when :gt - {from: op_value, include_lower: false} - when :gte - {from: op_value, include_lower: true} - when :lt - {to: op_value, include_upper: false} - when :lte - {to: op_value, include_upper: true} - else - raise "Unknown where operator" - end - filters << {range: {field => range_query}} - end + if value.is_a?(Array) # in query + filters << {terms: {field => value}} + elsif value.is_a?(Hash) + value.each do |op, op_value| + if op == :not # not equal + if op_value.is_a?(Array) + filters << {not: {terms: {field => op_value}}} + else + filters << {not: {term: {field => op_value}}} end else - filters << {term: {field => value}} + range_query = + case op + when :gt + {from: op_value, include_lower: false} + when :gte + {from: op_value, include_lower: true} + when :lt + {to: op_value, include_upper: false} + when :lte + {to: op_value, include_upper: true} + else + raise "Unknown where operator" + end + filters << {range: {field => range_query}} end end + else + filters << {term: {field => value}} end - filters end - - where_filters.call(options[:where]).each do |f| - type, value = f.first - filter type, value end + filters + end - # facets - if options[:facets] - facets = options[:facets] || {} - if facets.is_a?(Array) # convert to more advanced syntax - facets = Hash[ facets.map{|f| [f, {}] } ] - end + # filters + filters = where_filters.call(options[:where]) + if filters.any? + payload[:filter] = { + and: filters + } + end - facets.each do |field, facet_options| - facet_filters = where_filters.call(facet_options[:where]) - facet field do - terms field - if facet_filters.size == 1 - type, value = facet_filters.first.first - facet_filter type, value - elsif facet_filters.size > 1 - facet_filter :and, *facet_filters - end - end - end - end + # facets + if options[:facets] + facets = options[:facets] || {} + if facets.is_a?(Array) # convert to more advanced syntax + facets = Hash[ facets.map{|f| [f, {}] } ] end - payload = s.to_hash + payload[:facets] = {} + facets.each do |field, facet_options| + payload[:facets][field] = { + terms: { + field: field + } + } - if all - payload[:query][:custom_filters_score][:query][:match_all] = {} + facet_filters = where_filters.call(facet_options[:where]) + if facet_filters + payload[:facets][field][:facet_filter] = { + and: { + filters: facet_filters + } + } + end + end end - # An empty array will cause only the _id and _type for each hit to be returned - # http://www.elasticsearch.org/guide/reference/api/search/fields/ - payload[:fields] = [] if load - # suggestions if options[:suggest] suggest_fields = (@searchkick_options[:suggest] || []).map(&:to_s) @@ -211,6 +242,10 @@ module Searchkick end end + # An empty array will cause only the _id and _type for each hit to be returned + # http://www.elasticsearch.org/guide/reference/api/search/fields/ + payload[:fields] = [] if load + search = Tire::Search::Search.new(index_name, load: load, payload: payload) Searchkick::Results.new(search.json, search.options.merge(term: term)) end diff --git a/test/boost_test.rb b/test/boost_test.rb index d461efe..8e955b1 100644 --- a/test/boost_test.rb +++ b/test/boost_test.rb @@ -25,10 +25,11 @@ class TestBoost < Minitest::Unit::TestCase def test_boost store [ - {name: "Organic Tomato A"}, - {name: "Tomato B", orders_count: 10} + {name: "Tomato A"}, + {name: "Tomato B", orders_count: 10}, + {name: "Tomato C", orders_count: 100} ] - assert_order "tomato", ["Tomato B", "Organic Tomato A"], boost: "orders_count" + assert_order "tomato", ["Tomato C", "Tomato B", "Tomato A"], boost: "orders_count" end def test_boost_zero -- libgit2 0.21.0