diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 25add8e0..5aa5fa19 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -69,10 +69,6 @@ update_faceting_settings_1: |- index('books').update_faceting({ max_values_per_facet: 2 }) reset_faceting_settings_1: |- index('books').reset_faceting -settings_guide_faceting_1: |- - index('books').update_faceting({ max_values_per_facet: 5 }) -settings_guide_pagination_1: |- - index('books').update_pagination({ max_total_hits: 50 }) get_one_index_1: |- client.fetch_index('movies') list_all_indexes_1: |- @@ -281,6 +277,10 @@ filtering_guide_3: |- client.index('movies').search('Planet of the Apes', { filter: 'rating >= 3 AND (NOT director = "Tim Burton")' }) +filtering_guide_nested_1: |- + client.index('movies_ratings').search('thriller', { + filter: 'rating.users >= 90' + }) search_parameter_guide_query_1: |- client.index('movies').search('shifu') search_parameter_guide_offset_1: |- @@ -327,60 +327,6 @@ search_parameter_guide_matching_strategy_2: |- client.index('movies').search('big fat liar', { matching_strategy: 'all' }) -settings_guide_synonyms_1: |- - client.index('tops').update_settings({ - synonyms: { - sweater: ['jumper'], - jumper: ['sweater'] - } - }) -settings_guide_stop_words_1: |- - client.index('movies').update_settings({ - stop_words: [ - 'the', - 'a', - 'an' - ] - }) -settings_guide_ranking_rules_1: |- - client.index('movies').update_settings({ - ranking_rules: [ - 'words', - 'typo', - 'proximity', - 'attribute', - 'sort', - 'exactness', - 'release_date:asc', - 'rank:desc' - ] - }) -settings_guide_distinct_1: |- - client.index('jackets').update_distinct_attribute('product_id') -settings_guide_searchable_1: |- - client.index('movies').update_settings({ - searchable_attributes: [ - 'title', - 'overview', - 'genres' - ] - }) -settings_guide_displayed_1: |- - client.index('movies').update_settings({ - displayed_attributes: [ - 'title', - 'overview', - 'genres', - 'release_date' - ] - }) -settings_guide_sortable_1: |- - client.index('books').update_settings({ - sortable_attributes: [ - 'price', - 'author' - ] - }) add_movies_json_1: |- require 'json' @@ -389,8 +335,8 @@ add_movies_json_1: |- client.index('movies').add_documents(movies) documents_guide_add_movie_1: |- client.index('movies').add_documents([{ - "movie_id": "123sq178", - "title": "Amelie Poulain" + movie_id: '123sq178', + title: 'Amelie Poulain' }]) getting_started_add_documents_md: |- ```bash @@ -416,11 +362,33 @@ getting_started_search_md: |- ``` [About this SDK](https://www.github.com/meilisearch/meilisearch-ruby) -faceted_search_update_settings_1: |- +filtering_update_settings_1: |- client.index('movies').update_filterable_attributes([ 'director', 'genres' ]) +faceted_search_1: |- + client.index('books').search('classic', { + facets: ['genres', 'rating', 'language'] + }) +faceted_search_2: |- + client.multi_search([ + { + indexUid: 'books', + facets: ['language', 'genres', 'author', 'format'], + filter: [['language = English', 'language = French'], ['genres = Fiction']] + }, + { + indexUid: 'books', + facets: ['language'], + filter: [['genres = Fiction']] + }, + { + indexUid: 'books', + facets: ['genres'], + filter: [['language = English', 'language = French']] + } + ]) faceted_search_filter_1: |- client.index('movies').search('thriller', { filter: [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"'] @@ -431,6 +399,8 @@ faceted_search_walkthrough_filter_1: |- client.index('movies').search('thriller', { filter: [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"'] }) +faceted_search_update_settings_1: |- + client.index('books').update_filterable_attributes(['genres', 'rating', 'language']) post_dump_1: |- client.create_dump phrase_search_1: |- @@ -457,6 +427,8 @@ update_sortable_attributes_1: |- 'price', 'author' ]) +sorting_guide_sort_nested_1: |- + client.index('books').search('science fiction', { sort: ['rating.users:asc'] }) reset_sortable_attributes_1: |- client.index('books').reset_sortable_attributes search_parameter_guide_sort_1: |- @@ -467,6 +439,8 @@ geosearch_guide_filter_usage_1: |- client.index('restaurants').search('', { filter: '_geoRadius(45.472735, 9.184019, 2000)' }) geosearch_guide_filter_usage_2: |- client.index('restaurants').search('', { filter: '_geoRadius(45.472735, 9.184019, 2000) AND type = pizza' }) +geosearch_guide_filter_usage_3: |- + client.index('restaurants').search('', { filter: ['_geoBoundingBox([45.494181, 9.179175], [45.449484, 9.214024])'] }) geosearch_guide_sort_settings_1: |- client.index('restaurants').update_sortable_attributes(['_geo']) geosearch_guide_sort_usage_1: |- @@ -622,10 +596,10 @@ typo_tolerance_guide_4: |- two_typos: 10 } }) -settings_guide_typo_tolerance_1: |- - index('books').update_typo_tolerance({ - min_word_size_for_typos: { - two_typos: 12 - }, - disable_on_attributes: ['title'] - }) +multi_search_1: |- + client.multi_search([ + { index_uid: 'books', q: 'prince' }, + { index_uid: 'movies', q: 'pooh', limit: 5 } + { index_uid: 'movies', q: 'nemo', limit: 5 } + { index_uid: 'movie_ratings', q: 'us' } + ]) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c7748c6c..01a5cdf0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-07-27 15:08:15 UTC using RuboCop version 1.32.0. +# on 2023-03-30 12:31:09 UTC using RuboCop version 1.48.1. # 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 @@ -8,22 +8,22 @@ # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Include. +# Configuration parameters: Severity, Include. # Include: **/*.gemspec Gemspec/RequireMFA: Exclude: - 'meilisearch.gemspec' -# Offense count: 46 -# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. -# IgnoredMethods: refine +# Offense count: 45 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +# AllowedMethods: refine Metrics/BlockLength: - Max: 626 + Max: 624 # Offense count: 2 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 317 + Max: 318 # Offense count: 1 # Configuration parameters: Max, CountKeywordArgs. diff --git a/lib/meilisearch.rb b/lib/meilisearch.rb index b75baff4..2ba15581 100644 --- a/lib/meilisearch.rb +++ b/lib/meilisearch.rb @@ -3,6 +3,7 @@ require 'meilisearch/version' require 'meilisearch/utils' require 'meilisearch/http_request' +require 'meilisearch/multi_search' require 'meilisearch/tenant_token' require 'meilisearch/task' require 'meilisearch/client' diff --git a/lib/meilisearch/client.rb b/lib/meilisearch/client.rb index 951d13f2..c2d3ffa8 100644 --- a/lib/meilisearch/client.rb +++ b/lib/meilisearch/client.rb @@ -3,6 +3,7 @@ module MeiliSearch class Client < HTTPRequest include MeiliSearch::TenantToken + include MeiliSearch::MultiSearch ### INDEXES diff --git a/lib/meilisearch/index.rb b/lib/meilisearch/index.rb index 45f1364e..3d462267 100644 --- a/lib/meilisearch/index.rb +++ b/lib/meilisearch/index.rb @@ -96,9 +96,13 @@ def add_documents_ndjson(documents, primary_key = nil) alias replace_documents_ndjson add_documents_ndjson alias add_or_replace_documents_ndjson add_documents_ndjson - def add_documents_csv(documents, primary_key = nil) + def add_documents_csv(documents, primary_key = nil, delimiter = nil) options = { headers: { 'Content-Type' => 'text/csv' }, convert_body?: false } - http_post "/indexes/#{@uid}/documents", documents, { primaryKey: primary_key }.compact, options + + http_post "/indexes/#{@uid}/documents", documents, { + primaryKey: primary_key, + csvDelimiter: delimiter + }.compact, options end alias replace_documents_csv add_documents_csv alias add_or_replace_documents_csv add_documents_csv diff --git a/lib/meilisearch/multi_search.rb b/lib/meilisearch/multi_search.rb new file mode 100644 index 00000000..2cb3cba1 --- /dev/null +++ b/lib/meilisearch/multi_search.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module MeiliSearch + module MultiSearch + def multi_search(data) + body = Utils.transform_attributes(data) + + http_post '/multi-search', queries: body + end + end +end diff --git a/spec/meilisearch/client/multi_search_spec.rb b/spec/meilisearch/client/multi_search_spec.rb new file mode 100644 index 00000000..e8d52a53 --- /dev/null +++ b/spec/meilisearch/client/multi_search_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +RSpec.describe 'MeiliSearch::Client - Multiple Index Search' do + before do + client.create_index('books') + task = client.create_index('movies') + client.wait_for_task(task['taskUid']) + end + + it 'does a custom search with two different indexes' do + response = client.multi_search([ + { index_uid: 'books', q: 'prince' }, + { index_uid: 'movies', q: 'prince' } + ]) + + expect(response['results'].count).to eq(2) + expect(response['results'][0]['estimatedTotalHits']).to eq(0) + expect(response['results'][1]['estimatedTotalHits']).to eq(0) + end +end diff --git a/spec/meilisearch/index/documents_spec.rb b/spec/meilisearch/index/documents_spec.rb index 9d629ed4..6dbe3d53 100644 --- a/spec/meilisearch/index/documents_spec.rb +++ b/spec/meilisearch/index/documents_spec.rb @@ -78,6 +78,23 @@ expect(index.documents['results'].count).to eq(3) end + it 'adds CSV documents (as an array of documents with a different separator)' do + documents = <<~CSV + "objectRef:number"|"title:string"|"comment:string" + "1239"|"Pride and Prejudice"|"A great book" + "4569"|"Le Petit Prince"|"A french book" + "49"|"Harry Potter and the Half-Blood Prince"|"The best book" + CSV + + response = index.add_documents_csv(documents, 'objectRef', '|') + index.wait_for_task(response['taskUid']) + + expect(index.documents['results'].count).to eq(3) + expect(index.documents['results'][1]['objectRef']).to eq(4569) + expect(index.documents['results'][1]['title']).to eq('Le Petit Prince') + expect(index.documents['results'][1]['comment']).to eq('A french book') + end + it 'adds documents in a batch (as a array of documents)' do task = index.add_documents_in_batches(documents, 5) expect(task).to be_a(Array) diff --git a/spec/meilisearch/index/search/facets_distribution_spec.rb b/spec/meilisearch/index/search/facets_distribution_spec.rb index d57263a6..dbe21fb8 100644 --- a/spec/meilisearch/index/search/facets_distribution_spec.rb +++ b/spec/meilisearch/index/search/facets_distribution_spec.rb @@ -12,7 +12,8 @@ response = index.search('prinec', facets: ['genre', 'author']) expect(response.keys).to contain_exactly( *DEFAULT_SEARCH_RESPONSE_KEYS, - 'facetDistribution' + 'facetDistribution', + 'facetStats' ) expect(response['estimatedTotalHits']).to eq(2) expect(response['facetDistribution'].keys).to contain_exactly('genre', 'author') @@ -27,7 +28,8 @@ response = index.search('', facets: ['genre', 'author']) expect(response.keys).to contain_exactly( *DEFAULT_SEARCH_RESPONSE_KEYS, - 'facetDistribution' + 'facetDistribution', + 'facetStats' ) expect(response['estimatedTotalHits']).to eq(documents.count) expect(response['facetDistribution'].keys).to contain_exactly('genre', 'author') @@ -42,7 +44,8 @@ response = index.search('', facets: ['year']) expect(response.keys).to contain_exactly( *DEFAULT_SEARCH_RESPONSE_KEYS, - 'facetDistribution' + 'facetDistribution', + 'facetStats' ) expect(response['estimatedTotalHits']).to eq(documents.count) expect(response['facetDistribution'].keys).to contain_exactly('year') diff --git a/spec/meilisearch/index/search/multi_params_spec.rb b/spec/meilisearch/index/search/multi_params_spec.rb index ed252460..585d7062 100644 --- a/spec/meilisearch/index/search/multi_params_spec.rb +++ b/spec/meilisearch/index/search/multi_params_spec.rb @@ -66,9 +66,11 @@ response = index.update_filterable_attributes(['genre']) index.wait_for_task(response['taskUid']) response = index.search('prinec', facets: ['genre'], limit: 1) + expect(response.keys).to contain_exactly( *DEFAULT_SEARCH_RESPONSE_KEYS, - 'facetDistribution' + 'facetDistribution', + 'facetStats' ) expect(response['estimatedTotalHits']).to eq(2) expect(response['hits'].count).to eq(1) diff --git a/spec/meilisearch/utils_spec.rb b/spec/meilisearch/utils_spec.rb index 45fd9c39..81f55c17 100644 --- a/spec/meilisearch/utils_spec.rb +++ b/spec/meilisearch/utils_spec.rb @@ -20,4 +20,30 @@ expect(data).to eq({ 'date' => '2012-12-21T19:05:00+00:00' }) end end + + describe '.transform_attributes' do + it 'transforms snake_case into camelCased keys' do + data = described_class.transform_attributes({ + index_name: 'books', + my_UID: '123' + }) + + expect(data).to eq({ 'indexName' => 'books', 'myUid' => '123' }) + end + + it 'transforms snake_case into camel cased keys from array' do + data = described_class + .transform_attributes([ + { index_uid: 'books', q: 'prince' }, + { index_uid: 'movies', q: 'prince' } + ]) + + expect(data).to eq( + [ + { 'indexUid' => 'books', 'q' => 'prince' }, + { 'indexUid' => 'movies', 'q' => 'prince' } + ] + ) + end + end end