search_flip 4.0.0.beta11 → 4.0.0.beta14

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e032e109a7c31df14182c17d874f793327081c3c4e5b866dbfb261e8aa4e503
4
- data.tar.gz: 73a2ce2eb918e9f3da28f3bf129eb614c1da5492d138985732af4b41a9f15a41
3
+ metadata.gz: a711d443c26f483da4898b2b7e69d1ca7c2dfd1280cd4d0a7d8ae6affbcdbc0d
4
+ data.tar.gz: a5721bc56cc311ce664f49d89229b79fdfe525357c33b6ff9b46e5c7bc6e634f
5
5
  SHA512:
6
- metadata.gz: 152130c3a91ae69dcf53ff43534b0b853786900f1c03847ffead4d52254f95aec5dbfb48362d6386a61fdb92f128b68fc22b2b66f4df27f2a099a9d6db356eea
7
- data.tar.gz: '09f13f81114378ebd9dea98ae713f5be8a27dea15966bf2fc05195900be8ed74c73d6aa46d6a458a4d4aaeb8467e32cc4296fce4e22dbfee672a1dc30d4af14f'
6
+ metadata.gz: 43c8b33aaa23e84e65b93a624806664c7f7ea779e582ba010b21319ea8b597c4b53633edf9f6746762b2dce844a0ad0764fc1c2209fdee2ab19aea60a7dbbad4
7
+ data.tar.gz: 22c06e14023d4bd43475f3573b1e387bb855e43fdb0babc9b366e0f7388ecde08eaee53598d0dda148ecf1f62afb5ed0b328f91742471e7bd8f3e5c7349df146
@@ -7,30 +7,54 @@ jobs:
7
7
  fail-fast: false
8
8
  matrix:
9
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
- - docker.elastic.co/elasticsearch/elasticsearch:8.1.1
10
+ - image: plainpicture/elasticsearch:2.4.1_delete-by-query
11
+ env:
12
+ discovery.type: single-node
13
+ xpack.security.enabled: false
14
+ - image: elasticsearch:5.4
15
+ env:
16
+ discovery.type: single-node
17
+ xpack.security.enabled: false
18
+ - image: docker.elastic.co/elasticsearch/elasticsearch:6.7.0
19
+ env:
20
+ discovery.type: single-node
21
+ xpack.security.enabled: false
22
+ - image: docker.elastic.co/elasticsearch/elasticsearch:7.0.0
23
+ env:
24
+ discovery.type: single-node
25
+ xpack.security.enabled: false
26
+ - image: docker.elastic.co/elasticsearch/elasticsearch:7.11.2
27
+ env:
28
+ discovery.type: single-node
29
+ xpack.security.enabled: false
30
+ - image: docker.elastic.co/elasticsearch/elasticsearch:8.1.1
31
+ env:
32
+ discovery.type: single-node
33
+ xpack.security.enabled: false
34
+ - image: opensearchproject/opensearch:1.3.14
35
+ env:
36
+ discovery.type: single-node
37
+ plugins.security.disabled: true
38
+ - image: opensearchproject/opensearch:2.11.1
39
+ env:
40
+ discovery.type: single-node
41
+ plugins.security.disabled: true
16
42
  ruby:
17
- - 2.6
18
43
  - 2.7
19
44
  - 3.0
45
+ - 3.1
46
+ - 3.2
20
47
  services:
21
48
  elasticsearch:
22
- image: ${{ matrix.elasticsearch }}
23
- env:
24
- discovery.type: single-node
25
- xpack.security.enabled: false
49
+ image: ${{ matrix.elasticsearch.image }}
50
+ env: ${{ matrix.elasticsearch.env }}
26
51
  ports:
27
52
  - 9200:9200
28
53
  steps:
29
54
  - uses: actions/checkout@v1
30
- - uses: actions/setup-ruby@v1
55
+ - uses: ruby/setup-ruby@v1
31
56
  with:
32
57
  ruby-version: ${{ matrix.ruby }}
33
- - run: gem install bundler
34
58
  - run: bundle
35
59
  - run: bundle exec rspec
36
60
  - run: bundle exec rubocop
data/CHANGELOG.md CHANGED
@@ -11,6 +11,18 @@
11
11
  * Added `SearchFlip::Connection#get_cluster_settings` and
12
12
  `#update_cluster_settings`
13
13
 
14
+ ## v3.8.0.
15
+
16
+ * Support Opensearch 1.x and 2.x
17
+
18
+ ## v3.7.2
19
+
20
+ * Fix wrong AWS signatures by generating the json before passing it to http-rb
21
+
22
+ ## v3.7.1
23
+
24
+ * Fix thread-safety issue of http-rb
25
+
14
26
  ## v3.7.0
15
27
 
16
28
  * Add `SearchFlip::Connection#bulk` to allow more clean bulk indexing to
data/Gemfile CHANGED
@@ -2,4 +2,18 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem "activerecord", "< 5" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.2.2")
5
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.2.2")
6
+ gem "activerecord", ">= 3.0", "< 5"
7
+ else
8
+ gem "activerecord", ">= 3.0"
9
+ end
10
+
11
+ gem "aws-sdk-core"
12
+ gem "bundler"
13
+ gem "factory_bot"
14
+ gem "rake"
15
+ gem "rspec"
16
+ gem "rubocop"
17
+ gem "sqlite3"
18
+ gem "timecop"
19
+ gem "webmock"
data/README.md CHANGED
@@ -9,8 +9,9 @@
9
9
  Using SearchFlip it is dead-simple to create index classes that correspond to
