algolia 2.0.0.pre.alpha.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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +146 -0
  3. data/.github/ISSUE_TEMPLATE.md +20 -0
  4. data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  5. data/.gitignore +38 -0
  6. data/.rubocop.yml +186 -0
  7. data/.rubocop_todo.yml +14 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +18 -0
  10. data/LICENSE +21 -0
  11. data/README.md +56 -0
  12. data/Rakefile +45 -0
  13. data/Steepfile +6 -0
  14. data/algolia.gemspec +41 -0
  15. data/bin/console +21 -0
  16. data/bin/setup +8 -0
  17. data/lib/algolia.rb +42 -0
  18. data/lib/algolia/account_client.rb +65 -0
  19. data/lib/algolia/analytics_client.rb +105 -0
  20. data/lib/algolia/config/algolia_config.rb +40 -0
  21. data/lib/algolia/config/analytics_config.rb +20 -0
  22. data/lib/algolia/config/insights_config.rb +20 -0
  23. data/lib/algolia/config/recommendation_config.rb +20 -0
  24. data/lib/algolia/config/search_config.rb +40 -0
  25. data/lib/algolia/defaults.rb +35 -0
  26. data/lib/algolia/enums/call_type.rb +4 -0
  27. data/lib/algolia/enums/retry_outcome_type.rb +5 -0
  28. data/lib/algolia/error.rb +29 -0
  29. data/lib/algolia/helpers.rb +83 -0
  30. data/lib/algolia/http/http_requester.rb +84 -0
  31. data/lib/algolia/http/response.rb +23 -0
  32. data/lib/algolia/insights_client.rb +238 -0
  33. data/lib/algolia/iterators/base_iterator.rb +19 -0
  34. data/lib/algolia/iterators/object_iterator.rb +27 -0
  35. data/lib/algolia/iterators/paginator_iterator.rb +44 -0
  36. data/lib/algolia/iterators/rule_iterator.rb +9 -0
  37. data/lib/algolia/iterators/synonym_iterator.rb +9 -0
  38. data/lib/algolia/logger_helper.rb +14 -0
  39. data/lib/algolia/recommendation_client.rb +60 -0
  40. data/lib/algolia/responses/add_api_key_response.rb +38 -0
  41. data/lib/algolia/responses/base_response.rb +9 -0
  42. data/lib/algolia/responses/delete_api_key_response.rb +40 -0
  43. data/lib/algolia/responses/indexing_response.rb +28 -0
  44. data/lib/algolia/responses/multiple_batch_indexing_response.rb +29 -0
  45. data/lib/algolia/responses/multiple_response.rb +45 -0
  46. data/lib/algolia/responses/restore_api_key_response.rb +36 -0
  47. data/lib/algolia/responses/update_api_key_response.rb +39 -0
  48. data/lib/algolia/search_client.rb +614 -0
  49. data/lib/algolia/search_index.rb +1094 -0
  50. data/lib/algolia/transport/request_options.rb +94 -0
  51. data/lib/algolia/transport/retry_strategy.rb +117 -0
  52. data/lib/algolia/transport/stateful_host.rb +26 -0
  53. data/lib/algolia/transport/transport.rb +161 -0
  54. data/lib/algolia/user_agent.rb +25 -0
  55. data/lib/algolia/version.rb +3 -0
  56. data/sig/config/algolia_config.rbs +24 -0
  57. data/sig/config/analytics_config.rbs +11 -0
  58. data/sig/config/insights_config.rbs +11 -0
  59. data/sig/config/recommendation_config.rbs +11 -0
  60. data/sig/config/search_config.rbs +11 -0
  61. data/sig/enums/call_type.rbs +5 -0
  62. data/sig/helpers.rbs +12 -0
  63. data/sig/http/http_requester.rbs +17 -0
  64. data/sig/http/response.rbs +14 -0
  65. data/sig/interfaces/_connection.rbs +16 -0
  66. data/sig/iterators/base_iterator.rbs +15 -0
  67. data/sig/iterators/object_iterator.rbs +6 -0
  68. data/sig/iterators/paginator_iterator.rbs +8 -0
  69. data/sig/iterators/rule_iterator.rbs +5 -0
  70. data/sig/iterators/synonym_iterator.rbs +5 -0
  71. data/sig/transport/request_options.rbs +33 -0
  72. data/sig/transport/stateful_host.rbs +21 -0
  73. data/test/algolia/integration/account_client_test.rb +47 -0
  74. data/test/algolia/integration/analytics_client_test.rb +113 -0
  75. data/test/algolia/integration/base_test.rb +9 -0
  76. data/test/algolia/integration/insights_client_test.rb +80 -0
  77. data/test/algolia/integration/mocks/mock_requester.rb +45 -0
  78. data/test/algolia/integration/recommendation_client_test.rb +30 -0
  79. data/test/algolia/integration/search_client_test.rb +361 -0
  80. data/test/algolia/integration/search_index_test.rb +698 -0
  81. data/test/algolia/unit/helpers_test.rb +69 -0
  82. data/test/algolia/unit/retry_strategy_test.rb +139 -0
  83. data/test/algolia/unit/user_agent_test.rb +16 -0
  84. data/test/test_helper.rb +89 -0
  85. data/upgrade_guide.md +595 -0
  86. metadata +307 -0
