searchkick 5.0.1 → 5.0.4
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/CHANGELOG.md +13 -0
- data/README.md +190 -141
- data/lib/searchkick/index.rb +1 -1
- data/lib/searchkick/index_options.rb +2 -2
- data/lib/searchkick/model.rb +2 -2
- data/lib/searchkick/query.rb +6 -0
- data/lib/searchkick/relation.rb +193 -4
- data/lib/searchkick/version.rb +1 -1
- data/lib/searchkick/where.rb +11 -0
- data/lib/searchkick.rb +5 -3
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89c6a97d4c898be7f1f494cc4bfafc8aed5acc202a855588e81f73d86ea7b123
|
4
|
+
data.tar.gz: c362bb2916ec0b1fa83d72efd2314e747077b5cd696ed2ece089204be9452010
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf1a9191ee97a19afde4c44b84c02b34b7f6b8b3ac464cdd34671dd60cb8a191ec338a028db8221c2a502fadf0059987f957c918221175631e8aa70344371ee7
|
7
|
+
data.tar.gz: 32e7b4b9f0899088e709a77389d8fa845ef68a3fd819d3823229eb1ffd99a6121647d5ab82d209e6254e4c242530d7698441d3b97f65a1e2436acbced6b6086f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
## 5.0.4 (2022-06-16)
|
2
|
+
|
3
|
+
- Added `max_result_window` option
|
4
|
+
- Improved error message for unsupported versions of Elasticsearch
|
5
|
+
|
6
|
+
## 5.0.3 (2022-03-13)
|
7
|
+
|
8
|
+
- Fixed context for index name for inherited models
|
9
|
+
|
10
|
+
## 5.0.2 (2022-03-03)
|
11
|
+
|
12
|
+
- Fixed index name for inherited models
|
13
|
+
|
1
14
|
## 5.0.1 (2022-02-27)
|
2
15
|
|
3
16
|
- Prefer `mode: :async` over `async: true` for full reindex
|
data/README.md
CHANGED
@@ -50,8 +50,8 @@ Searchkick 5.0 was recently released! See [how to upgrade](#upgrading)
|
|
50
50
|
Install [Elasticsearch](https://www.elastic.co/downloads/elasticsearch) or [OpenSearch](https://opensearch.org/downloads.html). For Homebrew, use:
|
51
51
|
|
52
52
|
```sh
|
53
|
-
brew install elasticsearch
|
54
|
-
brew services start elasticsearch
|
53
|
+
brew install elastic/tap/elasticsearch-full
|
54
|
+
brew services start elasticsearch-full
|
55
55
|
# or
|
56
56
|
brew install opensearch
|
57
57
|
brew services start opensearch
|
@@ -66,7 +66,7 @@ gem "elasticsearch" # select one
|
|
66
66
|
gem "opensearch-ruby" # select one
|
67
67
|
```
|
68
68
|
|
69
|
-
The latest version works with Elasticsearch 7 and 8 and OpenSearch 1. For Elasticsearch 6, use version 4.6.3 and [this readme](https://github.com/ankane/searchkick/blob/v4.6.3/README.md).
|
69
|
+
The latest version works with Elasticsearch 7 and 8 and OpenSearch 1 and 2. For Elasticsearch 6, use version 4.6.3 and [this readme](https://github.com/ankane/searchkick/blob/v4.6.3/README.md).
|
70
70
|
|
71
71
|
Add searchkick to models you want to search.
|
72
72
|
|
@@ -98,7 +98,7 @@ Searchkick supports the complete [Elasticsearch Search API](https://www.elastic.
|
|
98
98
|
Query like SQL
|
99
99
|
|
100
100
|
```ruby
|
101
|
-
Product.search
|
101
|
+
Product.search("apples", where: {in_stock: true}, limit: 10, offset: 50)
|
102
102
|
```
|
103
103
|
|
104
104
|
Search specific fields
|
@@ -226,7 +226,7 @@ You can also boost by:
|
|
226
226
|
Use a `*` for the query.
|
227
227
|
|
228
228
|
```ruby
|
229
|
-
Product.search
|
229
|
+
Product.search("*")
|
230
230
|
```
|
231
231
|
|
232
232
|
### Pagination
|
@@ -235,7 +235,7 @@ Plays nicely with kaminari and will_paginate.
|
|
235
235
|
|
236
236
|
```ruby
|
237
237
|
# controller
|
238
|
-
@products = Product.search
|
238
|
+
@products = Product.search("milk", page: params[:page], per_page: 20)
|
239
239
|
```
|
240
240
|
|
241
241
|
View with kaminari
|
@@ -255,13 +255,13 @@ View with will_paginate
|
|
255
255
|
By default, results must match all words in the query.
|
256
256
|
|
257
257
|
```ruby
|
258
|
-
Product.search
|
258
|
+
Product.search("fresh honey") # fresh AND honey
|
259
259
|
```
|
260
260
|
|
261
261
|
To change this, use:
|
262
262
|
|
263
263
|
```ruby
|
264
|
-
Product.search
|
264
|
+
Product.search("fresh honey", operator: "or") # fresh OR honey
|
265
265
|
```
|
266
266
|
|
267
267
|
By default, results must match the entire word - `back` will not match `backpack`. You can change this behavior with:
|
@@ -275,7 +275,7 @@ end
|
|
275
275
|
And to search (after you reindex):
|
276
276
|
|
277
277
|
```ruby
|
278
|
-
Product.search
|
278
|
+
Product.search("back", fields: [:name], match: :word_start)
|
279
279
|
```
|
280
280
|
|
281
281
|
Available options are:
|
@@ -297,7 +297,7 @@ The default is `:word`. The most matches will happen with `:word_middle`.
|
|
297
297
|
To match a field exactly (case-sensitive), use:
|
298
298
|
|
299
299
|
```ruby
|
300
|
-
|
300
|
+
Product.search(query, fields: [{email: :exact}, :name])
|
301
301
|
```
|
302
302
|
|
303
303
|
### Phrase Matches
|
@@ -305,7 +305,7 @@ User.search query, fields: [{email: :exact}, :name]
|
|
305
305
|
To only match the exact order, use:
|
306
306
|
|
307
307
|
```ruby
|
308
|
-
|
308
|
+
Product.search("fresh honey", match: :phrase)
|
309
309
|
```
|
310
310
|
|
311
311
|
### Stemming and Language
|
@@ -426,7 +426,7 @@ end
|
|
426
426
|
Search with:
|
427
427
|
|
428
428
|
```ruby
|
429
|
-
Product.search
|
429
|
+
Product.search(query, fields: [:name_tagged])
|
430
430
|
```
|
431
431
|
|
432
432
|
### Misspellings
|
@@ -436,13 +436,13 @@ By default, Searchkick handles misspelled queries by returning results with an [
|
|
436
436
|
You can change this with:
|
437
437
|
|
438
438
|
```ruby
|
439
|
-
Product.search
|
439
|
+
Product.search("zucini", misspellings: {edit_distance: 2}) # zucchini
|
440
440
|
```
|
441
441
|
|
442
442
|
To prevent poor precision and improve performance for correctly spelled queries (which should be a majority for most applications), Searchkick can first perform a search without misspellings, and if there are too few results, perform another with them.
|
443
443
|
|
444
444
|
```ruby
|
445
|
-
Product.search
|
445
|
+
Product.search("zuchini", misspellings: {below: 5})
|
446
446
|
```
|
447
447
|
|
448
448
|
If there are fewer than 5 results, a 2nd search is performed with misspellings enabled. The result of this query is returned.
|
@@ -450,13 +450,13 @@ If there are fewer than 5 results, a 2nd search is performed with misspellings e
|
|
450
450
|
Turn off misspellings with:
|
451
451
|
|
452
452
|
```ruby
|
453
|
-
Product.search
|
453
|
+
Product.search("zuchini", misspellings: false) # no zucchini
|
454
454
|
```
|
455
455
|
|
456
456
|
Specify which fields can include misspellings with:
|
457
457
|
|
458
458
|
```ruby
|
459
|
-
Product.search
|
459
|
+
Product.search("zucini", fields: [:name, :color], misspellings: {fields: [:name]})
|
460
460
|
```
|
461
461
|
|
462
462
|
> When doing this, you must also specify fields to search
|
@@ -466,7 +466,7 @@ Product.search "zucini", fields: [:name, :color], misspellings: {fields: [:name]
|
|
466
466
|
If a user searches `butter`, they may also get results for `peanut butter`. To prevent this, use:
|
467
467
|
|
468
468
|
```ruby
|
469
|
-
Product.search
|
469
|
+
Product.search("butter", exclude: ["peanut butter"])
|
470
470
|
```
|
471
471
|
|
472
472
|
You can map queries and terms to exclude with:
|
@@ -477,7 +477,7 @@ exclude_queries = {
|
|
477
477
|
"cream" => ["ice cream", "whipped cream"]
|
478
478
|
}
|
479
479
|
|
480
|
-
Product.search
|
480
|
+
Product.search(query, exclude: exclude_queries[query])
|
481
481
|
```
|
482
482
|
|
483
483
|
You can demote results by boosting by a factor less than one:
|
@@ -499,7 +499,7 @@ gem "gemoji-parser"
|
|
499
499
|
And use:
|
500
500
|
|
501
501
|
```ruby
|
502
|
-
Product.search
|
502
|
+
Product.search("🍨🍰", emoji: true)
|
503
503
|
```
|
504
504
|
|
505
505
|
## Indexing
|
@@ -592,6 +592,14 @@ There are four strategies for keeping the index synced with your database.
|
|
592
592
|
end
|
593
593
|
```
|
594
594
|
|
595
|
+
And reindex a record or relation manually.
|
596
|
+
|
597
|
+
```ruby
|
598
|
+
product.reindex
|
599
|
+
# or
|
600
|
+
store.products.reindex(mode: :async)
|
601
|
+
```
|
602
|
+
|
595
603
|
You can also do bulk updates.
|
596
604
|
|
597
605
|
```ruby
|
@@ -608,6 +616,12 @@ Searchkick.callbacks(false) do
|
|
608
616
|
end
|
609
617
|
```
|
610
618
|
|
619
|
+
Or override the model’s strategy.
|
620
|
+
|
621
|
+
```ruby
|
622
|
+
product.reindex(mode: :async) # :inline or :queue
|
623
|
+
```
|
624
|
+
|
611
625
|
### Associations
|
612
626
|
|
613
627
|
Data is **not** automatically synced when an association is updated. If this is desired, add a callback to reindex:
|
@@ -651,23 +665,19 @@ end
|
|
651
665
|
The best starting point to improve your search **by far** is to track searches and conversions. [Searchjoy](https://github.com/ankane/searchjoy) makes it easy.
|
652
666
|
|
653
667
|
```ruby
|
654
|
-
Product.search
|
668
|
+
Product.search("apple", track: {user_id: current_user.id})
|
655
669
|
```
|
656
670
|
|
657
|
-
[See the docs](https://github.com/ankane/searchjoy) for how to install and use.
|
658
|
-
|
659
|
-
Focus on:
|
660
|
-
|
661
|
-
- top searches with low conversions
|
662
|
-
- top searches with no results
|
671
|
+
[See the docs](https://github.com/ankane/searchjoy) for how to install and use. Focus on top searches with a low conversion rate.
|
663
672
|
|
664
|
-
Searchkick can then use the conversion data to learn what users are looking for. If a user searches for “ice cream” and adds Ben & Jerry’s Chunky Monkey to the cart (our conversion metric at Instacart), that item gets a little more weight for similar searches.
|
673
|
+
Searchkick can then use the conversion data to learn what users are looking for. If a user searches for “ice cream” and adds Ben & Jerry’s Chunky Monkey to the cart (our conversion metric at Instacart), that item gets a little more weight for similar searches. This can make a huge difference on the quality of your search.
|
665
674
|
|
666
675
|
Add conversion data with:
|
667
676
|
|
668
677
|
```ruby
|
669
678
|
class Product < ApplicationRecord
|
670
|
-
has_many :
|
679
|
+
has_many :conversions, class_name: "Searchjoy::Conversion", as: :convertable
|
680
|
+
has_many :searches, class_name: "Searchjoy::Search", through: :conversions
|
671
681
|
|
672
682
|
searchkick conversions: [:conversions] # name of field
|
673
683
|
|
@@ -681,15 +691,100 @@ class Product < ApplicationRecord
|
|
681
691
|
end
|
682
692
|
```
|
683
693
|
|
684
|
-
Reindex and set up a cron job to add new conversions daily.
|
694
|
+
Reindex and set up a cron job to add new conversions daily. For zero downtime deployment, temporarily set `conversions: false` in your search calls until the data is reindexed.
|
695
|
+
|
696
|
+
### Performant Conversions
|
697
|
+
|
698
|
+
A performant way to do conversions is to cache them to prevent N+1 queries. For Postgres, create a migration with:
|
699
|
+
|
700
|
+
```ruby
|
701
|
+
add_column :products, :search_conversions, :jsonb
|
702
|
+
```
|
703
|
+
|
704
|
+
For MySQL, use `:json`, and for others, use `:text` with a [JSON serializer](https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html).
|
705
|
+
|
706
|
+
Next, update your model. Create a separate method for conversion data so you can use [partial reindexing](#partial-reindexing).
|
707
|
+
|
708
|
+
```ruby
|
709
|
+
class Product < ApplicationRecord
|
710
|
+
searchkick conversions: [:conversions]
|
711
|
+
|
712
|
+
def search_data
|
713
|
+
{
|
714
|
+
name: name,
|
715
|
+
category: category
|
716
|
+
}.merge(conversions_data)
|
717
|
+
end
|
718
|
+
|
719
|
+
def conversions_data
|
720
|
+
{
|
721
|
+
conversions: search_conversions || {}
|
722
|
+
}
|
723
|
+
end
|
724
|
+
end
|
725
|
+
```
|
726
|
+
|
727
|
+
Deploy and reindex your data. For zero downtime deployment, temporarily set `conversions: false` in your search calls until the data is reindexed.
|
728
|
+
|
729
|
+
```ruby
|
730
|
+
Product.reindex
|
731
|
+
```
|
732
|
+
|
733
|
+
Then, create a job to update the conversions column and reindex records with new conversions. Here’s one you can use for Searchjoy:
|
734
|
+
|
735
|
+
```ruby
|
736
|
+
class UpdateConversionsJob < ApplicationJob
|
737
|
+
def perform(class_name, since: nil, update: true, reindex: true)
|
738
|
+
model = Searchkick.load_model(class_name)
|
739
|
+
|
740
|
+
# get records that have a recent conversion
|
741
|
+
recently_converted_ids =
|
742
|
+
Searchjoy::Conversion.where(convertable_type: class_name).where(created_at: since..)
|
743
|
+
.order(:convertable_id).distinct.pluck(:convertable_id)
|
744
|
+
|
745
|
+
# split into batches
|
746
|
+
recently_converted_ids.in_groups_of(1000, false) do |ids|
|
747
|
+
if update
|
748
|
+
# fetch conversions
|
749
|
+
conversions =
|
750
|
+
Searchjoy::Conversion.where(convertable_id: ids, convertable_type: class_name)
|
751
|
+
.joins(:search).where.not(searchjoy_searches: {user_id: nil})
|
752
|
+
.group(:convertable_id, :query).distinct.count(:user_id)
|
753
|
+
|
754
|
+
# group by record
|
755
|
+
conversions_by_record = {}
|
756
|
+
conversions.each do |(id, query), count|
|
757
|
+
(conversions_by_record[id] ||= {})[query] = count
|
758
|
+
end
|
759
|
+
|
760
|
+
# update conversions column
|
761
|
+
model.transaction do
|
762
|
+
conversions_by_record.each do |id, conversions|
|
763
|
+
model.where(id: id).update_all(search_conversions: conversions)
|
764
|
+
end
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
if reindex
|
769
|
+
# reindex conversions data
|
770
|
+
model.where(id: ids).reindex(:conversions_data)
|
771
|
+
end
|
772
|
+
end
|
773
|
+
end
|
774
|
+
end
|
775
|
+
```
|
776
|
+
|
777
|
+
Run the job:
|
685
778
|
|
686
779
|
```ruby
|
687
|
-
|
780
|
+
UpdateConversionsJob.perform_now("Product")
|
688
781
|
```
|
689
782
|
|
690
|
-
|
783
|
+
And set it up to run daily.
|
691
784
|
|
692
|
-
|
785
|
+
```ruby
|
786
|
+
UpdateConversionsJob.perform_later("Product", since: 1.day.ago)
|
787
|
+
```
|
693
788
|
|
694
789
|
## Personalized Results
|
695
790
|
|
@@ -709,7 +804,7 @@ end
|
|
709
804
|
Reindex and search with:
|
710
805
|
|
711
806
|
```ruby
|
712
|
-
Product.search
|
807
|
+
Product.search("milk", boost_where: {orderer_ids: current_user.id})
|
713
808
|
```
|
714
809
|
|
715
810
|
## Instant Search / Autocomplete
|
@@ -733,7 +828,7 @@ end
|
|
733
828
|
Reindex and search with:
|
734
829
|
|
735
830
|
```ruby
|
736
|
-
Movie.search
|
831
|
+
Movie.search("jurassic pa", fields: [:title], match: :word_start)
|
737
832
|
```
|
738
833
|
|
739
834
|
Typically, you want to use a JavaScript library like [typeahead.js](https://twitter.github.io/typeahead.js/) or [jQuery UI](https://jqueryui.com/autocomplete/).
|
@@ -793,7 +888,7 @@ end
|
|
793
888
|
Reindex and search with:
|
794
889
|
|
795
890
|
```ruby
|
796
|
-
products = Product.search
|
891
|
+
products = Product.search("peantu butta", suggest: true)
|
797
892
|
products.suggestions # ["peanut butter"]
|
798
893
|
```
|
799
894
|
|
@@ -804,40 +899,40 @@ products.suggestions # ["peanut butter"]
|
|
804
899
|

|
805
900
|
|
806
901
|
```ruby
|
807
|
-
products = Product.search
|
902
|
+
products = Product.search("chuck taylor", aggs: [:product_type, :gender, :brand])
|
808
903
|
products.aggs
|
809
904
|
```
|
810
905
|
|
811
906
|
By default, `where` conditions apply to aggregations.
|
812
907
|
|
813
908
|
```ruby
|
814
|
-
Product.search
|
909
|
+
Product.search("wingtips", where: {color: "brandy"}, aggs: [:size])
|
815
910
|
# aggregations for brandy wingtips are returned
|
816
911
|
```
|
817
912
|
|
818
913
|
Change this with:
|
819
914
|
|
820
915
|
```ruby
|
821
|
-
Product.search
|
916
|
+
Product.search("wingtips", where: {color: "brandy"}, aggs: [:size], smart_aggs: false)
|
822
917
|
# aggregations for all wingtips are returned
|
823
918
|
```
|
824
919
|
|
825
920
|
Set `where` conditions for each aggregation separately with:
|
826
921
|
|
827
922
|
```ruby
|
828
|
-
Product.search
|
923
|
+
Product.search("wingtips", aggs: {size: {where: {color: "brandy"}}})
|
829
924
|
```
|
830
925
|
|
831
926
|
Limit
|
832
927
|
|
833
928
|
```ruby
|
834
|
-
Product.search
|
929
|
+
Product.search("apples", aggs: {store_id: {limit: 10}})
|
835
930
|
```
|
836
931
|
|
837
932
|
Order
|
838
933
|
|
839
934
|
```ruby
|
840
|
-
Product.search
|
935
|
+
Product.search("wingtips", aggs: {color: {order: {"_key" => "asc"}}}) # alphabetically
|
841
936
|
```
|
842
937
|
|
843
938
|
[All of these options are supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-order)
|
@@ -846,31 +941,31 @@ Ranges
|
|
846
941
|
|
847
942
|
```ruby
|
848
943
|
price_ranges = [{to: 20}, {from: 20, to: 50}, {from: 50}]
|
849
|
-
Product.search
|
944
|
+
Product.search("*", aggs: {price: {ranges: price_ranges}})
|
850
945
|
```
|
851
946
|
|
852
947
|
Minimum document count
|
853
948
|
|
854
949
|
```ruby
|
855
|
-
Product.search
|
950
|
+
Product.search("apples", aggs: {store_id: {min_doc_count: 2}})
|
856
951
|
```
|
857
952
|
|
858
953
|
Script support
|
859
954
|
|
860
955
|
```ruby
|
861
|
-
Product.search
|
956
|
+
Product.search("*", aggs: {color: {script: {source: "'Color: ' + _value"}}})
|
862
957
|
```
|
863
958
|
|
864
959
|
Date histogram
|
865
960
|
|
866
961
|
```ruby
|
867
|
-
Product.search
|
962
|
+
Product.search("pear", aggs: {products_per_year: {date_histogram: {field: :created_at, interval: :year}}})
|
868
963
|
```
|
869
964
|
|
870
965
|
For other aggregation types, including sub-aggregations, use `body_options`:
|
871
966
|
|
872
967
|
```ruby
|
873
|
-
Product.search
|
968
|
+
Product.search("orange", body_options: {aggs: {price: {histogram: {field: :price, interval: 10}}}})
|
874
969
|
```
|
875
970
|
|
876
971
|
## Highlight
|
@@ -878,7 +973,7 @@ Product.search "orange", body_options: {aggs: {price: {histogram: {field: :price
|
|
878
973
|
Specify which fields to index with highlighting.
|
879
974
|
|
880
975
|
```ruby
|
881
|
-
class
|
976
|
+
class Band < ApplicationRecord
|
882
977
|
searchkick highlight: [:name]
|
883
978
|
end
|
884
979
|
```
|
@@ -886,7 +981,7 @@ end
|
|
886
981
|
Highlight the search query in the results.
|
887
982
|
|
888
983
|
```ruby
|
889
|
-
bands = Band.search
|
984
|
+
bands = Band.search("cinema", highlight: true)
|
890
985
|
```
|
891
986
|
|
892
987
|
View the highlighted fields with:
|
@@ -900,19 +995,19 @@ end
|
|
900
995
|
To change the tag, use:
|
901
996
|
|
902
997
|
```ruby
|
903
|
-
Band.search
|
998
|
+
Band.search("cinema", highlight: {tag: "<strong>"})
|
904
999
|
```
|
905
1000
|
|
906
1001
|
To highlight and search different fields, use:
|
907
1002
|
|
908
1003
|
```ruby
|
909
|
-
Band.search
|
1004
|
+
Band.search("cinema", fields: [:name], highlight: {fields: [:description]})
|
910
1005
|
```
|
911
1006
|
|
912
1007
|
By default, the entire field is highlighted. To get small snippets instead, use:
|
913
1008
|
|
914
1009
|
```ruby
|
915
|
-
bands = Band.search
|
1010
|
+
bands = Band.search("cinema", highlight: {fragment_size: 20})
|
916
1011
|
bands.with_highlights(multiple: true).each do |band, highlights|
|
917
1012
|
highlights[:name].join(" and ")
|
918
1013
|
end
|
@@ -921,7 +1016,7 @@ end
|
|
921
1016
|
Additional options can be specified for each field:
|
922
1017
|
|
923
1018
|
```ruby
|
924
|
-
Band.search
|
1019
|
+
Band.search("cinema", fields: [:name], highlight: {fields: {name: {fragment_size: 200}}})
|
925
1020
|
```
|
926
1021
|
|
927
1022
|
You can find available highlight options in the [Elasticsearch reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/highlighting.html).
|
@@ -950,13 +1045,13 @@ end
|
|
950
1045
|
Reindex and search with:
|
951
1046
|
|
952
1047
|
```ruby
|
953
|
-
Restaurant.search
|
1048
|
+
Restaurant.search("pizza", where: {location: {near: {lat: 37, lon: -114}, within: "100mi"}}) # or 160km
|
954
1049
|
```
|
955
1050
|
|
956
1051
|
Bounded by a box
|
957
1052
|
|
958
1053
|
```ruby
|
959
|
-
Restaurant.search
|
1054
|
+
Restaurant.search("sushi", where: {location: {top_left: {lat: 38, lon: -123}, bottom_right: {lat: 37, lon: -122}}})
|
960
1055
|
```
|
961
1056
|
|
962
1057
|
**Note:** `top_right` and `bottom_left` also work
|
@@ -964,7 +1059,7 @@ Restaurant.search "sushi", where: {location: {top_left: {lat: 38, lon: -123}, bo
|
|
964
1059
|
Bounded by a polygon
|
965
1060
|
|
966
1061
|
```ruby
|
967
|
-
Restaurant.search
|
1062
|
+
Restaurant.search("dessert", where: {location: {geo_polygon: {points: [{lat: 38, lon: -123}, {lat: 39, lon: -123}, {lat: 37, lon: 122}]}}})
|
968
1063
|
```
|
969
1064
|
|
970
1065
|
### Boost By Distance
|
@@ -972,13 +1067,13 @@ Restaurant.search "dessert", where: {location: {geo_polygon: {points: [{lat: 38,
|
|
972
1067
|
Boost results by distance - closer results are boosted more
|
973
1068
|
|
974
1069
|
```ruby
|
975
|
-
Restaurant.search
|
1070
|
+
Restaurant.search("noodles", boost_by_distance: {location: {origin: {lat: 37, lon: -122}}})
|
976
1071
|
```
|
977
1072
|
|
978
1073
|
Also supports [additional options](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#function-decay)
|
979
1074
|
|
980
1075
|
```ruby
|
981
|
-
Restaurant.search
|
1076
|
+
Restaurant.search("wings", boost_by_distance: {location: {origin: {lat: 37, lon: -122}, function: "linear", scale: "30mi", decay: 0.5}})
|
982
1077
|
```
|
983
1078
|
|
984
1079
|
### Geo Shapes
|
@@ -1005,19 +1100,19 @@ See the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsea
|
|
1005
1100
|
Find shapes intersecting with the query shape
|
1006
1101
|
|
1007
1102
|
```ruby
|
1008
|
-
Restaurant.search
|
1103
|
+
Restaurant.search("soup", where: {bounds: {geo_shape: {type: "polygon", coordinates: [[{lat: 38, lon: -123}, ...]]}}})
|
1009
1104
|
```
|
1010
1105
|
|
1011
1106
|
Falling entirely within the query shape
|
1012
1107
|
|
1013
1108
|
```ruby
|
1014
|
-
Restaurant.search
|
1109
|
+
Restaurant.search("salad", where: {bounds: {geo_shape: {type: "circle", relation: "within", coordinates: [{lat: 38, lon: -123}], radius: "1km"}}})
|
1015
1110
|
```
|
1016
1111
|
|
1017
1112
|
Not touching the query shape
|
1018
1113
|
|
1019
1114
|
```ruby
|
1020
|
-
Restaurant.search
|
1115
|
+
Restaurant.search("burger", where: {bounds: {geo_shape: {type: "envelope", relation: "disjoint", coordinates: [{lat: 38, lon: -123}, {lat: 37, lon: -122}]}}})
|
1021
1116
|
```
|
1022
1117
|
|
1023
1118
|
## Inheritance
|
@@ -1047,9 +1142,9 @@ Dog.reindex # equivalent, all animals reindexed
|
|
1047
1142
|
And to search, use:
|
1048
1143
|
|
1049
1144
|
```ruby
|
1050
|
-
Animal.search
|
1051
|
-
Dog.search
|
1052
|
-
Animal.search
|
1145
|
+
Animal.search("*") # all animals
|
1146
|
+
Dog.search("*") # just dogs
|
1147
|
+
Animal.search("*", type: [Dog, Cat]) # just cats and dogs
|
1053
1148
|
```
|
1054
1149
|
|
1055
1150
|
**Notes:**
|
@@ -1057,7 +1152,7 @@ Animal.search "*", type: [Dog, Cat] # just cats and dogs
|
|
1057
1152
|
1. The `suggest` option retrieves suggestions from the parent at the moment.
|
1058
1153
|
|
1059
1154
|
```ruby
|
1060
|
-
Dog.search
|
1155
|
+
Dog.search("airbudd", suggest: true) # suggestions for all animals
|
1061
1156
|
```
|
1062
1157
|
2. This relies on a `type` field that is automatically added to the indexed document. Be wary of defining your own `type` field in `search_data`, as it will take precedence.
|
1063
1158
|
|
@@ -1380,6 +1475,8 @@ Create an initializer with multiple hosts:
|
|
1380
1475
|
|
1381
1476
|
```ruby
|
1382
1477
|
ENV["ELASTICSEARCH_URL"] = "https://user:password@host1,https://user:password@host2"
|
1478
|
+
# or
|
1479
|
+
ENV["OPENSEARCH_URL"] = "https://user:password@host1,https://user:password@host2"
|
1383
1480
|
```
|
1384
1481
|
|
1385
1482
|
See [elastic-transport](https://github.com/elastic/elastic-transport-ruby) or [opensearch-transport](https://github.com/opensearch-project/opensearch-ruby/tree/main/opensearch-transport) for a complete list of options.
|
@@ -1562,7 +1659,7 @@ end
|
|
1562
1659
|
Reindex and search with:
|
1563
1660
|
|
1564
1661
|
```ruby
|
1565
|
-
Business.search
|
1662
|
+
Business.search("ice cream", routing: params[:city_id])
|
1566
1663
|
```
|
1567
1664
|
|
1568
1665
|
### Partial Reindexing
|
@@ -1573,11 +1670,12 @@ Reindex a subset of attributes to reduce time spent generating search data and c
|
|
1573
1670
|
class Product < ApplicationRecord
|
1574
1671
|
def search_data
|
1575
1672
|
{
|
1576
|
-
name: name
|
1577
|
-
|
1673
|
+
name: name,
|
1674
|
+
category: category
|
1675
|
+
}.merge(prices_data)
|
1578
1676
|
end
|
1579
1677
|
|
1580
|
-
def
|
1678
|
+
def prices_data
|
1581
1679
|
{
|
1582
1680
|
price: price,
|
1583
1681
|
sale_price: sale_price
|
@@ -1589,68 +1687,7 @@ end
|
|
1589
1687
|
And use:
|
1590
1688
|
|
1591
1689
|
```ruby
|
1592
|
-
Product.reindex(:
|
1593
|
-
```
|
1594
|
-
|
1595
|
-
### Performant Conversions
|
1596
|
-
|
1597
|
-
Split out conversions into a separate method so you can use partial reindexing, and cache conversions to prevent N+1 queries. Be sure to use a centralized cache store like Memcached or Redis.
|
1598
|
-
|
1599
|
-
```ruby
|
1600
|
-
class Product < ApplicationRecord
|
1601
|
-
def search_data
|
1602
|
-
{
|
1603
|
-
name: name
|
1604
|
-
}.merge(search_conversions)
|
1605
|
-
end
|
1606
|
-
|
1607
|
-
def search_conversions
|
1608
|
-
{
|
1609
|
-
conversions: Rails.cache.read("search_conversions:#{self.class.name}:#{id}") || {}
|
1610
|
-
}
|
1611
|
-
end
|
1612
|
-
end
|
1613
|
-
```
|
1614
|
-
|
1615
|
-
Create a job to update the cache and reindex records with new conversions.
|
1616
|
-
|
1617
|
-
```ruby
|
1618
|
-
class ReindexConversionsJob < ApplicationJob
|
1619
|
-
def perform(class_name)
|
1620
|
-
# get records that have a recent conversion
|
1621
|
-
recently_converted_ids =
|
1622
|
-
Searchjoy::Search.where("convertable_type = ? AND converted_at > ?", class_name, 1.day.ago)
|
1623
|
-
.order(:convertable_id).distinct.pluck(:convertable_id)
|
1624
|
-
|
1625
|
-
# split into groups
|
1626
|
-
recently_converted_ids.in_groups_of(1000, false) do |ids|
|
1627
|
-
# fetch conversions
|
1628
|
-
conversions =
|
1629
|
-
Searchjoy::Search.where(convertable_id: ids, convertable_type: class_name)
|
1630
|
-
.group(:convertable_id, :query).distinct.count(:user_id)
|
1631
|
-
|
1632
|
-
# group conversions by record
|
1633
|
-
conversions_by_record = {}
|
1634
|
-
conversions.each do |(id, query), count|
|
1635
|
-
(conversions_by_record[id] ||= {})[query] = count
|
1636
|
-
end
|
1637
|
-
|
1638
|
-
# write to cache
|
1639
|
-
conversions_by_record.each do |id, conversions|
|
1640
|
-
Rails.cache.write("search_conversions:#{class_name}:#{id}", conversions)
|
1641
|
-
end
|
1642
|
-
|
1643
|
-
# partial reindex
|
1644
|
-
class_name.constantize.where(id: ids).reindex(:search_conversions)
|
1645
|
-
end
|
1646
|
-
end
|
1647
|
-
end
|
1648
|
-
```
|
1649
|
-
|
1650
|
-
Run the job with:
|
1651
|
-
|
1652
|
-
```ruby
|
1653
|
-
ReindexConversionsJob.perform_later("Product")
|
1690
|
+
Product.reindex(:prices_data)
|
1654
1691
|
```
|
1655
1692
|
|
1656
1693
|
## Advanced
|
@@ -1685,7 +1722,7 @@ end
|
|
1685
1722
|
And use the `body` option to search:
|
1686
1723
|
|
1687
1724
|
```ruby
|
1688
|
-
products = Product.search
|
1725
|
+
products = Product.search(body: {query: {match: {name: "milk"}}})
|
1689
1726
|
```
|
1690
1727
|
|
1691
1728
|
View the response with:
|
@@ -1697,14 +1734,14 @@ products.response
|
|
1697
1734
|
To modify the query generated by Searchkick, use:
|
1698
1735
|
|
1699
1736
|
```ruby
|
1700
|
-
products = Product.search
|
1737
|
+
products = Product.search("milk", body_options: {min_score: 1})
|
1701
1738
|
```
|
1702
1739
|
|
1703
1740
|
or
|
1704
1741
|
|
1705
1742
|
```ruby
|
1706
1743
|
products =
|
1707
|
-
Product.search
|
1744
|
+
Product.search("apples") do |body|
|
1708
1745
|
body[:min_score] = 1
|
1709
1746
|
end
|
1710
1747
|
```
|
@@ -1736,7 +1773,7 @@ Then use `products` and `coupons` as typical results.
|
|
1736
1773
|
Search across multiple models with:
|
1737
1774
|
|
1738
1775
|
```ruby
|
1739
|
-
Searchkick.search
|
1776
|
+
Searchkick.search("milk", models: [Product, Category])
|
1740
1777
|
```
|
1741
1778
|
|
1742
1779
|
Boost specific models with:
|
@@ -1762,7 +1799,7 @@ end
|
|
1762
1799
|
You can also scroll batches manually.
|
1763
1800
|
|
1764
1801
|
```ruby
|
1765
|
-
products = Product.search
|
1802
|
+
products = Product.search("*", scroll: "1m")
|
1766
1803
|
while products.any?
|
1767
1804
|
# process batch ...
|
1768
1805
|
|
@@ -1793,7 +1830,7 @@ Product.search("pears", body_options: {track_total_hits: true})
|
|
1793
1830
|
To query nested data, use dot notation.
|
1794
1831
|
|
1795
1832
|
```ruby
|
1796
|
-
|
1833
|
+
Product.search("san", fields: ["store.city"], where: {"store.zip_code" => 12345})
|
1797
1834
|
```
|
1798
1835
|
|
1799
1836
|
## Reference
|
@@ -1923,7 +1960,7 @@ Searchkick.queue_name = :search_reindex
|
|
1923
1960
|
Eager load associations
|
1924
1961
|
|
1925
1962
|
```ruby
|
1926
|
-
Product.search
|
1963
|
+
Product.search("milk", includes: [:brand, :stores])
|
1927
1964
|
```
|
1928
1965
|
|
1929
1966
|
Eager load different associations by model
|
@@ -1935,7 +1972,7 @@ Searchkick.search("*", models: [Product, Store], model_includes: {Product => [:
|
|
1935
1972
|
Run additional scopes on results
|
1936
1973
|
|
1937
1974
|
```ruby
|
1938
|
-
Product.search
|
1975
|
+
Product.search("milk", scope_results: ->(r) { r.with_attached_images })
|
1939
1976
|
```
|
1940
1977
|
|
1941
1978
|
Specify default fields to search
|
@@ -2031,13 +2068,25 @@ rake searchkick:reindex:all
|
|
2031
2068
|
Turn on misspellings after a certain number of characters
|
2032
2069
|
|
2033
2070
|
```ruby
|
2034
|
-
Product.search
|
2071
|
+
Product.search("api", misspellings: {prefix_length: 2}) # api, apt, no ahi
|
2072
|
+
```
|
2073
|
+
|
2074
|
+
**Note:** With this option, if the query length is the same as `prefix_length`, misspellings are turned off with Elasticsearch 7 and OpenSearch 1
|
2075
|
+
|
2076
|
+
```ruby
|
2077
|
+
Product.search("ah", misspellings: {prefix_length: 2}) # ah, no aha
|
2035
2078
|
```
|
2036
2079
|
|
2037
|
-
|
2080
|
+
BigDecimal values are indexed as floats by default so they can be used for boosting. Convert them to strings to keep full precision.
|
2038
2081
|
|
2039
2082
|
```ruby
|
2040
|
-
Product
|
2083
|
+
class Product < ApplicationRecord
|
2084
|
+
def search_data
|
2085
|
+
{
|
2086
|
+
units: units.to_s("F")
|
2087
|
+
}
|
2088
|
+
end
|
2089
|
+
end
|
2041
2090
|
```
|
2042
2091
|
|
2043
2092
|
## Gotchas
|
data/lib/searchkick/index.rb
CHANGED
@@ -418,7 +418,7 @@ module Searchkick
|
|
418
418
|
true
|
419
419
|
end
|
420
420
|
rescue => e
|
421
|
-
if Searchkick.transport_error?(e) && e.message.include?("No handler for type [text]")
|
421
|
+
if Searchkick.transport_error?(e) && (e.message.include?("No handler for type [text]") || e.message.include?("class java.util.ArrayList cannot be cast to class java.util.Map"))
|
422
422
|
raise UnsupportedVersionError
|
423
423
|
end
|
424
424
|
|
@@ -19,7 +19,7 @@ module Searchkick
|
|
19
19
|
mappings = generate_mappings.deep_symbolize_keys.deep_merge(custom_mappings)
|
20
20
|
end
|
21
21
|
|
22
|
-
set_deep_paging(settings) if options[:deep_paging]
|
22
|
+
set_deep_paging(settings) if options[:deep_paging] || options[:max_result_window]
|
23
23
|
|
24
24
|
{
|
25
25
|
settings: settings,
|
@@ -525,7 +525,7 @@ module Searchkick
|
|
525
525
|
def set_deep_paging(settings)
|
526
526
|
if !settings.dig(:index, :max_result_window) && !settings[:"index.max_result_window"]
|
527
527
|
settings[:index] ||= {}
|
528
|
-
settings[:index][:max_result_window] = 1_000_000_000
|
528
|
+
settings[:index][:max_result_window] = options[:max_result_window] || 1_000_000_000
|
529
529
|
end
|
530
530
|
end
|
531
531
|
|
data/lib/searchkick/model.rb
CHANGED
@@ -5,7 +5,7 @@ module Searchkick
|
|
5
5
|
|
6
6
|
unknown_keywords = options.keys - [:_all, :_type, :batch_size, :callbacks, :case_sensitive, :conversions, :deep_paging, :default_fields,
|
7
7
|
:filterable, :geo_shape, :highlight, :ignore_above, :index_name, :index_prefix, :inheritance, :language,
|
8
|
-
:locations, :mappings, :match, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity,
|
8
|
+
:locations, :mappings, :match, :max_result_window, :merge_mappings, :routing, :searchable, :search_synonyms, :settings, :similarity,
|
9
9
|
:special_characters, :stem, :stemmer, :stem_conversions, :stem_exclusion, :stemmer_override, :suggest, :synonyms, :text_end,
|
10
10
|
:text_middle, :text_start, :unscope, :word, :word_end, :word_middle, :word_start]
|
11
11
|
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
|
@@ -66,7 +66,7 @@ module Searchkick
|
|
66
66
|
alias_method Searchkick.search_method_name, :searchkick_search if Searchkick.search_method_name
|
67
67
|
|
68
68
|
def searchkick_index(name: nil)
|
69
|
-
index_name = name || searchkick_index_name
|
69
|
+
index_name = name || searchkick_klass.searchkick_index_name
|
70
70
|
index_name = index_name.call if index_name.respond_to?(:call)
|
71
71
|
index_cache = class_variable_get(:@@searchkick_index_cache)
|
72
72
|
index_cache.fetch(index_name) { Searchkick::Index.new(index_name, searchkick_options) }
|
data/lib/searchkick/query.rb
CHANGED
@@ -254,6 +254,12 @@ module Searchkick
|
|
254
254
|
offset = options[:offset] || (page - 1) * per_page + padding
|
255
255
|
scroll = options[:scroll]
|
256
256
|
|
257
|
+
max_result_window = searchkick_options[:max_result_window]
|
258
|
+
if max_result_window
|
259
|
+
offset = max_result_window if offset > max_result_window
|
260
|
+
per_page = max_result_window - offset if offset + per_page > max_result_window
|
261
|
+
end
|
262
|
+
|
257
263
|
# model and eager loading
|
258
264
|
load = options[:load].nil? ? true : options[:load]
|
259
265
|
|
data/lib/searchkick/relation.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
module Searchkick
|
2
2
|
class Relation
|
3
|
+
NO_DEFAULT_VALUE = Object.new
|
4
|
+
|
3
5
|
# note: modifying body directly is not supported
|
4
6
|
# and has no impact on query after being executed
|
5
7
|
# TODO freeze body object?
|
6
|
-
delegate :body, :params, to:
|
8
|
+
delegate :body, :params, to: :query
|
7
9
|
delegate_missing_to :private_execute
|
8
10
|
|
9
11
|
def initialize(model, term = "*", **options)
|
10
|
-
@
|
12
|
+
@model = model
|
13
|
+
@term = term
|
14
|
+
@options = options
|
15
|
+
|
16
|
+
# generate query to validate options
|
17
|
+
query
|
11
18
|
end
|
12
19
|
|
13
20
|
# same as Active Record
|
@@ -23,14 +30,196 @@ module Searchkick
|
|
23
30
|
self
|
24
31
|
end
|
25
32
|
|
33
|
+
# experimental
|
34
|
+
def limit(value)
|
35
|
+
clone.limit!(value)
|
36
|
+
end
|
37
|
+
|
38
|
+
# experimental
|
39
|
+
def limit!(value)
|
40
|
+
check_loaded
|
41
|
+
@options[:limit] = value
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# experimental
|
46
|
+
def offset(value = NO_DEFAULT_VALUE)
|
47
|
+
# TODO remove in Searchkick 6
|
48
|
+
if value == NO_DEFAULT_VALUE
|
49
|
+
private_execute.offset
|
50
|
+
else
|
51
|
+
clone.offset!(value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# experimental
|
56
|
+
def offset!(value)
|
57
|
+
check_loaded
|
58
|
+
@options[:offset] = value
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# experimental
|
63
|
+
def page(value)
|
64
|
+
clone.page!(value)
|
65
|
+
end
|
66
|
+
|
67
|
+
# experimental
|
68
|
+
def page!(value)
|
69
|
+
check_loaded
|
70
|
+
@options[:page] = value
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
# experimental
|
75
|
+
def per_page(value = NO_DEFAULT_VALUE)
|
76
|
+
# TODO remove in Searchkick 6
|
77
|
+
if value == NO_DEFAULT_VALUE
|
78
|
+
private_execute.per_page
|
79
|
+
else
|
80
|
+
clone.per_page!(value)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# experimental
|
85
|
+
def per_page!(value)
|
86
|
+
check_loaded
|
87
|
+
@options[:per_page] = value
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# experimental
|
92
|
+
def where(value = NO_DEFAULT_VALUE)
|
93
|
+
if value == NO_DEFAULT_VALUE
|
94
|
+
Where.new(self)
|
95
|
+
else
|
96
|
+
clone.where!(value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# experimental
|
101
|
+
def where!(value)
|
102
|
+
check_loaded
|
103
|
+
if @options[:where]
|
104
|
+
@options[:where] = {_and: [@options[:where], ensure_permitted(value)]}
|
105
|
+
else
|
106
|
+
@options[:where] = ensure_permitted(value)
|
107
|
+
end
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# experimental
|
112
|
+
def rewhere(value)
|
113
|
+
clone.rewhere!(value)
|
114
|
+
end
|
115
|
+
|
116
|
+
# experimental
|
117
|
+
def rewhere!(value)
|
118
|
+
check_loaded
|
119
|
+
@options[:where] = ensure_permitted(value)
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
# experimental
|
124
|
+
def order(*values)
|
125
|
+
clone.order!(*values)
|
126
|
+
end
|
127
|
+
|
128
|
+
# experimental
|
129
|
+
def order!(*values)
|
130
|
+
values = values.first if values.size == 1 && values.first.is_a?(Array)
|
131
|
+
check_loaded
|
132
|
+
(@options[:order] ||= []).concat(values)
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
# experimental
|
137
|
+
def reorder(*values)
|
138
|
+
clone.reorder!(*values)
|
139
|
+
end
|
140
|
+
|
141
|
+
# experimental
|
142
|
+
def reorder!(*values)
|
143
|
+
check_loaded
|
144
|
+
@options[:order] = values
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
# experimental
|
149
|
+
def select(*values, &block)
|
150
|
+
if block_given?
|
151
|
+
private_execute.select(*values, &block)
|
152
|
+
else
|
153
|
+
clone.select!(*values)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# experimental
|
158
|
+
def select!(*values)
|
159
|
+
check_loaded
|
160
|
+
(@options[:select] ||= []).concat(values)
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
# experimental
|
165
|
+
def reselect(*values)
|
166
|
+
clone.reselect!(*values)
|
167
|
+
end
|
168
|
+
|
169
|
+
# experimental
|
170
|
+
def reselect!(*values)
|
171
|
+
check_loaded
|
172
|
+
@options[:select] = values
|
173
|
+
self
|
174
|
+
end
|
175
|
+
|
176
|
+
# experimental
|
177
|
+
def includes(*values)
|
178
|
+
clone.includes!(*values)
|
179
|
+
end
|
180
|
+
|
181
|
+
# experimental
|
182
|
+
def includes!(*values)
|
183
|
+
check_loaded
|
184
|
+
(@options[:includes] ||= []).concat(values)
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# experimental
|
189
|
+
def only(*keys)
|
190
|
+
Relation.new(@model, @term, **@options.slice(*keys))
|
191
|
+
end
|
192
|
+
|
193
|
+
# experimental
|
194
|
+
def except(*keys)
|
195
|
+
Relation.new(@model, @term, **@options.except(*keys))
|
196
|
+
end
|
197
|
+
|
198
|
+
def loaded?
|
199
|
+
!@execute.nil?
|
200
|
+
end
|
201
|
+
|
26
202
|
private
|
27
203
|
|
28
204
|
def private_execute
|
29
|
-
@execute ||=
|
205
|
+
@execute ||= query.execute
|
30
206
|
end
|
31
207
|
|
32
208
|
def query
|
33
|
-
@query
|
209
|
+
@query ||= Query.new(@model, @term, **@options)
|
210
|
+
end
|
211
|
+
|
212
|
+
def check_loaded
|
213
|
+
raise Error, "Relation loaded" if loaded?
|
214
|
+
|
215
|
+
# reset query since options will change
|
216
|
+
@query = nil
|
217
|
+
end
|
218
|
+
|
219
|
+
# provides *very* basic protection from unfiltered parameters
|
220
|
+
# this is not meant to be comprehensive and may be expanded in the future
|
221
|
+
def ensure_permitted(obj)
|
222
|
+
obj.to_h
|
34
223
|
end
|
35
224
|
end
|
36
225
|
end
|
data/lib/searchkick/version.rb
CHANGED
data/lib/searchkick.rb
CHANGED
@@ -27,6 +27,7 @@ require "searchkick/relation"
|
|
27
27
|
require "searchkick/relation_indexer"
|
28
28
|
require "searchkick/results"
|
29
29
|
require "searchkick/version"
|
30
|
+
require "searchkick/where"
|
30
31
|
|
31
32
|
# integrations
|
32
33
|
require "searchkick/railtie" if defined?(Rails)
|
@@ -134,8 +135,9 @@ module Searchkick
|
|
134
135
|
@opensearch
|
135
136
|
end
|
136
137
|
|
137
|
-
|
138
|
-
|
138
|
+
# TODO always check true version in Searchkick 6
|
139
|
+
def self.server_below?(version, true_version = false)
|
140
|
+
server_version = !true_version && opensearch? ? "7.10.2" : self.server_version
|
139
141
|
Gem::Version.new(server_version.split("-")[0]) < Gem::Version.new(version.split("-")[0])
|
140
142
|
end
|
141
143
|
|
@@ -283,7 +285,7 @@ module Searchkick
|
|
283
285
|
relation
|
284
286
|
end
|
285
287
|
|
286
|
-
#
|
288
|
+
# public (for reindexing conversions)
|
287
289
|
def self.load_model(class_name, allow_child: false)
|
288
290
|
model = class_name.safe_constantize
|
289
291
|
raise Error, "Could not find class: #{class_name}" unless model
|
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: 5.0.
|
4
|
+
version: 5.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -71,6 +71,7 @@ files:
|
|
71
71
|
- lib/searchkick/relation_indexer.rb
|
72
72
|
- lib/searchkick/results.rb
|
73
73
|
- lib/searchkick/version.rb
|
74
|
+
- lib/searchkick/where.rb
|
74
75
|
- lib/tasks/searchkick.rake
|
75
76
|
homepage: https://github.com/ankane/searchkick
|
76
77
|
licenses:
|
@@ -91,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
92
|
- !ruby/object:Gem::Version
|
92
93
|
version: '0'
|
93
94
|
requirements: []
|
94
|
-
rubygems_version: 3.3.
|
95
|
+
rubygems_version: 3.3.7
|
95
96
|
signing_key:
|
96
97
|
specification_version: 4
|
97
98
|
summary: Intelligent search made easy with Rails and Elasticsearch or OpenSearch
|