10
10
  [Elasticsearch](https://www.elastic.co/) indices and to manipulate, query and
11
11
  aggregate these indices using a chainable, concise, yet powerful DSL. Finally,
12
- SearchFlip supports Elasticsearch 2.x, 5.x, 6.x, 7.x and 8.x. Check section
13
- [Feature Support](#feature-support) for version dependent features.
12
+ SearchFlip supports Elasticsearch 2.x, 5.x, 6.x, 7.x and 8.x as well as
13
+ Opensearch 1.x and 2.x. Check section [Feature Support](#feature-support) for
14
+ version dependent features.
14
15
 
15
16
  ```ruby
16
17
  CommentIndex.search("hello world", default_field: "title").where(visible: true).aggregate(:user_id).sort(id: "desc")
@@ -852,7 +853,7 @@ performance tracing, etc.
852
853
  To use instrumentation, configure the instrumenter:
853
854
 
854
855
  ```ruby
855
- SearchFlip::Config[:instrumenter] = ActiveSupport::Notifications.notifier
856
+ SearchFlip::Config[:instrumenter] = ActiveSupport::Notifications
856
857
  ```
857
858
 
858
859
  Subsequently, you can subscribe to notifcations for `request.search_flip`:
@@ -970,7 +971,6 @@ SearchFlip is using Semantic Versioning: [SemVer](http://semver.org/)
970
971
 
971
972
  * Elasticsearch: [https://www.elastic.co/](https://www.elastic.co/)
972
973
  * Reference Docs: [http://www.rubydoc.info/github/mrkamel/search_flip](http://www.rubydoc.info/github/mrkamel/search_flip)
973
- * Travis: [http://travis-ci.org/mrkamel/search_flip](http://travis-ci.org/mrkamel/search_flip)
974
974
  * will_paginate: [https://github.com/mislav/will_paginate](https://github.com/mislav/will_paginate)
975
975
  * kaminari: [https://github.com/kaminari/kaminari](https://github.com/kaminari/kaminari)
976
976
  * Oj: [https://github.com/ohler55/oj](https://github.com/ohler55/oj)
data/UPDATING.md CHANGED
@@ -8,14 +8,14 @@ inherits `Hashie::Mash`
8
8
 
9
9
  * It no longer supports symbol based access like `result[:id]`
10
10
 
11
- 2.x:
11
+ 3.x:
12
12
 
13
13
  ```ruby
14
14
  CommentIndex.match_all.results.first[:id]
15
15
  CommentIndex.aggregate(:tags).aggregations(:tags).values.first[:doc_count]
16
16
  ```
17
17
 
18
- 3.x
18
+ 4.x
19
19
 
20
20
  ```ruby
21
21
  CommentIndex.match_all.results.first["id"] # or .id
@@ -24,16 +24,20 @@ CommentIndex.aggregate(:tags).aggregations(:tags).values.first["doc_count"] # or
24
24
 
25
25
  * It no longer supports question mark methods like `result.title?`
26
26
 
27
- 2.x:
27
+ 3.x:
28
28
 
29
29
  ```ruby
30
30
  CommentIndex.match_all.results.first.is_published?
31
31
  ```
32
32
 
33
- 3.x
33
+ 4.x
34
34
 
35
35
  ```ruby
36
36
  CommentIndex.match_all.results.first.is_published == true
37
+
38
+ # or
39
+
40
+ !! CommentIndex.match_all.results.first.is_published
37
41
  ```
38
42
 
39
43
  * It no longer supports method based assignment like `result.some_key = "value"`.
@@ -30,7 +30,7 @@ module SearchFlip
30
30
  res[:aggregations] = aggregation_values if aggregation_values
31
31
 
32
32
  if must_values || must_not_values || filter_values
33
- if target.connection.version.to_i >= 2
33
+ if target.connection.distribution || target.connection.version.to_i >= 2
34
34
  res[:filter] = {
35
35
  bool: {}
36
36
  .merge(must_values ? { must: must_values } : {})
@@ -37,7 +37,6 @@ module SearchFlip
37
37
  }
38
38
 
39
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
40
 
42
41
  signature = signer.sign_request(signature_request)
43
42
 
@@ -148,7 +148,7 @@ module SearchFlip
148
148
  return unless parsed_response["errors"]
149
149
 
150
150
  parsed_response["items"].each do |item|
151
- item.each do |_, element|
151
+ item.each_value do |element|
152
152
  status = element["status"]
153
153
 
154
154
  next if status.between?(200, 299)
@@ -16,7 +16,17 @@ module SearchFlip
16
16
  @base_url = options[:base_url] || SearchFlip::Config[:base_url]
17
17
  @http_client = options[:http_client] || SearchFlip::HTTPClient.new
18
18
  @bulk_limit = options[:bulk_limit] || SearchFlip::Config[:bulk_limit]
19
- @version_mutex = Mutex.new
19
+ end
20
+
21
+ # Queries and returns the Elasticsearch distribution used.
22
+ #
23
+ # @example
24
+ # connection.distribution # => e.g. "opensearch"
25
+ #
26
+ # @return [String] The Elasticsearch distribution
27
+
28
+ def distribution
29
+ @distribution ||= SearchFlip::JSON.parse(version_response.to_s)["version"]["distribution"]
20
30
  end
21
31
 
22
32
  # Queries the cluster settings from Elasticsearch
@@ -51,18 +61,12 @@ module SearchFlip
51
61
  # Queries and returns the Elasticsearch version used.
52
62
  #
53
63
  # @example
54
- # connection.version # => e.g. 2.4.1
64
+ # connection.version # => e.g. "2.4.1"
55
65
  #
56
66
  # @return [String] The Elasticsearch version
57
67
 
58
68
  def version
59
- @version_mutex.synchronize do
60
- @version ||= begin
61
- response = http_client.headers(accept: "application/json").get("#{base_url}/")
62
-
63
- SearchFlip::JSON.parse(response.to_s)["version"]["number"]
64
- end
65
- end
69
+ @version ||= SearchFlip::JSON.parse(version_response.to_s)["version"]["number"]
66
70
  end
67
71
 
68
72
  # Queries and returns the Elasticsearch cluster health.
@@ -93,7 +97,7 @@ module SearchFlip
93
97
  def msearch(criterias)
94
98
  payload = criterias.flat_map do |criteria|
95
99
  [
96
- SearchFlip::JSON.generate(index: criteria.target.index_name_with_prefix, **(version.to_i < 8 ? { type: criteria.target.type_name } : {})),
100
+ SearchFlip::JSON.generate(index: criteria.target.index_name_with_prefix, **(distribution.nil? && version.to_i < 8 ? { type: criteria.target.type_name } : {})),
97
101
  SearchFlip::JSON.generate(criteria.request)
98
102
  ]
99
103
  end
@@ -329,8 +333,8 @@ module SearchFlip
329
333
  # @return [Boolean] Returns true or raises SearchFlip::ResponseError
330
334
 
331
335
  def update_mapping(index_name, mapping, type_name: nil)
332
- url = type_name && version.to_i < 8 ? type_url(index_name, type_name) : index_url(index_name)
333
- params = type_name && version.to_f >= 6.7 && version.to_i < 8 ? { include_type_name: true } : {}
336
+ url = type_name && distribution.nil? && version.to_i < 8 ? type_url(index_name, type_name) : index_url(index_name)
337
+ params = type_name && distribution.nil? && version.to_f >= 6.7 && version.to_i < 8 ? { include_type_name: true } : {}
334
338
 
335
339
  http_client.put("#{url}/_mapping", params: params, json: mapping)
336
340
 
@@ -347,8 +351,8 @@ module SearchFlip
347
351
  # @return [Hash] The current type mapping
348
352
 
349
353
  def get_mapping(index_name, type_name: nil)
350
- url = type_name && version.to_i < 8 ? type_url(index_name, type_name) : index_url(index_name)
351
- params = type_name && version.to_f >= 6.7 && version.to_i < 8 ? { include_type_name: true } : {}
354
+ url = type_name && distribution.nil? && version.to_i < 8 ? type_url(index_name, type_name) : index_url(index_name)
355
+ params = type_name && distribution.nil? && version.to_f >= 6.7 && version.to_i < 8 ? { include_type_name: true } : {}
352
356
 
353
357
  response = http_client.headers(accept: "application/json").get("#{url}/_mapping", params: params)
354
358
 
@@ -451,5 +455,11 @@ module SearchFlip
451
455
  def index_url(index_name)
452
456
  "#{base_url}/#{index_name}"
453
457
  end
458
+
459
+ private
460
+
461
+ def version_response
462
+ @version_response ||= http_client.headers(accept: "application/json").get("#{base_url}/")
463
+ end
454
464
  end
455
465
  end
@@ -350,8 +350,8 @@ module SearchFlip
350
350
  http_request = connection.http_client
351
351
  http_request = http_request.timeout(http_timeout_value) if http_timeout_value
352
352
 
353
- if connection.version.to_i >= 5
354
- url = connection.version.to_i < 8 ? target.type_url : target.index_url
353
+ if connection.distribution || connection.version.to_i >= 5
354
+ url = connection.distribution.nil? && connection.version.to_i < 8 ? target.type_url : target.index_url
355
355
 
356
356
  http_request.post("#{url}/_delete_by_query", params: request_params.merge(params), json: dupped_request)
357
357
  else
@@ -622,7 +622,7 @@ module SearchFlip
622
622
  json: { scroll: scroll_args[:timeout], scroll_id: scroll_args[:id] }
623
623
  )
624
624
  elsif scroll_args
625
- url = connection.version.to_i < 8 ? target.type_url : target.index_url
625
+ url = connection.distribution.nil? && connection.version.to_i < 8 ? target.type_url : target.index_url
626
626
 
627
627
  http_request.post(
628
628
  "#{url}/_search",
@@ -630,7 +630,7 @@ module SearchFlip
630
630
  json: request
631
631
  )
632
632
  else
633
- url = connection.version.to_i < 8 ? target.type_url : target.index_url
633
+ url = connection.distribution.nil? && connection.version.to_i < 8 ? target.type_url : target.index_url
634
634
 
635
635
  http_request.post("#{url}/_search", params: request_params, json: request)
636
636
  end
@@ -58,8 +58,22 @@ module SearchFlip
58
58
  private
59
59
 
60
60
  def execute(method, uri, options = {})
61
- final_request = plugins.inject(self) { |res, cur| cur.call(res, method, uri, options) }
62
- response = final_request.request.send(method, uri, options)
61
+ opts = options.dup
62
+ final_request = self
63
+
64
+ if opts[:json]
65
+ # Manually generate and pass the json body to http-rb to guarantee that
66
+ # we have the same json which is used for aws signatures and to
67
+ # guarantee that json is always generated as stated in the config
68
+
69
+ opts[:body] = JSON.generate(opts.delete(:json))
70
+ final_request = final_request.headers(content_type: "application/json")
71
+ end
72
+
73
+ final_request = plugins.inject(final_request) { |res, cur| cur.call(res, method, uri, opts) }
74
+ final_request = final_request.headers({}) # Prevent thread-safety issue of http-rb: https://github.com/httprb/http/issues/558
75
+
76
+ response = final_request.request.send(method, uri, opts)
63
77
 
64
78
  raise SearchFlip::ResponseError.new(code: response.code, body: response.body.to_s) unless response.status.success?
65
79
 
@@ -438,7 +438,7 @@ module SearchFlip
438
438
  # equal to _doc.
439
439
 
440
440
  def include_type_name?
441
- type_name != "_doc" || connection.version.to_i < 7
441
+ type_name != "_doc" || (connection.distribution.nil? && connection.version.to_i < 7)
442
442
  end
443
443
 
444
444
  # Retrieves the document specified by id from Elasticsearch. Raises
@@ -455,7 +455,7 @@ module SearchFlip
455
455
  # @return [Hash] The specified document
456
456
 
457
457
  def get(id, params = {})
458
- url = connection.version.to_i < 8 ? type_url : "#{index_url}/_doc"
458
+ url = connection.distribution.nil? && connection.version.to_i < 8 ? type_url : "#{index_url}/_doc"
459
459
  response = connection.http_client.headers(accept: "application/json").get("#{url}/#{id}", params: params)
460
460
 
461
461
  SearchFlip::JSON.parse(response.to_s)
@@ -474,7 +474,7 @@ module SearchFlip
474
474
  # @return [Hash] The raw response
475
475
 
476
476
  def mget(request, params = {})
477
- url = connection.version.to_i < 8 ? type_url : index_url
477
+ url = connection.distribution.nil? && connection.version.to_i < 8 ? type_url : index_url
478
478
  response = connection.http_client.headers(accept: "application/json").post("#{url}/_mget", json: request, params: params)
479
479
 
480
480
  SearchFlip::JSON.parse(response.to_s)
@@ -633,7 +633,7 @@ module SearchFlip
633
633
  bulk_max_mb: connection.bulk_max_mb
634
634
  }
635
635
 
636
- url = connection.version.to_i < 8 ? type_url : index_url
636
+ url = connection.distribution.nil? && connection.version.to_i < 8 ? type_url : index_url
637
637
 
638
638
  SearchFlip::Bulk.new("#{url}/_bulk", default_options.merge(options)) do |indexer|
639
639
  yield indexer
@@ -223,7 +223,7 @@ module SearchFlip
223
223
 
224
224
  def records
225
225
  @records ||= begin
226
- sort_map = ids.each_with_index.each_with_object({}) { |(id, index), hash| hash[id.to_s] = index }
226
+ sort_map = ids.each_with_index.with_object({}) { |(id, index), hash| hash[id.to_s] = index }
227
227
 
228
228
  scope.to_a.sort_by { |record| sort_map[criteria.target.record_id(record).to_s] }
229
229
  end
@@ -1,3 +1,3 @@
1
1
  module SearchFlip
2
- VERSION = "4.0.0.beta11"
2
+ VERSION = "4.0.0.beta14"
3
3
  end
data/search_flip.gemspec CHANGED
@@ -13,6 +13,10 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://github.com/mrkamel/search_flip"
14
14
  spec.license = "MIT"
15
15
 
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/mrkamel/search_flip"
18
+ spec.metadata["changelog_uri"] = "https://github.com/mrkamel/search_flip/blob/master/CHANGELOG.md"
19
+
16
20
  spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
21
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
22
  spec.require_paths = ["lib"]
@@ -23,17 +27,6 @@ Gem::Specification.new do |spec|
23
27
  https://github.com/mrkamel/search_flip/blob/master/UPDATING.md
24
28
  MESSAGE
25
29
 
26
- spec.add_development_dependency "activerecord", ">= 3.0"
27
- spec.add_development_dependency "aws-sdk-core"
28
- spec.add_development_dependency "bundler"
29
- spec.add_development_dependency "factory_bot"
30
- spec.add_development_dependency "rake"
31
- spec.add_development_dependency "rspec"
32
- spec.add_development_dependency "rubocop"
33
- spec.add_development_dependency "sqlite3"
34
- spec.add_development_dependency "timecop"
35
- spec.add_development_dependency "webmock"
36
-
37
30
  spec.add_dependency "http"
38
31
  spec.add_dependency "json"
39
32
  spec.add_dependency "oj"
@@ -29,15 +29,17 @@ RSpec.describe SearchFlip::AwsSigv4Plugin do
29
29
  end
30
30
 
31
31
  it "feeds the http method, full url and body to the signer" do
32
+ body = JSON.generate(key: "value")
33
+
32
34
  signing_request = {
33
35
  http_method: "GET",
34
36
  url: "http://localhost/index?param=value",
35
- body: JSON.generate(key: "value")
37
+ body: body
36
38
  }
37
39
 
38
40
  expect(plugin.signer).to receive(:sign_request).with(signing_request).and_call_original
39
41
 
40
- plugin.call(client, :get, "http://localhost/index", params: { param: "value" }, json: { key: "value" })
42
+ plugin.call(client, :get, "http://localhost/index", params: { param: "value" }, body: body)
41
43
  end
42
44
  end
43
45
  end
@@ -60,7 +60,7 @@ RSpec.describe SearchFlip::Bulk do
60
60
 
61
61
  it "uses the specified http_client" do
62
62
  product = create(:product)
63
- url = ProductIndex.connection.version.to_i < 8 ? ProductIndex.type_url : ProductIndex.index_url
63
+ url = ProductIndex.connection.distribution.nil? && ProductIndex.connection.version.to_i < 8 ? ProductIndex.type_url : ProductIndex.index_url
64
64
 
65
65
  stub_request(:put, "#{url}/_bulk").with(headers: { "X-Header" => "Value" }).to_return(status: 200, body: "{}")
66
66
 
@@ -1,6 +1,12 @@
1
1
  require File.expand_path("../spec_helper", __dir__)
2
2
 
3
3
  RSpec.describe SearchFlip::Connection do
4
+ describe "#distribution" do
5
+ it "reutrns the distribution" do
6
+ expect([nil, "opensearch"]).to include(SearchFlip::Connection.new.distribution)
7
+ end
8
+ end
9
+
4
10
  describe "#version" do
5
11
  it "returns the version" do
6
12
  expect(SearchFlip::Connection.new.version).to match(/\A[0-9.]+\z/)
@@ -29,11 +35,11 @@ RSpec.describe SearchFlip::Connection do
29
35
  let(:connection) { SearchFlip::Connection.new }
30
36
 
31
37
  after do
32
- connection.update_cluster_settings(persistent: { action: { auto_create_index: false } }) if connection.version.to_i > 2
38
+ connection.update_cluster_settings(persistent: { action: { auto_create_index: false } }) if connection.distribution || connection.version.to_i > 2
33
39
  end
34
40
 
35
41
  it "updates the cluster settings" do
36
- if connection.version.to_i > 2
42
+ if connection.distribution || connection.version.to_i > 2
37
43
  connection.update_cluster_settings(persistent: { action: { auto_create_index: false } })
38
44
  connection.update_cluster_settings(persistent: { action: { auto_create_index: true } })
39
45
 
@@ -42,7 +48,7 @@ RSpec.describe SearchFlip::Connection do
42
48
  end
43
49
 
44
50
  it "returns true" do
45
- if connection.version.to_i > 2
51
+ if connection.distribution || connection.version.to_i > 2
46
52
  expect(connection.update_cluster_settings(persistent: { action: { auto_create_index: false } })).to eq(true)
47
53
  end
48
54
  end
@@ -121,7 +127,7 @@ RSpec.describe SearchFlip::Connection do
121
127
  it "returns the specified indices" do
122
128
  connection = SearchFlip::Connection.new
123
129
 
124
- expect(connection.get_indices.map { |index| index["index"] }.to_set).to eq(["comments", "products"].to_set)
130
+ expect(connection.get_indices.map { |index| index["index"] }.grep_v(/^\./).to_set).to eq(["comments", "products"].to_set)
125
131
  expect(connection.get_indices("com*").map { |index| index["index"] }).to eq(["comments"])
126
132
  end
127
133
 
@@ -199,7 +205,7 @@ RSpec.describe SearchFlip::Connection do
199
205
  it "freezes the specified index" do
200
206
  connection = SearchFlip::Connection.new
201
207
 
202
- if connection.version.to_f >= 6.6 && connection.version.to_i < 8
208
+ if connection.distribution.nil? && connection.version.to_f >= 6.6 && connection.version.to_i < 8
203
209
  begin
204
210
  connection.create_index("index_name")
205
211
  connection.freeze_index("index_name")
@@ -216,7 +222,7 @@ RSpec.describe SearchFlip::Connection do
216
222
  it "unfreezes the specified index" do
217
223
  connection = SearchFlip::Connection.new
218
224
 
219
- if connection.version.to_f >= 6.6 && connection.version.to_i < 8
225
+ if connection.distribution.nil? && connection.version.to_f >= 6.6 && connection.version.to_i < 8
220
226
  begin
221
227
  connection.create_index("index_name")
222
228
  connection.freeze_index("index_name")
@@ -270,7 +276,7 @@ RSpec.describe SearchFlip::Connection do
270
276
  end
271
277
 
272
278
  describe "#update_mapping" do
273
- if SearchFlip::Connection.new.version.to_i >= 7
279
+ if SearchFlip::Connection.new.then { |connection| connection.distribution || connection.version.to_i >= 7 }
274
280
  it "updates the mapping of an index without type name" do
275
281
  begin
276
282
  connection = SearchFlip::Connection.new
@@ -295,7 +301,7 @@ RSpec.describe SearchFlip::Connection do
295
301
 
296
302
  connection.create_index("index_name")
297
303
 
298
- if connection.version.to_i < 8
304
+ if connection.distribution.nil? && connection.version.to_i < 8
299
305
  connection.update_mapping("index_name", { "type_name" => mapping }, type_name: "type_name")
300
306
 
301
307
  expect(connection.get_mapping("index_name", type_name: "type_name")).to eq("index_name" => { "mappings" => { "type_name" => mapping } })
@@ -350,9 +356,9 @@ RSpec.describe SearchFlip::Connection do
350
356
 
351
357
  bulk = proc do
352
358
  connection.bulk do |indexer|
353
- indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
354
- indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
355
- indexer.index 1, { id: 1 }, _index: CommentIndex.index_name, ** connection.version.to_i < 8 ? { _type: CommentIndex.type_name } : {}
359
+ indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
360
+ indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
361
+ indexer.index 1, { id: 1 }, _index: CommentIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: CommentIndex.type_name } : {}
356
362
  end
