Skip to content

Commit 081b8ef

Browse files
meili-bors[bot]thicolarescurquiza
authored
Merge #257
257: Add methods to add JSON, CSV and NDJSON document r=curquiza a=thicolares ### Addresses the following items of #243: - [x] addDocumentsJson(string docs, string primaryKey) - [x] addDocumentsCsv(string docs, string primaryKey) - [x] addDocumentsNdjson(string docs, string primaryKey) ### Changes _Tip: in order to ease the code review, you can start reviewing it commit by commit._ - Expose request options as a parameter of `http_post`. Motivations: - Allow who knows about the content to use `transform_body?` in order to decide if `http_request` should or not transform the body to JSON; - Group `transform_body?`, `headers` and [other options' default values that were spread around](https://github.com/meilisearch/meilisearch-ruby/blob/1ddbc9253f15cc4879bd6e7392c145ae9af9bde9/lib/meilisearch/http_request.rb#L82-L83) into a single Hash; - It can be improved in the next iterations, but it will already ease the next tasks within #243. - Create methods to add documents as JSON, NDJSON and CSV - Add related tests exposing the usage of primaryKeys - No _"add in batches"_ included in this PR. Co-authored-by: Thiago Colares <[email protected]> Co-authored-by: Clémentine Urquizar - curqui <[email protected]>
2 parents c6eb08f + c2ab838 commit 081b8ef

File tree

5 files changed

+134
-23
lines changed

5 files changed

+134
-23
lines changed

.rubocop_todo.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2021-11-19 14:59:32 UTC using RuboCop version 1.23.0.
3+
# on 2021-11-21 21:31:55 UTC using RuboCop version 1.23.0.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new
@@ -14,6 +14,12 @@ Gemspec/RequireMFA:
1414
Exclude:
1515
- 'meilisearch.gemspec'
1616

17+
# Offense count: 1
18+
# Cop supports --auto-correct.
19+
Layout/HeredocIndentation:
20+
Exclude:
21+
- 'spec/meilisearch/index/documents_spec.rb'
22+
1723
# Offense count: 32
1824
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
1925
# IgnoredMethods: refine
@@ -23,7 +29,12 @@ Metrics/BlockLength:
2329
# Offense count: 1
2430
# Configuration parameters: CountComments, CountAsOne.
2531
Metrics/ClassLength:
26-
Max: 271
32+
Max: 289
33+
34+
# Offense count: 1
35+
# Configuration parameters: Max, CountKeywordArgs.
36+
Metrics/ParameterLists:
37+
MaxOptionalParameters: 4
2738

2839
# Offense count: 2
2940
Naming/AccessorMethodName:

lib/meilisearch/http_request.rb

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,30 @@ class HTTPRequest
1212
def initialize(url, api_key = nil, options = {})
1313
@base_url = url
1414
@api_key = api_key
15-
@options = options
16-
@headers = {
17-
'Content-Type' => 'application/json',
18-
'X-Meili-API-Key' => api_key
19-
}.compact
20-
@headers_no_body = {
21-
'X-Meili-API-Key' => api_key
22-
}.compact
15+
@options = merge_options({
16+
timeout: 1,
17+
max_retries: 0,
18+
headers: build_default_options_headers(api_key),
19+
convert_body?: true
20+
}, options)
2321
end
2422

2523
def http_get(relative_path = '', query_params = {})
2624
send_request(
2725
proc { |path, config| self.class.get(path, config) },
2826
relative_path,
2927
query_params: query_params,
30-
headers: @headers_no_body
28+
options: remove_options_header(@options, 'Content-Type')
3129
)
3230
end
3331

34-
def http_post(relative_path = '', body = nil, query_params = nil)
32+
def http_post(relative_path = '', body = nil, query_params = nil, options = {})
3533
send_request(
3634
proc { |path, config| self.class.post(path, config) },
3735
relative_path,
3836
query_params: query_params,
3937
body: body,
40-
headers: @headers
38+
options: merge_options(@options, options)
4139
)
4240
end
4341

