algolia 2.0.4 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +10 -1
- data/CHANGELOG.md +27 -1
- data/README.md +1 -1
- data/lib/algolia/config/personalization_config.rb +20 -0
- data/lib/algolia/config/recommend_config.rb +6 -0
- data/lib/algolia/config/recommendation_config.rb +2 -15
- data/lib/algolia/helpers.rb +49 -0
- data/lib/algolia/http/http_requester.rb +4 -4
- data/lib/algolia/personalization_client.rb +60 -0
- data/lib/algolia/recommend_client.rb +134 -0
- data/lib/algolia/recommendation_client.rb +2 -55
- data/lib/algolia/responses/dictionary_response.rb +33 -0
- data/lib/algolia/search_client.rb +176 -2
- data/lib/algolia/search_index.rb +9 -50
- data/lib/algolia/version.rb +1 -1
- data/lib/algolia.rb +5 -0
- data/test/algolia/integration/analytics_client_test.rb +6 -2
- data/test/algolia/integration/mocks/mock_requester.rb +13 -11
- data/test/algolia/integration/personalization_client_test.rb +30 -0
- data/test/algolia/integration/recommend_client_test.rb +70 -0
- data/test/algolia/integration/search_client_test.rb +106 -12
- data/test/algolia/integration/search_index_test.rb +3 -0
- data/test/test_helper.rb +28 -0
- metadata +12 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c2f4626569fa71c66f1801a6608a0d66bdd7afc5d2d42342856d8083281d0ee
|
4
|
+
data.tar.gz: bbc716503ac0354dd80a1e5d1196cfc8ff5e8d80c3c166c196bd1ec5e227a446
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd82583938e41074786a9f6e3518256af5f54c6946e8224a9e2ecc40952f211475dbec10ba5d054058764bdccbdceff80a9147c195a8c3f62a5ab4f033c11757
|
7
|
+
data.tar.gz: 25971344a9f64260d29f8c1a7878605a2e408369de05b4e4add74e828697cefde461bb83ed5564dbd6f94de3844844e6e357568e830de53a3d77253a27151e33
|
data/.circleci/config.yml
CHANGED
@@ -23,6 +23,13 @@ aliases:
|
|
23
23
|
name: Run linting tool
|
24
24
|
command: bundle exec rake rubocop
|
25
25
|
|
26
|
+
- &credentials
|
27
|
+
name: Retrieve temporary Algolia credentials if needed
|
28
|
+
command: |
|
29
|
+
if [ "$CIRCLE_PR_REPONAME" ]; then
|
30
|
+
curl -s https://algoliasearch-client-keygen.herokuapp.com | sh >> $BASH_ENV
|
31
|
+
fi
|
32
|
+
|
26
33
|
- &run_tests
|
27
34
|
name: Run unit and integration tests
|
28
35
|
command: |
|
@@ -73,6 +80,7 @@ jobs:
|
|
73
80
|
- restore_cache: *restore_cache
|
74
81
|
- run: *install_bundler
|
75
82
|
- save_cache: *save_cache
|
83
|
+
- run: *credentials
|
76
84
|
- run: *run_tests
|
77
85
|
|
78
86
|
test_jruby:
|
@@ -88,6 +96,7 @@ jobs:
|
|
88
96
|
- restore_cache: *restore_cache
|
89
97
|
- run: *install_bundler
|
90
98
|
- save_cache: *save_cache
|
99
|
+
- run: *credentials
|
91
100
|
- run: *run_tests
|
92
101
|
|
93
102
|
release:
|
@@ -122,7 +131,7 @@ workflows:
|
|
122
131
|
- test_ruby:
|
123
132
|
matrix:
|
124
133
|
parameters:
|
125
|
-
version: ['2.2', '2.3', '2.4', '2.5', '2.6', '2.7']
|
134
|
+
version: ['2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0']
|
126
135
|
filters:
|
127
136
|
tags:
|
128
137
|
only: /.*/
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,32 @@
|
|
1
1
|
# ChangeLog
|
2
2
|
|
3
|
-
## [Unreleased](https://github.com/algolia/algoliasearch-client-ruby/compare/2.
|
3
|
+
## [Unreleased](https://github.com/algolia/algoliasearch-client-ruby/compare/2.2.2..master)
|
4
|
+
|
5
|
+
## [2.2.1](https://github.com/algolia/algoliasearch-client-ruby/compare/2.2.1...2.2.2) (2021-12-08)
|
6
|
+
### Fixed
|
7
|
+
- Added a `status` field to the `MockRequester` ([`#467`](https://github.com/algolia/algoliasearch-client-ruby/pull/467))
|
8
|
+
|
9
|
+
## [2.2.1](https://github.com/algolia/algoliasearch-client-ruby/compare/2.2.0...2.2.1) (2021-11-12)
|
10
|
+
### Chore
|
11
|
+
- Deprecated `RecommendationClient` in favor of `PersonalizationClient` ([`#461`](https://github.com/algolia/algoliasearch-client-ruby/pull/461))
|
12
|
+
|
13
|
+
## [2.2.0](https://github.com/algolia/algoliasearch-client-ruby/compare/2.1.1...2.2.0) (2021-11-08)
|
14
|
+
### Added
|
15
|
+
- Added RecommendClient ([`#466`](https://github.com/algolia/algoliasearch-client-ruby/pull/466))
|
16
|
+
- Added `search` alias for `multiple_queries` ([`#457`](https://github.com/algolia/algoliasearch-client-ruby/pull/457))
|
17
|
+
|
18
|
+
## [2.1.1](https://github.com/algolia/algoliasearch-client-ruby/compare/2.1.0...2.1.1) (2021-05-27)
|
19
|
+
|
20
|
+
### Fix
|
21
|
+
- Bug with read/write nodes caching ([`#455`](https://github.com/algolia/algoliasearch-client-ruby/pull/455))
|
22
|
+
|
23
|
+
## [2.1.0](https://github.com/algolia/algoliasearch-client-ruby/compare/2.0.4...2.1.0) (2021-03-30)
|
24
|
+
|
25
|
+
### Feat
|
26
|
+
- Custom dictionaries methods
|
27
|
+
|
28
|
+
### Fix
|
29
|
+
- The parameter `forwardToReplicas` should be handled independently in the `set_settings` method
|
4
30
|
|
5
31
|
## [2.0.4](https://github.com/algolia/algoliasearch-client-ruby/compare/2.0.3...2.0.4) (2021-01-05)
|
6
32
|
|
data/README.md
CHANGED
@@ -41,7 +41,7 @@ Then, create objects on your index:
|
|
41
41
|
client = Algolia::Search::Client.create('YourApplicationID', 'YourAPIKey')
|
42
42
|
index = client.init_index('your_index_name')
|
43
43
|
|
44
|
-
index.save_objects([objectID: 1, name: 'Foo'])
|
44
|
+
index.save_objects([{objectID: 1, name: 'Foo'}])
|
45
45
|
```
|
46
46
|
|
47
47
|
Finally, you may begin searching a object using the `search` method:
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Algolia
|
2
|
+
module Personalization
|
3
|
+
class Config < BaseConfig
|
4
|
+
attr_accessor :region, :default_hosts
|
5
|
+
|
6
|
+
# Initialize a config
|
7
|
+
#
|
8
|
+
# @option options [String] :application_id
|
9
|
+
# @option options [String] :api_key
|
10
|
+
# @option options [String] :region
|
11
|
+
#
|
12
|
+
def initialize(opts = {})
|
13
|
+
super(opts)
|
14
|
+
|
15
|
+
@region = opts[:region] || 'us'
|
16
|
+
@default_hosts = [Transport::StatefulHost.new("personalization.#{region}.algolia.com")]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,20 +1,7 @@
|
|
1
1
|
module Algolia
|
2
2
|
module Recommendation
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# Initialize a config
|
7
|
-
#
|
8
|
-
# @option options [String] :application_id
|
9
|
-
# @option options [String] :api_key
|
10
|
-
# @option options [String] :region
|
11
|
-
#
|
12
|
-
def initialize(opts = {})
|
13
|
-
super(opts)
|
14
|
-
|
15
|
-
@region = opts[:region] || 'us'
|
16
|
-
@default_hosts = [Transport::StatefulHost.new("recommendation.#{region}.algolia.com")]
|
17
|
-
end
|
3
|
+
# <b>DEPRECATED:</b> Please use <tt>Algolia::Personalization::Config</tt> instead.
|
4
|
+
class Config < Algolia::Personalization::Config
|
18
5
|
end
|
19
6
|
end
|
20
7
|
end
|
data/lib/algolia/helpers.rb
CHANGED
@@ -82,4 +82,53 @@ module Helpers
|
|
82
82
|
end
|
83
83
|
res
|
84
84
|
end
|
85
|
+
|
86
|
+
# Check the passed object to determine if it's an array
|
87
|
+
#
|
88
|
+
# @param object [Object]
|
89
|
+
#
|
90
|
+
def check_array(object)
|
91
|
+
raise Algolia::AlgoliaError, 'argument must be an array of objects' unless object.is_a?(Array)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Check the passed object
|
95
|
+
#
|
96
|
+
# @param object [Object]
|
97
|
+
# @param in_array [Boolean] whether the object is an array or not
|
98
|
+
#
|
99
|
+
def check_object(object, in_array = false)
|
100
|
+
case object
|
101
|
+
when Array
|
102
|
+
raise Algolia::AlgoliaError, in_array ? 'argument must be an array of objects' : 'argument must not be an array'
|
103
|
+
when String, Integer, Float, TrueClass, FalseClass, NilClass
|
104
|
+
raise Algolia::AlgoliaError, "argument must be an #{'array of' if in_array} object, got: #{object.inspect}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Check if passed object has a objectID
|
109
|
+
#
|
110
|
+
# @param object [Object]
|
111
|
+
# @param object_id [String]
|
112
|
+
#
|
113
|
+
def get_object_id(object, object_id = nil)
|
114
|
+
check_object(object)
|
115
|
+
object_id ||= object[:objectID] || object['objectID']
|
116
|
+
raise Algolia::AlgoliaError, "Missing 'objectID'" if object_id.nil?
|
117
|
+
object_id
|
118
|
+
end
|
119
|
+
|
120
|
+
# Build a batch request
|
121
|
+
#
|
122
|
+
# @param action [String] action to perform on the engine
|
123
|
+
# @param objects [Array] objects on which build the action
|
124
|
+
# @param with_object_id [Boolean] if set to true, check if each object has an objectID set
|
125
|
+
#
|
126
|
+
def chunk(action, objects, with_object_id = false)
|
127
|
+
objects.map do |object|
|
128
|
+
check_object(object, true)
|
129
|
+
request = { action: action, body: object }
|
130
|
+
request[:objectID] = get_object_id(object).to_s if with_object_id
|
131
|
+
request
|
132
|
+
end
|
133
|
+
end
|
85
134
|
end
|
@@ -9,9 +9,9 @@ module Algolia
|
|
9
9
|
# @param logger [Object] logger used to log requests. Defaults to Algolia::LoggerHelper
|
10
10
|
#
|
11
11
|
def initialize(adapter, logger)
|
12
|
-
@adapter
|
13
|
-
@logger
|
14
|
-
@
|
12
|
+
@adapter = adapter
|
13
|
+
@logger = logger
|
14
|
+
@connections = {}
|
15
15
|
end
|
16
16
|
|
17
17
|
# Sends request to the engine
|
@@ -65,7 +65,7 @@ module Algolia
|
|
65
65
|
# @return [Faraday::Connection]
|
66
66
|
#
|
67
67
|
def connection(host)
|
68
|
-
@
|
68
|
+
@connections[host.accept] ||= Faraday.new(build_url(host)) do |f|
|
69
69
|
f.adapter @adapter.to_sym
|
70
70
|
end
|
71
71
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Algolia
|
2
|
+
module Personalization
|
3
|
+
class Client
|
4
|
+
# Initializes the Personalization client
|
5
|
+
#
|
6
|
+
# @param personalization_config [Personalization::Config] a Personalization::Config object which contains your APP_ID and API_KEY
|
7
|
+
# @option adapter [Object] adapter object used for the connection
|
8
|
+
# @option logger [Object]
|
9
|
+
# @option http_requester [Object] http_requester object used for the connection
|
10
|
+
#
|
11
|
+
def initialize(personalization_config, opts = {})
|
12
|
+
@config = personalization_config
|
13
|
+
adapter = opts[:adapter] || Defaults::ADAPTER
|
14
|
+
logger = opts[:logger] || LoggerHelper.create('debug.log')
|
15
|
+
requester = opts[:http_requester] || Defaults::REQUESTER_CLASS.new(adapter, logger)
|
16
|
+
@transporter = Transport::Transport.new(@config, requester)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create a new client providing only app ID and API key
|
20
|
+
#
|
21
|
+
# @param app_id [String] Algolia application ID
|
22
|
+
# @param api_key [String] Algolia API key
|
23
|
+
#
|
24
|
+
# @return self
|
25
|
+
#
|
26
|
+
def self.create(app_id, api_key)
|
27
|
+
config = Personalization::Config.new(application_id: app_id, api_key: api_key)
|
28
|
+
create_with_config(config)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create a new client providing only an Personalization::Config object
|
32
|
+
#
|
33
|
+
# @param config [Personalization::Config]
|
34
|
+
#
|
35
|
+
# @return self
|
36
|
+
#
|
37
|
+
def self.create_with_config(config)
|
38
|
+
new(config)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set the personalization strategy.
|
42
|
+
#
|
43
|
+
# @param personalization_strategy [Hash] A strategy object.
|
44
|
+
#
|
45
|
+
# @return [Hash]
|
46
|
+
#
|
47
|
+
def set_personalization_strategy(personalization_strategy, opts = {})
|
48
|
+
@transporter.write(:POST, '1/strategies/personalization', personalization_strategy, opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the personalization strategy.
|
52
|
+
#
|
53
|
+
# @return [Hash]
|
54
|
+
#
|
55
|
+
def get_personalization_strategy(opts = {})
|
56
|
+
@transporter.read(:GET, '1/strategies/personalization', {}, opts)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Algolia
|
2
|
+
module Recommend
|
3
|
+
class Model
|
4
|
+
BOUGHT_TOGETHER = 'bought-together'
|
5
|
+
RELATED_PRODUCTS = 'related-products'
|
6
|
+
end
|
7
|
+
|
8
|
+
class Client
|
9
|
+
include Helpers
|
10
|
+
|
11
|
+
# Initializes the Recommend client
|
12
|
+
#
|
13
|
+
# @param recommend_config [Recommend::Config] a Recommend::Config object which contains your APP_ID and API_KEY
|
14
|
+
# @option adapter [Object] adapter object used for the connection
|
15
|
+
# @option logger [Object]
|
16
|
+
# @option http_requester [Object] http_requester object used for the connection
|
17
|
+
#
|
18
|
+
def initialize(recommend_config, opts = {})
|
19
|
+
@config = recommend_config
|
20
|
+
adapter = opts[:adapter] || Defaults::ADAPTER
|
21
|
+
logger = opts[:logger] || LoggerHelper.create('debug.log')
|
22
|
+
requester = opts[:http_requester] || Defaults::REQUESTER_CLASS.new(adapter, logger)
|
23
|
+
@transporter = Transport::Transport.new(@config, requester)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Create a new client providing only app ID and API key
|
27
|
+
#
|
28
|
+
# @param app_id [String] Algolia application ID
|
29
|
+
# @param api_key [String] Algolia API key
|
30
|
+
#
|
31
|
+
# @return self
|
32
|
+
#
|
33
|
+
def self.create(app_id, api_key)
|
34
|
+
config = Recommend::Config.new(application_id: app_id, api_key: api_key)
|
35
|
+
create_with_config(config)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Create a new client providing only an Recommend::Config object
|
39
|
+
#
|
40
|
+
# @param config [Recommend::Config]
|
41
|
+
#
|
42
|
+
# @return self
|
43
|
+
#
|
44
|
+
def self.create_with_config(config)
|
45
|
+
new(config)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get recommendation for the given queries
|
49
|
+
#
|
50
|
+
# @param requests [Array<Hash>] the queries to retrieve recommendations for
|
51
|
+
# @param opts [Hash] extra parameters to send with your request
|
52
|
+
#
|
53
|
+
# @return [Hash]
|
54
|
+
#
|
55
|
+
def get_recommendations(requests, opts = {})
|
56
|
+
@transporter.write(
|
57
|
+
:POST,
|
58
|
+
'/1/indexes/*/recommendations',
|
59
|
+
{ requests: format_recommendation_requests(symbolize_all(requests)) },
|
60
|
+
opts
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get related products for the given requests
|
65
|
+
#
|
66
|
+
# @param requests [Array<Hash>] the requests to get related products for
|
67
|
+
# @param opts [Hash] extra parameters to send with your request
|
68
|
+
#
|
69
|
+
# @return [Hash]
|
70
|
+
#
|
71
|
+
def get_related_products(requests, opts = {})
|
72
|
+
get_recommendations(
|
73
|
+
set_request_models(symbolize_all(requests), Model::RELATED_PRODUCTS),
|
74
|
+
opts
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get frequently bought together items for the given requests
|
79
|
+
#
|
80
|
+
# @param requests [Array<Hash>] the requests to get frequently bought together items for
|
81
|
+
# @param opts [Hash] extra parameters to send with your request
|
82
|
+
#
|
83
|
+
# @return [Hash]
|
84
|
+
#
|
85
|
+
def get_frequently_bought_together(requests, opts = {})
|
86
|
+
get_recommendations(
|
87
|
+
set_request_models(symbolize_all(requests), Model::BOUGHT_TOGETHER),
|
88
|
+
opts
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Symbolize all hashes in an array
|
95
|
+
#
|
96
|
+
# @param hash_array [Array<Hash<String|Symbol, any>>] the hashes to symbolize
|
97
|
+
#
|
98
|
+
# @return [Array<Hash<Symbol, any>>]
|
99
|
+
#
|
100
|
+
def symbolize_all(hash_array)
|
101
|
+
hash_array.map { |q| symbolize_hash(q) }
|
102
|
+
end
|
103
|
+
|
104
|
+
# Format the recommendation requests
|
105
|
+
#
|
106
|
+
# @param requests [Array<Hash>] the requests to retrieve recommendations for
|
107
|
+
#
|
108
|
+
# @return [Array<Hash>]
|
109
|
+
#
|
110
|
+
def format_recommendation_requests(requests)
|
111
|
+
requests.map do |request|
|
112
|
+
request[:threshold] = 0 unless request[:threshold].is_a? Numeric
|
113
|
+
request.delete(:fallbackParameters) if request[:model] == Model::BOUGHT_TOGETHER
|
114
|
+
|
115
|
+
request
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Force the requests to target a specific model
|
120
|
+
#
|
121
|
+
# @param requests [Array<Hash>] the requests to change
|
122
|
+
# @param model [String] the model to enforce
|
123
|
+
#
|
124
|
+
# @return [Array<Hash>]
|
125
|
+
#
|
126
|
+
def set_request_models(requests, model)
|
127
|
+
requests.map do |query|
|
128
|
+
query[:model] = model
|
129
|
+
query
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -1,60 +1,7 @@
|
|
1
1
|
module Algolia
|
2
2
|
module Recommendation
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
# @param recommendation_config [Recommendation::Config] a Recommendation::Config object which contains your APP_ID and API_KEY
|
7
|
-
# @option adapter [Object] adapter object used for the connection
|
8
|
-
# @option logger [Object]
|
9
|
-
# @option http_requester [Object] http_requester object used for the connection
|
10
|
-
#
|
11
|
-
def initialize(recommendation_config, opts = {})
|
12
|
-
@config = recommendation_config
|
13
|
-
adapter = opts[:adapter] || Defaults::ADAPTER
|
14
|
-
logger = opts[:logger] || LoggerHelper.create('debug.log')
|
15
|
-
requester = opts[:http_requester] || Defaults::REQUESTER_CLASS.new(adapter, logger)
|
16
|
-
@transporter = Transport::Transport.new(@config, requester)
|
17
|
-
end
|
18
|
-
|
19
|
-
# Create a new client providing only app ID and API key
|
20
|
-
#
|
21
|
-
# @param app_id [String] Algolia application ID
|
22
|
-
# @param api_key [String] Algolia API key
|
23
|
-
#
|
24
|
-
# @return self
|
25
|
-
#
|
26
|
-
def self.create(app_id, api_key)
|
27
|
-
config = Recommendation::Config.new(application_id: app_id, api_key: api_key)
|
28
|
-
create_with_config(config)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Create a new client providing only an Recommendation::Config object
|
32
|
-
#
|
33
|
-
# @param config [Recommendation::Config]
|
34
|
-
#
|
35
|
-
# @return self
|
36
|
-
#
|
37
|
-
def self.create_with_config(config)
|
38
|
-
new(config)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Set the personalization strategy.
|
42
|
-
#
|
43
|
-
# @param personalization_strategy [Hash] A strategy object.
|
44
|
-
#
|
45
|
-
# @return [Hash]
|
46
|
-
#
|
47
|
-
def set_personalization_strategy(personalization_strategy, opts = {})
|
48
|
-
@transporter.write(:POST, '1/strategies/personalization', personalization_strategy, opts)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Get the personalization strategy.
|
52
|
-
#
|
53
|
-
# @return [Hash]
|
54
|
-
#
|
55
|
-
def get_personalization_strategy(opts = {})
|
56
|
-
@transporter.read(:GET, '1/strategies/personalization', {}, opts)
|
57
|
-
end
|
3
|
+
# <b>DEPRECATED:</b> Please use <tt>Algolia::Personalization::Client</tt> instead.
|
4
|
+
class Client < Algolia::Personalization::Client
|
58
5
|
end
|
59
6
|
end
|
60
7
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Algolia
|
2
|
+
class DictionaryResponse < BaseResponse
|
3
|
+
include CallType
|
4
|
+
|
5
|
+
attr_reader :raw_response
|
6
|
+
|
7
|
+
# @param client [Search::Client] Algolia Search Client used for verification
|
8
|
+
# @param response [Hash] Raw response from the client
|
9
|
+
#
|
10
|
+
def initialize(client, response)
|
11
|
+
@client = client
|
12
|
+
@raw_response = response
|
13
|
+
@done = false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Wait for the task to complete
|
17
|
+
#
|
18
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
19
|
+
#
|
20
|
+
def wait(_opts = {})
|
21
|
+
until @done
|
22
|
+
res = @client.custom_request({}, path_encode('/1/task/%s', @raw_response[:taskID]), :GET, READ)
|
23
|
+
status = get_option(res, 'status')
|
24
|
+
if status == 'published'
|
25
|
+
@done = true
|
26
|
+
end
|
27
|
+
sleep(Defaults::WAIT_TASK_DEFAULT_TIME_BEFORE_RETRY / 1000)
|
28
|
+
end
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -80,7 +80,7 @@ module Algolia
|
|
80
80
|
end
|
81
81
|
|
82
82
|
# # # # # # # # # # # # # # # # # # # # #
|
83
|
-
#
|
83
|
+
# INDEX METHODS
|
84
84
|
# # # # # # # # # # # # # # # # # # # # #
|
85
85
|
|
86
86
|
# Initialize an index with a given name
|
@@ -480,6 +480,7 @@ module Algolia
|
|
480
480
|
def multiple_queries(queries, opts = {})
|
481
481
|
@transporter.read(:POST, '/1/indexes/*/queries', { requests: queries }, opts)
|
482
482
|
end
|
483
|
+
alias_method :search, :multiple_queries
|
483
484
|
|
484
485
|
# # # # # # # # # # # # # # # # # # # # #
|
485
486
|
# MCM METHODS
|
@@ -594,12 +595,185 @@ module Algolia
|
|
594
595
|
@transporter.read(:GET, '/1/clusters/mapping/pending' + handle_params({ getClusters: retrieve_mappings }), {}, request_options)
|
595
596
|
end
|
596
597
|
|
597
|
-
#
|
598
598
|
# Aliases the pending_mappings? method
|
599
599
|
#
|
600
600
|
alias_method :has_pending_mappings, :pending_mappings?
|
601
601
|
|
602
|
+
# # # # # # # # # # # # # # # # # # # # #
|
603
|
+
# CUSTOM DICTIONARIES METHODS
|
604
|
+
# # # # # # # # # # # # # # # # # # # # #
|
605
|
+
|
606
|
+
# Save entries for a given dictionary
|
607
|
+
#
|
608
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
609
|
+
# @param dictionary_entries [Array<Hash>] array of dictionary entries
|
610
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
611
|
+
#
|
612
|
+
# @return DictionaryResponse
|
613
|
+
#
|
614
|
+
def save_dictionary_entries(dictionary, dictionary_entries, opts = {})
|
615
|
+
response = @transporter.write(
|
616
|
+
:POST,
|
617
|
+
path_encode('/1/dictionaries/%s/batch', dictionary),
|
618
|
+
{ clearExistingDictionaryEntries: false, requests: chunk('addEntry', dictionary_entries) },
|
619
|
+
opts
|
620
|
+
)
|
621
|
+
|
622
|
+
DictionaryResponse.new(self, response)
|
623
|
+
end
|
624
|
+
|
625
|
+
# Save entries for a given dictionary and wait for the task to finish
|
626
|
+
#
|
627
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
628
|
+
# @param dictionary_entries [Array<Hash>] array of dictionary entries
|
629
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
630
|
+
#
|
631
|
+
def save_dictionary_entries!(dictionary, dictionary_entries, opts = {})
|
632
|
+
response = save_dictionary_entries(dictionary, dictionary_entries, opts)
|
633
|
+
|
634
|
+
response.wait(opts)
|
635
|
+
end
|
636
|
+
|
637
|
+
# Replace entries for a given dictionary
|
638
|
+
#
|
639
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
640
|
+
# @param dictionary_entries [Array<Hash>] array of dictionary entries
|
641
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
642
|
+
#
|
643
|
+
# @return DictionaryResponse
|
644
|
+
#
|
645
|
+
def replace_dictionary_entries(dictionary, dictionary_entries, opts = {})
|
646
|
+
response = @transporter.write(
|
647
|
+
:POST,
|
648
|
+
path_encode('/1/dictionaries/%s/batch', dictionary),
|
649
|
+
{ clearExistingDictionaryEntries: true, requests: chunk('addEntry', dictionary_entries) },
|
650
|
+
opts
|
651
|
+
)
|
652
|
+
|
653
|
+
DictionaryResponse.new(self, response)
|
654
|
+
end
|
655
|
+
|
656
|
+
# Replace entries for a given dictionary and wait for the task to finish
|
657
|
+
#
|
658
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
659
|
+
# @param dictionary_entries [Array<Hash>] array of dictionary entries
|
660
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
661
|
+
#
|
662
|
+
def replace_dictionary_entries!(dictionary, dictionary_entries, opts = {})
|
663
|
+
response = replace_dictionary_entries(dictionary, dictionary_entries, opts)
|
664
|
+
|
665
|
+
response.wait(opts)
|
666
|
+
end
|
667
|
+
|
668
|
+
# Delete entries for a given dictionary
|
669
|
+
#
|
670
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
671
|
+
# @param object_ids [Array<Hash>] array of object ids
|
672
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
673
|
+
#
|
674
|
+
# @return DictionaryResponse
|
675
|
+
#
|
676
|
+
def delete_dictionary_entries(dictionary, object_ids, opts = {})
|
677
|
+
request = object_ids.map do |object_id|
|
678
|
+
{ objectID: object_id }
|
679
|
+
end
|
680
|
+
response = @transporter.write(
|
681
|
+
:POST,
|
682
|
+
path_encode('/1/dictionaries/%s/batch', dictionary),
|
683
|
+
{ clearExistingDictionaryEntries: false, requests: chunk('deleteEntry', request) },
|
684
|
+
opts
|
685
|
+
)
|
686
|
+
|
687
|
+
DictionaryResponse.new(self, response)
|
688
|
+
end
|
689
|
+
|
690
|
+
# Delete entries for a given dictionary and wait for the task to finish
|
691
|
+
#
|
692
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
693
|
+
# @param object_ids [Array<Hash>] array of object ids
|
694
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
602
695
|
#
|
696
|
+
def delete_dictionary_entries!(dictionary, object_ids, opts = {})
|
697
|
+
response = delete_dictionary_entries(dictionary, object_ids, opts)
|
698
|
+
|
699
|
+
response.wait(opts)
|
700
|
+
end
|
701
|
+
|
702
|
+
# Clear all entries for a given dictionary
|
703
|
+
#
|
704
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
705
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
706
|
+
#
|
707
|
+
# @return DictionaryResponse
|
708
|
+
#
|
709
|
+
def clear_dictionary_entries(dictionary, opts = {})
|
710
|
+
replace_dictionary_entries(dictionary, [], opts)
|
711
|
+
end
|
712
|
+
|
713
|
+
# Clear all entries for a given dictionary and wait for the task to finish
|
714
|
+
#
|
715
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
716
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
717
|
+
#
|
718
|
+
def clear_dictionary_entries!(dictionary, opts = {})
|
719
|
+
response = replace_dictionary_entries(dictionary, [], opts)
|
720
|
+
|
721
|
+
response.wait(opts)
|
722
|
+
end
|
723
|
+
|
724
|
+
# Search entries for a given dictionary
|
725
|
+
#
|
726
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
727
|
+
# @param query [String] query to send
|
728
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
729
|
+
#
|
730
|
+
def search_dictionary_entries(dictionary, query, opts = {})
|
731
|
+
@transporter.read(
|
732
|
+
:POST,
|
733
|
+
path_encode('/1/dictionaries/%s/search', dictionary),
|
734
|
+
{ query: query },
|
735
|
+
opts
|
736
|
+
)
|
737
|
+
end
|
738
|
+
|
739
|
+
# Set settings for all the dictionaries
|
740
|
+
#
|
741
|
+
# @param dictionary_settings [Hash]
|
742
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
743
|
+
#
|
744
|
+
# @return DictionaryResponse
|
745
|
+
#
|
746
|
+
def set_dictionary_settings(dictionary_settings, opts = {})
|
747
|
+
response = @transporter.write(:PUT, '/1/dictionaries/*/settings', dictionary_settings, opts)
|
748
|
+
|
749
|
+
DictionaryResponse.new(self, response)
|
750
|
+
end
|
751
|
+
|
752
|
+
# Set settings for all the dictionaries and wait for the task to finish
|
753
|
+
#
|
754
|
+
# @param dictionary_settings [Hash]
|
755
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
756
|
+
#
|
757
|
+
# @return DictionaryResponse
|
758
|
+
#
|
759
|
+
def set_dictionary_settings!(dictionary_settings, opts = {})
|
760
|
+
response = set_dictionary_settings(dictionary_settings, opts)
|
761
|
+
|
762
|
+
response.wait(opts)
|
763
|
+
end
|
764
|
+
|
765
|
+
# Retrieve settings for all the dictionaries
|
766
|
+
#
|
767
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
768
|
+
#
|
769
|
+
def get_dictionary_settings(opts = {})
|
770
|
+
@transporter.read(:GET, '/1/dictionaries/*/settings', {}, opts)
|
771
|
+
end
|
772
|
+
|
773
|
+
# # # # # # # # # # # # # # # # # # # # #
|
774
|
+
# MISC METHODS
|
775
|
+
# # # # # # # # # # # # # # # # # # # # #
|
776
|
+
|
603
777
|
# Method available to make custom requests to the API
|
604
778
|
#
|
605
779
|
def custom_request(data, uri, method, call_type, opts = {})
|
data/lib/algolia/search_index.rb
CHANGED
@@ -987,7 +987,15 @@ module Algolia
|
|
987
987
|
# @return [IndexingResponse]
|
988
988
|
#
|
989
989
|
def set_settings(settings, opts = {})
|
990
|
-
|
990
|
+
request_options = symbolize_hash(opts)
|
991
|
+
forward_to_replicas = request_options.delete(:forwardToReplicas) || false
|
992
|
+
|
993
|
+
response = @transporter.write(
|
994
|
+
:PUT,
|
995
|
+
path_encode('/1/indexes/%s/settings', @name) + handle_params({ forwardToReplicas: forward_to_replicas }),
|
996
|
+
settings,
|
997
|
+
request_options
|
998
|
+
)
|
991
999
|
|
992
1000
|
IndexingResponse.new(self, response)
|
993
1001
|
end
|
@@ -1037,55 +1045,6 @@ module Algolia
|
|
1037
1045
|
|
1038
1046
|
private
|
1039
1047
|
|
1040
|
-
# Check the passed object to determine if it's an array
|
1041
|
-
#
|
1042
|
-
# @param object [Object]
|
1043
|
-
#
|
1044
|
-
def check_array(object)
|
1045
|
-
raise AlgoliaError, 'argument must be an array of objects' unless object.is_a?(Array)
|
1046
|
-
end
|
1047
|
-
|
1048
|
-
# Check the passed object
|
1049
|
-
#
|
1050
|
-
# @param object [Object]
|
1051
|
-
# @param in_array [Boolean] whether the object is an array or not
|
1052
|
-
#
|
1053
|
-
def check_object(object, in_array = false)
|
1054
|
-
case object
|
1055
|
-
when Array
|
1056
|
-
raise AlgoliaError, in_array ? 'argument must be an array of objects' : 'argument must not be an array'
|
1057
|
-
when String, Integer, Float, TrueClass, FalseClass, NilClass
|
1058
|
-
raise AlgoliaError, "argument must be an #{'array of' if in_array} object, got: #{object.inspect}"
|
1059
|
-
end
|
1060
|
-
end
|
1061
|
-
|
1062
|
-
# Check if passed object has a objectID
|
1063
|
-
#
|
1064
|
-
# @param object [Object]
|
1065
|
-
# @param object_id [String]
|
1066
|
-
#
|
1067
|
-
def get_object_id(object, object_id = nil)
|
1068
|
-
check_object(object)
|
1069
|
-
object_id ||= object[:objectID] || object['objectID']
|
1070
|
-
raise AlgoliaError, "Missing 'objectID'" if object_id.nil?
|
1071
|
-
object_id
|
1072
|
-
end
|
1073
|
-
|
1074
|
-
# Build a batch request
|
1075
|
-
#
|
1076
|
-
# @param action [String] action to perform on the engine
|
1077
|
-
# @param objects [Array] objects on which build the action
|
1078
|
-
# @param with_object_id [Boolean] if set to true, check if each object has an objectID set
|
1079
|
-
#
|
1080
|
-
def chunk(action, objects, with_object_id = false)
|
1081
|
-
objects.map do |object|
|
1082
|
-
check_object(object, true)
|
1083
|
-
request = { action: action, body: object }
|
1084
|
-
request[:objectID] = get_object_id(object).to_s if with_object_id
|
1085
|
-
request
|
1086
|
-
end
|
1087
|
-
end
|
1088
|
-
|
1089
1048
|
def raw_batch(requests, opts)
|
1090
1049
|
@transporter.write(:POST, path_encode('/1/indexes/%s/batch', @name), { requests: requests }, opts)
|
1091
1050
|
end
|
data/lib/algolia/version.rb
CHANGED
data/lib/algolia.rb
CHANGED
@@ -7,6 +7,8 @@ require 'algolia/config/base_config'
|
|
7
7
|
require 'algolia/config/search_config'
|
8
8
|
require 'algolia/config/analytics_config'
|
9
9
|
require 'algolia/config/insights_config'
|
10
|
+
require 'algolia/config/recommend_config'
|
11
|
+
require 'algolia/config/personalization_config'
|
10
12
|
require 'algolia/config/recommendation_config'
|
11
13
|
require 'algolia/enums/call_type'
|
12
14
|
require 'algolia/enums/retry_outcome_type'
|
@@ -20,6 +22,7 @@ require 'algolia/responses/indexing_response'
|
|
20
22
|
require 'algolia/responses/add_api_key_response'
|
21
23
|
require 'algolia/responses/update_api_key_response'
|
22
24
|
require 'algolia/responses/delete_api_key_response'
|
25
|
+
require 'algolia/responses/dictionary_response'
|
23
26
|
require 'algolia/responses/restore_api_key_response'
|
24
27
|
require 'algolia/responses/multiple_batch_indexing_response'
|
25
28
|
require 'algolia/responses/multiple_response'
|
@@ -32,6 +35,8 @@ require 'algolia/account_client'
|
|
32
35
|
require 'algolia/search_client'
|
33
36
|
require 'algolia/analytics_client'
|
34
37
|
require 'algolia/insights_client'
|
38
|
+
require 'algolia/recommend_client'
|
39
|
+
require 'algolia/personalization_client'
|
35
40
|
require 'algolia/recommendation_client'
|
36
41
|
require 'algolia/error'
|
37
42
|
require 'algolia/search_index'
|
@@ -23,7 +23,9 @@ class AnalyticsClientTest < BaseTest
|
|
23
23
|
endAt: tomorrow.strftime('%Y-%m-%dT%H:%M:%SZ')
|
24
24
|
}
|
25
25
|
|
26
|
-
response =
|
26
|
+
response = retry_test do
|
27
|
+
client.add_ab_test(ab_test)
|
28
|
+
end
|
27
29
|
ab_test_id = response[:abTestID]
|
28
30
|
|
29
31
|
index1.wait_task(response[:taskID])
|
@@ -86,7 +88,9 @@ class AnalyticsClientTest < BaseTest
|
|
86
88
|
endAt: tomorrow.strftime('%Y-%m-%dT%H:%M:%SZ')
|
87
89
|
}
|
88
90
|
|
89
|
-
response =
|
91
|
+
response = retry_test do
|
92
|
+
client.add_ab_test(ab_test)
|
93
|
+
end
|
90
94
|
ab_test_id = response[:abTestID]
|
91
95
|
|
92
96
|
index.wait_task(response[:taskID])
|
@@ -1,25 +1,27 @@
|
|
1
1
|
class MockRequester
|
2
|
+
attr_accessor :requests
|
2
3
|
def initialize
|
3
4
|
@connection = nil
|
5
|
+
@requests = []
|
4
6
|
end
|
5
7
|
|
6
|
-
def send_request(host, method, path,
|
7
|
-
|
8
|
-
response = {
|
9
|
-
connection: connection,
|
8
|
+
def send_request(host, method, path, body, headers, timeout, connect_timeout)
|
9
|
+
request = {
|
10
10
|
host: host,
|
11
|
+
method: method,
|
11
12
|
path: path,
|
13
|
+
body: body,
|
12
14
|
headers: headers,
|
13
|
-
|
14
|
-
|
15
|
-
body: '{"hits":[],"nbHits":0,"page":0,"nbPages":1,"hitsPerPage":20,"exhaustiveNbHits":true,"query":"test","params":"query=test","processingTimeMS":1}',
|
16
|
-
success: true
|
15
|
+
timeout: timeout,
|
16
|
+
connect_timeout: connect_timeout
|
17
17
|
}
|
18
18
|
|
19
|
+
@requests.push(request)
|
20
|
+
|
19
21
|
Algolia::Http::Response.new(
|
20
|
-
status:
|
21
|
-
body:
|
22
|
-
headers:
|
22
|
+
status: 200,
|
23
|
+
body: '{"hits": [], "status": "published"}',
|
24
|
+
headers: {}
|
23
25
|
)
|
24
26
|
end
|
25
27
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'base_test'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
class PersonalizationClientTest < BaseTest
|
5
|
+
describe 'Personalization client' do
|
6
|
+
def test_personalization_client
|
7
|
+
client = Algolia::Personalization::Client.create(APPLICATION_ID_1, ADMIN_KEY_1)
|
8
|
+
personalization_strategy = {
|
9
|
+
eventsScoring: [
|
10
|
+
{ eventName: 'Add to cart', eventType: 'conversion', score: 50 },
|
11
|
+
{ eventName: 'Purchase', eventType: 'conversion', score: 100 }
|
12
|
+
],
|
13
|
+
facetsScoring: [
|
14
|
+
{ facetName: 'brand', score: 100 },
|
15
|
+
{ facetName: 'categories', score: 10 }
|
16
|
+
],
|
17
|
+
personalizationImpact: 0
|
18
|
+
}
|
19
|
+
|
20
|
+
begin
|
21
|
+
client.set_personalization_strategy(personalization_strategy)
|
22
|
+
rescue Algolia::AlgoliaHttpError => e
|
23
|
+
raise e unless e.code == 429
|
24
|
+
end
|
25
|
+
response = client.get_personalization_strategy
|
26
|
+
|
27
|
+
assert_equal response, personalization_strategy
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require_relative 'base_test'
|
3
|
+
|
4
|
+
class RecommendClientTest < BaseTest
|
5
|
+
describe 'Recommendations' do
|
6
|
+
def test_get_recommendations
|
7
|
+
requester = MockRequester.new
|
8
|
+
client = Algolia::Recommend::Client.new(@@search_config, http_requester: requester)
|
9
|
+
|
10
|
+
# It correctly formats queries using the 'bought-together' model
|
11
|
+
client.get_recommendations([{ indexName: 'products', objectID: 'B018APC4LE', model: Algolia::Recommend::Model::BOUGHT_TOGETHER }])
|
12
|
+
|
13
|
+
# It correctly formats queries using the 'related-products' model
|
14
|
+
client.get_recommendations([{ indexName: 'products', objectID: 'B018APC4LE', model: Algolia::Recommend::Model::RELATED_PRODUCTS }])
|
15
|
+
|
16
|
+
# It correctly formats multiple queries.
|
17
|
+
client.get_recommendations(
|
18
|
+
[
|
19
|
+
{ indexName: 'products', objectID: 'B018APC4LE-1', model: Algolia::Recommend::Model::RELATED_PRODUCTS, threshold: 0 },
|
20
|
+
{ indexName: 'products', objectID: 'B018APC4LE-2', model: Algolia::Recommend::Model::RELATED_PRODUCTS, threshold: 0 }
|
21
|
+
]
|
22
|
+
)
|
23
|
+
|
24
|
+
# It resets the threshold to 0 if it's not numeric.
|
25
|
+
client.get_recommendations([{ indexName: 'products', objectID: 'B018APC4LE', model: Algolia::Recommend::Model::BOUGHT_TOGETHER, threshold: nil }])
|
26
|
+
|
27
|
+
# It passes the threshold correctly if it's numeric.
|
28
|
+
client.get_recommendations([{ indexName: 'products', objectID: 'B018APC4LE', model: Algolia::Recommend::Model::BOUGHT_TOGETHER, threshold: 42 }])
|
29
|
+
|
30
|
+
assert_requests(
|
31
|
+
requester,
|
32
|
+
[
|
33
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":0}]}' },
|
34
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"related-products","threshold":0}]}' },
|
35
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE-1","model":"related-products","threshold":0},{"indexName":"products","objectID":"B018APC4LE-2","model":"related-products","threshold":0}]}' },
|
36
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":0}]}' },
|
37
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":42}]}' }
|
38
|
+
]
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_get_related_products
|
43
|
+
requester = MockRequester.new
|
44
|
+
client = Algolia::Recommend::Client.new(@@search_config, http_requester: requester)
|
45
|
+
|
46
|
+
client.get_related_products([{ indexName: 'products', objectID: 'B018APC4LE' }])
|
47
|
+
|
48
|
+
assert_requests(
|
49
|
+
requester,
|
50
|
+
[{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"related-products","threshold":0}]}' }]
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_get_frequently_bought_together
|
55
|
+
requester = MockRequester.new
|
56
|
+
client = Algolia::Recommend::Client.new(@@search_config, http_requester: requester)
|
57
|
+
|
58
|
+
client.get_frequently_bought_together([{ indexName: 'products', objectID: 'B018APC4LE' }])
|
59
|
+
client.get_frequently_bought_together([{ indexName: 'products', objectID: 'B018APC4LE', fallbackParameters: {} }])
|
60
|
+
|
61
|
+
assert_requests(
|
62
|
+
requester,
|
63
|
+
[
|
64
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":0}]}' },
|
65
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":0}]}' }
|
66
|
+
]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'securerandom'
|
1
2
|
require_relative 'base_test'
|
2
3
|
|
3
4
|
class SearchClientTest < BaseTest
|
@@ -223,7 +224,9 @@ class SearchClientTest < BaseTest
|
|
223
224
|
assert_includes api_keys, @api_key[:value]
|
224
225
|
|
225
226
|
@@search_client.update_api_key!(@api_key[:value], { maxHitsPerQuery: 42 })
|
226
|
-
updated_api_key =
|
227
|
+
updated_api_key = retry_test do
|
228
|
+
@@search_client.get_api_key(@api_key[:value], test: 'test')
|
229
|
+
end
|
227
230
|
assert_equal 42, updated_api_key[:maxHitsPerQuery]
|
228
231
|
|
229
232
|
@@search_client.delete_api_key!(@api_key[:value])
|
@@ -234,18 +237,13 @@ class SearchClientTest < BaseTest
|
|
234
237
|
|
235
238
|
assert_equal 'Key does not exist', exception.message
|
236
239
|
|
237
|
-
|
238
|
-
|
239
|
-
@@search_client.restore_api_key!(@api_key[:value])
|
240
|
-
break
|
241
|
-
rescue Algolia::AlgoliaHttpError => e
|
242
|
-
if e.code != 404
|
243
|
-
raise StandardError
|
244
|
-
end
|
245
|
-
end
|
240
|
+
retry_test do
|
241
|
+
@@search_client.restore_api_key!(@api_key[:value])
|
246
242
|
end
|
247
243
|
|
248
|
-
restored_key =
|
244
|
+
restored_key = retry_test do
|
245
|
+
@@search_client.get_api_key(@api_key[:value])
|
246
|
+
end
|
249
247
|
|
250
248
|
refute_nil restored_key
|
251
249
|
end
|
@@ -334,7 +332,12 @@ class SearchClientTest < BaseTest
|
|
334
332
|
secured_index1 = secured_client.init_index(@index1.name)
|
335
333
|
secured_index2 = secured_client.init_index(@index2.name)
|
336
334
|
|
337
|
-
|
335
|
+
res = retry_test do
|
336
|
+
secured_index1.search('')
|
337
|
+
end
|
338
|
+
|
339
|
+
assert_equal 1, res[:hits].length
|
340
|
+
|
338
341
|
exception = assert_raises Algolia::AlgoliaHttpError do
|
339
342
|
secured_index2.search('')
|
340
343
|
end
|
@@ -367,5 +370,96 @@ class SearchClientTest < BaseTest
|
|
367
370
|
assert_equal 'The SecuredAPIKey doesn\'t have a validUntil parameter.', exception.message
|
368
371
|
end
|
369
372
|
end
|
373
|
+
|
374
|
+
describe 'Custom Dictionaries' do
|
375
|
+
def before_all
|
376
|
+
@client = Algolia::Search::Client.create(APPLICATION_ID_2, ADMIN_KEY_2)
|
377
|
+
end
|
378
|
+
|
379
|
+
def test_stopwords_dictionaries
|
380
|
+
entry_id = SecureRandom.hex
|
381
|
+
assert_equal 0, @client.search_dictionary_entries('stopwords', entry_id)[:nbHits]
|
382
|
+
|
383
|
+
entry = {
|
384
|
+
objectID: entry_id,
|
385
|
+
language: 'en',
|
386
|
+
word: 'down'
|
387
|
+
}
|
388
|
+
@client.save_dictionary_entries!('stopwords', [entry])
|
389
|
+
|
390
|
+
stopwords = @client.search_dictionary_entries('stopwords', entry_id)
|
391
|
+
assert_equal 1, stopwords[:nbHits]
|
392
|
+
assert_equal stopwords[:hits][0][:objectID], entry[:objectID]
|
393
|
+
assert_equal stopwords[:hits][0][:word], entry[:word]
|
394
|
+
|
395
|
+
@client.delete_dictionary_entries!('stopwords', [entry_id])
|
396
|
+
assert_equal 0, @client.search_dictionary_entries('stopwords', entry_id)[:nbHits]
|
397
|
+
|
398
|
+
old_dictionary_state = @client.search_dictionary_entries('stopwords', '')
|
399
|
+
old_dictionary_entries = old_dictionary_state[:hits].map do |hit|
|
400
|
+
hit.reject { |key| key == :type }
|
401
|
+
end
|
402
|
+
|
403
|
+
@client.save_dictionary_entries!('stopwords', [entry])
|
404
|
+
assert_equal 1, @client.search_dictionary_entries('stopwords', entry_id)[:nbHits]
|
405
|
+
|
406
|
+
@client.replace_dictionary_entries!('stopwords', old_dictionary_entries)
|
407
|
+
assert_equal 0, @client.search_dictionary_entries('stopwords', entry_id)[:nbHits]
|
408
|
+
|
409
|
+
stopwords_settings = {
|
410
|
+
disableStandardEntries: {
|
411
|
+
stopwords: {
|
412
|
+
en: true
|
413
|
+
}
|
414
|
+
}
|
415
|
+
}
|
416
|
+
|
417
|
+
@client.set_dictionary_settings!(stopwords_settings)
|
418
|
+
|
419
|
+
assert_equal @client.get_dictionary_settings, stopwords_settings
|
420
|
+
end
|
421
|
+
|
422
|
+
def test_plurals_dictionaries
|
423
|
+
entry_id = SecureRandom.hex
|
424
|
+
assert_equal 0, @client.search_dictionary_entries('plurals', entry_id)[:nbHits]
|
425
|
+
|
426
|
+
entry = {
|
427
|
+
objectID: entry_id,
|
428
|
+
language: 'fr',
|
429
|
+
words: %w(cheval chevaux)
|
430
|
+
}
|
431
|
+
@client.save_dictionary_entries!('plurals', [entry])
|
432
|
+
|
433
|
+
plurals = @client.search_dictionary_entries('plurals', entry_id)
|
434
|
+
assert_equal 1, plurals[:nbHits]
|
435
|
+
assert_equal plurals[:hits][0][:objectID], entry[:objectID]
|
436
|
+
assert_equal plurals[:hits][0][:words], entry[:words]
|
437
|
+
|
438
|
+
@client.delete_dictionary_entries!('plurals', [entry_id])
|
439
|
+
assert_equal 0, @client.search_dictionary_entries('plurals', entry_id)[:nbHits]
|
440
|
+
end
|
441
|
+
|
442
|
+
def test_compounds_dictionaries
|
443
|
+
entry_id = SecureRandom.hex
|
444
|
+
assert_equal 0, @client.search_dictionary_entries('compounds', entry_id)[:nbHits]
|
445
|
+
|
446
|
+
entry = {
|
447
|
+
objectID: entry_id,
|
448
|
+
language: 'de',
|
449
|
+
word: 'kopfschmerztablette',
|
450
|
+
decomposition: %w(kopf schmerz tablette)
|
451
|
+
}
|
452
|
+
@client.save_dictionary_entries!('compounds', [entry])
|
453
|
+
|
454
|
+
compounds = @client.search_dictionary_entries('compounds', entry_id)
|
455
|
+
assert_equal 1, compounds[:nbHits]
|
456
|
+
assert_equal compounds[:hits][0][:objectID], entry[:objectID]
|
457
|
+
assert_equal compounds[:hits][0][:word], entry[:word]
|
458
|
+
assert_equal compounds[:hits][0][:decomposition], entry[:decomposition]
|
459
|
+
|
460
|
+
@client.delete_dictionary_entries!('compounds', [entry_id])
|
461
|
+
assert_equal 0, @client.search_dictionary_entries('compounds', entry_id)[:nbHits]
|
462
|
+
end
|
463
|
+
end
|
370
464
|
end
|
371
465
|
end
|
@@ -274,6 +274,9 @@ class SearchIndexTest < BaseTest
|
|
274
274
|
@index.set_settings!(settings)
|
275
275
|
|
276
276
|
assert_equal @index.get_settings, settings
|
277
|
+
|
278
|
+
# check that the forwardToReplicas parameter is passed correctly
|
279
|
+
assert @index.set_settings!(settings, { forwardToReplicas: true })
|
277
280
|
end
|
278
281
|
end
|
279
282
|
|
data/test/test_helper.rb
CHANGED
@@ -28,6 +28,22 @@ class Minitest::Test
|
|
28
28
|
@@search_client = Algolia::Search::Client.new(@@search_config)
|
29
29
|
end
|
30
30
|
|
31
|
+
def assert_requests(requester, requests)
|
32
|
+
refute_empty requests
|
33
|
+
refute_nil requester
|
34
|
+
|
35
|
+
actual_requests = requester.requests
|
36
|
+
assert_equal requests.size, actual_requests.size
|
37
|
+
|
38
|
+
requests.each_with_index do |expected_request, i|
|
39
|
+
request = actual_requests[i]
|
40
|
+
|
41
|
+
assert_equal(expected_request[:body], request[:body])
|
42
|
+
assert_equal(expected_request[:method], request[:method])
|
43
|
+
assert_equal(expected_request[:path], request[:path])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
31
47
|
def check_environment_variables
|
32
48
|
raise Algolia::AlgoliaError, 'ALGOLIA_APPLICATION_ID_1 must be defined' if ENV['ALGOLIA_APPLICATION_ID_1'].to_s.strip.empty?
|
33
49
|
raise Algolia::AlgoliaError, 'ALGOLIA_ADMIN_KEY_1 must be defined' if ENV['ALGOLIA_ADMIN_KEY_1'].to_s.strip.empty?
|
@@ -87,3 +103,15 @@ def rule_without_metadata(rule)
|
|
87
103
|
rule.delete(:_metadata)
|
88
104
|
rule
|
89
105
|
end
|
106
|
+
|
107
|
+
def retry_test(delay = 0.1, max_retries = 30)
|
108
|
+
(1...max_retries).each do |i|
|
109
|
+
begin
|
110
|
+
return yield
|
111
|
+
rescue Algolia::AlgoliaHttpError
|
112
|
+
sleep delay * i
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
raise StandardError, 'reached the maximum number of retries'
|
117
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: algolia
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Algolia
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -218,6 +218,8 @@ files:
|
|
218
218
|
- lib/algolia/config/analytics_config.rb
|
219
219
|
- lib/algolia/config/base_config.rb
|
220
220
|
- lib/algolia/config/insights_config.rb
|
221
|
+
- lib/algolia/config/personalization_config.rb
|
222
|
+
- lib/algolia/config/recommend_config.rb
|
221
223
|
- lib/algolia/config/recommendation_config.rb
|
222
224
|
- lib/algolia/config/search_config.rb
|
223
225
|
- lib/algolia/defaults.rb
|
@@ -234,10 +236,13 @@ files:
|
|
234
236
|
- lib/algolia/iterators/rule_iterator.rb
|
235
237
|
- lib/algolia/iterators/synonym_iterator.rb
|
236
238
|
- lib/algolia/logger_helper.rb
|
239
|
+
- lib/algolia/personalization_client.rb
|
240
|
+
- lib/algolia/recommend_client.rb
|
237
241
|
- lib/algolia/recommendation_client.rb
|
238
242
|
- lib/algolia/responses/add_api_key_response.rb
|
239
243
|
- lib/algolia/responses/base_response.rb
|
240
244
|
- lib/algolia/responses/delete_api_key_response.rb
|
245
|
+
- lib/algolia/responses/dictionary_response.rb
|
241
246
|
- lib/algolia/responses/indexing_response.rb
|
242
247
|
- lib/algolia/responses/multiple_batch_indexing_response.rb
|
243
248
|
- lib/algolia/responses/multiple_response.rb
|
@@ -273,6 +278,8 @@ files:
|
|
273
278
|
- test/algolia/integration/base_test.rb
|
274
279
|
- test/algolia/integration/insights_client_test.rb
|
275
280
|
- test/algolia/integration/mocks/mock_requester.rb
|
281
|
+
- test/algolia/integration/personalization_client_test.rb
|
282
|
+
- test/algolia/integration/recommend_client_test.rb
|
276
283
|
- test/algolia/integration/recommendation_client_test.rb
|
277
284
|
- test/algolia/integration/search_client_test.rb
|
278
285
|
- test/algolia/integration/search_index_test.rb
|
@@ -304,7 +311,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
304
311
|
- !ruby/object:Gem::Version
|
305
312
|
version: '0'
|
306
313
|
requirements: []
|
307
|
-
rubygems_version: 3.
|
314
|
+
rubygems_version: 3.0.6
|
308
315
|
signing_key:
|
309
316
|
specification_version: 4
|
310
317
|
summary: A simple Ruby client for the algolia.com REST API
|
@@ -314,6 +321,8 @@ test_files:
|
|
314
321
|
- test/algolia/integration/base_test.rb
|
315
322
|
- test/algolia/integration/insights_client_test.rb
|
316
323
|
- test/algolia/integration/mocks/mock_requester.rb
|
324
|
+
- test/algolia/integration/personalization_client_test.rb
|
325
|
+
- test/algolia/integration/recommend_client_test.rb
|
317
326
|
- test/algolia/integration/recommendation_client_test.rb
|
318
327
|
- test/algolia/integration/search_client_test.rb
|
319
328
|
- test/algolia/integration/search_index_test.rb
|