357
363
  end
358
364
 
@@ -375,14 +381,14 @@ RSpec.describe SearchFlip::Connection do
375
381
  connection = SearchFlip::Connection.new
376
382
 
377
383
  connection.bulk do |indexer|
378
- indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
379
- indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
384
+ indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
385
+ indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
380
386
  end
381
387
 
382
388
  bulk = proc do
383
389
  connection.bulk do |indexer|
384
- indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
385
- indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
390
+ indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
391
+ indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
386
392
  end
387
393
  end
388
394
 
@@ -390,8 +396,8 @@ RSpec.describe SearchFlip::Connection do
390
396
 
391
397
  bulk = proc do
392
398
  connection.bulk ignore_errors: [409] do |indexer|
393
- indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
394
- indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
399
+ indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
400
+ indexer.index 2, { id: 2 }, _index: ProductIndex.index_name, version: 1, version_type: "external", ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
395
401
  end
396
402
  end
397
403
 
@@ -404,7 +410,7 @@ RSpec.describe SearchFlip::Connection do
404
410
  connection = SearchFlip::Connection.new
405
411
 
406
412
  connection.bulk do |indexer|
407
- indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
413
+ indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
408
414
  end
409
415
 
410
416
  expect(SearchFlip::Bulk).to have_received(:new).with(
@@ -427,7 +433,7 @@ RSpec.describe SearchFlip::Connection do
427
433
  }
