algoliasearch 1.2.8 → 1.2.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/ChangeLog +6 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +55 -50
- data/README.md +40 -6
- data/algoliasearch.gemspec +2 -2
- data/lib/algolia/client.rb +47 -6
- data/lib/algolia/index.rb +141 -2
- data/lib/algolia/protocol.rb +4 -0
- data/lib/algolia/version.rb +1 -1
- data/spec/client_spec.rb +88 -17
- metadata +16 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c2d37bc8e01bf59c2a23fd08ec86229e6b37f95
|
4
|
+
data.tar.gz: f2890ed7e2c1103ca6004ac91792c4c0ac3d62ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a83669bc6eedf16e80419534844ad6a654a9e8fb675800e5f68393689c205a77187a2c00fd1c82755d9ef68d4147be666a6c5a0fa97bafea9c0fbddc7b0de161
|
7
|
+
data.tar.gz: dee9c83ce0c9246f667663f1e683efda2f3e5c81476c61f3e75760438ccd6af3ab3fd77814a8149a5f1f9034f8fc59f467d9e3eefe0a7ebb015d11f62888a74d
|
data/.travis.yml
CHANGED
data/ChangeLog
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
CHANGELOG
|
2
2
|
|
3
|
+
2014-07-08 1.2.9
|
4
|
+
|
5
|
+
* Add new 'delete_by_query' method to delete all objects matching a specific query
|
6
|
+
* Add new 'get_objects' method to retrieve a list of objects from a single API call
|
7
|
+
* Add a helper to perform disjunctive faceting
|
8
|
+
|
3
9
|
2014-03-27 1.2.8
|
4
10
|
|
5
11
|
* Catch all exceptions before retrying with another host
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
ZenTest (4.
|
5
|
-
addressable (2.3.
|
4
|
+
ZenTest (4.10.0)
|
5
|
+
addressable (2.3.6)
|
6
6
|
autotest (4.4.6)
|
7
7
|
ZenTest (>= 4.4.1)
|
8
8
|
autotest-fsevent (0.2.9)
|
9
9
|
sys-uname
|
10
10
|
autotest-growl (0.2.16)
|
11
|
-
backports (3.
|
11
|
+
backports (3.6.0)
|
12
12
|
coderay (1.1.0)
|
13
13
|
coveralls (0.7.0)
|
14
14
|
multi_json (~> 1.3)
|
@@ -16,28 +16,27 @@ GEM
|
|
16
16
|
simplecov (>= 0.7)
|
17
17
|
term-ansicolor
|
18
18
|
thor
|
19
|
-
crack (0.4.
|
20
|
-
safe_yaml (~> 0.
|
19
|
+
crack (0.4.2)
|
20
|
+
safe_yaml (~> 1.0.0)
|
21
21
|
diff-lcs (1.2.5)
|
22
|
-
docile (1.1.
|
23
|
-
ethon (0.
|
22
|
+
docile (1.1.3)
|
23
|
+
ethon (0.7.0)
|
24
24
|
ffi (>= 1.3.0)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
faraday (>= 0.7.4, < 0.9)
|
25
|
+
faraday (0.9.0)
|
26
|
+
multipart-post (>= 1.2, < 3)
|
27
|
+
faraday_middleware (0.9.1)
|
28
|
+
faraday (>= 0.7.4, < 0.10)
|
30
29
|
ffi (1.9.3)
|
31
30
|
ffi (1.9.3-java)
|
32
31
|
ffi2-generators (0.1.1)
|
33
|
-
gh (0.13.
|
32
|
+
gh (0.13.2)
|
34
33
|
addressable
|
35
34
|
backports
|
36
35
|
faraday (~> 0.8)
|
37
36
|
multi_json (~> 1.0)
|
38
37
|
net-http-persistent (>= 2.7)
|
39
38
|
net-http-pipeline
|
40
|
-
highline (1.6.
|
39
|
+
highline (1.6.21)
|
41
40
|
httpclient (2.3.4.1)
|
42
41
|
json (1.8.1)
|
43
42
|
json (1.8.1-java)
|
@@ -48,35 +47,40 @@ GEM
|
|
48
47
|
spoon (~> 0.0.1)
|
49
48
|
method_source (0.8.2)
|
50
49
|
mime-types (1.25.1)
|
51
|
-
multi_json (1.
|
52
|
-
multipart-post (
|
53
|
-
net-http-persistent (2.9)
|
50
|
+
multi_json (1.10.1)
|
51
|
+
multipart-post (2.0.0)
|
52
|
+
net-http-persistent (2.9.4)
|
54
53
|
net-http-pipeline (1.0.1)
|
55
|
-
pry (0.9.12.
|
54
|
+
pry (0.9.12.6)
|
56
55
|
coderay (~> 1.0)
|
57
56
|
method_source (~> 0.8)
|
58
57
|
slop (~> 3.4)
|
59
|
-
pry (0.9.12.
|
58
|
+
pry (0.9.12.6-java)
|
60
59
|
coderay (~> 1.0)
|
61
60
|
method_source (~> 0.8)
|
62
61
|
slop (~> 3.4)
|
63
62
|
spoon (~> 0.0)
|
64
|
-
pusher-client (0.
|
65
|
-
|
66
|
-
|
67
|
-
|
63
|
+
pusher-client (0.6.0)
|
64
|
+
json
|
65
|
+
websocket (~> 1.0)
|
66
|
+
rake (10.3.2)
|
67
|
+
rdoc (4.1.1)
|
68
68
|
json (~> 1.4)
|
69
69
|
redgreen (1.2.2)
|
70
70
|
rest-client (1.6.7)
|
71
71
|
mime-types (>= 1.16)
|
72
|
-
rspec (
|
73
|
-
rspec-core (~>
|
74
|
-
rspec-expectations (~>
|
75
|
-
rspec-mocks (~>
|
76
|
-
rspec-core (
|
77
|
-
|
78
|
-
|
79
|
-
|
72
|
+
rspec (3.0.0)
|
73
|
+
rspec-core (~> 3.0.0)
|
74
|
+
rspec-expectations (~> 3.0.0)
|
75
|
+
rspec-mocks (~> 3.0.0)
|
76
|
+
rspec-core (3.0.0)
|
77
|
+
rspec-support (~> 3.0.0)
|
78
|
+
rspec-expectations (3.0.0)
|
79
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
80
|
+
rspec-support (~> 3.0.0)
|
81
|
+
rspec-mocks (3.0.0)
|
82
|
+
rspec-support (~> 3.0.0)
|
83
|
+
rspec-support (3.0.0)
|
80
84
|
rubysl (2.0.15)
|
81
85
|
rubysl-abbrev (~> 2.0)
|
82
86
|
rubysl-base64 (~> 2.0)
|
@@ -235,12 +239,12 @@ GEM
|
|
235
239
|
rubysl-observer (2.0.0)
|
236
240
|
rubysl-open-uri (2.0.0)
|
237
241
|
rubysl-open3 (2.0.0)
|
238
|
-
rubysl-openssl (2.0
|
242
|
+
rubysl-openssl (2.1.0)
|
239
243
|
rubysl-optparse (2.0.1)
|
240
244
|
rubysl-shellwords (~> 2.0)
|
241
245
|
rubysl-ostruct (2.0.4)
|
242
246
|
rubysl-pathname (2.0.0)
|
243
|
-
rubysl-prettyprint (2.0.
|
247
|
+
rubysl-prettyprint (2.0.3)
|
244
248
|
rubysl-prime (2.0.1)
|
245
249
|
rubysl-profile (2.0.0)
|
246
250
|
rubysl-profiler (2.0.1)
|
@@ -248,9 +252,9 @@ GEM
|
|
248
252
|
rubysl-pty (2.0.2)
|
249
253
|
rubysl-rational (2.0.1)
|
250
254
|
rubysl-readline (2.0.2)
|
251
|
-
rubysl-resolv (2.
|
255
|
+
rubysl-resolv (2.1.0)
|
252
256
|
rubysl-rexml (2.0.2)
|
253
|
-
rubysl-rinda (2.0.
|
257
|
+
rubysl-rinda (2.0.1)
|
254
258
|
rubysl-rss (2.0.0)
|
255
259
|
rubysl-scanf (2.0.0)
|
256
260
|
rubysl-securerandom (2.0.0)
|
@@ -268,7 +272,7 @@ GEM
|
|
268
272
|
rubysl-thwait (2.0.0)
|
269
273
|
rubysl-time (2.0.3)
|
270
274
|
rubysl-timeout (2.0.0)
|
271
|
-
rubysl-tmpdir (2.0.
|
275
|
+
rubysl-tmpdir (2.0.1)
|
272
276
|
rubysl-tsort (2.0.1)
|
273
277
|
rubysl-un (2.0.0)
|
274
278
|
rubysl-fileutils (~> 2.0)
|
@@ -279,38 +283,38 @@ GEM
|
|
279
283
|
rubysl-xmlrpc (2.0.0)
|
280
284
|
rubysl-yaml (2.0.4)
|
281
285
|
rubysl-zlib (2.0.1)
|
282
|
-
safe_yaml (0.
|
286
|
+
safe_yaml (1.0.3)
|
283
287
|
simplecov (0.8.2)
|
284
288
|
docile (~> 1.1.0)
|
285
289
|
multi_json
|
286
290
|
simplecov-html (~> 0.8.0)
|
287
291
|
simplecov-html (0.8.0)
|
288
|
-
slop (3.
|
292
|
+
slop (3.5.0)
|
289
293
|
spoon (0.0.4)
|
290
294
|
ffi
|
291
295
|
sys-uname (0.9.2)
|
292
296
|
ffi (>= 1.0.0)
|
293
|
-
term-ansicolor (1.
|
294
|
-
tins (~> 0
|
295
|
-
thor (0.
|
296
|
-
tins (
|
297
|
-
travis (1.6.
|
297
|
+
term-ansicolor (1.3.0)
|
298
|
+
tins (~> 1.0)
|
299
|
+
thor (0.19.1)
|
300
|
+
tins (1.3.0)
|
301
|
+
travis (1.6.11)
|
298
302
|
addressable (~> 2.3)
|
299
303
|
backports
|
300
|
-
faraday (~> 0.
|
304
|
+
faraday (~> 0.9)
|
301
305
|
faraday_middleware (~> 0.9)
|
302
306
|
gh (~> 0.13)
|
303
307
|
highline (~> 1.6)
|
304
308
|
launchy (~> 2.1)
|
305
309
|
pry (~> 0.9)
|
306
310
|
pusher-client (~> 0.4)
|
307
|
-
typhoeus (~> 0.6)
|
308
|
-
typhoeus (0.6.
|
309
|
-
ethon (
|
310
|
-
webmock (1.
|
311
|
-
addressable (>= 2.
|
311
|
+
typhoeus (~> 0.6, >= 0.6.8)
|
312
|
+
typhoeus (0.6.8)
|
313
|
+
ethon (>= 0.7.0)
|
314
|
+
webmock (1.18.0)
|
315
|
+
addressable (>= 2.3.6)
|
312
316
|
crack (>= 0.3.2)
|
313
|
-
websocket (1.
|
317
|
+
websocket (1.1.4)
|
314
318
|
|
315
319
|
PLATFORMS
|
316
320
|
java
|
@@ -323,6 +327,7 @@ DEPENDENCIES
|
|
323
327
|
coveralls
|
324
328
|
httpclient (~> 2.3)
|
325
329
|
json (>= 1.5.1)
|
330
|
+
mime-types (< 2.0)
|
326
331
|
rake
|
327
332
|
rdoc
|
328
333
|
redgreen
|
data/README.md
CHANGED
@@ -212,9 +212,17 @@ You can use the following optional arguments:
|
|
212
212
|
* **prefixAll**: all query words are interpreted as prefixes,
|
213
213
|
* **prefixLast**: only the last word is interpreted as a prefix (default behavior),
|
214
214
|
* **prefixNone**: no query word is interpreted as a prefix. This option is not recommended.
|
215
|
-
* **
|
215
|
+
* **typoTolerance**: if set to false, disable the typo-tolerance. Defaults to true.
|
216
216
|
* **minWordSizefor1Typo**: the minimum number of characters in a query word to accept one typo in this word.<br/>Defaults to 3.
|
217
217
|
* **minWordSizefor2Typos**: the minimum number of characters in a query word to accept two typos in this word.<br/>Defaults to 7.
|
218
|
+
* **allowTyposOnNumericTokens**: if set to false, disable typo-tolerance on numeric tokens (numbers). Default to true.
|
219
|
+
* **advancedSyntax**: Enable the advanced query syntax. Defaults to 0 (false).
|
220
|
+
* **Phrase query**: a phrase query defines a particular sequence of terms. A phrase query is build by Algolia's query parser for words surrounded by `"`. For example, `"search engine"` will retrieve records having `search` next to `engine` only. Typo-tolerance is _disabled_ on phrase queries.
|
221
|
+
* **Prohibit operator**: The prohibit operator excludes records that contain the term after the `-` symbol. For example `search -engine` will retrieve records containing `search` but not `engine`.
|
222
|
+
* **analytics**: If set to false, this query will not be taken into account in analytics feature. Default to true.
|
223
|
+
* **synonyms**: If set to false, this query will not use synonyms defined in configuration. Default to true.
|
224
|
+
* **replaceSynonymsInHighlight**: If set to false, words matched via synonyms expansion will not be replaced by the matched synonym in highlight result. Default to true.
|
225
|
+
* **optionalWords**: a string that contains the list of words that should be considered as optional when found in the query. The list of words is comma separated.
|
218
226
|
|
219
227
|
#### Pagination parameters
|
220
228
|
|
@@ -239,7 +247,12 @@ You can use the following optional arguments:
|
|
239
247
|
|
240
248
|
#### Numeric search parameters
|
241
249
|
* **numericFilters**: a string that contains the list of numeric filters you want to apply separated by a comma. The syntax of one filter is `attributeName` followed by `operand` followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`.
|
242
|
-
|
250
|
+
|
251
|
+
You can easily perform range queries via the `:` operator (equivalent to combining a `>=` and `<=` operand), for example `numericFilters=price:10 to 1000`.
|
252
|
+
|
253
|
+
You can also mix OR and AND operators. The OR operator is defined with a parenthesis syntax. For example `(code=1 AND (price:[0-100] OR price:[1000-2000]))` translates in `encodeURIComponent("code=1,(price:0 to 10,price:1000 to 2000)")`.
|
254
|
+
|
255
|
+
You can also use a string array encoding (for example `numericFilters: ["price>100","price<1000"]`).
|
243
256
|
|
244
257
|
#### Category search parameters
|
245
258
|
* **tagFilters**: filter the query by a set of tags. You can AND tags by separating them by commas. To OR tags, you must add parentheses. For example, `tags=tag1,(tag2,tag3)` means *tag1 AND (tag2 OR tag3)*. You can also use a string array encoding, for example `tagFilters: ["tag1",["tag2","tag3"]]` means *tag1 AND (tag2 OR tag3)*.<br/>At indexing, tags should be added in the **_tags** attribute of objects (for example `{"_tags":["tag1","tag2"]}`).
|
@@ -251,6 +264,7 @@ You can use the following optional arguments:
|
|
251
264
|
|
252
265
|
#### Distinct parameter
|
253
266
|
* **distinct**: If set to 1, enable the distinct feature (disabled by default) if the `attributeForDistinct` index setting is set. This feature is similar to the SQL "distinct" keyword: when enabled in a query with the `distinct=1` parameter, all hits containing a duplicate value for the attributeForDistinct attribute are removed from results. For example, if the chosen attribute is `show_name` and several hits have the same value for `show_name`, then only the best one is kept and others are removed.
|
267
|
+
**Note**: This feature is disabled if the query string is empty and there isn't any `tagFilters`, nor any `facetFilters`, nor any `numericFilters` parameters.
|
254
268
|
|
255
269
|
```ruby
|
256
270
|
index = Algolia::Index.new("contacts")
|
@@ -331,16 +345,19 @@ You can retrieve all settings using the `get_settings` function. The result will
|
|
331
345
|
* *Limit the attributes to index*.<br/>For example if you store a binary image in base64, you want to store it and be able to retrieve it but you don't want to search in the base64 string.
|
332
346
|
* *Control part of the ranking*.<br/>(see the ranking parameter for full explanation) Matches in attributes at the beginning of the list will be considered more important than matches in attributes further down the list. In one attribute, matching text at the beginning of the attribute will be considered more important than text after, you can disable this behavior if you add your attribute inside `unordered(AttributeName)`, for example `attributesToIndex: ["title", "unordered(text)"]`.
|
333
347
|
* **attributesForFaceting**: (array of strings) The list of fields you want to use for faceting. All strings in the attribute selected for faceting are extracted and added as a facet. If set to null, no attribute is used for faceting.
|
334
|
-
* **attributeForDistinct**: The attribute name used for the `Distinct` feature. This feature is similar to the SQL "distinct" keyword: when enabled in query with the `distinct=1` parameter, all hits containing a duplicate value for this attribute are removed from results. For example, if the chosen attribute is `show_name` and several hits have the same value for `show_name
|
335
|
-
* **ranking**: (array of strings) controls the way results are sorted.<br/>We have
|
348
|
+
* **attributeForDistinct**: The attribute name used for the `Distinct` feature. This feature is similar to the SQL "distinct" keyword: when enabled in query with the `distinct=1` parameter, all hits containing a duplicate value for this attribute are removed from results. For example, if the chosen attribute is `show_name` and several hits have the same value for `show_name`, then only the best one is kept and others are removed. **Note**: This feature is disabled if the query string is empty and there isn't any `tagFilters`, nor any `facetFilters`, nor any `numericFilters` parameters.
|
349
|
+
* **ranking**: (array of strings) controls the way results are sorted.<br/>We have nine available criteria:
|
336
350
|
* **typo**: sort according to number of typos,
|
337
351
|
* **geo**: sort according to decreassing distance when performing a geo-location based search,
|
352
|
+
* **words**: sort according to the number of query words matched by decreasing order. This parameter is useful when you use `optionalWords` query parameter to have results with the most matched words first.
|
338
353
|
* **proximity**: sort according to the proximity of query words in hits,
|
339
354
|
* **attribute**: sort according to the order of attributes defined by attributesToIndex,
|
340
355
|
* **exact**:
|
341
356
|
* if the user query contains one word: sort objects having an attribute that is exactly the query word before others. For example if you search for the "V" TV show, you want to find it with the "V" query and avoid to have all popular TV show starting by the v letter before it.
|
342
357
|
* if the user query contains multiple words: sort according to the number of words that matched exactly (and not as a prefix).
|
343
|
-
* **custom**: sort according to a user defined formula set in **customRanking** attribute
|
358
|
+
* **custom**: sort according to a user defined formula set in **customRanking** attribute.
|
359
|
+
* **asc(attributeName)**: sort according to a numeric attribute by ascending order. **attributeName** can be the name of any numeric attribute of your records (integer, a double or boolean).
|
360
|
+
* **desc(attributeName)**: sort according to a numeric attribute by descending order. **attributeName** can be the name of any numeric attribute of your records (integer, a double or boolean). <br/>The standard order is ["typo", "geo", "words", "proximity", "attribute", "exact", "custom"]
|
344
361
|
* **customRanking**: (array of strings) lets you specify part of the ranking.<br/>The syntax of this condition is an array of strings containing attributes prefixed by asc (ascending order) or desc (descending order) operator.
|
345
362
|
For example `"customRanking" => ["desc(population)", "asc(name)"]`
|
346
363
|
* **queryType**: Select how the query words are interpreted, it can be one of the following value:
|
@@ -349,6 +366,19 @@ For example `"customRanking" => ["desc(population)", "asc(name)"]`
|
|
349
366
|
* **prefixNone**: no query word is interpreted as a prefix. This option is not recommended.
|
350
367
|
* **slaves**: The list of indexes on which you want to replicate all write operations. In order to get response times in milliseconds, we pre-compute part of the ranking during indexing. If you want to use different ranking configurations depending of the use-case, you need to create one index per ranking configuration. This option enables you to perform write operations only on this index, and to automatically update slave indexes with the same operations.
|
351
368
|
|
369
|
+
#### Query expansion
|
370
|
+
* **synonyms**: (array of array of words considered as equals). For example, you may want to retrieve your **black ipad** record when your users are searching for **dark ipad**, even if the **dark** word is not part of the record: so you need to configure **black** as a synonym of **dark**. For example `"synomyms": [ [ "black", "dark" ], [ "small", "little", "mini" ], ... ]`.
|
371
|
+
* **placeholders**: (hash of array of words). This is an advanced use case to define a token substitutable by a list of words without having the original token searchable. It is defined by a hash associating placeholders to lists of substitutable words. For example `"placeholders": { "<streetnumber>": ["1", "2", "3", ..., "9999"]}` placeholder to be able to match all street numbers (we use the `< >` tag syntax to define placeholders in an attribute). For example:
|
372
|
+
* Push a record with the placeholder: `{ "name" : "Apple Store", "address" : "<streetnumber> Opera street, Paris" }`
|
373
|
+
* Configure the placeholder in your index settings: `"placeholders": { "<streetnumber>" : ["1", "2", "3", "4", "5", ... ], ... }`.
|
374
|
+
* **disableTypoToleranceOn**: (string array). Specify a list of words on which the automatic typo tolerance will be disabled.
|
375
|
+
* **altCorrections**: (object array). Specify alternative corrections that you want to consider. Each alternative correction is described by an object containing three attributes:
|
376
|
+
* **word**: the word to correct
|
377
|
+
* **correction**: the corrected word
|
378
|
+
* **nbTypos** the number of typos (1 or 2) that will be considered for the ranking algorithm (1 typo is better than 2 typos)
|
379
|
+
|
380
|
+
For example `"altCorrections": [ { "word" : "foot", "correction": "feet", "nbTypos": 1}, { "word": "feet", "correction": "foot", "nbTypos": 1}].`
|
381
|
+
|
352
382
|
#### Default query parameters (can be overwrite by query)
|
353
383
|
* **minWordSizefor1Typo**: (integer) the minimum number of characters to accept one typo (default = 3).
|
354
384
|
* **minWordSizefor2Typos**: (integer) the minimum number of characters to accept two typos (default = 7).
|
@@ -527,6 +557,8 @@ Algolia.delete_user_key("f420238212c54dcfad07ea0aa6d5c45f")
|
|
527
557
|
index.delete_user_key("71671c38001bf3ac857bc82052485107")
|
528
558
|
```
|
529
559
|
|
560
|
+
|
561
|
+
|
530
562
|
You may have a single index containing per-user data. In that case, all records should be tagged with their associated user_id in order to add a `tagFilters=(public,user_42)` filter at query time to retrieve only what a user has access to. If you're using the [JavaScript client](http://github.com/algolia/algoliasearch-client-js), it will result in a security breach since the user is able to modify the `tagFilters` you've set modifying the code from the browser. To keep using the JavaScript client (recommended for optimal latency) and target secured records, you can generate secured API key from your backend:
|
531
563
|
|
532
564
|
```ruby
|
@@ -570,6 +602,8 @@ This public API key must then be used in your JavaScript code as follow:
|
|
570
602
|
</script>
|
571
603
|
```
|
572
604
|
|
605
|
+
|
606
|
+
|
573
607
|
Copy or rename an index
|
574
608
|
-------------
|
575
609
|
|
@@ -595,7 +629,7 @@ puts Algolia.move_index("MyNewIndex", "MyIndex")
|
|
595
629
|
Backup / Retrieve all index content
|
596
630
|
-------------
|
597
631
|
|
598
|
-
You can retrieve all index content for backup purpose
|
632
|
+
You can retrieve all index content for backup purpose or for analytics using the browse method.
|
599
633
|
This method retrieve 1000 objects by API call and support pagination.
|
600
634
|
|
601
635
|
```ruby
|
data/algoliasearch.gemspec
CHANGED
@@ -6,12 +6,12 @@
|
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "algoliasearch"
|
9
|
-
s.version = "1.2.
|
9
|
+
s.version = "1.2.9"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Algolia"]
|
14
|
-
s.date = "2014-
|
14
|
+
s.date = "2014-07-08"
|
15
15
|
s.description = "A simple Ruby client for the algolia.com REST API"
|
16
16
|
s.email = "contact@algolia.com"
|
17
17
|
s.extra_rdoc_files = [
|
data/lib/algolia/client.rb
CHANGED
@@ -10,14 +10,17 @@ module Algolia
|
|
10
10
|
# A class which encapsulates the HTTPS communication with the Algolia
|
11
11
|
# API server. Uses the HTTPClient library for low-level HTTP communication.
|
12
12
|
class Client
|
13
|
-
attr_reader :hosts, :application_id, :api_key, :headers
|
13
|
+
attr_reader :ssl, :hosts, :application_id, :api_key, :headers, :connect_timeout, :send_timeout, :receive_timeout
|
14
14
|
|
15
15
|
|
16
16
|
def initialize(data = {})
|
17
|
-
@ssl
|
18
|
-
@application_id
|
19
|
-
@api_key
|
20
|
-
@hosts
|
17
|
+
@ssl = data[:ssl].nil? ? true : data[:ssl]
|
18
|
+
@application_id = data[:application_id]
|
19
|
+
@api_key = data[:api_key]
|
20
|
+
@hosts = (data[:hosts] || 1.upto(3).map { |i| "#{@application_id}-#{i}.algolia.io" }).shuffle
|
21
|
+
@connect_timeout = data[:connect_timeout]
|
22
|
+
@send_timeout = data[:send_timeout]
|
23
|
+
@receive_timeout = data[:receive_timeout]
|
21
24
|
@headers = {
|
22
25
|
Protocol::HEADER_API_KEY => api_key,
|
23
26
|
Protocol::HEADER_APP_ID => application_id,
|
@@ -71,6 +74,9 @@ module Algolia
|
|
71
74
|
:session => HTTPClient.new
|
72
75
|
}
|
73
76
|
hinfo[:session].transparent_gzip_decompression = true
|
77
|
+
hinfo[:session].connect_timeout = @connect_timeout if @connect_timeout
|
78
|
+
hinfo[:session].send_timeout = @send_timeout if @send_timeout
|
79
|
+
hinfo[:session].receive_timeout = @receive_timeout if @receive_timeout
|
74
80
|
hinfo[:session].ssl_config.add_trust_ca File.join(File.dirname(__FILE__), '..', '..', 'resources', 'ca-bundle.crt')
|
75
81
|
hinfo
|
76
82
|
end
|
@@ -177,7 +183,8 @@ module Algolia
|
|
177
183
|
requests = {
|
178
184
|
:requests => queries.map do |query|
|
179
185
|
indexName = query.delete(index_name_key) || query.delete(index_name_key.to_s)
|
180
|
-
|
186
|
+
encoded_params = Hash[query.map { |k,v| [k.to_s, v.is_a?(Array) ? v.to_json : v] }]
|
187
|
+
{ :indexName => indexName, :params => Protocol.to_query(encoded_params) }
|
181
188
|
end
|
182
189
|
}
|
183
190
|
Algolia.client.post(Protocol.multiple_queries_uri, requests.to_json)
|
@@ -203,6 +210,17 @@ module Algolia
|
|
203
210
|
Algolia.client.post(Protocol.index_operation_uri(src_index), request.to_json)
|
204
211
|
end
|
205
212
|
|
213
|
+
#
|
214
|
+
# Move an existing index and wait until the move has been processed
|
215
|
+
# @param src_index the name of index to copy.
|
216
|
+
# @param dst_index the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
|
217
|
+
#
|
218
|
+
def Algolia.move_index!(src_index, dst_index)
|
219
|
+
res = Algolia.move_index(src_index, dst_index)
|
220
|
+
Index.new(dst_index).wait_task(res['taskID'])
|
221
|
+
res
|
222
|
+
end
|
223
|
+
|
206
224
|
#
|
207
225
|
# Copy an existing index.
|
208
226
|
# @param src_index the name of index to copy.
|
@@ -213,6 +231,29 @@ module Algolia
|
|
213
231
|
Algolia.client.post(Protocol.index_operation_uri(src_index), request.to_json)
|
214
232
|
end
|
215
233
|
|
234
|
+
#
|
235
|
+
# Copy an existing index and wait until the copy has been processed.
|
236
|
+
# @param src_index the name of index to copy.
|
237
|
+
# @param dst_index the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
|
238
|
+
#
|
239
|
+
def Algolia.copy_index!(src_index, dst_index)
|
240
|
+
res = Algolia.copy_index(src_index, dst_index)
|
241
|
+
Index.new(dst_index).wait_task(res['taskID'])
|
242
|
+
res
|
243
|
+
end
|
244
|
+
|
245
|
+
# Delete an index
|
246
|
+
#
|
247
|
+
def delete_index(name)
|
248
|
+
Index.new(name).delete
|
249
|
+
end
|
250
|
+
|
251
|
+
# Delete an index and wait until the deletion has been processed.
|
252
|
+
#
|
253
|
+
def delete_index!(name)
|
254
|
+
Index.new(name).delete!
|
255
|
+
end
|
256
|
+
|
216
257
|
#
|
217
258
|
# Return last logs entries.
|
218
259
|
#
|
data/lib/algolia/index.rb
CHANGED
@@ -11,13 +11,23 @@ module Algolia
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# Delete an index
|
14
|
-
#
|
15
|
-
# return an hash of the form { "deletedAt" => "2013-01-18T15:33:13.556Z" }
|
14
|
+
#
|
15
|
+
# return an hash of the form { "deletedAt" => "2013-01-18T15:33:13.556Z", "taskID" => "42" }
|
16
16
|
def delete
|
17
17
|
Algolia.client.delete(Protocol.index_uri(name))
|
18
18
|
end
|
19
19
|
alias_method :delete_index, :delete
|
20
20
|
|
21
|
+
# Delete an index and wait until the deletion has been processed
|
22
|
+
#
|
23
|
+
# return an hash of the form { "deletedAt" => "2013-01-18T15:33:13.556Z", "taskID" => "42" }
|
24
|
+
def delete!
|
25
|
+
res = delete
|
26
|
+
wait_task(res['taskID'])
|
27
|
+
res
|
28
|
+
end
|
29
|
+
alias_method :delete_index!, :delete!
|
30
|
+
|
21
31
|
# Add an object in this index
|
22
32
|
#
|
23
33
|
# @param obj the object to add to the index.
|
@@ -154,6 +164,15 @@ module Algolia
|
|
154
164
|
end
|
155
165
|
end
|
156
166
|
|
167
|
+
#
|
168
|
+
# Get a list of objects from this index
|
169
|
+
#
|
170
|
+
# @param objectIDs the array of unique identifier of the objects to retrieve
|
171
|
+
#
|
172
|
+
def get_objects(objectIDs)
|
173
|
+
Algolia.client.post(Protocol.objects_uri, { :requests => objectIDs.map { |objectID| { :indexName => name, :objectID => objectID } } }.to_json)['results']
|
174
|
+
end
|
175
|
+
|
157
176
|
# Wait the publication of a task on the server.
|
158
177
|
# All server task are asynchronous and you can check with this method that the task is published.
|
159
178
|
#
|
@@ -291,6 +310,28 @@ module Algolia
|
|
291
310
|
return res
|
292
311
|
end
|
293
312
|
|
313
|
+
#
|
314
|
+
# Delete all objects matching a query
|
315
|
+
#
|
316
|
+
# @param query the query string
|
317
|
+
# @param params the optional query parameters
|
318
|
+
#
|
319
|
+
def delete_by_query(query, params = {})
|
320
|
+
params.delete(:hitsPerPage)
|
321
|
+
params.delete('hitsPerPage')
|
322
|
+
params.delete(:attributesToRetrieve)
|
323
|
+
params.delete('attributesToRetrieve')
|
324
|
+
|
325
|
+
params[:hitsPerPage] = 1000
|
326
|
+
params[:attributesToRetrieve] = ['objectID']
|
327
|
+
loop do
|
328
|
+
res = search(query, params)
|
329
|
+
break if res['hits'].empty?
|
330
|
+
res = delete_objects(res['hits'].map { |h| h['objectID'] })
|
331
|
+
wait_task res['taskID']
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
294
335
|
#
|
295
336
|
# Delete the index content
|
296
337
|
#
|
@@ -408,6 +449,104 @@ module Algolia
|
|
408
449
|
Algolia.client.post(Protocol.batch_uri(name), request.to_json)
|
409
450
|
end
|
410
451
|
|
452
|
+
# Send a batch request and wait the end of the indexing
|
453
|
+
def batch!(request)
|
454
|
+
res = batch(request)
|
455
|
+
wait_task(res['taskID'])
|
456
|
+
res
|
457
|
+
end
|
458
|
+
|
459
|
+
# Perform a search with disjunctive facets generating as many queries as number of disjunctive facets
|
460
|
+
#
|
461
|
+
# @param query the query
|
462
|
+
# @param disjunctive_facets the array of disjunctive facets
|
463
|
+
# @param params a hash representing the regular query parameters
|
464
|
+
# @param refinements a hash ("string" -> ["array", "of", "refined", "values"]) representing the current refinements
|
465
|
+
# ex: { "my_facet1" => ["my_value1", ["my_value2"], "my_disjunctive_facet1" => ["my_value1", "my_value2"] }
|
466
|
+
def search_disjunctive_faceting(query, disjunctive_facets, params = {}, refinements = {})
|
467
|
+
raise ArgumentError.new('Argument "disjunctive_facets" must be a String or an Array') unless disjunctive_facets.is_a?(String) || disjunctive_facets.is_a?(Array)
|
468
|
+
raise ArgumentError.new('Argument "refinements" must be a Hash of Arrays') if !refinements.is_a?(Hash) || !refinements.select { |k, v| !v.is_a?(Array) }.empty?
|
469
|
+
|
470
|
+
# extract disjunctive facets & associated refinements
|
471
|
+
disjunctive_facets = disjunctive_facets.split(',') if disjunctive_facets.is_a?(String)
|
472
|
+
disjunctive_refinements = {}
|
473
|
+
refinements.each do |k, v|
|
474
|
+
disjunctive_refinements[k] = v if disjunctive_facets.include?(k) || disjunctive_facets.include?(k.to_s)
|
475
|
+
end
|
476
|
+
|
477
|
+
# build queries
|
478
|
+
queries = []
|
479
|
+
## hits + regular facets query
|
480
|
+
filters = []
|
481
|
+
refinements.to_a.each do |k, values|
|
482
|
+
r = values.map { |v| "#{k}:#{v}" }
|
483
|
+
if disjunctive_refinements[k.to_s] || disjunctive_refinements[k.to_sym]
|
484
|
+
# disjunctive refinements are ORed
|
485
|
+
filters << r
|
486
|
+
else
|
487
|
+
# regular refinements are ANDed
|
488
|
+
filters += r
|
489
|
+
end
|
490
|
+
end
|
491
|
+
queries << params.merge({ :index_name => self.name, :query => query, :facetFilters => filters })
|
492
|
+
## one query per disjunctive facet (use all refinements but the current one + hitsPerPage=1 + single facet)
|
493
|
+
disjunctive_facets.each do |disjunctive_facet|
|
494
|
+
filters = []
|
495
|
+
refinements.each do |k, values|
|
496
|
+
if k.to_s != disjunctive_facet.to_s
|
497
|
+
r = values.map { |v| "#{k}:#{v}" }
|
498
|
+
if disjunctive_refinements[k.to_s] || disjunctive_refinements[k.to_sym]
|
499
|
+
# disjunctive refinements are ORed
|
500
|
+
filters << r
|
501
|
+
else
|
502
|
+
# regular refinements are ANDed
|
503
|
+
filters += r
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|
507
|
+
queries << params.merge({
|
508
|
+
:index_name => self.name,
|
509
|
+
:query => query,
|
510
|
+
:page => 0,
|
511
|
+
:hitsPerPage => 1,
|
512
|
+
:attributesToRetrieve => [],
|
513
|
+
:attributesToHighlight => [],
|
514
|
+
:attributesToSnippet => [],
|
515
|
+
:facets => disjunctive_facet,
|
516
|
+
:facetFilters => filters
|
517
|
+
})
|
518
|
+
end
|
519
|
+
answers = Algolia.multiple_queries(queries)
|
520
|
+
|
521
|
+
# aggregate answers
|
522
|
+
## first answer stores the hits + regular facets
|
523
|
+
aggregated_answer = answers['results'][0]
|
524
|
+
## others store the disjunctive facets
|
525
|
+
aggregated_answer['disjunctiveFacets'] = {}
|
526
|
+
answers['results'].each_with_index do |a, i|
|
527
|
+
next if i == 0
|
528
|
+
a['facets'].each do |facet, values|
|
529
|
+
## add the facet to the disjunctive facet hash
|
530
|
+
aggregated_answer['disjunctiveFacets'][facet] = values
|
531
|
+
## concatenate missing refinements
|
532
|
+
(disjunctive_refinements[facet.to_s] || disjunctive_refinements[facet.to_sym] || []).each do |r|
|
533
|
+
if aggregated_answer['disjunctiveFacets'][facet][r].nil?
|
534
|
+
aggregated_answer['disjunctiveFacets'][facet][r] = 0
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
aggregated_answer
|
541
|
+
end
|
542
|
+
|
543
|
+
#
|
544
|
+
# Alias of Algolia.list_indexes
|
545
|
+
#
|
546
|
+
def Index.all
|
547
|
+
Algolia.list_indexes
|
548
|
+
end
|
549
|
+
|
411
550
|
private
|
412
551
|
def check_array(objs)
|
413
552
|
raise ArgumentError.new("argument must be an array of objects") if !objs.is_a?(Array)
|
data/lib/algolia/protocol.rb
CHANGED
@@ -42,6 +42,10 @@ module Algolia
|
|
42
42
|
"/#{VERSION}/indexes/*/queries"
|
43
43
|
end
|
44
44
|
|
45
|
+
def Protocol.objects_uri
|
46
|
+
"/#{VERSION}/indexes/*/objects"
|
47
|
+
end
|
48
|
+
|
45
49
|
# Construct a uri referencing a given Algolia index
|
46
50
|
def Protocol.index_uri(index)
|
47
51
|
"/#{VERSION}/indexes/#{CGI.escape(index)}"
|
data/lib/algolia/version.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -124,8 +124,7 @@ describe 'Client' do
|
|
124
124
|
it "should have another index after" do
|
125
125
|
index = Algolia::Index.new(safe_index_name("àlgol?a"))
|
126
126
|
begin
|
127
|
-
index.delete_index
|
128
|
-
sleep 4 # Dirty but temporary
|
127
|
+
index.delete_index!
|
129
128
|
rescue
|
130
129
|
# friends_2 does not exist
|
131
130
|
end
|
@@ -139,12 +138,17 @@ describe 'Client' do
|
|
139
138
|
it "should get a object" do
|
140
139
|
@index.clear_index
|
141
140
|
@index.add_object!({:firstname => "Robert"})
|
141
|
+
@index.add_object!({:firstname => "Robert2"})
|
142
142
|
res = @index.search('')
|
143
|
-
|
143
|
+
res["nbHits"].should eq(2)
|
144
144
|
object = @index.get_object(res['hits'][0]['objectID'])
|
145
|
-
object['firstname'].should eq('
|
145
|
+
object['firstname'].should eq(res['hits'][0]['firstname'])
|
146
|
+
|
146
147
|
object = @index.get_object(res['hits'][0]['objectID'], 'firstname')
|
147
|
-
object['firstname'].should eq('
|
148
|
+
object['firstname'].should eq(res['hits'][0]['firstname'])
|
149
|
+
|
150
|
+
objects = @index.get_objects([ res['hits'][0]['objectID'], res['hits'][1]['objectID'] ])
|
151
|
+
objects.size.should eq(2)
|
148
152
|
end
|
149
153
|
|
150
154
|
it "should delete the object" do
|
@@ -166,11 +170,20 @@ describe 'Client' do
|
|
166
170
|
@index.search('')['nbHits'].should eq(0)
|
167
171
|
end
|
168
172
|
|
173
|
+
it "should delete several objects by query" do
|
174
|
+
@index.clear
|
175
|
+
@index.add_object({:firstname => "Robert1"})
|
176
|
+
@index.add_object!({:firstname => "Robert2"})
|
177
|
+
@index.search('')['nbHits'].should eq(2)
|
178
|
+
@index.delete_by_query('rob')
|
179
|
+
@index.search('')['nbHits'].should eq(0)
|
180
|
+
end
|
181
|
+
|
169
182
|
it "should copy the index" do
|
170
183
|
index = Algolia::Index.new(safe_index_name("àlgol?à"))
|
171
184
|
begin
|
172
185
|
@index.clear_index
|
173
|
-
index.
|
186
|
+
Algolia.delete_index index.name
|
174
187
|
rescue
|
175
188
|
# friends_2 does not exist
|
176
189
|
end
|
@@ -178,18 +191,18 @@ describe 'Client' do
|
|
178
191
|
@index.add_object!({:firstname => "Robert"})
|
179
192
|
@index.search('')['nbHits'].should eq(1)
|
180
193
|
|
181
|
-
Algolia.copy_index(safe_index_name("àlgol?a"), safe_index_name("àlgol?à"))
|
182
|
-
@index.delete_index
|
194
|
+
Algolia.copy_index!(safe_index_name("àlgol?a"), safe_index_name("àlgol?à"))
|
195
|
+
@index.delete_index!
|
183
196
|
|
184
197
|
index.search('')['nbHits'].should eq(1)
|
185
|
-
index.delete_index
|
198
|
+
index.delete_index!
|
186
199
|
end
|
187
200
|
|
188
201
|
it "should move the index" do
|
189
202
|
@index.clear_index rescue "friends does not exist"
|
190
203
|
index = Algolia::Index.new(safe_index_name("àlgol?à"))
|
191
204
|
begin
|
192
|
-
index.
|
205
|
+
Algolia.delete_index! index.name
|
193
206
|
rescue
|
194
207
|
# friends_2 does not exist
|
195
208
|
end
|
@@ -197,7 +210,7 @@ describe 'Client' do
|
|
197
210
|
@index.add_object!({:firstname => "Robert"})
|
198
211
|
@index.search('')['nbHits'].should eq(1)
|
199
212
|
|
200
|
-
Algolia.move_index(safe_index_name("àlgol?a"), safe_index_name("àlgol?à"))
|
213
|
+
Algolia.move_index!(safe_index_name("àlgol?a"), safe_index_name("àlgol?à"))
|
201
214
|
|
202
215
|
index.search('')['nbHits'].should eq(1)
|
203
216
|
index.delete_index
|
@@ -220,6 +233,7 @@ describe 'Client' do
|
|
220
233
|
end
|
221
234
|
|
222
235
|
it "should search on multipleIndex" do
|
236
|
+
@index.clear_index! rescue "Not fatal"
|
223
237
|
@index.add_object!({ :name => "John Doe", :email => "john@doe.org" }, "1")
|
224
238
|
res = Algolia.multiple_queries([{:index_name => safe_index_name("àlgol?a"), "query" => ""}])
|
225
239
|
res["results"][0]["hits"].length.should eq(1)
|
@@ -254,7 +268,7 @@ describe 'Client' do
|
|
254
268
|
"objectID" => "42"
|
255
269
|
}
|
256
270
|
]}
|
257
|
-
res = @index.batch(request)
|
271
|
+
res = @index.batch!(request)
|
258
272
|
@index.search('')['nbHits'].should eq(4)
|
259
273
|
end
|
260
274
|
|
@@ -293,19 +307,34 @@ describe 'Client' do
|
|
293
307
|
res['facets']['f']['f1'].should eq(1)
|
294
308
|
res['facets']['f']['f2'].should be_nil
|
295
309
|
res['facets']['f']['f3'].should be_nil
|
310
|
+
|
311
|
+
res = @index.search("", { :facets => "f,g", :facetFilters => [["f:f1", "g:g2"]] })
|
312
|
+
res['nbHits'].should eq(4)
|
313
|
+
res['facets']['f']['f1'].should eq(2)
|
314
|
+
res['facets']['f']['f2'].should eq(1)
|
315
|
+
res['facets']['f']['f3'].should eq(1)
|
316
|
+
|
317
|
+
res = @index.search("", { :facets => "f,g", :facetFilters => [["f:f1", "g:g2"], "g:g1"] })
|
318
|
+
res['nbHits'].should eq(1)
|
319
|
+
res['facets']['f']['f1'].should eq(1)
|
320
|
+
res['facets']['f']['f2'].should be_nil
|
321
|
+
res['facets']['f']['f3'].should be_nil
|
322
|
+
res['facets']['g']['g1'].should eq(1)
|
323
|
+
res['facets']['g']['g2'].should be_nil
|
296
324
|
end
|
297
325
|
|
298
326
|
it "should test keys" do
|
299
327
|
resIndex = @index.list_user_keys
|
300
328
|
newIndexKey = @index.add_user_key(['search'])
|
301
329
|
newIndexKey['key'].should_not eq("")
|
330
|
+
sleep 2 # no task ID here
|
302
331
|
resIndexAfter = @index.list_user_keys
|
303
332
|
is_include(resIndex['keys'], 'value', newIndexKey['key']).should eq(false)
|
304
333
|
is_include(resIndexAfter['keys'], 'value', newIndexKey['key']).should eq(true)
|
305
334
|
indexKey = @index.get_user_key(newIndexKey['key'])
|
306
335
|
indexKey['acl'][0].should eq('search')
|
307
336
|
@index.delete_user_key(newIndexKey['key'])
|
308
|
-
sleep
|
337
|
+
sleep 2 # no task ID here
|
309
338
|
resIndexEnd = @index.list_user_keys
|
310
339
|
is_include(resIndexEnd['keys'], 'value', newIndexKey['key']).should eq(false)
|
311
340
|
|
@@ -313,13 +342,14 @@ describe 'Client' do
|
|
313
342
|
res = Algolia.list_user_keys
|
314
343
|
newKey = Algolia.add_user_key(['search'])
|
315
344
|
newKey['key'].should_not eq("")
|
345
|
+
sleep 2 # no task ID here
|
316
346
|
resAfter = Algolia.list_user_keys
|
317
347
|
is_include(res['keys'], 'value', newKey['key']).should eq(false)
|
318
348
|
is_include(resAfter['keys'], 'value', newKey['key']).should eq(true)
|
319
349
|
key = Algolia.get_user_key(newKey['key'])
|
320
350
|
key['acl'][0].should eq('search')
|
321
351
|
Algolia.delete_user_key(newKey['key'])
|
322
|
-
sleep
|
352
|
+
sleep 2 # no task ID here
|
323
353
|
resEnd = Algolia.list_user_keys
|
324
354
|
is_include(resEnd['keys'], 'value', newKey['key']).should eq(false)
|
325
355
|
|
@@ -358,7 +388,7 @@ describe 'Client' do
|
|
358
388
|
end
|
359
389
|
|
360
390
|
it "Check attributes list_indexes:" do
|
361
|
-
res = Algolia.
|
391
|
+
res = Algolia::Index.all
|
362
392
|
res.should have_key('items')
|
363
393
|
res['items'][0].should have_key('name')
|
364
394
|
res['items'][0]['name'].should be_a(String)
|
@@ -492,7 +522,7 @@ describe 'Client' do
|
|
492
522
|
index = Algolia::Index.new(safe_index_name("àlgol?à"))
|
493
523
|
index2 = Algolia::Index.new(safe_index_name("àlgol?à2"))
|
494
524
|
index2.add_object!({ :name => "John Doe", :email => "john@doe.org" }, "1")
|
495
|
-
task = Algolia.move_index(safe_index_name("àlgol?à2"), safe_index_name("àlgol?à"))
|
525
|
+
task = Algolia.move_index!(safe_index_name("àlgol?à2"), safe_index_name("àlgol?à"))
|
496
526
|
task.should have_key('updatedAt')
|
497
527
|
task['updatedAt'].should be_a(String)
|
498
528
|
task.should have_key('taskID')
|
@@ -504,7 +534,7 @@ describe 'Client' do
|
|
504
534
|
index = Algolia::Index.new(safe_index_name("àlgol?à"))
|
505
535
|
index2 = Algolia::Index.new(safe_index_name("àlgol?à2"))
|
506
536
|
index2.add_object!({ :name => "John Doe", :email => "john@doe.org" }, "1")
|
507
|
-
task = Algolia.copy_index(safe_index_name("àlgol?à2"), safe_index_name("àlgol?à"))
|
537
|
+
task = Algolia.copy_index!(safe_index_name("àlgol?à2"), safe_index_name("àlgol?à"))
|
508
538
|
task.should have_key('updatedAt')
|
509
539
|
task['updatedAt'].should be_a(String)
|
510
540
|
task.should have_key('taskID')
|
@@ -528,6 +558,7 @@ describe 'Client' do
|
|
528
558
|
newIndexKey['key'].should be_a(String)
|
529
559
|
newIndexKey.should have_key('createdAt')
|
530
560
|
newIndexKey['createdAt'].should be_a(String)
|
561
|
+
sleep 2 # no task ID here
|
531
562
|
resIndex = @index.list_user_keys
|
532
563
|
resIndex.should have_key('keys')
|
533
564
|
resIndex['keys'].should be_a(Array)
|
@@ -605,4 +636,44 @@ describe 'Client' do
|
|
605
636
|
res['results'][0].should have_key('params')
|
606
637
|
res['results'][0]['params'].should be_a(String)
|
607
638
|
end
|
639
|
+
|
640
|
+
it 'should handle disjunctive faceting' do
|
641
|
+
index = Algolia::Index.new(safe_index_name("test_hotels"))
|
642
|
+
index.set_settings :attributesForFacetting => ['city', 'stars', 'facilities']
|
643
|
+
index.clear_index rescue nil
|
644
|
+
index.add_objects! [
|
645
|
+
{ :name => 'Hotel A', :stars => '*', :facilities => ['wifi', 'bath', 'spa'], :city => 'Paris' },
|
646
|
+
{ :name => 'Hotel B', :stars => '*', :facilities => ['wifi'], :city => 'Paris' },
|
647
|
+
{ :name => 'Hotel C', :stars => '**', :facilities => ['bath'], :city => 'San Francisco' },
|
648
|
+
{ :name => 'Hotel D', :stars => '****', :facilities => ['spa'], :city => 'Paris' },
|
649
|
+
{ :name => 'Hotel E', :stars => '****', :facilities => ['spa'], :city => 'New York' },
|
650
|
+
]
|
651
|
+
|
652
|
+
answer = index.search_disjunctive_faceting('h', ['stars', 'facilities'], { :facets => 'city' })
|
653
|
+
answer['nbHits'].should eq(5)
|
654
|
+
answer['facets'].size.should eq(1)
|
655
|
+
answer['disjunctiveFacets'].size.should eq(2)
|
656
|
+
|
657
|
+
answer = index.search_disjunctive_faceting('h', ['stars', 'facilities'], { :facets => 'city' }, { :stars => ['*'] })
|
658
|
+
answer['nbHits'].should eq(2)
|
659
|
+
answer['facets'].size.should eq(1)
|
660
|
+
answer['disjunctiveFacets'].size.should eq(2)
|
661
|
+
answer['disjunctiveFacets']['stars']['*'].should eq(2)
|
662
|
+
answer['disjunctiveFacets']['stars']['**'].should eq(1)
|
663
|
+
answer['disjunctiveFacets']['stars']['****'].should eq(2)
|
664
|
+
|
665
|
+
answer = index.search_disjunctive_faceting('h', ['stars', 'facilities'], { :facets => 'city' }, { :stars => ['*'], :city => ['Paris'] })
|
666
|
+
answer['nbHits'].should eq(2)
|
667
|
+
answer['facets'].size.should eq(1)
|
668
|
+
answer['disjunctiveFacets'].size.should eq(2)
|
669
|
+
answer['disjunctiveFacets']['stars']['*'].should eq(2)
|
670
|
+
answer['disjunctiveFacets']['stars']['****'].should eq(1)
|
671
|
+
|
672
|
+
answer = index.search_disjunctive_faceting('h', ['stars', 'facilities'], { :facets => 'city' }, { :stars => ['*', '****'], :city => ['Paris'] })
|
673
|
+
answer['nbHits'].should eq(3)
|
674
|
+
answer['facets'].size.should eq(1)
|
675
|
+
answer['disjunctiveFacets'].size.should eq(2)
|
676
|
+
answer['disjunctiveFacets']['stars']['*'].should eq(2)
|
677
|
+
answer['disjunctiveFacets']['stars']['****'].should eq(1)
|
678
|
+
end
|
608
679
|
end
|
metadata
CHANGED
@@ -1,83 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: algoliasearch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Algolia
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httpclient
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '2.3'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: json
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 1.5.1
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 1.5.1
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: travis
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rdoc
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
description: A simple Ruby client for the algolia.com REST API
|
@@ -89,8 +89,8 @@ extra_rdoc_files:
|
|
89
89
|
- LICENSE.txt
|
90
90
|
- README.md
|
91
91
|
files:
|
92
|
-
- .rspec
|
93
|
-
- .travis.yml
|
92
|
+
- ".rspec"
|
93
|
+
- ".travis.yml"
|
94
94
|
- ChangeLog
|
95
95
|
- Gemfile
|
96
96
|
- Gemfile.lock
|
@@ -121,12 +121,12 @@ require_paths:
|
|
121
121
|
- lib
|
122
122
|
required_ruby_version: !ruby/object:Gem::Requirement
|
123
123
|
requirements:
|
124
|
-
- -
|
124
|
+
- - ">="
|
125
125
|
- !ruby/object:Gem::Version
|
126
126
|
version: '0'
|
127
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '0'
|
132
132
|
requirements: []
|