search_flip 3.0.0 → 3.2.1
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/.github/workflows/test.yml +34 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +23 -0
- data/README.md +50 -3
- data/lib/search_flip.rb +2 -0
- data/lib/search_flip/aggregation.rb +2 -2
- data/lib/search_flip/aws_sigv4_plugin.rb +47 -0
- data/lib/search_flip/connection.rb +1 -1
- data/lib/search_flip/criteria.rb +20 -30
- data/lib/search_flip/filterable.rb +16 -0
- data/lib/search_flip/highlightable.rb +3 -2
- data/lib/search_flip/http_client.rb +8 -10
- data/lib/search_flip/index.rb +3 -3
- data/lib/search_flip/result.rb +1 -1
- data/lib/search_flip/version.rb +1 -1
- data/search_flip.gemspec +1 -0
- data/spec/search_flip/aws_sigv4_plugin_spec.rb +41 -0
- data/spec/search_flip/connection_spec.rb +4 -4
- data/spec/search_flip/criteria_spec.rb +22 -0
- data/spec/search_flip/http_client_spec.rb +17 -0
- data/spec/search_flip/index_spec.rb +17 -4
- data/spec/search_flip/null_instrumenter_spec.rb +2 -2
- data/spec/search_flip/result_spec.rb +6 -0
- metadata +21 -4
- data/.travis.yml +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa450fa83c07d1ed7e266ef436c26075a0bd6d8b5fd2904ed98d6b4b3921c6ab
|
|
4
|
+
data.tar.gz: b7c06e750f4b73d3ad1c8626485526b9389d95591f6c56b954f5f1f6610dec2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd8bbc2c91f7b35f5b3d54d225082b7d2c4a0b932b1323c4de836adfa8d3118e89bc70ac6f41b27c6dec3fdf200eafec10a7b7c145f3a73ccbc2a2f7c6eb8581
|
|
7
|
+
data.tar.gz: 8c6a974604a63b210d6dd8e585549116266db09321745511ffe78cece7fe84284dd1e035c01be685522f3fd118ac21daa714b2fdcb91f00c08d23dbf93adc692
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
on: push
|
|
2
|
+
name: test
|
|
3
|
+
jobs:
|
|
4
|
+
test:
|
|
5
|
+
runs-on: ubuntu-latest
|
|
6
|
+
strategy:
|
|
7
|
+
fail-fast: false
|
|
8
|
+
matrix:
|
|
9
|
+
elasticsearch:
|
|
10
|
+
- plainpicture/elasticsearch:2.4.1_delete-by-query
|
|
11
|
+
- elasticsearch:5.4
|
|
12
|
+
- docker.elastic.co/elasticsearch/elasticsearch:6.7.0
|
|
13
|
+
- docker.elastic.co/elasticsearch/elasticsearch:7.0.0
|
|
14
|
+
- docker.elastic.co/elasticsearch/elasticsearch:7.11.2
|
|
15
|
+
ruby:
|
|
16
|
+
- 2.5
|
|
17
|
+
- 2.6
|
|
18
|
+
- 2.7
|
|
19
|
+
services:
|
|
20
|
+
elasticsearch:
|
|
21
|
+
image: ${{ matrix.elasticsearch }}
|
|
22
|
+
env:
|
|
23
|
+
discovery.type: single-node
|
|
24
|
+
ports:
|
|
25
|
+
- 9200:9200
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v1
|
|
28
|
+
- uses: actions/setup-ruby@v1
|
|
29
|
+
with:
|
|
30
|
+
ruby-version: ${{ matrix.ruby }}
|
|
31
|
+
- run: bundle
|
|
32
|
+
- run: sleep 10
|
|
33
|
+
- run: bundle exec rspec
|
|
34
|
+
- run: bundle exec rubocop
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
AllCops:
|
|
2
2
|
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 2.4
|
|
4
|
+
|
|
5
|
+
Layout/EmptyLineBetweenDefs:
|
|
6
|
+
EmptyLineBetweenClassDefs: false
|
|
7
|
+
|
|
8
|
+
Gemspec/RequiredRubyVersion:
|
|
9
|
+
Enabled: false
|
|
10
|
+
|
|
11
|
+
Style/ExplicitBlockArgument:
|
|
12
|
+
Enabled: false
|
|
3
13
|
|
|
4
14
|
Style/HashTransformValues:
|
|
5
15
|
Enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
|
|
2
2
|
# CHANGELOG
|
|
3
3
|
|
|
4
|
+
## v3.2.1
|
|
5
|
+
|
|
6
|
+
* Fix `refresh` having a empty body breaking in elasticsearch 7.11
|
|
7
|
+
|
|
8
|
+
## v3.2.0
|
|
9
|
+
|
|
10
|
+
* Fix `index_scope` not being passed in `each_record` without block
|
|
11
|
+
* Added `SearchFlip::Criteria#match_none`
|
|
12
|
+
|
|
13
|
+
## v3.1.2
|
|
14
|
+
|
|
15
|
+
* Fix ignored false value for source when merging criterias
|
|
16
|
+
|
|
17
|
+
## v3.1.1
|
|
18
|
+
|
|
19
|
+
* Make `SearchFlip::Result.from_hit` work with the `_source` being disabled
|
|
20
|
+
|
|
21
|
+
## v3.1.0
|
|
22
|
+
|
|
23
|
+
* Added plugin support in `SearchFlip::HTTPClient`
|
|
24
|
+
* Added `SearchFlip::AwsSigv4Plugin` to be able to use AWS Elasticsearch with
|
|
25
|
+
signed requests
|
|
26
|
+
|
|
4
27
|
## v3.0.0
|
|
5
28
|
|
|
6
29
|
* Added `Criteria#to_query`, which returns a raw query including all queries
|
data/README.md
CHANGED
|
@@ -419,6 +419,14 @@ Simply matches all documents:
|
|
|
419
419
|
CommentIndex.match_all
|
|
420
420
|
```
|
|
421
421
|
|
|
422
|
+
* `match_none`
|
|
423
|
+
|
|
424
|
+
Simply matches none documents at all:
|
|
425
|
+
|
|
426
|
+
```ruby
|
|
427
|
+
CommentIndex.match_none
|
|
428
|
+
```
|
|
429
|
+
|
|
422
430
|
* `all`
|
|
423
431
|
|
|
424
432
|
Simply returns the criteria as is or an empty criteria when called on the index
|
|
@@ -475,8 +483,8 @@ end
|
|
|
475
483
|
```
|
|
476
484
|
|
|
477
485
|
Generally, aggregation results returned by Elasticsearch are returned as a
|
|
478
|
-
`SearchFlip::Result`, which basically is `Hashie::Mash
|
|
479
|
-
them via:
|
|
486
|
+
`SearchFlip::Result`, which basically is a `Hashie::Mash`, such that you can
|
|
487
|
+
access them via:
|
|
480
488
|
|
|
481
489
|
```ruby
|
|
482
490
|
query.aggregations(:username)["mrkamel"].revenue.value
|
|
@@ -769,6 +777,41 @@ http_client = http_client.headers(key: "value")
|
|
|
769
777
|
SearchFlip::Connection.new(base_url: "...", http_client: http_client)
|
|
770
778
|
```
|
|
771
779
|
|
|
780
|
+
## AWS Elasticsearch / Signed Requests
|
|
781
|
+
|
|
782
|
+
To use SearchFlip with AWS Elasticsearch and signed requests, you have to add
|
|
783
|
+
`aws-sdk-core` to your Gemfile and tell SearchFlip to use the
|
|
784
|
+
`SearchFlip::AwsSigv4Plugin`:
|
|
785
|
+
|
|
786
|
+
```ruby
|
|
787
|
+
require "search_flip/aws_sigv4_plugin"
|
|
788
|
+
|
|
789
|
+
MyConnection = SearchFlip::Connection.new(
|
|
790
|
+
base_url: "https://your-elasticsearch-cluster.es.amazonaws.com",
|
|
791
|
+
http_client: SearchFlip::HTTPClient.new(
|
|
792
|
+
plugins: [
|
|
793
|
+
SearchFlip::AwsSigv4Plugin.new(
|
|
794
|
+
region: "...",
|
|
795
|
+
access_key_id: "...",
|
|
796
|
+
secret_access_key: "..."
|
|
797
|
+
)
|
|
798
|
+
]
|
|
799
|
+
)
|
|
800
|
+
)
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
Again, in your index you need to specify this connection:
|
|
804
|
+
|
|
805
|
+
```ruby
|
|
806
|
+
class MyIndex
|
|
807
|
+
include SearchFlip::Index
|
|
808
|
+
|
|
809
|
+
def self.connection
|
|
810
|
+
MyConnection
|
|
811
|
+
end
|
|
812
|
+
end
|
|
813
|
+
```
|
|
814
|
+
|
|
772
815
|
## Routing and other index-time options
|
|
773
816
|
|
|
774
817
|
Override `index_options` in case you want to use routing or pass other
|
|
@@ -891,6 +934,7 @@ require "search_flip/to_json"
|
|
|
891
934
|
|
|
892
935
|
* for Elasticsearch 2.x, the delete-by-query plugin is required to delete
|
|
893
936
|
records via queries
|
|
937
|
+
* `#match_none` is only available with Elasticsearch >= 5
|
|
894
938
|
* `#track_total_hits` is only available with Elasticsearch >= 7
|
|
895
939
|
|
|
896
940
|
## Keeping your Models and Indices in Sync
|
|
@@ -914,6 +958,10 @@ It uses `after_commit` (if applicable, `after_save`, `after_destroy` and
|
|
|
914
958
|
`after_touch` otherwise) hooks to synchronously update the index when your
|
|
915
959
|
model changes.
|
|
916
960
|
|
|
961
|
+
## Semantic Versioning
|
|
962
|
+
|
|
963
|
+
SearchFlip is using Semantic Versioning: [SemVer](http://semver.org/)
|
|
964
|
+
|
|
917
965
|
## Links
|
|
918
966
|
|
|
919
967
|
* Elasticsearch: [https://www.elastic.co/](https://www.elastic.co/)
|
|
@@ -945,5 +993,4 @@ $ rspec
|
|
|
945
993
|
|
|
946
994
|
That's it.
|
|
947
995
|
|
|
948
|
-
|
|
949
996
|
[Bulk API]: https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
|
data/lib/search_flip.rb
CHANGED
|
@@ -40,7 +40,7 @@ module SearchFlip
|
|
|
40
40
|
else
|
|
41
41
|
filters = (filter_values || []) + (must_not_values || []).map { |must_not_value| { not: must_not_value } }
|
|
42
42
|
queries = must_values ? { must: must_values } : {}
|
|
43
|
-
filters_and_queries = filters + (queries.size > 0 ? [bool: queries] : [])
|
|
43
|
+
filters_and_queries = filters + (queries.size > 0 ? [{ bool: queries }] : [])
|
|
44
44
|
|
|
45
45
|
res[:filter] = filters_and_queries.size > 1 ? { and: filters_and_queries } : filters_and_queries.first
|
|
46
46
|
end
|
|
@@ -101,7 +101,7 @@ module SearchFlip
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
def respond_to_missing?(name, *args)
|
|
104
|
-
target.respond_to?(name, *args)
|
|
104
|
+
target.respond_to?(name, *args) || super
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def method_missing(name, *args, &block)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require "aws-sdk-core"
|
|
2
|
+
require "uri"
|
|
3
|
+
|
|
4
|
+
module SearchFlip
|
|
5
|
+
# The SearchFlip::AwsSigV4Plugin is a plugin for the SearchFlip::HTTPClient
|
|
6
|
+
# to be used with AWS Elasticsearch to sign requests, i.e. add signed
|
|
7
|
+
# headers, before sending the request to Elasticsearch.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# MyConnection = SearchFlip::Connection.new(
|
|
11
|
+
# base_url: "https://your-elasticsearch-cluster.es.amazonaws.com",
|
|
12
|
+
# http_client: SearchFlip::HTTPClient.new(
|
|
13
|
+
# plugins: [
|
|
14
|
+
# SearchFlip::AwsSigv4Plugin.new(
|
|
15
|
+
# region: "...",
|
|
16
|
+
# access_key_id: "...",
|
|
17
|
+
# secret_access_key: "..."
|
|
18
|
+
# )
|
|
19
|
+
# ]
|
|
20
|
+
# )
|
|
21
|
+
# )
|
|
22
|
+
|
|
23
|
+
class AwsSigv4Plugin
|
|
24
|
+
attr_accessor :signer
|
|
25
|
+
|
|
26
|
+
def initialize(options = {})
|
|
27
|
+
self.signer = Aws::Sigv4::Signer.new({ service: "es" }.merge(options))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def call(request, method, uri, options = {})
|
|
31
|
+
full_uri = URI.parse(uri)
|
|
32
|
+
full_uri.query = URI.encode_www_form(options[:params]) if options[:params]
|
|
33
|
+
|
|
34
|
+
signature_request = {
|
|
35
|
+
http_method: method.to_s.upcase,
|
|
36
|
+
url: full_uri.to_s
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
signature_request[:body] = options[:body] if options.key?(:body)
|
|
40
|
+
signature_request[:body] = options[:json].respond_to?(:to_str) ? options[:json] : JSON.generate(options[:json]) if options[:json]
|
|
41
|
+
|
|
42
|
+
signature = signer.sign_request(signature_request)
|
|
43
|
+
|
|
44
|
+
request.headers(signature.headers)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -272,7 +272,7 @@ module SearchFlip
|
|
|
272
272
|
# @return [Boolean] Returns true or raises SearchFlip::ResponseError
|
|
273
273
|
|
|
274
274
|
def refresh(index_names = nil)
|
|
275
|
-
http_client.post("#{index_names ? index_url(Array(index_names).join(",")) : base_url}/_refresh"
|
|
275
|
+
http_client.post("#{index_names ? index_url(Array(index_names).join(",")) : base_url}/_refresh")
|
|
276
276
|
|
|
277
277
|
true
|
|
278
278
|
end
|
data/lib/search_flip/criteria.rb
CHANGED
|
@@ -44,35 +44,25 @@ module SearchFlip
|
|
|
44
44
|
other = other.criteria
|
|
45
45
|
|
|
46
46
|
fresh.tap do |criteria|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
criteria.must_not_values = (criteria.must_not_values || []) + other.must_not_values if other.must_not_values
|
|
67
|
-
criteria.filter_values = (criteria.filter_values || []) + other.filter_values if other.filter_values
|
|
68
|
-
criteria.post_must_values = (criteria.post_must_values || []) + other.post_must_values if other.post_must_values
|
|
69
|
-
criteria.post_must_not_values = (criteria.post_must_not_values || []) + other.post_must_not_values if other.post_must_not_values
|
|
70
|
-
criteria.post_filter_values = (criteria.post_filter_values || []) + other.post_filter_values if other.post_filter_values
|
|
71
|
-
|
|
72
|
-
criteria.highlight_values = (criteria.highlight_values || {}).merge(other.highlight_values) if other.highlight_values
|
|
73
|
-
criteria.suggest_values = (criteria.suggest_values || {}).merge(other.suggest_values) if other.suggest_values
|
|
74
|
-
criteria.custom_value = (criteria.custom_value || {}).merge(other.custom_value) if other.custom_value
|
|
75
|
-
criteria.aggregation_values = (criteria.aggregation_values || {}).merge(other.aggregation_values) if other.aggregation_values
|
|
47
|
+
[
|
|
48
|
+
:profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :offset_value,
|
|
49
|
+
:limit_value, :scroll_args, :source_value, :preference_value, :search_type_value,
|
|
50
|
+
:routing_value, :track_total_hits_value, :explain_value
|
|
51
|
+
].each do |name|
|
|
52
|
+
criteria.send(:"#{name}=", other.send(name)) unless other.send(name).nil?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
[
|
|
56
|
+
:sort_values, :includes_values, :preload_values, :eager_load_values, :must_values,
|
|
57
|
+
:must_not_values, :filter_values, :post_must_values, :post_must_not_values,
|
|
58
|
+
:post_filter_values
|
|
59
|
+
].each do |name|
|
|
60
|
+
criteria.send(:"#{name}=", (criteria.send(name) || []) + other.send(name)) if other.send(name)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
[:highlight_values, :suggest_values, :custom_value, :aggregation_values].each do |name|
|
|
64
|
+
criteria.send(:"#{name}=", (criteria.send(name) || {}).merge(other.send(name))) if other.send(name)
|
|
65
|
+
end
|
|
76
66
|
end
|
|
77
67
|
end
|
|
78
68
|
|
|
@@ -576,7 +566,7 @@ module SearchFlip
|
|
|
576
566
|
end
|
|
577
567
|
|
|
578
568
|
def respond_to_missing?(name, *args)
|
|
579
|
-
target.respond_to?(name, *args)
|
|
569
|
+
target.respond_to?(name, *args) || super
|
|
580
570
|
end
|
|
581
571
|
|
|
582
572
|
def method_missing(name, *args, &block)
|
|
@@ -235,6 +235,22 @@ module SearchFlip
|
|
|
235
235
|
filter(match_all: options)
|
|
236
236
|
end
|
|
237
237
|
|
|
238
|
+
# Adds a match none filter to the criteria, which simply matches none
|
|
239
|
+
# documents at all. Check out the Elasticsearch docs for further details.
|
|
240
|
+
#
|
|
241
|
+
# @example Basic usage
|
|
242
|
+
# CommentIndex.match_none
|
|
243
|
+
#
|
|
244
|
+
# @example Filter chaining
|
|
245
|
+
# query = CommentIndex.search("...")
|
|
246
|
+
# query = query.match_none unless current_user.admin?
|
|
247
|
+
#
|
|
248
|
+
# @return [SearchFlip::Criteria] A newly created extended criteria
|
|
249
|
+
|
|
250
|
+
def match_none
|
|
251
|
+
filter(match_none: {})
|
|
252
|
+
end
|
|
253
|
+
|
|
238
254
|
# Adds an exists filter to the criteria, which selects all documents for
|
|
239
255
|
# which the specified field has a non-null value.
|
|
240
256
|
#
|
|
@@ -34,9 +34,10 @@ module SearchFlip
|
|
|
34
34
|
criteria.highlight_values = (criteria.highlight_values || {}).merge(options)
|
|
35
35
|
|
|
36
36
|
hash =
|
|
37
|
-
|
|
37
|
+
case fields
|
|
38
|
+
when Hash
|
|
38
39
|
fields
|
|
39
|
-
|
|
40
|
+
when Array
|
|
40
41
|
fields.each_with_object({}) { |field, h| h[field] = {} }
|
|
41
42
|
else
|
|
42
43
|
{ fields => {} }
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
module SearchFlip
|
|
2
|
-
# @api private
|
|
3
|
-
#
|
|
4
2
|
# The SearchFlip::HTTPClient class wraps the http gem, is for internal use
|
|
5
3
|
# and responsible for the http request/response handling, ie communicating
|
|
6
4
|
# with Elasticsearch.
|
|
7
5
|
|
|
8
6
|
class HTTPClient
|
|
9
|
-
attr_accessor :request
|
|
7
|
+
attr_accessor :request, :plugins
|
|
10
8
|
|
|
11
|
-
def initialize
|
|
9
|
+
def initialize(plugins: [])
|
|
12
10
|
self.request = HTTP
|
|
11
|
+
self.plugins = plugins
|
|
13
12
|
end
|
|
14
13
|
|
|
15
14
|
class << self
|
|
@@ -30,17 +29,16 @@ module SearchFlip
|
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
[:get, :post, :put, :delete, :head].each do |method|
|
|
33
|
-
define_method(method) do
|
|
34
|
-
execute(method,
|
|
32
|
+
define_method(method) do |uri, options = {}|
|
|
33
|
+
execute(method, uri, options)
|
|
35
34
|
end
|
|
36
|
-
|
|
37
|
-
ruby2_keywords method
|
|
38
35
|
end
|
|
39
36
|
|
|
40
37
|
private
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
def execute(method, uri, options = {})
|
|
40
|
+
final_request = plugins.inject(self) { |res, cur| cur.call(res, method, uri, options) }
|
|
41
|
+
response = final_request.request.send(method, uri, options)
|
|
44
42
|
|
|
45
43
|
raise SearchFlip::ResponseError.new(code: response.code, body: response.body.to_s) unless response.status.success?
|
|
46
44
|
|
data/lib/search_flip/index.rb
CHANGED
|
@@ -153,7 +153,7 @@ module SearchFlip
|
|
|
153
153
|
# scope to be applied to the scope
|
|
154
154
|
|
|
155
155
|
def each_record(scope, index_scope: false)
|
|
156
|
-
return enum_for(:each_record, scope) unless block_given?
|
|
156
|
+
return enum_for(:each_record, scope, index_scope: index_scope) unless block_given?
|
|
157
157
|
|
|
158
158
|
if scope.respond_to?(:find_each)
|
|
159
159
|
(index_scope ? self.index_scope(scope) : scope).find_each do |record|
|
|
@@ -247,8 +247,8 @@ module SearchFlip
|
|
|
247
247
|
SearchFlip::Criteria.new(target: self)
|
|
248
248
|
end
|
|
249
249
|
|
|
250
|
-
def_delegators :criteria, :all, :profile, :where, :where_not, :filter, :range, :match_all, :
|
|
251
|
-
:exists_not, :post_where, :post_where_not, :post_range, :post_exists, :post_exists_not,
|
|
250
|
+
def_delegators :criteria, :all, :profile, :where, :where_not, :filter, :range, :match_all, :match_none,
|
|
251
|
+
:exists, :exists_not, :post_where, :post_where_not, :post_range, :post_exists, :post_exists_not,
|
|
252
252
|
:post_filter, :post_must, :post_must_not, :post_should, :aggregate, :scroll, :source,
|
|
253
253
|
:includes, :eager_load, :preload, :sort, :resort, :order, :reorder, :offset, :limit, :paginate,
|
|
254
254
|
:page, :per, :search, :highlight, :suggest, :custom, :find_in_batches, :find_results_in_batches,
|
data/lib/search_flip/result.rb
CHANGED
|
@@ -17,7 +17,7 @@ module SearchFlip
|
|
|
17
17
|
# SearchFlip::Result.from_hit(top_sales_hits.first)
|
|
18
18
|
|
|
19
19
|
def self.from_hit(hit)
|
|
20
|
-
raw_result = hit["_source"].dup
|
|
20
|
+
raw_result = (hit["_source"] || {}).dup
|
|
21
21
|
|
|
22
22
|
raw_result["_hit"] = hit.each_with_object({}) do |(key, value), hash|
|
|
23
23
|
hash[key] = value if key != "_source"
|
data/lib/search_flip/version.rb
CHANGED
data/search_flip.gemspec
CHANGED
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
MESSAGE
|
|
26
26
|
|
|
27
27
|
spec.add_development_dependency "activerecord", ">= 3.0"
|
|
28
|
+
spec.add_development_dependency "aws-sdk-core"
|
|
28
29
|
spec.add_development_dependency "bundler"
|
|
29
30
|
spec.add_development_dependency "factory_bot"
|
|
30
31
|
spec.add_development_dependency "rake"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require File.expand_path("../spec_helper", __dir__)
|
|
2
|
+
require "search_flip/aws_sigv4_plugin"
|
|
3
|
+
|
|
4
|
+
RSpec.describe SearchFlip::AwsSigv4Plugin do
|
|
5
|
+
describe "#call" do
|
|
6
|
+
subject(:plugin) do
|
|
7
|
+
SearchFlip::AwsSigv4Plugin.new(
|
|
8
|
+
region: "us-east-1",
|
|
9
|
+
access_key_id: "access key",
|
|
10
|
+
secret_access_key: "secret access key"
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
let(:client) { SearchFlip::HTTPClient.new }
|
|
15
|
+
|
|
16
|
+
it "adds the signed headers to the request" do
|
|
17
|
+
Timecop.freeze Time.parse("2020-01-01 12:00:00 UTC") do
|
|
18
|
+
expect(client).to receive(:headers).with(
|
|
19
|
+
"host" => "localhost",
|
|
20
|
+
"authorization" => /.*/,
|
|
21
|
+
"x-amz-content-sha256" => /.*/,
|
|
22
|
+
"x-amz-date" => /20200101T120000Z/
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
plugin.call(client, :get, "http://localhost/index")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "feeds the http method, full url and body to the signer" do
|
|
30
|
+
signing_request = {
|
|
31
|
+
http_method: "GET",
|
|
32
|
+
url: "http://localhost/index?param=value",
|
|
33
|
+
body: JSON.generate(key: "value")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
expect(plugin.signer).to receive(:sign_request).with(signing_request).and_call_original
|
|
37
|
+
|
|
38
|
+
plugin.call(client, :get, "http://localhost/index", params: { param: "value" }, json: { key: "value" })
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -36,10 +36,10 @@ RSpec.describe SearchFlip::Connection do
|
|
|
36
36
|
it "changes the aliases" do
|
|
37
37
|
connection = SearchFlip::Connection.new
|
|
38
38
|
|
|
39
|
-
connection.update_aliases(actions: [add: { index: "products", alias: "alias1" }])
|
|
39
|
+
connection.update_aliases(actions: [{ add: { index: "products", alias: "alias1" } }])
|
|
40
40
|
expect(connection.get_aliases(alias_name: "alias1").keys).to eq(["products"])
|
|
41
41
|
|
|
42
|
-
connection.update_aliases(actions: [remove: { index: "products", alias: "alias1" }])
|
|
42
|
+
connection.update_aliases(actions: [{ remove: { index: "products", alias: "alias1" } }])
|
|
43
43
|
expect(connection.alias_exists?("alias1")).to eq(false)
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -79,11 +79,11 @@ RSpec.describe SearchFlip::Connection do
|
|
|
79
79
|
|
|
80
80
|
expect(connection.alias_exists?(:some_alias)).to eq(false)
|
|
81
81
|
|
|
82
|
-
connection.update_aliases(actions: [add: { index: "products", alias: "some_alias" }])
|
|
82
|
+
connection.update_aliases(actions: [{ add: { index: "products", alias: "some_alias" } }])
|
|
83
83
|
|
|
84
84
|
expect(connection.alias_exists?(:some_alias)).to eq(true)
|
|
85
85
|
ensure
|
|
86
|
-
connection.update_aliases(actions: [remove: { index: "products", alias: "some_alias" }])
|
|
86
|
+
connection.update_aliases(actions: [{ remove: { index: "products", alias: "some_alias" } }])
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
end
|
|
@@ -110,6 +110,16 @@ RSpec.describe SearchFlip::Criteria do
|
|
|
110
110
|
|
|
111
111
|
expect(criteria1.merge(criteria2).send(method)).to eq("value2")
|
|
112
112
|
end
|
|
113
|
+
|
|
114
|
+
it "handles false values correctly" do
|
|
115
|
+
criteria1 = SearchFlip::Criteria.new(target: TestIndex)
|
|
116
|
+
criteria1.send("#{method}=", true)
|
|
117
|
+
|
|
118
|
+
criteria2 = SearchFlip::Criteria.new(target: TestIndex)
|
|
119
|
+
criteria2.send("#{method}=", false)
|
|
120
|
+
|
|
121
|
+
expect(criteria1.merge(criteria2).send(method)).to eq(false)
|
|
122
|
+
end
|
|
113
123
|
end
|
|
114
124
|
end
|
|
115
125
|
|
|
@@ -406,6 +416,18 @@ RSpec.describe SearchFlip::Criteria do
|
|
|
406
416
|
end
|
|
407
417
|
end
|
|
408
418
|
|
|
419
|
+
describe "#match_none" do
|
|
420
|
+
it "does not match any documents" do
|
|
421
|
+
if ProductIndex.connection.version.to_i >= 5
|
|
422
|
+
ProductIndex.import create(:product)
|
|
423
|
+
|
|
424
|
+
query = ProductIndex.match_none
|
|
425
|
+
|
|
426
|
+
expect(query.records).to eq([])
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
409
431
|
describe "#exists" do
|
|
410
432
|
it "sets up the constraints correctly and is chainable" do
|
|
411
433
|
product1 = create(:product, title: "title1", description: "description1")
|
|
@@ -39,6 +39,23 @@ RSpec.describe SearchFlip::HTTPClient do
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
describe "plugins" do
|
|
43
|
+
subject do
|
|
44
|
+
SearchFlip::HTTPClient.new(
|
|
45
|
+
plugins: [
|
|
46
|
+
->(request, _method, _uri, _options = {}) { request.headers("First-Header" => "Value") },
|
|
47
|
+
->(request, _method, _uri, _options = {}) { request.headers("Second-Header" => "Value") }
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "injects the plugins and uses their result in the request" do
|
|
53
|
+
stub_request(:get, "http://localhost/path").with(query: { key: "value" }, headers: { "First-Header" => "Value", "Second-Header" => "Value" }).and_return(body: "success")
|
|
54
|
+
|
|
55
|
+
expect(subject.get("http://localhost/path", params: { key: "value" }).body.to_s).to eq("success")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
42
59
|
[:via, :basic_auth, :auth].each do |method|
|
|
43
60
|
describe "##{method}" do
|
|
44
61
|
it "creates a dupped instance" do
|
|
@@ -5,8 +5,8 @@ RSpec.describe SearchFlip::Index do
|
|
|
5
5
|
subject { ProductIndex }
|
|
6
6
|
|
|
7
7
|
methods = [
|
|
8
|
-
:all, :profile, :where, :where_not, :filter, :range, :match_all, :
|
|
9
|
-
:exists_not, :post_where, :post_where_not, :post_filter, :post_must,
|
|
8
|
+
:all, :profile, :where, :where_not, :filter, :range, :match_all, :match_none,
|
|
9
|
+
:exists, :exists_not, :post_where, :post_where_not, :post_filter, :post_must,
|
|
10
10
|
:post_must_not, :post_should, :post_range, :post_exists, :post_exists_not,
|
|
11
11
|
:aggregate, :scroll, :source, :includes, :eager_load, :preload, :sort, :resort,
|
|
12
12
|
:order, :reorder, :offset, :limit, :paginate, :page, :per, :search,
|
|
@@ -211,12 +211,18 @@ RSpec.describe SearchFlip::Index do
|
|
|
211
211
|
mapping = { properties: { id: { type: "long" } } }
|
|
212
212
|
|
|
213
213
|
allow(TestIndex).to receive(:mapping).and_return(mapping)
|
|
214
|
-
allow(TestIndex.connection).to receive(:update_mapping)
|
|
214
|
+
allow(TestIndex.connection).to receive(:update_mapping)
|
|
215
215
|
|
|
216
216
|
TestIndex.update_mapping
|
|
217
217
|
|
|
218
218
|
expect(TestIndex.connection).to have_received(:update_mapping).with("test", { "test" => mapping }, type_name: "test")
|
|
219
219
|
end
|
|
220
|
+
|
|
221
|
+
it "updates the mapping" do
|
|
222
|
+
TestIndex.create_index
|
|
223
|
+
|
|
224
|
+
expect(TestIndex.update_mapping).to eq(true)
|
|
225
|
+
end
|
|
220
226
|
end
|
|
221
227
|
end
|
|
222
228
|
|
|
@@ -258,12 +264,19 @@ RSpec.describe SearchFlip::Index do
|
|
|
258
264
|
TestIndex.create_index
|
|
259
265
|
TestIndex.update_mapping
|
|
260
266
|
|
|
261
|
-
allow(TestIndex.connection).to receive(:get_mapping)
|
|
267
|
+
allow(TestIndex.connection).to receive(:get_mapping)
|
|
262
268
|
|
|
263
269
|
TestIndex.get_mapping
|
|
264
270
|
|
|
265
271
|
expect(TestIndex.connection).to have_received(:get_mapping).with("test", type_name: "test")
|
|
266
272
|
end
|
|
273
|
+
|
|
274
|
+
it "returns the mapping" do
|
|
275
|
+
TestIndex.create_index
|
|
276
|
+
TestIndex.update_mapping
|
|
277
|
+
|
|
278
|
+
expect(TestIndex.get_mapping).to be_present
|
|
279
|
+
end
|
|
267
280
|
end
|
|
268
281
|
end
|
|
269
282
|
|
|
@@ -7,7 +7,7 @@ RSpec.describe SearchFlip::NullInstrumenter do
|
|
|
7
7
|
it "calls start" do
|
|
8
8
|
allow(subject).to receive(:start)
|
|
9
9
|
|
|
10
|
-
subject.instrument("name", { key: "value" }) {}
|
|
10
|
+
subject.instrument("name", { key: "value" }) { true }
|
|
11
11
|
|
|
12
12
|
expect(subject).to have_received(:start)
|
|
13
13
|
end
|
|
@@ -15,7 +15,7 @@ RSpec.describe SearchFlip::NullInstrumenter do
|
|
|
15
15
|
it "calls finish" do
|
|
16
16
|
allow(subject).to receive(:finish)
|
|
17
17
|
|
|
18
|
-
subject.instrument("name", { key: "value" }) {}
|
|
18
|
+
subject.instrument("name", { key: "value" }) { true }
|
|
19
19
|
|
|
20
20
|
expect(subject).to have_received(:finish)
|
|
21
21
|
end
|
|
@@ -13,5 +13,11 @@ RSpec.describe SearchFlip::Result do
|
|
|
13
13
|
|
|
14
14
|
expect(result.key1[0].key2).to eq("value")
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
it "works with the _source being disabled" do
|
|
18
|
+
result = SearchFlip::Result.from_hit("_id" => 1)
|
|
19
|
+
|
|
20
|
+
expect(result._hit._id).to eq(1)
|
|
21
|
+
end
|
|
16
22
|
end
|
|
17
23
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: search_flip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Benjamin Vetter
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-03-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '3.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: aws-sdk-core
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: bundler
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -199,9 +213,9 @@ executables: []
|
|
|
199
213
|
extensions: []
|
|
200
214
|
extra_rdoc_files: []
|
|
201
215
|
files:
|
|
216
|
+
- ".github/workflows/test.yml"
|
|
202
217
|
- ".gitignore"
|
|
203
218
|
- ".rubocop.yml"
|
|
204
|
-
- ".travis.yml"
|
|
205
219
|
- CHANGELOG.md
|
|
206
220
|
- Gemfile
|
|
207
221
|
- LICENSE.txt
|
|
@@ -212,6 +226,7 @@ files:
|
|
|
212
226
|
- lib/search_flip.rb
|
|
213
227
|
- lib/search_flip/aggregatable.rb
|
|
214
228
|
- lib/search_flip/aggregation.rb
|
|
229
|
+
- lib/search_flip/aws_sigv4_plugin.rb
|
|
215
230
|
- lib/search_flip/bulk.rb
|
|
216
231
|
- lib/search_flip/config.rb
|
|
217
232
|
- lib/search_flip/connection.rb
|
|
@@ -238,6 +253,7 @@ files:
|
|
|
238
253
|
- search_flip.gemspec
|
|
239
254
|
- spec/delegate_matcher.rb
|
|
240
255
|
- spec/search_flip/aggregation_spec.rb
|
|
256
|
+
- spec/search_flip/aws_sigv4_plugin_spec.rb
|
|
241
257
|
- spec/search_flip/bulk_spec.rb
|
|
242
258
|
- spec/search_flip/connection_spec.rb
|
|
243
259
|
- spec/search_flip/criteria_spec.rb
|
|
@@ -271,13 +287,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
271
287
|
- !ruby/object:Gem::Version
|
|
272
288
|
version: '0'
|
|
273
289
|
requirements: []
|
|
274
|
-
rubygems_version: 3.
|
|
290
|
+
rubygems_version: 3.2.3
|
|
275
291
|
signing_key:
|
|
276
292
|
specification_version: 4
|
|
277
293
|
summary: Full-Featured Elasticsearch Ruby Client with a Chainable DSL
|
|
278
294
|
test_files:
|
|
279
295
|
- spec/delegate_matcher.rb
|
|
280
296
|
- spec/search_flip/aggregation_spec.rb
|
|
297
|
+
- spec/search_flip/aws_sigv4_plugin_spec.rb
|
|
281
298
|
- spec/search_flip/bulk_spec.rb
|
|
282
299
|
- spec/search_flip/connection_spec.rb
|
|
283
300
|
- spec/search_flip/criteria_spec.rb
|
data/.travis.yml
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
sudo: false
|
|
2
|
-
language: ruby
|
|
3
|
-
env:
|
|
4
|
-
- ES_IMAGE=plainpicture/elasticsearch:2.4.1_delete-by-query
|
|
5
|
-
- ES_IMAGE=elasticsearch:5.4
|
|
6
|
-
- ES_IMAGE=docker.elastic.co/elasticsearch/elasticsearch:6.7.0
|
|
7
|
-
- ES_IMAGE=docker.elastic.co/elasticsearch/elasticsearch:7.0.0
|
|
8
|
-
- ES_IMAGE=docker.elastic.co/elasticsearch/elasticsearch:7.6.0
|
|
9
|
-
rvm:
|
|
10
|
-
- ruby-2.5.3
|
|
11
|
-
- ruby-2.6.2
|
|
12
|
-
- ruby-2.7.1
|
|
13
|
-
before_install:
|
|
14
|
-
- docker-compose up -d
|
|
15
|
-
- sleep 10
|
|
16
|
-
install:
|
|
17
|
-
- travis_retry bundle install
|
|
18
|
-
script:
|
|
19
|
-
- bundle exec rspec
|
|
20
|
-
- bundle exec rubocop
|