yc-algoliasearch-rails 2.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/CHANGELOG.MD +566 -0
- data/Gemfile +38 -0
- data/Gemfile.lock +213 -0
- data/LICENSE +21 -0
- data/README.md +1171 -0
- data/Rakefile +17 -0
- data/algoliasearch-rails.gemspec +95 -0
- data/lib/algoliasearch/algolia_job.rb +9 -0
- data/lib/algoliasearch/configuration.rb +30 -0
- data/lib/algoliasearch/pagination/kaminari.rb +40 -0
- data/lib/algoliasearch/pagination/will_paginate.rb +15 -0
- data/lib/algoliasearch/pagination.rb +19 -0
- data/lib/algoliasearch/railtie.rb +11 -0
- data/lib/algoliasearch/tasks/algoliasearch.rake +19 -0
- data/lib/algoliasearch/utilities.rb +48 -0
- data/lib/algoliasearch/version.rb +3 -0
- data/lib/algoliasearch-rails.rb +1083 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/utilities_spec.rb +30 -0
- data/vendor/assets/javascripts/algolia/algoliasearch.angular.js +2678 -0
- data/vendor/assets/javascripts/algolia/algoliasearch.angular.min.js +7 -0
- data/vendor/assets/javascripts/algolia/algoliasearch.jquery.js +2678 -0
- data/vendor/assets/javascripts/algolia/algoliasearch.jquery.min.js +7 -0
- data/vendor/assets/javascripts/algolia/algoliasearch.js +2663 -0
- data/vendor/assets/javascripts/algolia/algoliasearch.min.js +7 -0
- data/vendor/assets/javascripts/algolia/bloodhound.js +727 -0
- data/vendor/assets/javascripts/algolia/bloodhound.min.js +7 -0
- data/vendor/assets/javascripts/algolia/typeahead.bundle.js +1782 -0
- data/vendor/assets/javascripts/algolia/typeahead.bundle.min.js +7 -0
- data/vendor/assets/javascripts/algolia/typeahead.jquery.js +1184 -0
- data/vendor/assets/javascripts/algolia/typeahead.jquery.min.js +7 -0
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.js +2678 -0
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.min.js +7 -0
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.js +2678 -0
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.min.js +7 -0
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.js +2663 -0
- data/vendor/assets/javascripts/algolia/v2/algoliasearch.min.js +7 -0
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.js +6277 -0
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.min.js +3 -0
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.js +6223 -0
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.min.js +3 -0
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.js +6070 -0
- data/vendor/assets/javascripts/algolia/v3/algoliasearch.min.js +3 -0
- metadata +174 -0
data/README.md
ADDED
@@ -0,0 +1,1171 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://www.algolia.com">
|
3
|
+
<img alt="Algolia for Rails" src="https://raw.githubusercontent.com/algolia/algoliasearch-client-common/master/banners/rails.png"/>
|
4
|
+
</a>
|
5
|
+
</p>
|
6
|
+
|
7
|
+
<h4 align="center">The perfect starting point to integrate <a href="https://algolia.com" target="_blank">Algolia</a> within your Rails project</h4>
|
8
|
+
|
9
|
+
<p align="center">
|
10
|
+
<a href="https://circleci.com/gh/algolia/algoliasearch-rails"><img src="https://circleci.com/gh/algolia/algoliasearch-rails.svg?style=shield" alt="CircleCI" /></a>
|
11
|
+
<a href="http://badge.fury.io/rb/algoliasearch-rails"><img src="https://badge.fury.io/rb/algoliasearch-rails.svg" alt="Gem Version"/></a>
|
12
|
+
<a href="https://codeclimate.com/github/algolia/algoliasearch-rails"><img src="https://codeclimate.com/github/algolia/algoliasearch-rails.svg" alt="Code Climate"/></a>
|
13
|
+
<img src="https://img.shields.io/badge/ActiveRecord-yes-blue.svg?style=flat-square" alt="ActiveRecord"/>
|
14
|
+
<img src="https://img.shields.io/badge/Mongoid-yes-blue.svg?style=flat-square" alt="Mongoid"/>
|
15
|
+
<img src="https://img.shields.io/badge/Sequel-yes-blue.svg?style=flat-square" alt="Sequel"/>
|
16
|
+
</p>
|
17
|
+
|
18
|
+
<p align="center">
|
19
|
+
<a href="https://www.algolia.com/doc/framework-integration/rails/getting-started/setup/?language=ruby" target="_blank">Documentation</a> •
|
20
|
+
<a href="https://discourse.algolia.com" target="_blank">Community Forum</a> •
|
21
|
+
<a href="http://stackoverflow.com/questions/tagged/algolia" target="_blank">Stack Overflow</a> •
|
22
|
+
<a href="https://github.com/algolia/algoliasearch-rails/issues" target="_blank">Report a bug</a> •
|
23
|
+
<a href="https://www.algolia.com/doc/framework-integration/rails/troubleshooting/faq/" target="_blank">FAQ</a> •
|
24
|
+
<a href="https://www.algolia.com/support" target="_blank">Support</a>
|
25
|
+
</p>
|
26
|
+
|
27
|
+
|
28
|
+
This gem let you easily integrate the Algolia Search API to your favorite ORM. It's based on the [algoliasearch-client-ruby](https://github.com/algolia/algoliasearch-client-ruby) gem.
|
29
|
+
Rails 5.x and 6.x are supported.
|
30
|
+
|
31
|
+
You might be interested in the sample Ruby on Rails application providing a `autocomplete.js`-based auto-completion and `InstantSearch.js`-based instant search results page: [algoliasearch-rails-example](https://github.com/algolia/algoliasearch-rails-example/).
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
## API Documentation
|
36
|
+
|
37
|
+
You can find the full reference on [Algolia's website](https://www.algolia.com/doc/framework-integration/rails/).
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
1. **[Setup](#setup)**
|
42
|
+
* [Install](#install)
|
43
|
+
* [Configuration](#configuration)
|
44
|
+
* [Timeouts](#timeouts)
|
45
|
+
* [Notes](#notes)
|
46
|
+
|
47
|
+
1. **[Usage](#usage)**
|
48
|
+
* [Index Schema](#index-schema)
|
49
|
+
* [Relevancy](#relevancy)
|
50
|
+
* [Indexing](#indexing)
|
51
|
+
* [Frontend Search (realtime experience)](#frontend-search-realtime-experience)
|
52
|
+
* [Backend Search](#backend-search)
|
53
|
+
* [Backend Pagination](#backend-pagination)
|
54
|
+
* [Tags](#tags)
|
55
|
+
* [Faceting](#faceting)
|
56
|
+
* [Faceted search](#faceted-search)
|
57
|
+
* [Group by](#group-by)
|
58
|
+
* [Geo-Search](#geo-search)
|
59
|
+
|
60
|
+
1. **[Options](#options)**
|
61
|
+
* [Auto-indexing & asynchronism](#auto-indexing--asynchronism)
|
62
|
+
* [Custom index name](#custom-index-name)
|
63
|
+
* [Per-environment indices](#per-environment-indices)
|
64
|
+
* [Custom attribute definition](#custom-attribute-definition)
|
65
|
+
* [Nested objects/relations](#nested-objectsrelations)
|
66
|
+
* [Custom <code>objectID</code>](#custom-objectid)
|
67
|
+
* [Restrict indexing to a subset of your data](#restrict-indexing-to-a-subset-of-your-data)
|
68
|
+
* [Sanitizer](#sanitizer)
|
69
|
+
* [UTF-8 Encoding](#utf-8-encoding)
|
70
|
+
* [Exceptions](#exceptions)
|
71
|
+
* [Configuration example](#configuration-example)
|
72
|
+
|
73
|
+
1. **[Indices](#indices)**
|
74
|
+
* [Manual indexing](#manual-indexing)
|
75
|
+
* [Manual removal](#manual-removal)
|
76
|
+
* [Reindexing](#reindexing)
|
77
|
+
* [Clearing an index](#clearing-an-index)
|
78
|
+
* [Using the underlying index](#using-the-underlying-index)
|
79
|
+
* [Primary/replica](#primaryreplica)
|
80
|
+
* [Share a single index](#share-a-single-index)
|
81
|
+
* [Target multiple indices](#target-multiple-indices)
|
82
|
+
|
83
|
+
1. **[Testing](#testing)**
|
84
|
+
* [Notes](#notes)
|
85
|
+
|
86
|
+
1. **[Troubleshooting](#troubleshooting)**
|
87
|
+
* [Frequently asked questions](#frequently-asked-questions)
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
# Setup
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
## Install
|
96
|
+
|
97
|
+
```sh
|
98
|
+
gem install algoliasearch-rails
|
99
|
+
```
|
100
|
+
|
101
|
+
Add the gem to your <code>Gemfile</code>:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
gem "algoliasearch-rails"
|
105
|
+
```
|
106
|
+
|
107
|
+
And run:
|
108
|
+
|
109
|
+
```sh
|
110
|
+
bundle install
|
111
|
+
```
|
112
|
+
|
113
|
+
## Configuration
|
114
|
+
|
115
|
+
Create a new file <code>config/initializers/algoliasearch.rb</code> to setup your <code>APPLICATION_ID</code> and <code>API_KEY</code>.
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
AlgoliaSearch.configuration = { application_id: 'YourApplicationID', api_key: 'YourAPIKey' }
|
119
|
+
```
|
120
|
+
|
121
|
+
The gem is compatible with [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord), [Mongoid](https://github.com/mongoid/mongoid) and [Sequel](https://github.com/jeremyevans/sequel).
|
122
|
+
|
123
|
+
## Timeouts
|
124
|
+
|
125
|
+
You can configure a various timeout thresholds by setting the following options at initialization time:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
AlgoliaSearch.configuration = {
|
129
|
+
application_id: 'YourApplicationID',
|
130
|
+
api_key: 'YourAPIKey',
|
131
|
+
connect_timeout: 2,
|
132
|
+
receive_timeout: 30,
|
133
|
+
send_timeout: 30,
|
134
|
+
batch_timeout: 120,
|
135
|
+
search_timeout: 5
|
136
|
+
}
|
137
|
+
```
|
138
|
+
|
139
|
+
## Notes
|
140
|
+
|
141
|
+
This gem makes extensive use of Rails' callbacks to trigger the indexing tasks. If you're using methods bypassing `after_validation`, `before_save` or `after_commit` callbacks, it will not index your changes. For example: `update_attribute` doesn't perform validations checks, to perform validations when updating use `update_attributes`.
|
142
|
+
|
143
|
+
All methods injected by the `AlgoliaSearch` module are prefixed by `algolia_` and aliased to the associated short names if they aren't already defined.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
Contact.algolia_reindex! # <=> Contact.reindex!
|
147
|
+
|
148
|
+
Contact.algolia_search("jon doe") # <=> Contact.search("jon doe")
|
149
|
+
```
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
# Usage
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
## Index Schema
|
158
|
+
|
159
|
+
The following code will create a <code>Contact</code> index and add search capabilities to your <code>Contact</code> model:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
class Contact < ActiveRecord::Base
|
163
|
+
include AlgoliaSearch
|
164
|
+
|
165
|
+
algoliasearch do
|
166
|
+
attributes :first_name, :last_name, :email
|
167
|
+
end
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
You can either specify the attributes to send (here we restricted to <code>:first_name, :last_name, :email</code>) or not (in that case, all attributes are sent).
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
class Product < ActiveRecord::Base
|
175
|
+
include AlgoliaSearch
|
176
|
+
|
177
|
+
algoliasearch do
|
178
|
+
# all attributes will be sent
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
You can also use the <code>add_attribute</code> method, to send all model attributes + extra ones:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
class Product < ActiveRecord::Base
|
187
|
+
include AlgoliaSearch
|
188
|
+
|
189
|
+
algoliasearch do
|
190
|
+
# all attributes + extra_attr will be sent
|
191
|
+
add_attribute :extra_attr
|
192
|
+
end
|
193
|
+
|
194
|
+
def extra_attr
|
195
|
+
"extra_val"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
200
|
+
## Relevancy
|
201
|
+
|
202
|
+
We provide many ways to configure your index allowing you to tune your overall index relevancy. The most important ones are the **searchable attributes** and the attributes reflecting **record popularity**.
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
class Product < ActiveRecord::Base
|
206
|
+
include AlgoliaSearch
|
207
|
+
|
208
|
+
algoliasearch do
|
209
|
+
# list of attribute used to build an Algolia record
|
210
|
+
attributes :title, :subtitle, :description, :likes_count, :seller_name
|
211
|
+
|
212
|
+
# the `searchableAttributes` (formerly known as attributesToIndex) setting defines the attributes
|
213
|
+
# you want to search in: here `title`, `subtitle` & `description`.
|
214
|
+
# You need to list them by order of importance. `description` is tagged as
|
215
|
+
# `unordered` to avoid taking the position of a match into account in that attribute.
|
216
|
+
searchableAttributes ['title', 'subtitle', 'unordered(description)']
|
217
|
+
|
218
|
+
# the `customRanking` setting defines the ranking criteria use to compare two matching
|
219
|
+
# records in case their text-relevance is equal. It should reflect your record popularity.
|
220
|
+
customRanking ['desc(likes_count)']
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
226
|
+
## Indexing
|
227
|
+
|
228
|
+
To index a model, simple call `reindex` on the class:
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
Product.reindex
|
232
|
+
```
|
233
|
+
|
234
|
+
To index all of your models, you can do something like this:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
Rails.application.eager_load! # Ensure all models are loaded (required in development).
|
238
|
+
|
239
|
+
algolia_models = ActiveRecord::Base.descendants.select{ |model| model.respond_to?(:reindex) }
|
240
|
+
|
241
|
+
algolia_models.each(&:reindex)
|
242
|
+
```
|
243
|
+
|
244
|
+
## Frontend Search (realtime experience)
|
245
|
+
|
246
|
+
Traditional search implementations tend to have search logic and functionality on the backend. This made sense when the search experience consisted of a user entering a search query, executing that search, and then being redirected to a search result page.
|
247
|
+
|
248
|
+
Implementing search on the backend is no longer necessary. In fact, in most cases it is harmful to performance because of added network and processing latency. We highly recommend the usage of our [JavaScript API Client](https://github.com/algolia/algoliasearch-client-javascript) issuing all search requests directly from the end user's browser, mobile device, or client. It will reduce the overall search latency while offloading your servers at the same time.
|
249
|
+
|
250
|
+
The JS API client is part of the gem, just require `algolia/v3/algoliasearch.min` somewhere in your JavaScript manifest, for example in `application.js` if you are using Rails 3.1+:
|
251
|
+
|
252
|
+
```javascript
|
253
|
+
//= require algolia/v3/algoliasearch.min
|
254
|
+
```
|
255
|
+
|
256
|
+
Then in your JavaScript code you can do:
|
257
|
+
|
258
|
+
```js
|
259
|
+
var client = algoliasearch(ApplicationID, Search-Only-API-Key);
|
260
|
+
var index = client.initIndex('YourIndexName');
|
261
|
+
index.search('something', { hitsPerPage: 10, page: 0 })
|
262
|
+
.then(function searchDone(content) {
|
263
|
+
console.log(content)
|
264
|
+
})
|
265
|
+
.catch(function searchFailure(err) {
|
266
|
+
console.error(err);
|
267
|
+
});
|
268
|
+
```
|
269
|
+
|
270
|
+
**We recently (March 2015) released a new version (V3) of our JavaScript client, if you were using our previous version (V2), [read the migration guide](https://github.com/algolia/algoliasearch-client-javascript/wiki/Migration-guide-from-2.x.x-to-3.x.x)**
|
271
|
+
|
272
|
+
## Backend Search
|
273
|
+
|
274
|
+
***Notes:*** We recommend the usage of our [JavaScript API Client](https://github.com/algolia/algoliasearch-client-javascript) to perform queries directly from the end-user browser without going through your server.
|
275
|
+
|
276
|
+
A search returns ORM-compliant objects reloading them from your database. We recommend the usage of our [JavaScript API Client](https://github.com/algolia/algoliasearch-client-javascript) to perform queries to decrease the overall latency and offload your servers.
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
hits = Contact.search("jon doe")
|
280
|
+
p hits
|
281
|
+
p hits.raw_answer # to get the original JSON raw answer
|
282
|
+
```
|
283
|
+
|
284
|
+
A `highlight_result` attribute is added to each ORM object:
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
hits[0].highlight_result['first_name']['value']
|
288
|
+
```
|
289
|
+
|
290
|
+
If you want to retrieve the raw JSON answer from the API, without re-loading the objects from the database, you can use:
|
291
|
+
|
292
|
+
```ruby
|
293
|
+
json_answer = Contact.raw_search("jon doe")
|
294
|
+
p json_answer
|
295
|
+
p json_answer['hits']
|
296
|
+
p json_answer['facets']
|
297
|
+
```
|
298
|
+
|
299
|
+
Search parameters can be specified either through the index's [settings](https://github.com/algolia/algoliasearch-client-ruby#index-settings-parameters) statically in your model or dynamically at search time specifying [search parameters](https://github.com/algolia/algoliasearch-client-ruby#search) as second argument of the `search` method:
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
class Contact < ActiveRecord::Base
|
303
|
+
include AlgoliaSearch
|
304
|
+
|
305
|
+
algoliasearch do
|
306
|
+
attribute :first_name, :last_name, :email
|
307
|
+
|
308
|
+
# default search parameters stored in the index settings
|
309
|
+
minWordSizefor1Typo 4
|
310
|
+
minWordSizefor2Typos 8
|
311
|
+
hitsPerPage 42
|
312
|
+
end
|
313
|
+
end
|
314
|
+
```
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
# dynamical search parameters
|
318
|
+
p Contact.raw_search('jon doe', { hitsPerPage: 5, page: 2 })
|
319
|
+
```
|
320
|
+
|
321
|
+
## Backend Pagination
|
322
|
+
|
323
|
+
Even if we **highly recommend to perform all search (and therefore pagination) operations from your frontend using JavaScript**, we support both [will_paginate](https://github.com/mislav/will_paginate) and [kaminari](https://github.com/amatsuda/kaminari) as pagination backend.
|
324
|
+
|
325
|
+
To use <code>:will_paginate</code>, specify the <code>:pagination_backend</code> as follow:
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
AlgoliaSearch.configuration = { application_id: 'YourApplicationID', api_key: 'YourAPIKey', pagination_backend: :will_paginate }
|
329
|
+
```
|
330
|
+
|
331
|
+
Then, as soon as you use the `search` method, the returning results will be a paginated set:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
# in your controller
|
335
|
+
@results = MyModel.search('foo', hitsPerPage: 10)
|
336
|
+
|
337
|
+
# in your views
|
338
|
+
# if using will_paginate
|
339
|
+
<%= will_paginate @results %>
|
340
|
+
|
341
|
+
# if using kaminari
|
342
|
+
<%= paginate @results %>
|
343
|
+
```
|
344
|
+
|
345
|
+
## Tags
|
346
|
+
|
347
|
+
Use the <code>tags</code> method to add tags to your record:
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
class Contact < ActiveRecord::Base
|
351
|
+
include AlgoliaSearch
|
352
|
+
|
353
|
+
algoliasearch do
|
354
|
+
tags ['trusted']
|
355
|
+
end
|
356
|
+
end
|
357
|
+
```
|
358
|
+
|
359
|
+
or using dynamical values:
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
class Contact < ActiveRecord::Base
|
363
|
+
include AlgoliaSearch
|
364
|
+
|
365
|
+
algoliasearch do
|
366
|
+
tags do
|
367
|
+
[first_name.blank? || last_name.blank? ? 'partial' : 'full', has_valid_email? ? 'valid_email' : 'invalid_email']
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
```
|
372
|
+
|
373
|
+
At query time, specify <code>{ tagFilters: 'tagvalue' }</code> or <code>{ tagFilters: ['tagvalue1', 'tagvalue2'] }</code> as search parameters to restrict the result set to specific tags.
|
374
|
+
|
375
|
+
## Faceting
|
376
|
+
|
377
|
+
Facets can be retrieved calling the extra `facets` method of the search answer.
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
class Contact < ActiveRecord::Base
|
381
|
+
include AlgoliaSearch
|
382
|
+
|
383
|
+
algoliasearch do
|
384
|
+
# [...]
|
385
|
+
|
386
|
+
# specify the list of attributes available for faceting
|
387
|
+
attributesForFaceting [:company, :zip_code]
|
388
|
+
end
|
389
|
+
end
|
390
|
+
```
|
391
|
+
|
392
|
+
```ruby
|
393
|
+
hits = Contact.search('jon doe', { facets: '*' })
|
394
|
+
p hits # ORM-compliant array of objects
|
395
|
+
p hits.facets # extra method added to retrieve facets
|
396
|
+
p hits.facets['company'] # facet values+count of facet 'company'
|
397
|
+
p hits.facets['zip_code'] # facet values+count of facet 'zip_code'
|
398
|
+
```
|
399
|
+
|
400
|
+
```ruby
|
401
|
+
raw_json = Contact.raw_search('jon doe', { facets: '*' })
|
402
|
+
p raw_json['facets']
|
403
|
+
```
|
404
|
+
|
405
|
+
## Faceted search
|
406
|
+
|
407
|
+
You can also search for facet values.
|
408
|
+
|
409
|
+
```ruby
|
410
|
+
Product.search_for_facet_values('category', 'Headphones') # Array of {value, highlighted, count}
|
411
|
+
```
|
412
|
+
|
413
|
+
This method can also take any parameter a query can take.
|
414
|
+
This will adjust the search to only hits which would have matched the query.
|
415
|
+
|
416
|
+
```ruby
|
417
|
+
# Only sends back the categories containing red Apple products (and only counts those)
|
418
|
+
Product.search_for_facet_values('category', 'phone', {
|
419
|
+
query: 'red',
|
420
|
+
filters: 'brand:Apple'
|
421
|
+
}) # Array of phone categories linked to red Apple products
|
422
|
+
```
|
423
|
+
|
424
|
+
## Group by
|
425
|
+
|
426
|
+
More info on distinct for grouping can be found
|
427
|
+
[here](https://www.algolia.com/doc/guides/managing-results/refine-results/grouping/).
|
428
|
+
|
429
|
+
```ruby
|
430
|
+
class Contact < ActiveRecord::Base
|
431
|
+
include AlgoliaSearch
|
432
|
+
|
433
|
+
algoliasearch do
|
434
|
+
# [...]
|
435
|
+
|
436
|
+
# specify the attribute to be used for distinguishing the records
|
437
|
+
# in this case the records will be grouped by company
|
438
|
+
attributeForDistinct "company"
|
439
|
+
end
|
440
|
+
end
|
441
|
+
```
|
442
|
+
|
443
|
+
## Geo-Search
|
444
|
+
|
445
|
+
Use the <code>geoloc</code> method to localize your record:
|
446
|
+
|
447
|
+
```ruby
|
448
|
+
class Contact < ActiveRecord::Base
|
449
|
+
include AlgoliaSearch
|
450
|
+
|
451
|
+
algoliasearch do
|
452
|
+
geoloc :lat_attr, :lng_attr
|
453
|
+
end
|
454
|
+
end
|
455
|
+
```
|
456
|
+
|
457
|
+
At query time, specify <code>{ aroundLatLng: "37.33, -121.89", aroundRadius: 50000 }</code> as search parameters to restrict the result set to 50KM around San Jose.
|
458
|
+
|
459
|
+
|
460
|
+
|
461
|
+
# Options
|
462
|
+
|
463
|
+
|
464
|
+
|
465
|
+
## Auto-indexing & asynchronism
|
466
|
+
|
467
|
+
Each time a record is saved, it will be *asynchronously* indexed. On the other hand, each time a record is destroyed, it will be - asynchronously - removed from the index. That means that a network call with the ADD/DELETE operation is sent **synchronously** to the Algolia API but then the engine will **asynchronously** process the operation (so if you do a search just after, the results may not reflect it yet).
|
468
|
+
|
469
|
+
You can disable auto-indexing and auto-removing setting the following options:
|
470
|
+
|
471
|
+
```ruby
|
472
|
+
class Contact < ActiveRecord::Base
|
473
|
+
include AlgoliaSearch
|
474
|
+
|
475
|
+
algoliasearch auto_index: false, auto_remove: false do
|
476
|
+
attribute :first_name, :last_name, :email
|
477
|
+
end
|
478
|
+
end
|
479
|
+
```
|
480
|
+
|
481
|
+
### Temporary disable auto-indexing
|
482
|
+
|
483
|
+
You can temporary disable auto-indexing using the <code>without_auto_index</code> scope. This is often used for performance reason.
|
484
|
+
|
485
|
+
```ruby
|
486
|
+
Contact.delete_all
|
487
|
+
Contact.without_auto_index do
|
488
|
+
1.upto(10000) { Contact.create! attributes } # inside this block, auto indexing task will not run.
|
489
|
+
end
|
490
|
+
Contact.reindex! # will use batch operations
|
491
|
+
```
|
492
|
+
|
493
|
+
### Queues & background jobs
|
494
|
+
|
495
|
+
You can configure the auto-indexing & auto-removal process to use a queue to perform those operations in background. ActiveJob (Rails >=4.2) queues are used by default but you can define your own queuing mechanism:
|
496
|
+
|
497
|
+
```ruby
|
498
|
+
class Contact < ActiveRecord::Base
|
499
|
+
include AlgoliaSearch
|
500
|
+
|
501
|
+
algoliasearch enqueue: true do # ActiveJob will be triggered using a `algoliasearch` queue
|
502
|
+
attribute :first_name, :last_name, :email
|
503
|
+
end
|
504
|
+
end
|
505
|
+
```
|
506
|
+
|
507
|
+
### Things to Consider
|
508
|
+
|
509
|
+
If you are performing updates & deletions in the background then a record deletion can be committed to your database prior
|
510
|
+
to the job actually executing. Thus if you were to load the record to remove it from the database than your ActiveRecord#find will fail with a RecordNotFound.
|
511
|
+
|
512
|
+
In this case you can bypass loading the record from ActiveRecord and just communicate with the index directly:
|
513
|
+
|
514
|
+
```ruby
|
515
|
+
class MySidekiqWorker
|
516
|
+
def perform(id, remove)
|
517
|
+
if remove
|
518
|
+
# the record has likely already been removed from your database so we cannot
|
519
|
+
# use ActiveRecord#find to load it
|
520
|
+
index = Algolia::Index.new("index_name")
|
521
|
+
index.delete_object(id)
|
522
|
+
else
|
523
|
+
# the record should be present
|
524
|
+
c = Contact.find(id)
|
525
|
+
c.index!
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
```
|
530
|
+
|
531
|
+
### With Sidekiq
|
532
|
+
|
533
|
+
If you're using [Sidekiq](https://github.com/mperham/sidekiq):
|
534
|
+
|
535
|
+
```ruby
|
536
|
+
class Contact < ActiveRecord::Base
|
537
|
+
include AlgoliaSearch
|
538
|
+
|
539
|
+
algoliasearch enqueue: :trigger_sidekiq_worker do
|
540
|
+
attribute :first_name, :last_name, :email
|
541
|
+
end
|
542
|
+
|
543
|
+
def self.trigger_sidekiq_worker(record, remove)
|
544
|
+
MySidekiqWorker.perform_async(record.id, remove)
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
class MySidekiqWorker
|
549
|
+
def perform(id, remove)
|
550
|
+
if remove
|
551
|
+
# the record has likely already been removed from your database so we cannot
|
552
|
+
# use ActiveRecord#find to load it
|
553
|
+
index = Algolia::Index.new("index_name")
|
554
|
+
index.delete_object(id)
|
555
|
+
else
|
556
|
+
# the record should be present
|
557
|
+
c = Contact.find(id)
|
558
|
+
c.index!
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
```
|
563
|
+
|
564
|
+
### With DelayedJob
|
565
|
+
|
566
|
+
If you're using [delayed_job](https://github.com/collectiveidea/delayed_job):
|
567
|
+
|
568
|
+
```ruby
|
569
|
+
class Contact < ActiveRecord::Base
|
570
|
+
include AlgoliaSearch
|
571
|
+
|
572
|
+
algoliasearch enqueue: :trigger_delayed_job do
|
573
|
+
attribute :first_name, :last_name, :email
|
574
|
+
end
|
575
|
+
|
576
|
+
def self.trigger_delayed_job(record, remove)
|
577
|
+
if remove
|
578
|
+
record.delay.remove_from_index!
|
579
|
+
else
|
580
|
+
record.delay.index!
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
```
|
586
|
+
|
587
|
+
### Synchronism & testing
|
588
|
+
|
589
|
+
You can force indexing and removing to be synchronous (in that case the gem will call the `wait_task` method to ensure the operation has been taken into account once the method returns) by setting the following option: (this is **NOT** recommended, except for testing purpose)
|
590
|
+
|
591
|
+
```ruby
|
592
|
+
class Contact < ActiveRecord::Base
|
593
|
+
include AlgoliaSearch
|
594
|
+
|
595
|
+
algoliasearch synchronous: true do
|
596
|
+
attribute :first_name, :last_name, :email
|
597
|
+
end
|
598
|
+
end
|
599
|
+
```
|
600
|
+
|
601
|
+
## Custom index name
|
602
|
+
|
603
|
+
By default, the index name will be the class name, e.g. "Contact". You can customize the index name by using the `index_name` option:
|
604
|
+
|
605
|
+
```ruby
|
606
|
+
class Contact < ActiveRecord::Base
|
607
|
+
include AlgoliaSearch
|
608
|
+
|
609
|
+
algoliasearch index_name: "MyCustomName" do
|
610
|
+
attribute :first_name, :last_name, :email
|
611
|
+
end
|
612
|
+
end
|
613
|
+
```
|
614
|
+
|
615
|
+
## Per-environment indices
|
616
|
+
|
617
|
+
You can suffix the index name with the current Rails environment using the following option:
|
618
|
+
|
619
|
+
```ruby
|
620
|
+
class Contact < ActiveRecord::Base
|
621
|
+
include AlgoliaSearch
|
622
|
+
|
623
|
+
algoliasearch per_environment: true do # index name will be "Contact_#{Rails.env}"
|
624
|
+
attribute :first_name, :last_name, :email
|
625
|
+
end
|
626
|
+
end
|
627
|
+
```
|
628
|
+
|
629
|
+
## Custom attribute definition
|
630
|
+
|
631
|
+
You can use a block to specify a complex attribute value
|
632
|
+
|
633
|
+
```ruby
|
634
|
+
class Contact < ActiveRecord::Base
|
635
|
+
include AlgoliaSearch
|
636
|
+
|
637
|
+
algoliasearch do
|
638
|
+
attribute :email
|
639
|
+
attribute :full_name do
|
640
|
+
"#{first_name} #{last_name}"
|
641
|
+
end
|
642
|
+
add_attribute :full_name2
|
643
|
+
end
|
644
|
+
|
645
|
+
def full_name2
|
646
|
+
"#{first_name} #{last_name}"
|
647
|
+
end
|
648
|
+
end
|
649
|
+
```
|
650
|
+
|
651
|
+
***Notes:*** As soon as you use such code to define extra attributes, the gem is not anymore able to detect if the attribute has changed (the code uses Rails's `#{attribute}_changed?` method to detect that). As a consequence, your record will be pushed to the API even if its attributes didn't change. You can work-around this behavior creating a `_changed?` method:
|
652
|
+
|
653
|
+
```ruby
|
654
|
+
class Contact < ActiveRecord::Base
|
655
|
+
include AlgoliaSearch
|
656
|
+
|
657
|
+
algoliasearch do
|
658
|
+
attribute :email
|
659
|
+
attribute :full_name do
|
660
|
+
"#{first_name} #{last_name}"
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
def full_name_changed?
|
665
|
+
first_name_changed? || last_name_changed?
|
666
|
+
end
|
667
|
+
end
|
668
|
+
```
|
669
|
+
|
670
|
+
## Nested objects/relations
|
671
|
+
|
672
|
+
### Defining the relationship
|
673
|
+
|
674
|
+
You can easily embed nested objects defining an extra attribute returning any JSON-compliant object (an array or a hash or a combination of both).
|
675
|
+
|
676
|
+
```ruby
|
677
|
+
class Profile < ActiveRecord::Base
|
678
|
+
include AlgoliaSearch
|
679
|
+
|
680
|
+
belongs_to :user
|
681
|
+
has_many :specializations
|
682
|
+
|
683
|
+
algoliasearch do
|
684
|
+
attribute :user do
|
685
|
+
# restrict the nested "user" object to its `name` + `email`
|
686
|
+
{ name: user.name, email: user.email }
|
687
|
+
end
|
688
|
+
attribute :public_specializations do
|
689
|
+
# build an array of public specialization (include only `title` and `another_attr`)
|
690
|
+
specializations.select { |s| s.public? }.map do |s|
|
691
|
+
{ title: s.title, another_attr: s.another_attr }
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
end
|
697
|
+
```
|
698
|
+
|
699
|
+
### Propagating the change from a nested child
|
700
|
+
|
701
|
+
#### With ActiveRecord
|
702
|
+
|
703
|
+
With ActiveRecord, we'll be using `touch` and `after_touch` to achieve this.
|
704
|
+
|
705
|
+
```ruby
|
706
|
+
# app/models/app.rb
|
707
|
+
class App < ApplicationRecord
|
708
|
+
include AlgoliaSearch
|
709
|
+
|
710
|
+
belongs_to :author, class_name: :User
|
711
|
+
after_touch :index!
|
712
|
+
|
713
|
+
algoliasearch do
|
714
|
+
attribute :title
|
715
|
+
attribute :author do
|
716
|
+
author.as_json
|
717
|
+
end
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
# app/models/user.rb
|
722
|
+
class User < ApplicationRecord
|
723
|
+
# If your association uses belongs_to
|
724
|
+
# - use `touch: true`
|
725
|
+
# - do not define an `after_save` hook
|
726
|
+
has_many :apps, foreign_key: :author_id
|
727
|
+
|
728
|
+
after_save { apps.each(&:touch) }
|
729
|
+
end
|
730
|
+
```
|
731
|
+
|
732
|
+
#### With Sequel
|
733
|
+
|
734
|
+
With Sequel, you can use the `touch` plugin to propagate the changes:
|
735
|
+
|
736
|
+
```ruby
|
737
|
+
# app/models/app.rb
|
738
|
+
class App < Sequel::Model
|
739
|
+
include AlgoliaSearch
|
740
|
+
|
741
|
+
many_to_one :author, class: :User
|
742
|
+
|
743
|
+
plugin :timestamps
|
744
|
+
plugin :touch
|
745
|
+
|
746
|
+
algoliasearch do
|
747
|
+
attribute :title
|
748
|
+
attribute :author do
|
749
|
+
author.to_hash
|
750
|
+
end
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
# app/models/user.rb
|
755
|
+
class User < Sequel::Model
|
756
|
+
one_to_many :apps, key: :author_id
|
757
|
+
|
758
|
+
plugin :timestamps
|
759
|
+
# Can't use the associations since it won't trigger the after_save
|
760
|
+
plugin :touch
|
761
|
+
|
762
|
+
# Define the associations that need to be touched here
|
763
|
+
# Less performant, but allows for the after_save hook to trigger
|
764
|
+
def touch_associations
|
765
|
+
apps.map(&:touch)
|
766
|
+
end
|
767
|
+
|
768
|
+
def touch
|
769
|
+
super
|
770
|
+
touch_associations
|
771
|
+
end
|
772
|
+
end
|
773
|
+
```
|
774
|
+
|
775
|
+
## Custom `objectID`
|
776
|
+
|
777
|
+
By default, the `objectID` is based on your record's `id`. You can change this behavior specifying the `:id` option (be sure to use a uniq field).
|
778
|
+
|
779
|
+
```ruby
|
780
|
+
class UniqUser < ActiveRecord::Base
|
781
|
+
include AlgoliaSearch
|
782
|
+
|
783
|
+
algoliasearch id: :uniq_name do
|
784
|
+
end
|
785
|
+
end
|
786
|
+
```
|
787
|
+
|
788
|
+
## Restrict indexing to a subset of your data
|
789
|
+
|
790
|
+
You can add constraints controlling if a record must be indexed by using options the `:if` or `:unless` options.
|
791
|
+
|
792
|
+
It allows you to do conditional indexing on a per document basis.
|
793
|
+
|
794
|
+
```ruby
|
795
|
+
class Post < ActiveRecord::Base
|
796
|
+
include AlgoliaSearch
|
797
|
+
|
798
|
+
algoliasearch if: :published?, unless: :deleted? do
|
799
|
+
end
|
800
|
+
|
801
|
+
def published?
|
802
|
+
# [...]
|
803
|
+
end
|
804
|
+
|
805
|
+
def deleted?
|
806
|
+
# [...]
|
807
|
+
end
|
808
|
+
end
|
809
|
+
```
|
810
|
+
|
811
|
+
**Notes:** As soon as you use those constraints, `addObjects` and `deleteObjects` calls will be performed in order to keep the index synced with the DB (The state-less gem doesn't know if the object don't match your constraints anymore or never matched, so we force ADD/DELETE operations to be sent). You can work-around this behavior creating a `_changed?` method:
|
812
|
+
|
813
|
+
```ruby
|
814
|
+
class Contact < ActiveRecord::Base
|
815
|
+
include AlgoliaSearch
|
816
|
+
|
817
|
+
algoliasearch if: :published do
|
818
|
+
end
|
819
|
+
|
820
|
+
def published
|
821
|
+
# true or false
|
822
|
+
end
|
823
|
+
|
824
|
+
def published_changed?
|
825
|
+
# return true only if you know that the 'published' state changed
|
826
|
+
end
|
827
|
+
end
|
828
|
+
```
|
829
|
+
|
830
|
+
You can index a subset of your records using either:
|
831
|
+
|
832
|
+
```ruby
|
833
|
+
# will generate batch API calls (recommended)
|
834
|
+
MyModel.where('updated_at > ?', 10.minutes.ago).reindex!
|
835
|
+
```
|
836
|
+
|
837
|
+
or
|
838
|
+
|
839
|
+
```ruby
|
840
|
+
MyModel.index_objects MyModel.limit(5)
|
841
|
+
```
|
842
|
+
|
843
|
+
## Sanitizer
|
844
|
+
|
845
|
+
You can sanitize all your attributes using the `sanitize` option. It will strip all HTML tags from your attributes.
|
846
|
+
|
847
|
+
```ruby
|
848
|
+
class User < ActiveRecord::Base
|
849
|
+
include AlgoliaSearch
|
850
|
+
|
851
|
+
algoliasearch per_environment: true, sanitize: true do
|
852
|
+
attributes :name, :email, :company
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
856
|
+
```
|
857
|
+
|
858
|
+
If you're using Rails 4.2+, you also need to depend on `rails-html-sanitizer`:
|
859
|
+
|
860
|
+
```ruby
|
861
|
+
gem 'rails-html-sanitizer'
|
862
|
+
```
|
863
|
+
|
864
|
+
## UTF-8 Encoding
|
865
|
+
|
866
|
+
You can force the UTF-8 encoding of all your attributes using the `force_utf8_encoding` option:
|
867
|
+
|
868
|
+
```ruby
|
869
|
+
class User < ActiveRecord::Base
|
870
|
+
include AlgoliaSearch
|
871
|
+
|
872
|
+
algoliasearch force_utf8_encoding: true do
|
873
|
+
attributes :name, :email, :company
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
877
|
+
```
|
878
|
+
|
879
|
+
***Notes:*** This option is not compatible with Ruby 1.8
|
880
|
+
|
881
|
+
## Exceptions
|
882
|
+
|
883
|
+
You can disable exceptions that could be raised while trying to reach Algolia's API by using the `raise_on_failure` option:
|
884
|
+
|
885
|
+
```ruby
|
886
|
+
class Contact < ActiveRecord::Base
|
887
|
+
include AlgoliaSearch
|
888
|
+
|
889
|
+
# only raise exceptions in development env
|
890
|
+
algoliasearch raise_on_failure: Rails.env.development? do
|
891
|
+
attribute :first_name, :last_name, :email
|
892
|
+
end
|
893
|
+
end
|
894
|
+
```
|
895
|
+
|
896
|
+
## Configuration example
|
897
|
+
|
898
|
+
Here is a real-word configuration example (from [HN Search](https://github.com/algolia/hn-search)):
|
899
|
+
|
900
|
+
```ruby
|
901
|
+
class Item < ActiveRecord::Base
|
902
|
+
include AlgoliaSearch
|
903
|
+
|
904
|
+
algoliasearch per_environment: true do
|
905
|
+
# the list of attributes sent to Algolia's API
|
906
|
+
attribute :created_at, :title, :url, :author, :points, :story_text, :comment_text, :author, :num_comments, :story_id, :story_title
|
907
|
+
|
908
|
+
# integer version of the created_at datetime field, to use numerical filtering
|
909
|
+
attribute :created_at_i do
|
910
|
+
created_at.to_i
|
911
|
+
end
|
912
|
+
|
913
|
+
# `title` is more important than `{story,comment}_text`, `{story,comment}_text` more than `url`, `url` more than `author`
|
914
|
+
# btw, do not take into account position in most fields to avoid first word match boost
|
915
|
+
searchableAttributes ['unordered(title)', 'unordered(story_text)', 'unordered(comment_text)', 'unordered(url)', 'author']
|
916
|
+
|
917
|
+
# tags used for filtering
|
918
|
+
tags do
|
919
|
+
[item_type, "author_#{author}", "story_#{story_id}"]
|
920
|
+
end
|
921
|
+
|
922
|
+
# use associated number of HN points to sort results (last sort criteria)
|
923
|
+
customRanking ['desc(points)', 'desc(num_comments)']
|
924
|
+
|
925
|
+
# google+, $1.5M raises, C#: we love you
|
926
|
+
separatorsToIndex '+#$'
|
927
|
+
end
|
928
|
+
|
929
|
+
def story_text
|
930
|
+
item_type_cd != Item.comment ? text : nil
|
931
|
+
end
|
932
|
+
|
933
|
+
def story_title
|
934
|
+
comment? && story ? story.title : nil
|
935
|
+
end
|
936
|
+
|
937
|
+
def story_url
|
938
|
+
comment? && story ? story.url : nil
|
939
|
+
end
|
940
|
+
|
941
|
+
def comment_text
|
942
|
+
comment? ? text : nil
|
943
|
+
end
|
944
|
+
|
945
|
+
def comment?
|
946
|
+
item_type_cd == Item.comment
|
947
|
+
end
|
948
|
+
|
949
|
+
# [...]
|
950
|
+
end
|
951
|
+
```
|
952
|
+
|
953
|
+
|
954
|
+
|
955
|
+
# Indices
|
956
|
+
|
957
|
+
|
958
|
+
|
959
|
+
## Manual indexing
|
960
|
+
|
961
|
+
You can trigger indexing using the <code>index!</code> instance method.
|
962
|
+
|
963
|
+
```ruby
|
964
|
+
c = Contact.create!(params[:contact])
|
965
|
+
c.index!
|
966
|
+
```
|
967
|
+
|
968
|
+
## Manual removal
|
969
|
+
|
970
|
+
And trigger index removing using the <code>remove_from_index!</code> instance method.
|
971
|
+
|
972
|
+
```ruby
|
973
|
+
c.remove_from_index!
|
974
|
+
c.destroy
|
975
|
+
```
|
976
|
+
|
977
|
+
## Reindexing
|
978
|
+
|
979
|
+
The gem provides 2 ways to reindex all your objects:
|
980
|
+
|
981
|
+
### Atomical reindexing
|
982
|
+
|
983
|
+
To reindex all your records (taking into account the deleted objects), the `reindex` class method indices all your objects to a temporary index called `<INDEX_NAME>.tmp` and moves the temporary index to the final one once everything is indexed (atomically). This is the safest way to reindex all your content.
|
984
|
+
|
985
|
+
```ruby
|
986
|
+
Contact.reindex
|
987
|
+
```
|
988
|
+
|
989
|
+
**Notes**: if you're using an index-specific API key, ensure you're allowing both `<INDEX_NAME>` and `<INDEX_NAME>.tmp`.
|
990
|
+
|
991
|
+
**Warning:** You should not use such an atomic reindexing operation while scoping/filtering the model because this operation **replaces the entire index**, keeping the filtered objects only. ie: Don't do `MyModel.where(...).reindex` but do `MyModel.where(...).reindex!` (with the trailing `!`)!!!
|
992
|
+
|
993
|
+
### Regular reindexing
|
994
|
+
|
995
|
+
To reindex all your objects in place (without temporary index and therefore without deleting removed objects), use the `reindex!` class method:
|
996
|
+
|
997
|
+
```ruby
|
998
|
+
Contact.reindex!
|
999
|
+
```
|
1000
|
+
|
1001
|
+
## Clearing an index
|
1002
|
+
|
1003
|
+
To clear an index, use the <code>clear_index!</code> class method:
|
1004
|
+
|
1005
|
+
```ruby
|
1006
|
+
Contact.clear_index!
|
1007
|
+
```
|
1008
|
+
|
1009
|
+
## Using the underlying index
|
1010
|
+
|
1011
|
+
You can access the underlying `index` object by calling the `index` class method:
|
1012
|
+
|
1013
|
+
```ruby
|
1014
|
+
index = Contact.index
|
1015
|
+
# index.get_settings, index.partial_update_object, ...
|
1016
|
+
```
|
1017
|
+
|
1018
|
+
## Primary/replica
|
1019
|
+
|
1020
|
+
You can define replica indices using the <code>add_replica</code> method.
|
1021
|
+
Use `inherit: true` on the replica block if you want it to inherit from the primary settings.
|
1022
|
+
|
1023
|
+
```ruby
|
1024
|
+
class Book < ActiveRecord::Base
|
1025
|
+
attr_protected
|
1026
|
+
|
1027
|
+
include AlgoliaSearch
|
1028
|
+
|
1029
|
+
algoliasearch per_environment: true do
|
1030
|
+
searchableAttributes [:name, :author, :editor]
|
1031
|
+
|
1032
|
+
# define a replica index to search by `author` only
|
1033
|
+
add_replica 'Book_by_author', per_environment: true do
|
1034
|
+
searchableAttributes [:author]
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
# define a replica index with custom ordering but same settings than the main block
|
1038
|
+
add_replica 'Book_custom_order', inherit: true, per_environment: true do
|
1039
|
+
customRanking ['asc(rank)']
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
end
|
1044
|
+
```
|
1045
|
+
|
1046
|
+
To search using a replica, use the following code:
|
1047
|
+
|
1048
|
+
```ruby
|
1049
|
+
Book.raw_search 'foo bar', replica: 'Book_by_editor'
|
1050
|
+
# or
|
1051
|
+
Book.search 'foo bar', replica: 'Book_by_editor'
|
1052
|
+
```
|
1053
|
+
|
1054
|
+
## Share a single index
|
1055
|
+
|
1056
|
+
It can make sense to share an index between several models. In order to implement that, you'll need to ensure you don't have any conflict with the `objectID` of the underlying models.
|
1057
|
+
|
1058
|
+
```ruby
|
1059
|
+
class Student < ActiveRecord::Base
|
1060
|
+
attr_protected
|
1061
|
+
|
1062
|
+
include AlgoliaSearch
|
1063
|
+
|
1064
|
+
algoliasearch index_name: 'people', id: :algolia_id do
|
1065
|
+
# [...]
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
private
|
1069
|
+
def algolia_id
|
1070
|
+
"student_#{id}" # ensure the teacher & student IDs are not conflicting
|
1071
|
+
end
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
class Teacher < ActiveRecord::Base
|
1075
|
+
attr_protected
|
1076
|
+
|
1077
|
+
include AlgoliaSearch
|
1078
|
+
|
1079
|
+
algoliasearch index_name: 'people', id: :algolia_id do
|
1080
|
+
# [...]
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
private
|
1084
|
+
def algolia_id
|
1085
|
+
"teacher_#{id}" # ensure the teacher & student IDs are not conflicting
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
```
|
1089
|
+
|
1090
|
+
***Notes:*** If you target a single index from several models, you must never use `MyModel.reindex` and only use `MyModel.reindex!`. The `reindex` method uses a temporary index to perform an atomic reindexing: if you use it, the resulting index will only contain records for the current model because it will not reindex the others.
|
1091
|
+
|
1092
|
+
## Target multiple indices
|
1093
|
+
|
1094
|
+
You can index a record in several indices using the <code>add_index</code> method:
|
1095
|
+
|
1096
|
+
```ruby
|
1097
|
+
class Book < ActiveRecord::Base
|
1098
|
+
attr_protected
|
1099
|
+
|
1100
|
+
include AlgoliaSearch
|
1101
|
+
|
1102
|
+
PUBLIC_INDEX_NAME = "Book_#{Rails.env}"
|
1103
|
+
SECURED_INDEX_NAME = "SecuredBook_#{Rails.env}"
|
1104
|
+
|
1105
|
+
# store all books in index 'SECURED_INDEX_NAME'
|
1106
|
+
algoliasearch index_name: SECURED_INDEX_NAME do
|
1107
|
+
searchableAttributes [:name, :author]
|
1108
|
+
# convert security to tags
|
1109
|
+
tags do
|
1110
|
+
[released ? 'public' : 'private', premium ? 'premium' : 'standard']
|
1111
|
+
end
|
1112
|
+
|
1113
|
+
# store all 'public' (released and not premium) books in index 'PUBLIC_INDEX_NAME'
|
1114
|
+
add_index PUBLIC_INDEX_NAME, if: :public? do
|
1115
|
+
searchableAttributes [:name, :author]
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
private
|
1120
|
+
def public?
|
1121
|
+
released && !premium
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
end
|
1125
|
+
```
|
1126
|
+
|
1127
|
+
To search using an extra index, use the following code:
|
1128
|
+
|
1129
|
+
```ruby
|
1130
|
+
Book.raw_search 'foo bar', index: 'Book_by_editor'
|
1131
|
+
# or
|
1132
|
+
Book.search 'foo bar', index: 'Book_by_editor'
|
1133
|
+
```
|
1134
|
+
|
1135
|
+
|
1136
|
+
|
1137
|
+
# Testing
|
1138
|
+
|
1139
|
+
|
1140
|
+
|
1141
|
+
## Notes
|
1142
|
+
|
1143
|
+
To run the specs, please set the <code>ALGOLIA_APPLICATION_ID</code> and <code>ALGOLIA_API_KEY</code> environment variables. Since the tests are creating and removing indices, DO NOT use your production account.
|
1144
|
+
|
1145
|
+
You may want to disable all indexing (add, update & delete operations) API calls, you can set the `disable_indexing` option:
|
1146
|
+
|
1147
|
+
```ruby
|
1148
|
+
class User < ActiveRecord::Base
|
1149
|
+
include AlgoliaSearch
|
1150
|
+
|
1151
|
+
algoliasearch per_environment: true, disable_indexing: Rails.env.test? do
|
1152
|
+
end
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
class User < ActiveRecord::Base
|
1156
|
+
include AlgoliaSearch
|
1157
|
+
|
1158
|
+
algoliasearch per_environment: true, disable_indexing: Proc.new { Rails.env.test? || more_complex_condition } do
|
1159
|
+
end
|
1160
|
+
end
|
1161
|
+
```
|
1162
|
+
|
1163
|
+
|
1164
|
+
## ❓ Troubleshooting
|
1165
|
+
|
1166
|
+
Encountering an issue? Before reaching out to support, we recommend heading to our [FAQ](https://www.algolia.com/doc/api-client/troubleshooting/faq/ruby/) where you will find answers for the most common issues and gotchas with the client.
|
1167
|
+
|
1168
|
+
## Use the Dockerfile
|
1169
|
+
|
1170
|
+
If you want to contribute to this project without installing all its dependencies, you can use our Docker image. Please check our [dedicated guide](DOCKER_README.MD) to learn more.
|
1171
|
+
|