Skip to content

Commit 4e1f36c

Browse files
committed
Refactor interfaces to account for API additions
1 parent 255882e commit 4e1f36c

8 files changed

Lines changed: 164 additions & 81 deletions

File tree

examples/client_initialization.rb

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,26 @@
3838
host: 'localhost',
3939
port: 8108,
4040
protocol: 'http'
41-
},
42-
# Uncomment if starting a 3-node cluster, using Option 2 under Setup instructions above
43-
{
44-
host: 'localhost',
45-
port: 7108,
46-
protocol: 'http'
47-
},
48-
{
49-
host: 'localhost',
50-
port: 9108,
51-
protocol: 'http'
5241
}
42+
# Uncomment if starting a 3-node cluster, using Option 2 under Setup instructions above
43+
# {
44+
# host: 'localhost',
45+
# port: 7108,
46+
# protocol: 'http'
47+
# },
48+
# {
49+
# host: 'localhost',
50+
# port: 9108,
51+
# protocol: 'http'
52+
# }
5353
],
5454
# If this optional key is specified, requests are always sent to this node first if it is healthy
5555
# before falling back on the nodes mentioned in the `nodes` key. This is useful when running a distributed set of search clusters.
56-
'nearest_node': {
57-
'host': 'localhost',
58-
'port': '8108',
59-
'protocol': 'http'
60-
},
56+
# 'nearest_node': {
57+
# 'host': 'localhost',
58+
# 'port': '8108',
59+
# 'protocol': 'http'
60+
# },
6161
api_key: 'xyz',
6262
num_retries: 10,
6363
healthcheck_interval_seconds: 1,

examples/collections_and_documents.rb

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@
167167
# "num_employees" => 5215
168168
# }
169169

170-
# You can also upsert a document, by providing it as an option
171-
# document = @typesense.collections['companies'].documents.create(document, upsert: true)
170+
# You can also upsert a document, which will update the document if it already exists or create a new one if it doesn't exist
171+
# document = @typesense.collections['companies'].documents.upsert(document)
172172
# ap document
173173

174174
##
@@ -185,13 +185,17 @@
185185
# }
186186

187187
##
188-
# Update a document
188+
# Update a document. Unlike upsert, update will error out if the doc doesn't already exist.
189189
document = @typesense.collections['companies'].documents['124'].update(
190-
'id' => '124',
191-
'num_employees' => 550
190+
'num_employees' => 5500
192191
)
193192
ap document
194193

194+
# This should error out, since document 145 doesn't exist
195+
# document = @typesense.collections['companies'].documents['145'].update(
196+
# 'num_employees' => 5500
197+
# )
198+
195199
# {
196200
# "id" => "124",
197201
# "num_employees" => 5500
@@ -225,14 +229,17 @@
225229
'country' => 'France'
226230
}
227231
]
228-
ap @typesense.collections['companies'].documents.create_many(documents)
232+
ap @typesense.collections['companies'].documents.import(documents)
229233

230-
## If you already have documents in JSONL format, you can also use #import instead, to avoid the JSON parsing overhead:
234+
## If you already have documents in JSONL format, you can also pass it directly to #import, to avoid the JSON parsing overhead:
231235
# @typesense.collections['companies'].documents.import(documents_in_jsonl_format)
232236

233-
## You can also bulk upsert documents, by adding an upsert option to #create_many or #import
234-
# @typesense.collections['companies'].documents.create_many(documents, upsert: true)
235-
# @typesense.collections['companies'].documents.import(documents_in_jsonl_format, upsert: true)
237+
## You can bulk upsert documents, by adding an upsert mode option to #import
238+
@typesense.collections['companies'].documents.import(documents, mode: :upsert)
239+
240+
## You can bulk upsert documents, by adding an update mode option to #import
241+
# `mode: update` will throw an error if the document doesn't already exist
242+
@typesense.collections['companies'].documents.import(documents, mode: :update)
236243

237244
##
238245
# Export all documents in a collection in JSON Lines format