428
434
 
429
435
  connection.bulk(options) do |indexer|
430
- indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
436
+ indexer.index 1, { id: 1 }, _index: ProductIndex.index_name, ** connection.distribution.nil? && connection.version.to_i < 8 ? { _type: ProductIndex.type_name } : {}
431
437
  end
432
438
 
433
439
  expect(SearchFlip::Bulk).to have_received(:new).with(anything, options)
@@ -435,7 +435,7 @@ RSpec.describe SearchFlip::Criteria do
435
435
 
436
436
  describe "#match_none" do
437
437
  it "does not match any documents" do
438
- if ProductIndex.connection.version.to_i >= 5
438
+ if ProductIndex.connection.distribution || ProductIndex.connection.version.to_i >= 5
439
439
  ProductIndex.import create(:product)
440
440
 
441
441
  query = ProductIndex.match_none
@@ -479,7 +479,7 @@ RSpec.describe SearchFlip::Criteria do
479
479
 
480
480
  describe "#post_search" do
481
481
  it "sets up the constraints correctly and is chainable" do
482
- if ProductIndex.connection.version.to_i >= 2
482
+ if ProductIndex.connection.distribution || ProductIndex.connection.version.to_i >= 2
483
483
  product1 = create(:product, title: "title1", category: "category1")
