Commit 9462cd49c40e63a99980c34d8204dec4d41ef8a3

Authored by Andrew Kane
2 parents 54e5e5a6 aa6812c4

Merge branch 'meetrajesh-support_for_aggregations'

CHANGELOG.md
1 1 ## 0.9.2 [unreleased]
2 2  
  3 +- Added support for aggregations
3 4 - Added ability to use misspellings for partial matches
4 5 - Added `fragment_size` option for highlight
5 6 - Added `took` method to results
... ...
lib/searchkick/query.rb
... ... @@ -266,7 +266,7 @@ module Searchkick
266 266 # filters
267 267 filters = where_filters(options[:where])
268 268 if filters.any?
269   - if options[:facets]
  269 + if options[:facets] || options[:aggs]
270 270 payload[:filter] = {
271 271 and: filters
272 272 }
... ... @@ -336,6 +336,43 @@ module Searchkick
336 336 end
337 337 end
338 338  
  339 + # aggregations
  340 + if options[:aggs]
  341 + aggs = options[:aggs]
  342 + payload[:aggs] = {}
  343 +
  344 + if aggs.is_a?(Array) # convert to more advanced syntax
  345 + aggs = aggs.map { |f| [f, {}] }.to_h
  346 + end
  347 +
  348 + aggs.each do |field, agg_options|
  349 + size = agg_options[:limit] ? agg_options[:limit] : 100_000
  350 +
  351 + payload[:aggs][field] = {
  352 + terms: {
  353 + field: agg_options[:field] || field,
  354 + size: size
  355 + }
  356 + }
  357 +
  358 + agg_options.deep_merge!(where: options.fetch(:where, {}).reject { |k| k == field } ) if options[:smart_aggs] == true
  359 + agg_filters = where_filters(agg_options[:where])
  360 +
  361 + if agg_filters.any?
  362 + payload[:aggs][field] = {
  363 + filter: {
  364 + bool: {
  365 + must: agg_filters
  366 + }
  367 + },
  368 + aggs: {
  369 + field => payload[:aggs][field]
  370 + }
  371 + }
  372 + end
  373 + end
  374 + end
  375 +
339 376 # suggestions
340 377 if options[:suggest]
341 378 suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s)
... ...
lib/searchkick/results.rb
... ... @@ -75,6 +75,19 @@ module Searchkick
75 75 response["facets"]
76 76 end
77 77  
  78 + def aggs
  79 + @aggs ||= begin
  80 + response["aggregations"].dup.each do |field, filtered_agg|
  81 + buckets = filtered_agg[field]
  82 + # move the buckets one level above into the field hash
  83 + if buckets
  84 + filtered_agg.delete(field)
  85 + filtered_agg.merge!(buckets)
  86 + end
  87 + end
  88 + end
  89 + end
  90 +
78 91 def took
79 92 response["took"]
80 93 end
... ...
test/aggs_test.rb 0 โ†’ 100644
... ... @@ -0,0 +1,84 @@
  1 +require_relative "test_helper"
  2 +require "active_support/core_ext"
  3 +
  4 +class TestAggs < Minitest::Test
  5 +
  6 + def setup
  7 + super
  8 + store [
  9 + {name: "Product Show", latitude: 37.7833, longitude: 12.4167, store_id: 1, in_stock: true, color: "blue", price: 21, created_at: 2.days.ago},
  10 + {name: "Product Hide", latitude: 29.4167, longitude: -98.5000, store_id: 2, in_stock: false, color: "green", price: 25, created_at: 2.days.from_now},
  11 + {name: "Product B", latitude: 43.9333, longitude: -122.4667, store_id: 2, in_stock: false, color: "red", price: 5},
  12 + {name: "Foo", latitude: 43.9333, longitude: 12.4667, store_id: 3, in_stock: false, color: "yellow", price: 15}
  13 + ]
  14 + end
  15 +
  16 + def test_basic
  17 + assert_equal ({1 => 1, 2 => 2}), store_agg(aggs: [:store_id])
  18 + end
  19 +
  20 + def test_where
  21 + assert_equal ({1 => 1}), store_agg(aggs: {store_id: {where: {in_stock: true}}})
  22 + end
  23 +
  24 + def test_field
  25 + assert_equal ({1 => 1, 2 => 2}), store_agg(aggs: {store_id: {}})
  26 + assert_equal ({1 => 1, 2 => 2}), store_agg(aggs: {store_id: {field: "store_id"}})
  27 + assert_equal ({1 => 1, 2 => 2}), store_agg({aggs: {store_id_new: {field: "store_id"}}}, "store_id_new")
  28 + end
  29 +
  30 + def test_limit
  31 + agg = Product.search("Product", aggs: {store_id: {limit: 1}}).aggs["store_id"]
  32 + assert_equal 1, agg["buckets"].size
  33 + # assert_equal 3, agg["doc_count"]
  34 + assert_equal 1, agg["sum_other_doc_count"]
  35 + end
  36 +
  37 + def test_where_no_smart_aggs
  38 + assert_equal ({2 => 2}), store_agg(where: {color: "red"}, aggs: {store_id: {where: {in_stock: false}}})
  39 + assert_equal ({2 => 2}), store_agg(where: {color: "blue"}, aggs: {store_id: {where: {in_stock: false}}})
  40 + end
  41 +
  42 + def test_smart_aggs
  43 + assert_equal ({1 => 1}), store_agg(where: {in_stock: true}, aggs: [:store_id], smart_aggs: true)
  44 + end
  45 +
  46 + def test_smart_aggs_where
  47 + assert_equal ({2 => 1}), store_agg(where: {color: "red"}, aggs: {store_id: {where: {in_stock: false}}}, smart_aggs: true)
  48 + end
  49 +
  50 + def test_smart_aggs_where_override
  51 + assert_equal ({2 => 1}), store_agg(where: {color: "red"}, aggs: {store_id: {where: {in_stock: false, color: "blue"}}}, smart_aggs: true)
  52 + assert_equal ({}), store_agg(where: {color: "blue"}, aggs: {store_id: {where: {in_stock: false, color: "red"}}}, smart_aggs: true)
  53 + end
  54 +
  55 + def test_smart_aggs_skip_agg
  56 + assert_equal ({1 => 1, 2 => 2}), store_agg(where: {store_id: 2}, aggs: [:store_id], smart_aggs: true)
  57 + end
  58 +
  59 + def test_smart_aggs_skip_agg_complex
  60 + assert_equal ({1 => 1, 2 => 1}), store_agg(where: {store_id: 2, price: {gt: 5}}, aggs: [:store_id], smart_aggs: true)
  61 + end
  62 +
  63 + def test_multiple_aggs
  64 + assert_equal ({"store_id" => {1 => 1, 2 => 2}, "color" => {"blue" => 1, "green" => 1, "red" => 1}}), store_multiple_aggs(aggs: [:store_id, :color])
  65 + end
  66 +
  67 + protected
  68 +
  69 + def buckets_as_hash(agg)
  70 + agg["buckets"].map { |v| [v["key"], v["doc_count"]] }.to_h
  71 + end
  72 +
  73 + def store_agg(options, agg_key = "store_id")
  74 + buckets = Product.search("Product", options).aggs[agg_key]
  75 + buckets_as_hash(buckets)
  76 + end
  77 +
  78 + def store_multiple_aggs(options)
  79 + Product.search("Product", options).aggs.map do |field, filtered_agg|
  80 + [field, buckets_as_hash(filtered_agg)]
  81 + end.to_h
  82 + end
  83 +
  84 +end
... ...