lib/typesense/api_call.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ module Typesense
77
class ApiCall
88
API_KEY_HEADER_NAME = 'X-TYPESENSE-API-KEY'
99

10+
attr_reader :logger
11+
1012
def initialize(configuration)
1113
@configuration = configuration
1214

@@ -31,6 +33,13 @@ def post(endpoint, body_parameters = {}, query_parameters = {})
3133
body_parameters: body_parameters
3234
end
3335

36+
def patch(endpoint, body_parameters = {}, query_parameters = {})
37+
perform_request :patch,
38+
endpoint,
39+
query_parameters: query_parameters,
40+
body_parameters: body_parameters
41+
end
42+
3443
def put(endpoint, body_parameters = {}, query_parameters = {})
3544
perform_request :put,
3645
endpoint,
@@ -76,7 +85,7 @@ def perform_request(method, endpoint, query_parameters: nil, body_parameters: ni
7685
response = Typhoeus::Request.new(uri_for(endpoint, node), request_options).run
7786
set_node_healthcheck(node, is_healthy: true) if response.code >= 1 && response.code <= 499
7887

79-
@logger.debug "Request to Node #{node[:index]} was successfully made (at the network layer). Response Code was #{response.code}."
88+
@logger.debug "Request #{method}:#{uri_for(endpoint, node)} to Node #{node[:index]} was successfully made (at the network layer). Response Code was #{response.code}."
8089

8190
parsed_response = if response.headers && (response.headers['content-type'] || '').include?('application/json')
8291
Oj.load(response.body)
@@ -97,7 +106,7 @@ def perform_request(method, endpoint, query_parameters: nil, body_parameters: ni
97106
# other languages that might not support the same construct.
98107
set_node_healthcheck(node, is_healthy: false)
99108
last_exception = e
100-
@logger.warn "Request to Node #{node[:index]} failed due to \"#{e.class}: #{e.message}\""
109+
@logger.warn "Request #{method}:#{uri_for(endpoint, node)} to Node #{node[:index]} failed due to \"#{e.class}: #{e.message}\""
101110
@logger.warn "Sleeping for #{@retry_interval_seconds}s and then retrying request..."
102111
sleep @retry_interval_seconds
103112
end

lib/typesense/document.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def delete
1717
end
1818

1919
def update(partial_document)
20-
@api_call.put(endpoint_path, partial_document)
20+
@api_call.patch(endpoint_path, partial_document)
2121
end
2222

2323
private

lib/typesense/documents.rb

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,44 @@ def initialize(collection_name, api_call)
1212
@documents = {}
1313
end
1414

15-
def create(document, options = {})
16-
@api_call.post(endpoint_path, document, options)
15+
def create(document)
16+
@api_call.post(endpoint_path, document)
17+
end
18+
19+
def upsert(document)
20+
@api_call.post(endpoint_path, document, mode: :upsert)
21+
end
22+
23+
def update(document)
24+
@api_call.post(endpoint_path, document, mode: :update)
1725
end
1826

1927
def create_many(documents, options = {})
20-
documents_in_jsonl_format = documents.map { |document| Oj.dump(document) }.join("\n")
21-
results_in_jsonl_format = import(documents_in_jsonl_format, options)
22-
results_in_jsonl_format.split("\n").map { |r| Oj.load(r) }
28+
@api_call.logger.warn('#create_many is deprecated and will be removed in a future version. Use #import instead, which now takes both an array of documents or a JSONL string of documents')
29+
import(documents, options)
2330
end
2431

25-
def import(documents_in_jsonl_format, options = {})
26-
@api_call.perform_request(
32+
# @param [Array,String] documents An array of document hashes or a JSONL string of documents.
33+
def import(documents, options = {})
34+
documents_in_jsonl_format = if documents.is_a?(Array)
35+
documents.map { |document| Oj.dump(document) }.join("\n")
36+
else
37+
documents
38+
end
39+
40+
results_in_jsonl_format = @api_call.perform_request(
2741
'post',
2842
endpoint_path('import'),
2943
query_parameters: options,
3044
body_parameters: documents_in_jsonl_format,
3145
additional_headers: { 'Content-Type' => 'text/plain' }
3246
)
47+
48+
if documents.is_a?(Array)
49+
results_in_jsonl_format.split("\n").map { |r| Oj.load(r) }
50+
else
51+
results_in_jsonl_format
52+
end
3353
end
3454

3555
def export

spec/typesense/document_spec.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,13 @@
6363
'id' => '124',
6464
'num_employees' => 5200
6565
}
66-
stub_request(:put, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents/124', typesense.configuration.nodes[0]))
67-
.with(headers: {
68-
'Content-Type' => 'application/json',
69-
'X-Typesense-Api-Key' => typesense.configuration.api_key
70-
})
66+
stub_request(:patch, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents/124', typesense.configuration.nodes[0]))
67+
.with(
68+
headers: {
69+
'Content-Type' => 'application/json',
70+
'X-Typesense-Api-Key' => typesense.configuration.api_key
71+
}
72+
)
7173
.to_return(status: 200, body: JSON.dump(partial_document), headers: { 'Content-Type': 'application/json' })
7274

7375
result = document_124.update(partial_document)

spec/typesense/documents_spec.rb

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -43,39 +43,56 @@
4343
end
4444

4545
describe '#create' do
46-
context 'when no options are specified' do
47-
it 'creates creates/indexes a document and returns it' do
48-
stub_request(:post, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents', typesense.configuration.nodes[0]))
49-
.with(body: document,
50-
headers: {
51-
'X-Typesense-Api-Key' => typesense.configuration.api_key,
52-
'Content-Type' => 'application/json'
53-
})
54-
.to_return(status: 200, body: JSON.dump(document), headers: { 'Content-Type': 'application/json' })
46+
it 'creates creates/indexes a document and returns it' do
47+
stub_request(:post, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents', typesense.configuration.nodes[0]))
48+
.with(body: document,
49+
headers: {
50+
'X-Typesense-Api-Key' => typesense.configuration.api_key,
51+
'Content-Type' => 'application/json'
52+
})
53+
.to_return(status: 200, body: JSON.dump(document), headers: { 'Content-Type': 'application/json' })
5554

56-
result = companies_documents.create(document)
55+
result = companies_documents.create(document)
5756

58-
expect(result).to eq(document)
59-
end
57+
expect(result).to eq(document)
6058
end
59+
end
6160

62-
context 'when an option is specified' do
63-
it 'creates creates/upserts a document and returns it' do
64-
stub_request(:post, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents', typesense.configuration.nodes[0]))
65-
.with(body: document,
66-
headers: {
67-
'X-Typesense-Api-Key' => typesense.configuration.api_key,
68-
'Content-Type' => 'application/json'
69-
},
70-
query: {
71-
'upsert' => 'true'
72-
})
73-
.to_return(status: 200, body: JSON.dump(document), headers: { 'Content-Type': 'application/json' })
61+
describe '#update' do
62+
it 'updates the document and returns it' do
63+
stub_request(:post, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents', typesense.configuration.nodes[0]))
64+
.with(body: document,
65+
headers: {
66+
'X-Typesense-Api-Key' => typesense.configuration.api_key,
67+
'Content-Type' => 'application/json'
68+
},
69+
query: {
70+
'mode' => 'update'
71+
})
72+
.to_return(status: 200, body: JSON.dump(document), headers: { 'Content-Type': 'application/json' })
7473

75-
result = companies_documents.create(document, upsert: true)
74+
result = companies_documents.update(document)
7675

77-
expect(result).to eq(document)
78-
end
76+
expect(result).to eq(document)
77+
end
78+
end
79+
80+
describe '#upserts' do
81+
it 'upserts the document and returns it' do
82+
stub_request(:post, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents', typesense.configuration.nodes[0]))
83+
.with(body: document,
84+
headers: {
85+
'X-Typesense-Api-Key' => typesense.configuration.api_key,
86+
'Content-Type' => 'application/json'
87+
},
88+
query: {
89+
'mode' => 'upsert'
90+
})
91+
.to_return(status: 200, body: JSON.dump(document), headers: { 'Content-Type': 'application/json' })
92+
93+
result = companies_documents.upsert(document)
94+
95+
expect(result).to eq(document)
7996
end
8097
end
8198

@@ -115,36 +132,49 @@
115132
end
116133

117134
describe '#import' do
118-
context 'when no options are specified' do
119-
it 'imports documents in JSONL format' do
135+
context 'when an option is specified' do
136+
it 'passes the option to the API' do
120137
stub_request(:post, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents/import', typesense.configuration.nodes[0]))
121138
.with(body: "#{JSON.dump(document)}\n#{JSON.dump(document)}",
122139
headers: {
123140
'X-Typesense-Api-Key' => typesense.configuration.api_key
141+
},
142+
query: {
143+
'mode' => 'upsert'
124144
})
125-
.to_return(status: 200, body: '{}', headers: { 'Content-Type': 'application/json' })
145+
.to_return(status: 200, body: '{}', headers: { 'Content-Type': 'text/plain' })
126146

127-
result = companies_documents.import("#{JSON.dump(document)}\n#{JSON.dump(document)}")
147+
result = companies_documents.import("#{JSON.dump(document)}\n#{JSON.dump(document)}", mode: :upsert)
128148

129-
expect(result).to eq({})
149+
expect(result).to eq('{}')
130150
end
131151
end
152+
context 'when an array of docs is passed' do
153+
it 'converts it to JSONL and returns an array of results' do
154+
stub_request(:post, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents/import', typesense.configuration.nodes[0]))
155+
.with(body: "#{JSON.dump(document)}\n#{JSON.dump(document)}",
156+
headers: {
157+
'X-Typesense-Api-Key' => typesense.configuration.api_key
158+
})
159+
.to_return(status: 200, body: "{}\n{}", headers: { 'Content-Type': 'text/plain' })
132160

133-
context 'when an option is specified' do
134-
it 'imports documents in JSONL format, with the option' do
161+
result = companies_documents.import([document, document])
162+
163+
expect(result).to eq([{}, {}])
164+
end
165+
end
166+
context 'when a JSONL string is passed' do
167+
it 'it sends the string as is and returns a string' do
135168
stub_request(:post, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/documents/import', typesense.configuration.nodes[0]))
136169
.with(body: "#{JSON.dump(document)}\n#{JSON.dump(document)}",
137170
headers: {
138171
'X-Typesense-Api-Key' => typesense.configuration.api_key
139-
},
140-
query: {
141-
'upsert' => 'true'
142172
})
143-
.to_return(status: 200, body: '{}', headers: { 'Content-Type': 'application/json' })
173+
.to_return(status: 200, body: "{}\n{}", headers: { 'Content-Type': 'text/plain' })
144174

145-
result = companies_documents.import("#{JSON.dump(document)}\n#{JSON.dump(document)}", upsert: true)
175+
result = companies_documents.import("#{JSON.dump(document)}\n#{JSON.dump(document)}")
146176

147-
expect(result).to eq({})
177+
expect(result).to eq("{}\n{}")
148178
end
149179
end
150180
end

spec/typesense/overrides_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@
3636
end
3737
end
3838

39+
describe '#retrieve' do
40+
it 'creates an override rule and returns it' do
41+
stub_request(:get, Typesense::ApiCall.new(typesense.configuration).send(:uri_for, '/collections/companies/overrides', typesense.configuration.nodes[0]))
42+
.with(headers: {
43+
'X-Typesense-Api-Key' => typesense.configuration.api_key,
44+
'Content-Type' => 'application/json'
45+
})
46+
.to_return(status: 201, body: JSON.dump([override]), headers: { 'Content-Type': 'application/json' })
47+
48+
result = companies_overrides.retrieve
49+
50+
expect(result).to eq([override])
51+
end
52+
end
53+
3954
describe '#[]' do
4055
it 'creates an override object and returns it' do
4156
result = companies_overrides['lex-override']

0 commit comments

Comments
 (0)