diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 9cf60aee..ab48169c 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -603,12 +603,16 @@ multi_search_1: |- { index_uid: 'movies', q: 'nemo', limit: 5 } { index_uid: 'movie_ratings', q: 'us' } ]) +facet_search_1: |- + client.index('books').facet_search('genres', 'fiction', filter: 'rating > 3') facet_search_2: |- client.index('books').update_faceting( sort_facet_values_by: { genres: 'count' } ) +facet_search_3: |- + client.index('books').facet_search('genres', 'c') get_dictionary_1: |- client.index('books').dictionary update_dictionary_1: |- diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1154e166..6bd8a3f8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,12 +1,12 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2023-10-10 14:01:22 UTC using RuboCop version 1.50.2. +# on 2023-10-11 10:43:57 UTC using RuboCop version 1.50.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 52 +# Offense count: 55 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. # AllowedMethods: refine Metrics/BlockLength: @@ -15,7 +15,7 @@ Metrics/BlockLength: # Offense count: 2 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 358 + Max: 363 # Offense count: 1 # Configuration parameters: Max, CountKeywordArgs. diff --git a/lib/meilisearch/index.rb b/lib/meilisearch/index.rb index df5d5e99..d1634fcf 100644 --- a/lib/meilisearch/index.rb +++ b/lib/meilisearch/index.rb @@ -234,6 +234,15 @@ def search(query, options = {}) response end + ### FACET SEARCH + + def facet_search(name, query = '', **options) + options.merge!(facet_name: name, facet_query: query) + options = Utils.transform_attributes(options) + + http_post("/indexes/#{@uid}/facet-search", options) + end + ### TASKS def task_endpoint diff --git a/spec/meilisearch/index/search/facet_search_spec.rb b/spec/meilisearch/index/search/facet_search_spec.rb new file mode 100644 index 00000000..f28a6114 --- /dev/null +++ b/spec/meilisearch/index/search/facet_search_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +RSpec.describe 'MeiliSearch::Index - Facet search' do + include_context 'search books with author, genre, year' + + before do + response = index.update_filterable_attributes(['genre', 'year', 'author']) + index.wait_for_task(response['taskUid']) + end + + it 'requires facet name parameter' do + expect { index.facet_search }.to raise_error ArgumentError + end + + context 'without query parameter' do + let(:results) { index.facet_search 'genre' } + + it 'returns all genres' do + expect(results).to include( + 'facetHits' => a_collection_including( + a_hash_including('value' => 'fantasy'), + a_hash_including('value' => 'adventure'), + a_hash_including('value' => 'romance') + ) + ) + end + + it 'returns all genre counts' do + expect(results).to include( + 'facetHits' => a_collection_including( + a_hash_including('count' => 3), + a_hash_including('count' => 3), + a_hash_including('count' => 2) + ) + ) + end + + it 'filters correctly' do + results = index.facet_search 'genre', filter: 'year < 1940' + + expect(results['facetHits']).to contain_exactly( + { + 'value' => 'adventure', + 'count' => 2 + }, + { + 'value' => 'romance', + 'count' => 2 + } + ) + end + end + + context 'with facet_query argument' do + let(:results) { index.facet_search 'genre', 'fan' } + + it 'returns only matching genres' do + expect(results).to include( + 'facetHits' => a_collection_containing_exactly( + 'value' => 'fantasy', + 'count' => 3 + ) + ) + end + + it 'filters correctly' do + results = index.facet_search 'genre', 'fantasy', filter: 'year < 2006' + + expect(results['facetHits']).to contain_exactly( + 'value' => 'fantasy', + 'count' => 2 + ) + end + end + + context 'with q parameter' do + it 'applies matching_strategy "all"' do + results = index.facet_search 'author', 'J. K. Rowling', q: 'Potter Stories', matching_strategy: 'all' + + expect(results['facetHits']).to be_empty + end + + it 'applies matching_strategy "last"' do + results = index.facet_search 'author', 'J. K. Rowling', q: 'Potter Stories', matching_strategy: 'last' + + expect(results).to include( + 'facetHits' => a_collection_containing_exactly( + 'value' => 'J. K. Rowling', + 'count' => 2 + ) + ) + end + + it 'applies filter parameter' do + results = index.facet_search 'author', 'J. K. Rowling', q: 'Potter', filter: 'year < 2007' + + expect(results).to include( + 'facetHits' => a_collection_containing_exactly( + 'value' => 'J. K. Rowling', + 'count' => 1 + ) + ) + end + + it 'applies attributes_to_search_on parameter' do + results = index.facet_search 'author', 'J. K. Rowling', q: 'Potter', attributes_to_search_on: ['year'] + + expect(results['facetHits']).to be_empty + end + end +end