@@ -0,0 +1,69 @@
1
+ require 'algolia'
2
+ require 'test_helper'
3
+
4
+ class HelpersTest
5
+ include Helpers
6
+
7
+ describe 'test helpers' do
8
+ def test_deserialize_settings
9
+ old_settings = {
10
+ attributesToIndex: %w(attr1 attr2),
11
+ numericAttributesToIndex: %w(attr1 attr2),
12
+ slaves: %w(index1 index2)
13
+ }
14
+
15
+ new_settings = {
16
+ searchableAttributes: %w(attr1 attr2),
17
+ numericAttributesForFiltering: %w(attr1 attr2),
18
+ replicas: %w(index1 index2)
19
+ }
20
+
21
+ deserialized_settings = deserialize_settings(old_settings)
22
+ assert_equal new_settings, deserialized_settings
23
+ end
24
+ end
25
+
26
+ describe 'test hash_includes_subset' do
27
+ def test_empty_hashes
28
+ h = {}
29
+ subset = {}
30
+ assert hash_includes_subset?(h, subset)
31
+ end
32
+
33
+ def test_with_empty_subset
34
+ h = { a: 100, b: 200 }
35
+ subset = {}
36
+ assert hash_includes_subset?(h, subset)
37
+ end
38
+
39
+ def test_subset_included
40
+ h = { a: 100, b: 200 }
41
+ subset = { a: 100 }
42
+ assert hash_includes_subset?(h, subset)
43
+ end
44
+
45
+ def test_subset_not_included
46
+ h = { a: 100, b: 200 }
47
+ subset = { c: 300 }
48
+ refute hash_includes_subset?(h, subset)
49
+ end
50
+
51
+ def test_subset_included_but_wrong_value
52
+ h = { a: 100, b: 200 }
53
+ subset = { a: 200 }
54
+ refute hash_includes_subset?(h, subset)
55
+ end
56
+
57
+ def test_subset_included_with_multiple_values
58
+ h = { a: 100, b: 200, c: 300 }
59
+ subset = { a: 100, b: 200 }
60
+ assert hash_includes_subset?(h, subset)
61
+ end
62
+
63
+ def test_subset_not_included_because_too_many_values
64
+ h = { a: 100, b: 200 }
65
+ subset = { a: 100, b: 200, c: 300 }
66
+ refute hash_includes_subset?(h, subset)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,139 @@
1
+ require 'algolia'
2
+ require 'test_helper'
3
+
4
+ class RetryStrategyTest
5
+ include RetryOutcomeType
6
+ include CallType
7
+ describe 'get tryable hosts' do
8
+ def before_all
9
+ super
10
+ @app_id = 'app_id'
11
+ @api_key = 'api_key'
12
+ stateful_hosts = []
13
+ stateful_hosts << "#{@app_id}-4.algolianet.com"
14
+ stateful_hosts << "#{@app_id}-5.algolianet.com"
15
+ stateful_hosts << "#{@app_id}-6.algolianet.com"
16
+ @config = Algolia::Search::Config.new(app_id: @app_id, api_key: @api_key, custom_hosts: stateful_hosts)
17
+ end
18
+
19
+ def test_resets_expired_hosts_according_to_read_type
20
+ @config.default_hosts[1].up = false
21
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
22
+
23
+ hosts = retry_strategy.get_tryable_hosts(READ)
24
+ assert_equal 2, hosts.length
25
+ end
26
+
27
+ def test_resets_expired_hosts_according_to_write_type
28
+ @config.default_hosts[1].up = false
29
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
30
+
31
+ hosts = retry_strategy.get_tryable_hosts(WRITE)
32
+ assert_equal 2, hosts.length
33
+ end
34
+
35
+ def test_resets_expired_hosts_according_to_read_type_with_timeout
36
+ @config.default_hosts[1].up = false
37
+ @config.default_hosts[1].last_use = Time.new.utc - 1000
38
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
39
+
40
+ hosts = retry_strategy.get_tryable_hosts(READ)
41
+ assert_equal 3, hosts.length
42
+ end
43
+
44
+ def test_resets_expired_hosts_according_to_write_type_with_timeout
45
+ @config.default_hosts[1].up = false
46
+ @config.default_hosts[1].last_use = Time.new.utc - 1000
47
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
48
+
49
+ hosts = retry_strategy.get_tryable_hosts(WRITE)
50
+ assert_equal 3, hosts.length
51
+ end
52
+
53
+ def test_resets_all_hosts_when_expired_according_to_read_type
54
+ 0.upto(2).map do |i|
55
+ @config.default_hosts[i].up = false
56
+ end
57
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
58
+
59
+ hosts = retry_strategy.get_tryable_hosts(READ)
60
+ assert_equal 3, hosts.length
61
+ end
62
+
63
+ def test_resets_all_hosts_when_expired_according_to_write_type
64
+ 0.upto(2).map do |i|
65
+ @config.default_hosts[i].up = false
66
+ end
67
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
68
+
69
+ hosts = retry_strategy.get_tryable_hosts(WRITE)
70
+ assert_equal 3, hosts.length
71
+ end
72
+ end
73
+
74
+ describe 'All hosts are unreachable' do
75
+ def test_failure_when_all_hosts_are_down
76
+ stateful_hosts = ['0.0.0.0']
77
+ @config = Algolia::Search::Config.new(app_id: 'foo', api_key: 'bar', custom_hosts: stateful_hosts)
78
+ client = Algolia::Search::Client.create_with_config(@config)
79
+ index = client.init_index(get_test_index_name('failure'))
80
+
81
+ exception = assert_raises Algolia::AlgoliaUnreachableHostError do
82
+ index.save_object({ objectID: 'one' })
83
+ end
84
+
85
+ assert_equal 'Unreachable hosts', exception.message
86
+ end
87
+ end
88
+
89
+ describe 'retry stategy decisions' do
90
+ def before_all
91
+ super
92
+ @app_id = 'app_id'
93
+ @api_key = 'api_key'
94
+ @config = Algolia::Search::Config.new(app_id: @app_id, api_key: @api_key)
95
+ @retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
96
+ @hosts = @retry_strategy.get_tryable_hosts(READ|WRITE)
97
+ end
98
+
99
+ def test_retry_decision_on_300
100
+ decision = @retry_strategy.decide(@hosts.first, http_response_code: 300)
101
+ assert_equal RETRY, decision
102
+ end
103
+
104
+ def test_retry_decision_on_500
105
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
106
+
107
+ decision = retry_strategy.decide(@hosts.first, http_response_code: 500)
108
+ assert_equal RETRY, decision
109
+ end
110
+
111
+ def test_retry_decision_on_timed_out
112
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
113
+
114
+ decision = retry_strategy.decide(@hosts.first, is_timed_out: true)
115
+ assert_equal RETRY, decision
116
+ end
117
+
118
+ def test_retry_decision_on_400
119
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
120
+
121
+ decision = retry_strategy.decide(@hosts.first, http_response_code: 400)
122
+ assert_equal FAILURE, decision
123
+ end
124
+
125
+ def test_retry_decision_on_404
126
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
127
+
128
+ decision = retry_strategy.decide(@hosts.first, http_response_code: 404)
129
+ assert_equal FAILURE, decision
130
+ end
131
+
132
+ def test_retry_decision_on_200
133
+ retry_strategy = Algolia::Transport::RetryStrategy.new(@config)
134
+
135
+ decision = retry_strategy.decide(@hosts.first, http_response_code: 200)
136
+ assert_equal SUCCESS, decision
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,16 @@
1
+ require 'algolia'
2
+ require 'test_helper'
3
+
4
+ class UserAgentTest
5
+ describe 'define user agent' do
6
+ def before_all
7
+ @default = "Algolia for Ruby (#{Algolia::VERSION}), Ruby (#{RUBY_VERSION})"
8
+ end
9
+
10
+ def test_add_user_agents
11
+ Algolia::UserAgent.add('Foo Bar', 'v1.0')
12
+ Algolia::UserAgent.add('Front Web', '2.0')
13
+ assert_equal(format('%<default>s; Foo Bar (v1.0); Front Web (2.0)', default: @default), Algolia::UserAgent.value)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,89 @@
1
+ require 'simplecov'
2
+
3
+ if ENV['COVERAGE']
4
+ SimpleCov.start
5
+ end
6
+
7
+ require 'bundler/setup'
8
+ require 'algolia'
9
+ require 'minitest/autorun'
10
+ require 'minitest/hooks'
11
+ require 'algolia/integration/mocks/mock_requester'
12
+
13
+ APPLICATION_ID_1 = ENV['ALGOLIA_APPLICATION_ID_1']
14
+ ADMIN_KEY_1 = ENV['ALGOLIA_ADMIN_KEY_1']
15
+ SEARCH_KEY_1 = ENV['ALGOLIA_SEARCH_KEY_1']
16
+ APPLICATION_ID_2 = ENV['ALGOLIA_APPLICATION_ID_2']
17
+ ADMIN_KEY_2 = ENV['ALGOLIA_ADMIN_KEY_2']
18
+ MCM_APPLICATION_ID = ENV['ALGOLIA_APPLICATION_ID_MCM']
19
+ MCM_ADMIN_KEY = ENV['ALGOLIA_ADMIN_KEY_MCM']
20
+ USER_AGENT = 'test-ruby'
21
+
22
+ class Minitest::Test
23
+ attr_reader :search_client
24
+
25
+ include Minitest::Hooks
26
+ include Helpers
27
+ @@search_config = Algolia::Search::Config.new(app_id: APPLICATION_ID_1, api_key: ADMIN_KEY_1, user_agent: USER_AGENT)
28
+ @@search_client = Algolia::Search::Client.new(@@search_config)
29
+ end
30
+
31
+ def check_environment_variables
32
+ raise Algolia::AlgoliaError, 'ALGOLIA_APPLICATION_ID_1 must be defined' if ENV['ALGOLIA_APPLICATION_ID_1'].to_s.strip.empty?
33
+ raise Algolia::AlgoliaError, 'ALGOLIA_ADMIN_KEY_1 must be defined' if ENV['ALGOLIA_ADMIN_KEY_1'].to_s.strip.empty?
34
+ raise Algolia::AlgoliaError, 'ALGOLIA_SEARCH_KEY_1 must be defined' if ENV['ALGOLIA_SEARCH_KEY_1'].to_s.strip.empty?
35
+ raise Algolia::AlgoliaError, 'ALGOLIA_APPLICATION_ID_2 must be defined' if ENV['ALGOLIA_APPLICATION_ID_2'].to_s.strip.empty?
36
+ raise Algolia::AlgoliaError, 'ALGOLIA_ADMIN_KEY_2 must be defined' if ENV['ALGOLIA_ADMIN_KEY_2'].to_s.strip.empty?
37
+ raise Algolia::AlgoliaError, 'ALGOLIA_APPLICATION_ID_MCM must be defined' if ENV['ALGOLIA_APPLICATION_ID_MCM'].to_s.strip.empty?
38
+ raise Algolia::AlgoliaError, 'ALGOLIA_ADMIN_KEY_MCM must be defined' if ENV['ALGOLIA_ADMIN_KEY_MCM'].to_s.strip.empty?
39
+ end
40
+
41
+ def get_test_index_name(name)
42
+ date = DateTime.now.strftime('%Y-%m-%d_%H_%M_%S')
43
+ user = ENV['USER'] || 'unknown'
44
+
45
+ instance = ENV['CI'].to_s == 'true' ? ENV['CIRCLE_BUILD_NUM'] : user
46
+
47
+ format('ruby_%<date>s_%<instance>s_%<name>s', date: date, instance: instance, name: name)
48
+ end
49
+
50
+ def get_mcm_user_name(user_id)
51
+ date = DateTime.now.strftime('%Y-%m-%d')
52
+ user = ENV['USER'] || 'unknown'
53
+
54
+ instance = ENV['CI'].to_s == 'true' ? ENV['CIRCLE_BUILD_NUM'] : user
55
+
56
+ format('ruby-%<date>s-%<instance>s-%<user_id>s', date: date, instance: instance, user_id: user_id)
57
+ end
58
+
59
+ def generate_object(object_id = nil)
60
+ object = { property: 'property' }
61
+ if object_id
62
+ object[:objectID] = object_id
63
+ end
64
+
65
+ object
66
+ end
67
+
68
+ def create_employee_records
69
+ [
70
+ { company: 'Algolia', name: 'Julien Lemoine', objectID: 'julien-lemoine' },
71
+ { company: 'Algolia', name: 'Nicolas Dessaigne', objectID: 'nicolas-dessaigne' },
72
+ { company: 'Amazon', name: 'Jeff Bezos' },
73
+ { company: 'Apple', name: 'Steve Jobs' },
74
+ { company: 'Apple', name: 'Steve Wozniak' },
75
+ { company: 'Arista Networks', name: 'Jayshree Ullal' },
76
+ { company: 'Google', name: 'Larry Page' },
77
+ { company: 'Google', name: 'Rob Pike' },
78
+ { company: 'Google', name: 'Serguey Brin' },
79
+ { company: 'Microsoft', name: 'Bill Gates' },
80
+ { company: 'SpaceX', name: 'Elon Musk' },
81
+ { company: 'Tesla', name: 'Elon Musk' },
82
+ { company: 'Yahoo', name: 'Marissa Mayer' }
83
+ ]
84
+ end
85
+
86
+ def rule_without_metadata(rule)
87
+ rule.delete(:_metadata)
88
+ rule
89
+ end
@@ -0,0 +1,595 @@
1
+ # Upgrading to v2 of the Ruby API client
2
+
3
+ ## Gem
4
+ First, you'll have to include the new version in your Gemfile. To do so, change the following:
5
+
6
+ ```diff
7
+ - gem 'algoliasearch'
8
+ + gem 'algolia', git: 'https://github.com/algolia/algoliasearch-client-ruby.git', tag: 'v2.0.0-alpha.1'
9
+ ```
10
+
11
+ Then, you'll need to change your current `require` statements:
12
+
13
+ ```diff
14
+ - require 'algoliasearch'
15
+ + require 'algolia'
16
+ ```
17
+
18
+ ## Class names
19
+ All classes have been namespaced. The mostly used classes are now as follows:
20
+
21
+ - `Algolia::Client` -> `Algolia::Search::Client`
22
+ - `Algolia::Index` -> `Algolia::Search::Index`
23
+ - `Algolia::AccountClient` -> `Algolia::Account::Client`
24
+ - `Algolia::Analytics` -> `Algolia::Analytics::Client`
25
+ - `Algolia::Insights` -> `Algolia::Insights::Client`
26
+
27
+ ## Initialize the client and index
28
+ There's a slight change in how you initialize the client. The index initialization didn't change.
29
+ ```ruby
30
+ # Before
31
+ client = Algolia::Client.new(
32
+ application_id: 'APP_ID',
33
+ api_key: 'API_KEY'
34
+ )
35
+ index = client.init_index('index_name')
36
+
37
+ # After
38
+ client = Algolia::Search::Client.create('APP_ID', 'API_KEY')
39
+ index = client.init_index('index_name')
40
+ # or
41
+ search_config = Algolia::Search::Config.new(app_id: app_id, api_key: api_key)
42
+ client = Algolia::Search::Client.create_with_config(search_config)
43
+ index = client.init_index('index_name')
44
+ ```
45
+
46
+ ## Search parameters and request options
47
+ The search parameters and request options are still optional, but they are combined into a single hash instead of two.
48
+ For example:
49
+ ```ruby
50
+ # Before
51
+ request_opts = { 'X-Algolia-UserToken': 'user123' }
52
+ search_params = { hitsPerPage: 50 }
53
+
54
+ index.search('query', search_params, request_opts)
55
+
56
+ # After
57
+ opts = {
58
+ :headers => {
59
+ 'X-Algolia-UserToken': 'user123'
60
+ },
61
+ :params => {
62
+ hitsPerPage: 50
63
+ }
64
+ }
65
+ index.search('query', opts)
66
+ ```
67
+
68
+ ## Methods
69
+
70
+ ### `Client`
71
+
72
+ #### `multiple_queries`
73
+ The `strategy` parameter is no longer a string, but a key in the `requestOptions`.
74
+ ```ruby
75
+ queries = [
76
+ { indexName: 'index_name1', params: { query: 'query', hitsPerPage: 2 } },
77
+ { indexName: 'index_name2', params: { query: 'another_query', hitsPerPage: 5 } }
78
+ ]
79
+
80
+ # Before
81
+ client.multiple_queries(queries, 'stopIfEnoughMatches')
82
+
83
+ # After
84
+ client.multiple_queries(queries, { strategy: 'stopIfEnoughMatches' })
85
+ ```
86
+
87
+ #### `copy_settings`
88
+ No change.
89
+
90
+ #### `list_indexes`
91
+ No change.
92
+
93
+ #### `copy_index`
94
+ No change.
95
+
96
+ #### `move_index`
97
+ No change.
98
+
99
+ #### `generate_secured_api_key`
100
+ This method is moved to the `Algolia::Search::Client` class.
101
+ ```ruby
102
+ # Before
103
+ secured_api_key = Algolia.generate_secured_api_key('api_key', {
104
+ validUntil: now - (10 * 60)
105
+ })
106
+
107
+ # After
108
+ secured_api_key = Algolia::Search::Client.generate_secured_api_key('api_key', {
109
+ validUntil: now - (10 * 60)
110
+ })
111
+ ```
112
+
113
+ #### `add_api_key`
114
+ `acl` is still the first parameter. The other parameters have been moved to the `requestOptions`.
115
+
116
+ ```ruby
117
+ # Before
118
+ client.add_api_key({ acl: ['search'], description: 'A description', indexes: ['index']})
119
+
120
+ # After
121
+ client.add_api_key(['search'], {
122
+ description: 'A description',
123
+ indexes: ['index']
124
+ })
125
+ ```
126
+
127
+ #### `update_api_key`
128
+ This method is moved to the `Algolia::Search::Client` class.
129
+ ```ruby
130
+ # Before
131
+ Algolia.update_api_key('api_key', { maxHitsPerQuery: 42 })
132
+
133
+ # After
134
+ client.update_api_key('api_key', { maxHitsPerQuery: 42 })
135
+ ```
136
+
137
+ #### `delete_api_key`
138
+ No change.
139
+
140
+ #### `restore_api_key`
141
+ No change.
142
+
143
+ #### `get_api_key`
144
+ No change.
145
+
146
+ #### `list_api_keys`
147
+ No change.
148
+
149
+ #### `get_secured_api_key_remaining_validity`
150
+ This method is moved to the `Algolia::Search::Client` class.
151
+ ```ruby
152
+ # Before
153
+ Algolia.get_secured_api_key_remaining_validity('api_key')
154
+
155
+ # After
156
+ Algolia::Search::Client.get_secured_api_key_remaining_validity('api_key')
157
+ ```
158
+
159
+ #### `copy_synonyms`
160
+ No change.
161
+
162
+ #### `assign_user_id`
163
+ No change.
164
+
165
+ #### `assign_user_ids`
166
+ Newly added method to add multiple userIDs to a cluster.
167
+ ```ruby
168
+ user_ids = ['1','2','3']
169
+
170
+ # Before
171
+ user_ids.each { |id| client.assign_user_id(id, 'my-cluster')}
172
+
173
+ # After
174
+ client.assign_user_ids(user_ids, 'my-cluster')
175
+ ```
176
+
177
+ #### `get_top_user_id`
178
+ No change.
179
+
180
+ #### `get_user_id`
181
+ No change.
182
+
183
+ #### `list_clusters`
184
+ No change.
185
+
186
+ #### `list_user_ids`
187
+ The `page` and `hitsPerPage` parameters are now part of the `requestOptions`.
188
+ ```ruby
189
+ # Before
190
+ page = 0
191
+ hits_per_page = 20
192
+ client.list_user_ids(page, hits_per_page)
193
+
194
+ # After
195
+ client.list_user_ids({ hitPerPage: 20, page: 0 })
196
+ ```
197
+
198
+ #### `remove_user_id`
199
+ No change.
200
+
201
+ #### `search_user_ids`
202
+ The `clusterName`, `page` and `hitsPerPage` parameters are now part of the `requestOptions`.
203
+ ```ruby
204
+ # Before
205
+ page = 0
206
+ hits_per_page = 12
207
+ client.search_user_ids('query', 'my-cluster', page, hits_per_page)
208
+
209
+ # After
210
+ client.search_user_ids('query', {clusterName: 'my-cluster', hitPerPage: 12, page: 0 })
211
+ ```
212
+
213
+ #### `pending_mappings`
214
+ New method to check the status of your clusters' migration or user creation.
215
+ ```ruby
216
+ client.pending_mapping?({ retrieveMappings: true })
217
+ ```
218
+
219
+ #### `get_logs`
220
+ The `offset`, `length`, and `type` parameters are now part of the `requestOptions`.
221
+ ```ruby
222
+ # Before
223
+ offset = 5
224
+ length = 100
225
+ puts client.get_logs(offset, length, 'all')
226
+
227
+ # After
228
+ client.get_logs({ offset: 5, length: 10, type: 'all' })
229
+ ```
230
+
231
+ #### `copy_rules`
232
+ No change.
233
+
234
+ ### `Index`
235
+ #### `search`
236
+ `searchParameters` and `requestOptions` are a single parameter now.
237
+
238
+ ```ruby
239
+ # Before
240
+ request_opts = { 'X-Algolia-UserToken': 'user123' }
241
+ search_params = { hitsPerPage: 50 }
242
+
243
+ index.search('query', search_params, request_opts)
244
+
245
+ # After
246
+ opts = {
247
+ :headers => {
248
+ 'X-Algolia-UserToken': 'user123'
249
+ },
250
+ :params => {
251
+ hitsPerPage: 50
252
+ }
253
+ }
254
+ index.search('query', opts)
255
+ ```
256
+
257
+ #### `search_for_facet_values`
258
+ `searchParameters` and `requestOptions` are a single parameter now.
259
+ ```ruby
260
+ # Before
261
+ request_opts = { 'X-Algolia-UserToken': 'user123' }
262
+ search_params = { hitsPerPage: 50 }
263
+
264
+ index.search_for_facet_values('category', 'phone', search_params, request_opts)
265
+
266
+ # After
267
+ opts = {
268
+ :headers => {
269
+ 'X-Algolia-UserToken': 'user123'
270
+ },
271
+ :params => {
272
+ hitsPerPage: 50
273
+ }
274
+ }
275
+ index.search_for_facet_values('category', 'phone', opts)
276
+ ```
277
+
278
+ #### `find_object`
279
+ The method takes a lambda, proc or block as the first argument (anything that responds to `call`), and the `requestOptions` as the second
280
+ ```ruby
281
+ # Before
282
+ index.find_object({query: 'query', paginate: true}) { |hit| hit[:title].include?('algolia') }
283
+
284
+ # After
285
+ index.find_object(-> (hit) { hit[:title].include?('algolia') }, { query: 'query', paginate: true })
286
+ ```
287
+
288
+ #### `get_object_position`
289
+ The classname has changed, not the method itself.
290
+ ```ruby
291
+ # Before
292
+ position = Algolia::Index.get_object_position(results, 'object')
293
+
294
+ # After
295
+ position = Algolia::Search::Index.get_object_position(results, 'object')
296
+ ```
297
+
298
+ #### `add_object` and `add_objects`
299
+ These methods have been removed in favor of `save_object` and `save_objects`.
300
+
301
+ #### `save_object` and `save_objects`
302
+ No change.
303
+
304
+ #### `partial_update_object`
305
+ The `objectID` parameter is removed. `create_if_not_exists` is now part of the `requestOptions` parameter.
306
+
307
+ ```ruby
308
+ obj = { objectID: '1234', prop: 'value' }
309
+
310
+ # Before
311
+ create_if_not_exists = true
312
+ index.partial_update_object(obj, obj[:objectID], create_if_not_exists)
313
+
314
+ # After
315
+ index.partial_update_object(obj, { createIfNotExists: true })
316
+ ```
317
+
318
+ #### `partial_update_objects`
319
+ The `create_if_not_exists` parameter is now part of the `requestOptions` parameter.
320
+
321
+ ```ruby
322
+ # Before
323
+ create_if_not_exists = true
324
+ index.partial_update_objects(objects, create_if_not_exists)
325
+
326
+ # After
327
+ index.partial_update_objects(objects, { createIfNotExists: true })
328
+ ```
329
+
330
+ #### `delete_object` and `delete_objects`
331
+ No change.
332
+
333
+ #### `replace_all_objects`
334
+ No change.
335
+
336
+ #### `delete_by`
337
+ No change.
338
+
339
+ #### `clear_index`
340
+ Renamed to `clear_objects`.
341
+ ```ruby
342
+ # Before
343
+ index.clear_index
344
+
345
+ # After
346
+ index.clear_objects
347
+ ```
348
+
349
+ #### `get_object` and `get_objects`
350
+ The `attributesToRetrieve` parameter is now part of the `requestOptions`.
351
+ ```ruby
352
+ # Before
353
+ index.get_object('1234', ['title'])
354
+ index.get_objects([1,2,3], ['title'])
355
+
356
+ # After
357
+ index.get_object('1234', { attributesToRetrieve: ['title'] })
358
+ index.get_objects([1,2,3], { attributesToRetrieve: ['title'] })
359
+ ```
360
+
361
+ #### `multiple_get_objects`
362
+ No change.
363
+
364
+ #### `batch`
365
+ No change.
366
+
367
+ #### `get_settings`
368
+ No change.
369
+
370
+ #### `set_settings`
371
+ No change.
372
+
373
+ #### `delete_index`
374
+ Instead of calling the `delete_index` method on the client, you should call the `delete` method directly on the index object.
375
+
376
+ ```ruby
377
+ # Before
378
+ client.delete_index('foo')
379
+
380
+ # After
381
+ index.delete
382
+ ```
383
+
384
+ #### `browse`
385
+ Renamed to `browse_objects`.
386
+ ```ruby
387
+ # Before
388
+ request_opts = { 'X-Algolia-UserToken': 'user123' }
389
+ index.browse({ query: 'query'}, nil, request_opts) do |hit|
390
+ puts hit
391
+ end
392
+
393
+ # After
394
+ opts = {
395
+ query: 'query',
396
+ headers: { 'X-Algolia-UserToken': 'user123' }
397
+ }
398
+ index.browse_objects(opts) do |hit|
399
+ puts hit
400
+ end
401
+ ```
402
+
403
+ #### `index.exists?`
404
+ No change.
405
+
406
+ #### `save_synonym`
407
+ The `objectID` parameter has been removed, and should be part of the synonym hash.
408
+ ```ruby
409
+ # Before
410
+ forward_to_replicas = true
411
+ index.save_synonym('one', { objectID: 'one', type: 'synonym', synonyms: %w(one two) }, forward_to_replicas)
412
+
413
+ # After
414
+ index.save_synonym({ objectID: 'one', type: 'synonym', synonyms: %w(one two) }, { forwardToReplicas: true})
415
+ ```
416
+
417
+ #### `batch_synonyms`
418
+ Renamed to `save_synonyms`. `forwardToReplicas` and `replaceExistingSynonyms` parmameters are now part of `requestOptions`.
419
+ ```ruby
420
+ # Before
421
+ forward_to_replicas = true
422
+ replace_existing_synonyms = true
423
+ index.batch_synonyms(synonyms, forward_to_replicas, replace_existing_synonyms)
424
+ # After
425
+ index.save_synonyms(synonyms, { forwardToReplicas: true, replaceExistingSynonyms: true })
426
+ ```
427
+
428
+ #### `delete_synonym`
429
+ No change.
430
+
431
+ #### `clear_synonyms`
432
+ No change.
433
+
434
+ #### `get_synonym`
435
+ No change.
436
+
437
+ #### `search_synonyms`
438
+ No change.
439
+
440
+ #### `replace_all_synonyms`
441
+ No change.
442
+
443
+ #### `export_synonyms`
444
+ Renamed to `browse_synonyms`.
445
+ ```ruby
446
+ # Before
447
+ synonyms = index.export_synonyms
448
+
449
+ # After
450
+ synonyms = index.browse_synonyms
451
+ ```
452
+
453
+ #### `save_rule`
454
+ The `objectID` parameter has been removed, and should be part of the Rule object.
455
+ ```ruby
456
+ # Before
457
+ index.save_rule('unique-id', {
458
+ objectID: 'unique-id',
459
+ condition: { anchoring: 'is', pattern: 'pattern' },
460
+ consequence: {
461
+ params: {
462
+ query: {
463
+ edits: [
464
+ { type: 'remove', delete: 'pattern' }
465
+ ]
466
+ }
467
+ }
468
+ }
469
+ })
470
+
471
+ # After
472
+ index.save_rule({
473
+ objectID: 'unique-id',
474
+ condition: { anchoring: 'is', pattern: 'pattern' },
475
+ consequence: {
476
+ params: {
477
+ query: {
478
+ edits: [
479
+ { type: 'remove', delete: 'pattern' }
480
+ ]
481
+ }
482
+ }
483
+ }
484
+ })
485
+ ```
486
+
487
+ #### `batch_rules`
488
+ Renamed to `save_rules`. The `forwardToReplicas` and `clearExistingRules` parameters should now be part of the `requestOptions`.
489
+ ```ruby
490
+ # Before
491
+ forward_to_replicas = true
492
+ clear_existing_rules = true
493
+ index.batch_rules(rules, forward_to_replicas, clear_existing_rules)
494
+
495
+ # After
496
+ index.save_rules(rules, { forwardToReplicas: true, clearExistingRules: true })
497
+ ```
498
+
499
+ #### `get_rule`
500
+ No change.
501
+
502
+ #### `delete_rule`
503
+ The `forwardToReplicas` parameter is now part of the `requestOptions`.
504
+ ```ruby
505
+ # Before
506
+ forward_to_replicas = true
507
+ index.delete_rule('rule-id', forward_to_replicas)
508
+
509
+ # After
510
+ index.delete_rule('rule-id', { forwardToReplicas: true })
511
+ ```
512
+
513
+ #### `clear_rules`
514
+ The `forwardToReplicas` parameter is now part of the `requestOptions`.
515
+ ```ruby
516
+ # Before
517
+ forward_to_replicas = true
518
+ index.clear_rules(forward_to_replicas)
519
+
520
+ # After
521
+ index.clear_rules({ forwardToReplicas: true })
522
+ ```
523
+
524
+ #### `search_rules`
525
+ No change.
526
+
527
+ #### `replace_all_rules`
528
+ No change.
529
+
530
+ #### `export_rules`
531
+ Renamed to `browse_rules`.
532
+
533
+ ### `AnalyticsClient`
534
+ #### `add_ab_test`
535
+ No change.
536
+
537
+ #### `get_ab_test`
538
+ No change.
539
+
540
+ #### `get_ab_tests`
541
+ No change.
542
+
543
+ #### `stop_ab_test`
544
+ No change.
545
+
546
+ #### `delete_ab_test`
547
+ No change.
548
+
549
+ ### `InsightsClient`
550
+ #### `clicked_object_ids_after_search`
551
+ No change.
552
+
553
+ #### `clicked_object_ids`
554
+ No change.
555
+
556
+ #### `clicked_filters`
557
+ No change.
558
+
559
+ #### `converted_object_ids_after_search`
560
+ No change.
561
+
562
+ #### `converted_object_ids`
563
+ No change.
564
+
565
+ #### `converted_filters`
566
+ No change.
567
+
568
+ #### `viewed_object_ids`
569
+ No change.
570
+
571
+ #### `viewed_filters`
572
+ No change.
573
+
574
+ ### `RecommendationClient`
575
+ #### `set_personalization_strategy`
576
+ This new method is available on the `Algolia::Recommendation::Client` class.
577
+
578
+ #### `set_personalization_strategy`
579
+ This new method is available on the `Algolia::Recommendation::Client` class.
580
+
581
+ ## Configuring timeouts
582
+ You can configure timeouts by passing a custom `Algolia::Search::Config` object to the constructor of your client.
583
+ ```ruby
584
+ # Before
585
+ client = Algolia::Client.new({
586
+ application_id: 'app_id',
587
+ api_key: 'api_key',
588
+ connect_timeout: 2,
589
+ receive_timeout: 10,
590
+ })
591
+
592
+ # After
593
+ search_config = Algolia::Search::Config.new(app_id: 'app_id', api_key: 'api_key', read_timeout: 10, connect_timeout: 2)
594
+ client = Algolia::Search::Client.create_with_config(search_config)
595
+ ```