diff --git a/lib/searchkick/query.rb b/lib/searchkick/query.rb index 047b333..fa006e2 100644 --- a/lib/searchkick/query.rb +++ b/lib/searchkick/query.rb @@ -266,7 +266,7 @@ module Searchkick # filters filters = where_filters(options[:where]) if filters.any? - if options[:facets] + if options[:facets] || options[:aggs] payload[:filter] = { and: filters } @@ -336,6 +336,44 @@ module Searchkick end end + # aggregations + if options[:aggs] + aggs = options[:aggs] + payload[:aggs] = {} + + if aggs.is_a?(Array) # convert to more advanced syntax + aggs = aggs.map { |f| [f, {}] }.to_h + end + + aggs.each do |field, agg_options| + payload[:aggs][field] = { + terms: { + field: agg_options[:field] || field + } + } + + agg_options.deep_merge!(where: options.fetch(:where, {}).reject { |k| k == field } ) if options[:smart_aggs] == true + agg_filters = where_filters(agg_options[:where]) + + if agg_filters.any? + payload[:aggs][field] = { + filter: { + bool: { + must: agg_filters + } + }, + aggs: { + field => { + terms: { + field: field + } + } + } + } + end + end + end + # suggestions if options[:suggest] suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s) diff --git a/lib/searchkick/results.rb b/lib/searchkick/results.rb index 060d0a8..4d7ea63 100644 --- a/lib/searchkick/results.rb +++ b/lib/searchkick/results.rb @@ -75,6 +75,17 @@ module Searchkick response["facets"] end + def aggs + response["aggregations"].each do |field, filtered_agg| + buckets = filtered_agg[field] + # move the buckets one level above into the field hash + if buckets + filtered_agg.delete(field) + filtered_agg.merge!(buckets) + end + end + end + def took response["took"] end diff --git a/test/aggs_test.rb b/test/aggs_test.rb new file mode 100644 index 0000000..82cce31 --- /dev/null +++ b/test/aggs_test.rb @@ -0,0 +1,77 @@ +require_relative "test_helper" +require "active_support/core_ext" + +class TestAggs < Minitest::Test + + def setup + super + store [ + {name: "Product Show", latitude: 37.7833, longitude: 12.4167, store_id: 1, in_stock: true, color: "blue", price: 21, created_at: 2.days.ago}, + {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}, + {name: "Product B", latitude: 43.9333, longitude: -122.4667, store_id: 2, in_stock: false, color: "red", price: 5}, + {name: "Foo", latitude: 43.9333, longitude: 12.4667, store_id: 3, in_stock: false, color: "yellow", price: 15} + ] + end + + def test_basic + assert_equal ({1 => 1, 2 => 2}), store_agg(aggs: [:store_id]) + end + + def test_where + assert_equal ({1 => 1}), store_agg(aggs: {store_id: {where: {in_stock: true}}}) + end + + def test_field + assert_equal ({1 => 1, 2 => 2}), store_agg(aggs: {store_id: {}}) + assert_equal ({1 => 1, 2 => 2}), store_agg(aggs: {store_id: {field: "store_id"}}) + assert_equal ({1 => 1, 2 => 2}), store_agg({aggs: {store_id_new: {field: "store_id"}}}, "store_id_new") + end + + def test_where_no_smart_aggs + assert_equal ({2 => 2}), store_agg(where: {color: "red"}, aggs: {store_id: {where: {in_stock: false}}}) + assert_equal ({2 => 2}), store_agg(where: {color: "blue"}, aggs: {store_id: {where: {in_stock: false}}}) + end + + def test_smart_aggs + assert_equal ({1 => 1}), store_agg(where: {in_stock: true}, aggs: [:store_id], smart_aggs: true) + end + + def test_smart_aggs_where + assert_equal ({2 => 1}), store_agg(where: {color: "red"}, aggs: {store_id: {where: {in_stock: false}}}, smart_aggs: true) + end + + def test_smart_aggs_where_override + assert_equal ({2 => 1}), store_agg(where: {color: "red"}, aggs: {store_id: {where: {in_stock: false, color: "blue"}}}, smart_aggs: true) + assert_equal ({}), store_agg(where: {color: "blue"}, aggs: {store_id: {where: {in_stock: false, color: "red"}}}, smart_aggs: true) + end + + def test_smart_aggs_skip_agg + assert_equal ({1 => 1, 2 => 2}), store_agg(where: {store_id: 2}, aggs: [:store_id], smart_aggs: true) + end + + def test_smart_aggs_skip_agg_complex + assert_equal ({1 => 1, 2 => 1}), store_agg(where: {store_id: 2, price: {gt: 5}}, aggs: [:store_id], smart_aggs: true) + end + + def test_multiple_aggs + assert_equal ({"store_id" => {1 => 1, 2 => 2}, "color" => {"blue" => 1, "green" => 1, "red" => 1}}), store_multiple_aggs(aggs: [:store_id, :color]) + end + + protected + + def buckets_as_hash(agg) + agg["buckets"].map { |v| [v["key"], v["doc_count"]] }.to_h + end + + def store_agg(options, agg_key = "store_id") + buckets = Product.search("Product", options).aggs[agg_key] + buckets_as_hash(buckets) + end + + def store_multiple_aggs(options) + Product.search("Product", options).aggs.map do |field, filtered_agg| + [field, buckets_as_hash(filtered_agg)] + end.to_h + end + +end -- libgit2 0.21.0