searchkick 4.4.4 → 4.6.0

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: 864e600f47d8f3cc55c5664f04b71a1eb054bdad20acd5eb206a77bdeed03503
4
- data.tar.gz: 2dcd69102ab91f46209dd345cf180309729d470423fb0832fa8e6ed0772cfa45
3
+ metadata.gz: d93159910ecc579b56511b5d8c6908b34fecbce17b49ccbc264383068610feb9
4
+ data.tar.gz: a60319ffc12e2aac95bf96f616bb65afcb8e75b77a89598e960193c8424c677a
5
5
  SHA512:
6
- metadata.gz: c687faaae288a53c7ec5260c501c390c0c27cef372cb76dea4caeaa4bdbd6554ccf627ba10a82fe8202f236d84ee00e8872632316f66e0cc89fe4f3900e017a0
7
- data.tar.gz: c61486f67dc88dee390022aacb91f45278559d23cf62dd7a9f9468aa2e317b0d643a7f500162608e81496c750987e39391b1200a1d2f0528985187b32b094f53
6
+ metadata.gz: 1ce03474756a2adf3ada602580c6e56d2a39d39d93096b9d796167f9bc841a92b5d321914a05ab75ca9e10e87cf6e77ed8590b6e9934cae1ec0e56ba590f65c8
7
+ data.tar.gz: ae617b74a6ed27f6caa6bf9f3494a62d35ab4176e87bf868a5c04e60ae1b78462bee07de1f182cd3d81cac5a3bcaa22a57aad5e75f61c384aa8ceb6344822486
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 4.6.0 (2021-08-22)
2
+
3
+ - Added support for case-insensitive regular expressions with Elasticsearch 7.10+
4
+ - Added support for `OPENSEARCH_URL`
5
+ - Fixed error with `debug` option
6
+
7
+ ## 4.5.2 (2021-08-05)
8
+
9
+ - Fixed error with reindex queue
10
+ - Fixed error with `model_name` method with multiple models
11
+ - Fixed error with `debug` option with elasticsearch-ruby 7.14
12
+
13
+ ## 4.5.1 (2021-08-03)
14
+
15
+ - Improved performance of reindex queue
16
+
17
+ ## 4.5.0 (2021-06-07)
18
+
19
+ - Added experimental support for OpenSearch
20
+ - Added support for synonyms in Japanese
21
+
1
22
  ## 4.4.4 (2021-03-12)
2
23
 
3
24
  - Fixed `too_long_frame_exception` with `scroll` method
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2020 Andrew Kane
1
+ Copyright (c) 2013-2021 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -20,7 +20,7 @@ Plus:
20
20
  - autocomplete
21
21
  - “Did you mean” suggestions
22
22
  - supports many languages
23
- - works with ActiveRecord, Mongoid, and NoBrainer
23
+ - works with Active Record, Mongoid, and NoBrainer
24
24
 