484
484
  product2 = create(:product, title: "title2", category: "category2")
485
485
  product3 = create(:product, title: "title3", category: "category1")
@@ -886,7 +886,7 @@ RSpec.describe SearchFlip::Criteria do
886
886
 
887
887
  describe "#profile" do
888
888
  it "sets up the constraints correctly" do
889
- if ProductIndex.connection.version.to_i >= 2
889
+ if ProductIndex.connection.distribution || ProductIndex.connection.version.to_i >= 2
890
890
  expect(ProductIndex.profile(true).raw_response["profile"]).not_to be_nil
891
891
  end
892
892
  end
@@ -1322,7 +1322,7 @@ RSpec.describe SearchFlip::Criteria do
1322
1322
 
1323
1323
  describe "#track_total_hits" do
1324
1324
  it "is added to the request" do
1325
- if ProductIndex.connection.version.to_i >= 7
1325
+ if ProductIndex.connection.distribution || ProductIndex.connection.version.to_i >= 7
1326
1326
  query = ProductIndex.track_total_hits(false)
1327
1327
 
1328
1328
  expect(query.request[:track_total_hits]).to eq(false)
@@ -1350,7 +1350,7 @@ RSpec.describe SearchFlip::Criteria do
1350
1350
 
1351
1351
  describe "#preference" do
