diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ecad7c..1a8fe54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Please mark backwards incompatible changes with an exclamation mark at the start ## [Unreleased] +### Added +- Allow the `Elasticsearch::Index` and `Elasticsearch::Indexes`'s `#push` method + to receive a `type` parameter, just like `#index` does. + ## [29.4.0] - 2026-01-28 ### Added diff --git a/lib/jay_api/elasticsearch/indexable.rb b/lib/jay_api/elasticsearch/indexable.rb index 02f4c57..3dce1f9 100644 --- a/lib/jay_api/elasticsearch/indexable.rb +++ b/lib/jay_api/elasticsearch/indexable.rb @@ -48,9 +48,13 @@ def initialize(client:, index_names:, batch_size: 100, logger: nil) # Pushes a record into the index. (This does not send the record to the # Elasticsearch instance, only puts it into the send queue). # @param [Hash] data The data to be pushed to the index. - def push(data) + # @param [String, nil] type The type of the document. When set to +nil+ + # the decision is left to Elasticsearch's API. Which will normally + # default to +_doc+. + def push(data, type: DEFAULT_DOC_TYPE) + validate_type(type) index_names.each do |index_name| - batch << { index: { _index: index_name, _type: 'nested', data: data } } + batch << { index: { _index: index_name, _type: type, data: data }.compact } end flush! if batch.size >= batch_size @@ -78,8 +82,7 @@ def push(data) # For information on the contents of this Hash please see: # https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#docs-index-api-response-body def index(data, type: DEFAULT_DOC_TYPE) - raise ArgumentError, "Unsupported type: '#{type}'" unless SUPPORTED_TYPES.include?(type) - + validate_type(type) index_names.map { |index_name| client.index index: index_name, type: type, body: data } end @@ -220,6 +223,15 @@ def query_results(query, response, batch_counter, type) def async @async ||= JayAPI::Elasticsearch::Async.new(self) end + + # @param [String, nil] type The type of the document. When set to +nil+ + # the decision is left to Elasticsearch's API. Which will normally + # default to +_doc+. + # @raise [ArgumentError] When the given +type+ is not one of the + # +SUPPORTED_TYPES+ + def validate_type(type) + raise ArgumentError, "Unsupported type: '#{type}'" unless SUPPORTED_TYPES.include?(type) + end end end end diff --git a/spec/jay_api/elasticsearch/index_spec.rb b/spec/jay_api/elasticsearch/index_spec.rb index dc92cf3..8c3dd7e 100644 --- a/spec/jay_api/elasticsearch/index_spec.rb +++ b/spec/jay_api/elasticsearch/index_spec.rb @@ -49,8 +49,10 @@ describe '#push' do subject(:method_call) { described_method.call } + let(:method_params) { {} } + # Needed in order to be able to repeat the method call - let(:described_method) { -> { index.push(data) } } + let(:described_method) { -> { index.push(data, **method_params) } } let(:constructor_params) do super().merge(batch_size: 10) @@ -70,26 +72,62 @@ } end - context 'when the amount of data is smaller than the batch size' do - it 'enqueues the data but does not push it to Elasticsearch' do - expect(client).not_to receive(:bulk) - 5.times { described_method.call } + shared_examples_for '#push' do + context 'when the amount of data is smaller than the batch size' do + it 'enqueues the data but does not push it to Elasticsearch' do + expect(client).not_to receive(:bulk) + 5.times { described_method.call } + end end - end - context 'when the amount of data matches the batch size' do - it 'puts the data with the correct structure in the queue' do - expect(client).to receive(:bulk).with(expected_data) - 10.times { described_method.call } + context 'when the amount of data matches the batch size' do + it 'puts the data with the correct structure in the queue' do + expect(client).to receive(:bulk).with(expected_data) + 10.times { described_method.call } + end end + + context 'when the amount of data goes over the batch size' do + it 'pushes the data to Elasticsearch every time the batch size is hit' do + expect(client).to receive(:bulk).with(expected_data).exactly(3).times + 30.times { described_method.call } + end + end + end + + context 'when no type is specified' do + let(:method_params) { {} } + + it_behaves_like '#push' + end + + context "when 'nested' is specified as a type" do + let(:method_params) { { type: 'nested' } } + + it_behaves_like '#push' end - context 'when the amount of data goes over the batch size' do - it 'pushes the data to Elasticsearch every time the batch size is hit' do - expect(client).to receive(:bulk).with(expected_data).exactly(3).times - 30.times { described_method.call } + context 'when nil is specified as a type' do + let(:method_params) { { type: nil } } + + let(:expected_data) do + { + body: [ + { + index: { + _index: index_name, + # _type: 'nested', <==== Removed, it should not be present + data: data + } + } + ] * 10 + } end + + it_behaves_like '#push' end + + it_behaves_like 'Indexable#validate_type' end describe '#index' do @@ -164,7 +202,7 @@ end end - it_behaves_like 'Indexable#index' + it_behaves_like 'Indexable#validate_type' end describe '#queue_size' do diff --git a/spec/jay_api/elasticsearch/indexable_shared.rb b/spec/jay_api/elasticsearch/indexable_shared.rb index 12772e5..8384078 100644 --- a/spec/jay_api/elasticsearch/indexable_shared.rb +++ b/spec/jay_api/elasticsearch/indexable_shared.rb @@ -47,7 +47,7 @@ end end -RSpec.shared_examples_for 'Indexable#index' do +RSpec.shared_examples_for 'Indexable#validate_type' do context 'when type is set to an invalid value' do let(:method_params) { { type: 'flatten' } } diff --git a/spec/jay_api/elasticsearch/indexes_spec.rb b/spec/jay_api/elasticsearch/indexes_spec.rb index 6b25480..ec736d7 100644 --- a/spec/jay_api/elasticsearch/indexes_spec.rb +++ b/spec/jay_api/elasticsearch/indexes_spec.rb @@ -123,8 +123,10 @@ describe '#push' do subject(:method_call) { described_method.call } + let(:method_params) { {} } + # Needed in order to be able to repeat the method call - let(:described_method) { -> { indexes.push(data) } } + let(:described_method) { -> { indexes.push(data, **method_params) } } let(:constructor_params) do super().merge(batch_size: 15) @@ -144,31 +146,67 @@ } end - context 'when the amount of data is smaller than the batch size' do - it 'enqueues the data but does not push it to Elasticsearch' do - expect(client).not_to receive(:bulk) - 3.times { described_method.call } + shared_examples_for '#push' do + context 'when the amount of data is smaller than the batch size' do + it 'enqueues the data but does not push it to Elasticsearch' do + expect(client).not_to receive(:bulk) + 3.times { described_method.call } + end end - end - context 'when the amount of data matches the batch size' do - it 'puts the data with the correct structure in the queue' do - expect(client).to receive(:bulk).with(expected_data) - 5.times { described_method.call } + context 'when the amount of data matches the batch size' do + it 'puts the data with the correct structure in the queue' do + expect(client).to receive(:bulk).with(expected_data) + 5.times { described_method.call } + end + + it 'leaves the queue empty' do + 5.times { described_method.call } + expect(indexes.queue_size).to be_zero + end end - it 'leaves the queue empty' do - 5.times { described_method.call } - expect(indexes.queue_size).to be_zero + context 'when the amount of data goes over the batch size' do + it 'pushes the data to Elasticsearch every time the batch size is hit' do + expect(client).to receive(:bulk).with(expected_data).exactly(3).times + 15.times { described_method.call } + end end end - context 'when the amount of data goes over the batch size' do - it 'pushes the data to Elasticsearch every time the batch size is hit' do - expect(client).to receive(:bulk).with(expected_data).exactly(3).times - 15.times { described_method.call } + context 'when no type is specified' do + let(:method_params) { {} } + + it_behaves_like '#push' + end + + context "when 'nested' is specified as a type" do + let(:method_params) { { type: 'nested' } } + + it_behaves_like '#push' + end + + context 'when nil is specified as a type' do + let(:method_params) { { type: nil } } + + let(:expected_data) do + { + body: index_names.cycle(5).map do |index_name| + { + index: { + _index: index_name, + # _type: 'nested', <=== Removed, should not be present + data: data + } + } + end + } end + + it_behaves_like '#push' end + + it_behaves_like 'Indexable#validate_type' end describe '#index' do @@ -298,7 +336,7 @@ end end - it_behaves_like 'Indexable#index' + it_behaves_like 'Indexable#validate_type' end describe '#queue_size' do