From 7263c8fa5f30e0e72a64f108fc6599089b948782 Mon Sep 17 00:00:00 2001 From: Sergio Bobillier Date: Fri, 28 Aug 2026 17:36:26 +0200 Subject: [PATCH] [ESRTEST-31237] Add 'missing' to Elasticsearch's Terms aggregation Adds a 'missing' parameter to Elasticsearch's Terms aggregation. The parameter allows the user to specify a value to be used for documents that do not have the field being aggregated. Without specifying a 'missing' value, documents without the field are excluded from the aggregation. So, this parameter is required if one wants to prevent the exclusion of said documents. Note: To avoid RuboCop's Metrics/ParameterLists and Reek's LongParameterList warnings in the Aggregations class, the keyword arguments for the #terms method are being shortened to **params. This is not done in the Terms class's #initialize method in order to retain Ruby's validation of the keywords. --- CHANGELOG.md | 3 ++ .../query_builder/aggregations.rb | 6 ++-- .../query_builder/aggregations/terms.rb | 22 ++++++++++---- .../query_builder/aggregations/terms_spec.rb | 30 +++++++++++++++++++ .../query_builder/aggregations_spec.rb | 23 +++++++++++--- 5 files changed, 70 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc2e2e..690a907 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Please mark backwards incompatible changes with an exclamation mark at the start ## [Unreleased] +### Added +- The `missing` parameter for Elasticsearch's `Terms` aggregation. + ## [29.10.0] - 2026-08-21 ### Added diff --git a/lib/jay_api/elasticsearch/query_builder/aggregations.rb b/lib/jay_api/elasticsearch/query_builder/aggregations.rb index 0af2c6c..dd80a85 100644 --- a/lib/jay_api/elasticsearch/query_builder/aggregations.rb +++ b/lib/jay_api/elasticsearch/query_builder/aggregations.rb @@ -32,11 +32,9 @@ def initialize # Adds a +terms+ type aggregation. For information about the parameters # @see JayAPI::Elasticsearch::QueryBuilder::Aggregations::Terms#initialize - def terms(name, field: nil, script: nil, size: nil, order: nil) + def terms(name, **params) add( - ::JayAPI::Elasticsearch::QueryBuilder::Aggregations::Terms.new( - name, field: field, script: script, size: size, order: order - ) + ::JayAPI::Elasticsearch::QueryBuilder::Aggregations::Terms.new(name, **params) ) end diff --git a/lib/jay_api/elasticsearch/query_builder/aggregations/terms.rb b/lib/jay_api/elasticsearch/query_builder/aggregations/terms.rb index 18aab7f..8942ab4 100644 --- a/lib/jay_api/elasticsearch/query_builder/aggregations/terms.rb +++ b/lib/jay_api/elasticsearch/query_builder/aggregations/terms.rb @@ -14,7 +14,7 @@ class Aggregations # Information about this type of aggregation can be found in: # https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html class Terms < ::JayAPI::Elasticsearch::QueryBuilder::Aggregations::Aggregation - attr_reader :field, :script, :size, :order + attr_reader :field, :script, :size, :order, :missing # @param [String] name The name used by Elasticsearch to identify each # of the aggregations. @@ -29,9 +29,15 @@ class Terms < ::JayAPI::Elasticsearch::QueryBuilder::Aggregations::Aggregation # aggregation. By default, the +terms+ aggregation orders terms by # descending document +_count+. This can be changed by providing a # custom +order+ hash. + # @param [String] missing The value to use for documents that are + # missing a value in the given +field+ or for whom the +script+ + # returns +null+. When +missing+ is not given, such documents are + # ignored. # @raise [ArgumentError] If neither a +field+ nor a +script+ are given # or if both of them are given. Only one should be present. - def initialize(name, field: nil, script: nil, size: nil, order: nil) + # rubocop:disable Metrics/ParameterLists -- Constraint by Elasticsearch's design + # :reek:LongParameterList -- Constraint by Elasticsearch's design + def initialize(name, field: nil, script: nil, size: nil, order: nil, missing: nil) if (field.present? && script.present?) || (field.blank? && script.blank?) raise ArgumentError, "Either 'field' or 'script' must be provided" end @@ -42,13 +48,16 @@ def initialize(name, field: nil, script: nil, size: nil, order: nil) @script = script @size = size @order = order + @missing = missing end + # rubocop:enable Metrics/ParameterLists + # @return [self] A copy of the receiver. def clone - self.class.new(name, field: field, script: script, size: size, order: order&.deep_dup).tap do |copy| - copy.aggregations = aggregations.clone - end + self.class.new( + name, field: field, script: script, size: size, order: order&.deep_dup, missing: missing + ).tap { |copy| copy.aggregations = aggregations.clone } end # @return [Hash] The Hash representation of the +Aggregation+. @@ -60,7 +69,8 @@ def to_h field: field, size: size, script: script&.to_h, - order: order + order: order, + missing: missing }.compact } end diff --git a/spec/jay_api/elasticsearch/query_builder/aggregations/terms_spec.rb b/spec/jay_api/elasticsearch/query_builder/aggregations/terms_spec.rb index 890032f..8541704 100644 --- a/spec/jay_api/elasticsearch/query_builder/aggregations/terms_spec.rb +++ b/spec/jay_api/elasticsearch/query_builder/aggregations/terms_spec.rb @@ -140,6 +140,20 @@ end end + context "when no 'missing' value has been given" do + it "haa the same value for 'missing' fields" do + expect(method_call.missing).to be(terms.missing) + end + end + + context "when a 'missing' value has been given" do + let(:constructor_params) { super().merge(missing: 'N/A') } + + it "haa the same value for 'missing' fields" do + expect(method_call.missing).to be(terms.missing) & eq('N/A') + end + end + it_behaves_like 'JayAPI::Elasticsearch::QueryBuilder::Aggregations::Terms::#clone' end @@ -230,5 +244,21 @@ expect(method_call).to eq(expected_hash) end end + + context "when a value for 'missing' fields has been given" do + let(:constructor_params) { super().merge(missing: 'N/A') } + + let(:expected_hash) do + { + 'genres' => { + terms: { field: 'genre', missing: 'N/A' } + } + } + end + + it "returns the expected hash (including the given 'missing' value)" do + expect(method_call).to eq(expected_hash) + end + end end end diff --git a/spec/jay_api/elasticsearch/query_builder/aggregations_spec.rb b/spec/jay_api/elasticsearch/query_builder/aggregations_spec.rb index 0f145e6..302846d 100644 --- a/spec/jay_api/elasticsearch/query_builder/aggregations_spec.rb +++ b/spec/jay_api/elasticsearch/query_builder/aggregations_spec.rb @@ -76,7 +76,7 @@ it 'creates the Terms instance with the expected parameters' do expect(JayAPI::Elasticsearch::QueryBuilder::Aggregations::Terms) - .to receive(:new).with(name, field: field, script: nil, size: nil, order: nil) + .to receive(:new).with(name, field: field) method_call end @@ -90,7 +90,7 @@ it 'creates the Terms instance with the expected parameters' do expect(JayAPI::Elasticsearch::QueryBuilder::Aggregations::Terms) - .to receive(:new).with(name, field: field, script: nil, size: 100, order: nil) + .to receive(:new).with(name, field: field, size: 100) method_call end @@ -105,7 +105,7 @@ it 'creates the Terms instance with the expected parameters' do expect(JayAPI::Elasticsearch::QueryBuilder::Aggregations::Terms) - .to receive(:new).with(name, field: field, script: nil, size: nil, order: { _key: :asc }) + .to receive(:new).with(name, field: field, order: { _key: :asc }) method_call end @@ -126,7 +126,22 @@ it 'creates the Terms instance with the expected parameters' do expect(JayAPI::Elasticsearch::QueryBuilder::Aggregations::Terms) - .to receive(:new).with(name, field: field, script: script, size: nil, order: nil) + .to receive(:new).with(name, field: field, script: script) + + method_call + end + end + + context "when a 'missing' value is provided" do + subject(:method_call) do + aggregations.terms( + name, field: field, missing: 'NULL' + ) + end + + it 'creates the Terms instance with the expected parameters' do + expect(JayAPI::Elasticsearch::QueryBuilder::Aggregations::Terms) + .to receive(:new).with(name, field: field, missing: 'NULL') method_call end