1352
1352
  it "sets the preference" do
1353
- url = ProductIndex.connection.version.to_i < 8 ? "products/products" : "products"
1353
+ url = ProductIndex.connection.distribution.nil? && ProductIndex.connection.version.to_i < 8 ? "products/products" : "products"
1354
1354
 
1355
1355
  stub_request(:post, "http://127.0.0.1:9200/#{url}/_search?preference=value")
1356
1356
  .to_return(status: 200, headers: { content_type: "application/json" }, body: "{}")
@@ -1363,7 +1363,7 @@ RSpec.describe SearchFlip::Criteria do
1363
1363
 
1364
1364
  describe "#search_type" do
1365
1365
  it "sets the search_type" do
1366
- url = ProductIndex.connection.version.to_i < 8 ? "products/products" : "products"
1366
+ url = ProductIndex.connection.distribution.nil? && ProductIndex.connection.version.to_i < 8 ? "products/products" : "products"
1367
1367
 
1368
1368
  stub_request(:post, "http://127.0.0.1:9200/#{url}/_search?search_type=value")
1369
1369
  .to_return(status: 200, headers: { content_type: "application/json" }, body: "{}")
@@ -1376,7 +1376,7 @@ RSpec.describe SearchFlip::Criteria do
1376
1376
 
1377
1377
  describe "#routing" do