25
25
  Check out [Searchjoy](https://github.com/ankane/searchjoy) for analytics and [Autosuggest](https://github.com/ankane/autosuggest) for query suggestions
26
26
 
@@ -45,11 +45,11 @@ Check out [Searchjoy](https://github.com/ankane/searchjoy) for analytics and [Au
45
45
 
46
46
  ## Getting Started
47
47
 
48
- [Install Elasticsearch](https://www.elastic.co/downloads/elasticsearch). For Homebrew, use:
48
+ Install [Elasticsearch](https://www.elastic.co/downloads/elasticsearch) or [OpenSearch](https://opensearch.org/downloads.html). For Homebrew, use:
49
49
 
50
50
  ```sh
51
- brew install elasticsearch
52
- brew services start elasticsearch
51
+ brew install elasticsearch # or opensearch
52
+ brew services start elasticsearch # or opensearch
53
53
  ```
54
54
 
55
55
  Add this line to your application’s Gemfile:
@@ -58,7 +58,7 @@ Add this line to your application’s Gemfile:
58
58
  gem 'searchkick'
59
59
  ```
60
60
 
61
- The latest version works with Elasticsearch 6 and 7. For Elasticsearch 5, use version 3.1.3 and [this readme](https://github.com/ankane/searchkick/blob/v3.1.3/README.md).
61
+ The latest version works with Elasticsearch 6 and 7 and OpenSearch 1. For Elasticsearch 5, use version 3.1.3 and [this readme](https://github.com/ankane/searchkick/blob/v3.1.3/README.md).
62
62
 
63
63
  Add searchkick to models you want to search.
64
64
 
@@ -125,7 +125,7 @@ Order
125
125
  order: {_score: :desc} # most relevant first - default
126
126
  ```
127
127
 
128
- [All of these sort options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-sort.html)
128
+ [All of these sort options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html)
129
129
 
130
130
  Limit / offset
131
131
 
@@ -139,7 +139,7 @@ Select
139
139
  select: [:name]
140
140
  ```
141
141
 
142
- [These source filtering options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-source-filtering)
142
+ [These source filtering options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html#source-filtering)
143
143
 
144
144
  ### Results
145
145
 
@@ -392,7 +392,7 @@ search_synonyms: "synonyms.txt"
392
392
  Add [elasticsearch-xpack](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-xpack) to your Gemfile:
393
393
 
394
394
  ```ruby
395
- gem 'elasticsearch-xpack', '>= 7.8.0'
395
+ gem 'elasticsearch-xpack', '>= 7.8', '< 7.14'
396
396
  ```
397
397
 
398
398
  And use:
@@ -401,7 +401,7 @@ And use:
401
401
  Product.search_index.reload_synonyms
402
402
  ```
403
403
 
404
- #### Elasticsearch < 7.3
404
+ #### Elasticsearch < 7.3 or OpenSearch
405
405
 
406
406
  You can use a library like [ActsAsTaggableOn](https://github.com/mbleigh/acts-as-taggable-on) and do:
407
407
 
@@ -649,7 +649,7 @@ class Product < ApplicationRecord
649
649
  def search_data
650
650
  {
651
651
  name: name,
652
- conversions: searches.group(:query).uniq.count(:user_id)
652
+ conversions: searches.group(:query).distinct.count(:user_id)
653
653
  # {"ice cream" => 234, "chocolate" => 67, "cream" => 2}
654
654
  }
655
655
  end
@@ -899,7 +899,7 @@ Additional options can be specified for each field:
899
899
  Band.search "cinema", fields: [:name], highlight: {fields: {name: {fragment_size: 200}}}
900
900
  ```
901
901
 
902
- You can find available highlight options in the [Elasticsearch reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-highlighting.html#_highlighted_fragments).
902
+ You can find available highlight options in the [Elasticsearch reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/highlighting.html).
903
903
 
904
904
  ## Similar Items
905
905
 
@@ -950,7 +950,7 @@ Boost results by distance - closer results are boosted more
950
950
  Restaurant.search "noodles", boost_by_distance: {location: {origin: {lat: 37, lon: -122}}}
951
951
  ```
952
952
 
953
- Also supports [additional options](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_decay_functions)
953
+ Also supports [additional options](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#function-decay)
954
954
 
955
955
  ```ruby
956
956
  Restaurant.search "wings", boost_by_distance: {location: {origin: {lat: 37, lon: -122}, function: "linear", scale: "30mi", decay: 0.5}}
@@ -1212,12 +1212,18 @@ FactoryBot.create(:product, :some_trait, :reindex, some_attribute: "foo")
1212
1212
 
1213
1213
  ### GitHub Actions
1214
1214
 
1215
- Check out [setup-elasticsearch](https://github.com/ankane/setup-elasticsearch) for an easy way to install Elasticsearch.
1215
+ Check out [setup-elasticsearch](https://github.com/ankane/setup-elasticsearch) for an easy way to install Elasticsearch:
1216
1216
 
1217
1217
  ```yml
1218
1218
  - uses: ankane/setup-elasticsearch@v1
1219
1219
  ```
1220
1220
 
1221
+ And [setup-opensearch](https://github.com/ankane/setup-opensearch) for an easy way to install OpenSearch:
1222
+
1223
+ ```yml
1224
+ - uses: ankane/setup-opensearch@v1
1225
+ ```
1226
+
1221
1227
  ## Deployment
1222
1228
 
1223
1229
  Searchkick uses `ENV["ELASTICSEARCH_URL"]` for the Elasticsearch server. This defaults to `http://localhost:9200`.
@@ -1248,7 +1254,7 @@ Choose an add-on: [Bonsai](https://elements.heroku.com/addons/bonsai), [SearchBo
1248
1254
  For Bonsai:
1249
1255
 
1250
1256
  ```sh
1251
- heroku addons:create bonsai
1257
+ heroku addons:create bonsai # use --engine=opensearch for OpenSearch
1252
1258
  heroku config:set ELASTICSEARCH_URL=`heroku config:get BONSAI_URL`
1253
1259
  ```
1254
1260
 
@@ -1580,14 +1586,14 @@ class ReindexConversionsJob < ApplicationJob
1580
1586
  # get records that have a recent conversion
1581
1587
  recently_converted_ids =
1582
1588
  Searchjoy::Search.where("convertable_type = ? AND converted_at > ?", class_name, 1.day.ago)
1583
- .order(:convertable_id).uniq.pluck(:convertable_id)
1589
+ .order(:convertable_id).distinct.pluck(:convertable_id)
1584
1590
 
1585
1591
  # split into groups
1586
1592
  recently_converted_ids.in_groups_of(1000, false) do |ids|
1587
1593
  # fetch conversions
1588
1594
  conversions =
1589
1595
  Searchjoy::Search.where(convertable_id: ids, convertable_type: class_name)
1590
- .group(:convertable_id, :query).uniq.count(:user_id)
1596
+ .group(:convertable_id, :query).distinct.count(:user_id)
1591
1597
 
1592
1598
  # group conversions by record
1593
1599
  conversions_by_record = {}
@@ -1711,7 +1717,7 @@ Check out [this great post](https://www.tiagoamaro.com.br/2014/12/11/multi-tenan
1711
1717
 
1712
1718
  ## Scroll API
1713
1719
 
1714
- Searchkick also supports the [scroll API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html). Scrolling is not intended for real time user requests, but rather for processing large amounts of data.
1720
+ Searchkick also supports the [scroll API](https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html#scroll-search-results). Scrolling is not intended for real time user requests, but rather for processing large amounts of data.
1715
1721
 
1716
1722
  ```ruby
1717
1723
  Product.search("*", scroll: "1m").scroll do |batch|
@@ -1839,7 +1845,7 @@ class Product < ApplicationRecord
1839
1845
  def search_data
1840
1846
  {
1841
1847
  name: name,
1842
- unique_user_conversions: searches.group(:query).uniq.count(:user_id),
1848
+ unique_user_conversions: searches.group(:query).distinct.count(:user_id),
1843
1849
  # {"ice cream" => 234, "chocolate" => 67, "cream" => 2}
1844
1850
  total_conversions: searches.group(:query).count
1845
1851
  # {"ice cream" => 412, "chocolate" => 117, "cream" => 6}
@@ -1964,7 +1970,7 @@ products = Product.search("carrots", execute: false)
1964
1970
  products.each { ... } # search not executed until here
1965
1971
  ```
1966
1972
 
1967
- Add [request parameters](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html), like `search_type` and `query_cache`
1973
+ Add [request parameters](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html#search-search-api-query-params) like `search_type`
1968
1974
 
1969
1975
  ```ruby
1970
1976
  Product.search("carrots", request_params: {search_type: "dfs_query_then_fetch"})
data/lib/searchkick.rb CHANGED
@@ -56,7 +56,7 @@ module Searchkick
56
56
  require "typhoeus/adapters/faraday" if defined?(Typhoeus) && Gem::Version.new(Faraday::VERSION) < Gem::Version.new("0.14.0")
57
57
 
58
58
  Elasticsearch::Client.new({
59
- url: ENV["ELASTICSEARCH_URL"],
59
+ url: ENV["ELASTICSEARCH_URL"] || ENV["OPENSEARCH_URL"],
60
60
  transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}},
61
61
  retry_on_failure: 2
62
62
  }.deep_merge(client_options)) do |f|
@@ -74,11 +74,24 @@ module Searchkick
74
74
  (defined?(@search_timeout) && @search_timeout) || timeout
75
75
  end
76
76
 
77
+ # private
78
+ def self.server_info
79
+ @server_info ||= client.info
80
+ end
81
+
77
82
  def self.server_version
78
- @server_version ||= client.info["version"]["number"]
83
+ @server_version ||= server_info["version"]["number"]
84
+ end
85
+
86
+ def self.opensearch?
87
+ unless defined?(@opensearch)
88
+ @opensearch = server_info["version"]["distribution"] == "opensearch"
89
+ end
90
+ @opensearch
79
91
  end
80
92
 
81
93
  def self.server_below?(version)
94
+ server_version = opensearch? ? "7.10.2" : self.server_version
82
95
  Gem::Version.new(server_version.split("-")[0]) < Gem::Version.new(version.split("-")[0])
83
96
  end
84
97
 
@@ -235,6 +235,27 @@ module Searchkick
235
235
  type: "kuromoji"
236
236
  }
237
237
  )
238
+ when "japanese2"
239
+ analyzer = {
240
+ type: "custom",
241
+ tokenizer: "kuromoji_tokenizer",
242
+ filter: [
243
+ "kuromoji_baseform",
244
+ "kuromoji_part_of_speech",
245
+ "cjk_width",
246
+ "ja_stop",
247
+ "searchkick_stemmer",
248
+ "lowercase"
249
+ ]
250
+ }
251
+ settings[:analysis][:analyzer].merge!(
252
+ default_analyzer => analyzer.deep_dup,
253
+ searchkick_search: analyzer.deep_dup,
254
+ searchkick_search2: analyzer.deep_dup
255
+ )
256
+ settings[:analysis][:filter][:searchkick_stemmer] = {
257
+ type: "kuromoji_stemmer"
258
+ }
238
259
  when "korean"
239
260
  settings[:analysis][:analyzer].merge!(
240
261
  default_analyzer => {
@@ -512,8 +533,18 @@ module Searchkick
512
533
  end
513
534
  settings[:analysis][:filter][:searchkick_synonym_graph] = synonym_graph
514
535
 
515
- [:searchkick_search2, :searchkick_word_search].each do |analyzer|
516
- settings[:analysis][:analyzer][analyzer][:filter].insert(2, "searchkick_synonym_graph")
536
+ if options[:language] == "japanese2"
537
+ [:searchkick_search, :searchkick_search2].each do |analyzer|
538
+ settings[:analysis][:analyzer][analyzer][:filter].insert(4, "searchkick_synonym_graph")
539
+ end
540
+ else
541
+ [:searchkick_search2, :searchkick_word_search].each do |analyzer|
542
+ unless settings[:analysis][:analyzer][analyzer].key?(:filter)
543
+ raise Searchkick::Error, "Search synonyms are not supported yet for language"
544
+ end
545
+
546
+ settings[:analysis][:analyzer][analyzer][:filter].insert(2, "searchkick_synonym_graph")
547
+ end
517
548
  end
518
549
  end
519
550
  end
@@ -11,7 +11,7 @@ module Searchkick
11
11
  if record_ids.any?
12
12
  batch_options = {
13
13
  class_name: class_name,
14
- record_ids: record_ids,
14
+ record_ids: record_ids.uniq,
15
15
  index_name: index_name
16
16
  }
17
17
 
@@ -109,7 +109,12 @@ module Searchkick
109
109
  request_params = query.except(:index, :type, :body)
110
110
 
111
111
  # no easy way to tell which host the client will use
112
- host = Searchkick.client.transport.hosts.first
112
+ host =
113
+ if Gem::Version.new(Elasticsearch::VERSION) >= Gem::Version.new("7.14.0")
114
+ Searchkick.client.transport.transport.hosts.first
115
+ else
116
+ Searchkick.client.transport.hosts.first
117
+ end
113
118
  credentials = host[:user] || host[:password] ? "#{host[:user]}:#{host[:password]}@" : nil
114
119
  params = ["pretty"]
115
120
  request_params.each do |k, v|
@@ -353,8 +358,8 @@ module Searchkick
353
358
  shared_options[:cutoff_frequency] = 0.001 unless operator.to_s == "and" || field_misspellings == false || (!below73? && !track_total_hits?)
354
359
  qs << shared_options.merge(analyzer: "searchkick_search")
355
360
 
356
- # searchkick_search and searchkick_search2 are the same for ukrainian
357
- unless %w(japanese korean polish ukrainian vietnamese).include?(searchkick_options[:language])
361
+ # searchkick_search and searchkick_search2 are the same for some languages
362
+ unless %w(japanese japanese2 korean polish ukrainian vietnamese).include?(searchkick_options[:language])
358
363
  qs << shared_options.merge(analyzer: "searchkick_search2")
359
364
  end
360
365
  exclude_analyzer = "searchkick_search2"
@@ -864,10 +869,11 @@ module Searchkick
864
869
  }
865
870
  end
866
871
 
867
- # TODO id transformation for arrays
868
872
  def set_order(payload)
869
873
  order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc}
870
874
  id_field = :_id
875
+ # TODO no longer map id to _id in Searchkick 5
876
+ # since sorting on _id is deprecated in Elasticsearch
871
877
  payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? id_field : k, v] }]
872
878
  end
873
879
 
@@ -1022,10 +1028,6 @@ module Searchkick
1022
1028
  elsif value.nil?
1023
1029
  {bool: {must_not: {exists: {field: field}}}}
1024
1030
  elsif value.is_a?(Regexp)
1025
- if value.casefold?
1026
- Searchkick.warn("Case-insensitive flag does not work with Elasticsearch")
1027
- end
1028
-
1029
1031
  source = value.source
1030
1032
  unless source.start_with?("\\A") && source.end_with?("\\z")
1031
1033
  # https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html
@@ -1047,7 +1049,14 @@ module Searchkick
1047
1049
  # source = "#{source}.*"
1048
1050
  end
1049
1051
 
1050
- {regexp: {field => {value: source, flags: "NONE"}}}
1052
+ if below710?
1053
+ if value.casefold?
1054
+ Searchkick.warn("Case-insensitive flag does not work with Elasticsearch < 7.10")
1055
+ end
1056
+ {regexp: {field => {value: source, flags: "NONE"}}}
1057
+ else
1058
+ {regexp: {field => {value: source, flags: "NONE", case_insensitive: value.casefold?}}}
1059
+ end
1051
1060
  else
1052
1061
  # TODO add this for other values
1053
1062
  if value.as_json.is_a?(Enumerable)
@@ -1145,5 +1154,9 @@ module Searchkick
1145
1154
  def below75?
1146
1155
  Searchkick.server_below?("7.5.0")
1147
1156
  end
1157
+
1158
+ def below710?
1159
+ Searchkick.server_below?("7.10.0")
1160
+ end
1148
1161
  end
1149
1162
  end
@@ -14,11 +14,17 @@ module Searchkick
14
14
 
15
15
  # TODO use reliable queuing
16
16
  def reserve(limit: 1000)
17
- record_ids = Set.new
18
- while record_ids.size < limit && (record_id = Searchkick.with_redis { |r| r.rpop(redis_key) })
19
- record_ids << record_id
17
+ if supports_rpop_with_count?
18
+ Searchkick.with_redis { |r| r.call("rpop", redis_key, limit) }.to_a
19
+ else
20
+ record_ids = []
21
+ Searchkick.with_redis do |r|
22
+ while record_ids.size < limit && (record_id = r.rpop(redis_key))
23
+ record_ids << record_id
24
+ end
25
+ end
26
+ record_ids
20
27
  end
21
- record_ids.to_a
22
28
  end
23
29
 
24
30
  def clear
@@ -34,5 +40,13 @@ module Searchkick
34
40
  def redis_key
35
41
  "searchkick:reindex_queue:#{name}"
36
42
  end
43
+
44
+ def supports_rpop_with_count?
45
+ redis_version >= Gem::Version.new("6.2")
46
+ end
47
+
48
+ def redis_version
49
+ @redis_version ||= Searchkick.with_redis { |r| Gem::Version.new(r.info["redis_version"]) }
50
+ end
37
51
  end
38
52
  end
@@ -71,7 +71,11 @@ module Searchkick
71
71
  end
72
72
 
73
73
  def model_name
74
- klass.model_name
74
+ if klass.nil?
75
+ ActiveModel::Name.new(self.class, nil, 'Result')
76
+ else
77
+ klass.model_name
78
+ end
75
79
  end
76
80
 
77
81
  def entry_name(options = {})
@@ -231,7 +235,7 @@ module Searchkick
231
235
  index_alias = index.split("_")[0..-2].join("_")
232
236
  Array((options[:index_mapping] || {})[index_alias])
233
237
  end
234
- raise Searchkick::Error, "Unknown model for index: #{index}" unless models.any?
238
+ raise Searchkick::Error, "Unknown model for index: #{index}. Pass the `models` option to the search method." unless models.any?
235
239
  index_models[index] = models
236
240
  end
237
241
 
@@ -1,3 +1,3 @@
1
1
  module Searchkick
2
- VERSION = "4.4.4"
2
+ VERSION = "4.6.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchkick
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.4
4
+ version: 4.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-12 00:00:00.000000000 Z
11
+ date: 2021-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -31,6 +31,9 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '6'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '7.14'
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -38,6 +41,9 @@ dependencies:
38
41
  - - ">="
39
42
  - !ruby/object:Gem::Version
40
43
  version: '6'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '7.14'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: hashie
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -53,7 +59,7 @@ dependencies:
53
59
  - !ruby/object:Gem::Version
54
60
  version: '0'
55
61
  description:
56
- email: andrew@chartkick.com
62
+ email: andrew@ankane.org
57
63
  executables: []
58
64
  extensions: []
59
65
  extra_rdoc_files: []
@@ -102,8 +108,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
108
  - !ruby/object:Gem::Version
103
109
  version: '0'
104
110
  requirements: []
105
- rubygems_version: 3.2.3
111
+ rubygems_version: 3.2.22
106
112
  signing_key:
107
113
  specification_version: 4
108
- summary: Intelligent search made easy with Rails and Elasticsearch
114
+ summary: Intelligent search made easy with Rails and Elasticsearch or OpenSearch
109
115
  test_files: []