caoutsearch 0.0.5 → 0.0.6
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/README.md +26 -784
- data/lib/caoutsearch/search/inspect.rb +1 -1
- data/lib/caoutsearch/search/internal_dsl.rb +7 -2
- data/lib/caoutsearch/search/query_builder/contexts.rb +34 -0
- data/lib/caoutsearch/search/query_builder.rb +2 -8
- data/lib/caoutsearch/search/search_methods.rb +10 -5
- data/lib/caoutsearch/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d22b648c6800d29d075baa757ec80d6c1429752918db6fb899fb7484d3ff9f7
|
4
|
+
data.tar.gz: 03fdbf0e6cd2dcc966bf435e9ea7e48ede4e14bd0ae3ec50215cb6867b07d90a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 129553e5adac2bf6e4c4158ce977812ee49e7c05381987ba8a45e4f2dc6e9cac9a1d9203ca8f36e8a72fa8e0e0bc40ad11141db2adc8e5858ae8694e3bed39f5
|
7
|
+
data.tar.gz: e2207e9d3d647a4f4465aaf11ec9f5a7cbde73b1db4867b45b56ad67ffcc5d86be639cb03ec965cdc25ea1c686c1e3fd1e9805035cb946fdf051af8306547063
|
data/README.md
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# Caoutsearch [\ˈkawt͡ˈsɝtʃ\\](http://ipa-reader.xyz/?text=ˈkawt͡ˈsɝtʃ)
|
2
2
|
|
3
|
-
[](https://rubygems.org/gems/caoutsearch)
|
4
|
-
[](https://github.com/mon-territoire/caoutsearch/actions/workflows/ci.yml)
|
5
|
-
[](https://github.com/testdouble/standard)
|
6
|
-
[](https://codeclimate.com/github/mon-territoire/caoutsearch/maintainability)
|
7
|
-
[](https://codeclimate.com/github/mon-territoire/caoutsearch/test_coverage)
|
3
|
+
<span>[](https://rubygems.org/gems/caoutsearch)</span> <span>
|
4
|
+
[](https://github.com/mon-territoire/caoutsearch/actions/workflows/ci.yml)</span> <span>
|
5
|
+
[](https://github.com/testdouble/standard)</span> <span>
|
6
|
+
[](https://codeclimate.com/github/mon-territoire/caoutsearch/maintainability)</span> <span>
|
7
|
+
[](https://codeclimate.com/github/mon-territoire/caoutsearch/test_coverage)</span>
|
8
8
|
|
9
|
-
[](https://github.com/mon-territoire/caoutsearch/actions/workflows/jruby.yml)
|
10
|
-
[](https://github.com/mon-territoire/caoutsearch/actions/workflows/truffle_ruby.yml)
|
9
|
+
<span>[](https://github.com/mon-territoire/caoutsearch/actions/workflows/jruby.yml)</span> <span>
|
10
|
+
[](https://github.com/mon-territoire/caoutsearch/actions/workflows/truffle_ruby.yml)</span>
|
11
11
|
|
12
12
|
**!! Gem under development before public release !!**
|
13
13
|
|
@@ -17,177 +17,42 @@ It provides a simple but powerful DSL to perform complex indexing and searching,
|
|
17
17
|
Caoutsearch only supports Elasticsearch 8.x right now.
|
18
18
|
It is used in production in a robust application, updated and maintained for several years at [Mon Territoire](https://mon-territoire.fr).
|
19
19
|
|
20
|
-
Caoutsearch was inspired by awesome gems such as [elasticsearch-rails](https://github.com/elastic/elasticsearch-rails) or [search_flip](https://github.com/mrkamel/search_flip).
|
21
|
-
|
22
|
-
|
23
|
-
## Table of Contents
|
24
|
-
|
25
|
-
- [Installation](#installation)
|
26
|
-
- [Configuration](#configuration)
|
27
|
-
- Instrumentation
|
28
|
-
- [Usage](#usage)
|
29
|
-
- [Indice Configuration](#indice-configuration)
|
30
|
-
- Mapping & settings
|
31
|
-
- Text analysis
|
32
|
-
- Versionning
|
33
|
-
- [Index Engine](#index-engine)
|
34
|
-
- Properties
|
35
|
-
- Partial updates
|
36
|
-
- Eager loading
|
37
|
-
- Interdependencies
|
38
|
-
- [Search Engine](#search-engine)
|
39
|
-
- Queries
|
40
|
-
- [Filters](#filters)
|
41
|
-
- Full-text query
|
42
|
-
- Custom filters
|
43
|
-
- Orders
|
44
|
-
- [Aggregations](#aggregations)
|
45
|
-
- [Transform aggregations](#transform-aggregations)
|
46
|
-
- [Responses](#responses)
|
47
|
-
- [Loading records](#loading-records)
|
48
|
-
- [Model integration](#model-integration)
|
49
|
-
- [Add Caoutsearch to your models](#add-caoutsearch-to-your-models)
|
50
|
-
- [Index records](#index-records)
|
51
|
-
- [Index multiple records](#index-multiple-records)
|
52
|
-
- [Index single records](#index-single-records)
|
53
|
-
- [Delete documents](#delete-documents)
|
54
|
-
- [Automatic Callbacks](#automatic-callbacks)
|
55
|
-
- Asynchronous methods
|
56
|
-
- [Search for records](#search-for-records)
|
57
|
-
- [Search API](#search-api)
|
58
|
-
- [Pagination](#pagination)
|
59
|
-
- [Total count](#total-count)
|
60
|
-
- [Iterating results](#iterating-results)
|
61
|
-
- [Testing with Caoutsearch](#testing-with-Caoutsearch)
|
20
|
+
Caoutsearch was inspired by awesome gems such as [elasticsearch-rails](https://github.com/elastic/elasticsearch-rails) or [search_flip](https://github.com/mrkamel/search_flip).
|
21
|
+
Depending on your search scenarios, they may better suite your needs.
|
62
22
|
|
63
23
|
## Installation
|
64
24
|
|
25
|
+
Add the gem in your Gemfile:
|
26
|
+
|
65
27
|
```bash
|
66
28
|
bundle add caoutsearch
|
67
29
|
```
|
68
30
|
|
69
|
-
##
|
70
|
-
|
71
|
-
TODO
|
72
|
-
|
73
|
-
## Usage
|
74
|
-
|
75
|
-
### Indice Configuration
|
76
|
-
|
77
|
-
TODO
|
78
|
-
|
79
|
-
### Index Engine
|
80
|
-
|
81
|
-
TODO
|
31
|
+
## Overview
|
82
32
|
|
83
|
-
|
33
|
+
Caoutsearch let you create `Index` and `Search` classes to manipulate your data :
|
84
34
|
|
85
|
-
#### Filters
|
86
|
-
Filters declared in the search engine will define how Caoutsearch will build the queries
|
87
|
-
|
88
|
-
The main use of filters is to expose a field for search, but they can also be used to build more complex queries:
|
89
35
|
```ruby
|
90
|
-
class
|
91
|
-
|
92
|
-
|
36
|
+
class ArticleIndex < Caoutsearch::Index::Base
|
37
|
+
property :title
|
38
|
+
property :published_on
|
39
|
+
property :tags
|
93
40
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
# Build a more complex filter by using other filters
|
98
|
-
filter :public, as: :boolean
|
99
|
-
filter :published_on, as: :date
|
100
|
-
filter :active do |value|
|
101
|
-
search_by(published: value, published_on: value)
|
41
|
+
def tags
|
42
|
+
records.tags.public.map(&:to_s)
|
102
43
|
end
|
103
44
|
end
|
104
|
-
```
|
105
|
-
|
106
|
-
Caoutsearch different types of filters to handle different types of data or ways to search them:
|
107
45
|
|
108
|
-
|
109
|
-
|
110
|
-
##### Boolean filter
|
46
|
+
ArticleIndex.reindex(:tags)
|
47
|
+
```
|
111
48
|
|
112
|
-
##### Date filter
|
113
49
|
|
114
|
-
For a date filter defined like this:
|
115
50
|
```ruby
|
116
51
|
class ArticleSearch < Caoutsearch::Search::Base
|
117
|
-
|
118
|
-
|
52
|
+
filter :title, as: :match
|
119
53
|
filter :published_on, as: :date
|
120
|
-
|
121
|
-
```
|
122
|
-
|
123
|
-
You can now search the matching index with the `published_on` criterion:
|
124
|
-
```ruby
|
125
|
-
Article.search(published_on: Date.today)
|
126
|
-
```
|
127
|
-
|
128
|
-
and the following query will be generated to send to elasticsearch:
|
129
|
-
```json
|
130
|
-
{
|
131
|
-
"query": {
|
132
|
-
"bool": {
|
133
|
-
"filter": [
|
134
|
-
{ "range": { "published_on": { "gte": "2022-23-11", "lte": "2022-23-11"}}}
|
135
|
-
]
|
136
|
-
}
|
137
|
-
}
|
138
|
-
}
|
139
|
-
```
|
140
|
-
|
141
|
-
The date filter accepts multiple types of arguments :
|
54
|
+
filter :tags
|
142
55
|
|
143
|
-
```ruby
|
144
|
-
# Search for articles published on a date:
|
145
|
-
Article.search(published_on: Date.today)
|
146
|
-
|
147
|
-
# Search for articles published before a date:
|
148
|
-
Article.search(published_on: { less_than: "2022-12-25" })
|
149
|
-
Article.search(published_on: { less_than_or_equal: "2022-12-25" })
|
150
|
-
Article.search(published_on: ..Date.new(2022, 12, 25))
|
151
|
-
Article.search(published_on: [[nil, "now-2w/d"]])
|
152
|
-
|
153
|
-
# Search for articles published after a date:
|
154
|
-
Article.search(published_on: { greater_than: "2022-12-25" })
|
155
|
-
Article.search(published_on: { greater_than_or_equal: "2022-12-25" })
|
156
|
-
Article.search(published_on: Date.new(2022, 12, 25)..)
|
157
|
-
Article.search(published_on: [["now-1w/d", nil]])
|
158
|
-
|
159
|
-
# Search for articles published between two dates:
|
160
|
-
Article.search(published_on: { greater_than: "2022-12-25", less_than: "2023-12-25" })
|
161
|
-
Article.search(published_on: Date.new(2022, 12, 25)..Date.new(2023, 12, 25))
|
162
|
-
Article.search(published_on: [["now-1w/d", "now/d"]])
|
163
|
-
```
|
164
|
-
|
165
|
-
Dates of various formats are handled:
|
166
|
-
```ruby
|
167
|
-
"2022-10-11"
|
168
|
-
Date.today
|
169
|
-
Time.zone.now
|
170
|
-
```
|
171
|
-
|
172
|
-
We also support elasticsearch's date math
|
173
|
-
```ruby
|
174
|
-
"now-1h"
|
175
|
-
"now+2w/d"
|
176
|
-
```
|
177
|
-
|
178
|
-
##### GeoPoint filter
|
179
|
-
|
180
|
-
##### Match filter
|
181
|
-
|
182
|
-
##### Range filter
|
183
|
-
|
184
|
-
#### Aggregations
|
185
|
-
|
186
|
-
You can define simple to complex aggregations.
|
187
|
-
|
188
|
-
````ruby
|
189
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
190
|
-
has_aggregation :view_count, { sum: { field: :view_count } }
|
191
56
|
has_aggregation :popular_tags, {
|
192
57
|
filter: { term: { published: true } },
|
193
58
|
aggs: {
|
@@ -197,637 +62,14 @@ class ArticleSearch < Caoutsearch::Search::Base
|
|
197
62
|
}
|
198
63
|
}
|
199
64
|
end
|
200
|
-
````
|
201
|
-
|
202
|
-
Then you can request one or more aggregations at the same time or chain the `aggregate` method.
|
203
|
-
The `aggregations` method will trigger a request and returns a [Response::Aggregations](#responses).
|
204
|
-
|
205
|
-
````ruby
|
206
|
-
ArticleSearch.aggregate(:view_count).aggregations
|
207
|
-
# ArticleSearch Search { "body": { "aggs": { "view_count": { "sum": { "field": "view_count" }}}}}
|
208
|
-
# ArticleSearch Search (10ms / took 5ms)
|
209
|
-
=> #<Caoutsearch::Response::Aggregations view_count=#<Caoutsearch::Response::Response value=119652>>
|
210
|
-
|
211
|
-
ArticleSearch.aggregate(:view_count, :popular_tags).aggregations
|
212
|
-
# ArticleSearch Search { "body": { "aggs": { "view_count": {…}, "popular_tags": {…}}}}
|
213
|
-
# ArticleSearch Search (10ms / took 5ms)
|
214
|
-
=> #<Caoutsearch::Response::Aggregations view_count=#<Caoutsearch::Response::Response value=119652> popular_tags=#<Caoutsearch::Response::Response buckets=…>>
|
215
|
-
|
216
|
-
ArticleSearch.aggregate(:view_count).aggregate(:popular_tags).aggregations
|
217
|
-
# ArticleSearch Search { "body": { "aggs": { "view_count": {…}, "popular_tags": {…}}}}
|
218
|
-
# ArticleSearch Search (10ms / took 5ms)
|
219
|
-
=> #<Caoutsearch::Response::Aggregations view_count=#<Caoutsearch::Response::Response value=119652> popular_tags=#<Caoutsearch::Response::Response buckets=…>>
|
220
|
-
````
|
221
|
-
|
222
|
-
You can create powerful aggregations using blocks and pass arguments to them.
|
223
|
-
|
224
|
-
````ruby
|
225
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
226
|
-
has_aggregation :popular_tags_since do |date|
|
227
|
-
raise TypeError unless date.is_a?(Date)
|
228
|
-
|
229
|
-
query.aggregations[:popular_tags_since] = {
|
230
|
-
filter: { range: { publication_date: { gte: date.to_s } } },
|
231
|
-
aggs: {
|
232
|
-
published: {
|
233
|
-
terms: { field: :tags, size: 20 }
|
234
|
-
}
|
235
|
-
}
|
236
|
-
}
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
ArticleSearch.aggregate(popular_tags_since: 1.day.ago).aggregations
|
241
|
-
# ArticleSearch Search { "body": { "aggs": { "popular_tags_since": {…}}}}
|
242
|
-
# ArticleSearch Search (10ms / took 5ms)
|
243
|
-
=> #<Caoutsearch::Response::Aggregations popular_tags_since=#<Caoutsearch::Response::Response …
|
244
|
-
````
|
245
|
-
|
246
|
-
Only one argument can be passed to an aggregation block.
|
247
|
-
Use an Array or a Hash if you need to pass multiple options.
|
248
|
-
|
249
|
-
````ruby
|
250
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
251
|
-
has_aggregation :popular_tags_since do |options|
|
252
|
-
# …
|
253
|
-
end
|
254
|
-
|
255
|
-
has_aggregation :popular_tags_between do |(first_date, end_date)|
|
256
|
-
# …
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
ArticleSearch.aggregate(popular_tags_since: { date: 1.day.ago, size: 20 })
|
261
|
-
ArticleSearch.aggregate(popular_tags_between: [date1, date2])
|
262
|
-
````
|
263
|
-
|
264
|
-
Finally, you can create a "catch-all" aggregation to handle cumbersome behaviors:
|
265
|
-
|
266
|
-
````ruby
|
267
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
268
|
-
has_aggregation do |name, options = {}|
|
269
|
-
raise "unxpected_error" unless name.match?(/^view_count_(?<year>\d{4})$/)
|
270
|
-
|
271
|
-
query.aggregations[name] = {
|
272
|
-
filter: { term: { year: $LAST_LATCH_INFO[:year] } },
|
273
|
-
aggs: {
|
274
|
-
filtered: {
|
275
|
-
sum: { field: :view_count }
|
276
|
-
}
|
277
|
-
}
|
278
|
-
}
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
ArticleSearch.aggregate(:view_count_2020, :view_count_2019).aggregations
|
283
|
-
# ArticleSearch Search { "body": { "aggs": { "view_count_2020": {…}, "view_count_2019": {…}}}}
|
284
|
-
# ArticleSearch Search (10ms / took 5ms)
|
285
|
-
=> #<Caoutsearch::Response::Aggregations view_count_2020=#<Caoutsearch::Response::Response …
|
286
|
-
````
|
287
|
-
|
288
|
-
#### Transform aggregations
|
289
|
-
|
290
|
-
When using [buckets aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html) and/or [pipeline aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline.html), the path to the expected values can get complicated and become subject to unexpected changes for a public API.
|
291
|
-
|
292
|
-
````ruby
|
293
|
-
ArticleSearch.aggregate(popular_tags_since: 1.month.ago).aggregations.popular_tags_since.published.buckets.pluck(:key)
|
294
|
-
=> ["Blog", "Tech", …]
|
295
|
-
````
|
296
|
-
|
297
|
-
Instead, you can define transformations to provide simpler access to aggregated data:
|
298
|
-
|
299
|
-
````ruby
|
300
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
301
|
-
has_aggregation :popular_tags_since do |since|
|
302
|
-
# …
|
303
|
-
end
|
304
|
-
|
305
|
-
transform_aggregation :popular_tags_since do |aggs|
|
306
|
-
aggs.dig(:popular_tags_since, :published, :buckets).pluck(:key)
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
ArticleSearch.aggregate(popular_tags_since: 1.month.ago).aggregations.popular_tags_since
|
311
|
-
=> ["Blog", "Tech", …]
|
312
|
-
````
|
313
|
-
|
314
|
-
You can also use transformations to combine multiple aggregations:
|
315
|
-
|
316
|
-
````ruby
|
317
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
318
|
-
has_aggregation :blog_count, { filter: { term: { category: "blog" } } }
|
319
|
-
has_aggregation :archives_count, { filter: { term: { archived: true } } }
|
320
|
-
|
321
|
-
transform_aggregation :stats, from: %i[blog_count archives_count] do |aggs|
|
322
|
-
{
|
323
|
-
blog_count: aggs.dig(:blog_count, :doc_count),
|
324
|
-
archives_count: aggs.dig(:archives, :doc_count)
|
325
|
-
}
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
ArticleSearch.aggregate(:stats).aggregations.stats
|
330
|
-
# ArticleSearch Search { "body": { "aggs": { "blog_count": {…}, "archives_count": {…}}}}
|
331
|
-
# ArticleSearch Search (10ms / took 5ms)
|
332
|
-
=> { blog_count: 124, archives_count: 2452 }
|
333
|
-
````
|
334
|
-
|
335
|
-
This is also usefull to unify the API between different search engines:
|
336
|
-
|
337
|
-
````ruby
|
338
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
339
|
-
has_aggregation :popular_tags, {
|
340
|
-
filter: { term: { published: true } },
|
341
|
-
aggs: { published: { terms: { field: :tags, size: 10 } } }
|
342
|
-
}
|
343
|
-
|
344
|
-
transform_aggregation :popular_tags do |aggs|
|
345
|
-
aggs.dig(:popular_tags, :published, :buckets).pluck(:key)
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
class TagSearch < Caoutsearch::Search::Base
|
350
|
-
has_aggregation :popular_tags, {
|
351
|
-
terms: { field: "label", size: 20, order: { used_count: "desc" } }
|
352
|
-
}
|
353
|
-
|
354
|
-
transform_aggregation :popular_tags do |aggs|
|
355
|
-
aggs.dig(:popular_tags, :buckets).pluck(:key)
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
ArticleSearch.aggregate(:popular_tags).aggregations.popular_tags
|
360
|
-
=> ["Blog", "Tech", …]
|
361
|
-
|
362
|
-
TagSearch.aggregate(:popular_tags).aggregations.popular_tags
|
363
|
-
=> ["Tech", "Blog", …]
|
364
|
-
````
|
365
|
-
|
366
|
-
Transformations are performed on demand and result is memorized. That means:
|
367
|
-
- the result of transformation is not visible in the [Response::Aggregations](#responses) output.
|
368
|
-
- the block is called only once for the same search instance.
|
369
|
-
|
370
|
-
````ruby
|
371
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
372
|
-
has_aggregation :popular_tags, {…}
|
373
|
-
|
374
|
-
transform_aggregation :popular_tags do |aggs|
|
375
|
-
tags = aggs.dig(:popular_tags, :published, :buckets).pluck(:key)
|
376
|
-
authorized = Tag.where(title: tags, authorize: true).pluck(:title)
|
377
|
-
tags & authorized
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
article_search = ArticleSearch.aggregate(:popular_tags)
|
382
|
-
=> #<ArticleSearch current_aggregations: [:popular_tags]>
|
383
|
-
|
384
|
-
article_search.aggregations
|
385
|
-
# ArticleSearch Search (10ms / took 5ms)
|
386
|
-
=> #<Caoutsearch::Response::Aggregations popular_tags=#<Caoutsearch::Response::Response doc_count=100 …
|
387
|
-
|
388
|
-
article_search.aggregations.popular_tags
|
389
|
-
# (10.2ms) SELECT "tags"."title" FROM "tags" WHERE "tags"."title" IN …
|
390
|
-
=> ["Blog", "Tech", …]
|
391
|
-
|
392
|
-
article_search.aggregations.popular_tags
|
393
|
-
=> ["Blog", "Tech", …]
|
394
|
-
|
395
|
-
article_search.search("Tech").aggregations.popular_tags
|
396
|
-
# ArticleSearch Search (10ms / took 5ms)
|
397
|
-
# (10.2ms) SELECT "tags"."title" FROM "tags" WHERE "tags"."title" IN …
|
398
|
-
=> ["Blog", "Tech", …]
|
399
|
-
````
|
400
|
-
|
401
|
-
Be careful to avoid using `aggregations.<aggregation_name>` inside a transformation block: it can lead to an infinite recursion.
|
402
|
-
|
403
|
-
````ruby
|
404
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
405
|
-
transform_aggregation :popular_tags do
|
406
|
-
aggregations.popular_tags.buckets.pluck("key")
|
407
|
-
end
|
408
|
-
end
|
409
|
-
|
410
|
-
ArticleSearch.aggregate(:popular_tags).aggregations.popular_tags
|
411
|
-
Traceback (most recent call last):
|
412
|
-
4: from app/searches/article_search.rb:3:in `block in <class:ArticleSearch>'
|
413
|
-
3: from app/searches/article_search.rb:3:in `block in <class:ArticleSearch>'
|
414
|
-
2: from app/searches/article_search.rb:3:in `block in <class:ArticleSearch>'
|
415
|
-
1: from app/searches/article_search.rb:3:in `block in <class:ArticleSearch>'
|
416
|
-
SystemStackError (stack level too deep)
|
417
|
-
````
|
418
|
-
|
419
|
-
Instead, use the argument passed to the block: it's is a shortcut for `response.aggregations` which is a [Response::Reponse](#responses) and not a [Response::Aggregations](#responses).
|
420
|
-
|
421
|
-
````ruby
|
422
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
423
|
-
transform_aggregation :popular_tags do |aggs|
|
424
|
-
aggs.popular_tags.buckets.pluck("key")
|
425
|
-
end
|
426
|
-
end
|
427
|
-
|
428
|
-
ArticleSearch.aggregate(:popular_tags).aggregations.popular_tags
|
429
|
-
=> ["Blog", "Tech", …]
|
430
|
-
````
|
431
|
-
|
432
|
-
One last helpful argument is `track_total_hits` which allows to perform calculations over aggregations using the `total_count` method without sending a second request.
|
433
|
-
Take a look at [Total count](#total-count) to understand why a second request could be performed.
|
434
|
-
|
435
|
-
````ruby
|
436
|
-
class ArticleSearch < Caoutsearch::Search::Base
|
437
|
-
aggregation :tagged, filter: { exist: "tag" }
|
438
65
|
|
439
|
-
|
440
|
-
count = aggs.dig(:tagged, :doc_count)
|
441
|
-
count.to_f / total_count
|
442
|
-
end
|
443
|
-
|
444
|
-
transform_aggregation :tagged_rate_without_track_total_hits, from: :tagged do |aggs|
|
445
|
-
count = aggs.dig(:tagged, :doc_count)
|
446
|
-
count.to_f / total_count
|
447
|
-
end
|
448
|
-
end
|
449
|
-
|
450
|
-
ArticleSearch.aggregate(:tagged_rate).aggregations.tagged_rate
|
451
|
-
# ArticleSearch Search { "body": { "track_total_hits": true, "aggs": { "blog_count": {…}, "archives_count": {…}}}}
|
452
|
-
# ArticleSearch Search (10ms / took 5ms)
|
453
|
-
=> 0.95
|
454
|
-
|
455
|
-
ArticleSearch.aggregate(:tagged_rate_without_track_total_hits).aggregations.tagged_rate
|
456
|
-
# ArticleSearch Search { "body": { "aggs": { "blog_count": {…}, "archives_count": {…}}}}
|
457
|
-
# ArticleSearch Search (10ms / took 5ms)
|
458
|
-
# ArticleSearch Search { "body": { "track_total_hits": true, "aggs": { "blog_count": {…}, "archives_count":
|
459
|
-
# ArticleSearch Search (10ms / took 5ms)
|
460
|
-
=> 0.95
|
461
|
-
````
|
462
|
-
|
463
|
-
#### Responses
|
464
|
-
|
465
|
-
After the request has been sent by calling a method such as `load`, `response` or `hits`, the results is wrapped in a `Response::Response` class which provides method access to its properties via [Hashie::Mash](http://github.com/intridea/hashie).
|
466
|
-
|
467
|
-
Aggregations and suggestions are wrapped in their own respective subclass of `Response::Response`
|
468
|
-
|
469
|
-
````ruby
|
470
|
-
results.response
|
471
|
-
=> #<Caoutsearch::Response::Response _shards=#<Caoutsearch::Response::Response failed=0 skipped=0 successful=5 total=5> hits=…
|
472
|
-
|
473
|
-
search.hits
|
474
|
-
=> #<Hashie::Array [#<Caoutsearch::Response::Response _id="2"…
|
475
|
-
|
476
|
-
search.aggregations
|
477
|
-
=> #<Caoutsearch::Response::Aggregations view_count=#<Caoutsearch::Response::Response…
|
478
|
-
|
479
|
-
search.suggestions
|
480
|
-
=> #<Caoutsearch::Response::Suggestions tags=#<Caoutsearch::Response::Response…
|
481
|
-
````
|
482
|
-
|
483
|
-
##### Loading records
|
484
|
-
|
485
|
-
When calling `records`, the search engine will try to load records from a model using the same class name without `Search` the suffix:
|
486
|
-
* `ArticleSearch` > `Article`
|
487
|
-
* `Blog::ArticleSearch` > `Blog::Article`
|
488
|
-
|
489
|
-
````ruby
|
490
|
-
ArticleSearch.new.records.first
|
491
|
-
# ArticleSearch Search (10ms / took 5ms)
|
492
|
-
# Article Load (9.6ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1, …
|
493
|
-
=> #<Article id: 1, …>
|
494
|
-
````
|
495
|
-
|
496
|
-
However, you can define an alternative model to load records. This might be helpful when using [single table inheritance](https://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html).
|
497
|
-
|
498
|
-
````ruby
|
499
|
-
ArticleSearch.new.records(use: BlogArticle).first
|
500
|
-
# ArticleSearch Search (10ms / took 5ms)
|
501
|
-
# BlogArticle Load (9.6ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1, …
|
502
|
-
=> #<BlogArticle id: 1, …>
|
503
|
-
````
|
504
|
-
|
505
|
-
You can also define an alternative model at class level:
|
506
|
-
|
507
|
-
````ruby
|
508
|
-
class BlogArticleSearch < Caoutsearch::Search::Base
|
509
|
-
self.model_name = "Article"
|
510
|
-
|
511
|
-
default do
|
512
|
-
query.filters << { term: { category: "blog" } }
|
513
|
-
end
|
514
|
-
end
|
515
|
-
|
516
|
-
BlogArticleSearch.new.records.first
|
517
|
-
# BlogArticleSearch Search (10ms / took 5ms)
|
518
|
-
# Article Load (9.6ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" IN (1, …
|
519
|
-
=> #<Article id: 1, …>
|
520
|
-
````
|
521
|
-
|
522
|
-
### Model integration
|
523
|
-
|
524
|
-
#### Add Caoutsearch to your models
|
525
|
-
|
526
|
-
The simplest solution is to add `Caoutsearch::Model` to your model and the link the appropriate `Index` and/or `Search` engines:
|
527
|
-
|
528
|
-
```ruby
|
529
|
-
class Article < ActiveRecord::Base
|
530
|
-
include Caoutsearch::Model
|
531
|
-
|
532
|
-
index_with ArticleIndex
|
533
|
-
search_with ArticleSearch
|
534
|
-
end
|
535
|
-
```
|
536
|
-
|
537
|
-
If you don't need your models to be `Indexable` and `Searchable`, you can include only one of the following two modules:
|
538
|
-
|
539
|
-
````ruby
|
540
|
-
class Article < ActiveRecord::Base
|
541
|
-
include Caoutsearch::Model::Indexable
|
542
|
-
|
543
|
-
index_with ArticleIndex
|
544
|
-
end
|
545
|
-
````
|
546
|
-
or
|
547
|
-
````ruby
|
548
|
-
class Article < ActiveRecord::Base
|
549
|
-
include Caoutsearch::Model::Searchable
|
550
|
-
|
551
|
-
search_with ArticleSearch
|
552
|
-
end
|
553
|
-
````
|
554
|
-
|
555
|
-
The modules can be safely included in the meta model `ApplicationRecord`.
|
556
|
-
Indexing & searching features are not available until you call `index_with` or `search_with`:
|
557
|
-
|
558
|
-
````ruby
|
559
|
-
class ApplicationRecord < ActiveRecord::Base
|
560
|
-
include Caoutsearch::Model
|
561
|
-
end
|
562
|
-
````
|
563
|
-
|
564
|
-
#### Index records
|
565
|
-
|
566
|
-
##### Index multiple records
|
567
|
-
|
568
|
-
Import all your records or a restricted scope of records to Elastcisearch.
|
569
|
-
|
570
|
-
````ruby
|
571
|
-
Article.reindex
|
572
|
-
Article.where(published: true).reindex
|
573
|
-
````
|
574
|
-
|
575
|
-
You can update one or more properties. (see [Indexation Engines](#indexation-engines) to read more about properties):
|
576
|
-
|
577
|
-
````ruby
|
578
|
-
Article.reindex(:category)
|
579
|
-
Article.reindex(%i[category published_on])
|
580
|
-
````
|
581
|
-
|
582
|
-
When `reindex` is called without properties, it'll import the full document to ES.
|
583
|
-
On the contrary, when properties are passed, it'll only update existing documents.
|
584
|
-
You can control this behavior with the `method` argument.
|
585
|
-
|
586
|
-
````ruby
|
587
|
-
Article.where(id: 123).reindex(:category)
|
588
|
-
# ArticleIndex Reindex {"index":"articles","body":[{"update":{"_id":123}},{"doc":{"category":"blog"}}]}
|
589
|
-
# [Error] {"update"=>{"_index"=>"articles", "_id"=>"123", "status"=>404, "error"=>{"type"=>"document_missing_exception", …}}
|
590
|
-
|
591
|
-
Article.where(id: 123).reindex(:category, method: :index)
|
592
|
-
# ArticleIndex Reindex {"index":"articles","body":[{"index":{"_id":123}},{"category":"blog"}]}
|
593
|
-
|
594
|
-
Article.where(id: 123).reindex(method: :update)
|
595
|
-
# ArticleIndex Reindex {"index":"articles","body":[{"update":{"_id":123}},{"doc":{…}}]}
|
596
|
-
````
|
597
|
-
|
598
|
-
##### Index single records
|
599
|
-
|
600
|
-
Import a single record.
|
601
|
-
|
602
|
-
````ruby
|
603
|
-
Article.find(123).update_index
|
604
|
-
````
|
605
|
-
|
606
|
-
You can update one or more properties. (see [Indexation Engines](#indexation-engines) to read more about properties):
|
607
|
-
|
608
|
-
````ruby
|
609
|
-
Article.find(123).update_index(:category)
|
610
|
-
Article.find(123).update_index(%i[category published_on])
|
611
|
-
````
|
612
|
-
|
613
|
-
You can verify if and how documents are indexed.
|
614
|
-
If the document is missing in ES, it'll raise a `Elastic::Transport::Transport::Errors::NotFound`.
|
615
|
-
|
616
|
-
````ruby
|
617
|
-
Article.find(123).indexed_document
|
618
|
-
# Traceback (most recent call last):
|
619
|
-
# 1: from (irb):1
|
620
|
-
# Elastic::Transport::Transport::Errors::NotFound ([404] {"_index":"articles","_id":"123","found":false})
|
621
|
-
|
622
|
-
Article.find(123).update_index
|
623
|
-
Article.find(123).indexed_document
|
624
|
-
=> {"_index"=>"articles", "_id"=>"123", "_version"=>1"found"=>true, "_source"=>{…}}
|
625
|
-
````
|
626
|
-
|
627
|
-
##### Delete documents
|
628
|
-
|
629
|
-
You can delete one or more documents.
|
630
|
-
**Note**: it won't delete records from database, only from the ES indice.
|
631
|
-
|
632
|
-
````ruby
|
633
|
-
Article.delete_indexes
|
634
|
-
Article.where(id: 123).delete_indexed_documents
|
635
|
-
Article.find(123).delete_index
|
636
|
-
````
|
637
|
-
|
638
|
-
If a record is already deleted from the database, you can still delete its document.
|
639
|
-
|
640
|
-
````ruby
|
641
|
-
Article.delete_index(123)
|
642
|
-
````
|
643
|
-
|
644
|
-
##### Automatic Callbacks
|
645
|
-
|
646
|
-
Callbacks are not provided by Caoutsearch but they are very easy to add:
|
647
|
-
|
648
|
-
````ruby
|
649
|
-
class Article < ApplicationRecord
|
650
|
-
index_with ArticleIndex
|
651
|
-
|
652
|
-
after_commit :update_index, on: %i[create update]
|
653
|
-
after_commit :delete_index, on: %i[destroy]
|
654
|
-
end
|
655
|
-
````
|
656
|
-
|
657
|
-
##### Asynchronous methods
|
658
|
-
|
659
|
-
TODO
|
660
|
-
|
661
|
-
#### Search for records
|
662
|
-
|
663
|
-
##### Search API
|
664
|
-
Searching is pretty simple.
|
665
|
-
|
666
|
-
````ruby
|
667
|
-
Article.search("Quick brown fox")
|
668
|
-
=> #<ArticleSearch current_criteria: ["Quick brown fox"]>
|
669
|
-
````
|
670
|
-
|
671
|
-
You can chain criteria and many other parameters:
|
672
|
-
````ruby
|
673
|
-
Article.search("Quick brown fox").search(published: true)
|
674
|
-
=> #<ArticleSearch current_criteria: ["Quick brown fox", {"published"=>true}]>
|
675
|
-
|
676
|
-
Article.search("Quick brown fox").order(:publication_date)
|
677
|
-
=> #<ArticleSearch current_criteria: ["Quick brown fox"], current_order: :publication_date>
|
678
|
-
|
679
|
-
Article.search("Quick brown fox").limit(100).offset(100)
|
680
|
-
=> #<ArticleSearch current_criteria: ["Quick brown fox"], current_limit: 100, current_offset: 100>
|
681
|
-
|
682
|
-
Article.search("Quick brown fox").page(1).per(100)
|
683
|
-
=> #<ArticleSearch current_criteria: ["Quick brown fox"], current_page: 1, current_limit: 100>
|
684
|
-
|
685
|
-
Article.search("Quick brown fox").aggregate(:tags).aggregate(:dates)
|
686
|
-
=> #<ArticleSearch current_criteria: ["Quick brown fox"], current_aggregations: [:tags, :dates]>>
|
687
|
-
````
|
688
|
-
|
689
|
-
##### Pagination
|
690
|
-
|
691
|
-
Search results can be paginated.
|
692
|
-
````ruby
|
693
|
-
search = Article.search("Quick brown fox").page(1).per(100)
|
694
|
-
search.current_page
|
695
|
-
=> 1
|
696
|
-
|
697
|
-
search.total_pages
|
698
|
-
=> 2546
|
699
|
-
|
700
|
-
> search.total_count
|
701
|
-
=> 254514
|
702
|
-
````
|
703
|
-
|
704
|
-
##### Total count
|
705
|
-
|
706
|
-
By default [ES doesn't return the total number of hits](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-your-data.html#track-total-hits). So, when calling `total_count` or `total_pages` a second request might be sent to ES.
|
707
|
-
To avoid a second roundtrip, use `track_total_hits`:
|
708
|
-
|
709
|
-
````ruby
|
710
|
-
search = Article.search("Quick brown fox")
|
711
|
-
search.hits
|
712
|
-
# ArticleSearch Search {…}
|
713
|
-
# ArticleSearch Search (81.8ms / took 16ms)
|
714
|
-
=> […]
|
715
|
-
|
716
|
-
search.total_count
|
717
|
-
# ArticleSearch Search {…, track_total_hits: true }
|
718
|
-
# ArticleSearch Search (135.3ms / took 76ms)
|
719
|
-
=> 276
|
720
|
-
|
721
|
-
search = Article.search("Quick brown fox").track_total_hits
|
722
|
-
search.hits
|
723
|
-
# ArticleSearch Search {…, track_total_hits: true }
|
724
|
-
# ArticleSearch Search (120.2ms / took 56ms)
|
725
|
-
=> […]
|
726
|
-
|
727
|
-
search.total_count
|
728
|
-
=> 276
|
729
|
-
````
|
730
|
-
|
731
|
-
##### Iterating results
|
732
|
-
|
733
|
-
Several methods are provided to loop through a collection or hits or records.
|
734
|
-
These methods are processing batches in the most efficient way: [PIT search_after](https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html#search-after).
|
735
|
-
|
736
|
-
* `find_each_hit` to yield each hit returned by Elasticsearch.
|
737
|
-
* `find_each_record` to yield each record from your database.
|
738
|
-
* `find_hits_in_batches` to yield each batch of hits as returned by Elasticsearch.
|
739
|
-
* `find_records_in_batches` to yield each batch of records from the database.
|
740
|
-
|
741
|
-
Example:
|
742
|
-
|
743
|
-
```ruby
|
744
|
-
Article.search(published: true).find_each_record do |record|
|
745
|
-
record.inspect
|
746
|
-
end
|
747
|
-
```
|
748
|
-
|
749
|
-
The `keep_alive` parameter tells Elasticsearch how long it should keep the point in time alive. Defaults to 1 minute.
|
750
|
-
|
751
|
-
```ruby
|
752
|
-
Article.search(published: true).find_each_record(keep_alive: "2h")
|
753
|
-
```
|
754
|
-
|
755
|
-
To specifies the size of the batch, use `per` chainable method or `batch_size` parameter. Defaults to 1000.
|
756
|
-
|
757
|
-
```ruby
|
758
|
-
Article.search(published: true).find_records_in_batches(batch_size: 500)
|
759
|
-
Article.search(published: true).per(500).find_records_in_batches
|
760
|
-
```
|
761
|
-
|
762
|
-
## Testing with Caoutsearch
|
763
|
-
|
764
|
-
Caoutsearch offers few methods to stub Elasticsearch requests.
|
765
|
-
You first need to add [webmock](https://github.com/bblimke/webmock) to your Gemfile.
|
766
|
-
|
767
|
-
```bash
|
768
|
-
bundle add webmock
|
769
|
-
```
|
770
|
-
|
771
|
-
Then, add `Caoutsearch::Testing::MockRequests` to your test suite.
|
772
|
-
The examples below uses RSpec, but it should be compatible with other test framework.
|
773
|
-
|
774
|
-
```ruby
|
775
|
-
# spec/spec_helper.rb
|
776
|
-
|
777
|
-
require "caoutsearch/testing"
|
778
|
-
|
779
|
-
RSpec.configure do |config|
|
780
|
-
config.include Caoutsearch::Testing::MockRequests
|
781
|
-
end
|
782
|
-
```
|
783
|
-
|
784
|
-
You can then call the following methods:
|
785
|
-
|
786
|
-
```ruby
|
787
|
-
RSpec.describe SomeClass do
|
788
|
-
before do
|
789
|
-
stub_elasticsearch_request(:head, "articles").to_return(status: 200)
|
790
|
-
|
791
|
-
stub_elasticsearch_request(:get, "_cat/indices?format=json&h=index").to_return_json, [
|
792
|
-
{ index: "ca_locals_v14" }
|
793
|
-
])
|
794
|
-
|
795
|
-
stub_elasticsearch_reindex_request("articles")
|
796
|
-
stub_elasticsearch_search_request("articles", [
|
797
|
-
{"_id" => "135", "_source" => {"name" => "Hello World"}},
|
798
|
-
{"_id" => "137", "_source" => {"name" => "Hello World"}}
|
799
|
-
])
|
800
|
-
end
|
801
|
-
|
802
|
-
# ... do your tests...
|
803
|
-
end
|
66
|
+
ArticleSearch.search(published_on: [["now-1y", nil]]).aggregate(:popular_tags)
|
804
67
|
```
|
805
68
|
|
806
|
-
`stub_elasticsearch_search_request` accepts an array or records:
|
807
69
|
|
808
|
-
|
809
|
-
RSpec.describe SomeClass do
|
810
|
-
let(:articles) { create_list(:article, 5) }
|
811
|
-
|
812
|
-
before do
|
813
|
-
stub_elasticsearch_search_request("articles", articles)
|
814
|
-
end
|
70
|
+
## Documentation
|
815
71
|
|
816
|
-
|
817
|
-
end
|
818
|
-
```
|
819
|
-
|
820
|
-
It allows to shim the total number of hits returned.
|
821
|
-
|
822
|
-
```ruby
|
823
|
-
RSpec.describe SomeClass do
|
824
|
-
before do
|
825
|
-
stub_elasticsearch_search_request("articles", [], total: 250)
|
826
|
-
end
|
827
|
-
|
828
|
-
# ... do your tests...
|
829
|
-
end
|
830
|
-
```
|
72
|
+
Visit our [offical documentation](https://mon-territoire.github.io/caoutsearch) to understand how to use Caoutsearch.
|
831
73
|
|
832
74
|
## Contributing
|
833
75
|
|
@@ -15,13 +15,13 @@ module Caoutsearch
|
|
15
15
|
# self.config[:filter][key] = ...
|
16
16
|
#
|
17
17
|
class_attribute :config, default: {
|
18
|
-
contexts: ActiveSupport::HashWithIndifferentAccess.new,
|
19
18
|
filters: ActiveSupport::HashWithIndifferentAccess.new,
|
20
19
|
defaults: ActiveSupport::HashWithIndifferentAccess.new,
|
21
20
|
suggestions: ActiveSupport::HashWithIndifferentAccess.new,
|
22
21
|
sorts: ActiveSupport::HashWithIndifferentAccess.new
|
23
22
|
}
|
24
23
|
|
24
|
+
class_attribute :contexts, instance_accessor: false, default: {}
|
25
25
|
class_attribute :aggregations, instance_accessor: false, default: {}
|
26
26
|
class_attribute :transformations, instance_accessor: false, default: {}
|
27
27
|
end
|
@@ -32,7 +32,7 @@ module Caoutsearch
|
|
32
32
|
config[:match_all] = block
|
33
33
|
end
|
34
34
|
|
35
|
-
%w[
|
35
|
+
%w[default].each do |method|
|
36
36
|
config_attribute = method.pluralize.to_sym
|
37
37
|
|
38
38
|
define_method method do |name = nil, &block|
|
@@ -68,6 +68,11 @@ module Caoutsearch
|
|
68
68
|
sort(new_name) { |direction| sort_by(old_name, direction) }
|
69
69
|
end
|
70
70
|
|
71
|
+
def has_context(name, &block)
|
72
|
+
self.contexts = contexts.dup
|
73
|
+
contexts[name.to_s] = Caoutsearch::Search::DSL::Item.new(name, &block)
|
74
|
+
end
|
75
|
+
|
71
76
|
def has_aggregation(name, definition = {}, &block)
|
72
77
|
raise ArgumentError, "has_aggregation accepts Hash definition or block but not both" if block && definition.any?
|
73
78
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caoutsearch
|
4
|
+
module Search
|
5
|
+
module QueryBuilder
|
6
|
+
module Contexts
|
7
|
+
private
|
8
|
+
|
9
|
+
def build_contexts
|
10
|
+
call_contexts(*current_contexts) if current_contexts
|
11
|
+
end
|
12
|
+
|
13
|
+
def call_contexts(*args)
|
14
|
+
args.each do |arg|
|
15
|
+
call_context(arg)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def call_context(name)
|
20
|
+
name = name.to_s
|
21
|
+
|
22
|
+
if self.class.contexts.include?(name)
|
23
|
+
item = self.class.contexts[name]
|
24
|
+
call_context_item(item)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def call_context_item(item)
|
29
|
+
instance_exec(&item.block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -5,6 +5,7 @@ module Caoutsearch
|
|
5
5
|
module QueryBuilder
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
include QueryBuilder::Aggregations
|
8
|
+
include QueryBuilder::Contexts
|
8
9
|
|
9
10
|
def build
|
10
11
|
reset_variable(:@elasticsearch_query)
|
@@ -13,7 +14,7 @@ module Caoutsearch
|
|
13
14
|
run_callbacks :build do
|
14
15
|
build_prepend_hash
|
15
16
|
build_search_criteria
|
16
|
-
|
17
|
+
build_contexts
|
17
18
|
build_defaults
|
18
19
|
build_limits
|
19
20
|
build_orders
|
@@ -35,13 +36,6 @@ module Caoutsearch
|
|
35
36
|
search_by(search_criteria)
|
36
37
|
end
|
37
38
|
|
38
|
-
def build_context
|
39
|
-
return unless current_context
|
40
|
-
|
41
|
-
item = config[:contexts][current_context.to_s]
|
42
|
-
instance_exec(&item.block) if item
|
43
|
-
end
|
44
|
-
|
45
39
|
def build_defaults
|
46
40
|
keys = search_criteria_keys.map(&:to_s)
|
47
41
|
|
@@ -5,7 +5,7 @@ module Caoutsearch
|
|
5
5
|
module SearchMethods
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :current_contexts, :current_order, :current_aggregations,
|
9
9
|
:current_suggestions, :current_fields, :current_source
|
10
10
|
|
11
11
|
# Public API
|
@@ -97,8 +97,9 @@ module Caoutsearch
|
|
97
97
|
self
|
98
98
|
end
|
99
99
|
|
100
|
-
def context!(
|
101
|
-
@
|
100
|
+
def context!(*values)
|
101
|
+
@current_contexts ||= []
|
102
|
+
@current_contexts += values.flatten
|
102
103
|
self
|
103
104
|
end
|
104
105
|
|
@@ -173,7 +174,7 @@ module Caoutsearch
|
|
173
174
|
"aggregations" => :@current_aggregations,
|
174
175
|
"suggest" => :@current_suggestions,
|
175
176
|
"suggestions" => :@current_suggestions,
|
176
|
-
"context" => :@
|
177
|
+
"context" => :@current_contexts,
|
177
178
|
"order" => :@current_order,
|
178
179
|
"page" => :@current_page,
|
179
180
|
"offset" => :@current_offset,
|
@@ -189,7 +190,7 @@ module Caoutsearch
|
|
189
190
|
self
|
190
191
|
end
|
191
192
|
|
192
|
-
# Getters
|
193
|
+
# Getters and predicates
|
193
194
|
# ------------------------------------------------------------------------
|
194
195
|
def search_criteria
|
195
196
|
@search_criteria ||= []
|
@@ -213,6 +214,10 @@ module Caoutsearch
|
|
213
214
|
end
|
214
215
|
end
|
215
216
|
|
217
|
+
def current_context?(name)
|
218
|
+
@current_contexts&.map(&:to_s)&.include?(name.to_s)
|
219
|
+
end
|
220
|
+
|
216
221
|
# Criteria handlers
|
217
222
|
# ------------------------------------------------------------------------
|
218
223
|
def find_criterion(key)
|
data/lib/caoutsearch/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: caoutsearch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Savater Sebastien
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-03-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -147,6 +147,7 @@ files:
|
|
147
147
|
- lib/caoutsearch/search/query/setters.rb
|
148
148
|
- lib/caoutsearch/search/query_builder.rb
|
149
149
|
- lib/caoutsearch/search/query_builder/aggregations.rb
|
150
|
+
- lib/caoutsearch/search/query_builder/contexts.rb
|
150
151
|
- lib/caoutsearch/search/query_methods.rb
|
151
152
|
- lib/caoutsearch/search/records.rb
|
152
153
|
- lib/caoutsearch/search/resettable.rb
|
@@ -179,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
179
180
|
- !ruby/object:Gem::Version
|
180
181
|
version: '0'
|
181
182
|
requirements: []
|
182
|
-
rubygems_version: 3.
|
183
|
+
rubygems_version: 3.4.8
|
183
184
|
signing_key:
|
184
185
|
specification_version: 4
|
185
186
|
summary: An alternative approach to index & search with Elasticsearch & Ruby on Rails
|