1378
1378
  it "sets the search_type" do
1379
- url = ProductIndex.connection.version.to_i < 8 ? "products/products" : "products"
1379
+ url = ProductIndex.connection.distribution.nil? && ProductIndex.connection.version.to_i < 8 ? "products/products" : "products"
1380
1380
 
1381
1381
  stub_request(:post, "http://127.0.0.1:9200/#{url}/_search?routing=value")
1382
1382
  .to_return(status: 200, headers: { content_type: "application/json" }, body: "{}")
@@ -36,6 +36,12 @@ RSpec.describe SearchFlip::HTTPClient do
36
36
 
37
37
  expect(SearchFlip::HTTPClient.new.send(method, "http://localhost/path", body: "body", params: { key: "value" }).body.to_s).to eq("success")
38
38
  end
39
+
40
+ it "generates json, passes it as body and sets the content type when the json option is used" do
41
+ stub_request(method, "http://localhost/path").with(body: '{"key":"value"}', headers: { "Content-Type" => "application/json" }).to_return(body: "success")
42
+
43
+ expect(SearchFlip::HTTPClient.new.send(method, "http://localhost/path", json: { "key" => "value" }).body.to_s).to eq("success")
44
+ end
39
45
  end
40
46
  end
41
47
 
@@ -77,7 +77,7 @@ RSpec.describe SearchFlip::Index do
77
77
  include SearchFlip::Index
78
78
  end
79
79
 
80
- expect(klass.include_type_name?).to eq(klass.connection.version.to_i < 7)
80
+ expect(klass.include_type_name?).to eq(klass.connection.distribution.nil? && klass.connection.version.to_i < 7)
81
81
  end
82
82
 
83
83
  it "returns true if the type name is not equal to _doc" do
@@ -185,7 +185,7 @@ RSpec.describe SearchFlip::Index do
185
185
  end
186
186
 
187
187
  describe ".update_mapping" do
188
- if TestIndex.connection.version.to_i >= 7
188
+ if TestIndex.connection.distribution || TestIndex.connection.version.to_i >= 7
189
189
  context "without type name" do
190
190
  it "delegates to connection" do
191
191
  TestIndex.create_index
@@ -215,7 +215,7 @@ RSpec.describe SearchFlip::Index do
215
215
 
216
216
  TestIndex.update_mapping
217
217
 
218
- if TestIndex.connection.version.to_i < 8
218
+ if TestIndex.connection.distribution.nil? && TestIndex.connection.version.to_i < 8
219
219
  expect(TestIndex.connection).to have_received(:update_mapping).with("test", { "test" => mapping }, type_name: "test")
220
220
  else
221
221
  expect(TestIndex.connection).to have_received(:update_mapping).with("test", mapping)
@@ -245,7 +245,7 @@ RSpec.describe SearchFlip::Index do
245
245
  end
246
246
 
247
247
  describe ".get_mapping" do
248
- if TestIndex.connection.version.to_i >= 7
248
+ if TestIndex.connection.distribution || TestIndex.connection.version.to_i >= 7
249
249
  context "without type name" do
250
250
  it "delegates to connection" do
251
251
  allow(TestIndex).to receive(:include_type_name?).and_return(false)
@@ -272,7 +272,7 @@ RSpec.describe SearchFlip::Index do
272
272
 
273
273
  TestIndex.get_mapping
274
274
 
275
- if TestIndex.connection.version.to_i < 8
275
+ if TestIndex.connection.distribution.nil? && TestIndex.connection.version.to_i < 8
276
276
  expect(TestIndex.connection).to have_received(:get_mapping).with("test", type_name: "test")
277
277
  else
278
278
  expect(TestIndex.connection).to have_received(:get_mapping).with("test")
