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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eaaabc5a559b204abd97ed48ee06b40414ed2201
4
- data.tar.gz: 1d90fa315597cf218329b1c6bd5798c83cc688e5
3
+ metadata.gz: 3c2d37bc8e01bf59c2a23fd08ec86229e6b37f95
4
+ data.tar.gz: f2890ed7e2c1103ca6004ac91792c4c0ac3d62ac
5
5
  SHA512:
6
- metadata.gz: de132995a5dcdef698e3a9fc949637925ea6922adbf6f4504de770f932781e0e4b019d65ad1d9285476ffd6530f33dffe261ab1406895333c6a44a5f06de220d
7
- data.tar.gz: 035cf82798698c855d733d6e0aeb41fd468d6c671541d8d1e4180648d14e343e13ca55853e45b91051c5930e420053f3c564c7b353a97df3e9d3ca6b4336a383
6
+ metadata.gz: a83669bc6eedf16e80419534844ad6a654a9e8fb675800e5f68393689c205a77187a2c00fd1c82755d9ef68d4147be666a6c5a0fa97bafea9c0fbddc7b0de161
7
+ data.tar.gz: dee9c83ce0c9246f667663f1e683efda2f3e5c81476c61f3e75760438ccd6af3ab3fd77814a8149a5f1f9034f8fc59f467d9e3eefe0a7ebb015d11f62888a74d
@@ -8,7 +8,7 @@ rvm:
8
8
  - 2.0.0
9
9
  - 1.9.3
10
10
  - jruby
11
- - rbx
11
+ - rbx-2
12
12
  env:
13
13
  global:
14
14
  - secure: lBLtTjQyeJW7+TWgGmf1mmsXgUzsTnq3/KHvCk+BvOGwoHXTBR1Y6yiczWWHEP4/wBeXXm31xUQC+VoYc/7Ev/2NNB/mqlXAgGw+D0nwQriNXjYt1WV6lQvyoiaUzQ+w+7VahWdx0LuKvSYI+khe8M8BfHQqYacu0VlpAUnjb2I=
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
@@ -19,4 +19,5 @@ group :test do
19
19
  gem 'autotest-growl'
20
20
  gem 'webmock'
21
21
  gem 'simplecov'
22
+ gem 'mime-types', '< 2.0'
22
23
  end
@@ -1,14 +1,14 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- ZenTest (4.9.5)
5
- addressable (2.3.5)
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.4.0)
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.1)
20
- safe_yaml (~> 0.9.0)
19
+ crack (0.4.2)
20
+ safe_yaml (~> 1.0.0)
21
21
  diff-lcs (1.2.5)
22
- docile (1.1.2)
23
- ethon (0.6.2)
22
+ docile (1.1.3)
23
+ ethon (0.7.0)
24
24
  ffi (>= 1.3.0)
25
- mime-types (~> 1.18)
26
- faraday (0.8.8)
27
- multipart-post (~> 1.2.0)
28
- faraday_middleware (0.9.0)
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.0)
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.20)
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.8.2)
52
- multipart-post (1.2.0)
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.4)
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.4-java)
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.4.0)
65
- websocket (~> 1.0.0)
66
- rake (10.1.1)
67
- rdoc (4.1.0)
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 (2.14.1)
73
- rspec-core (~> 2.14.0)
74
- rspec-expectations (~> 2.14.0)
75
- rspec-mocks (~> 2.14.0)
76
- rspec-core (2.14.7)
77
- rspec-expectations (2.14.4)
78
- diff-lcs (>= 1.1.3, < 2.0)
79
- rspec-mocks (2.14.4)
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.6)
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.2)
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.0.0)
255
+ rubysl-resolv (2.1.0)
252
256
  rubysl-rexml (2.0.2)
253
- rubysl-rinda (2.0.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.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.9.7)
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.4.7)
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.2.2)
294
- tins (~> 0.8)
295
- thor (0.18.1)
296
- tins (0.13.1)
297
- travis (1.6.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.8.7)
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.7)
309
- ethon (~> 0.6.2)
310
- webmock (1.16.1)
311
- addressable (>= 2.2.7)
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.0.7)
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
- * **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.
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
- You can have multiple conditions on one attribute like for example `numericFilters=price>100,price<1000`. You can also use a string array encoding (for example `numericFilters: ["price>100","price<1000"]`).
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, then only the best one is kept and others are removed.
335
- * **ranking**: (array of strings) controls the way results are sorted.<br/>We have six available criteria:
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.<br/>The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
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" : "&lt;streetnumber&gt; 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 of for analytics using the browse method.
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
@@ -6,12 +6,12 @@
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "algoliasearch"
9
- s.version = "1.2.8"
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-03-27"
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 = [
@@ -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 = 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
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
- { :indexName => indexName, :params => Protocol.to_query(query) }
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
  #
@@ -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)
@@ -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)}"
@@ -1,3 +1,3 @@
1
1
  module Algolia
2
- VERSION = "1.2.8"
2
+ VERSION = "1.2.9"
3
3
  end
@@ -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
- @index.search("")["nbHits"].should eq(1)
143
+ res["nbHits"].should eq(2)
144
144
  object = @index.get_object(res['hits'][0]['objectID'])
145
- object['firstname'].should eq('Robert')
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('Robert')
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.delete_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.delete_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 1 # Dirty but temporary
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 1 # Dirty but temporary
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.list_indexes
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.8
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-03-27 00:00:00.000000000 Z
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: []