@@ -47,22 +45,48 @@ def http_put(relative_path = '', body = nil, query_params = nil)
4745
relative_path,
4846
query_params: query_params,
4947
body: body,
50-
headers: @headers
48+
options: @options
5149
)
5250
end
5351

5452
def http_delete(relative_path = '')
5553
send_request(
5654
proc { |path, config| self.class.delete(path, config) },
5755
relative_path,
58-
headers: @headers_no_body
56+
options: remove_options_header(@options, 'Content-Type')
5957
)
6058
end
6159

6260
private
6361

64-
def send_request(http_method, relative_path, query_params: nil, body: nil, headers: nil)
65-
config = http_config(query_params, body, headers)
62+
def build_default_options_headers(api_key = nil)
63+
{
64+
'Content-Type' => 'application/json',
65+
'X-Meili-API-Key' => api_key
66+
}.compact
67+
end
68+
69+
def merge_options(default_options, added_options = {})
70+
default_cloned_headers = default_options[:headers].clone
71+
merged_options = default_options.merge(added_options)
72+
merged_options[:headers] = default_cloned_headers.merge(added_options[:headers]) if added_options.key?(:headers)
73+
merged_options
74+
end
75+
76+
def remove_options_header(options, key)
77+
cloned_options = clone_options(options)
78+
cloned_options[:headers].tap { |headers| headers.delete(key) }
79+
cloned_options
80+
end
81+
82+
def clone_options(options)
83+
cloned_options = options.clone
84+
cloned_options[:headers] = options[:headers].clone
85+
cloned_options
86+
end
87+
88+
def send_request(http_method, relative_path, query_params: nil, body: nil, options: {})
89+
config = http_config(query_params, body, options)
6690
begin
6791
response = http_method.call(@base_url + relative_path, config)
6892
rescue Errno::ECONNREFUSED => e
@@ -71,13 +95,14 @@ def send_request(http_method, relative_path, query_params: nil, body: nil, heade
7195
validate(response)
7296
end
7397

74-
def http_config(query_params, body, headers)
98+
def http_config(query_params, body, options)
99+
body = body.to_json if options[:convert_body?] == true
75100
{
76-
headers: headers,
101+
headers: options[:headers],
77102
query: query_params,
78-
body: body.to_json,
79-
timeout: @options[:timeout] || 1,
80-
max_retries: @options[:max_retries] || 0
103+
body: body,
104+
timeout: options[:timeout],
105+
max_retries: options[:max_retries]
81106
}.compact
82107
end
83108

lib/meilisearch/index.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,27 @@ def add_documents!(documents, primary_key = nil)
8484
alias replace_documents! add_documents!
8585
alias add_or_replace_documents! add_documents!
8686

87+
def add_documents_json(documents, primary_key = nil)
88+
options = { headers: { 'Content-Type' => 'application/json' }, convert_body?: false }
89+
http_post "/indexes/#{@uid}/documents", documents, { primaryKey: primary_key }.compact, options
90+
end
91+
alias replace_documents_json add_documents_json
92+
alias add_or_replace_documents_json add_documents_json
93+
94+
def add_documents_ndjson(documents, primary_key = nil)
95+
options = { headers: { 'Content-Type' => 'application/x-ndjson' }, convert_body?: false }
96+
http_post "/indexes/#{@uid}/documents", documents, { primaryKey: primary_key }.compact, options
97+
end
98+
alias replace_documents_ndjson add_documents_ndjson
99+
alias add_or_replace_documents_ndjson add_documents_ndjson
100+
101+
def add_documents_csv(documents, primary_key = nil)
102+
options = { headers: { 'Content-Type' => 'text/csv' }, convert_body?: false }
103+
http_post "/indexes/#{@uid}/documents", documents, { primaryKey: primary_key }.compact, options
104+
end
105+
alias replace_documents_csv add_documents_csv
106+
alias add_or_replace_documents_csv add_documents_csv
107+
87108
def update_documents(documents, primary_key = nil)
88109
documents = [documents] if documents.is_a?(Hash)
89110
http_put "/indexes/#{@uid}/documents", documents, { primaryKey: primary_key }.compact

spec/meilisearch/index/base_spec.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,15 @@
8787
options = { timeout: 2, max_retries: 1 }
8888
new_client = MeiliSearch::Client.new(URL, MASTER_KEY, options)
8989
index = new_client.create_index('options')
90-
expect(index.options).to eq({ timeout: 2, max_retries: 1 })
90+
expect(index.options).to eq({
91+
headers: {
92+
'Content-Type' => 'application/json',
93+
'X-Meili-API-Key' => MASTER_KEY
94+
},
95+
max_retries: 1,
96+
timeout: 2,
97+
convert_body?: true
98+
})
9199
expect(MeiliSearch::Index).to receive(:get).with(
92100
"#{URL}/indexes/options",
93101
{

spec/meilisearch/index/documents_spec.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,52 @@
3636
expect(index.documents.first.keys).to eq(docs.first.keys.map(&:to_s))
3737
end
3838

39+
it 'adds JSON documents (as a array of documents)' do
40+
documents = <<JSON
41+
[
42+
{ "objectRef": 123, "title": "Pride and Prejudice", "comment": "A great book" },
43+
{ "objectRef": 456, "title": "Le Petit Prince", "comment": "A french book" },
44+
{ "objectRef": 1, "title": "Alice In Wonderland", "comment": "A weird book" },
45+
{ "objectRef": 1344, "title": "The Hobbit", "comment": "An awesome book" },
46+
{ "objectRef": 4, "title": "Harry Potter and the Half-Blood Prince", "comment": "The best book" }
47+
]
48+
JSON
49+
response = index.add_documents_json(documents, 'objectRef')
50+
51+
expect(response).to be_a(Hash)
52+
expect(response).to have_key('updateId')
53+
index.wait_for_pending_update(response['updateId'])
54+
expect(index.documents.count).to eq(5)
55+
end
56+
57+
it 'adds NDJSON documents (as a array of documents)' do
58+
documents = <<NDJSON
59+
{ "objectRef": 123, "title": "Pride and Prejudice", "comment": "A great book" }
60+
{ "objectRef": 456, "title": "Le Petit Prince", "comment": "A french book" }
61+
{ "objectRef": 1, "title": "Alice In Wonderland", "comment": "A weird book" }
62+
{ "objectRef": 4, "title": "Harry Potter and the Half-Blood Prince", "comment": "The best book" }
63+
NDJSON
64+
response = index.add_documents_ndjson(documents, 'objectRef')
65+
expect(response).to be_a(Hash)
66+
expect(response).to have_key('updateId')
67+
index.wait_for_pending_update(response['updateId'])
68+
expect(index.documents.count).to eq(4)
69+
end
70+
71+
it 'adds CSV documents (as a array of documents)' do
72+
documents = <<CSV
73+
"objectRef:number","title:string","comment:string"
74+
"1239","Pride and Prejudice","A great book"
75+
"4569","Le Petit Prince","A french book"
76+
"49","Harry Potter and the Half-Blood Prince","The best book"
77+
CSV
78+
response = index.add_documents_csv(documents, 'objectRef')
79+
expect(response).to be_a(Hash)
80+
expect(response).to have_key('updateId')
81+
index.wait_for_pending_update(response['updateId'])
82+
expect(index.documents.count).to eq(3)
83+
end
84+
3985
it 'adds documents in a batch (as a array of documents)' do
4086
response = index.add_documents_in_batches(documents, 5)
4187
expect(response).to be_a(Array)

0 commit comments

Comments
 (0)