@@ -340,7 +340,7 @@ RSpec.describe SearchFlip::Index do
340
340
 
341
341
  TestIndex.type_url
342
342
 
343
- expect(TestIndex.connection).to have_received(:type_url).with("test", TestIndex.connection.version.to_i < 8 ? "test" : "_doc")
343
+ expect(TestIndex.connection).to have_received(:type_url).with("test", TestIndex.connection.distribution.nil? && TestIndex.connection.version.to_i < 8 ? "test" : "_doc")
344
344
  end
345
345
  end
346
346
 
@@ -433,7 +433,7 @@ RSpec.describe SearchFlip::Index do
433
433
 
434
434
  products = create_list(:product, 2)
435
435
 
436
- if ProductIndex.connection.version.to_i >= 5
436
+ if ProductIndex.connection.distribution || ProductIndex.connection.version.to_i >= 5
437
437
  expect { ProductIndex.create products, {}, routing: "r1" }.to(change { ProductIndex.total_count }.by(2))
438
438
 
439
439
  expect(ProductIndex.get(products.first.id, routing: "r1")["_routing"]).to eq("r1")
@@ -447,7 +447,7 @@ RSpec.describe SearchFlip::Index do
447
447
  it "allows respects class options" do
448
448
  products = create_list(:product, 2)
449
449
 
450
- if ProductIndex.connection.version.to_i >= 5
450
+ if ProductIndex.connection.distribution || ProductIndex.connection.version.to_i >= 5
451
451
  allow(ProductIndex).to receive(:index_options).and_return(routing: "r1")
452
452
 
453
453
  expect { ProductIndex.create products }.to(change { ProductIndex.total_count }.by(2))
@@ -82,7 +82,7 @@ RSpec.describe SearchFlip::Response do
82
82
 
83
83
  ProductIndex.import products
84
84
 
85
- expect(ProductIndex.match_all.results.map(&:id).to_set).to eq(products.map(&:id).to_set)
85
+ expect(ProductIndex.match_all.results.to_set(&:id)).to eq(products.to_set(&:id))
86
86
  end
87
87
  end
88
88
 
@@ -126,7 +126,7 @@ RSpec.describe SearchFlip::Response do
126
126
 
127
127
  response = ProductIndex.match_all.response
128
128
 
129
- expect(response.ids.to_set).to eq(products.map(&:id).map(&:to_s).to_set)
129
+ expect(response.ids.to_set).to eq(products.map(&:id).to_set(&:to_s))
130
130
  expect(response.ids).to eq(response.raw_response["hits"]["hits"].map { |hit| hit["_id"] })
131
131
  end
132
132
  end
data/spec/spec_helper.rb CHANGED
@@ -86,7 +86,7 @@ class CommentIndex
86
86
  include SearchFlip::Index
87
87
 
88
88
  def self.type_name
89
- connection.version.to_i < 8 ? "comments" : "_doc"
89
+ connection.distribution.nil? && connection.version.to_i < 8 ? "comments" : "_doc"
90
90
  end
91
91
 
92
92
  def self.index_name
@@ -113,7 +113,7 @@ class ProductIndex
113
113
  include SearchFlip::Index
114
114
 
115
115
  def self.mapping
116
- if ProductIndex.connection.version.to_i >= 5
116
+ if ProductIndex.connection.distribution || ProductIndex.connection.version.to_i >= 5
117
117
  {
118
118
  properties: {
119
119
  category: {
@@ -136,7 +136,7 @@ class ProductIndex
136
136
  end
137
137
 
138
138
  def self.type_name
139
- connection.version.to_i < 8 ? "products" : "_doc"
139
+ connection.distribution.nil? && connection.version.to_i < 8 ? "products" : "_doc"
140
140
  end
141
141
 
142
142
  def self.index_name
@@ -177,7 +177,7 @@ class TestIndex
177
177
  end
178
178
 
179
179
  def self.type_name
180
- connection.version.to_i < 8 ? "test" : "_doc"
180
+ connection.distribution.nil? && connection.version.to_i < 8 ? "test" : "_doc"
181
181
  end
182
182
 
183
183
  def self.index_name
metadata CHANGED
@@ -1,155 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search_flip
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.beta11
4
+ version: 4.0.0.beta14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Vetter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-27 00:00:00.000000000 Z
11
+ date: 2024-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activerecord
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '3.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
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'
41
- - !ruby/object:Gem::Dependency
42
- name: bundler
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: factory_bot
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: rake
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rspec
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: rubocop
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: sqlite3
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: timecop
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: webmock
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
13
  - !ruby/object:Gem::Dependency
154
14
  name: http
155
15
  requirement: !ruby/object:Gem::Requirement
@@ -268,7 +128,10 @@ files:
268
128
  homepage: https://github.com/mrkamel/search_flip
269
129
  licenses:
270
130
  - MIT
271
- metadata: {}
131
+ metadata:
132
+ homepage_uri: https://github.com/mrkamel/search_flip
133
+ source_code_uri: https://github.com/mrkamel/search_flip
134
+ changelog_uri: https://github.com/mrkamel/search_flip/blob/master/CHANGELOG.md
272
135
  post_install_message: |
273
136
  Thanks for using search_flip!
274
137
  When upgrading major versions, please check out